diff --git a/apps/web/DESIGN.md b/apps/web/DESIGN.md index 378f2f7..9d6c25e 100644 --- a/apps/web/DESIGN.md +++ b/apps/web/DESIGN.md @@ -248,6 +248,7 @@ Every rule, with the token or class it is about. Cite these IDs verbatim in code | MOTION-3 | Animate transform and opacity only | `transform`, `opacity` | | MOTION-4 | No client-side animation runtime | `package.json` | | MOTION-5 | Page transitions and reveals have one implementation each | `@view-transition`, `ui/Timeline` | +| MOTION-6 | Liquid glass parts share one press, unfold and type motion | `--duration-glass-*`, `lib/motion/` | | KICKOFF-1 | Join Q is its own visual system | `components/kickoff/` | | HACK-1 | Q-Hack x Hacktoberfest is its own campaign system | `components/hackathon/` | | TICKETS-1 | The 2027 ticket page is its own sales system | `components/tickets/` | @@ -341,7 +342,7 @@ Radii are small and functional. Buttons are nearly square (5px and 7px), cards a Shared patterns live in `global.css` as flat classes, and the `ui/` Astro components are style-less typed wrappers over those classes (see `PRIM-*` in [`AGENTS.md`](AGENTS.md)). Not every pattern has earned a class: `.button` is real and shipped, while the card is small enough that each component declares it, so for cards the tokens are the whole contract. -**COMP-1 Buttons are `.button` plus its documented variants** (`.is-alternate`, `.is-secondary`, `.is-small`, `.is-glass`) from `global.css`. No hand-styled anchor or button. `.is-glass` is the light liquid glass call to action for dark grounds (COMP-6): ice glass, navy ink, a small uppercase label and a trailing arrow (`.button-arrow`), a cyan halo on hover and focus. +**COMP-1 Buttons are `.button` plus its documented variants** (`.is-alternate`, `.is-secondary`, `.is-small`, `.is-glass`) from `global.css`. No hand-styled anchor or button. `.is-glass` is the liquid glass call to action for dark grounds such as the home hero video (COMP-6): clear glass with no color fill, the ground showing through a light blur held slightly darker so the white label reads on any frame, a lit edge, a specular rim and a soft cyan halo, a small uppercase label and a trailing arrow (`.button-arrow`); hover and focus brighten the glass and lift the halo, focus adds a white ring, and a press compresses it a little on the first frame, runs a light pulse from the touch point and springs it back on release (MOTION-6). **COMP-2 Cards use `--radius-card` and a `--color-neutral-lighter` border.** There is no shared `.card` class to inherit from, so this rule is the contract. @@ -364,7 +365,7 @@ The footer's column labels are deliberately not `.u-eyebrow`: they label a navig **COMP-4 Accordions are native `
`/``.** The shared open and close animation lives in `global.css`, once, for every accordion on the site, and so does the summary row itself (`.u-accordion-summary`) and the chevron (`src/components/icons/IconChevron.astro`). No JS widget replaces the native element. What stays with each accordion is what genuinely differs: its hairline, its label type, and the `[open]` rotation, which targets that accordion's own chevron box. -**COMP-6 The site header is the floating liquid glass bar on every page.** `layout/Nav.astro` is the one header, rendered once by `Base.astro`; there is no plain-bar variant and no page-local copy. It is the bar first built for the ticket page (TICKETS-1): inset from the viewport edges, light frosted glass (white at about half strength) with a strong blur and a still specular rim, dense enough that navy or purple shapes passing under it never cut the links' contrast, small uppercase chip links with a raised white chip for the current page, and on phones an opaque Q27 liquid sheet as the open menu. Over a dark ground (`.section.is-dark`, including the home video hero, or anything marked `data-nav-tone="dark"`) the bar frosts to a denser white fill so the navy logo and links keep their contrast; `nav-tone.client.ts` switches it, and CSS sets the frosted state for a page that opens on a dark section so the first paint is right. The header and `.button.is-glass` may use the `ticket-*` tokens they are built from (radius, Q27 blues, ticket durations); nothing else on conference pages may. Its geometry (`--site-header-height` and friends) lives on `:root` so a section that runs under the bar, like the home hero video, can pull itself up by exactly that height. +**COMP-6 The site header is the floating liquid glass bar on every page.** `layout/Nav.astro` is the one header, rendered once by `Base.astro`; there is no plain-bar variant and no page-local copy. It is the bar first built for the ticket page (TICKETS-1): inset from the viewport edges, clear liquid glass in the home Buy Now's material (a thin white fill over a strong blur that lifts brightness and saturation, so what passes under it shows as color and shape but never as a second line of text, a still specular rim, a caustic cyan lower edge and a soft cyan halo), small uppercase chip links with a raised white chip for the current page, and on phones a sheet of blue-tinted clear glass (the Q27 blues over a strong blur) as the open menu. The sheet's glass is its own layer (`.nav-glass`) that unfolds out of the menu button; the links are never scaled or clipped and only appear once the glass is open, the links leave before it folds back, and the icon turns on the same timeline (MOTION-6). The bar's own glass also sits on a layer (`.nav-bar-glass`), not on `.nav-inner`: an element with `backdrop-filter` is a backdrop root, and the menu's blur inside it would otherwise only see the bar, not the page. Over a dark ground (`.section.is-dark`, including the home video hero, or anything marked `data-nav-tone="dark"`) the bar frosts to a denser white fill so the navy logo and links keep their contrast; `nav-tone.client.ts` switches it, and CSS sets the frosted state for a page that opens on a dark section so the first paint is right. The header and `.button.is-glass` may use the `ticket-*` tokens they are built from (radius, Q27 blues, ticket durations); nothing else on conference pages may. Its geometry (`--site-header-height` and friends) lives on `:root` so a section that runs under the bar, like the home hero video, can pull itself up by exactly that height. PRIM-3's note that the home and program FAQs are "intentionally separate" is about their section layouts, which really are two designs (a two-column copy-plus-list band, and a single left-aligned column). It is not a license to re-copy the summary row inside them. @@ -382,6 +383,10 @@ Motion is small, fast, purposeful, and interruptible. The timing model follows t | Cross-document page transition | `--ease-snappy` | `--duration-page` 220ms | | Sliding in from an edge (sheet, drawer) | `--ease-buttery` | context | | Continuous loop (the partner marquee) | `linear` | the loop's own period | +| Glass press (MOTION-6) | `--ease-snappy` in, `--ease-glass-spring` back | `--duration-glass-press` 90ms, `--duration-glass-release` 420ms | +| Glass surface unfolding out of its trigger (MOTION-6) | clip `--ease-emphasized`, size `--ease-glass-spring` | `--duration-glass-unfold` 380ms, stagger `--duration-glass-stagger` 28ms | +| The same surface folding back | `--ease-emphasized` | `--duration-glass-fold` 220ms | +| Dynamic type, per word (MOTION-6) | `--ease-snappy` | `--duration-type-word` 150ms apart, each fading in `--duration-type-fade` 120ms | Exit is faster than enter, hover stays under 150ms, and nothing crosses 500ms. `linear` is for loops and for scroll-driven timelines whose progress is the scroll position rather than a clock: the partner-logo marquee, and the timeline's rail and reveal keyframes in `ui/Timeline.astro`. It is never the curve for a click-triggered transition, where it reads as robotic. @@ -416,6 +421,14 @@ Everything below is CSS. None of it ships an animation runtime, so none of it co A second component wanting a reveal extracts the shared utility at that point rather than copying Timeline's keyframes. `global.css` briefly carried a `.u-reveal` utility with no consumers while Timeline implemented its own; an extracted utility nothing extracts to is worse than no utility, so it was removed. +**MOTION-6 Liquid glass parts share one press, unfold and type motion.** The timings come from the team's reference videos (Apple's Liquid Glass demos), measured frame by frame, and live in the `--duration-glass-*`, `--duration-type-*` and `--ease-glass-spring` tokens. Every glass part uses the same three patterns, so new ones are composed rather than reinvented: + +- **Press.** Mark the element `data-glass-press` and give it a `.glass-light` child (it needs `position: relative` and a radius). `lib/motion/glass-press.ts`, booted once in `Base.astro`, sets `.is-pressed` for touch, mouse and Enter/Space alike and replays a light pulse from the touch point (`--press-x` / `--press-y`). The element gives a little on the first frame (`--duration-glass-press`) and springs back past its size on release (`--duration-glass-release`, `--ease-glass-spring`). Nothing waits for it: links navigate and buttons fire on the native event. Users: the home Buy Now (`.button.is-glass`), the header's menu button, the compare picker fields. +- **Unfold and fold.** A surface grows out of the control that opened it: it starts clipped to and squeezed into the trigger's box, the clip opens on `--ease-emphasized` while the size springs (`--ease-glass-spring`, a small overshoot that settles), a light runs across it, and its contents follow `--duration-glass-stagger` apart, invisible while squeezed so text is never seen distorted. Folding plays back into the trigger faster (`--duration-glass-fold`) and fades only at the end; the element is hidden only after the fold. Toggles are CSS transitions on a state attribute, so a quick second tap reverses mid-way; panels that move between places use the Web Animations API and cancel what is still running. Users: the header's phone menu (`data-sheet`), the compare pickers' list (`data-state`), What's included (`tickets.client.ts`). +- **Dynamic type.** `lib/motion/typewriter.ts` writes a short line word by word inside a surface that has just unfolded: each word fades up in `--duration-type-fade`, one every `--duration-type-word`, capped at about 0.7s per line; no cursor, no loop. Every word is laid out from the first frame, so nothing reflows; screen readers get the sentence once from a visually hidden copy; calling it again replaces a line still typing; reduced motion and no-JS show the plain text. Only for supporting text that appears in an interaction (the ticket description in What's included and in each compare column); never for prices, buttons, navigation, options or compare data, and never on scroll. + +Scale and clip stay on the glass surface itself, never on content that has to be compared or read in place: text sits on its own layer and appears only once its glass is open. A glass layer that has to blur what is behind it must not sit inside another element with `backdrop-filter` (a backdrop root); give the outer glass its own background layer instead. Each pattern has its reduced-motion escape (MOTION-2): no transition, no pulse, no typing. + Progressive enhancement is the rule, not an aspiration: the fallback for every one of these is the un-animated state, never hidden content. Never gate visibility on an animation that may not run. ## Kickoff @@ -436,7 +449,7 @@ The violet ramp is sampled from the Hack CI background artwork, which ships no w ## Tickets -`/ticket-categories/` is the 2027 ticket shop front, and it trades the conference minimalism for a calm, precise sales surface in the Q27 CI, built from liquid glass after the reference images the team picked. Liquid glass here means clear glass with a specular rim that catches light top left and bottom right, a caustic cyan glow along the lower inner edge, a little brightness and saturation lifted from what lies behind, and depth from light rather than from a flat fill (`.t-glass` in `tickets/ticket-theme.css`). A pale cool ground carries glowing light pools and Q bubbles; every ticket card is its own field of ice, cyan and Q27 blue light with Q bubbles inside, and the highlights sit on a glass panel that shows the light behind it blurred. A Q bubble is the original Q mark (`public/icon-512.png`) used as a mask and filled with flowing light; its rim is cut from the same mask, so the brand drawing is never redrawn, and it only floats, turns a few degrees and scales evenly, never stretched or skewed. Buttons are calm rounded rectangles (`--radius-ticket-button`) with a small uppercase label: buy buttons are navy glass with a lit rim, a Q27 blue inner glow and a trailing arrow; pressing one turns it light blue (`ticket-press`) with navy text, on touch, mouse and keyboard alike, and navigation is never delayed for the animation. Moving parts are glass too: a lens that glides to the picked tab and stretches as it moves (on phones the five tabs form a centered, symmetric three over two), glass carousel arrows and a small Q bubble marking the current ticket among the dots, the benefit meter as a glass tube whose glowing fill slides between tickets and carries a passing glint, and the compare pickers, whose open list (where the browser supports a styled select) is one flat frosted glass tone, dense enough that nothing behind it shows through, that settles in with a short stretch and marks options with raised glass chips, never with a color. Switching tickets under What's included plays one short entrance: the summary rises in, the benefit sheet slides up, and the rows follow in a quick cascade with their status icons popping in; statuses sit in one fixed-width column so icons align. The site header (COMP-6, first built for this page) is a floating bar of light frosted glass inset from the viewport edges: one flat tone with no gradient of its own, the page light showing through the blur, depth only from its lit edges, and small chip links; its open menu on phones is an opaque liquid surface in the Q27 blues, like the ticket cards, with light that turns slowly behind the links, so nothing of the page ever shows through the text. The page ends in a short glass fade into the footer (`.t-page-fade`), which replaces the footer's hairline on this page. The compare view is a product spec sheet in the CI: a glass sheet over the page light, navy ink and hairline rows, with the same buy buttons as the cards. The hero Q keeps its exact drawing: material comes only from light, a soft highlight and a shallow shadow, never from redrawing, tilting or distorting the mark. +`/ticket-categories/` is the 2027 ticket shop front, and it trades the conference minimalism for a calm, precise sales surface in the Q27 CI, built from liquid glass after the reference images the team picked. Liquid glass here means clear glass with a specular rim that catches light top left and bottom right, a caustic cyan glow along the lower inner edge, a little brightness and saturation lifted from what lies behind, and depth from light rather than from a flat fill (`.t-glass` in `tickets/ticket-theme.css`). A pale cool ground carries glowing light pools and Q bubbles; every ticket card is its own field of ice, cyan and Q27 blue light with Q bubbles inside, and the highlights sit on a glass panel that shows the light behind it blurred. A Q bubble is the original Q mark (`public/icon-512.png`) used as a mask and filled with flowing light; its rim is cut from the same mask, so the brand drawing is never redrawn, and it only floats, turns a few degrees and scales evenly, never stretched or skewed. Buttons are calm rounded rectangles (`--radius-ticket-button`) with a small uppercase label: buy buttons are navy glass with a lit rim, a Q27 blue inner glow and a trailing arrow; pressing one turns it light blue (`ticket-press`) with navy text, on touch, mouse and keyboard alike, and navigation is never delayed for the animation. Moving parts are glass too: a lens that glides to the picked tab and stretches as it moves (on phones the five tabs form a centered, symmetric three over two), glass carousel arrows and a small Q bubble marking the current ticket among the dots, the benefit meter as a glass tube whose glowing fill slides between tickets and carries a passing glint, and the compare pickers: a clear glass field in the home Buy Now's material that opens a listbox of its own (not the browser select, whose list flipped upward or could not be dismissed on some phones), always downward, easing the page down first when the list would not fit, closed again by a second tap, a tap outside or Escape without changing the pick; the list is one flat frosted glass tone, dense enough that nothing behind it shows through, unfolds out of the field and folds back into it (MOTION-6), and marks the picked ticket with a raised glass chip and a check, never with a color. Above the compare rows the two picked names stay pinned just under the site header, each at the left of its own column, on a clear glass bar that blurs the rows passing under it; the compare sheet keeps its fill and blur on a pseudo layer so it is no backdrop root and that blur reaches the rows. Switching tickets under What's included grows the summary card out of the tab just picked, runs a light across it and writes its description in, then unrolls the benefit sheet in place, and the rows follow in a quick cascade with their status icons popping in (MOTION-6); statuses sit in one fixed-width column so icons align. The site header (COMP-6, first built for this page) is a floating bar of clear liquid glass inset from the viewport edges, with small chip links; its open menu on phones is blue-tinted clear glass in the Q27 blues, with light that turns slowly inside it, blurred strongly enough that nothing of the page ever reads through the text. The hero's Compare tickets jump link and the pinned compare names are the same clear glass, with a hairline rim and a soft cyan halo. The page ends in a short glass fade into the footer (`.t-page-fade`), which replaces the footer's hairline on this page. The compare view is a product spec sheet in the CI: a glass sheet over the page light, navy ink and hairline rows, with the same buy buttons as the cards. The hero Q keeps its exact drawing: material comes only from light, a soft highlight and a shallow shadow, never from redrawing, tilting or distorting the mark. On phones the cards become a scroll-snap carousel with exactly one card at rest, buttons, dots and a `1 / 5` count; it never autoplays and never captures vertical scrolling. Tablets and laptops show three cards over two, centered; wide screens show all five in one row. Without JavaScript the carousel still swipes, every panel of What's included is shown, and every buy link works. diff --git a/apps/web/src/components/home/Hero.astro b/apps/web/src/components/home/Hero.astro index c23de0f..4075122 100644 --- a/apps/web/src/components/home/Hero.astro +++ b/apps/web/src/components/home/Hero.astro @@ -204,7 +204,9 @@ const { headline, tagline, announcement, cta } = Astro.props; data-ph-event={EVENTS.hero_cta_clicked} data-ph-prop-cta-label={cta.label} data-ph-prop-cta-href={cta.href} + data-glass-press > +