/* ============================================================
   Handshake v1 → v2 bridge

   The migration problem: 19 pages and ~1.5 MB of CSS reference v1 token
   names (--hair 289x, --ink-3 283x, --panel, --accent, --r-md, --sp-*).
   Rewriting that markup page by page would take weeks and break flows.

   But v1 already routes almost everything through custom properties. So
   instead of rewriting pages, this file REDEFINES the v1 names in terms of
   v2 roles. Every rule that says `color: var(--ink-2)` starts resolving to
   the v2 secondary foreground with no markup change at all.

   What this file deliberately does NOT do:
     - it does not load hs-base.css (its reset would restyle v1 layout)
     - it does not restyle any v1 class, only tokens
     - it does not touch spacing scales that already agree between versions

   Load order on a bridged page:
       ds/hs-fonts.css
       ds/hs-tokens.css      ← v2 values, scoped to [data-hs="2"]
       ds/hs-bridge.css      ← this file, maps v1 names onto them
       ...the page's own v1 stylesheets...

   The page's <html> needs data-hs="2" plus a surface, or the v2 tokens this
   file references will not resolve.
   ============================================================ */

/* ---------------------------------------------------------------------------
   The v1 → v2 token map.

   One rule this map must obey: never point a Layer 1 primitive
   (--hs-neutral-*, --hs-forest-*, --hs-aqua-*) at a role that hs-tokens.css
   builds from that same primitive. Roles are defined FROM primitives, so
   such an alias closes a custom-property cycle, and CSS then treats every
   property in the cycle as invalid at computed-value time — silently. No
   warning, no console error, no failing gate.

   That is not theoretical. Two such aliases lived here and took four more
   tokens down with them:

     --hs-neutral-200: var(--hs-border-default)
     --hs-neutral-500: var(--hs-fg-tertiary)

   The first also took --hair-2 and --hs-border-default with it; the second
   took --ink-3 and --fg-muted. All six resolved to the empty string.
   `color: var(--ink-3)` then fell back to inherited ink, so muted text
   painted at full strength, and `border: 1px solid var(--hs-neutral-200)`
   fell back to currentColor and drew near-black hairlines. Both aliases were
   redundant too: in light theme each role resolved to the very primitive the
   alias was shadowing.

   Safest habit: map the v1 name onto the role (--hair, --ink-3) and leave
   primitives alone. --hs-neutral-800 below is the one surviving primitive
   alias — --hs-fg-primary resolves through --hs-forest-900, not through
   --hs-neutral-800, so there is no cycle and 52 v1 rules depend on the
   value. Check for that before adding another.

   This was written twice: once on [data-hs="2"], then again, verbatim, on
   .surface-admin / .surface-user — because surfaces.css declares the same
   names on those classes and out-specifies the root. Sixty-two of sixty-two
   declarations were byte-identical, so every token change had to be made in
   two places or the app went half-converted.

   A selector list fixes it. `[data-hs="2"] .surface-admin` still carries the
   specificity needed to beat surfaces.css; it just no longer needs its own
   copy of the map to do it.
   --------------------------------------------------------------------------- */
