/* brand.css: the TROUT brand laid over the site, for the prototype only.
 *
 * Loaded after styles/site.css, so it wins on order alone. Three parts:
 *
 *   1. Tier 1 redefined. The old ramps keep their names and get the new
 *      values, so every token in tokens.css, and every component that names
 *      one, follows without being touched: mint becomes lime, the neutrals
 *      run from cream to ink, Intrade becomes Unbounded, Plex Sans becomes
 *      Plex Mono. This is the migration in miniature.
 *   2. Tier 2 where the new faces need different sizes. Unbounded is far
 *      wider than Intrade, so the headings step down a size.
 *   3. The brand's components (the Box, the Cursor, the Mark, the snippet,
 *      the Melt, the Rip) and the still-life hero.
 *
 * The brand, from TROUT_Digitized_X_2026_rev4.pdf: lime #D5FF3F, the only
 * accent, never a large fill; cream #EDE6D3, never pure white; ink #050404,
 * never pure black.
 */

/* 1. Tier 1 ---------------------------------------------------------------- */

:root {
  --color-lime: #d5ff3f;
  --color-cream: #ede6d3;
  --color-ink: #050404;

  /* Lime as a ramp, 500 the brand's. Tints and shades for states the deck
   * never draws. */
  --color-primary-50: #f8ffe2;
  --color-primary-100: #f1ffc4;
  --color-primary-200: #e8ff9d;
  --color-primary-300: #e0ff78;
  --color-primary-400: #daff5a;
  --color-primary-500: #d5ff3f;
  --color-primary-600: #a9cc31;
  --color-primary-650: #8fac29;
  --color-primary-700: #748d22;
  --color-primary-800: #55671a;
  --color-primary-900: #353f12;
  --color-primary-950: #171b09;

  /* Cream to ink, warm all the way down. 0 is cream too: never white. */
  --color-neutral-0: #f5f0e3;
  --color-neutral-50: #f1ebdc;
  --color-neutral-100: #ede6d3;
  --color-neutral-200: #d3ccba;
  --color-neutral-300: #b4ad9d;
  --color-neutral-400: #948e80;
  --color-neutral-500: #767165;
  --color-neutral-600: #57534b;
  --color-neutral-700: #2e2b28;
  --color-neutral-800: #201d1b; /* the surface: a step off ink, so a round button reads on the page */
  --color-neutral-900: #050404;
  --color-neutral-1000: #050404;

  --color-focus-lime: #d5ff3f;
  --color-focus-ink: #050404;

  --font-family-primary: "Unbounded", "Arial Black", Helvetica, Arial, sans-serif;
  --font-family-secondary: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;

  /* 2. Tier 2: sizes for the new faces --------------------------------- */

  --font-heading: var(--font-family-primary);
  --font-body: var(--font-family-secondary);

  --type-display: 800 2.5rem / 1 var(--font-heading);
  --type-h1: 800 2rem / 1.05 var(--font-heading);
  --type-h2: 800 1.625rem / 1.1 var(--font-heading);
  --type-h3: 800 1.25rem / 1.15 var(--font-heading);
  --type-h4: 800 1.0625rem / 1.2 var(--font-heading);
  --type-h5: 800 1rem / 1.25 var(--font-heading);
  --type-h6: 800 0.9375rem / 1.3 var(--font-heading);
  --type-display-tracking: 0;
  --type-h1-tracking: 0;
  --type-h2-tracking: 0;
  --type-h3-tracking: 0;
  --type-h4-tracking: 0;

  --type-body-xxl: 400 1.25rem / 1.5 var(--font-body);
  --type-quote: 400 1.25rem / 1.5 var(--font-body);
  --type-body-xl: 400 1.125rem / 1.55 var(--font-body);
  --type-body-lg: 400 1rem / 1.55 var(--font-body);
  --type-body: 400 0.9375rem / 1.55 var(--font-body);
  --type-body-sm: 400 0.75rem / 1.5 var(--font-body);
  --type-label: 500 0.75rem / 1.4 var(--font-body);
  --type-caption: 400 0.625rem / 1.4 var(--font-body);
  --type-label-tracking: 0.08em;
}

@media (min-width: 48rem) {
  :root {
    --type-display: 800 4.5rem / 1 var(--font-heading);
    --type-h1: 800 3rem / 1.05 var(--font-heading);
    --type-h2: 800 2.25rem / 1.08 var(--font-heading);
    --type-h3: 800 1.75rem / 1.12 var(--font-heading);
    --type-h4: 800 1.375rem / 1.15 var(--font-heading);
    --type-h5: 800 1.125rem / 1.2 var(--font-heading);
    --type-h6: 800 1rem / 1.25 var(--font-heading);

    --type-body-xxl: 400 1.5rem / 1.5 var(--font-body);
    --type-quote: 400 1.75rem / 1.4 var(--font-body);
    --type-body-xl: 400 1.25rem / 1.55 var(--font-body);
    --type-body-lg: 400 1.125rem / 1.55 var(--font-body);
    --type-body: 400 1rem / 1.55 var(--font-body);
    --type-body-sm: 400 0.875rem / 1.5 var(--font-body);
    --type-label: 500 0.8125rem / 1.4 var(--font-body);
    --type-caption: 400 0.75rem / 1.4 var(--font-body);
  }
}

/* The header keeps its shape; Plex Mono is wider than Plex Sans, so its
 * text steps down a size to keep the facts clear of the nav. */
@media (min-width: 64rem) and (max-width: 79.9375rem) {
  /* The name's second line is "Conference" alone until 80rem: in the mono
   * face "Digital Design Conference" is 240px, and below 80rem it ran into
   * the first menu link (found measuring the facts' size, 2026-10-30). */
  .site-header__wide {
    display: none;
  }
}

@media (min-width: 64rem) {
  /* tighter tracking on the facts, the owner's ask (2026-10-30), so a 16px
   * mono line takes less of the bar */
  .site-header__event,
  .site-header__fact {
    letter-spacing: -0.025em;
  }

  /* the bar's facts (name, dates, venue) at 16px, the owner's ask on
   * 2026-10-30 (15, then a little larger; 17 squeezed the menu links); checked beside the 16px links */
  .site-header {
    --site-header-info: 400 1rem / 1.35 var(--font-body);
  }

  /* the links at the shell's 16px (the owner's ask, 2026-10-30, after a
   * step down to 14 to clear the facts); the facts stay smaller */
  .site-header__nav a,
  .site-header__nav-trigger {
    font-size: 1rem;
  }
}

/* The phone's menu panel has the room the bar does not: its links go a size
 * up from the shell's 16px, and the date and venue at its head with them. */
