Files
ComfyUI_frontend/apps/website/src/styles/global.css
Yourz 6d5fa743b3 fix: seamless SocialProofBar marquee loop (#12139)
*PR Created by the Glary-Bot Agent*

---

## Summary

The partner-logo marquee on the homepage `SocialProofBar` glitched on
every loop restart — a visible jump where the strip snapped back to the
start.

## Root cause

The previous implementation rendered all logos as siblings of a single
flex container and animated the track from `translateX(0)` to
`translateX(-50%)`. Because the `gap` utility inserts spacing between
every adjacent pair of items (including the seam between the two
duplicated halves), `-50%` of the total width does not equal the
distance from one half-start to the next half-start. The mismatch (`gap
/ 2`) is exactly what the eye sees as a jump.

## Fix

- Wrap each duplicated half in its own flex group.
- Place the two groups as siblings of an outer `flex w-max gap-X` track
with a gap that matches the inner gap.
- Animate each group by `translateX(calc(-100% - var(--marquee-gap)))`,
where `--marquee-gap` is set inline to the same value as the Tailwind
gap class.
- Scope the `animation` declaration to `@media (prefers-reduced-motion:
no-preference)` so reduced-motion users get a stable, non-animated
client list instead of the global "snap to 0.01ms" jump.

At `t = end`, the second group sits at `x = 0` — exactly where the first
group started — so the next animation cycle is visually
indistinguishable from the previous frame. The duplicate carries
`aria-hidden="true"` so screen readers don't read the client list twice.

## Verification

- `pnpm typecheck`, `pnpm format`, `npx eslint` on changed files: clean.
- Geometry verified at runtime on desktop (1440×900) and mobile
(390×844): copy widths match, second copy lands at `x = 0` at animation
end.
- New Playwright regression tests
(`apps/website/e2e/responsive.spec.ts`) pause the CSS animation, sample
bounding rects at `t=0` and `t≈duration`, and assert the seam invariant
— covering desktop forward, mobile forward, and mobile reverse marquees.
All 5 SocialProofBar tests pass on both `desktop` and `mobile` projects.
- Reduced-motion behavior verified in the browser: `animationName:
none`, `transform: none`, tracks at their natural positions.

Fixes FE-649

## Screenshots

![Desktop SocialProofBar marquee — continuous
strip](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/46e0ea18461e4d9b4410ff1bda9be669b44aeee095b2ac5a976280e1df3867dc/pr-images/1778515303716-158840bf-9182-4928-b095-c38f5284419b.png)

![Desktop marquee at the loop boundary — seamless, no visible
jump](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/46e0ea18461e4d9b4410ff1bda9be669b44aeee095b2ac5a976280e1df3867dc/pr-images/1778515304106-c46ab421-7214-4123-a4d0-e819af2e1b49.png)

![Mobile SocialProofBar — two stacked marquee
rows](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/46e0ea18461e4d9b4410ff1bda9be669b44aeee095b2ac5a976280e1df3867dc/pr-images/1778515304521-b0fe828f-90ba-4cf4-bb63-65be4d28f627.png)

![Reduced-motion fallback — stable static client
list](https://pub-1fd11710d4c8405b948c9edc4287a3f2.r2.dev/sessions/46e0ea18461e4d9b4410ff1bda9be669b44aeee095b2ac5a976280e1df3867dc/pr-images/1778515304824-6f3262ab-127c-46e4-bc04-6e7b8850545b.png)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12139-fix-seamless-SocialProofBar-marquee-loop-35d6d73d36508141b6ccf0167016b8c8)
by [Unito](https://www.unito.io)

Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
2026-05-11 18:36:40 +00:00

211 lines
4.6 KiB
CSS

@import 'tailwindcss';
@import '@comfyorg/design-system/css/base.css';
@font-face {
font-family: 'PP Formula';
src: url('/fonts/PPFormula-Light.woff2') format('woff2');
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'PP Formula';
src: url('/fonts/PPFormula-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'PP Formula';
src: url('/fonts/PPFormula-Medium.woff2') format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'PP Formula';
src: url('/fonts/PPFormula-Semibold.woff2') format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'PP Formula';
src: url('/fonts/PPFormula-Bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'PP Formula Narrow';
src: url('/fonts/PPFormula-NarrowSemibold.woff2') format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
}
@theme {
--color-site-dropdown: #332b38;
--color-primary-comfy-yellow: #f2ff59;
--color-primary-comfy-ink: #211927;
--color-primary-comfy-canvas: #c2bfb9;
--color-primary-warm-white: #f0efed;
--color-primary-warm-gray: #7e7c78;
--color-secondary-mauve: #4d3762;
--color-primary-comfy-plum: #49378b;
--color-secondary-cool-gray: #3c3c3c;
--color-illustration-forest: #20464c;
--color-transparency-white-t4: rgb(255 255 255 / 0.04);
--color-transparency-ink-t80: rgb(33 25 39 / 0.8);
--font-formula: 'PP Formula', sans-serif;
--font-formula-narrow: 'PP Formula Narrow', sans-serif;
--text-3\.5xl: 2rem;
--text-6\.5xl: 4.125rem;
--radius-4xl: 2rem;
--radius-4\.5xl: 2.25rem;
--radius-5xl: 2.5rem;
--container-8xl: 88rem;
--container-9xl: 96rem;
--container-10xl: 100rem;
--aspect-ratio-gallery-card: 47/31;
}
@property --border-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@keyframes border-angle-spin {
to {
--border-angle: 360deg;
}
}
@utility animate-border-spin {
animation: border-angle-spin 2s linear infinite;
background: conic-gradient(
from var(--border-angle),
color-mix(in srgb, var(--color-primary-comfy-yellow) 4%, transparent) 0%,
var(--color-primary-comfy-yellow) 100%
);
}
@keyframes marquee {
0% {
transform: translateX(0);
}
100% {
transform: translateX(calc(-100% - var(--marquee-gap, 0px)));
}
}
@keyframes marquee-reverse {
0% {
transform: translateX(calc(-100% - var(--marquee-gap, 0px)));
}
100% {
transform: translateX(0);
}
}
@utility animate-marquee {
@media (prefers-reduced-motion: no-preference) {
animation: marquee 30s linear infinite;
}
}
@utility animate-marquee-reverse {
@media (prefers-reduced-motion: no-preference) {
animation: marquee-reverse 30s linear infinite;
}
}
@keyframes ripple-effect {
0% {
transform: scale(1);
opacity: 1;
}
85% {
opacity: 1;
}
100% {
transform: scale(1.75);
opacity: 0;
}
}
@utility animate-ripple {
transform-origin: center;
transform-box: fill-box;
animation: ripple-effect 4s linear infinite;
}
@utility animate-delay-* {
animation-delay: --value([*]);
}
.crossfade-enter-active,
.crossfade-leave-active {
transition: opacity 0.3s ease;
}
.crossfade-enter-from,
.crossfade-leave-to {
opacity: 0;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
@utility scrollbar-none {
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
@utility icon-mask {
background-color: currentColor;
mask-size: contain;
mask-repeat: no-repeat;
mask-position: center;
}
/* PP Formula has asymmetric vertical metrics
Shifting visually with `top` corrects the appearance without changing the
inline-block's bounding box, so button/badge sizes are unaffected. */
@utility ppformula-text-center {
display: inline-block;
position: relative;
top: 0.19em;
}
/* Hide native play-button overlay iOS Safari shows when autoplay is blocked
(e.g. Low Power Mode). These are decorative background videos. */
video::-webkit-media-controls-start-playback-button,
video::-webkit-media-controls-panel {
display: none !important;
appearance: none;
}
:root {
--site-bg: var(--color-primary-comfy-ink);
--site-bg-soft: color-mix(in srgb, var(--site-bg) 88%, black 12%);
--site-border-subtle: rgb(255 255 255 / 0.1);
}