/* ============================================================================ HOME MOTION - the reveals the redesigned home page runs ---------------------------------------------------------------------------- Every animation in the Claude Design draft ("SHARP-ER Home v2 Dark"), carried over as classes so the page modules stay free of inline keyframes and raw values. Durations and easings are the motion tokens from semantic.css; the few long, decorative loops are expressed as multiples of them rather than as bare seconds, so retiming the system retimes the page. TWO KINDS OF MOTION LIVE HERE * entrance - time based, runs once from the server-rendered HTML (.sh-rise, .sh-bar-grow), staggered with .sh-rise--2..5 * scroll - scroll-driven (`animation-timeline`), so a band reveals as it enters the viewport and the top progress bar tracks the document WHY THE @supports GUARD: scroll-driven timelines ship in Chromium only. The from-state (opacity 0) is applied ONLY where `animation-timeline: view()` is understood, so Safari and Firefox render every band at rest, fully visible, instead of holding an invisible element forever. REDUCED MOTION: app.css already collapses every animation to 0.01ms. The scroll block is additionally gated on `prefers-reduced-motion: no-preference` so a reduced-motion visitor never gets a timeline-driven opacity ramp at all. DIRECTION: `transform-origin` and `linear-gradient()` have no logical keywords, so the progress bar and the brand stripe carry an explicit RTL override. These are the only physical directions in the file, and they exist because the property itself has no logical form - every box edge elsewhere is still ps-/pe-/start-/end-. ========================================================================= */ @layer theme, base, components, utilities; @layer components { /* ------------------------------------------------------------------------- ENTRANCE - hero content, first paint ---------------------------------------------------------------------- */ .sh-rise { animation: sh-rise var(--motion-duration-reveal) var(--motion-ease-entrance) var(--sh-rise-delay, 0ms) both; } /* The hero's cascade. The step is the system's fast duration, so the whole stagger retimes with the token instead of drifting on hard-coded ms. */ .sh-rise--2 { --sh-rise-delay: calc(var(--motion-duration-fast) * 0.75); } .sh-rise--3 { --sh-rise-delay: calc(var(--motion-duration-fast) * 1.5); } .sh-rise--4 { --sh-rise-delay: calc(var(--motion-duration-fast) * 2.25); } .sh-rise--5 { --sh-rise-delay: calc(var(--motion-duration-fast) * 3); } /* The brand stripe under the hero picture, drawn on from the inline start. */ .sh-bar-grow { transform-origin: left center; animation: sh-bar-grow var(--motion-duration-reveal) var(--motion-ease-entrance) calc(var(--motion-duration-fast) * 4) both; } [dir="rtl"] .sh-bar-grow { transform-origin: right center; } /* ------------------------------------------------------------------------- AMBIENT LOOPS - decorative, never carrying meaning ---------------------------------------------------------------------- */ /* The two soft washes behind the hero. Long, slow drifts: 22.5x and 30x the reveal duration (18s / 24s at the shipped token). */ .sh-glow { animation: sh-glow calc(var(--motion-duration-reveal) * 22.5) ease-in-out infinite; } /* A highlight travelling across the hero picture. */ .sh-sheen { background-size: 240% 100%; animation: sh-sheen calc(var(--motion-duration-reveal) * 8) linear infinite; } /* The live dot in the hero status pill. */ .sh-pulse { animation: sh-pulse calc(var(--motion-duration-reveal) * 2.75) ease-in-out infinite; } /* ------------------------------------------------------------------------- INTERACTION ---------------------------------------------------------------------- */ /* Tiles that rise a little under the pointer. No shadow: the draft's hover glow is deliberately left out. */ .sh-lift { transition: transform var(--motion-duration-base) var(--motion-ease-standard), border-color var(--motion-duration-base) var(--motion-ease-standard), background-color var(--motion-duration-base) var(--motion-ease-standard); } @media (hover: hover) { .sh-lift:hover { transform: translateY(-5px); } } /* An FAQ answer opening under its question. */ .sh-answer { animation: sh-answer var(--motion-duration-base) var(--motion-ease-entrance) both; } /* ------------------------------------------------------------------------- SCROLL DRIVEN - Chromium today, inert and harmless elsewhere ---------------------------------------------------------------------- */ @media (prefers-reduced-motion: no-preference) { @supports (animation-timeline: view()) { .sh-reveal { animation: sh-rise linear both; animation-timeline: view(); animation-range: entry 0% entry 70%; } /* Cards and pictures: the same reveal with a touch of scale. */ .sh-reveal--in { animation-name: sh-reveal-in; animation-range: entry 0% entry 80%; } } @supports (animation-timeline: scroll()) { /* The reading-progress rule across the top of the page. It is painted ONLY inside this @supports block: without a scroll timeline the bar would sit there permanently at full width, which is a rule the design never asked for. */ .sh-progress { background-image: linear-gradient( to right, var(--surface-accent), var(--text-accent) 55%, var(--surface-danger) ); transform-origin: left center; animation: sh-bar-grow linear both; animation-timeline: scroll(); } [dir="rtl"] .sh-progress { background-image: linear-gradient( to left, var(--surface-accent), var(--text-accent) 55%, var(--surface-danger) ); transform-origin: right center; } } } } @keyframes sh-rise { from { opacity: 0; transform: translateY(26px); } to { opacity: 1; transform: none; } } @keyframes sh-reveal-in { from { opacity: 0; transform: translateY(20px) scale(0.985); } to { opacity: 1; transform: none; } } @keyframes sh-bar-grow { from { transform: scaleX(0); } to { transform: scaleX(1); } } @keyframes sh-glow { 0%, 100% { opacity: 0.5; transform: translate3d(-4%, 0, 0) scale(1); } 50% { opacity: 0.85; transform: translate3d(6%, -3%, 0) scale(1.12); } } @keyframes sh-sheen { from { background-position: -160% 0; } to { background-position: 260% 0; } } @keyframes sh-pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.35; transform: scale(0.72); } } @keyframes sh-answer { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }