/* ============================================================
   8020d - SHARED PAGE CHROME
   Header bar, primary nav, mobile drawer, footer, skip link.

   Hoisted out of 14 duplicated inline <style> blocks on 2026-08-31
   as part of the dark-mode work: the theme needs ONE place to
   restyle the chrome, and 14 copies is 14 chances to miss one.

   Loaded AFTER /css/tokens.css and BEFORE each page's inline
   <style>, so a page can still override any rule here by simply
   redeclaring it.

   Only selectors that EVERY chrome-bearing page already had live
   here. The four case studies write their header with
   `header.bar nav ...` instead of `.bar nav ...`, which is a higher
   specificity; putting those here would let them win on every page.
   They live in elements.css, which only the case studies load.

   Cache: served immutable, so bump ?v= in every <link> when you
   edit this file or nobody will see the change for a year.
   ============================================================ */
.menu-close {position: absolute; top: 1.1rem; right: 1.4rem; background: transparent; border: 0; font-size: 2rem; line-height: 1; cursor: pointer; color:var(--fg-1); padding: .35rem .6rem; border-radius: 6px; transition: background .15s; min-width: 44px; min-height: 44px; -webkit-tap-highlight-color: transparent;}
.menu-close:hover,.menu-close:focus-visible {background: var(--tint-2); outline: none;}
.menu-drawer {position: fixed; inset: 0; background: var(--scrim); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); opacity: 0; pointer-events: none; transition: opacity .25s ease; z-index: 100;}
.menu-drawer.open {opacity: 1; pointer-events: auto;}
.menu-drawer.open .menu-panel {transform: translateX(0);}
.menu-panel {position: absolute; top: 0; right: 0; width: min(86vw, 360px); height: 100%; background:var(--bg-canvas); padding: 5rem 2.25rem 2.5rem; transform: translateX(100%); transition: transform .32s cubic-bezier(.4, 0, .2, 1); display: flex; flex-direction: column; gap: 1.25rem; box-shadow: -8px 0 32px var(--shadow-c); overflow-y: auto;}
.menu-panel a.active {color:var(--fg-accent); font-style: italic;}
.menu-panel a.menu-cv:hover {background:var(--btn-primary-bg-hover); color:var(--btn-primary-fg-hover); transform: none;}
.menu-panel a:hover {color:var(--fg-accent); transform: translateX(4px);}
.menu-toggle {display: none; background: transparent; border: 0; padding: .55rem; cursor: pointer; color:var(--fg-1); border-radius: 6px; transition: background .15s; -webkit-tap-highlight-color: transparent; min-width: 44px; min-height: 44px; align-items: center; justify-content: center;}
.menu-toggle svg {display: block;}
.menu-toggle:hover,.menu-toggle:focus-visible {background: var(--tint-1); outline: none;}
.skip-link {position: absolute; left: -9999px; top: auto; width: 1px; height: 1px; overflow: hidden;}

@media (max-width: 720px) {
  .menu-toggle {display: flex;}
}

/* ============================================================
   THEME CONTROL  (phase 6)

   Three segments, all visible at once: Auto / Light / Dark.
   A single sun-or-moon button is ambiguous - does a moon mean
   "you are in dark" or "click for dark"? Showing all three states
   removes the question structurally rather than explaining it.

   Semantics are a radio group, because these are three mutually
   exclusive named options. A screen reader announces "Colour
   theme, group. Auto, radio button, 1 of 3, selected."
   ============================================================ */

/* Visually hidden but reachable. */
.vh {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* Progressive enhancement: the control does nothing without JS, and a dead
   control is worse than none. The bootstrap adds .js synchronously in <head>,
   so this never flashes. Non-JS visitors still get the right theme, because
   color-scheme: light dark follows the OS on its own. */
.ts { display: none; }
html.js .ts { display: inline-flex; }

.ts {
  border: 1px solid var(--rule);
  border-radius: var(--radius-2);
  padding: 2px;
  margin: 0;
  min-width: 0;
  align-items: center;
  gap: 0;
}
.ts legend { display: none; }

.ts-in { position: absolute; opacity: 0; width: 1px; height: 1px; }

.ts-lb {
  display: inline-flex; align-items: center; justify-content: center;
  gap: .4rem;
  min-width: 28px; min-height: 26px;
  padding: 0 .4rem;
  border-radius: var(--radius-1);
  color: var(--fg-3);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-standard),
              background-color var(--dur-fast) var(--ease-standard);
}
.ts-lb svg { display: block; width: 15px; height: 15px; }
.ts-lb:hover { color: var(--fg-1); }
.ts-in:focus-visible + .ts-lb {
  outline: 2px solid var(--fg-accent);
  outline-offset: 1px;
}

/* SELECTED state comes from <html>, not :checked. The bootstrap sets
   data-theme synchronously in <head>, so the right segment is highlighted at
   FIRST PAINT - site.js only mirrors it into .checked afterwards for AT. That
   is what prevents the "flash of wrong toggle state" that catches out
   client-rendered toggles.

   Two segments, and the highlight follows the RESOLVED theme, not a stored
   choice: with nothing stored the site follows the OS, and the button showing
   what you are actually looking at is the honest thing to highlight. Hence the
   media queries - they cover the un-overridden case, which no attribute can
   describe. */