@media (max-width: 63.9375rem) {
  .site-header {
    --site-header-info: 400 1rem / 1.45 var(--font-body);
  }

  .site-header__nav a,
  .site-header__nav-trigger {
    font-size: 1.25rem;
    letter-spacing: 0.02em;
  }

  /* the date and the place at the head of the menu, a size up again at
   * the owner's ask (2026-10-31), so they sit beside the 20px links */
  .site-header__panel-facts {
    font-size: 1.25rem;
    line-height: 1.4;
  }

  /* the discount pill beside Conference in the menu: centred on the
   * capitals rather than sat on their baseline, where it hung low beside
   * 20px type, and outlined in ink like the badge on the ticket button */
  .site-header__nav-badge {
    vertical-align: middle;
    translate: 0 -0.1em;
    box-shadow: 0 0 0 1.5px var(--color-ink);
  }

  /* the ticket button's badge hangs a little further in, clear of the
   * name now that the semibold "Book now" is wider */
  .site-header__cta .tag--hung {
    --tag-hang-inset: 1.75rem;
  }

  /* and the bar's own "Digitized Conference" beside the mark */
  .site-header__event {
    font-size: 1.0625rem;
    line-height: 1.3;
  }
}

/* The footer keeps its shape too, but not its lime ground: lime is "never
 * a large fill". Ink-dark, cream type, and the #D26 wordmark in lime, the
 * one lime thing in it. The cream sponsor band above still makes the edge
 * the reveal needs. */
.site-footer {
  --color-surface-brand: var(--color-neutral-800);
  --color-text-on-light: var(--color-cream);
  --color-icon-on-light: var(--color-cream);
  --color-divider: var(--color-neutral-700);
}

.site-footer__wordmark {
  color: var(--color-lime);
}

/* The footer's icons and logos under the pointer: a lime pad with the
 * icon in ink, the same fill the text links take, since a colour change
 * alone did not read (the owner, 2026-10-30). The pad is padding with a
 * matching negative margin, so nothing moves. Subscribe's envelope goes
 * ink with its text. */
/* the pad's room is taken at rest, padding against a matching negative
 * margin, so hover changes the colour and nothing moves (it was added on
 * hover alone and the icons jumped, 2026-10-30) */
.site-footer__social a,
.site-footer__partners a {
  border-radius: 0.3em;
  transition: background-color var(--motion-response), color var(--motion-response);
}

.site-footer__partners a {
  margin: -0.3em -0.5em;
  padding: 0.3em 0.5em;
}

.site-footer__social a {
  margin-inline: -0.4em;
  padding-inline: 0.4em;
}

.site-footer__social svg,
.site-footer__partners svg,
.site-footer__news svg {
  transition: color var(--motion-response);
}

@media (hover: hover) {
  .site-footer__social a:hover,
  .site-footer__partners a:hover {
    background: var(--color-lime);
    color: var(--color-ink);
    text-decoration: none;
  }

  .site-footer__social a:hover svg,
  .site-footer__partners a:hover svg,
  .site-footer__news a:hover svg {
    color: var(--color-ink);
  }
}

/* The inside pages' sky becomes the still life: six frames cut from the
 * hero's renderer, each from its own place round the table (straight on,
 * from the left and the right, close in, low, and from above; cut again
 * from the finished scene on 2026-10-31), one for each of the six the
 * page's head picks from at random on every load, so the pages differ and
 * a page differs from one visit to the next (the owner's ask). Darker than
 * the hero, since a page title sits on it. */
.sky {
  background: url("assets/still-1-1031b.webp") 50% 45% / cover no-repeat;
}

.sky::before {
  content: "";
  position: absolute;
  inset: 0;
  background: rgb(5 4 4 / 0.55);
}

html[data-sky="2"] .sky { background-image: url("assets/still-2-1031b.webp"); }
html[data-sky="3"] .sky { background-image: url("assets/still-3-1031b.webp"); }
html[data-sky="4"] .sky { background-image: url("assets/still-4-1031b.webp"); }
html[data-sky="5"] .sky { background-image: url("assets/still-5-1031b.webp"); }
html[data-sky="6"] .sky { background-image: url("assets/still-6-1031b.webp"); }

/* A page's title on a phone sizes to the screen: "CONFERENCE" in
 * Unbounded at the 40px display size is wider than a 360px phone. */
@media (max-width: 47.9375rem) {
  .section-head--page h1 {
    font-size: min(2.5rem, 9.2vw);
  }

  /* The organisers on About sit three across; in mono a long name steps
   * down a size, and a studio written as one word ("Niceandneat.Digital")
   * may break inside it rather than push past the screen's edge. */
  .organisers__profile {
    min-width: 0;
  }

  .organisers__people {
    gap: 0.5rem;
  }

  .organisers__studio {
    max-width: 100%;
    font-size: 0.6875rem;
    letter-spacing: -0.02em;
    overflow-wrap: anywhere;
  }

  .organisers__name {
    font-size: 0.75rem;
    letter-spacing: -0.02em;
  }
}

/* Backstage's title is a sentence of long capitals ("COLLABORATION" is
 * thirteen of them), so on a phone it sizes to the screen rather than
 * running off its edge. */
.backstage__head h1 {
  font-size: min(2.5rem, 8.2vw);
}

/* The home page's introduction at the original site's sizes, 24px on a
 * phone and 36px from 48rem, rather than the quote size the mono face had
 * stepped down to: it is the one paragraph the page leads with. */
.intro__text {
  font-size: 1.5rem;
  line-height: 1.45;
}

@media (min-width: 48rem) {
  .intro__text {
    font-size: 2.25rem;
    line-height: 1.4;
  }
}

/* The organisers' portraits are cut round by a clip path rather than by the
 * frame's overflow and radius alone: on the owner's machine the radius did
 * not clip the photograph (it showed square, the lime ground round it on
 * hover), which happens when the photograph is composited on its own layer
 * (the scale and the multiply). A clip path always cuts what it holds. The
 * site's wide shadow on hover is cut with it, which on this ground was
 * barely there. */
.organisers__frame {
  clip-path: circle(50%);
}

/* Unbounded is display only, and display is all caps. */
h1,
h2,
h3,
.cards__name,
.alumni__name {
  text-transform: uppercase;
}

/* The page's own title ends on the Cursor, the way a name does in the
 * frames: one Cursor a page (the owner's call, 2026-10-30; section titles
 * and card names had one each). */
.section-head--page h1::after {
  content: "";
  display: inline-block;
  width: 0.5em;
  height: 0.16em;
  margin-inline-start: 0.08em;
  background: var(--color-lime);
  vertical-align: baseline;
}

/* The date beside a title stays in the mono face, as the Box writes dates. */
.section-head__date {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.6em;
  letter-spacing: 0.04em;
  vertical-align: 0.25em;
}

/* The speakers' photographs take the frames' grade: black and white warmed
 * toward sepia, deep blacks. A filter in the prototype; for real, the crops
 * are regraded once from their source/ originals. */
.cards__photo,
.alumni__photo,
.popup:not(.popup--sponsor, .popup--partner) .popup__photo {
  filter: grayscale(1) sepia(0.35) contrast(1.18) brightness(0.86);
}

/* UNDERLINES. Plex Mono's strokes are heavier than Plex Sans's, and the
 * shell's 1px underlines read as hairlines under it. 2px under running
 * text (3px was too heavy beside the mono, the owner's call), a tenth of
 * the type under a title, set a quarter of an em below the baseline so it
 * clears the descenders. !important because the shell
 * states thickness in a dozen places, each at its own specificity, and the
 * demo's point is one rule; migrating, this becomes a token they all name. */