[data-hs="2"],
[data-hs="2"] .surface-admin,
[data-hs="2"] .surface-user {
  /* ---- Surfaces ------------------------------------------------------ */
  --canvas: var(--hs-bg-canvas);
  --panel: var(--hs-glass-card);
  --panel-2: var(--hs-glass-recessed);
  --panel-3: var(--hs-accent-subtle);
  --hs-bg: var(--hs-bg-canvas);
  --hs-bg-sunken: var(--hs-glass-sunken);
  --hs-bg-raised: var(--hs-glass-card);

  /* ---- Hairlines ----------------------------------------------------- */
  --hair: var(--hs-border-subtle);
  --hair-2: var(--hs-border-default);
  --hs-border: var(--hs-border-default);
  --border-hair: var(--hs-border-subtle);

  /* ---- Ink ----------------------------------------------------------- */
  --ink: var(--hs-fg-primary);
  --ink-2: var(--hs-fg-secondary);
  --ink-3: var(--hs-fg-tertiary);
  --hs-text: var(--hs-fg-primary);
  --hs-text-muted: var(--hs-fg-secondary);
  --hs-neutral-800: var(--hs-fg-primary);
  --fg-on-light: var(--hs-fg-primary);
  --fg-muted: var(--hs-fg-tertiary);
  --v2-text-primary: var(--hs-fg-primary);
  --v2-text-secondary: var(--hs-fg-secondary);

  /* ---- Accent -------------------------------------------------------- */
  --accent: var(--hs-accent-solid);
  --accent-ink: var(--hs-fg-on-accent);
  --accent-tint: var(--hs-accent-muted);
  --accent-tint-2: var(--hs-accent-subtle);
  --hs-aqua: var(--hs-aqua-400);
  --hs-aqua-bright: var(--hs-aqua-300);
  --hs-aqua-deep: var(--hs-aqua-600);

  /* --accent-2 means Everglade on the admin surface and Jasmine on the
     consumer one. Console keeps the purple reading; everything else takes
     the deep green so the mapping is never wrong, only conservative. */
  --accent-2: var(--hs-accent-ink);
  --hs-jasmine: var(--hs-accent2-solid);
  --hs-indigo: var(--hs-accent2-ink);

  /* ---- Status -------------------------------------------------------- */
  --pass: var(--hs-success-fg);
  --pass-tint: var(--hs-success-bg);
  --fail: var(--hs-danger-fg);
  --fail-tint: var(--hs-danger-bg);
  --hs-red: var(--hs-danger-solid);
  --hs-amber: var(--hs-warning-solid);
  --hs-blue: var(--hs-info-solid);

  /* ---- Radii — v1 and v2 agree on every step ------------------------- */
  --r-xs: var(--hs-radius-xs);
  --r-sm: var(--hs-radius-sm);
  --r-md: var(--hs-radius-md);
  --r-lg: var(--hs-radius-lg);
  --r-xl: var(--hs-radius-xl);
  --r-2xl: var(--hs-radius-2xl);
  --r-pill: var(--hs-radius-pill);

  /* ---- Elevation ----------------------------------------------------- */
  --shadow: var(--hs-glass-lift);
  --shadow-sm: var(--hs-elevation-raised);
  --shadow-card: var(--hs-glass-lift);
  --hs-shadow-card: var(--hs-glass-lift);

  /* ---- Type ---------------------------------------------------------- */
  --font: var(--hs-font-body);
  --hs-font: var(--hs-font-body);
  --font-body: var(--hs-font-body);
  --font-ui: var(--hs-font-ui);
  --font-display: var(--hs-font-display);
  --font-mono: var(--hs-font-mono);

  /* ---- Dock ----------------------------------------------------------
     52px, matching what the dock actually measures. A size token that
     disagrees with the thing it names is worse than no token. */
  --hs-dock-h: 52px;

  /* ---- Bottom chrome inset -------------------------------------------
     ONE number for "how much of the bottom of the page is already spoken
     for", so a scrolling pane can reserve it without knowing which piece of
     floating chrome is in play. Both surfaces read this; see the rules that
     apply it in hs-bridge-components.css.

     Desktop value covers the Julia assistant, which rests bottom-right and
     was landing on real rows — on the workspace's Connected sites list, on
     the "Add another page" control specifically. It is draggable, so this
     reserves its RESTING height and nothing more; parked anywhere else it is
     the owner's own choice and no layout can chase it.

     The mobile override below swaps in the dock, which is taller and spans
     the full width. They never both apply: the dock only exists where the
     rail does not. */
  --hs-assist-h: 44px;
  --hs-page-bottom-inset: calc(var(--hs-assist-h) + var(--hs-space-6) + env(safe-area-inset-bottom, 0px));

  /* ---- Motion -------------------------------------------------------- */
  --hs-ease: var(--hs-ease-standard);
  --hs-dur: var(--hs-duration-base);
  --hs-dur-fast: var(--hs-duration-fast);
  --hs-dur-slow: var(--hs-duration-slow);
}