html[data-theme="light"] .ts-lb[for^="ts-light"],
html[data-theme="dark"]  .ts-lb[for^="ts-dark"] {
  background: var(--tint-2);
  color: var(--fg-1);
}
@media (prefers-color-scheme: light) {
  html:not([data-theme]) .ts-lb[for^="ts-light"] {
    background: var(--tint-2);
    color: var(--fg-1);
  }
}
@media (prefers-color-scheme: dark) {
  html:not([data-theme]) .ts-lb[for^="ts-dark"] {
    background: var(--tint-2);
    color: var(--fg-1);
  }
}

/* Header instance: icon-only, label available to AT. */
.bar .ts .ts-t { position: absolute; width: 1px; height: 1px;
  margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* Drawer instance: full width with visible words, where there is room. */
.ts--wide { display: none; width: 100%; }
html.js .ts--wide { display: none; }
@media (max-width: 720px) {
  html.js .ts--wide { display: flex; }
  html.js .bar .ts { display: none; }
  .ts--wide .ts-lb { flex: 1; min-height: 44px; font-size: .8rem;
    letter-spacing: .06em; text-transform: uppercase; }
}

/* The two pages with no header at all (404, contact-success). Their bare
   fixed .logo is the whole chrome; this mirrors it on the other side rather
   than bolting a full header onto them. */
.ts--float { position: fixed; top: 1.5rem; right: 2rem; z-index: 60; }
@media (max-width: 720px) { .ts--float { top: 1rem; right: 1rem; } }

/* WCAG 2.2 SC 2.5.8 wants 24x24 CSS px; touch gets the AAA 44. */
@media (hover: none) {
  .ts-lb { min-width: 44px; min-height: 44px; }
}

/* Suppress transitions for one frame while the theme flips, or every element
   animates independently and the change sweeps visibly down the page.
   Transitions only - killing animations mid-run would strand the logo egg. */
html.theme-switching,
html.theme-switching *,
html.theme-switching *::before,
html.theme-switching *::after { transition: none !important; }

@media (prefers-reduced-motion: reduce) {
  .ts-lb { transition: none; }
}

/* ---- light-background imagery -------------------------------
   Case-study section shots and covers, the homepage and portfolio
   card thumbnails, the About journey grid, and the two product
   GIFs. box-shadow rather than border: it follows border-radius,
   costs no layout, and is invisible in light mode because the
   token resolves to transparent there. */
.img,
.img-cap,
.wt-thumb img,
.cs-img img,
.cs-hero-aside img,
.chapter-body img,
.journey-zoom img,
.img-frame img {
  /* outline, not border or box-shadow. border would shift layout by a pixel
     in BOTH themes; an outset box-shadow draws the line outside the image,
     where a dark hairline against a dark page is invisible. outline with a
     negative offset paints ON the image's own edge, so it separates from the
     bright content regardless of what is behind it - and outline never
     affects layout. */
  outline: 1px solid var(--img-edge);
  outline-offset: -1px;
}

/* ---- hero illustrations: monoline in dark ------------------
   The three page heroes are pure black line art on transparency.
   In dark they are redrawn in ONE light tone - line work, solid
   masses and all - which is the whole treatment. No plate, no
   tonal system, no computed fills. The brand accent is the single
   exception, because it carries meaning rather than form.

   Why a swapped FILE and not CSS on the art: an <img src="*.svg">
   is an isolated document, so currentColor and custom properties
   stop at its boundary. Inlining or an external <use> would fix
   that, but both cost something real here - page weight for the
   76KB Writings drawing, and for <use> the fact that SVG 2 leaves
   processing of an external file's <style> block up to the browser,
   which is exactly where that drawing keeps its pen animation.
   A generated twin costs nothing at runtime and cannot break.

   Regenerate after ANY edit to a light original:
     python3 tools/darkmode/illo_monoline.py SRC.svg SRC-dark.svg

   content: url() on a replaced element swaps what it paints while
   leaving the box alone, so the dark art occupies exactly the same
   space as the light one - the generator refuses to write a file
   whose width/height/viewBox differ, which is what guarantees it.
   Matching on [src] rather than a new class keeps the markup
   untouched, and survives portfolio.html rewriting src with a
   cache-busting query to replay its SMIL animation. */
:root[data-theme="dark"] img[src*="oc-on-the-laptop"] {
  content: url("/images/svg/oc-on-the-laptop-dark.svg?v=2026-09f");
}
:root[data-theme="dark"] img[src*="remote-work"] {
  content: url("/images/remote-work-dark.svg?v=2026-09f");
}
:root[data-theme="dark"] img[src*="oc-going-through-emails"] {
  content: url("/images/oc-going-through-emails-dark.svg?v=2026-09f");
}
@media (prefers-color-scheme: dark) {
  /* no-JS / no stored preference; the bootstrap sets data-theme
     whenever a choice exists, so this cannot fight it. */
  :root:not([data-theme]) img[src*="oc-on-the-laptop"] {
    content: url("/images/svg/oc-on-the-laptop-dark.svg?v=2026-09f");
  }
  :root:not([data-theme]) img[src*="remote-work"] {
    content: url("/images/remote-work-dark.svg?v=2026-09f");
  }
  :root:not([data-theme]) img[src*="oc-going-through-emails"] {
    content: url("/images/oc-going-through-emails-dark.svg?v=2026-09f");
  }
}