a,
a:hover,
a:focus-visible,
[aria-current="page"] {
  text-decoration-thickness: 2px !important;
  text-underline-offset: 0.25em !important;
}

/* Every text link (the venue line, the prose, the privacy page, the
 * footer's nav and Subscribe) fills lime with ink type under the pointer,
 * the way the pills do: a colour change alone was invisible on a link
 * already lime, and the owner liked the fill, 2026-10-30. Padding and a
 * matching negative margin keep the text where it was; the fill wraps
 * each line of a link that breaks. The footer's icons and logos are not
 * text and take their own fill below; the header's links keep the shell's
 * own hovers (the submenu's items are blocks, and the fill's margins moved
 * them). */
@media (hover: hover) {
  :is(a:where(:not([class])), .prose a, .doc a, .section-head__lede a, .site-footer__nav a, .site-footer__news a):where(:not(.site-header a, .site-footer__social a, .site-footer__partners a)):hover {
    margin-inline: -0.15em;
    padding-inline: 0.15em;
    border-radius: 0.2em;
    background: var(--color-lime);
    color: var(--color-ink);
    text-decoration-color: transparent !important;
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
  }
}

/* In a title the underline hugs the letters: a hairline right under the
 * capitals, the owner's call on 2026-10-30, after a lower, thicker line
 * opened a gap between the title's lines on hover. */
:is(h1, h2, h3) a {
  text-decoration-thickness: 0.04em !important;
  text-underline-offset: 0.02em !important;
}

/* A button's label never breaks: Plex Mono is wider than Plex Sans, and
 * "View conference" wrapped to two lines beside its title on a phone. */
.button {
  white-space: nowrap;
}

/* THE BOOKING CTAs (primary and secondary: Book now, Book workshop,
 * Register, Pre-booking) read heavier and a size up from the shell's: Plex
 * Mono at its medium weight is lighter on the eye than Plex Sans was, so
 * the action that sells has to say so. Semibold, and each size a step up. */
.button--primary,
.button--secondary {
  --button-font: 600 1rem / 1.25rem var(--font-body);
}

.button--lg:is(.button--primary, .button--secondary) {
  --button-font: 600 1.125rem / 1.5rem var(--font-body);
}

.button--xl:is(.button--primary, .button--secondary),
.popup .popup__action {
  --button-font: 600 1.375rem / 1.75rem var(--font-body);
}

@media (max-width: 47.9375rem) {
  .popup .popup__action,
  .button--xl:is(.button--primary, .button--secondary) {
    --button-font: 600 1.1875rem / 1.5rem var(--font-body);
  }

  /* Backstage's "Booking opens on November 23rd" is thirty characters of
   * mono: at 19px it is wider than a 360px phone. */
  .backstage__action.button--xl {
    --button-font: 600 0.9375rem / 1.25rem var(--font-body);
  }
}

/* The booking box's button at the header button's size (18px) at every
 * width: at the xl 22px "Book early bird 20% off" ran out of its box on
 * desktop, and it widened the whole column on a phone. The owner's
 * call, 2026-10-30. */
.cta-box .button--xl {
  --button-font: 600 1.125rem / 1.5rem var(--font-body);
}

/* The card rows' link inverts on hover the way the carousel's arrow does:
 * the brand fill with the ink on it, the arrow's own two tokens, instead of
 * the ghost button's 12% tint. The owner's call, 2026-10-30. Pointer only,
 * as every hover here is (base.css says why). */
@media (hover: hover) {
  .cards__action {
    --button-hover-background: var(--color-brand);
    --button-hover-color: var(--color-text-on-brand);
  }
}

/* The Events dropdown's row fills lime under the pointer or the keyboard,
 * the brand fill with the ink on it, as the pills and the card rows' link do
 * (the owner's call, 2026-10-30). The date goes to the ink with the name, so
 * the grey it wears at rest does not sit on the lime. */
@media (min-width: 64rem) {
  .site-header__nav .site-header__submenu a:is(:hover, :focus-visible) {
    background-color: var(--color-brand);
    color: var(--color-text-on-brand);
  }

  .site-header__nav .site-header__submenu a:is(:hover, :focus-visible) .site-header__submenu-when {
    color: inherit;
  }
}

/* THE HEADER'S ROW, on desktop: the link you are on and the link under the
 * pointer are filled lime with the ink on it, in place of the deep-mint
 * underline and colour, the owner's call on 2026-10-30. The fill is a
 * pseudo-element laid behind the text and pulled out past the link's box
 * (the row's links carry no padding of their own, and a real one would push
 * their neighbours), so nothing in the row moves by a pixel and the scramble
 * keeps shuffling the same text in the same box. The current page's fill is
 * a state of the page and is not transitioned; the pointer's fades in at the
 * button's own rate. The Events trigger is left as it was: its open dropdown
 * is its feedback. */
@media (min-width: 64rem) {
  .site-header__nav > ul > li > a {
    position: relative;
    isolation: isolate;
  }

  .site-header__nav > ul > li > a::before {
    content: "";
    position: absolute;
    inset: -0.45em -0.75em;
    z-index: -1;
    border-radius: 0.5em;
    background: var(--color-lime);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--motion-response);
  }

  .site-header__nav > ul > li > a[aria-current="page"] {
    color: var(--color-ink);
    text-decoration: none;
  }

  .site-header__nav > ul > li > a[aria-current="page"]::before {
    opacity: 1;
  }

  .site-header__nav > ul > li > a:focus-visible {
    color: var(--color-ink);
  }

  .site-header__nav > ul > li > a:focus-visible::before {
    opacity: 1;
  }
}

/* THE EVENTS TRIGGER is selected, the same lime fill, while the page you are
 * on is one of the three in its dropdown (the owner's ask, 2026-10-30). The
 * dropdown marks its current link with aria-current like any other, so the
 * group asks for it with :has() and no script. Same pseudo-element, same
 * reach past the box, so the chevron and its neighbours stay where they are. */
@media (min-width: 64rem) {
  .site-header__nav-trigger {
    position: relative;
    isolation: isolate;
  }

  .site-header__nav-trigger::before {
    content: "";
    position: absolute;
    inset: -0.45em -0.75em;
    z-index: -1;
    border-radius: 0.5em;
    background: var(--color-lime);
    opacity: 0;
    pointer-events: none;
  }

  .site-header__nav-group:has([aria-current="page"]) > .site-header__nav-trigger {
    color: var(--color-ink);
  }

  .site-header__nav-group:has([aria-current="page"]) > .site-header__nav-trigger::before {
    opacity: 1;
  }
}

@media (min-width: 64rem) and (hover: hover) {
  .site-header__nav > ul > li > a:hover {
    color: var(--color-ink);
  }

  .site-header__nav > ul > li > a:hover::before {
    opacity: 1;
  }
}

/* On a phone the card rows keep their link beside the title (the owner's
 * call, 2026-09-17), so with the wider faces the link's label steps down a
 * size to share the line: "Conference" and "View conference" with its
 * arrow have to fit the 358px of the narrowest phone together. */