/* Root-only. The scroll-lander declares these on <body>, so they never need
   the surface-class escalation the map above does. */
[data-hs="2"] {
  --v2-font-display: var(--hs-font-display);
  --v2-font-ui: var(--hs-font-ui);
  --hero-display: var(--hs-font-display);
  --sans: var(--hs-font-body);
  --mono: var(--hs-font-mono);
}

/* v1's --sp-* scale is identical to v2's --hs-space-*, so it is aliased
   rather than remapped — no visual change, just one source of truth. */
[data-hs="2"] {
  --sp-1: var(--hs-space-1);
  --sp-2: var(--hs-space-2);
  --sp-3: var(--hs-space-3);
  --sp-4: var(--hs-space-4);
  --sp-5: var(--hs-space-5);
  --sp-6: var(--hs-space-6);
  --sp-8: var(--hs-space-8);
  --sp-10: var(--hs-space-10);
  --sp-12: var(--hs-space-12);
  --sp-16: var(--hs-space-16);
}

/* ---- Page canvas ----------------------------------------------------
   v1 pages paint their own background. Give bridged pages the v2 wash and
   texture, matching the surface they declare.

   When --hs-bg-photo is set (brand texture PNGs), the baked photo replaces the
   CSS wash/texture layers — those are already composited into the image. */
[data-hs="2"] body,
[data-hs="2"] body.hs-portal-body {
  background-color: var(--hs-bg-canvas) !important;
  /* Wash first: --hs-bg-texture is TWO gradients, so a two-value position list
     gets repeated and the wash never receives its own offset. */
  background-image: var(--hs-bg-tint, none), var(--hs-bg-texture, none) !important;
  /* Every one of these needs !important, not just the image. v1 paints the body
     with a `background:` SHORTHAND, which resets each longhand it omits back to
     its initial value — so repeat fell to `repeat` and size to `auto`, and the
     wash TILED at its natural size instead of stretching. On a short page one
     tile covers the viewport and it looks fine; on a long page you get a hard
     horizontal seam at every tile boundary. The layer set only works intact. */
  background-repeat: no-repeat, no-repeat, no-repeat !important;
  background-size: 100% var(--hs-wash-scale, 320%), var(--hs-texture-scale, 150% 150%) !important;
  background-position: 0% var(--hs-wash-shift, 0%), var(--hs-texture-pos, 0% 0%), var(--hs-texture-pos, 0% 0%) !important;
  background-attachment: fixed, fixed !important;
  font-family: var(--hs-font-body);
  color: var(--hs-fg-primary);
}

/* --hs-bg-photo is a LAYER LIST, not necessarily one image. A baked PNG wants
   `cover / no-repeat / center`; a CSS wash wants a tiling grain layer on top of
   stretched gradients, which needs per-layer values. Hence the three escape
   hatches — a surface that overrides --hs-bg-photo with N layers must override
   all three with N matching values or the shorter list repeats and the grain
   stretches. Defaults keep every PNG page byte-identical. */
[data-hs="2"][data-hs-bg="photo"] body,
[data-hs="2"][data-hs-bg="photo"] body.hs-portal-body {
  background-color: var(--hs-bg-photo-canvas, var(--hs-bg-canvas)) !important;
  background-image: var(--hs-bg-photo, none) !important;
  background-repeat: var(--hs-bg-photo-repeat, no-repeat) !important;
  background-size: var(--hs-bg-photo-size, cover) !important;
  background-position: var(--hs-bg-photo-pos, center) !important;
  background-attachment: fixed !important;
}

