Skip to content

Commit 7700b1f

Browse files
authored
Merge pull request #59 from pras75299/fix/hero-animations-production
Fix/hero animations production
2 parents 9010340 + 5fc0e82 commit 7700b1f

21 files changed

Lines changed: 198 additions & 38 deletions

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,13 @@ entities.json
3333
# Local design/planning docs — keep on disk, do not version
3434
COMPONENT_PROMPTS.md
3535
backlogs.md
36+
37+
# Locally-installed agent skills (via `npx skills add ...`) — per-developer, do not version.
38+
# Whitelist project-tracked slash-commands so the intent stays explicit alongside the ignore.
39+
.agents/
40+
.claude/skills/
41+
!.claude/skills/debug-issue.md
42+
!.claude/skills/explore-codebase.md
43+
!.claude/skills/refactor-safely.md
44+
!.claude/skills/review-changes.md
45+
skills-lock.json

apps/www/components/ui/hero-iridescent-sweep.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ export function IridescentSweepBackground({
6666
mixBlendMode: "screen",
6767
opacity: 0.85,
6868
}}
69-
initial={false}
69+
// Anchor `initial` to 0deg — motion v12 skips repeat:Infinity loops if SSR bakes in the target frame.
70+
initial={{ rotate: 0 }}
7071
animate={cycle ? { rotate: 360 } : { rotate: 0 }}
7172
transition={cycle ? { duration: cycle, ease: "linear", repeat: Infinity } : undefined}
7273
/>

