/* ==========================================================================
   SIM MASTER — CHROME: header, mobile drawer, footer, first-load intro
   ========================================================================== */

/* ---- Skip link --------------------------------------------------------- */

/*
  WCAG 2.4.1 Bypass Blocks (Level A). There are 14 header links plus two
  dropdowns before <main>, so without this a keyboard or screen-reader user
  tabs through the whole navigation on EVERY page before reaching content.

  It is the first focusable element in <body> and is lifted with the header by
  pagekit, so all 26 pages carry it. `--z-skip` (1000) was reserved for this in
  tokens.css and outranks --z-header, so the link cannot appear behind the
  sticky bar it sits in front of.

  Hidden by CLIPPING, never by `display:none` or `visibility:hidden` — both of
  those remove an element from the focus order, which would defeat the point.
  <main> already carries `id="main" tabindex="-1"`, so activating it moves real
  keyboard focus into the content rather than only scrolling the page.
*/
.skip-link {
    /*
      FIXED, not absolute. Absolute would pin it to the top of the DOCUMENT, so
      shift-tabbing back to it from halfway down a page would leave it off
      screen and rely on the browser's scroll-into-view. Fixed keeps it in the
      viewport at any scroll position. It is a SIBLING of <header>, not a
      child, so the header's backdrop-filter — which makes that element the
      containing block for fixed descendants (the bug that broke the mobile
      drawer, see CLAUDE.md 0.1.6) — cannot capture it.
    */
    position: fixed;
    top: var(--space-sm);
    left: var(--space-sm);
    z-index: var(--z-skip);
    padding: 0.75rem var(--space-md);
    background: var(--action);
    color: var(--color-white);
    border-radius: var(--radius-btn);
    font-family: var(--font-mono);
    font-size: var(--fs-caption);
    letter-spacing: var(--tracking-mono);
    text-transform: uppercase;
    text-decoration: none;
    white-space: nowrap;
    /* Clipped out of view but still focusable. */
    clip-path: inset(50%);
    transform: translateY(-150%);
    opacity: 0;
  }

  .skip-link:focus-visible,
  .skip-link:focus {
    clip-path: none;
    transform: translateY(0);
    opacity: 1;
    outline: 3px solid var(--focus-ring);
    outline-offset: 3px;
  }

  @media (prefers-reduced-motion: no-preference) {
    .skip-link {
      transition: transform var(--dur-base) var(--ease-out),
                  opacity var(--dur-base) var(--ease-out);
    }
  }

/* ---- Header ------------------------------------------------------------ */