@media (max-width: 47.9375rem) {
  .cards__action {
    font-size: 0.6875rem;
    letter-spacing: 0.04em;
  }

  .cards .section-head > h2 {
    font-size: 1.375rem;
  }

  /* A wrapping row rather than the shell's two-column grid, so a phone too
   * narrow for both (360px and under) drops the link under the title rather
   * than pushing it off the screen. The title's basis is its widest word, so
   * the row breaks only when the title and the link truly cannot share it. */
  .cards .section-head {
    display: flex;
    flex-wrap: wrap;
    align-items: first baseline;
    column-gap: var(--space-component-sm);
  }

  .cards .section-head > h2 {
    flex: 1 1 0;
    min-width: min-content;
  }

  .cards .section-head > .cards__action {
    margin-inline-start: auto;
  }

  .cards .section-head__lede {
    flex-basis: 100%;
  }
}

/* Unbounded is wide: the names and the workshop titles size to their card,
 * so the longest name (KONSTANTINIDIS, fourteen capitals) fits a phone's
 * two-across card and keeps its Cursor on its own last line. The card is the
 * container; 7.4% of its width, capped at the desktop size. */
.cards__card {
  container-type: inline-size;
}

.cards__name {
  font-size: min(1.3125rem, 7cqi);
}

.cards__title {
  font-size: clamp(1rem, 1.35vw, 1.1875rem);
  line-height: 1.2;
}

/* Where make.js has glued a title's last word into .name-end, the Cursor
 * hangs on that span instead of the title, so it travels with the word. */
.name-end {
  white-space: nowrap;
}

.section-head--page h1:has(.name-end)::after {
  display: none;
}

.section-head--page h1 .name-end::after {
  content: "";
  display: inline-block;
  width: 0.55em;
  height: 0.18em;
  margin-inline-start: 0.06em;
  background: var(--color-lime);
}

/* THE PROMO STRIP AND THE PILLS, in the Box's own voice: IBM Plex Mono
 * Medium, caps, +0.08em, a round dot for a bullet. The strip is the Box's
 * lime bar run along the edge, ink on lime, ink with lime on hover; a strip
 * is a bar, not the "large fill" the brand rules out. The discount badges
 * are small lime pills with ink type; the popup's link pills are outlined in
 * cream on ink, so lime stays for the one thing that is an offer. */
.ribbon {
  background: var(--color-lime);
  color: var(--color-ink);
  font: 500 0.75rem / 1 var(--font-body);
  letter-spacing: 0.08em;
}

@media (hover: hover) {
  .ribbon:hover {
    background: var(--color-ink);
    color: var(--color-lime);
  }
}

/* The dot is centred on the capitals rather than sat on the baseline: an
 * inline box with no text of its own lands on the baseline and reads low,
 * so each copy is a flex row and the dot is centred in it, then lifted to
 * the middle of the cap height, which sits above the middle of the line. */
.ribbon__copy {
  display: inline-flex;
  align-items: center;
}

.ribbon__dot {
  flex: none;
  width: 0.4375rem;
  height: 0.4375rem;
  margin-inline: 0.875rem;
  border-radius: 50%;
  background: currentColor;
  font-size: 0;
  translate: 0 -0.04em;
}