apps/www/components/ui/hero-liquid-aurora-mesh.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export function LiquidAuroraMeshBackground({
6262
</svg>
6363

6464
<div className="absolute inset-0" style={{ filter: `url(#${filterId})` }}>
65+
{/* Anchor `initial` to the first keyframe — motion v12 skips repeat:Infinity loops if SSR bakes in the target frame. */}
6566
<motion.div
6667
className="absolute inset-[-20%]"
6768
style={{
@@ -70,7 +71,7 @@ export function LiquidAuroraMeshBackground({
7071
radial-gradient(70% 70% at 50% 90%, ${c}88 0%, transparent 60%)`,
7172
mixBlendMode: "screen",
7273
}}
73-
initial={false}
74+
initial={{ rotate: 0, scale: 1 }}
7475
animate={cycle ? { rotate: [0, 360], scale: [1, 1.08, 1] } : { rotate: 0 }}
7576
transition={cycle ? { duration: cycle, ease: "linear", repeat: Infinity } : undefined}
7677
/>
@@ -80,7 +81,8 @@ export function LiquidAuroraMeshBackground({
8081
background: `conic-gradient(from 90deg at 50% 50%, ${a}55, ${b}55, ${c}55, ${a}55)`,
8182
mixBlendMode: "screen",
8283
}}
83-
initial={false}
84+
// Match `animate` when reduced so reduced-motion users land static at 0deg instead of spinning once.
85+
initial={cycle ? { rotate: 360 } : { rotate: 0 }}
8486
animate={cycle ? { rotate: [360, 0] } : { rotate: 0 }}
8587
transition={cycle ? { duration: cycle * 1.6, ease: "linear", repeat: Infinity } : undefined}
8688
/>

apps/www/components/ui/hero-logo-marquee.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ export function LogoMarqueeRow({
6262
<motion.ul
6363
aria-label={label}
6464
className="flex w-max items-center gap-12 whitespace-nowrap text-base font-medium text-white/55"
65-
initial={false}
65+
// Anchor `initial` to the first keyframe — motion v12 skips repeat:Infinity loops if SSR bakes in the target frame. Reduced motion freezes statically at 0%.
66+
initial={reduced ? { x: "0%" } : { x: xRange[0] }}
6667
animate={reduced ? { x: "0%" } : { x: xRange }}
6768
transition={
6869
reduced

apps/www/components/ui/hero-reference-pulse.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ export function ReferencePulseBackground({
7272
background: conicGradient,
7373
filter: "blur(80px)",
7474
}}
75-
initial={false}
75+
// Anchor `initial` to 0deg — motion v12 skips repeat:Infinity loops if SSR bakes in the target frame.
76+
initial={{ rotate: 0 }}
7677
animate={cycle ? { rotate: 360 } : { rotate: 0 }}
7778
transition={cycle ? { duration: cycle * 4, ease: "linear", repeat: Infinity } : undefined}
7879
/>

apps/www/public/r/hero-iridescent-sweep.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

apps/www/public/r/hero-liquid-aurora-mesh.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

apps/www/public/r/hero-logo-marquee.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"files": [
1313
{
1414
"path": "components/ui/hero-logo-marquee.tsx",
15-
"content": "\"use client\";\n\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\nconst DEFAULT_LOGOS = [\n \"Acme\",\n \"Globex\",\n \"Soylent\",\n \"Initech\",\n \"Hooli\",\n \"Umbrella\",\n \"Stark\",\n \"Wayne\",\n \"Cyberdyne\",\n \"Wonka\",\n \"Tyrell\",\n \"Aperture\",\n];\n\ntype LogoMarqueeRowProps = Omit<ComponentProps<\"div\">, \"children\"> & {\n /** Brand names rendered as the ticker items. Repeated to keep the loop seamless. */\n logos?: ReadonlyArray<string>;\n /** Loop duration in seconds. Lower = faster. Clamped to a positive number. */\n speed?: number;\n /** Scroll direction. */\n direction?: \"left\" | \"right\";\n /**\n * Accessible name for the marquee. Surfaces to screen readers as the\n * row's label (e.g. \"Customer logos\", \"Trusted by\"). Defaults to a\n * sensible generic so the row isn't anonymous in AT output.\n */\n label?: string;\n};\n\nexport function LogoMarqueeRow({\n logos = DEFAULT_LOGOS,\n speed = 32,\n direction = \"left\",\n label = \"Customer logos\",\n className,\n ...rest\n}: LogoMarqueeRowProps) {\n const reduced = useReducedMotion();\n const safeSpeed = Number.isFinite(speed) && speed > 0 ? speed : 32;\n\n // Two copies of the list keep the loop seamless: when the first copy has\n // moved by exactly its own width, the second copy is in the same spot.\n const items = [...logos, ...logos];\n const xRange = direction === \"left\" ? [\"0%\", \"-50%\"] : [\"-50%\", \"0%\"];\n\n return (\n <div\n className={cn(\n \"relative w-full overflow-hidden\",\n \"[mask-image:linear-gradient(to_right,transparent,black_12%,black_88%,transparent)]\",\n className,\n )}\n {...rest}\n >\n <motion.ul\n aria-label={label}\n className=\"flex w-max items-center gap-12 whitespace-nowrap text-base font-medium text-white/55\"\n initial={false}\n animate={reduced ? { x: \"0%\" } : { x: xRange }}\n transition={\n reduced\n ? undefined\n : { duration: safeSpeed, ease: \"linear\", repeat: Infinity }\n }\n >\n {items.map((logo, i) => (\n <li\n key={`${logo}-${i}`}\n className=\"flex shrink-0 items-center gap-3 text-xl tracking-tight\"\n aria-hidden={i >= logos.length || undefined}\n >\n <span className=\"inline-block h-2 w-2 rounded-full bg-white/30\" aria-hidden />\n {logo}\n </li>\n ))}\n </motion.ul>\n </div>\n );\n}\n\ntype LogoMarqueeHeroProps = Omit<ComponentProps<\"section\">, \"children\"> & {\n children?: ReactNode;\n /** Logos for the top row. */\n logos?: ReadonlyArray<string>;\n /** Optional second row. Defaults to a reversed copy of `logos`. Pass `null` to hide. */\n secondaryLogos?: ReadonlyArray<string> | null;\n /** Row scroll speed in seconds. */\n speed?: number;\n};\n\nexport function LogoMarqueeHero({\n children,\n logos = DEFAULT_LOGOS,\n secondaryLogos,\n speed = 32,\n className,\n ...rest\n}: LogoMarqueeHeroProps) {\n // `undefined` means \"use the reversed default\"; `null` means \"hide it\".\n const showSecondary = secondaryLogos !== null;\n const resolvedSecondary =\n secondaryLogos === undefined ? [...logos].reverse() : secondaryLogos;\n\n return (\n <section\n {...rest}\n className={cn(\n \"relative isolate flex min-h-[100svh] w-full flex-col items-center justify-center overflow-hidden bg-[#08080a] text-white\",\n className,\n )}\n >\n <div className=\"absolute inset-0 -z-10 bg-[radial-gradient(ellipse_at_top,_rgba(80,80,120,0.18)_0%,_transparent_60%)]\" />\n <div className=\"relative z-10 mx-auto flex w-full max-w-3xl flex-col items-center px-6 text-center\">\n {children ?? <DefaultMarqueeContent />}\n </div>\n <div\n data-slot=\"marquee\"\n className={cn(\n \"relative z-10 mt-12 flex w-full flex-col gap-6\",\n showSecondary ? \"sm:mt-16\" : \"sm:mt-20\",\n )}\n >\n <LogoMarqueeRow\n logos={logos}\n speed={speed}\n direction=\"left\"\n label=\"Customer logos\"\n />\n {showSecondary && resolvedSecondary && resolvedSecondary.length > 0 ? (\n <LogoMarqueeRow\n logos={resolvedSecondary}\n speed={speed * 1.15}\n direction=\"right\"\n label=\"More customer logos\"\n />\n ) : null}\n </div>\n </section>\n );\n}\n\nfunction DefaultMarqueeContent() {\n return (\n <>\n <motion.span\n initial={{ opacity: 0, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ delay: 0.05, type: \"spring\", stiffness: 120, damping: 18 }}\n className=\"mb-5 inline-flex items-center gap-2 rounded-full border border-white/15 bg-white/5 px-3 py-1 text-xs uppercase tracking-[0.18em] text-white/70 backdrop-blur\"\n >\n <span className=\"h-1.5 w-1.5 rounded-full bg-white/80\" aria-hidden />\n Trusted across teams\n </motion.span>\n <motion.h1\n initial={{ opacity: 0, y: 18 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ delay: 0.12, type: \"spring\", stiffness: 110, damping: 18 }}\n className=\"text-balance text-4xl font-semibold tracking-tight sm:text-6xl\"\n >\n The platform shipping teams ship with.\n </motion.h1>\n <motion.p\n initial={{ opacity: 0, y: 18 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ delay: 0.22, type: \"spring\", stiffness: 110, damping: 18 }}\n className=\"mt-5 max-w-xl text-pretty text-base text-white/65 sm:text-lg\"\n >\n From scrappy seed startups to public companies — UniqueUI components\n ship in production every day.\n </motion.p>\n </>\n );\n}\n",
15+
"content": "\"use client\";\n\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\nconst DEFAULT_LOGOS = [\n \"Acme\",\n \"Globex\",\n \"Soylent\",\n \"Initech\",\n \"Hooli\",\n \"Umbrella\",\n \"Stark\",\n \"Wayne\",\n \"Cyberdyne\",\n \"Wonka\",\n \"Tyrell\",\n \"Aperture\",\n];\n\ntype LogoMarqueeRowProps = Omit<ComponentProps<\"div\">, \"children\"> & {\n /** Brand names rendered as the ticker items. Repeated to keep the loop seamless. */\n logos?: ReadonlyArray<string>;\n /** Loop duration in seconds. Lower = faster. Clamped to a positive number. */\n speed?: number;\n /** Scroll direction. */\n direction?: \"left\" | \"right\";\n /**\n * Accessible name for the marquee. Surfaces to screen readers as the\n * row's label (e.g. \"Customer logos\", \"Trusted by\"). Defaults to a\n * sensible generic so the row isn't anonymous in AT output.\n */\n label?: string;\n};\n\nexport function LogoMarqueeRow({\n logos = DEFAULT_LOGOS,\n speed = 32,\n direction = \"left\",\n label = \"Customer logos\",\n className,\n ...rest\n}: LogoMarqueeRowProps) {\n const reduced = useReducedMotion();\n const safeSpeed = Number.isFinite(speed) && speed > 0 ? speed : 32;\n\n // Two copies of the list keep the loop seamless: when the first copy has\n // moved by exactly its own width, the second copy is in the same spot.\n const items = [...logos, ...logos];\n const xRange = direction === \"left\" ? [\"0%\", \"-50%\"] : [\"-50%\", \"0%\"];\n\n return (\n <div\n className={cn(\n \"relative w-full overflow-hidden\",\n \"[mask-image:linear-gradient(to_right,transparent,black_12%,black_88%,transparent)]\",\n className,\n )}\n {...rest}\n >\n <motion.ul\n aria-label={label}\n className=\"flex w-max items-center gap-12 whitespace-nowrap text-base font-medium text-white/55\"\n // Anchor `initial` to the first keyframe — motion v12 skips repeat:Infinity loops if SSR bakes in the target frame. Reduced motion freezes statically at 0%.\n initial={reduced ? { x: \"0%\" } : { x: xRange[0] }}\n animate={reduced ? { x: \"0%\" } : { x: xRange }}\n transition={\n reduced\n ? undefined\n : { duration: safeSpeed, ease: \"linear\", repeat: Infinity }\n }\n >\n {items.map((logo, i) => (\n <li\n key={`${logo}-${i}`}\n className=\"flex shrink-0 items-center gap-3 text-xl tracking-tight\"\n aria-hidden={i >= logos.length || undefined}\n >\n <span className=\"inline-block h-2 w-2 rounded-full bg-white/30\" aria-hidden />\n {logo}\n </li>\n ))}\n </motion.ul>\n </div>\n );\n}\n\ntype LogoMarqueeHeroProps = Omit<ComponentProps<\"section\">, \"children\"> & {\n children?: ReactNode;\n /** Logos for the top row. */\n logos?: ReadonlyArray<string>;\n /** Optional second row. Defaults to a reversed copy of `logos`. Pass `null` to hide. */\n secondaryLogos?: ReadonlyArray<string> | null;\n /** Row scroll speed in seconds. */\n speed?: number;\n};\n\nexport function LogoMarqueeHero({\n children,\n logos = DEFAULT_LOGOS,\n secondaryLogos,\n speed = 32,\n className,\n ...rest\n}: LogoMarqueeHeroProps) {\n // `undefined` means \"use the reversed default\"; `null` means \"hide it\".\n const showSecondary = secondaryLogos !== null;\n const resolvedSecondary =\n secondaryLogos === undefined ? [...logos].reverse() : secondaryLogos;\n\n return (\n <section\n {...rest}\n className={cn(\n \"relative isolate flex min-h-[100svh] w-full flex-col items-center justify-center overflow-hidden bg-[#08080a] text-white\",\n className,\n )}\n >\n <div className=\"absolute inset-0 -z-10 bg-[radial-gradient(ellipse_at_top,_rgba(80,80,120,0.18)_0%,_transparent_60%)]\" />\n <div className=\"relative z-10 mx-auto flex w-full max-w-3xl flex-col items-center px-6 text-center\">\n {children ?? <DefaultMarqueeContent />}\n </div>\n <div\n data-slot=\"marquee\"\n className={cn(\n \"relative z-10 mt-12 flex w-full flex-col gap-6\",\n showSecondary ? \"sm:mt-16\" : \"sm:mt-20\",\n )}\n >\n <LogoMarqueeRow\n logos={logos}\n speed={speed}\n direction=\"left\"\n label=\"Customer logos\"\n />\n {showSecondary && resolvedSecondary && resolvedSecondary.length > 0 ? (\n <LogoMarqueeRow\n logos={resolvedSecondary}\n speed={speed * 1.15}\n direction=\"right\"\n label=\"More customer logos\"\n />\n ) : null}\n </div>\n </section>\n );\n}\n\nfunction DefaultMarqueeContent() {\n return (\n <>\n <motion.span\n initial={{ opacity: 0, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ delay: 0.05, type: \"spring\", stiffness: 120, damping: 18 }}\n className=\"mb-5 inline-flex items-center gap-2 rounded-full border border-white/15 bg-white/5 px-3 py-1 text-xs uppercase tracking-[0.18em] text-white/70 backdrop-blur\"\n >\n <span className=\"h-1.5 w-1.5 rounded-full bg-white/80\" aria-hidden />\n Trusted across teams\n </motion.span>\n <motion.h1\n initial={{ opacity: 0, y: 18 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ delay: 0.12, type: \"spring\", stiffness: 110, damping: 18 }}\n className=\"text-balance text-4xl font-semibold tracking-tight sm:text-6xl\"\n >\n The platform shipping teams ship with.\n </motion.h1>\n <motion.p\n initial={{ opacity: 0, y: 18 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ delay: 0.22, type: \"spring\", stiffness: 110, damping: 18 }}\n className=\"mt-5 max-w-xl text-pretty text-base text-white/65 sm:text-lg\"\n >\n From scrappy seed startups to public companies — UniqueUI components\n ship in production every day.\n </motion.p>\n </>\n );\n}\n",
1616
"type": "registry:component",
1717
"target": "components/ui/hero-logo-marquee.tsx"
1818
}

0 commit comments

Comments
 (0)