.site-header {
    position: sticky;
    top: 0;
    z-index: var(--z-header);
    background: color-mix(in srgb, var(--surface-void) 88%, transparent);
    backdrop-filter: blur(14px) saturate(1.3);
    -webkit-backdrop-filter: blur(14px) saturate(1.3);
    border-bottom: 1px solid var(--line-faint);
    transition: background-color var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out);
  }

  .site-header[data-scrolled="true"] {
    background: color-mix(in srgb, var(--surface-void) 96%, transparent);
    border-bottom-color: var(--line-soft);
  }

  /* --------------------------------------------------------- header bar -- */

  /*
    Three-track grid: logo hard left, nav optically centred in the bar, actions
    hard right. A flex row with auto margins would centre the nav in the space
    LEFT OVER after the logo and actions, which drifts off-centre whenever
    those two differ in width (they always do). Equal 1fr side tracks make the
    centre track the true middle of the header regardless.
  */
  .header-bar__inner {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: var(--space-md);
    min-height: var(--header-h);
  }

  .brand {
    grid-column: 1;
    justify-self: start;
    display: block;
    line-height: 0;
    border-radius: var(--radius-sm);
  }

  .brand img {
    width: clamp(3.5rem, 2.6rem + 2.8vw, 5rem);
    height: auto;
    transition: transform var(--dur-base) var(--ease-spring);
  }

  .brand:hover img {
    transform: scale(1.05);
  }

  /* ---------------------------------------------------------------- nav -- */

  .nav {
    display: none;
    grid-column: 2;
    justify-self: center;
  }

  @media (min-width: 75rem) {
    .nav {
      display: block;
    }
  }

  .nav__list {
    display: flex;
    align-items: center;
    gap: 0.15rem;
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .nav__item {
    position: relative;
  }

  .nav__link {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.75rem 0.85rem;
    font-family: var(--font-display);
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    text-decoration: none;
    color: var(--text-secondary);
    white-space: nowrap;
    border-radius: var(--radius-sm);
    transition: color var(--dur-fast) var(--ease-out);
  }

  /* Racing-style underline that draws in from the left. */
  .nav__link::after {
    content: "";
    position: absolute;
    inset-inline: 0.7rem;
    bottom: 0.3rem;
    height: 2px;
    background: var(--color-cyan);
    transform: scaleX(0);
    transform-origin: left;
    transition: transform var(--dur-base) var(--ease-drive);
  }

  .nav__link:hover,
  .nav__link[data-active="true"] {
    color: var(--text-primary);
  }

  .nav__link:hover::after,
  .nav__link[data-active="true"]::after,
  .nav__trigger[aria-expanded="true"]::after {
    transform: scaleX(1);
  }

  .nav__trigger svg {
    transition: transform var(--dur-fast) var(--ease-out);
  }

  .nav__trigger[aria-expanded="true"] svg {
    transform: rotate(180deg);
  }

  .nav__menu {
    position: absolute;
    top: calc(100% + 0.5rem);
    left: 0;
    min-width: 15rem;
    padding: 0.4rem;
    background: var(--surface-panel);
    border: 1px solid var(--line-soft);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
  }

  /*
    Invisible bridge across the 0.5rem gap between the trigger and this menu.
    Without it the pointer leaves the hoverable area on the way down, fires
    mouseleave on the parent, and the menu closes out from under the cursor.
    It only exists while the menu is open, so it never blocks anything else.
  */
  .nav__menu::before {
    content: "";
    position: absolute;
    inset-inline: 0;
    bottom: 100%;
    height: 0.5rem;
  }

  .nav__menu[hidden] {
    display: none;
  }

  .nav__menu ul {
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .nav__menu a {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
    padding: 0.65rem 0.75rem;
    font-size: var(--fs-body-sm);
    color: var(--text-secondary);
    text-decoration: none;
    border-radius: var(--radius-sm);
    border-left: 2px solid transparent;
    transition:
      background-color var(--dur-fast) var(--ease-out),
      color var(--dur-fast) var(--ease-out),
      border-color var(--dur-fast) var(--ease-out);
  }

  .nav__menu a svg {
    opacity: 0;
    transform: translateX(-0.35rem);
    transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
  }

  .nav__menu a:hover,
  .nav__menu a:focus-visible {
    background: rgba(232, 19, 14, 0.08);
    border-left-color: var(--action-text);
    color: var(--text-primary);
  }

  .nav__menu a:hover svg,
  .nav__menu a:focus-visible svg {
    opacity: 1;
    transform: translateX(0);
  }

  .nav__menu a[aria-current="page"] {
    color: var(--action-text);
    border-left-color: var(--action-text);
  }

  /* Scrollspy (js/scrollspy.js) — while it runs, the lit dropdown item
     follows the section under the header (.is-current) rather than the
     static aria-current="page". */
  html.spy-on .nav__menu a[aria-current="page"]:not(.is-current) {
    color: var(--text-secondary);
    border-left-color: transparent;
  }

  html.spy-on .nav__menu a.is-current,
  html.spy-on .drawer__link--sub.is-current {
    color: var(--action-text);
    border-left-color: var(--action-text);
  }

  /* ------------------------------------------------------------ actions -- */

  .header-actions {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    grid-column: 3;
    justify-self: end;
  }

  /* Kept blue on purpose, like the active/hover states below: a small,
     persistent utility control that reads more calmly as its own colour
     than as another red-bordered element in the header. */
  .lang {
    display: flex;
    align-items: center;
    border: 1px solid rgba(0, 201, 255, 0.22);
    border-radius: var(--radius-sm);
    overflow: hidden;
  }

  .lang__btn {
    padding: 0.55rem 0.8rem;
    font-family: var(--font-mono);
    font-size: var(--fs-caption);
    font-weight: 500;
    letter-spacing: 0.12em;
    color: var(--text-muted);
    text-decoration: none;
    transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
  }

  .lang__btn + .lang__btn {
    border-left: 1px solid rgba(0, 201, 255, 0.12);
  }

  .lang__btn:hover {
    color: var(--text-primary);
    background: rgba(0, 201, 255, 0.1);
  }

  .lang__btn[aria-current="true"] {
    background: var(--color-cyan);
    color: var(--text-on-light);
  }

  /* ------------------------------------------------------------- burger -- */

  .burger {
    display: grid;
    place-items: center;
    width: 3.1rem;
    height: 3.1rem;
    border: 1px solid var(--line-soft);
    border-radius: var(--radius-sm);
    color: var(--text-primary);
  }

  @media (min-width: 75rem) {
    .burger {
      display: none;
    }
  }

  .burger__bars {
    display: grid;
    gap: 4px;
    width: 1.125rem;
  }

  .burger__bars i {
    display: block;
    height: 2px;
    background: currentColor;
    transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-fast) var(--ease-out);
  }

  .burger[aria-expanded="true"] .burger__bars i:nth-child(1) {
    transform: translateY(6px) rotate(45deg);
  }
  .burger[aria-expanded="true"] .burger__bars i:nth-child(2) {
    opacity: 0;
  }
  .burger[aria-expanded="true"] .burger__bars i:nth-child(3) {
    transform: translateY(-6px) rotate(-45deg);
  }

  /* ------------------------------------------------------------- drawer -- */

  .drawer {
    position: fixed;
    inset: 0;
    /* Below the header (--z-header) so the bar and its close button stay
       visible and clickable while the drawer is open. */
    z-index: var(--z-drawer);
    background: var(--surface-void);
    overflow-y: auto;
    overscroll-behavior: contain;
    /* nav.ts measures the header and sets --header-h-actual; the token is the
       fallback for the first frame and for the no-JS case. */
    padding-top: var(--header-h-actual, var(--header-h));
    animation: drawer-in var(--dur-base) var(--ease-out);
  }

  .drawer[hidden] {
    display: none;
  }

  @media (min-width: 75rem) {
    .drawer {
      display: none !important;
    }
  }

  @keyframes drawer-in {
    from {
      opacity: 0;
      transform: translateY(-0.75rem);
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .drawer {
      animation: none;
    }
  }

  .drawer__inner {
    padding: var(--space-lg) var(--gutter) var(--space-2xl);
  }

  .drawer__list {
    margin: 0 0 var(--space-xl);
    padding: 0;
    list-style: none;
  }

  .drawer__list > li + li {
    margin-top: 0.25rem;
    border-top: 1px solid var(--line-faint);
    padding-top: 0.25rem;
  }

  .drawer__link {
    display: block;
    padding: 0.85rem 0.25rem;
    font-family: var(--font-display);
    font-size: 1.375rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    text-decoration: none;
    color: var(--text-primary);
  }

  .drawer__link--sub {
    font-size: 1rem;
    font-weight: 600;
    color: var(--text-secondary);
    padding-block: 0.6rem;
    padding-inline-start: var(--space-md);
    border-left: 1px solid var(--line-soft);
  }

  .drawer__group-label {
    display: block;
    padding: 0.85rem 0.25rem 0.35rem;
    font-family: var(--font-mono);
    font-size: var(--fs-mono-label);
    letter-spacing: var(--tracking-mono);
    text-transform: uppercase;
    color: var(--text-accent);
  }

  .drawer__sublist {
    margin: 0 0 0.5rem;
    padding: 0;
    list-style: none;
  }

  .drawer__footer {
    display: grid;
    gap: var(--space-sm);
    padding-top: var(--space-lg);
    border-top: 1px solid var(--line-soft);
  }

  .drawer__contact {
    font-family: var(--font-mono);
    font-size: var(--fs-body-sm);
    color: var(--text-secondary);
    text-decoration: none;
  }

/* ---- Footer ------------------------------------------------------------ */

.site-footer {
    position: relative;
    isolation: isolate;
    padding-block: var(--space-2xl) var(--space-lg);
    background: linear-gradient(to bottom, var(--surface-base), var(--surface-inset));
    border-top: 1px solid var(--line-soft);
    overflow: hidden;
  }

  .site-footer__inner {
    display: grid;
    gap: var(--space-2xl);
    position: relative;
    z-index: 1;
  }

  @media (min-width: 60rem) {
    .site-footer__inner {
      grid-template-columns: minmax(14rem, 20rem) 1fr;
      gap: var(--space-3xl);
    }
  }

  /*
    Centred column: logo, tagline, contact info, then social — in that order,
    the owner's requested stacking. Everything else in the footer stays
    left-aligned; this is the one deliberately centred piece.
  */
  .footer-brand {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
  }

  .footer-brand__logo {
    display: inline-block;
    line-height: 0;
    border-radius: var(--radius-sm);
  }

  .footer-brand__logo img {
    width: 8rem;
    height: auto;
  }

  .footer-brand__tag {
    margin-top: var(--space-sm);
    font-family: var(--font-mono);
    font-size: var(--fs-mono-label);
    line-height: 1.5;
    letter-spacing: var(--tracking-mono);
    text-transform: uppercase;
    color: var(--text-muted);
  }

  /* Two independent lines rather than a dash-joined phrase. */
  .footer-brand__tag span {
    display: block;
  }

  /*
    Moved here from its own nav column so it reads as part of the brand block
    rather than a fifth link list. Reuses .footer-col__heading for the same
    small red label style as the columns beside it.
  */
  .footer-brand__contact-heading {
    margin-top: var(--space-lg);
  }

  .footer-brand__contact {
    display: grid;
    gap: 0.6rem;
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .footer-brand__contact a {
    font-size: var(--fs-body-sm);
    color: var(--text-secondary);
    text-decoration: none;
    transition: color var(--dur-fast) var(--ease-out);
  }

  .footer-brand__contact a:hover {
    color: var(--text-primary);
    text-decoration: underline;
    text-decoration-color: var(--line-strong);
  }

  .social {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-2xs) var(--space-sm);
    margin: var(--space-lg) 0 0;
    padding: 0;
    list-style: none;
  }

  .social a {
    display: inline-block;
    padding: 0.3rem 0.65rem;
    border: 1px solid var(--line-faint);
    border-radius: var(--radius-sm);
    font-family: var(--font-mono);
    font-size: var(--fs-mono-label);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    text-decoration: none;
    color: var(--text-secondary);
    transition:
      border-color var(--dur-fast) var(--ease-out),
      color var(--dur-fast) var(--ease-out),
      background-color var(--dur-fast) var(--ease-out);
  }

  .social a:hover {
    border-color: var(--action);
    color: var(--action-text);
    background: rgba(232, 19, 14, 0.08);
  }

  .footer-nav {
    display: grid;
    gap: var(--space-xl) var(--space-lg);
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  }

  .footer-col__heading {
    margin-bottom: var(--space-sm);
    font-family: var(--font-mono);
    font-size: var(--fs-mono-label);
    font-weight: 500;
    letter-spacing: var(--tracking-mono);
    text-transform: uppercase;
    color: var(--text-accent);
  }

  .footer-col ul {
    display: grid;
    gap: 0.6rem;
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .footer-col a {
    font-size: var(--fs-body-sm);
    color: var(--text-secondary);
    text-decoration: none;
    transition: color var(--dur-fast) var(--ease-out);
  }

  .footer-col a:hover {
    color: var(--text-primary);
    text-decoration: underline;
    text-decoration-color: var(--line-strong);
  }

  /* Opening-hours column: a day/hours pair list styled to match the link
     columns beside it (same gap, same body-sm size) without pretending the
     rows are links. */
  .footer-hours {
    display: grid;
    gap: 0.6rem;
    margin: 0;
    padding: 0;
  }

  .footer-hours > div {
    display: flex;
    justify-content: space-between;
    gap: var(--space-sm);
    font-size: var(--fs-body-sm);
  }

  .footer-hours dt {
    color: var(--text-secondary);
  }

  .footer-hours dd {
    margin: 0;
    color: var(--text-primary);
    font-variant-numeric: tabular-nums;
    text-align: right;
    white-space: nowrap;
  }

  .footer-hours dd[data-closed] {
    color: var(--action-text);
  }

  .footer-legal {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-sm);
    position: relative;
    z-index: 1;
    margin-top: var(--space-2xl);
    padding-top: var(--space-md);
    border-top: 1px solid var(--line-faint);
    font-family: var(--font-mono);
    font-size: var(--fs-mono-label);
    letter-spacing: 0.06em;
    color: var(--text-muted);
  }

  .footer-top {
    color: var(--text-accent);
    text-decoration: none;
    text-transform: uppercase;
    letter-spacing: var(--tracking-mono);
  }

  .footer-top:hover {
    color: var(--color-white);
  }

/* ---- Intro ------------------------------------------------------------- */

/* ------------------------------------------------------------ overlay -- */

  .intro {
    position: fixed;
    inset: 0;
    z-index: var(--z-intro);
    display: grid;
    place-items: center;
    isolation: isolate;
    overflow: hidden;
    /* Stops touch scrolling the page underneath on mobile (CLAUDE.md §12). */
    touch-action: none;
    overscroll-behavior: contain;
  }

  /* The dialog receives focus on open purely to anchor the focus trap — it is
     a container, not a control, so it should not paint a ring. */
  .intro:focus {
    outline: none;
  }

  /* Without JS the intro can never be dismissed, so it is never shown. */
  .no-js .intro {
    display: none;
  }

  /* Set by the blocking head script when this session already saw the intro:
     the overlay is removed from the very first paint. */
  .intro-done .intro {
    display: none;
  }

  /* The opaque backdrop that the flag wipes away, left to right. */
  .intro__veil {
    position: absolute;
    inset: 0;
    z-index: -2;
    background:
      radial-gradient(ellipse 70% 60% at 50% 45%, #0b1c21 0%, transparent 70%),
      linear-gradient(to bottom, var(--surface-void), #000 60%, var(--surface-void));
    /*
      The mask is 3× the viewport wide and split into thirds: fully
      transparent for the first third, a soft ramp through the middle, fully
      opaque for the last third. At mask-position 100% the viewport sees only
      the opaque third; at 0% it sees only the transparent third. Animating
      between the two therefore dissolves the backdrop cleanly left→right,
      with no bleed at either end.
    */
    -webkit-mask-image: linear-gradient(
      90deg,
      transparent 0 33.333%,
      rgba(0, 0, 0, 0.55) 48%,
      rgba(0, 0, 0, 0.92) 60%,
      #000 66.667% 100%
    );
    mask-image: linear-gradient(
      90deg,
      transparent 0 33.333%,
      rgba(0, 0, 0, 0.55) 48%,
      rgba(0, 0, 0, 0.92) 60%,
      #000 66.667% 100%
    );
    -webkit-mask-size: 300% 100%;
    mask-size: 300% 100%;
    -webkit-mask-position: 100% 0;
    mask-position: 100% 0;
  }

  /*
    Red only. This used to blend a cyan radial glow behind the red one, which
    read as exactly the "blue circle around the wheel" the intro is meant not
    to have — a soft ambient wash still helps the wheel sit off the black
    backdrop, but it has to stay on the red side of the palette to do that
    without contradicting the rest of the intro.
  */
  .intro__bloom {
    position: absolute;
    inset: 0;
    z-index: -1;
    background: radial-gradient(circle 40vmin at 50% 46%, rgba(232, 19, 14, 0.16), transparent 68%);
    animation: bloom-breathe 5s var(--ease-in-out) infinite alternate;
  }

  .intro__grid {
    z-index: -1;
    opacity: 0.5;
  }

  @keyframes bloom-breathe {
    to {
      opacity: 0.6;
      transform: scale(1.06);
    }
  }

  /* ------------------------------------------------------------- stage -- */

  .intro__stage {
    display: grid;
    justify-items: center;
    gap: var(--space-md);
    padding: var(--gutter);
    animation: stage-in 900ms var(--ease-out) both;
  }

  @keyframes stage-in {
    from {
      opacity: 0;
      transform: translateY(1.5rem);
    }
  }

  /* -------------------------------------------------------------- wheel -- */

  .wheel {
    position: relative;
    display: grid;
    place-items: center;
    width: min(66vw, 42vh, 26rem);
    aspect-ratio: 1;
    border-radius: 50%;
    cursor: pointer;
    /* A real control, not a flat illustration: it sits on a soft floor
       shadow and lifts toward the pointer. */
    filter: drop-shadow(0 2rem 3rem rgba(0, 0, 0, 0.85));
    transition: transform var(--dur-base) var(--ease-spring);
    /*
      THIS element is what actually animates (hover/active scale, and the
      wheel-turn rotation on launch) — will-change belongs here, not on
      .wheel__spin below, which never moves. It was on the wrong element,
      so the browser was promoting a compositor layer AFTER the rotation
      had already started rather than ahead of it, which is what "the wheel
      turning is laggy" actually was.
    */
    will-change: transform;
  }

  .wheel:hover,
  .wheel:focus-visible {
    transform: scale(1.035);
  }

  .wheel:active {
    transform: scale(0.985);
  }

  /*
    Hugs the rim instead of floating 1.25rem off it. At the old offset this
    outline drew a large detached circle around the wheel — and because the
    intro moves focus into the dialog on open, it painted on every fresh load
    and read as a decorative blue ring rather than a focus indicator. Focus
    now lands on the dialog itself (see intro.js), so this only ever shows
    when someone actually Tabs to the wheel, which is exactly when it should.
  */
  .wheel:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 0.35rem;
  }

  /*
    The ambient halo: one cyan arc and one red arc sweeping continuously
    behind the rim. Blurred heavily so they read as soft moving glows rather
    than hard bands.

    `will-change: transform` matters here. A blurred element that rotates
    forever would otherwise be re-rasterised every frame; promoting it to its
    own compositor layer means the blur is rasterised ONCE and the rotation is
    a pure transform on that cached layer.
  */
  .wheel__halo {
    position: absolute;
    inset: -12%;
    border-radius: 50%;
    background: conic-gradient(
      from 0deg,
      transparent 0deg,
      rgba(0, 201, 255, 0.5) 40deg,
      transparent 90deg,
      transparent 180deg,
      rgba(245, 20, 15, 0.45) 220deg,
      transparent 280deg
    );
    filter: blur(1.5rem);
    opacity: 0.45;
    pointer-events: none;
    animation: halo-spin 9s linear infinite;
    will-change: transform;
  }

  @keyframes halo-spin {
    to {
      transform: rotate(360deg);
    }
  }

  .wheel__spin {
    display: block;
    width: 100%;
  }

  .wheel__img {
    width: 100%;
    height: auto;
  }

  .wheel__logo {
    position: absolute;
    /* Centred inside the wheel, sized to sit inside the rim rather than
       spill over it. Smaller than the rim itself so it reads as a badge on
       the hub, not a second wheel. */
    width: 30%;
    line-height: 0;
    pointer-events: none;
    filter: drop-shadow(0 0 1.5rem rgba(0, 0, 0, 0.9));
    /* Most wheel art carries its hub slightly below the image's geometric
       centre, so a grid-centred badge reads as sitting high. Nudge it down
       onto the hub. The amount is per-wheel: the inline picker sets
       --logo-shift from the chosen wheel's own offset. */
    transform: translateY(var(--logo-shift, 8%));
  }

  .wheel__logo img {
    width: 100%;
    height: auto;
  }

  /* -------------------------------------------------------------- copy -- */

  .intro__title {
    margin: 0;
    font-family: var(--font-display);
    font-size: clamp(1.25rem, 0.8rem + 2vw, 2.25rem);
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    text-align: center;
    color: var(--text-primary);
  }

  .intro__skip {
    position: absolute;
    bottom: clamp(1.25rem, 4vh, 2.5rem);
    left: 50%;
    transform: translateX(-50%);
    padding: 0.5rem 1rem;
    border: 1px solid var(--line-faint);
    border-radius: var(--radius-sm);
    font-family: var(--font-mono);
    font-size: var(--fs-mono-label);
    letter-spacing: var(--tracking-mono);
    text-transform: uppercase;
    color: var(--text-muted);
    background: rgba(0, 0, 0, 0.35);
    transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
  }

  .intro__skip:hover,
  .intro__skip:focus-visible {
    color: var(--text-primary);
    border-color: var(--line-strong);
  }

  /* --------------------------------------------------------------- flag -- */

  /*
    The chequered flag. Activating the wheel sweeps it across the screen and
    the page is uncovered behind it — the finish-line moment.

    ONE element, not a stack of columns. An earlier version built the flag
    from 14 separate columns so each could ride its own wave, which meant 14
    full-height compositor layers each painting a large repeating gradient —
    that was the lag. A single sheet with a single transform is one layer and
    one animation, and at the speed it crosses the screen the difference in
    the cloth effect is not perceptible.
  */
  .flag {
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0;
    contain: strict;
  }

  /*
    One conic-gradient paints a 2x2 checker per tile, so background-size is
    TWICE the square size: 16vmin tiles give 8vmin squares — large, like a
    real flag held close to camera, rather than a fine grid.

    The slight skew is the only nod to cloth left. It is part of the same
    composited transform as the sweep, so it costs nothing extra, and the
    vertical bleed below covers the corners it pulls off-screen.
  */
  .flag__sheet {
    position: absolute;
    inset-block: -18%;
    left: 0;
    width: 62vw;
    background-image: conic-gradient(
      #f2f5f6 0deg 90deg,
      #080c0d 90deg 180deg,
      #f2f5f6 180deg 270deg,
      #080c0d 270deg 360deg
    );
    background-size: 16vmin 16vmin;
    opacity: 0;
    transform: translate3d(-70vw, 0, 0) skewY(-3.5deg);
    -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 14%, #000 84%, transparent 100%);
    mask-image: linear-gradient(90deg, transparent 0%, #000 14%, #000 84%, transparent 100%);
  }

  /* There was a bright white leading edge sweeping just ahead of the cloth
     here (.flag__edge), so the flag arrived with a flash of light. The owner
     asked for it gone — it read as a glowing white line rather than as light.
     The sheet and the veil wipe are unchanged and still carry the reveal; do
     not reintroduce a leading flash without being asked. */

  /* Promoted only while it actually moves. Leaving a permanent compositor
     layer here would cost memory on every load, and the intro is absent
     from most of them. */
  .intro.is-launching .flag__sheet {
    will-change: transform, opacity;
  }

  /* -------------------------------------------- activated: the sequence -- */

  .intro.is-launching .wheel {
    /* Real wheels do not spring back: ease out of a hard turn-in. */
    animation: wheel-turn 1300ms var(--ease-drive) forwards;
  }

  .intro.is-launching .wheel__halo {
    animation: halo-spin 0.9s linear infinite, halo-flare 700ms var(--ease-out) forwards;
  }

  @keyframes halo-flare {
    40% {
      opacity: 1;
    }
    100% {
      opacity: 0;
    }
  }

  @keyframes wheel-turn {
    0% {
      transform: rotate(0deg) scale(1);
    }
    18% {
      transform: rotate(-42deg) scale(1.02);
    }
    100% {
      transform: rotate(548deg) scale(0.82);
    }
  }

  .intro.is-launching .intro__stage {
    animation: stage-out 560ms var(--ease-out) 420ms forwards;
  }

  .intro.is-launching .intro__skip,
  .intro.is-launching .intro__grid,
  .intro.is-launching .intro__bloom {
    animation: fade-out 360ms var(--ease-out) 340ms forwards;
  }

  @keyframes stage-out {
    to {
      opacity: 0;
      transform: scale(0.94);
    }
  }

  @keyframes fade-out {
    to {
      opacity: 0;
    }
  }

  .intro.is-launching .flag {
    opacity: 1;
  }

  /*
    Timing contract for the reveal — these must stay in step:
      flag sheet    240ms ->  1440ms
      backdrop wipe 420ms ->  1420ms  (trails the cloth's leading edge)
      SEQUENCE_MS   1500ms            (then FINISH_MS 260ms fade-out)

    Both sweeps are `linear` on purpose. A CSS timing function applies
    between EACH PAIR of keyframes, not across the animation as a whole, so an
    eased sweep desynchronises the flag from the wipe and the flag stops
    looking like it uncovers anything.

    Every keyframe below animates transform and opacity ONLY — both are
    composited properties, so no frame of this sequence triggers layout or
    paint.
  */
  .intro.is-launching .flag__sheet {
    animation: flag-sweep 1200ms linear 240ms forwards;
  }

  @keyframes flag-sweep {
    0% {
      opacity: 0;
      /* skewY is repeated in both transform keyframes on purpose: a transform
         list is interpolated as a whole, so omitting it here would animate
         the skew away to 0 across the sweep. */
      transform: translate3d(-70vw, 0, 0) skewY(-3.5deg);
    }
    10% {
      opacity: 1;
    }
    88% {
      opacity: 1;
    }
    100% {
      opacity: 0;
      transform: translate3d(112vw, 0, 0) skewY(-3.5deg);
    }
  }

  /* The uncover itself: the backdrop dissolves away just behind the flag's
     leading edge, so the homepage is revealed rather than merely faded to. */
  .intro.is-launching .intro__veil {
    animation: veil-wipe 1000ms linear 420ms forwards;
  }

  @keyframes veil-wipe {
    to {
      -webkit-mask-position: 0% 0;
      mask-position: 0% 0;
    }
  }

  /* Final safety net — whatever the browser did with masks, the layer is
     gone by the end of the sequence. */
  .intro.is-finishing {
    animation: fade-out 260ms var(--ease-out) forwards;
    pointer-events: none;
  }

  /* --------------------------------------------------- reduced motion --
     A shortened, non-animated version. The interaction is preserved (the
     user still opens the site by activating the wheel) but nothing spins,
     sweeps or lingers. */
  @media (prefers-reduced-motion: reduce) {
    .intro__bloom,
    .wheel__halo {
      animation: none;
    }

    .intro__stage {
      animation: none;
    }

    .intro.is-launching .wheel,
    .intro.is-launching .wheel__halo,
    .intro.is-launching .intro__stage,
    .intro.is-launching .flag__sheet,
    .intro.is-launching .intro__veil {
      animation: none;
    }

    .intro.is-launching .flag {
      opacity: 0;
    }

    .intro.is-finishing {
      animation: none;
      opacity: 0;
    }
  }