.tag {
  --tag-font: 500 0.6875rem / 1 var(--font-body);

  gap: 0.375em;
  padding: 0.3125rem 0.5rem 0.25rem;
  border-radius: var(--radius-pill);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.tag::before {
  content: "";
  width: 0.4375em;
  height: 0.4375em;
  border-radius: 50%;
  background: currentColor;
}

.tag--warning {
  background: var(--color-lime);
  color: var(--color-ink);
}

/* on the ticket button, whose ground is ink: an ink edge keeps the pill's
 * outline from melting into it */
.tag--hung {
  box-shadow: 0 0 0 1.5px var(--color-ink);
}

.popup__links .button--surface {
  /* the icon sized to the label, not the shell's 24px, so a pill with an
   * icon is as tall as one without */
  --button-icon-size: 1.0625rem;

  border: 1px solid color-mix(in srgb, var(--color-cream) 55%, transparent);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-cream);
  font: 500 0.75rem / 1.0625rem var(--font-body);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* hover: filled lime, ink type (the owner's call, 2026-10-30) */
@media (hover: hover) {
  .popup__links .button--surface:hover {
    border-color: var(--color-lime);
    background: var(--color-lime);
    color: var(--color-ink);
  }
}

/* 3. Components ------------------------------------------------------------ */

/* THE CARD HOVER is still to be chosen: the shell's own hover (the lift
 * and the shadow) is all a card does for now. The deck's speaker frames
 * (sepia, grain, a tube's glow) are being worked out against the owner's
 * photographs before anything ships again, 2026-10-30. */

/* THE BOX. A lime bar over an ink body with a thin cream outline, and a
 * lime shape at 22% 10px down and right behind it (the deck draws a second
 * outline there; the owner preferred a see-through fill, 2026-10-30). Body right-aligned,
 * cream; the last line lime italic. */
.box {
  --box-radius: 0.875rem;
  --box-offset: 10px;

  position: relative;
  isolation: isolate;
  min-width: min(22rem, 100%);
  font-family: var(--font-body);
  text-transform: uppercase;
}

.box::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  translate: var(--box-offset) var(--box-offset);
  border-radius: var(--box-radius);
  background: color-mix(in srgb, var(--color-lime) 22%, transparent);
}

.box__label {
  margin: 0;
  padding: 0.35em 1em 0.3em;
  border-radius: var(--box-radius) var(--box-radius) 0 0;
  background: var(--color-lime);
  color: var(--color-ink);
  font-weight: 500;
  font-size: 0.9em;
  letter-spacing: 0.08em;
}

.box__label::before {
  content: "";
  display: inline-block;
  width: 0.55em;
  height: 0.55em;
  margin-inline-end: 0.55em;
  border-radius: 50%;
  background: currentColor;
  vertical-align: 0.08em;
}

.box__body {
  display: grid;
  gap: 0.55em;
  padding: 1em 1.15em 1.1em;
  border: 1px solid color-mix(in srgb, var(--color-cream) 70%, transparent);
  border-top: 0;
  border-radius: 0 0 var(--box-radius) var(--box-radius);
  /* solid now: the shape behind it is a lime fill (the owner's call over
   * the deck's outline), and a see-through body tinted green over it */
  background: var(--color-ink);
  color: var(--color-cream);
  font-weight: 600;
  text-align: end;
}

.box__body p {
  margin: 0;
}

.box__last {
  color: var(--color-lime);
  font-style: italic;
}

/* On the site the Box drops the deck's last line, the address (the reader
 * is already on it), and the date takes the lime instead, upright: the
 * owner's call, 2026-10-30. */
.box__date {
  color: var(--color-lime);
}

/* THE BOX, PRESSED AND DRAGGED (box-drag.js). The Box is a thing you pick
 * up: on press the label and the body lift off, a little up and to the
 * left, away from the lime shape behind them, and the shape goes out (the
 * owner's call, 2026-10-31; it had pressed down onto the shape, a key
 * bottoming, and read as a button rather than a thing in the hand); while
 * held the whole Box follows the pointer; on release it stays where it was
 * left, the label and the body settle back down and the shape comes back
 * under them. For the design system: a draggable card lifts on press.
 *
 * Three layers so none fights another: the drag is a transform on the Box
 * itself, set by the script each frame and never transitioned (a transition
 * on it would trail the pointer); the press is a translate on the two
 * children, which is what eases; the tilt is a rotate on the Box, steered
 * by the drag's speed. The entrance animation (still-in) owns the Box's own
 * translate, so none of these touches it.
 *
 * The lift: 60% of the shape's offset (6px), 140ms, fast then settling.
 * The release: 260ms with a hair of overshoot past the rest position before
 * it settles, set down, not dropped. The shape fades over 90ms on the lift
 * and 180ms on the release, so it is gone as the Box rises and back once
 * it has landed. Under reduced motion the states are the same and nothing
 * eases. */
.box--drag {
  --box-dx: 0px;
  --box-dy: 0px;
  --box-tilt: 0deg;

  cursor: grab;
  transform: translate(var(--box-dx), var(--box-dy));
  rotate: var(--box-tilt);
  transition: rotate 260ms cubic-bezier(0.3, 1.4, 0.5, 1);
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  /* a thumb on the Box still scrolls the page: the script takes the gesture
   * only after a hold, by cancelling the touch's own move (see box-drag.js) */
  touch-action: pan-x pan-y;
}

.box--drag .box__label,
.box--drag .box__body {
  translate: 0 0;
  transition: translate 260ms cubic-bezier(0.3, 1.4, 0.5, 1);
}

.box--drag::after {
  transition: opacity 180ms ease-out;
}

.box--pressed {
  cursor: grabbing;
}

.box--pressed .box__label,
.box--pressed .box__body {
  translate: calc(var(--box-offset) * -0.6) calc(var(--box-offset) * -0.6);
  transition: translate 140ms cubic-bezier(0.2, 0, 0, 1);
}

.box--pressed::after {
  opacity: 0;
  transition: opacity 90ms ease-in;
}

/* While the drag's speed tilts the Box the rotate follows it with a short
 * lag, so it reads as weight rather than a twitch. */
.box--dragging {
  transition: rotate 120ms ease-out;
}

/* Once held, the Box paints over the title and the mark, since whoever
 * dropped it there put it on top; the grain stays over it, as over all the
 * rest of the frame. */
.still__box.box--held {
  z-index: 3;
}

@media (prefers-reduced-motion: reduce) {
  .box--drag,
  .box--drag .box__label,
  .box--drag .box__body,
  .box--drag::after {
    transition: none;
  }
}

/* THE CURSOR. A short lime block on the baseline after a name or the
 * wordmark. One per frame, never a vertical bar. Its size follows the type. */
.cursor {
  display: inline-block;
  width: 0.34em;
  height: 0.12em;
  margin-inline-start: 0.04em;
  background: var(--color-lime);
  vertical-align: baseline;
}

@media (prefers-reduced-motion: no-preference) {
  .cursor {
    animation: cursor-blink 1.1s steps(1) infinite;
  }

  @keyframes cursor-blink {
    50% {
      opacity: 0;
    }
  }
}

/* TYPING IN, scripts in demo/type.js. Every letter holds its room from the
 * first paint to the last and is only drawn once typed, so the title is laid
 * out exactly as it will finish and nothing on the page moves: removing the
 * untyped letters instead rebuilt the lines letter by letter, and on a phone
 * the title jumped. The title's own Cursor, and the hero's Rip, wait until
 * the typing is done. */
.type {
  position: relative;
}

.type__ch {
  transition: opacity 0.1s ease-out;
}

.type--waiting .type__ch,
.type--typing .type__ch:not(.type__ch--in) {
  opacity: 0;
}

/* Armed from the head, before the first paint, so a title is never seen
 * whole and then wiped: hidden until type.js has taken it over. The head's
 * own timeout drops the arming if the script never arrives. */
[data-type-state="armed"] :is(.still__word, .still__soft, .still__film, .section-head--page h1, .section-head h2, .cards__name, .still__snippet code),
.snip--waiting {
  visibility: hidden;
}

/* Hidden, not removed: the Cursor keeps its room while the title waits,
 * so the height held through the typing is the finished title's, and
 * nothing moves when the Cursor comes back. */
.type.type--waiting::after,
.type.type--typing::after,
.type--waiting .cursor,
.type--typing .cursor,
.type--waiting .name-end::after,
.type--typing .name-end::after {
  visibility: hidden;
}

.type--waiting .rip::after,
.type--typing .rip::after {
  display: none;
}

/* The caret lies over the title rather than in its text, so it moves
 * nothing; type.js sets where, on the last letter's baseline, and it glides
 * there rather than jumping. */
.type__caret {
  position: absolute;
  top: 0;
  left: 0;
  width: 0.34em;
  height: 0.12em;
  margin: -0.12em 0 0 0.04em;
  background: var(--color-lime);
  transform-origin: left bottom;
  transition: transform 0.09s ease-out;
  pointer-events: none;
}

/* The snippet types like a terminal: each character simply appears, and the
 * caret after the last one stays and blinks. */
.snip--typing .snip__ch:not(.snip__ch--in) {
  visibility: hidden;
}

.snip__caret {
  display: inline-block;
  width: 0.6em;
  height: 0.16em;
  margin-inline-start: 0.08em;
  background: var(--color-lime);
  vertical-align: baseline;
}

.snip__caret--idle {
  animation: cursor-blink 1.1s steps(1) infinite;
}

/* THE MARK. [d*26] at 60% cream, in the corner opposite the Box. */
.mark {
  position: relative;
  isolation: isolate;
  margin: 0;
  color: color-mix(in srgb, var(--color-cream) 60%, transparent);
  font: 800 1rem / 1 var(--font-heading);
  letter-spacing: 0.02em;
}

/* THE SNIPPET. Small mono, the last line lime. */
.snippet {
  margin: 0;
  color: color-mix(in srgb, var(--color-cream) 55%, transparent);
  font: 400 0.6875rem / 1.45 var(--font-body);
  white-space: pre;
}

@media (min-width: 64rem) {
  .snippet {
    font-size: 1rem;
  }
}

.snippet code {
  font: inherit;
}

.snippet__last {
  color: var(--color-lime);
}

/* THE TITLE EFFECT, as the deck draws it (read at 300dpi): crisp letters
 * with patches out of focus, as if parts of the word sat outside the lens's
 * depth of field, the blur spreading past the letters' edges in soft halos,
 * and film grain over the type. Plain CSS, five copies of the word in one
 * grid cell, from the bottom up:
 *
 *   the crisp copy, with the patches cut out of it;
 *   three blurred copies (near, mid, far, each blurred more than the one
 *   under it), each shown through a disc of every patch, the discs nested
 *   from the rim (near, a light blur, which thins out again in the core)
 *   to the core (far, a heavy one), so the
 *   focus falls off in steps that read as one slope, and the copies under a
 *   copy fill in what its blur thinned, so a core never goes hollow;
 *   the grain, a copy whose letters are cut from a noise tile and multiplied
 *   over the crisp one.
 *
 * It was an SVG filter, which iPhone Safari rasterises at low resolution,
 * and the title came out blocky; CSS blur and masks render at the screen's
 * own resolution everywhere. Each patch is one ellipse, stated once in em
 * of the title (--still-p1 to --still-p10, "rx ry at x y" from the copy's
 * padded corner), so the patches sit on the same letters at every size:
 * the deck's blurs sit on the D's left stem and counter, the G, the T's
 * left arm, the Z's diagonal, the E and the last D, and these do. The
 * copies share the geometry and differ only in the stops, so moving a
 * patch is one line. The word goes soft in the same places on every
 * visit, as a printed frame would.
 *
 * What it depends on: mask-image with several gradients composited by
 * intersection (mask-composite: intersect, or -webkit-mask-composite:
 * source-in in Safari before 15.4 and Chrome before 120), filter: blur(),
 * and for the grain background-clip: text, mix-blend-mode and :has(). The
 * whole effect sits behind an @supports for the first two, and the grain
 * behind one for :has(), so a browser without them draws the crisp copy
 * alone, a plain title: the blurred copies and the grain are display: none
 * until the @supports turns them on, and nothing is drawn half way. */
.still__soft,
.still__film {
  display: none;
}

.still__title {
  --still-bleed: 0.35em;

  /* the ellipses, in em of the title, from the top left of the word */
  --still-p1: 0.46em 0.5em at calc(var(--still-bleed) + 0.3em) calc(var(--still-bleed) + 0.45em);
  --still-p2: 0.22em 0.3em at calc(var(--still-bleed) + 0.6em) calc(var(--still-bleed) + 0.5em);
  --still-p3: 0.48em 0.5em at calc(var(--still-bleed) + 1.7em) calc(var(--still-bleed) + 0.5em);
  --still-p4: 0.2em 0.25em at calc(var(--still-bleed) + 2.45em) calc(var(--still-bleed) + 0.14em);
  --still-p5: 0.38em 0.32em at calc(var(--still-bleed) + 0.2em) calc(var(--still-bleed) + 1.1em);
  --still-p6: 0.46em 0.4em at calc(var(--still-bleed) + 1.72em) calc(var(--still-bleed) + 1.4em);
  --still-p7: 0.26em 0.28em at calc(var(--still-bleed) + 2.62em) calc(var(--still-bleed) + 1.06em);
  --still-p8: 0.42em 0.44em at calc(var(--still-bleed) + 3.45em) calc(var(--still-bleed) + 1.5em);
  --still-p9: 0.34em 0.22em at calc(var(--still-bleed) + 2.15em) calc(var(--still-bleed) + 1.64em);
  --still-p10: 0.28em 0.26em at calc(var(--still-bleed) + 3.28em) calc(var(--still-bleed) + 1.1em);

  /* the crisp copy's hole in each: clear to the first stop, whole from the second */
  --still-hole: transparent 46%, #000 80%;
}

@supports ((mask-composite: intersect) or (-webkit-mask-composite: source-in)) and (filter: blur(1px)) {
  /* The crisp copy: visible everywhere but the patches. Each layer is a
   * hole (clear at the centre, solid past the rim) and the layers
   * intersect, so two patches that overlap deepen the hole rather than
   * cancel, which is what exclude did. The sharp letter fades into the
   * blur rather than ending at it. */
  .still__word,
  .still__film {
    -webkit-mask-image:
      radial-gradient(var(--still-p1), var(--still-hole)),
      radial-gradient(var(--still-p2), var(--still-hole)),
      radial-gradient(var(--still-p3), var(--still-hole)),
      radial-gradient(var(--still-p4), var(--still-hole)),
      radial-gradient(var(--still-p5), var(--still-hole)),
      radial-gradient(var(--still-p6), var(--still-hole)),
      radial-gradient(var(--still-p7), var(--still-hole)),
      radial-gradient(var(--still-p8), var(--still-hole)),
      radial-gradient(var(--still-p9), var(--still-hole)),
      radial-gradient(var(--still-p10), var(--still-hole));
    -webkit-mask-composite: source-in;
    mask-image:
      radial-gradient(var(--still-p1), var(--still-hole)),
      radial-gradient(var(--still-p2), var(--still-hole)),
      radial-gradient(var(--still-p3), var(--still-hole)),
      radial-gradient(var(--still-p4), var(--still-hole)),
      radial-gradient(var(--still-p5), var(--still-hole)),
      radial-gradient(var(--still-p6), var(--still-hole)),
      radial-gradient(var(--still-p7), var(--still-hole)),
      radial-gradient(var(--still-p8), var(--still-hole)),
      radial-gradient(var(--still-p9), var(--still-hole)),
      radial-gradient(var(--still-p10), var(--still-hole));
    mask-composite: intersect;
  }

  /* The blurred copies: each shows through a disc of every patch, solid
   * to its first stop and gone at its second, the discs nested from the
   * rim to the core. The stops are written on the copy itself rather than
   * inherited, because a custom property resolves where it is declared and
   * a shared list would have taken the title's stops, not the copy's. The
   * blur is a share of the type, so a halo is the same size at every
   * width; the deck's heaviest is about a seventh of the cap height. */
  .still__soft {
    display: block;
    -webkit-mask-image:
      radial-gradient(var(--still-p1), var(--still-disc)),
      radial-gradient(var(--still-p2), var(--still-disc)),
      radial-gradient(var(--still-p3), var(--still-disc)),
      radial-gradient(var(--still-p4), var(--still-disc)),
      radial-gradient(var(--still-p5), var(--still-disc)),
      radial-gradient(var(--still-p6), var(--still-disc)),
      radial-gradient(var(--still-p7), var(--still-disc)),
      radial-gradient(var(--still-p8), var(--still-disc)),
      radial-gradient(var(--still-p9), var(--still-disc)),
      radial-gradient(var(--still-p10), var(--still-disc));
    mask-image:
      radial-gradient(var(--still-p1), var(--still-disc)),
      radial-gradient(var(--still-p2), var(--still-disc)),
      radial-gradient(var(--still-p3), var(--still-disc)),
      radial-gradient(var(--still-p4), var(--still-disc)),
      radial-gradient(var(--still-p5), var(--still-disc)),
      radial-gradient(var(--still-p6), var(--still-disc)),
      radial-gradient(var(--still-p7), var(--still-disc)),
      radial-gradient(var(--still-p8), var(--still-disc)),
      radial-gradient(var(--still-p9), var(--still-disc)),
      radial-gradient(var(--still-p10), var(--still-disc));
    filter: blur(var(--still-blur));
    text-shadow: none;
    pointer-events: none;
  }

  .still__soft--near {
    --still-disc: transparent 18%, #000 50%, #000 70%, transparent 95%;
    --still-blur: 0.028em;
  }

  .still__soft--mid {
    --still-disc: #000 30%, transparent 76%;
    --still-blur: 0.06em;
  }

  .still__soft--far {
    --still-disc: #000 36%, transparent 66%;
    --still-blur: 0.12em;
  }
}

/* The grain: the deck's film sits over the type, so a copy's letters are
 * cut from a noise tile (grey from mid to white, so it only ever darkens)
 * and multiplied over the crisp copy, which the title's own stacking
 * context keeps to the title. It takes the crisp copy's mask, so the grain
 * ends where the focus does. Not typed: it is hidden while the word types
 * and fades up once the word is whole, which needs :has(); without it the
 * grain is left out rather than drawn over letters not yet typed. */
@supports ((mask-composite: intersect) or (-webkit-mask-composite: source-in)) and selector(:has(*)) and (background-clip: text) {
  .still__film {
    display: block;
    color: transparent;
    background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='g'><feTurbulence type='fractalNoise' baseFrequency='1.1' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0.55 0 0 0 0.45 0.55 0 0 0 0.45 0.55 0 0 0 0.45 0 0 0 0 0.5'/></filter><rect width='100%' height='100%' filter='url(%23g)'/></svg>") 0 0 / 180px 180px;
    -webkit-background-clip: text;
    background-clip: text;
    mix-blend-mode: multiply;
    text-shadow: none;
    pointer-events: none;
    transition: opacity 0.5s ease-out;
  }

  .still__title:has(.type--waiting, .type--typing) .still__film {
    opacity: 0;
  }
}

/* the copies' Cursors hold their room and are never drawn: the crisp one
 * is the Cursor */
.still__soft .cursor,
.still__film .cursor {
  visibility: hidden;
  animation: none;
}

/* The WebGL title (demo/gl/, title-focus.js): its canvas lies over the
 * title, and once it has drawn, the HTML word goes transparent and keeps
 * only the layout, the Cursor and the Rip; without WebGL, or under reduced
 * motion, the word stays cream and nothing is laid over it. The CSS
 * effect's copies are not in that page's markup at all. */
.still__focus {
  position: absolute;
  display: block;
  pointer-events: none;
}

.still__title--focus .still__word {
  color: transparent;
  text-shadow: none;
}

/* The switch between the two title effects, home page and demo/gl/ only. */
.compare {
  position: fixed;
  right: 1rem;
  bottom: 7rem;
  z-index: 60;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--color-lime);
  border-radius: var(--radius-pill);
  background: var(--color-ink);
  color: var(--color-lime);
  font: 500 0.6875rem / 1 var(--font-body);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
}

