/* ---------------------------------------------------------------- */
/* Layers keep cascade order explicit without a preprocessor.       */
/* ---------------------------------------------------------------- */
@layer reset, tokens, base, layout, components, utilities;

@layer reset {
  *, *::before, *::after {
    box-sizing: border-box;
  }

  body, h1, h2, h3, p, ul {
    margin: 0;
  }

  ul {
    list-style: none;
    padding: 0;
  }

  img, svg {
    display: block;
    max-width: 100%;
  }
}

@layer tokens {
  :root {
    /* Colors — Figma collection "Colors", mode 1. Names kept verbatim so
       the mapping back to the design system stays greppable. */
    --background-primary: #f9faf5;
    --background-secondary: rgba(221, 221, 213, 0.4);
    --text-primary: #282922;
    --accent: #fff690;

    /* Figma draws this at 0.4, which fails AA on both the page and the
       row-hover accent (2.3:1 / 2.2:1). 0.7 is the first alpha that
       clears 4.5:1 on both — 0.64 only works if you forget the hover. */
    --text-secondary: rgba(40, 41, 34, 0.7);

    /* Text — Figma collection "Text", mode "Default". Figma stores line
       heights as percentages; they are unitless multipliers here. */
    --headings-font-size-h1: 47px;
    --headings-font-size-h2: 36px;
    --headings-font-size-h3: 27px;
    --headings-font-size-h4: 20px;
    --headings-font-size-h5: 15px;
    --headings-line-height-lg: 1.2;
    --headings-line-height-sm: 1;

    --body-font-size-sm: 12px;
    --body-font-size-md: 14px;
    --body-line-height-sm: 1.6;
    --body-line-height-md: 1.8;
    --body-letter-spacing-sm: 0.4px;
    --body-letter-spacing-md: 0;

    /* The hero and the nav pill track viewport width. Below the 1512
       artboard they hold the drawn sizes (hence the tokens as clamp
       minimums); above it, every size and gap grows by the same ratio —
       value ÷ 1512 × 100vw — so the title page and the pill keep their
       proportions on large displays. Capped at 1.6x, reached around
       2420px, so they stop short of becoming a billboard. */
    --hero-font-size-title: clamp(var(--headings-font-size-h1), 3.1085vw, 75.2px);
    --hero-font-size-sub: clamp(var(--body-font-size-md), 0.9259vw, 22.4px);
    --hero-font-size-name: clamp(var(--headings-font-size-h5), 0.9921vw, 24px);
    --hero-gap: clamp(48px, 3.1746vw, 76.8px);
    --hero-gap-text: clamp(16px, 1.0582vw, 25.6px);

    /* 18px is not a heading token; the pill is drawn between H5 and H4
       and the measured geometry (Figma 1:148, 332.6 × 49.6) only
       resolves at 18. Padding, gap and radius scale with the type so
       the shape does not change as the font grows. */
    --menu-font-size: clamp(18px, 1.1905vw, 28.8px);
    --menu-gap: clamp(10.8px, 0.7143vw, 17.28px);
    --menu-padding-block: clamp(14px, 0.9259vw, 22.4px);
    --menu-padding-inline: clamp(18px, 1.1905vw, 28.8px);
    --menu-item-padding-block: clamp(1.8px, 0.119vw, 2.88px);
    --menu-radius: clamp(10px, 0.6614vw, 16px);

    --display: 'PP Kyoto', Georgia, 'Times New Roman', serif;
    --mono: 'PP Right Grotesk Mono', ui-monospace, 'SF Mono', monospace;

    /* Side inset is always the 16px Figma gutter, including on wide
       screens. The column grows with the viewport up to 2400px and then
       centres; 1512 is the artboard the type is drawn at, not a cap. */
    --page-max: 2400px;
    --gutter: 16px;
    --column-gap: 8px;

    /* Breathing room above and below #about and #projects. */
    --section-padding-block: 128px;

    --duration-fast: 200ms;
    --duration-slow: 400ms;
    --ease: cubic-bezier(0.4, 0, 0.2, 1);
  }

  @font-face {
    font-family: 'PP Kyoto';
    src: url('../fonts/PPKyoto-Medium.woff2') format('woff2');
    font-weight: 500;
    font-style: normal;
    font-display: swap;
  }

  @font-face {
    font-family: 'PP Kyoto';
    src: url('../fonts/PPKyoto-MediumItalic.woff2') format('woff2');
    font-weight: 500;
    font-style: italic;
    font-display: swap;
  }

  @font-face {
    font-family: 'PP Kyoto';
    src: url('../fonts/PPKyoto-Extrabold.woff2') format('woff2');
    font-weight: 800;
    font-style: normal;
    font-display: swap;
  }

  @font-face {
    font-family: 'PP Right Grotesk Mono';
    src: url('../fonts/PPRightGroteskMono-Regular.woff2') format('woff2');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
  }

  @font-face {
    font-family: 'PP Right Grotesk Mono';
    src: url('../fonts/PPRightGroteskMono-RegularItalic.woff2') format('woff2');
    font-weight: 400;
    font-style: italic;
    font-display: swap;
  }

  /* Pangram Pangram ships this cut with `usWeightClass: 400`, same as the
     Regular — which is why Figma reports Body/Title as weight 400 under a
     style named Medium. The descriptor below overrides the file's own
     value so `font-weight: 500` actually selects it. */
  @font-face {
    font-family: 'PP Right Grotesk Mono';
    src: url('../fonts/PPRightGroteskMono-Medium.woff2') format('woff2');
    font-weight: 500;
    font-style: normal;
    font-display: swap;
  }
}