/* Mobile photo canvas — avoid the zoom flash.
   iOS/WebKit often ignores background-attachment:fixed, so `cover` on <body>
   resolves against the growing document instead of the viewport: first paint
   looks viewport-fit, then content mounts and the image snaps. Paint the
   photo on a real position:fixed layer sized to the viewport with cover so
   it stays edge-to-edge as a true background.

   Gradient surfaces are immune to that flash (a gradient has no intrinsic size,
   so `cover` is a no-op and it just fills whatever box it is handed) — the
   fixed layer is kept anyway because it is also what puts the canvas behind
   `backdrop-filter` chrome like the footer dock. */
@media (max-width: 720px) {
  [data-hs="2"][data-hs-bg="photo"] {
    background-color: var(--hs-bg-photo-canvas, var(--hs-bg-canvas));
  }

  [data-hs="2"][data-hs-bg="photo"] body,
  [data-hs="2"][data-hs-bg="photo"] body.hs-portal-body {
    background-image: none !important;
    background-color: transparent !important;
    background-attachment: scroll !important;
    /* Stacking context so z-index:-1 ::before stays behind UI, not behind <html>. */
    isolation: isolate;
  }

  [data-hs="2"][data-hs-bg="photo"] body::before,
  [data-hs="2"][data-hs-bg="photo"] body.hs-portal-body::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background-color: var(--hs-bg-photo-canvas, var(--hs-bg-canvas));
    background-image: var(--hs-bg-photo, none);
    background-repeat: var(--hs-bg-photo-repeat, no-repeat);
    background-position: var(--hs-bg-photo-pos, center);
    background-size: var(--hs-bg-photo-size, cover);
  }
}

/* Glass panels need something behind them, so any v1 surface that used to
   be opaque white becomes translucent along with the token swap. */
[data-hs="2"] .surface-admin,
[data-hs="2"] .surface-user {
  background: transparent;
}

/* ---- Lander scope --------------------------------------------------
   scroll-lander-ux.css declares its font tokens on `body.scroll-lander-page`,
   which out-specifies any root-level mapping — the lander hero kept rendering
   Instrument Serif. Redeclare them at that scope so the lander uses the
   system display face like every other page. */
[data-hs="2"] body.scroll-lander-page {
  --v2-font-ui: var(--hs-font-ui);
  --v2-font-display: var(--hs-font-display);
  --v2-text-primary: var(--hs-fg-primary);
  --v2-text-secondary: var(--hs-fg-secondary);
}

/* ============================================================
   Repairs — things the token bridge cannot reach

   The bridge only works where v1 routed a value through a custom property.
   These are the places v1 hardcoded a literal, so they keep rendering the
   old value no matter what the tokens say. Each one is a targeted override,
   not a rewrite of the page.
   ============================================================ */

/* Display serifs were written as literals in eight stylesheets (PT Serif in
   the lander and legal pages, Instrument Serif in scroll-lander-polish). The
   system settled on Lexend, so anything asking for those faces is redirected. */
[data-hs="2"] :is(h1, h2, h3, .hs-h1, .hs-h2, .display-hero, .hero-title,
  [class*="hero__title"], [class*="narr-line"], [class*="-display"]) {
  font-family: var(--hs-font-display);
}

/* legal.css sets its own serif on headings with no token in between. */
[data-hs="2"] .legal h1,
[data-hs="2"] .legal h2,
[data-hs="2"] .legal h3,
[data-hs="2"] main h1,
[data-hs="2"] article h1 {
  font-family: var(--hs-font-display);
}

/* lander.css paints an opaque white body, which hides the wash entirely. */
[data-hs="2"] body {
  background-color: var(--hs-bg-canvas) !important;
}