@media (min-width: 64rem) {
  .compare {
    bottom: 1rem;
  }
}

/* THE RIP. One thin slice of the first line, cut at the baseline, shifted
 * sideways and blurred. A copy of the line, so the real one stays whole. */
.rip {
  position: relative;
}

.rip::after {
  content: attr(data-rip);
  position: absolute;
  inset: 0;
  clip-path: inset(70% 0 24% 0);
  translate: 0.05em 0;
  filter: blur(1.2px);
  color: var(--color-cream);
}

/* THE HERO: Styleframe 1 for the web. The still life in the canvas, the
 * snippet top left, the Box top right, DIGI / TIZED. bottom left, the mark
 * bottom right. -------------------------------------------------------------- */

.still {
  --still-pad: clamp(1rem, 4vw, 5rem);

  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: auto 1fr auto;
  place-items: stretch;
  min-height: 100svh;
  padding: var(--still-pad);
  padding-block-start: calc(var(--still-pad) + var(--site-header-height, 0px));
  /* The poster: a frame of the scene itself, shown from the first paint, so
   * the hero is never a black void while the scene builds (it waits for
   * the title to type, still-life.js) and the live canvas fades in over the
   * same picture. Cut from ?still at 1440x900 and 393x760; a phone takes
   * the tall one. A new cut takes a new file name (-MMDD): the server tells
   * browsers to keep an image for 30 days, and the old picture showed under
   * the new scene (2026-10-31); -1031b is the finished scene's, the same
   * frame the canvas draws first, so the swap does not show. -1031c
   * (2026-10-31) is cut taller, 1892 by 1600, and laid at the canvas's
   * width rather than to cover it: the camera holds its horizontal field
   * and shows more or less above and below as the hero's shape changes, and
   * a covering picture had been scaled and cropped differently, so the
   * table jumped when the canvas faded in. The inside pages' stills are
   * named the same way. */
  background:
    url("assets/poster-wide-1031c.webp") center / 100% auto no-repeat,
    radial-gradient(120% 90% at 60% 55%, #1b1512 0%, var(--color-ink) 70%);
  color: var(--color-cream);
}

/* a phone takes the tall poster */
@media (max-width: 47.9375rem) {
  .still {
    background-image:
      url("assets/poster-tall-1031b.webp"),
      radial-gradient(120% 90% at 60% 55%, #1b1512 0%, var(--color-ink) 70%);
    background-position: center 70%, center;
  }
}

.still::after {
  /* the fade into the page below, kept from the old hero, and a vignette */
  background:
    linear-gradient(to bottom, transparent, var(--color-ink));
  z-index: 1;
}

.still__scene {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  opacity: 0;
  transition: opacity 0.5s ease-out;

  /* the frames' grade: drained, warm, the blacks crushed a little */
  filter: saturate(0.72) sepia(0.18) contrast(1.12);
}

/* shown once the scanned models are in (still-life.js), so the canvas
 * fades in over the poster showing the same frame */
.still--dressed .still__scene {
  opacity: 1;
}

/* A desktop grades the frame in the renderer itself (still-life.js, the
 * grade pass: warmth, crushed blacks, vignette, grain), so the CSS grade
 * comes off the canvas and the page's grain is turned down to what the
 * title needs, or the two would double up. A phone keeps both. */
.still--graded .still__scene {
  filter: none;
}

.still--graded .still__grain {
  opacity: 0.3;
}

/* Grain and a vignette over the render, as the frames have: film, not a
 * game engine. An SVG noise tile, stepped so it crawls like film grain. */
.still__grain {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(130% 100% at 55% 45%, transparent 45%, rgb(5 4 4 / 0.85) 100%),
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='220' height='220'><filter id='g'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.93 0 0 0 0 0.9 0 0 0 0 0.83 0 0 0 0.55 0'/></filter><rect width='100%' height='100%' filter='url(%23g)'/></svg>");
  background-size: cover, 220px 220px;
  mix-blend-mode: overlay;
  opacity: 0.55;
}

@media (prefers-reduced-motion: no-preference) {
  .still__grain {
    /* still: the crawling grain, blended over the canvas, recomposited the
     * whole hero three times a second for ever, idle or not (2026-10-31) */
    animation: none;
  }

  @keyframes grain {
    0% { background-position: 0 0, 0 0; }
    33% { background-position: 0 0, -60px 40px; }
    66% { background-position: 0 0, 50px -70px; }
  }
}

.still__snippet,
.still__box,
.still__title,
.still__mark {
  position: relative;
  z-index: 2;
}

/* The pointer passes through the words to the table (2026-10-31): the
 * title lies over the head and the sweets, and every loose thing can be
 * picked up. The Box keeps the pointer, it is dragged on its own. For the
 * design system: type over a scene that can be touched lets the pointer
 * through. */
.still__snippet,
.still__title,
.still__mark {
  pointer-events: none;
}

/* The film grain lies over the title too, as it does in the deck's frames:
 * the letters are part of the photograph, not a layer on top of it. Above
 * the held Box as well (THE BOX, PRESSED AND DRAGGED), for the same reason. */
.still__grain {
  z-index: 4;
}

.still__snippet {
  grid-column: 1;
  grid-row: 1;
  align-self: start;
}

.still__box {
  grid-column: 2;
  grid-row: 1;
  align-self: start;
  justify-self: end;
  font-size: clamp(0.75rem, 1.15vw, 1.0625rem);
}

.still__title {
  grid-column: 1 / -1;
  grid-row: 3;
  align-self: end;
  margin: 0;
  font: 800 clamp(4.5rem, 11.8vw, 12.5rem) / 0.88 var(--font-heading);
  letter-spacing: -0.01em;
  color: var(--color-cream);
  text-shadow: 0 0.02em 0.25em rgb(5 4 4 / 0.45);
}

.still__title {
  display: grid;
  justify-items: start;
}

/* Every copy of the word in the one cell, each as wide as the word and no
 * wider, so a patch stated in em from the copy's corner lands on the same
 * letter at every width: a copy stretched to the cell had put the same
 * patch on the G at one width and on the I at another. The bleed is room
 * for a halo to spread past the letters' edges before the mask cuts it. */
.still__word {
  display: block;
}

.still__word,
.still__soft,
.still__film {
  grid-area: 1 / 1;
  width: max-content;
  padding: var(--still-bleed);
  margin: calc(-1 * var(--still-bleed));
}

/* A line keeps its Cursor: "TIZED" and the block after it never part, or
 * on a phone the Cursor wrapped to a third line the moment typing ended and
 * the title jumped up by a line. */
.still__line {
  display: block;
  white-space: nowrap;
}

.still__mark {
  grid-column: 2;
  grid-row: 3;
  align-self: end;
  justify-self: end;
  font-size: clamp(1rem, 1.6vw, 1.5rem);
  margin-block-end: 0.4em;
}

/* A phone: the Box under the snippet, full width, the title over the
 * bottom of the scene, the mark beside the Box rather than over the title. */
@media (max-width: 47.9375rem) {
  /* the header is a bar at the foot of the screen here, so the room it
   * needs is below the title rather than above the snippet */
  /* and the room between the Box and the title is a window onto the still
   * life, not the rest of the screen: the title had sat at the foot of a
   * full-height hero with most of a screen of scene above it */
  /* and the whole hero fits the screen that is visible with the browser's
   * bars showing (svh), less the promo strip above it, the title last and
   * clear of the menu bar at the foot: the scene takes whatever room is left between the Box and the
   * title, and none if there is none. */
  .still {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto minmax(0, 1fr) auto;
    gap: 1rem;
    min-height: calc(100svh - var(--ribbon-height, 1.75rem));
    padding-block: calc(var(--still-pad) + 1.75rem) 7.75rem;
  }

  /* The title sits over the still life rather than under it, as the deck's
   * title cuts across the table: the fade into the page is shorter, so the
   * table shows behind the letters, and the title keeps 24px clear of the
   * menu bar (the owner's ask, 2026-10-30). */
  .still::after {
    height: var(--space-section);
  }

  .still__snippet {
    font-size: 0.5625rem;
  }

  .still__box {
    grid-column: 1;
    grid-row: 2;
    justify-self: stretch;
    margin-inline-end: 10px;
    font-size: 0.75rem;
  }

  .still__title {
    grid-row: 4;
    font-size: clamp(3rem, 20vw, 7rem);
  }

  .still__mark {
    grid-column: 1;
    grid-row: 1;
    align-self: start;
  }
}

@media (prefers-reduced-motion: no-preference) {
  .still__box,
  .still__snippet,
  .still__mark {
    animation: still-in 1.2s cubic-bezier(0.2, 0.7, 0.2, 1) 0.5s backwards;
  }

  .still__title {
    animation: still-in 1.6s cubic-bezier(0.2, 0.7, 0.2, 1) 0.2s backwards;
  }

  @keyframes still-in {
    from {
      opacity: 0;
      translate: 0 0.5rem;
    }
  }
}

/* THE TREATED PHOTOGRAPH (card-fx.js): a canvas drawn once from the card's
 * picture, in the deck's speaker frame, sitting over the picture at the
 * picture's own overshoot and corners, shown on hover. Where a frame has
 * one, the earlier hover (the CSS filter and the .cards__fx layer) stands
 * down, since the canvas carries the grade, grain and vignette itself.
 * It arrives by a crossfade (the owner, 2026-10-30); data-card-fx-in on
 * <html> picks "dissolve" (the pixel grid) or "wipe" (the scanlines, top to
 * bottom in steps) instead, for comparison. Reduced motion gets the
 * crossfade whatever is set. */
.cards__treated {
  position: absolute;
  inset: -1px;
  width: calc(100% + 2px);
  height: calc(100% + 2px);
  z-index: 1;
  object-fit: cover;
  object-position: center;
  border-radius: var(--radius-card);
  opacity: 0;
  pointer-events: none;
}

.cards__frame[data-treated] .cards__fx {
  display: none;
}

/* The caption stays above the treated canvas. The canvas is z-index 1 over
 * the whole frame and the caption has none, so on hover it painted over the
 * name and the company. The scrim is the caption's own background, so the
 * lettering keeps its runway on the graded picture. */
.cards__frame[data-treated] .cards__caption {
  z-index: 2;
}

@media (hover: hover) {
  /* The photograph keeps its black and white grade under the pointer too:
   * the treated canvas crossfades over it, and with the grade dropped on
   * hover the colour photograph showed through for the length of the fade
   * (the owner, 2026-10-30). */
  .cards__link:hover .cards__treated {
    opacity: 1;
  }
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  /* a smooth crossfade in and out (the owner's ask, 2026-10-30: the pixel
   * dissolve was abrupt here); the dissolve and the wipe stay on
   * data-card-fx-in for comparison */
  .cards__treated {
    transition: opacity 0.6s ease;
  }

  [data-card-fx-in="dissolve"] .cards__frame[data-treated] .cards__treated {
    transition: none;
    -webkit-mask-size: 96px 96px;
    mask-size: 96px 96px;
    -webkit-mask-repeat: repeat;
    mask-repeat: repeat;
  }

  [data-card-fx-in="dissolve"] .cards__link:hover .cards__treated {
    animation: px-in 0.4s steps(1, end) forwards;
  }

  [data-card-fx-in="wipe"] .cards__treated {
    transition: none;
    -webkit-mask-image: none;
    mask-image: none;
    clip-path: inset(0 0 100% 0);
  }

  [data-card-fx-in="wipe"] .cards__link:hover .cards__treated {
    animation: wipe-in 0.45s steps(18, end) forwards;
  }

  @keyframes wipe-in {
    from { clip-path: inset(0 0 100% 0); }
    to { clip-path: inset(0); }
  }
}

@media (prefers-reduced-motion: reduce) {
  .cards__treated {
    transition: opacity 0.3s;
  }
}
