Files

250 lines
7.1 KiB
CSS
Raw Permalink Normal View History

/* ============================================================================
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;
}
}