@layer base {
  html {
    color-scheme: light;
    scroll-behavior: smooth;
    /* Hoists the section view-timelines so the fixed nav, a sibling of
       `<main>`, can read them. */
    timeline-scope: --section-about, --section-projects, --section-contact;
  }

  body {
    background: var(--background-primary);
    color: var(--text-primary);
    font-family: var(--mono);
    font-size: var(--body-font-size-md);
    line-height: var(--body-line-height-sm);
    letter-spacing: var(--body-letter-spacing-md);
    -webkit-font-smoothing: antialiased;
  }

  h1, h2, h3 {
    font-family: var(--display);
    font-weight: 500;
  }

  a {
    color: inherit;
  }

  :focus-visible {
    outline: 1px dashed var(--text-primary);
    outline-offset: 3px;
  }
}

@layer layout {
  .hero,
  .about,
  .projects,
  .footer {
    margin-inline: auto;
    max-width: var(--page-max);
    padding-inline: var(--gutter);
    width: 100%;
  }

  /* Named view-timelines for the nav. A thin band low in the viewport
     so `#contact` still wins at the page end — the footer is shorter
     than the remaining `#projects` slice, and a mid-viewport band
     would light both. */
  .about,
  .projects,
  .footer {
    view-timeline-axis: block;
    view-timeline-inset: 70% 30%;
  }

  .about { view-timeline-name: --section-about; }
  .projects { view-timeline-name: --section-projects; }
  .footer { view-timeline-name: --section-contact; }

  .hero {
    align-items: center;
    display: flex;
    flex-direction: column;
    gap: var(--hero-gap);
    justify-content: center;
    /* `svh` is the area above Safari's overlay chrome, so the next
       section peeked under the address bar. `lvh` is the full screen
       (toolbars retracted); `vh` is the same value on engines that
       never got the small/large split. */
    min-height: 100vh;
    min-height: 100lvh;
    text-align: center;
  }

  .about {
    column-gap: var(--column-gap);
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    padding-block: var(--section-padding-block);
  }

  /* Only the stack needs placing: the role and bio auto-flow into columns
     1 and 2, and the visually-hidden heading is absolute, so it is not a
     grid item and does not consume a cell. */
  .about__stack {
    display: flex;
    flex-direction: column;
    gap: 64px;
    grid-column: 4;
  }

  .projects {
    display: flex;
    flex-direction: column;
    gap: 64px;
    /* Top is 128. The cream above the title is that plus about's
       matching 128, so 256 to the previous copy. Bottom would only
       be 128 + the footer's 16px gutter without this — short by
       a whole section pad. */
    padding-block: var(--section-padding-block);
    padding-block-end: calc(2 * var(--section-padding-block) - var(--gutter));
  }

  .footer {
    display: flex;
    flex-direction: column;
    gap: 64px;
    padding-block: var(--gutter);
  }

  .footer__links {
    column-gap: var(--column-gap);
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}

@layer components {
  /* Clipped rather than parked off-canvas: it is already in its final
     position, so revealing it is one property and it can never widen the
     document the way a negative offset can. */
  .skip-link {
    background: var(--text-primary);
    clip-path: inset(50%);
    color: var(--background-primary);
    inset: var(--gutter) auto auto var(--gutter);
    padding: 0.75rem 1rem;
    position: absolute;
    z-index: 100;

    &:focus {
      clip-path: none;
    }
  }

  /* -- Type roles lifted straight from the Figma text styles -- */

  /* Body/Title */
  .eyebrow {
    font-family: var(--mono);
    font-size: var(--body-font-size-sm);
    font-weight: 500;
    letter-spacing: var(--body-letter-spacing-sm);
    line-height: var(--body-line-height-md);
    text-transform: uppercase;
  }

  /* The `/ ''` is the alt text of the generated content: the dash is a
     bullet, so screen readers should read "Liquid", not "dash Liquid".
     Hanging indent keeps a wrapped line under the text, not under the
     dash — otherwise a parenthetical breaks as `embeddings,` / `hybrid)`. */
  .dash-list li {
    padding-inline-start: 2ch;
    text-indent: -2ch;
  }

  .dash-list li::before {
    content: '— ' / '';
  }

  /* Blank-line rhythm: paragraphs are separated by exactly one line. */
  .about__bio p + p,
  .project__description p + p {
    margin-block-start: 1.6em;
  }

  .stack-group .eyebrow {
    margin-block-end: 16px;
  }

  /* -- Hero -- */
  .hero__text {
    display: flex;
    flex-direction: column;
    gap: var(--hero-gap-text);
  }

  .hero__title {
    font-size: var(--hero-font-size-title);
    line-height: var(--headings-line-height-lg);
  }

  .hero__sub {
    font-size: var(--hero-font-size-sub);
  }

  /* Desktop keeps the subtitle on one line. Hidden with `display: none`
     rather than a visibility trick, so it does not leave a space. */
  .hero__sub-break {
    display: none;
  }

  .hero__name {
    font-family: var(--display);
    font-size: var(--hero-font-size-name);
    font-weight: 800;
    line-height: var(--headings-line-height-sm);
  }

  /* -- Links: default / hover / active / focus, per the Figma variants. -- */
  .link {
    align-items: center;
    display: inline-flex;
    font-family: var(--display);
    font-size: var(--headings-font-size-h5);
    font-weight: 500;
    line-height: var(--headings-line-height-sm);
    padding-block: 2px;
    text-decoration: none;
    transition: background-color var(--duration-fast) var(--ease);

    &:hover {
      background: var(--accent);
      font-style: italic;
    }

    &:active {
      background: none;
      font-style: italic;
    }
  }

  /* PP Kyoto's italic is ~3% narrower than its roman, so swapping cuts on
     hover shrank the element and slid everything after it — most visibly
     the items inside the nav pill. The zero-height ::before holds the roman
     label as an invisible sizer, so the grid column stays the wider of the
     two and nothing moves. Hidden from a11y by `visibility`. */
  .link__label {
    display: grid;
    justify-items: start;

    &::before {
      block-size: 0;
      content: attr(data-label);
      font-style: normal;
      overflow: hidden;
      visibility: hidden;
    }
  }

  /* The list items are flex containers so the inline-flex links do not sit
     on a text baseline — a line box added 14px of leading per item and
     pushed the list 42px past the drawn height. */
  .link-list {
    align-items: start;
    display: flex;
    flex-direction: column;

    & li {
      display: flex;
    }
  }

  .link-list--spaced {
    gap: 12px;
  }

  /* Temporary: the blog is not public yet. Hits, hover and keyboard
     focus are all off; the muted label uses the same token as pending
     project rows. Flex collapses the text-node space between the
     spans, so the gap has to be on the container. Drop this block
     (and `.link__soon`) when the blog URL is live — see Open. */
  .link--pending {
    cursor: default;
    gap: var(--column-gap);
    pointer-events: none;

    .link__label {
      text-decoration: line-through;
    }
  }

  /* `Connect` in the sign-off, the one mailto sitting inside running copy.
     It has to keep the type of whatever surrounds it — body mono in the bio,
     36px display in the footer — so it cannot use `.link`, which imposes the
     display face at 15px. Only the hover fill is borrowed. At rest it is
     deliberately indistinguishable from the words either side of it: the
     cursor is the affordance, and the footer's own email link is the
     discoverable path. Keyboard focus is the global `:focus-visible`. */
  .cta-connect {
    color: inherit;
    text-decoration: none;
    transition: background-color var(--duration-fast) var(--ease);

    &:hover {
      background: var(--accent);
    }
  }

  .link__soon {
    color: var(--text-secondary);
    font-family: var(--mono);
    font-size: var(--body-font-size-md);
    font-style: normal;
    font-weight: 400;
  }

  /* -- Projects table -- */
  .projects__title {
    font-size: var(--headings-font-size-h4);
    line-height: var(--headings-line-height-sm);
  }

  .project {
    border-block-start: 1px solid var(--text-primary);
  }

  /* 0fr → 1fr interpolates in Safari and Firefox; `interpolate-size`
     (0 → auto) is Chromium-only, which is why the old height slide
     snapped on iOS. Padding cannot live on the `0fr` child — it would
     floor the track at 128px when closed — so `.project__clip` is a
     padding-free shrink wrapper. */
  .project__panel {
    display: grid;
    grid-template-rows: 0fr;
    opacity: 0;
    transition: grid-template-rows var(--duration-slow) var(--ease),
                opacity var(--duration-slow) var(--ease);
  }

  .project[open] .project__panel {
    grid-template-rows: 1fr;
    opacity: 1;

    /* First frame of `[open]`: without this the panel is already 1fr
       when content-visibility flips on, and only opacity would fade. */
    @starting-style {
      grid-template-rows: 0fr;
      opacity: 0;
    }
  }

  .project__clip {
    min-height: 0;
    overflow: hidden;
  }

  /* Five columns for four cells: the trailing empty track is the gutter
     Figma draws on the right. Only `.row__acquired` occupies it. */
  .row {
    cursor: pointer;
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    list-style: none;
    padding-inline: var(--column-gap);
    transition: background-color var(--duration-fast) var(--ease);

    &::-webkit-details-marker {
      display: none;
    }

    &:hover {
      background: var(--accent);
    }
  }

  /* Cells are single-line by design: the table keeps a fixed row rhythm and
     long values ellipsize rather than reflowing it.
     (Native nesting has no Sass-style name concatenation — writing `&__cell`
     inside `.row` is invalid and silently drops the whole block.) */
  .row__cell {
    overflow: hidden;
    padding-block: 8px;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Rows with no ship date yet. The cell keeps the 14px / 1.6 strut of
     its neighbours so the row height and baseline do not move; the
     inner `<i>` is the acquired cut (12px italic, tracked, caps).
     Colour is the AA-safe secondary, not Figma's 0.4. */
  .row__status--pending {
    color: var(--text-secondary);
    text-transform: uppercase;
  }

  .row__status--pending i {
    font-size: var(--body-font-size-sm);
    font-style: italic;
    font-weight: 400;
    letter-spacing: var(--body-letter-spacing-sm);
  }

  /* Fifth-column tag. Body/sm italic uppercase, right-aligned, drawn at
     53px in Figma. Hidden below 900px: that column is dropped, and a
     fifth cell would wrap onto a second line. */
  .row__acquired {
    font-size: var(--body-font-size-sm);
    font-style: italic;
    letter-spacing: var(--body-letter-spacing-sm);
    line-height: var(--body-line-height-md);
    overflow: visible;
    text-align: end;
    text-transform: uppercase;
  }

  .project__body {
    column-gap: var(--column-gap);
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    padding-block: 64px;
  }

  .project__description {
    display: flex;
    flex-direction: column;
    gap: 64px;
    grid-column: 2;
  }

  /* The arrow is only for a live project URL. Figma 1:119: 15px icon
     slot (same as H5), 9px glyph inset 20%, 4px gap to the label.
     0.8em filled the slot with the artwork and looked a size too big. */
  .project__live {
    align-self: start;
    gap: 4px;
    grid-column: 4;
    justify-self: start;

    &::after {
      background: currentColor;
      block-size: 1em;
      content: '';
      flex: none;
      inline-size: 1em;
      mask: url('../img/arrow-external.svg') no-repeat center / 60%;
    }
  }

  /* Keep the panel painted while it collapses. Closing `<details>`
     would otherwise set `content-visibility: hidden` on the same
     frame, and the 1fr → 0fr transition would have nothing to animate. */
  .project::details-content {
    transition: content-visibility var(--duration-slow) var(--ease) allow-discrete;
  }

  /* -- Footer -- */
  .footer__menu {
    display: flex;
    flex-direction: column;
    gap: 16px;
  }

  .footer__sign {
    font-family: var(--display);
    font-size: var(--headings-font-size-h2);
    font-weight: 500;
    line-height: var(--headings-line-height-lg);
    padding-block-end: 96px;
    text-align: center;
  }

  /* -- Fixed section nav (Figma 1:148: 332.6 × 49.6) -- */
  .menu {
    backdrop-filter: blur(10px);
    background: var(--background-secondary);
    border-radius: var(--menu-radius);
    bottom: 32px;
    display: flex;
    gap: var(--menu-gap);
    left: 50%;
    padding: var(--menu-padding-block) var(--menu-padding-inline);
    position: fixed;
    translate: -50% 0;
    z-index: 60;
  }

  .menu__item {
    font-size: var(--menu-font-size);
    padding-block: var(--menu-item-padding-block);
    /* `text-decoration: underline` is a shorthand and would reset these;
       the keyframes therefore set `text-decoration-line` only. 1px is
       half the auto thickness Chrome paints on 18px Kyoto italic, which
       also collided with the j in Projects. */
    text-decoration-thickness: 1px;
    text-underline-offset: 2px;

    /* Later section wins: a second animation keyed to the *next*
       timeline paints the rest state back on, so a boundary that still
       intersects two sections never marks two links. */
    &[href="#about"] {
      animation: menu-current 1s linear, menu-rest 1s linear, menu-rest 1s linear;
      animation-range: cover;
      animation-timeline: --section-about, --section-projects, --section-contact;
    }

    &[href="#projects"] {
      animation: menu-current 1s linear, menu-rest 1s linear;
      animation-range: cover;
      animation-timeline: --section-projects, --section-contact;
    }

    &[href="#contact"] {
      animation: menu-current 1s linear;
      animation-range: cover;
      animation-timeline: --section-contact;
    }

    /* Animations beat `:hover` on `font-style`, so the rest-state
       override would freeze the previous item roman while you pointed
       at it. Background is not animated, so yellow comes from `.link`.
       Pointer-coarse gets no hover — a tap would otherwise stick the
       fill until the next tap elsewhere. */
    @media (hover: hover) and (pointer: fine) {
      &:hover {
        font-style: italic !important;
      }
    }
  }

  @media (hover: none), (pointer: coarse) {
    .link.menu__item:hover,
    .link.menu__item:active {
      background: none;
      font-style: unset;
    }
  }

  /* Current = italic + underline (Figma 1:189). Fill is `none`, so the
     styles exist only while the section occupies the spy line — `both`
     would freeze the last keyframe and leave every link marked after
     you scroll past. Yellow stays on `:hover` via `.link`. */
  @keyframes menu-current {
    0%, 100% {
      font-style: italic;
      text-decoration-line: underline;
    }
  }

  @keyframes menu-rest {
    0%, 100% {
      font-style: normal;
      text-decoration-line: none;
    }
  }

  /* -- Mobile top scrim (Figma mobile frame, node 1:288) -- */
  .scrim {
    background: linear-gradient(to bottom, var(--background-primary), rgba(249, 250, 245, 0));
    block-size: 64px;
    display: none;
    inset: 0 0 auto;
    pointer-events: none;
    position: fixed;
    z-index: 40;
  }
}

@layer utilities {
  .visually-hidden {
    block-size: 1px;
    clip-path: inset(50%);
    inline-size: 1px;
    overflow: hidden;
    position: absolute;
    white-space: nowrap;
  }

  .nowrap {
    white-space: nowrap;
  }

  /* Mobile — Figma node 1:224, 393px frame. */
  @media (width < 900px) {
    .scrim {
      display: block;
    }

    /* The nav pill sits at the bottom, so the mobile scrim is the only
       thing that can cover the top of an anchor target. */
    :target {
      scroll-margin-top: 64px;
    }

    .hero__title {
      font-size: var(--headings-font-size-h3);
    }

    .hero__sub-break {
      display: revert;
    }

    /* The three columns stack, and the section tightens up: #projects keeps
       the full 128px because the table needs the separation, #about does
       not. Bottom still equals the stacked cream above the title
       (about 64 + projects 128 − footer gutter). */
    .about {
      display: flex;
      flex-direction: column;
      gap: 128px;
      padding-block: 64px;
    }

    .projects {
      padding-block-end: calc(64px + var(--section-padding-block) - var(--gutter));
    }

    /* No tablet frame exists in Figma. At 393 this is wider than the
       viewport and does nothing; it only stops prose from running to a
       100-character line on the way up to the desktop breakpoint. */
    .about__bio,
    .project__description {
      max-inline-size: 62ch;
    }

    /* The index is content-width here; the remaining three cells share
       what is left, and the desktop gutter column is dropped. */
    .row {
      column-gap: 16px;
      grid-template-columns: auto repeat(3, minmax(0, 1fr));
    }

    .row__acquired {
      display: none;
    }

    /* The three value columns are 92px here and five cells overflow, but only
       the platform label slides. `Shopify Custom App` needs 131px and `Shopify
       Cus…` hides the word carrying the meaning; `Product mig…` and `God Save
       Qu…` you finish in your head, and the year only ever cuts to `2022 –
       Pres…`, where `Present` is the one thing it can say.

       The names were wrapped too, briefly, on the argument that "the row shows
       me what it cut" is a rule a visitor can infer while a hand-picked
       exception is invisible. Reverted, because a marquee is read over time —
       head first, tail second, whole string never — and row 009, the only one
       with two overflowing cells, then asked you to reconstruct two strings at
       once from two passes: `duct migrator — 2024 — fy Custom App`. With the
       name held still it stays an anchor and only one thing is in motion.
       Staggering them was the other way out, but delaying the platform would
       leave rows 010–012, where it is the only cut cell, doing nothing for six
       seconds.

       The motion is user-initiated, so WCAG 2.2.2 (Pause, Stop, Hide) does not
       apply, and closing the row stops it. Twice, not once and not forever: one
       pass is missed because the eye is on the panel opening underneath, and a
       loop would move beside the description for as long as it is being read.
       The 10% hold at the start keeps it clear of the panel's height animation.

       No `will-change`: it could only be set for the lifetime of `[open]`, which
       outlasts the animation, and the repo rule is to clear it at rest. */
    .row__kind {
      container-type: inline-size;
    }

    /* `display` is switched here rather than in the base rule so a closed row
       keeps an inline label and therefore keeps its ellipsis — an inline box
       cannot be transformed, an inline-block one does not ellipsize. */
    .project[open] .row__kind > .row__slide {
      animation: row-kind-reveal 6s ease-in-out 2;
      display: inline-block;
    }

    /* `100%` is the label's own width and `100cqw` its cell's, so the difference
       is exactly the overflow. `min()` pins labels that already fit to zero. */
    @keyframes row-kind-reveal {
      0%, 10% { transform: translateX(0); }
      45%, 70% { transform: translateX(min(0px, calc(100cqw - 100%))); }
      100% { transform: translateX(0); }
    }

    .project__body {
      display: flex;
      flex-direction: column;
      gap: 64px;
      padding-inline-end: 64px;
    }

    .footer {
      gap: 32px;
      padding-block: var(--gutter) 144px;
    }

    .footer__links {
      display: flex;
      flex-direction: column;
      gap: 64px;
    }

    .footer__sign {
      font-size: var(--headings-font-size-h3);
      padding-block: 64px 0;
    }

    .footer__legal {
      text-align: center;
    }

    .menu {
      bottom: 16px;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    html {
      scroll-behavior: auto;
    }

    *, *::before, *::after {
      animation-duration: 0.001ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.001ms !important;
    }

    /* Scroll-driven nav still needs a non-zero duration as the dummy the
       timeline replaces. This is a state change, not motion. */
    .menu__item {
      animation-duration: 1s !important;
    }
  }
}

/* Unlayered: Chromium ignores ::selection inside @layer (highlight
   origin). Hex, not custom properties — WebKit (Safari, iOS Chrome)
   does not resolve `var()` on this pseudo, so the system blue leaked
   through on phones. Ink stays --text-primary so the selection does
   not invert to white. */
::selection {
  background-color: #fff690;
  color: #282922;
}

::-moz-selection {
  background-color: #fff690;
  color: #282922;
}