/* ---------- Focus and press artifacts ----------

   hs-base.css owns both of these rules, and the bridged pages — workspace,
   admin, connect, every `[data-hs="2"]` page — do not load hs-base.css. So
   they had neither, and both absences showed up as the same complaint: a dark
   line around anything you clicked, on toggles, buttons, cards and sections
   alike.

   Two different mechanisms produced it:

   1. WebKit sets `:focus` on a *tap*, not only on keyboard entry. With no
      `:focus:not(:focus-visible)` reset, the UA paints its own dark focus ring
      on every tapped control. That is the line.
   2. The OS tap-highlight paints a translucent black rectangle over a tapped
      element's boxes rather than its radius, so a rounded card shows it as
      dark edges and corners.

   The tap-highlight kill used to be gated on `@media (pointer: coarse)`, which
   reads as "only touch devices tap". An iPad with a keyboard or trackpad
   attached reports `pointer: fine` and still taps, so it failed the gate and
   got the highlight — which is exactly where this was reported. The property is
   inert on a device that never taps, so the gate protected nothing and cost
   this bug; it is gone.

   The `:focus-visible` ring is declared here too, and deliberately: removing
   the tap ring without it would leave keyboard users on these pages with no
   focus indicator at all, which is worse than the artifact. Bridged pages
   previously relied on the UA ring; now they get the designed one. */

[data-hs="2"] {
  -webkit-tap-highlight-color: transparent;
}

[data-hs="2"] :focus-visible:not(:is(input:not([type]), input[type="text"],
  input[type="email"], input[type="password"], input[type="search"],
  input[type="tel"], input[type="url"], input[type="number"],
  input[type="date"], input[type="time"], input[type="datetime-local"],
  input[type="month"], input[type="week"], textarea, select)) {
  outline: var(--hs-size-focus-ring) solid var(--hs-border-focus);
  outline-offset: var(--hs-size-focus-offset);
  border-radius: inherit;
}

/* Every text field starts with a soft radius. Focus changes only the
   one-pixel border, so mouse and keyboard entry keep stable geometry. */
[data-hs="2"] :is(input:not([type]), input[type="text"], input[type="email"],
  input[type="password"], input[type="search"], input[type="tel"],
  input[type="url"], input[type="number"], input[type="date"],
  input[type="time"], input[type="datetime-local"], input[type="month"],
  input[type="week"], textarea, select):not(.live-input) {
  border-radius: var(--hs-radius-md, 12px);
}

[data-hs="2"] :is(input:not([type]), input[type="text"], input[type="email"],
  input[type="password"], input[type="search"], input[type="tel"],
  input[type="url"], input[type="number"], input[type="date"],
  input[type="time"], input[type="datetime-local"], input[type="month"],
  input[type="week"], textarea, select):focus {
  outline: none;
  border-color: var(--hs-border-field-focus);
  box-shadow: none;
}

/* These class rules keep compatibility with pages that carry older, more
   specific focus selectors. */
[data-hs="2"] :is(
  .hs-input,
  .hs-select,
  .hs-textarea,
  .hs-cs-label-input,
  .hs-training-input,
  .hs-tasks-composer__title,
  .hs-tasks-tagadd__input
):focus-visible {
  outline: none;
}

[data-hs="2"] .hs-input-group:focus-within :is(.hs-input, .hs-select):focus-visible {
  outline: none;
}

/* Chat composers keep their own radius. Inherit copies the form's 0 radius
   onto the field and turns a rounded input into a sharp square. */
[data-hs="2"] :is(.julia-chat-form, .cs-form, .composer, .chat-input) :is(input, textarea):focus-visible {
  outline: none;
  outline-offset: 0;
  border-radius: 12px;
  box-shadow: none;
}

[data-hs="2"] .live-input:focus-visible {
  outline: none;
  outline-offset: 0;
  border-radius: var(--hs-radius-pill);
  box-shadow: none;
}

/* Never widen this to a bare `:focus` — that is the rule that removes the ring
   from keyboard users as well. */
[data-hs="2"] :focus:not(:focus-visible) {
  outline: none;
}
