/* PHFX shared stylesheet — 2026 V01 */
/* Linked by all PHFX tools/pages. Edit here to restyle site-wide. */

  @import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;700&family=Inter:wght@300;400;500&family=DM+Sans:wght@400;700&family=Open+Sans:wght@400;700&family=Lato:wght@400;700&family=Michroma&family=Montserrat:wght@400;700&family=Roboto:wght@400;700&family=Oswald:wght@400;700&family=Hanken+Grotesque:wght@400;700&family=Bebas+Neue&family=Orbitron:wght@400;700&family=Space+Grotesk:wght@400;700&family=Work+Sans:wght@400;700&family=Arimo:wght@400;700&family=Cutive+Mono&display=swap');
  @import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0&icon_names=rotate_right&display=block');

  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

  /* The [hidden] attribute must always win over any class's own `display`
     value. Without this, an element like <div class="wysiwyg-fields" hidden>
     stays visually visible: [hidden] and a class selector have equal
     specificity, so whichever rule comes later in the stylesheet cascade
     wins — and an author rule always comes after the browser's UA
     stylesheet. !important forces [hidden] to win regardless of ordering. */
  [hidden] { display: none !important; }

  :root {
    --bg: #171717;
    --surface: #1f1f1f;
    --surface-hover: #1d1d1f;
    --border: #2A2A30;
    --text: #ebebeb;
    --text-muted: #9e9e9e;
    --frame-bg: #0a0a0a;
    --frame-border: #ebd081;
    --gold: #ebd081;
    --gold-dim: rgba(235, 208, 129, 0.15);
    --current: #81ebeb;
    /* Dimmed cyan, same idea as --gold-dim: alpha baked into the token so
       anything drawn over it stays at full strength, and so it flips with the
       theme.

       Higher alpha than --gold-dim's 0.15 on purpose. That value is calibrated
       for a large fill - the HFOV wedge covers a couple of thousand square
       pixels, so a faint wash still reads. These pan ticks are 2.5px strokes
       covering about one percent of that area, and the same alpha would make
       them all but invisible. 0.55 keeps them clearly subordinate to the gold
       while remaining legible. */
    --current-dim: rgba(129, 235, 235, 0.55);
    --danger: #eb1313;
    --danger-dim: rgba(235, 19, 19, 0.08);
    --danger-soft: #eb6d6d;
    --warning: #e0836e;
    --hover-accent: #ebd081;
    --icon-accent: #9e9e9e;
    --success: #4caf50;
    --header-compass: #ebebeb;
    --header-wordmark: #ebd081;
    --header-sep1: #ebebeb;
    --header-pagetype: #ebd081;
    --header-sep2: #ebebeb;
    --header-pagetitle: #81ebeb;
    --header-compass-hover: #ebd081;
    --header-theme-toggle: #9e9e9e;
    --header-theme-toggle-hover: #ebd081;
    --header-rule: #ebd081;
    --footer-rule: #171717;
    --header-row: 2.2rem; /* fixed header content-row height; shared by .header-inner and .header-link so they can't drift */
  }

  /* Light / day theme. Opt in with <html data-theme="light">; dark is the default (no attribute).
     Only the tokens change here \x{2014} the rest of the sheet reads these variables. */
  :root[data-theme="light"] {
    --bg: #e6e6e6;
    --surface: #ffffff;
    --surface-hover: #f1ebe0;
    --border: #2A2A30;
    --text: #181818;
    --text-muted: #5c5650;
    --frame-bg: #0a0a0a;
    --frame-border: #ebd081;
    --gold: #8a3a10;
    --gold-dim: rgba(138, 58, 16, 0.12);
    --current: #0b6b93;
    /* Light theme: the cyan is already dark against a pale ground, so it needs
       less alpha reduction to sit back than it does on black. */
    --current-dim: rgba(11, 107, 147, 0.5);
    --danger: #eb1313;
    --danger-dim: rgba(235, 19, 19, 0.08);
    --danger-soft: #eb6d6d;
    --warning: #e0836e;
    --hover-accent: #b8460f;
    --icon-accent: #3a352f;
    --success: #2e7d32;
    --header-compass: #3a352f;
    --header-wordmark: #8a3a10;
    --header-sep1: #0b6b93;
    --header-pagetype: #8a3a10;
    --header-sep2: #0b6b93;
    --header-pagetitle: #0b6b93;
    --header-compass-hover: #8a3a10;
    --header-theme-toggle: #3a352f;
    --header-theme-toggle-hover: #8a3a10;
    --header-rule: #2A2A30;
    --footer-rule: #2A2A30;
  }

  /* symbolSize tokens — sizes for header icons, PNG watermarks, and page
     animations. Kept in its own :root block, separate from the dark/light
     theme block above, so symbolSize.cgi's regex-based rewrite can never
     collide with color.cgi's own (which rebuilds that whole block from its
     own fixed token list). See /symbolSize/index.html. */
                                                          :root {
    --symbol-compass-size: 32px;
    --symbol-theme-toggle-size: 24px;
    --symbol-watermark-default-pct: 50%;
    --symbol-watermark-default-max: 90px;
    --symbol-watermark-gallery-pct: 12%;
    --symbol-watermark-gallery-max: 640px;
    --symbol-watermark-formatcompare-pct: 12%;
    --symbol-watermark-formatcompare-max: 240px;
    --symbol-watermark-panoviewer-pct: 12%;
    --symbol-watermark-panoviewer-max: 240px;
    --symbol-watermark-formatchart-pct: 12%;
    --symbol-watermark-formatchart-max: 240px;
    --symbol-watermark-download-pct: 12%;
    --symbol-watermark-download-max: 640px;
    --symbol-contact-globe-size: 128px;
    --symbol-login-lock-size: 128px;
  }

  /* buttonStyles tokens — written by cgi-bin/buttonStyles.cgi from
     /buttons/index.html. One group per button family; each rule below reads
     these with its shipped value as the var() fallback, so deleting this whole
     block restores the original look rather than breaking anything.

     Kept in its own :root block, anchored by the comment above, so this
     script's rewrite can never collide with symbolSize.cgi's or color.cgi's.
     Do not merge it into another :root. */
  :root {
    --btn-export-bg: var(--surface);
    --btn-export-border: var(--border);
    --btn-export-fg: var(--text-muted);
    --btn-export-pad-y: 0.5rem;
    --btn-export-pad-x: 0.9rem;
    --btn-export-size: 0.75rem;
    --btn-export-tracking: 0.05em;
    --btn-export-radius: 0;
    --btn-export-hover-bg: var(--surface);
    --btn-export-hover-border: var(--gold);
    --btn-export-hover-fg: var(--gold);

    --btn-subdiv-bg: var(--surface);
    --btn-subdiv-border: var(--border);
    --btn-subdiv-fg: var(--text-muted);
    --btn-subdiv-pad-y: 0.45rem;
    --btn-subdiv-pad-x: 0.45rem;
    --btn-subdiv-radius: 0;

    --btn-ratio-bg: var(--surface);
    --btn-ratio-border: var(--border);
    --btn-ratio-fg: var(--text-muted);
    --btn-ratio-pad-y: 0.6rem;
    --btn-ratio-pad-x: 1.1rem;
    --btn-ratio-size: 0.78rem;
    --btn-ratio-tracking: 0.05em;
    --btn-ratio-radius: 0;

    --btn-wysiwyg-bg: var(--bg);
    --btn-wysiwyg-border: var(--border);
    --btn-wysiwyg-fg: var(--text);
    --btn-wysiwyg-pad-y: 0.45rem;
    --btn-wysiwyg-pad-x: 0.7rem;
    --btn-wysiwyg-size: 0.78rem;
    --btn-wysiwyg-tracking: 0.03em;
    --btn-wysiwyg-radius: 0;

    --btn-ldrow-bg: transparent;
    --btn-ldrow-border: var(--border);
    --btn-ldrow-fg: var(--text-muted);
    --btn-ldrow-pad-y: 0.15rem;
    --btn-ldrow-pad-x: 0.55rem;
    --btn-ldrow-size: 0.68rem;
    --btn-ldrow-tracking: 0.06em;
    --btn-ldrow-radius: 0;

    --btn-affirm-bg: transparent;
    --btn-affirm-border: var(--gold);
    --btn-affirm-fg: var(--gold);
    --btn-affirm-pad-y: 0.45rem;
    --btn-affirm-pad-x: 0.6rem;
    --btn-affirm-size: 0.75rem;
    --btn-affirm-tracking: 0.05em;
    --btn-affirm-radius: 0;
    --btn-affirm-hover-bg: var(--gold-dim);

    --btn-admin-bg: transparent;
    --btn-admin-border: var(--danger);
    --btn-admin-fg: var(--danger);
    --btn-admin-size: 0.85rem;
    --btn-admin-radius: 0;
    --btn-admin-hover-bg: var(--danger);
    --btn-admin-hover-fg: var(--bg);

    --btn-overlay-bg: rgba(0, 0, 0, 0.45);
    --btn-overlay-border: var(--border);
    --btn-overlay-fg: var(--text);
    --btn-overlay-pad-y: 0.3rem;
    --btn-overlay-pad-x: 0.55rem;
    --btn-overlay-size: 0.72rem;
    --btn-overlay-tracking: 0.04em;
    --btn-overlay-radius: 0;
  }

  html, body {
    height: 100%;
    background: var(--bg);
    color: var(--text);
    font-family: 'Roboto', sans-serif;
    font-weight: 300;
    -webkit-font-smoothing: antialiased;
  }

  body {
    position: relative;
    display: flex;
    flex-direction: column;
    height: 100vh;
    height: 100dvh;
    overflow: hidden;
  }

  /* -------------------------------------------------------------------------
     Fixed-width lock (desktop half of the pair)

     The <meta name="viewport" content="width=761"> on every page pins mobile
     browsers to a 761px layout and scales to fit. Desktop browsers ignore the
     meta tag entirely, so a narrowed desktop window would still reflow through
     the 640 / 600 / 480 / 440 breakpoints. This block stops that: below the
     lock point the shell holds 761px and the viewport scrolls horizontally
     instead of reflowing.

     761, not 760, because the breakpoint queries are max-width (inclusive) —
     at exactly 760 all twenty of the ~760px rules fire and we'd freeze the
     collapsed layout we're trying to avoid.

     Why the html rules are required: the viewport takes its overflow from
     <html>, falling back to <body> only when html's overflow computes to
     'visible'. html is currently unset (visible), so it's body's
     overflow:hidden that propagates up and suppresses all page scrolling.
     Setting an explicit overflow on html breaks that propagation — html
     becomes the horizontal scroll container, and body's overflow:hidden
     finally applies to body's own box rather than to the viewport.

     The height override is not cosmetic: a horizontal scrollbar eats ~15px of
     viewport height, which 100dvh does not account for, so the footer's lower
     edge would sit under the scrollbar and be clipped by html's overflow-y.
     height:100% resolves against html's content box, which the scrollbar has
     already shrunk, so the shell ends exactly where it should.

     Note this block can only ever engage on desktop: mobile is locked at 761,
     so a mobile browser never evaluates a viewport of 760 or less.
     ------------------------------------------------------------------------- */
  @media (max-width: 760px) { /* @shell-lock — this one must stay live */
    html {
      overflow-x: auto;
      overflow-y: hidden;
    }
    body {
      min-width: 761px;
      height: 100%;
    }
  }

  /* -------------------------------------------------------------------------
     DISABLED BREAKPOINTS — read this before editing any @media block below.

     While the site is locked (the normal state), every breakpoint below 761px
     is written as:

         @media (max-width: NNNpx) and (min-width: 761px)

     which is self-contradictory and therefore never matches. That is
     deliberate, not a typo, and swapViewport.pl adds and removes the clause.

     The reason it's necessary: a min-width on <body> alone does NOT stop the
     reflow, because media queries are evaluated against the viewport and never
     against an element's width. A narrowed desktop window would keep matching
     ~640px and rearranging things inside the locked 761px shell. Neutralizing
     the conditions is what actually holds the layout still.

     Left live either way: 900px, 860px, print, prefers-reduced-motion, and the
     @shell-lock block above. Those sit above the lock or are orthogonal to it.

     Toggle both halves — CSS and the viewport meta — together:
         perl swapViewport.pl --to=locked
         perl swapViewport.pl --to=fluid
     ------------------------------------------------------------------------- */

  /* Web-component wrappers must not create their own box; the inner
     <header>/<footer> stay as the real flex children of <body>. */
  phfx-header, phfx-footer { display: contents; }

  /* Header */
  header {
    position: relative;
    flex: 0 0 auto;
    z-index: 20;
    background: var(--bg);
    padding: 0.5rem 1rem; /* vertical 8px keeps the bar compact; horizontal stays 1rem so .header-inner aligns with main's own 1rem gutter */
  }
  /* The rule is drawn as a pseudo-element rather than a border on the bar
     itself, so it can stop at the content measure instead of running the full
     window width. 960px matches .header-inner / .footer-inner exactly; below
     that the calc keeps it inset by the same 1rem gutter the content uses, so
     the line and the content edges stay flush at every width.

     --header-rule, not --border: that token is shared by every card, panel,
     table rule and modal, so tuning the chrome through it would drag the
     whole site along. Falls back to --border if the token is ever missing. */
  header::after,
  footer::before {
    content: '';
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    width: calc(100% - 2rem);
    max-width: 960px;
    height: 1px;
  }
  header::after {
    bottom: 0;
    height: var(--header-rule-width, 1px);
    background: var(--header-rule, var(--border));
  }
  footer::before {
    top: 0;
    background: var(--footer-rule, var(--border));
  }

  /* Defocus strip.

     The footer sits below main in the flex column, so nothing ever scrolls
     underneath it - a backdrop-filter on the bar itself would have no
     backdrop to work on. Instead this sits directly ABOVE the footer,
     overlapping the last stretch of main, so content blurs as it approaches
     the bar rather than being cut off hard at the rule.

     Blur only, no background: an earlier version layered a gradient to --bg
     over the top to dim as well as defocus, but that read as a shadow above
     the footer rather than as depth. The mask still fades the blur out
     upward so there is no visible edge where the effect begins. */
  footer::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 100%;
    height: var(--footer-defocus-height, 3rem);
    pointer-events: none;
    z-index: 1;
    backdrop-filter: blur(var(--footer-defocus-blur, 5px));
    -webkit-backdrop-filter: blur(var(--footer-defocus-blur, 5px));
    -webkit-mask-image: linear-gradient(to top, #000 0%, #000 25%, transparent 100%);
            mask-image: linear-gradient(to top, #000 0%, #000 25%, transparent 100%);
  }
  .header-inner {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 1rem;
    min-height: var(--header-row); /* lock consistent header height across all pages, with or without header buttons */
  }

  .brand {
    display: flex;
    align-items: center;
    gap: 0.65rem;
  }
  .brand-sep {
    color: var(--header-sep1);
    font-size: 1rem;
    font-family: 'Roboto', sans-serif;
  }
  .brand a {
    text-decoration: none;
  }
  .brand a:hover {
    text-decoration: underline;
  }
  .subtitle a {
    color: var(--header-pagetype);
    text-transform: uppercase;
    font-size: 1rem;
    font-weight: 400;
    letter-spacing: 0.2em;
  }
  .subtitle .current {
    color: var(--current);
    text-transform: uppercase;
    font-size: 1rem;
    font-weight: 400;
    letter-spacing: 0.2em;
  }
  .subtitle .ff {
    color: var(--header-pagetitle);
    font-family: 'Roboto', sans-serif;
    font-size: 1rem;
  }

  .subtitle .name {
    color: var(--current);
    font-family: 'Roboto', sans-serif;
    font-size: 1rem;
  }
  .subtitle .dot {
    color: var(--header-sep2);
    font-size: 1rem;
  }

  .header-links {
    display: flex;
    gap: 0.5rem;
  }

  .header-link {
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--text-muted);
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    letter-spacing: 0.05em;
    padding: 0.55rem 0.95rem;
    height: var(--header-row); /* deterministic button height == reserved header row, so PHFX sits identically on every page */
    border-radius: 0;
    cursor: pointer;
    text-decoration: none;
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: all 0.18s ease;
  }

  .header-link:hover {
    border-color: var(--gold);
    color: var(--text);
  }

  .wordmark {
    font-family: 'Roboto', sans-serif;
    font-size: 1rem;
    font-weight: 400;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--header-wordmark);
  }

  .subtitle {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--text-muted);
    letter-spacing: 0.05em;
  }

  /* Main layout */
  main {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: safe center;
    padding: 1rem;
    /* Clearance for the footer, which now overlays the bottom of this box.
       Without it the last line of content could never be scrolled out from
       under the bar. --footer-h is measured live in JS. */
    padding-bottom: calc(1rem + var(--footer-h, 3.5rem));
    gap: 3rem;
    scrollbar-width: none;    /* Firefox: hide native bar, custom overlay replaces it */
    -ms-overflow-style: none; /* legacy Edge/IE */
  }
  main::-webkit-scrollbar { display: none; } /* Chromium/Safari: hide native bar */

  /* Custom overlay scrollbar (see phfx_2026_V01.js). Fixed-position so it never
     consumes layout width — header/main/footer stay pixel-aligned regardless
     of whether the page scrolls. */
  .phfx-scrollbar-track {
    position: fixed;
    right: 3px;
    width: 6px;
    z-index: 25;
    pointer-events: none;
  }
  .phfx-scrollbar-thumb {
    position: absolute;
    left: 0;
    width: 100%;
    background: var(--border);
    pointer-events: auto;
    cursor: pointer;
    transition: background 0.15s ease;
  }
  .phfx-scrollbar-thumb:hover,
  .phfx-scrollbar-thumb.dragging {
    background: var(--gold);
  }

  /* Tools lander grid */
  /* Lander page: top-aligned (not vertically centered), with a full-width
     intro box above the grid. */
  .tools-page { justify-content: flex-start; gap: 1rem; }
  .tools-intro {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    border: 1px solid var(--border);
    background: var(--surface);
    border-radius: 0;
    padding: 1.25rem 1.5rem;
    color: var(--text);
    font-family: 'Roboto', sans-serif;
    font-weight: 300;
    font-size: 0.95rem;
    line-height: 1.6;
  }
  .tools-intro p { margin: 0; }
  .tools-intro p + p { margin-top: 0.8rem; }
  /* Collapsible intro: the welcome line + toggle stay visible; body expands. */
  details.tools-intro > summary {
    list-style: none;
    cursor: pointer;
  }
  details.tools-intro > summary::-webkit-details-marker { display: none; }
  .tools-intro-summary {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5rem 0.6rem;
  }
  .tools-intro-toggle {
    color: var(--gold);
    font-size: 0.85rem;
  }
  .tools-intro-summary:hover .tools-intro-toggle { color: var(--text); }
  .tools-intro-toggle::after { content: 'Expand Introduction.'; }
  details.tools-intro[open] .tools-intro-toggle::after { content: 'Collapse Introduction.'; }
  .tools-intro-body { margin-top: 0.85rem; }
  /* Optional picture inside an intro block. Full width of the intro box,
     natural aspect — no crop, since these tend to be wide production stills
     whose own burned-in caption runs along the bottom edge and would be the
     first thing a fixed aspect ratio cut off. */
  .intro-figure {
    margin: 0 0 0.9rem;
    width: 100%;
    line-height: 0;
  }
  .intro-figure img {
    display: block;
    width: 100%;
    height: auto;
    border: 1px solid var(--border);
  }
  .intro-figure[hidden] { display: none; }
  .tools-intro-sign { font-family: 'Roboto', sans-serif; color: var(--gold); letter-spacing: 0.05em; text-align: right; }
  .tool-grid {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 1rem;
  }

  .tool-card {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-height: 7rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0;
    padding: 1.1rem 1.2rem;
    text-decoration: none;
    color: var(--text);
    transition: border-color 0.18s ease, transform 0.18s ease,
                box-shadow 0.18s ease, background 0.18s ease;
  }

  .tool-card:hover {
    border-color: var(--gold);
    background: var(--surface-hover);
    transform: translateY(-2px);
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35);
  }

  .tool-card:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
  }

  .tool-name {
    font-family: 'Roboto', sans-serif;
    font-size: 0.95rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    color: var(--gold);
  }

  .tool-desc {
    font-family: 'Roboto', sans-serif;
    font-size: 0.8rem;
    line-height: 1.45;
    color: var(--text-muted);
  }

  /* Coming-soon tiles: visible but inert until the tool ships */
  .tool-card.is-soon {
    opacity: 0.5;
    pointer-events: none;
  }
  .tool-card.is-soon .tool-name {
    color: var(--text);
  }

  .tool-soon {
    position: absolute;
    top: 0.75rem;
    right: 0.75rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-muted);
    border: 1px solid var(--border);
    border-radius: 0;
    padding: 0.1rem 0.5rem;
    line-height: 1.4;
  }

  @media (max-width: 900px) {
    .tool-grid { grid-template-columns: repeat(3, 1fr); }
  }
  @media (max-width: 640px) and (min-width: 761px) {
    .tool-grid { grid-template-columns: repeat(2, 1fr); }
  }
  @media (max-width: 440px) and (min-width: 761px) {
    .tool-grid { grid-template-columns: 1fr; }
  }

  /* ===== legend (admin reference) page — fixed 7:5 button aspect ratio ===== */
  .legend-page .tool-card { aspect-ratio: 7 / 5; min-height: 0; justify-content: center; align-items: center; text-align: center; }
  .legend-page .tool-card .tool-name { font-size: 1.05rem; }

  /* Display + Delivery controls (side by side) */
  .controls-row {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    display: flex;
    flex-direction: row;
    align-items: stretch;
    gap: 0;
    border: 1px solid var(--border);
    border-radius: 0;
    background: var(--surface);
  }

  /* Display aspect resolution selector */
  .display-section {
    flex: 0 0 33.333%;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.85rem;
    padding: 1.1rem 1.25rem;
  }

  .display-label {
    font-size: 0.75rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-muted);
    font-family: 'Roboto', sans-serif;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
  }

  .display-selector {
    display: flex;
    gap: 0.75rem;
    flex: 1 1 auto;
    align-items: center;
    justify-content: center;
    /* Without an explicit width this sits in a column flex with
       align-items:center, so it is shrink-to-fit — it sizes to its own
       content and punches out of .display-section instead of being
       constrained by it. 100% gives the buttons a definite box to shrink
       against, which is what makes flex-shrink do anything at all here. */
    width: 100%;
    min-width: 0;
  }

  .display-btn {
    background: none;
    border: none;
    color: var(--text-muted);
    cursor: pointer;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 0;
    transition: color 0.18s ease;
  }

  .display-btn:hover {
    color: var(--text);
  }

  .display-btn.active {
    color: var(--gold);
  }

  /* The aspect-ratio box contains the caption text.

     135px is a cap, not a fixed size. .display-section is flex 0 0 33.333% of
     a row capped at 960px, so its content box tops out near 280px — while two
     135px boxes plus the 0.75rem gap need 282px. The old fixed width therefore
     overflowed even at maximum width (by ~2px, hidden against the divider) and
     the overflow grew as the window narrowed. Sizing is split by orientation
     below so the landscape pair can shrink while the rotated pair, which is
     height-driven, keeps its proportions. */
  .display-icon-inner {
    border: 1.5px solid currentColor;
    border-radius: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    letter-spacing: 0.03em;
    text-align: center;
    box-sizing: border-box;
    /* width/height come from the orientation rules; height from aspect-ratio */
  }

  /* Landscape: the two boxes share the row and shrink together. 135px is the
     flex-basis, so they render at full size when there's room and give way
     proportionally when there isn't. */
  .display-selector:not(.rotated) .display-btn { flex: 0 1 135px; }
  .display-selector:not(.rotated) .display-icon-inner { width: 100%; height: auto; }

  /* Rotated: height-driven, width follows from the inline aspect-ratio, so
     these come out at 135/ratio (~76px each) and have room to spare. */
  .display-selector.rotated .display-btn { flex: 0 1 auto; }
  .display-selector.rotated .display-icon-inner {
    width: auto;
    height: 135px;
    max-width: 100%;
  }

  /* Composition subdivision toggles */
  .subdiv-section {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.85rem;
    width: 100%;
  }

  .subdiv-label {
    font-size: 0.75rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-muted);
    font-family: 'Roboto', sans-serif;
  }

  .subdiv-selector {
    display: grid;
    grid-template-columns: repeat(3, auto);
    gap: 0.5rem;
    justify-content: center;
    justify-items: center;
  }

  /* Bordered control deck */
  .control-deck {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    border: 1px solid var(--border);
    border-radius: 0;
    background: var(--surface);
  }
  .deck-cols {
    display: flex;
    flex-direction: row;
    align-items: stretch;
    justify-content: center;
    /* Four columns plus three dividers don't fit at the 761px lock with a flat
       1.25rem gap — the min-width floors need 430px and only ~421px is left,
       so the row overflowed by ~19px no matter what the columns did. The vw
       term reaches the 1.25rem ceiling at ~953px viewport, so anything from
       960 up is byte-for-byte the old spacing; only the 761–950 band tightens. */
    gap: clamp(0.6rem, 2.1vw, 1.25rem);
    padding: 1.1rem clamp(0.6rem, 2.1vw, 1.25rem);
  }
  .deck-col {
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    min-width: 0;
  }
  .deck-col .subdiv-label {
    align-self: center;
  }
  .deck-col-guide {
    flex: 0 0 auto;
    align-items: center;
  }
  .deck-col-guide .subdiv-label {
    align-self: center;
  }
  .deck-col-titles {
    flex: 1 1 160px;
    min-width: 140px;
  }
  .deck-col-settings {
    flex: 1 1 160px;
    /* Matched to .deck-col-titles. The old 150px floor existed to hold the
       selects' intrinsic width; now that .settings-grid lets them shrink,
       the column doesn't need the extra 10px, and giving it back is what
       buys the deck its headroom at the lock width. */
    min-width: 140px;
  }
  .deck-col-output {
    flex: 1 1 160px;
    min-width: 150px;
  }
  .deck-divider {
    flex: 0 0 auto;
    align-self: stretch;
    width: 1px;
    background: var(--border);
  }
  /* Horizontal counterpart, for a control deck that runs more than one row of
     columns. Same colour and weight as the vertical rule between columns, so
     the grid of controls reads as one lattice. */
  .deck-divider-h {
    width: 100%;
    height: 1px;
    background: var(--border);
    margin: 1rem 0;
  }
  .settings-grid {
    display: grid;
    /* minmax(0, 1fr), not 1fr. Plain '1fr' means minmax(auto, 1fr), and that
       auto floor is the item's min-content width — which for a <select> is set
       by its LONGEST OPTION ('Bottom Middle Third', 'Hanken Grotesque'). The
       track therefore refused to shrink below ~160px and pushed the whole
       column off the page. The 0 floor lets the track follow the column.

       .field needs min-width:0 for the same reason one level down: grid items
       default to min-width:auto, so the field would keep the select's
       intrinsic width even once the track could shrink. This is exactly what
       .field-inline .extraction-range already does, which is why the Frame
       Output sliders never had this problem. */
    grid-template-columns: minmax(0, 1fr);
    gap: 0.7rem;
    width: 100%;
  }
  .settings-grid > .field {
    min-width: 0;
  }
  .deck-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    flex-wrap: wrap;
    padding: 0.75rem 1.25rem;
    border-top: 1px solid var(--border);
  }
  .deck-actions-left,
  .deck-actions-right {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
  }

  .export-buttons,
  .export-guide {
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }
  .res-readout {
    display: flex;
    align-items: center;
    gap: 1rem;
    border: 1px solid var(--border);
    border-radius: 0;
    padding: 0.5rem 0.75rem;
    background: var(--surface);
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--text);
    white-space: nowrap;
  }
  .res-readout[hidden] { display: none; }
  .res-readout-label {
    color: var(--text-muted);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    margin-right: 0.4rem;
  }
  .res-readout-lead {
    color: var(--gold);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    font-weight: 700;
  }
  .res-readout-dsq .res-readout-lead { color: var(--current); }
  .field {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.3rem;
    width: 100%;
  }
  .field .text-input,
  .field .export-select {
    width: 100%;
    box-sizing: border-box;
  }
  .field-inline {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
  }
  .field-row {
    flex-direction: row;
    align-items: center;
    gap: 0.5rem;
  }
  .field-row .extraction-range {
    flex: 1 1 auto;
    min-width: 0;
  }
  .field-inline .extraction-range {
    flex: 1 1 auto;
    min-width: 0;
  }
  @media (max-width: 760px) and (min-width: 761px) {
    .deck-cols {
      display: grid;
      grid-template-columns: 1fr 1fr;
      align-items: start;
      gap: 1rem;
    }
    .deck-col {
      min-width: 0;
    }
    .deck-divider {
      display: none;
    }
    .deck-col-guide {
      align-items: center;
    }
    .deck-col-settings .field .export-select {
      width: auto;
      max-width: 100%;
    }
    .deck-actions {
      justify-content: center;
    }
    .deck-actions-right {
      flex-direction: column;
      align-items: center;
    }
  }

  .subdiv-btn {
    background: var(--btn-subdiv-bg, var(--surface));
    border: 1px solid var(--btn-subdiv-border, var(--border));
    color: var(--btn-subdiv-fg, var(--text-muted));
    padding: var(--btn-subdiv-pad-y, 0.45rem) var(--btn-subdiv-pad-x, 0.45rem);
    cursor: pointer;
    border-radius: var(--btn-subdiv-radius, 0);
    transition: all 0.18s ease;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 0;
  }

  .subdiv-btn svg {
    width: 26px;
    height: 26px;
    display: block;
  }

  .subdiv-btn:hover {
    border-color: var(--gold);
    color: var(--text);
  }

  .subdiv-btn.active {
    border-color: var(--gold);
    color: var(--gold);
    background: var(--gold-dim);
  }

  /* Export guide controls */
  .export-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    justify-content: center;
    margin-top: 0.25rem;
  }

  .export-label {
    font-size: 0.75rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-muted);
    font-family: 'Roboto', sans-serif;
  }
  .negfill-label {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.75rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-muted);
    font-family: 'Roboto', sans-serif;
    cursor: pointer;
    user-select: none;
  }
  .negfill-check {
    accent-color: var(--gold);
    cursor: pointer;
    margin: 0;
  }

  .export-select {
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--text);
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    padding: 0.5rem 0.6rem;
    border-radius: 0;
    cursor: pointer;
  }

  .export-btn {
    background: var(--btn-export-bg, var(--surface));
    border: 1px solid var(--btn-export-border, var(--border));
    color: var(--btn-export-fg, var(--text-muted));
    font-family: 'Roboto', sans-serif;
    font-size: var(--btn-export-size, 0.75rem);
    letter-spacing: var(--btn-export-tracking, 0.05em);
    padding: var(--btn-export-pad-y, 0.5rem) var(--btn-export-pad-x, 0.9rem);
    cursor: pointer;
    border-radius: var(--btn-export-radius, 0);
    /* Named properties, not `all`. `all` also transitions width and padding,
       so any layout change while the pointer is over a button — a sibling's
       label changing, the row rewrapping — animates the button's box and the
       control slides out from under the cursor mid-hover. */
    transition: border-color 0.18s ease, color 0.18s ease, background 0.18s ease;
  }

  .export-btn:hover {
    background: var(--btn-export-hover-bg, var(--surface));
    border-color: var(--btn-export-hover-border, var(--gold));
    color: var(--btn-export-hover-fg, var(--gold));
  }

  .extraction-range {
    accent-color: var(--gold);
    width: 110px;
    cursor: pointer;
  }

  .extraction-value {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--gold);
    min-width: 2.8em;
    text-align: left;
  }

  .export-divider {
    width: 1px;
    align-self: stretch;
    background: var(--border);
    margin: 0 0.35rem;
  }

  /* Custom text overlay controls */
  .overlay-heading {
    font-size: 0.75rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-muted);
    font-family: 'Roboto', sans-serif;
    margin-top: 1.1rem;
    margin-bottom: 0.55rem;
  }
  .overlay-row {
    margin-top: 0;
  }
  /* Mobile-only forced line breaks within the text overlay controls */
  .overlay-break {
    display: none;
  }
  @media (max-width: 600px) and (min-width: 761px) {
    .overlay-row {
      align-items: flex-start;
    }
    .overlay-row .overlay-break {
      display: block;
      flex-basis: 100%;
      width: 100%;
      height: 0;
    }
    .overlay-row .export-divider {
      display: none;
    }
  }
  .text-input {
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--text);
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    letter-spacing: 0.03em;
    padding: 0.4rem 0.5rem;
    border-radius: 0;
    width: 9.5rem;
    max-width: 40vw;
  }
  .text-input::placeholder {
    color: var(--text-muted);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    opacity: 0.7;
  }
  .text-input:focus {
    outline: none;
    border-color: var(--gold);
  }
  .ta-input {
    width: 100%;
    max-width: none;
    resize: vertical;
    min-height: 4.2rem;
    line-height: 1.4;
    font-family: 'Roboto', sans-serif;
  }

  /* On-frame custom text (Title + Note) */
  .frame-text {
    position: absolute;
    transform: translate(-50%, -50%);
    text-align: center;
    pointer-events: none;
    white-space: nowrap;
    line-height: 1.15;
    z-index: 3;
    display: none;
  }
  .frame-title,
  .frame-note {
    color: #ebebeb;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.75);
  }
  .frame-note {
    margin-top: 0.2em;
  }
  .frame-description {
    color: #ebebeb;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.75);
    white-space: pre-line;
  }

  /* Rotate icons next to the section labels */
  .material-symbols-outlined {
    font-family: 'Material Symbols Outlined';
    font-weight: normal;
    font-style: normal;
    line-height: 1;
    letter-spacing: normal;
    text-transform: none;
    display: inline-block;
    white-space: nowrap;
    direction: ltr;
    font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
    font-size: 1.1rem;
  }

  .rotate-icon {
    background: none;
    border: none;
    padding: 0;
    margin: 0;
    cursor: pointer;
    color: inherit;
    display: inline-flex;
    align-items: center;
    line-height: 1;
    transition: color 0.18s ease, transform 0.18s ease;
  }

  .rotate-icon:hover {
    color: var(--text);
  }

  .rotate-icon.active {
    color: var(--gold);
  }

  /* Ratio selector */
  .ratio-section {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.85rem;
    flex: 0 0 33.333%;
    min-width: 0;
    padding: 1.1rem 1.25rem;
    border-left: 1px solid var(--border);
  }
  .info-section-top {
    flex: 0 0 33.333%;
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 0.85rem;
    padding: 1.1rem 1.25rem;
    border-left: 1px solid var(--border);
  }
  .info-section-top .info-item {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
  }
  .info-section-top .info-section-hr {
    width: 100%;
    height: 0;
    margin: 0;
    border: 0;
    border-top: 1px solid var(--border);
  }

  .ratio-label {
    font-size: 0.75rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-muted);
    font-family: 'Roboto', sans-serif;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
  }

  .ratio-selector {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 0.5rem;
    width: 100%;
  }
  /* Custom Frame Aspect Ratio dropdown (replaces the native select for full
     control over hover / selected colors). */
  .ratio-dd { position: relative; min-width: 200px; max-width: 100%; }
  .ratio-section .ff-custom-ratio { width: 200px; max-width: 100%; box-sizing: border-box; text-align: center; }
  .ratio-dd-trigger {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--text);
    font-family: 'Roboto', sans-serif;
    font-size: 0.78rem;
    letter-spacing: 0.05em;
    padding: 0.5rem 0.7rem;
    border-radius: 0;
    cursor: pointer;
    text-align: left;
    transition: border-color 0.18s ease;
  }
  .ratio-dd-trigger:hover,
  .ratio-dd.open .ratio-dd-trigger { border-color: var(--gold); }
  .ratio-dd-caret { flex: none; color: var(--text-muted); transition: transform 0.18s ease; }
  .ratio-dd.open .ratio-dd-caret { transform: rotate(180deg); }
  .ratio-dd-list {
    position: absolute;
    top: calc(100% + 3px);
    left: 0;
    right: 0;
    z-index: 100;
    margin: 0;
    padding: 0;
    list-style: none;
    background: var(--surface);
    border: 1px solid var(--border);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
    max-height: 320px;
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: var(--border) transparent;
    display: none;
  }
  .ratio-dd.open .ratio-dd-list { display: block; }
  .ratio-dd.up .ratio-dd-list { top: auto; bottom: calc(100% + 3px); }
  .ratio-dd-list::-webkit-scrollbar { width: 8px; }
  .ratio-dd-list::-webkit-scrollbar-track { background: transparent; }
  .ratio-dd-list::-webkit-scrollbar-thumb { background: var(--border); border: 2px solid var(--surface); }
  .ratio-dd-list::-webkit-scrollbar-thumb:hover { background: var(--gold); }
  .ratio-dd-option {
    padding: 0.45rem 0.7rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.78rem;
    letter-spacing: 0.05em;
    color: var(--text);
    cursor: pointer;
  }
  .ratio-dd-option.selected { color: var(--text); }
  .ratio-dd-option:hover,
  .ratio-dd-option.selected:hover { background: var(--gold); color: var(--surface); }

  /* Generic custom dropdown — native selects enhanced to match the ratio dropdown. */
  .custom-dd { position: relative; width: 100%; max-width: 100%; }
  .custom-dd.is-disabled { opacity: 0.4; pointer-events: none; }
  .custom-dd-option.is-disabled { opacity: 0.5; cursor: not-allowed; }
  /* Frame & Focus export-line + theme dropdown widths live on the <select> so
     the shared enhancer measures and reproduces them on the custom dropdown.
     Scoped to .ff-stage so Shot Prep's shared .export-guide/.deck-actions-left
     dropdowns keep their own content-sized widths. */
  .ff-stage .deck-actions-left > .custom-dd { flex: 0 0 auto; }
  .ff-stage .deck-actions-left > .export-select { width: 5rem; }
  .ff-stage .export-guide .export-select { width: 10rem; }
  /* Frame & Focus Guide Theme: label + dropdown on one line, dropdown sized to fit. */
  .ff-theme-field { flex-direction: row; align-items: center; justify-content: space-between; gap: 0.5rem; }
  .ff-theme-field .custom-dd { flex: 0 0 auto; }
  .ff-theme-field .export-select { width: 6.5rem; }
  .custom-dd-trigger {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--text);
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    padding: 0.5rem 0.6rem;
    border-radius: 0;
    cursor: pointer;
    text-align: left;
    transition: border-color 0.18s ease;
  }
  .custom-dd-trigger:hover,
  .custom-dd.open .custom-dd-trigger { border-color: var(--gold); }
  .custom-dd-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .custom-dd-caret { flex: none; color: var(--text-muted); transition: transform 0.18s ease; }
  .custom-dd.open .custom-dd-caret { transform: rotate(180deg); }
  .custom-dd-list {
    position: absolute;
    top: calc(100% + 3px);
    left: 0;
    right: 0;
    z-index: 100;
    margin: 0;
    padding: 0;
    list-style: none;
    background: var(--surface);
    border: 1px solid var(--border);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
    max-height: 320px;
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: var(--border) transparent;
    display: none;
  }
  .custom-dd.open .custom-dd-list { display: block; }
  .custom-dd.up .custom-dd-list { top: auto; bottom: calc(100% + 3px); }
  .custom-dd-list::-webkit-scrollbar { width: 8px; }
  .custom-dd-list::-webkit-scrollbar-track { background: transparent; }
  .custom-dd-list::-webkit-scrollbar-thumb { background: var(--border); border: 2px solid var(--surface); }
  .custom-dd-list::-webkit-scrollbar-thumb:hover { background: var(--gold); }
  .custom-dd-option {
    padding: 0.45rem 0.6rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--text);
    cursor: pointer;
    white-space: nowrap;
  }
  .custom-dd-option.selected { color: var(--text); }
  .custom-dd-option:hover,
  .custom-dd-option.selected:hover { background: var(--gold); color: var(--surface); }

  .ratio-btn {
    background: var(--btn-ratio-bg, var(--surface));
    border: 1px solid var(--btn-ratio-border, var(--border));
    color: var(--btn-ratio-fg, var(--text-muted));
    font-family: 'Roboto', sans-serif;
    font-size: var(--btn-ratio-size, 0.78rem);
    letter-spacing: var(--btn-ratio-tracking, 0.05em);
    padding: var(--btn-ratio-pad-y, 0.6rem) var(--btn-ratio-pad-x, 1.1rem);
    cursor: pointer;
    border-radius: var(--btn-ratio-radius, 0);
    transition: all 0.18s ease;
    position: relative;
    overflow: hidden;
  }

  .ratio-btn::before {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--gold-dim);
    opacity: 0;
    transition: opacity 0.18s ease;
  }

  .ratio-btn:hover {
    border-color: var(--gold);
    color: var(--text);
  }

  .ratio-btn:hover::before { opacity: 1; }

  .ratio-btn.active {
    border-color: var(--gold);
    color: var(--gold);
    background: var(--gold-dim);
  }

  .ratio-btn.active::before { opacity: 1; }

  /* Frame stage */
  .stage {
    width: 100%;
    max-width: 960px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1.5rem;
    margin-top: -1.5rem;
  }

  /* Summary label above the preview */
  .preview-summary {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    font-weight: 400;
    letter-spacing: 0.03em;
    line-height: 1.5;
    color: var(--text-muted);
    text-align: center;
  }

  .preview-summary .em {
    color: var(--gold);
    font-weight: 400;
  }

  /* Fixed footprint — never changes size, so the page never reflows.
     Its aspect ratio equals the TALLEST display option (smallest ratio)
     so every display shape fits inside, width-limited. */
  .frame-bounds {
    width: 100%;
    aspect-ratio: 16 / 9;
    margin: 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  /* Aspect Ratio Information beside the (smaller) preview */
  .preview-row {
    width: 100%;
    max-width: 960px;
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    gap: 1.5rem;
  }
  .preview-row .info-section {
    flex: 0 0 33.333%;
    width: auto;
    align-items: stretch;
  }
  .preview-row .frame-bounds {
    flex: 1 1 0;
    min-width: 0;
    width: auto;
  }
  @media (max-width: 760px) and (min-width: 761px) {
    .controls-row {
      flex-direction: column;
      align-items: center;
      gap: 1rem;
    }
    .controls-row .display-section,
    .controls-row .ratio-section {
      flex: none;
      width: 100%;
    }
    .controls-row .display-selector {
      /* Lock the display box area to the desktop box width (135px) so rotating
         the display aspect ratio doesn't change this area's height and shove
         the rest of the layout. The box stays centered within it. */
      min-height: 135px;
    }
    .controls-row .ratio-section {
      border-left: none;
      border-top: 1px solid var(--border);
    }
    .preview-row {
      flex-direction: column;
      align-items: center;
    }
    .preview-row .info-section {
      flex: none;
      width: 100%;
    }
    .preview-row .frame-bounds {
      width: 100%;
      flex: none;
    }
  }

  /* The selected display shape — fits within the fixed bounds and is
     centered. Its aspect ratio is set dynamically in JS. */
  .frame-wrapper {
    /* width/height set in JS to contain the display box within bounds */
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    overflow: hidden;
    background-color: var(--frame-bg);
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    /* Display box guide */
    border: 1px solid rgba(235, 208, 129, 0.5);
    border-radius: 0;
  }

  .frame {
    border: 1.5px solid rgba(var(--guide-rgb, 235, 208, 129), var(--frame-border-alpha, 1));
    background: transparent;
    /* Shade the area outside the delivery frame (clipped to the display box) */
    box-shadow: 0 0 0 9999px rgba(0, 0, 0, var(--negfill-alpha, 0.5));
    position: relative;
    transition: width 0.45s cubic-bezier(0.4, 0, 0.2, 1),
                height 0.45s cubic-bezier(0.4, 0, 0.2, 1);
    overflow: hidden;
  }

  /* Composition subdivision guides */
  .frame-guides {
    position: absolute;
    inset: 0;
    pointer-events: none;
  }
  .guide-line {
    position: absolute;
    border-color: rgba(235, 208, 129, 0.6666);
    border-style: var(--guide-style, solid);
  }
  .guide-h {
    left: 0;
    width: 100%;
    height: 0;
    border-top-width: var(--guide-thickness, 1px);
    transform: translateY(-0.5px);
  }
  .guide-v {
    top: 0;
    height: 100%;
    width: 0;
    border-left-width: var(--guide-thickness, 1px);
    transform: translateX(-0.5px);
  }

  /* Center cross arms */
  .guide-cross-h, .guide-cross-v {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    border-color: rgba(235, 208, 129, 0.6666);
    border-style: var(--guide-style, solid);
  }
  .guide-cross-h { height: 0; border-top-width: var(--guide-thickness, 1px); }
  .guide-cross-v { width: 0; border-left-width: var(--guide-thickness, 1px); }

  /* Center mass circle */
  .guide-circle {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    border-width: var(--guide-thickness, 1px);
    border-style: var(--guide-style, solid);
    border-color: rgba(235, 208, 129, 0.6666);
    border-radius: 50%;
    box-sizing: border-box;
  }

  /* Edge center ticks (perpendicular, pointing inward) */
  .guide-edge {
    position: absolute;
    border-color: rgba(235, 208, 129, 0.6666);
    border-style: var(--guide-style, solid);
  }
  /* top & bottom: vertical lines (length set inline) */
  .guide-edge-top, .guide-edge-bottom {
    left: 50%;
    transform: translateX(-50%);
    width: 0;
    border-left-width: var(--guide-thickness, 1px);
  }
  /* left & right: horizontal lines (length set inline) */
  .guide-edge-left, .guide-edge-right {
    top: 50%;
    transform: translateY(-50%);
    height: 0;
    border-top-width: var(--guide-thickness, 1px);
  }

  /* X diagonals */
  .guide-x {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
  }
  .guide-x line {
    stroke: rgba(235, 208, 129, 0.6666);
    stroke-width: var(--guide-thickness, 1);
  }

  /* Watermark logo, bottom-right of the frame */
  .frame-logo {
    position: absolute;
    right: 8px;
    bottom: 8px;
    /* 12% of the frame, capped at 640px, everywhere. The watermark used to
       size differently per context - 50%/90px here, 128px on the tool stages,
       200px in the panorama viewer, 12%/640px in the gallery - which meant it
       read as a different mark depending on where you met it. One pair of
       numbers makes it the same mark throughout, scaling with its frame rather
       than with which page it happens to be on. */
    width: var(--symbol-watermark-default-pct, 12%);
    max-width: var(--symbol-watermark-default-max, 640px);
    height: auto;
    pointer-events: none;
    filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.55));
  }

  /* Per-image watermark placement, set from a gallery image's .ph file.

     Applied by gallery.js / view.html and by the home hero in index.html —
     shotPrep and frameAndFocus build their own .frame-logo and never add
     these classes, so their watermarks keep the bottom-right rule above
     untouched.

     Each alternative resets `right` before setting its own anchor, since the
     base rule's right:8px would otherwise stretch the element across the
     frame. The 8px inset matches the download compositor in gallery.js,
     which uses 0.008 of the image width for the same edge gap. */
  .frame-logo-left {
    right: auto;
    left: 8px;
  }
  .frame-logo-center {
    right: auto;
    left: 50%;
    transform: translateX(-50%);
  }

  /* Canvas-drawn Title/Note/Description overlay (titleCard tool) — a canvas
     rather than DOM text so positioning can use exact glyph-metric bounds
     (ctx.measureText) instead of CSS line-height, which always leaves extra
     leading space above/below the visible characters. Sits alongside the
     logo outside #frame's clipping, so Display-constrained text can extend
     into letterbox/pillarbox space exactly like the logo already can. */
  .text-overlay-canvas {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 3;
  }

  /* Info panel */
  .data-row {
    width: 100%;
    display: flex;
    gap: 1.5rem;
    align-items: flex-start;
    flex-wrap: wrap;
  }

  .data-row .info-section {
    flex: 1 1 260px;
  }

  .data-row .res-section {
    flex: 2 1 380px;
  }

  .info-section {
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.85rem;
  }

  .info-section-label {
    font-size: 0.75rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-muted);
    font-family: 'Roboto', sans-serif;
  }

  .info-panel {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1px;
    width: 100%;
    background: var(--border);
    border: 1px solid var(--border);
    border-radius: 0;
    overflow: hidden;
  }
  .info-panel-row {
    grid-template-columns: repeat(3, 1fr);
    max-width: 960px;
    margin: 0 auto;
  }
  @media (max-width: 600px) and (min-width: 761px) {
    .info-panel-row {
      grid-template-columns: 1fr;
    }
  }

  .info-cell {
    background: var(--surface);
    padding: 0.85rem 1.1rem;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
  }

  .info-label {
    font-size: 0.75rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-muted);
    font-family: 'Roboto', sans-serif;
  }

  .info-value {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--text);
    font-weight: 700;
    transition: color 0.2s ease;
  }

  .info-value.highlight { color: var(--gold); }

  /* Resolution table */
  .res-section {
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.85rem;
  }

  .data-row .info-section,
  .data-row .res-section {
    flex: 1 1 0;
    width: auto;
    min-width: 0;
  }
  @media (max-width: 600px) and (min-width: 761px) {
    .data-row { flex-direction: column; }
    .data-row .info-section,
    .data-row .res-section { flex: none; width: 100%; }
  }

  .res-label {
    font-size: 0.75rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-muted);
    font-family: 'Roboto', sans-serif;
  }

  .res-table-wrap {
    width: 100%;
    overflow-x: auto;
    border: 1px solid var(--border);
    border-radius: 0;
  }

  .res-table {
    width: 100%;
    border-collapse: collapse;
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    background: var(--surface);
  }

  .res-table th {
    font-size: 0.75rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-muted);
    font-weight: 400;
    padding: 0.65rem 1.1rem;
    border-bottom: 1px solid var(--border);
    background: var(--bg);
    white-space: nowrap;
    text-align: left;
  }

  .res-table td {
    padding: 0.55rem 1.1rem;
    color: var(--text);
    border-bottom: 1px solid var(--border);
    white-space: nowrap;
  }

  .res-table tr:last-child td { border-bottom: none; }

  .res-table th:not(:first-child),
  .res-table td:not(:first-child) { text-align: right; }

  .res-table .tier { color: var(--gold); font-weight: 700; }
  .res-table .res-container { color: var(--text-muted); }

  /* Footer

     Lifted out of the flex flow and pinned to the bottom of the viewport, so
     main's content scrolls UNDERNEATH it. That's what makes the translucency
     mean anything — while the footer was a flex row below main, nothing was
     ever behind it and a transparent background would have shown the page
     colour, i.e. no visible change at all.

     main gets padding-bottom of --footer-h (measured in JS, since the bar's
     height changes with the admin chain and with wrapping) so the last of the
     content can still be scrolled clear of the bar rather than being stuck
     permanently beneath it. */
  footer {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 20;
    padding: 1rem;
    background: color-mix(in srgb, var(--bg) var(--footer-opacity, 82%), transparent);
    backdrop-filter: blur(var(--footer-blur, 8px));
    -webkit-backdrop-filter: blur(var(--footer-blur, 8px));
  }
  /* No color-mix support: fall back to the opaque bar rather than an
     unreadable one. */
  @supports not (background: color-mix(in srgb, red 50%, transparent)) {
    footer { background: var(--bg); }
  }
  .footer-inner {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    display: flex;
    justify-content: center;
    align-items: center;
    text-align: center;
  }

  .footer-note {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--text-muted);
    letter-spacing: 0.04em;
  }

  .footer-link-text {
    color: var(--gold);
    text-decoration: none;
    transition: color 0.18s ease;
  }

  .footer-link-text:hover {
    text-decoration: underline;
  }

  /* SVG grain filter (hidden) */
  .grain-filter { position: absolute; width: 0; height: 0; }

  /* Responsive */
  @media (max-width: 600px) and (min-width: 761px) {
    header { padding: 0.5rem 1rem; }
    main { padding: 1rem; gap: 2rem; }
    .ratio-btn { font-size: 0.75rem; padding: 0.5rem 0.4rem; }
    .ratio-selector { grid-template-columns: repeat(5, 1fr); }
    footer { padding: 1rem; }
  }

  @media (prefers-reduced-motion: reduce) {
    .frame, .info-value, .fc-rect, .fc-stage { transition: none; }
  }

  /* ===================== formatCompare tool ===================== */
  .fc-page { justify-content: flex-start; gap: 1rem; }
  .fc-controls {
    padding: 1.25rem;
    display: flex;
    flex-direction: column;
    gap: 1.1rem;
  }
  .fc-format-grid {
    display: grid;
    grid-template-columns: 1fr 1px 1fr;
    gap: 1.25rem;
  }
  .fc-format-divider {
    align-self: stretch;
    width: 1px;
    background: var(--border);
  }
  .fc-format-col {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
  }
  .fc-anamorphic {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    margin-top: 0.25rem;
    padding-top: 0.85rem;
    border-top: 1px solid var(--border);
  }
  .fc-col-head {
    display: flex;
    align-items: center;
    gap: 0.6rem;
  }
  .fc-col-title {
    font-family: 'Roboto', sans-serif;
    font-size: 0.95rem;
    color: var(--gold);
    letter-spacing: 0.04em;
  }
  .fc-col-title.fc-two,
  .fc-spec-title.fc-two { color: var(--current); }
  .fc-col-sub {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--text-muted);
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }
  .fc-col-head .rotate-icon { margin-left: auto; }
  .fc-format-col .fc-col-head .rotate-icon { margin-left: 0; order: 1; }
  .fc-format-col .fc-col-head .fc-col-sub { margin-left: auto; order: 2; }
  .fc-fields {
    display: flex;
    flex-direction: row;
    gap: 0.5rem;
  }
  .fc-fields .fc-field {
    flex: 1 1 0;
    min-width: 0;
  }
  .fc-field {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
  }
  .fc-field-label {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--text-muted);
    letter-spacing: 0.05em;
  }
  .fc-field .export-select { width: 100%; }
  .fc-focal-input { width: 100%; max-width: none; }
  .fc-focal .fc-field { flex-direction: row; align-items: center; gap: 0.6rem; }
  .fc-focal .fc-field-label { white-space: nowrap; }
  .fc-focal .fc-focal-input { width: auto; flex: 1 1 auto; min-width: 0; }
  .fc-actions {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
  }
  .fc-focal-row {
    display: flex;
    gap: 1.25rem;
    align-items: flex-end;
  }
  .fc-focal-row .fc-focal { flex: 1 1 50%; min-width: 0; }
  .fc-focal-row .fc-actions { flex: 1 1 50%; justify-content: flex-end; }

  .fc-preview-row {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    align-items: center;
  }
  .fc-stage-wrap {
    flex: 1 1 auto;
    min-width: 0;
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.6rem;
  }
  .fc-stage {
    position: relative;
    width: 100%;
    max-width: 960px;
    overflow: hidden;
    transition: height 0.45s cubic-bezier(0.4, 0, 0.2, 1);
  }
  .fc-rect {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    box-sizing: border-box;
    transition: width 0.45s cubic-bezier(0.4, 0, 0.2, 1),
                height 0.45s cubic-bezier(0.4, 0, 0.2, 1);
  }
  .fc-rect-a {
    z-index: 1;
    border: 1.5px solid var(--frame-border);
    background-color: #0a0a0a;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
  }
  .fc-rect-b {
    z-index: 2;
    border: 1.5px solid var(--current);
    background: transparent;
  }
  /* Anamorphic desqueezed guides: same color as their format, dotted border */
  .fc-rect-desq {
    background: transparent;
    border: 1.5px dotted;
  }
  .fc-rect-desq-a { border-color: var(--frame-border); z-index: 3; }
  .fc-rect-desq-b { border-color: var(--current); z-index: 4; }
  .fc-rect-tag {
    position: absolute;
    top: 0;
    left: 0;
    white-space: nowrap;
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    letter-spacing: 0.08em;
    padding: 0.05rem 0.4rem;
    line-height: 1.3;
  }
  .fc-rect-a .fc-rect-tag, .fc-rect-desq-a .fc-rect-tag { color: #0a0a0a; background: var(--frame-border); }
  .fc-rect-b .fc-rect-tag, .fc-rect-desq-b .fc-rect-tag { color: #0a0a0a; background: var(--current); top: auto; bottom: 0; left: auto; right: 0; }
  /* In light mode the preview stage stays dark (cinematic); tag text stays near-black on the coloured badges. */
  :root[data-theme="light"] .fc-rect-a { background-color: #0a0a0a; }
  :root[data-theme="light"] .fc-stage-cap { color: var(--text-muted); }
  :root[data-theme="light"] .fc-rect-b,
  :root[data-theme="light"] .fc-rect-desq-b { border-color: #81ebeb; }
  :root[data-theme="light"] .fc-rect-a .fc-rect-tag,
  :root[data-theme="light"] .fc-rect-desq-a .fc-rect-tag { background: #ebd081; }
  :root[data-theme="light"] .fc-rect-b .fc-rect-tag,
  :root[data-theme="light"] .fc-rect-desq-b .fc-rect-tag { background: #81ebeb; }
  :root[data-theme="light"] .export-btn:hover { border-color: var(--hover-accent); color: var(--hover-accent); }
  :root[data-theme="light"] .header-link:hover { border-color: var(--hover-accent); color: var(--hover-accent); }
  :root[data-theme="light"] .rotate-icon.active { color: var(--hover-accent); }
  .fc-logo {
    position: absolute;
    left: 50%;
    bottom: 8px;
    transform: translateX(-50%);
    width: var(--symbol-watermark-formatcompare-pct, 10%);
    max-width: var(--symbol-watermark-formatcompare-max, 200px);
    height: auto;
    pointer-events: none;
    filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.55));
  }
  .fc-stage-cap {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--text-muted);
    text-align: center;
    letter-spacing: 0.04em;
    line-height: 1.4;
  }

  .fc-sheet {
    width: 100%;
    max-width: 960px;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.75rem;
    align-items: start;
  }
  .fc-sheet .fc-span { grid-column: 1 / -1; }
  .fc-stack { display: flex; flex-direction: column; gap: 0.75rem; min-width: 0; }
  .fc-spec {
    border: 1px solid var(--border);
    background: var(--surface);
    padding: 0.85rem 1rem;
  }
  .fc-spec-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    margin-bottom: 0.4rem;
  }
  .fc-spec-title {
    font-family: 'Roboto', sans-serif;
    font-size: 0.8rem;
    color: var(--gold);
    letter-spacing: 0.04em;
  }
  .fc-spec-sub {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--text-muted);
    letter-spacing: 0.06em;
    text-transform: uppercase;
  }
  .fc-spec-name {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--text);
    margin-bottom: 0.5rem;
    line-height: 1.4;
  }
  .fc-spec-rows {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
  }
  .fc-row {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
  }
  .fc-row > span:first-child { color: var(--text-muted); }
  .fc-row > span:last-child { color: var(--text); text-align: right; }
  .fc-row-head > span { color: var(--gold); }
  .fc-crop .fc-row > span:last-child { color: var(--current); }
  /* Divider between the flat (spherical) rows and the anamorphic rows */
  .fc-spec-divider {
    border: 0;
    border-top: 1px solid var(--border);
    margin: 0.4rem 0;
  }
  /* "Open in Shot Prep" link box (replaces Resolution Disparity) */
  .fc-sp-links {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }
  .fc-sp-link {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    letter-spacing: 0.04em;
    text-decoration: none;
    color: var(--gold);
    border: 1px solid var(--border);
    background: var(--surface);
    padding: 0.5rem 0.7rem;
    transition: border-color 0.18s ease, color 0.18s ease, background 0.18s ease;
  }
  .fc-sp-link.fc-two { color: var(--current); }
  .fc-sp-link:hover { border-color: var(--gold); background: var(--gold-dim); }
  .fc-sp-link.fc-two:hover { border-color: var(--current); background: rgba(129, 235, 235, 0.12); }
  .fc-sp-arrow { font-size: 0.95rem; line-height: 1; }
  .fc-note {
    margin-top: 0.5rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.8rem;
    color: var(--text-muted);
    line-height: 1.45;
  }
  .fc-focal-intro {
    font-family: 'Roboto', sans-serif;
    font-size: 0.8rem;
    color: var(--text);
    line-height: 1.5;
    margin-bottom: 0.6rem;
  }
  .fc-focal-list {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
  }
  .fc-focal-line {
    font-family: 'Roboto', sans-serif;
    font-size: 0.8rem;
    color: var(--text-muted);
    line-height: 1.5;
  }
  .fc-focal-src {
    font-family: 'Roboto', sans-serif;
    color: var(--gold);
  }
  .fc-focal-eq {
    font-family: 'Roboto', sans-serif;
    color: var(--current);
  }

  @media (max-width: 760px) and (min-width: 761px) {
    .fc-format-grid { grid-template-columns: 1fr; }
    .fc-format-divider { display: none; }
    .fc-sheet { grid-template-columns: 1fr; }
    .fc-focal-row { flex-direction: column; align-items: stretch; }
    .fc-focal-row .fc-focal, .fc-focal-row .fc-actions { flex: none; }
    .fc-focal-row .fc-actions { justify-content: flex-end; }
  }

  @media (max-width: 480px) and (min-width: 761px) {
    .fc-fields { flex-direction: column; }
    .sp-opts-row, .sp-lensfocal-row, .sp-lf-stack { flex-direction: column; }
    .sp-lensfocal-row .sp-deck-divider { display: none; }
    .sp-fov-item { flex-basis: calc((100% - 0.5rem) / 2); }
  }

  /* ===================== shotPrep tool ===================== */
  .sp-setup {
    display: grid;
    grid-template-columns: 1.6fr 1px 1fr;
    gap: 1.25rem;
  }
  .sp-deck-divider {
    flex: 0 0 auto;
    align-self: stretch;
    width: 1px;
    background: var(--border);
  }
  .sp-col {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    min-width: 0;
  }
  .sp-opts-row { display: flex; gap: 0.5rem; }
  .sp-opts-row .fc-field { flex: 1 1 0; min-width: 0; }
  .sp-lensfocal-row { display: flex; gap: 1.25rem; align-items: flex-start; }
  .sp-lf-stack { flex: 1 1 0; min-width: 0; display: flex; flex-direction: row; gap: 0.75rem; }
  .sp-lf-stack .fc-field { flex: 1 1 0; min-width: 0; }
  .sp-col-format .fc-col-head .rotate-icon { margin-left: 0; }
  .sp-ratio-field { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 0.25rem; }
  .sp-ratio-field .fc-field-label { display: inline-flex; align-items: center; gap: 0.4rem; }
  .sp-ratio-field .sp-ratio-controls { width: 100%; }
  .sp-ratio-field .sp-ratio-select { flex: 1 1 auto; min-width: 0; }
  /* Custom Ratio field shares the existing controls row rather than adding a
     line of its own: while it's showing, the dropdown gives up width to it.
     The dropdown needs the flex rule on .custom-dd (not the <select>, which
     the shared enhancer hides) or it keeps the fixed px width the enhancer
     measured at load and won't yield the row space the input needs. */
  .sp-ratio-field .sp-ratio-controls > .custom-dd { flex: 1 1 auto; min-width: 6rem; }
  .sp-ratio-field .sp-ratio-controls .sp-custom-ratio { flex: 0 0 6.5rem; width: 6.5rem; max-width: 6.5rem; text-align: center; }
  .sp-opts-row .text-input,
  .sp-lensfocal-row .text-input { width: 100%; max-width: none; }
  .sp-sheet {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: stretch;
    gap: 0.75rem;
  }
  .sp-sheet .fc-span { grid-column: 1 / -1; }
  .sp-vis {
    margin-top: 0.85rem;
    display: flex;
    justify-content: center;
  }
  .sp-vis svg {
    width: 100%;
    max-width: 340px;
    height: auto;
    display: block;
  }
  .sp-visbox {
    display: flex;
    flex-direction: column;
  }
  .sp-visbox .sp-vis {
    flex: 1 1 auto;
    align-items: center;
    margin-top: 0.5rem;
  }
  .sp-legend-title {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--current);
    margin: 0.85rem 0 0.45rem;
  }
  .sp-legend {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
  }
  .sp-leg-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }
  .sp-leg-swatch {
    width: 0.9rem;
    height: 0.9rem;
    flex: 0 0 auto;
  }
  .sp-leg-label {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--text-muted);
  }
  .sp-fov-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
  }
  .sp-fov-item {
    flex: 0 0 calc((100% - 2.5rem) / 6);
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 0.25rem;
    padding: 0.85rem 0.65rem;
    border: 1px solid var(--border);
    background: var(--frame-bg);
    min-width: 0;
  }
  .sp-fov-type {
    font-family: 'Roboto', sans-serif;
    font-size: 0.8rem;
    color: var(--gold);
    letter-spacing: 0.04em;
  }
  .sp-fov-focal {
    font-family: 'Roboto', sans-serif;
    font-size: 0.8rem;
    color: var(--current);
  }
  .sp-fov-val {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--text-muted);
  }
  .sp-fov-val b { color: var(--text); font-weight: 600; }
  /* Deep link into Shot Stage, one per focal. Sits under the FOV figures with
     a rule above it so it reads as an action rather than another value. Muted
     until hover so a column of six cards isn't six competing gold links. */
  .sp-fov-stage {
    margin-top: 0.4rem;
    padding-top: 0.4rem;
    width: 100%;
    border-top: 1px solid var(--border);
    font-family: 'Roboto Mono', monospace;
    font-size: 0.66rem;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    text-decoration: none;
    transition: color 0.18s ease;
  }
  .sp-fov-stage:hover { color: var(--gold); }
  /* A second hand-off stacked under the first. The rule and padding above it
     separate the links from the FOV values; between two links that gap reads
     as a break, so the follower tightens up and drops its own divider. */
  .sp-fov-stage-next {
    margin-top: 0.15rem;
    padding-top: 0;
    border-top: 0;
  }
  /* Field of View header: title + nesting-overlay toggle share the left side,
     with the lens/squeeze sub staying right. Toggle reuses the site's standard
     .negfill-label / .negfill-check checkbox pairing. */
  .sp-fov-head-left { display: flex; align-items: center; gap: 0.85rem; min-width: 0; }
  .sp-fov-toggle { line-height: 1; white-space: nowrap; }
  /* FOV nesting labels sit inside each rectangle's top-left corner. Position,
     size, colour and opacity are set inline by renderGuides so they track the
     frame size and Guide Theme; this is the styling hook only. */
  .frame-fov-label { position: absolute; pointer-events: none; }

  /* shotPrep frame-guide layout */
  .sp-stage { margin-top: 0; gap: 1rem; }
  .ff-stage { margin-top: 0; }
  /* Frame & Focus: match Shot Prep's watermark logo (no drop shadow). Size is
     the shared 12% / 640px from .frame-logo. */
  .ff-stage .frame-logo { filter: none; }
  /* Frame & Focus: overlay text drop shadow is always off. */
  .ff-stage .frame-title,
  .ff-stage .frame-note { text-shadow: none; }
  .sp-page .frame-wrapper {
    transition: width 0.45s cubic-bezier(0.4, 0, 0.2, 1),
                height 0.45s cubic-bezier(0.4, 0, 0.2, 1);
  }
  .sp-page .frame-bounds {
    transition: aspect-ratio 0.45s cubic-bezier(0.4, 0, 0.2, 1);
  }
  /* shotPrep: no drop shadow on the watermark logo or the overlay title/note.
     Size is the shared 12% / 640px from .frame-logo. */
  .sp-page .frame-logo { filter: none; }
  .sp-page .frame-title,
  .sp-page .frame-note { text-shadow: none; }
  /* shotPrep: resolution readout (left) + export controls (right) share the
     export row, like Frame and Focus. */
  .sp-page .deck-actions-right { justify-content: flex-end; }
  .sp-page .sp-export-group { display: flex; align-items: center; gap: 0.5rem; }
  .sp-page .sp-export-group .export-select { width: 9rem; }
  .sp-frame-row { justify-content: center; }
  .sp-sheet-row { width: 100%; display: flex; justify-content: center; }
  .sp-ratio-select { min-width: 180px; }
  .sp-ratio-controls { display: flex; gap: 0.5rem; align-items: center; }
  .export-select:disabled { opacity: 0.4; cursor: not-allowed; }

  @media (max-width: 760px) and (min-width: 761px) {
    .sp-setup { grid-template-columns: 1fr; }
    .sp-setup .sp-deck-divider { display: none; }
    .sp-sheet { grid-template-columns: 1fr; }
    .sp-fov-item { flex-basis: calc((100% - 1rem) / 3); }
  }

  @media (prefers-reduced-motion: reduce) {
    .sp-page .frame-wrapper { transition: none; }
    .sp-page .frame-bounds { transition: none; }
  }

  /* ===================== dataRateCalc tool ===================== */
  .dr-page { justify-content: flex-start; gap: 1.5rem; }
  .dr-col-source { flex: 1.3 1 220px; }
  .dr-col-rate { flex: 1 1 150px; }
  .dr-col-duration { flex: 1.5 1 250px; }
  .dr-controls .text-input { width: 100%; max-width: none; }
  .dr-time { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.5rem; width: 100%; }
  .dr-two { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; width: 100%; }
  .dr-hint {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--text-muted);
    letter-spacing: 0.04em;
  }
  .dr-output {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
  }
  .dr-summary { margin: 0; }
  .dr-output .res-section {
    align-items: stretch;
    border: 1px solid var(--border);
    background: var(--surface);
    border-radius: 0;
    padding: 1.1rem 1.25rem;
  }
  .dr-output .res-section[hidden] { display: none; }
  .dr-output .res-label { text-align: left; }
  .dr-table-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    width: 100%;
    flex-wrap: wrap;
  }
  .dr-head-stacked {
    flex-direction: column;
    align-items: stretch;
    gap: 0.6rem;
  }
  .dr-delivery { display: inline-flex; align-items: center; gap: 0.5rem; }
  .dr-delivery .fc-field-label { white-space: nowrap; }
  .dr-delivery .export-select { width: auto; }
  .dr-delivery-group { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
  .dr-note { margin-top: 0; }
  #drNote { margin-top: -1rem; }

  /* Storage & cost controls (grouped with the result, bottom of page) */
  .dr-storage-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 0.75rem;
    align-items: end;
  }
  .dr-storage-grid .text-input,
  .dr-storage-grid .export-select { width: 100%; max-width: none; }
  .dr-storage-note {
    font-family: 'Roboto', sans-serif;
    font-size: 0.7rem;
    color: var(--text-muted);
    letter-spacing: 0.04em;
    line-height: 1.4;
    text-align: center;
  }
  .dr-storage-toggle { width: 100%; margin-top: 0.2rem; }
  .dr-storage-toggle:disabled { opacity: 0.4; cursor: not-allowed; }
  .dr-storage-toggle.is-on { border-color: var(--gold); color: var(--gold); background: var(--gold-dim); }
  #costResult { display: flex; flex-direction: column; gap: 0.85rem; }
  #costResult[hidden] { display: none; }
  .dr-fill-field { gap: 0.5rem; }
  .dr-fill-cap { color: var(--gold); }
  .dr-fill-range { width: 100%; }
  .dr-cost-panel { grid-template-columns: repeat(4, 1fr); }

  /* Custom up/down stepper: native spinner removed, gold well with dark arrows */
  .dr-page input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
  .dr-page input[type="number"]::-webkit-inner-spin-button,
  .dr-page input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
  .dr-stepper { position: relative; display: block; width: 100%; }
  .dr-stepper .text-input { width: 100%; max-width: none; padding-right: 1.9rem; }
  .dr-stepper-btns {
    position: absolute;
    top: 1px; right: 1px; bottom: 1px;
    width: 1.5rem;
    display: flex;
    flex-direction: column;
    border-left: 1px solid var(--border);
    overflow: hidden;
  }
  .dr-step {
    flex: 1 1 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    margin: 0;
    border: none;
    background: var(--surface);
    color: var(--text);
    font-size: 0.5rem;
    line-height: 1;
    cursor: pointer;
    transition: background 0.12s ease;
  }
  .dr-step:hover { background: var(--border); }
  .dr-step:active { background: var(--bg); }
  .dr-step-up { border-bottom: 1px solid var(--border); }
  @media (max-width: 760px) and (min-width: 761px) {
    .dr-storage-grid { grid-template-columns: 1fr 1fr; }
    .dr-cost-panel { grid-template-columns: 1fr 1fr; }
  }
  @media (max-width: 440px) and (min-width: 761px) {
    .dr-storage-grid { grid-template-columns: 1fr; }
    .dr-cost-panel { grid-template-columns: 1fr; }
  }

  /* ===== depthOfFieldCalc ===== */
  .dof-page .deck-col { flex: 1 1 200px; }
  .dof-info-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1.5rem;
  }
  .dof-specs { display: flex; flex-direction: column; gap: 0.4rem; }
  .dof-spec {
    display: grid;
    grid-template-columns: minmax(110px, 38%) 1fr;
    gap: 0.75rem;
    align-items: baseline;
  }
  .dof-spec-label { font-family: 'Roboto', sans-serif; font-size: 0.78rem; color: var(--text-muted); }
  .dof-spec-value { font-family: 'Roboto', sans-serif; font-size: 0.78rem; color: var(--text); }
  .dof-page .res-table-wrap { overflow-x: auto; }
  .dof-table { width: 100%; }
  .dof-diff-flag { color: var(--gold); }
  .dof-table-note { margin-top: 0.6rem; }
  /* Table title row: label left, "Distances shown in …" note right. */
  .dof-table-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem 1rem; width: 100%; flex-wrap: wrap; }
  .dof-units-note { font-family: 'Roboto', sans-serif; font-size: 0.75rem; color: var(--text-muted); letter-spacing: 0.04em; white-space: nowrap; }
  /* Static guidance line at the bottom of the Lens and Focus box. */
  .dof-lens-note { margin-top: 0.85rem; font-family: 'Roboto', sans-serif; font-size: 0.78rem; color: var(--text-muted); line-height: 1.5; }
  /* About Photosite-Based CoC: lockout note, button, and info modal */
  .dof-about-btn { width: 100%; }
  .dof-samp-lock { font-family: 'Roboto', sans-serif; font-size: 0.7rem; letter-spacing: 0.05em; color: var(--gold); }
  .dof-modal { position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center; padding: 1.5rem; }
  .dof-modal[hidden] { display: none; }
  .dof-modal-backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.7); }
  .dof-modal-box {
    position: relative; max-width: 560px; width: 100%; max-height: 85vh; overflow-y: auto;
    background: var(--surface); border: 1px solid var(--gold); padding: 1.5rem 1.75rem;
    box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6);
  }
  .dof-modal-close {
    position: absolute; top: 0.5rem; right: 0.7rem; background: none; border: 0;
    color: var(--text-muted); font-size: 1.4rem; line-height: 1; cursor: pointer; padding: 0.2rem;
    transition: color 0.18s ease;
  }
  .dof-modal-close:hover { color: var(--gold); }
  .dof-modal-title { font-family: 'Roboto', sans-serif; font-size: 0.95rem; letter-spacing: 0.04em; color: var(--gold); margin: 0 1.5rem 0.9rem 0; }
  .dof-modal-body { font-family: 'Roboto', sans-serif; font-weight: 300; font-size: 0.85rem; line-height: 1.55; color: var(--text); display: flex; flex-direction: column; gap: 0.7rem; }
  .dof-modal-body strong { color: var(--gold); font-weight: 500; }
  .dof-modal-body ul { margin: 0; padding-left: 1.1rem; display: flex; flex-direction: column; gap: 0.35rem; }
  /* Match the PHFX header wordmark's link treatment: gold, monospace,
     uppercase, wide tracking, underline only on hover/focus. */
  .dof-modal-body a {
    color: var(--gold);
    font-family: 'Roboto', sans-serif;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    text-decoration: none;
    transition: color 0.18s ease;
  }
  .dof-modal-body a:hover,
  .dof-modal-body a:focus-visible {
    text-decoration: underline;
  }
  .dof-modal-sign { font-family: 'Roboto', sans-serif; color: var(--gold); text-align: right; letter-spacing: 0.05em; margin-top: 0.3rem; }
  @media (max-width: 760px) and (min-width: 761px) {
    .dof-info-row { grid-template-columns: 1fr; }
  }

  /* ===== lensPrep ===== */
  .lp-page .deck-col { flex: 1 1 210px; }
  /* Camera/Format + Lens Info share one row; a hidden box lets the other fill it. */
  .lp-info-row { display: flex; gap: 1.5rem; align-items: stretch; }
  .lp-info-row > .res-section { flex: 1 1 0; min-width: 0; }
  /* 55/45 rather than an even split: the format card carries the longest
     values on the page (a full camera + format name) plus the frame preview,
     while Lens Info's values are all short. */
  .lp-info-row > #lpFormatCard { flex-grow: 55; }
  .lp-info-row > #lpLensCard { flex-grow: 45; }

  /* Anamorphic + frame-extraction controls, mirroring the same block in
     shotStage so a squeeze or a ratio looks and behaves identically in both
     tools. Values copied from shotStage's page-local .ss-* rules; kept as
     .lp-* because shotStage still defines its own copies. Worth unifying into
     one shared set if a third tool ever needs them. */
  .lp-setup .deck-col { gap: 0.4rem; }
  .lp-inline-fields { display: flex; align-items: flex-start; gap: 0.75rem; }
  .lp-inline-fields > .fc-field { flex: 1 1 0; min-width: 0; }
  .lp-inline-fields > .fc-field > .export-select,
  .lp-inline-fields > .fc-field > .text-input { width: 100%; min-width: 0; max-width: none; }

  .lp-ratio-field { display: flex; flex-direction: column; gap: 0.15rem; margin-top: 0.4rem; }
  .lp-ratio-field .fc-field-label { display: inline-flex; align-items: center; gap: 0.4rem; white-space: nowrap; }
  .lp-ratio-controls { display: flex; align-items: center; gap: 0.5rem; width: 100%; }
  .lp-ratio-controls .lp-ratio-select { flex: 1 1 auto; min-width: 0; }
  .lp-ratio-controls .lp-custom-ratio { flex: 0 0 6.5rem; width: 6.5rem; max-width: 6.5rem; text-align: center; }
  .lp-ratio-controls .lp-custom-ratio[hidden] { display: none; }

  .lp-slider-field { display: flex; flex-direction: column; gap: 0.15rem; }
  .lp-extraction-field { margin-top: 0.4rem; }
  .lp-slider-field .field-inline { display: flex; align-items: center; gap: 0.5rem; }
  .lp-slider-field .extraction-range { flex: 1 1 auto; min-width: 6rem; }
  .lp-slider-field .extraction-value {
    min-width: 4.6rem;
    text-align: right;
    font-family: 'Roboto Mono', monospace;
    font-size: 0.78rem;
    color: var(--text-muted);
  }
  /* Rotate affordances follow the shared .rotate-icon, which is muted and
     warms to --text on hover; the active state is gold so a rotated ratio
     reads at a glance. */
  .lp-ratio-field .rotate-icon.active,
  .lp-page .fc-col-head .rotate-icon.active { color: var(--gold); }
  /* Sit the rotate button beside its title rather than flush right.

     .fc-col-head .rotate-icon carries margin-left:auto, which pushes it to the
     far edge of the column - correct where the head is a wide bar, wrong here.
     Shot Prep cancels it with .sp-col-format; this is the same override for
     the Acquisition Format column on Lens Prep, so the two tools match. */
  .lp-page .fc-col-head .rotate-icon { margin-left: 0; }
  /* Same override for Shot Stage's Acquisition Format column, so all three
     tools sit the rotate button beside its title rather than flush right. */
  .ss-page .fc-col-head .rotate-icon { margin-left: 0; }
  .lp-ratio-field .rotate-icon:disabled { opacity: 0.35; cursor: not-allowed; }
  /* The hand-off is an <a> wearing .export-btn, so it needs the button's
     line-height and centring back — an anchor won't inherit them. */
  .lp-handoff {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
    line-height: 1;
  }

  /* ===== framingDistance: frame preview =====

     Same split as .fd-lens-row beside the FOV dial: specs on the left, a fixed
     150px envelope on the right. nowrap for the same reason — the preview must
     never drop onto its own line. */
  .lp-format-row {
    display: flex;
    gap: 1.25rem;
    /* flex-start, not center: the frame preview's 150px envelope is taller
       than the six spec rows, so centring pushed the first line of data ~14px
       down and left this card's text visibly lower than Lens Info's beside
       it. The specs top-align in both cards now; each card's graphic centres
       itself against them instead. */
    align-items: flex-start;
    flex-wrap: nowrap;
    width: 100%;
  }
  .lp-format-row .dof-specs { flex: 1 1 auto; min-width: 0; }
  /* Tighter label column than the default minmax(110px, 38%). This card's
     labels are short ("Camera and Format" is the longest at ~100px) but its
     values are the longest on the page, so the split is weighted toward the
     value — without this, a full camera + format name wraps at the 55/45
     column width. */
  .lp-format-row .dof-spec { grid-template-columns: minmax(100px, 32%) 1fr; gap: 0.6rem; }

  .lp-frame {
    flex: 0 0 auto;
    align-self: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.45rem;
  }
  /* Fixed square envelope, so the preview holds its footprint as formats
     change and only the shape inside it moves. */
  .lp-frame-box {
    width: 150px;
    height: 150px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  /* What the sensor offers, after rotation and desqueeze. */
  .lp-frame-native {
    position: relative;
    border: 1px solid var(--border);
    background: var(--bg);
  }
  /* What the extraction ratio actually delivers. */
  .lp-frame-extract {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    background: var(--gold-dim);
    border: 1px solid var(--gold);
  }
  /* The frame-extraction punch-in, drawn only when it differs from the crop. */
  .lp-frame-punch {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    border: 1px dashed var(--current);
  }
  .lp-frame-punch[hidden] { display: none; }
  @media (max-width: 760px) and (min-width: 761px) { .lp-info-row { flex-direction: column; } }
  .lp-page .res-table-wrap { overflow-x: auto; }
  .lp-table { width: 100%; }
  .lp-framing { color: var(--text-muted); }
  .lp-fit-note { margin-top: 0.6rem; }
  .lp-divider { border: 0; border-top: 1px solid var(--border); width: 100%; margin: 0; }
  .lp-table tr.lp-sel td { background: var(--gold-dim); }
  .lp-page .text-input:disabled { opacity: 0.45; cursor: not-allowed; }
  .lp-page .dr-step:disabled { opacity: 0.4; cursor: not-allowed; }
  /* Focal length: log-mapped slider paired with the stepper number field. The
     stepper wrapper is display:block/width:100% by default, so it needs a fixed
     basis here or it eats the whole row and squeezes the slider out. */
  .lp-focal-row { display: flex; align-items: center; gap: 0.55rem; width: 100%; }
  .lp-focal-slider { accent-color: var(--gold); cursor: pointer; flex: 1 1 auto; min-width: 0; }
  .lp-focal-row .dr-stepper { flex: 0 0 auto; width: 5.6rem; }
  .lp-focal-row .dr-stepper .text-input { text-align: right; }

  /* ===== panSpeedAssist ===== */
  .ps-page .deck-col { flex: 1 1 210px; }
  .ps-page .res-table-wrap { overflow-x: auto; }
  /* Suggested Fastest Pan title row: label left, About button right. */
  .ps-pan-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; width: 100%; }
  .ps-pan-head .res-label { flex: 1 1 auto; min-width: 0; }
  .ps-about-btn { flex: 0 0 auto; }
  /* Shutter entry: value box (auto-detects angle vs 1/x speed) + Lock Shutter checkbox. */
  .ps-shutter-row { display: flex; gap: 0.5rem; align-items: center; width: 100%; }
  .ps-shutter-row .text-input { flex: 1 1 auto; min-width: 0; width: auto; }
  .ps-shutter-row .negfill-label { flex: 0 0 auto; white-space: nowrap; }
  .ps-page .text-input:disabled { opacity: 0.45; cursor: not-allowed; }
  .ps-shutter-warn { margin: 0; font-family: 'Roboto', sans-serif; font-size: 0.72rem; color: var(--warning); line-height: 1.4; }
  .ps-warn { color: var(--warning); }
  /* Camera/Format + Lens Information share one row. */
  .ps-info-row { display: flex; gap: 1.5rem; align-items: stretch; }
  .ps-info-row > .res-section { flex: 1 1 0; min-width: 0; }
  @media (max-width: 760px) and (min-width: 761px) { .ps-info-row { flex-direction: column; } }
  /* Actions row: hint left, Framing Notes toggle centered, Reset right. */
  .ps-page .deck-actions { display: grid; grid-template-columns: 1fr auto 1fr; }
  .ps-page .deck-actions-left { justify-self: start; }
  .ps-page .deck-actions-center { justify-self: center; display: flex; align-items: center; }
  .ps-page .deck-actions-right { justify-self: end; }
  @media (max-width: 760px) and (min-width: 761px) {
    .ps-page .deck-actions { grid-template-columns: 1fr; gap: 0.6rem; }
    .ps-page .deck-actions-left,
    .ps-page .deck-actions-center,
    .ps-page .deck-actions-right { justify-self: center; }
  }
  .ps-lens-row { display: flex; gap: 1.75rem; align-items: center; flex-wrap: wrap; }
  .ps-lens-row .dof-specs { flex: 1 1 240px; }
  .ps-hfov { display: flex; flex-direction: column; align-items: center; gap: 0.55rem; }
  .ps-hfov-dial {
    width: 150px; height: 150px; border-radius: 50%;
    border: 1px solid var(--border); background: var(--surface);
    position: relative;
  }
  /* Pan ticks, drawn over the HFOV wedge on the same centre. The SVG fills the
     dial exactly, so its -50..50 viewBox maps to the circle's own radius and
     the tick radii can be read as a percentage of it. */
  .ps-hfov-ticks {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
    pointer-events: none;
  }
  .ps-hfov-cap { font-family: 'Roboto', sans-serif; font-size: 0.75rem; color: var(--gold); }
  /* Caption parts. The dot borrows the header's separator colour so the two
     read as the same punctuation, and the pan figure takes the cyan of its own
     ticks on the dial above. */
  .ps-cap-fov { color: var(--gold); }
  .ps-cap-dot { color: var(--header-sep2); margin: 0 0.45rem; }
  .ps-cap-pan { color: var(--current); }

  /* ===== framingDistance: horizontal FOV half dial =====

     Same conic-gradient technique as .ps-hfov-dial above, but a semicircle
     rather than a full one: the camera sits at the midpoint of the flat edge
     and the wedge opens upward, which is how a coverage diagram is drawn on
     paper. A full circle would spend three quarters of its area on angles a
     rectilinear lens can never reach.

     The wedge angle arrives as --fd-hfov, set by framingDistance.js. Because
     conic-gradient measures from 12 o'clock clockwise, starting at minus half
     the angle and running one full sweep of it centres the wedge on the lens
     axis without any further arithmetic.

     Sizing note: at the row's 55/45 split this card gets ~421px, or ~379px of
     usable width. A 132px dial and a 20px gap leave ~227px for the specs,
     which fits every ordinary value once the label column is tightened — the
     labels here are short ("Horizontal FOV" is the longest) and the values
     mostly shorter. The one exception is an out-of-range Shot Type such as
     "Wider than Extreme Ultra Wide", which wraps to two lines; that is a rare
     edge case and not worth sizing the whole card around.

     flex-wrap is off deliberately: with wrapping on, a specs column asking for
     more than the remainder would drop the dial onto its own line, which is
     exactly what it must not do. */
  .fd-lens-row {
    display: flex;
    gap: 1.25rem;
    /* Matches .lp-format-row: specs top-align, the dial centres against them.
       Today the specs are the taller of the two so this changes nothing, but
       it keeps the two cards' first data line level if a wrapping Shot Type
       ever makes the specs taller than the dial block. */
    align-items: flex-start;
    flex-wrap: nowrap;
    width: 100%;
  }
  .fd-lens-row .dof-specs { flex: 1 1 auto; min-width: 0; gap: 0.3rem; }
  /* Tighter than the default minmax(110px, 38%) on both axes, to buy back the
     width the dial needs. */
  .fd-lens-row .dof-spec { grid-template-columns: minmax(84px, 36%) 1fr; gap: 0.5rem; }

  .fd-hfov {
    display: flex;
    flex-direction: column;
    align-items: center;
    align-self: center;
    gap: 0.5rem;
    flex: 0 0 auto;
  }
  .fd-hfov-dial {
    position: relative;
    width: 132px;
    aspect-ratio: 2 / 1;
    /* A radius equal to the full height on the top corners only is exactly a
       semicircle at 2:1, so the shape needs no clip path. */
    border-radius: 999px 999px 0 0;
    border: 1px solid var(--border);
    background: var(--surface);
    overflow: hidden;
  }
  .fd-hfov-dial::before {
    content: '';
    position: absolute;
    inset: 0;
    background: conic-gradient(
      from calc(0deg - var(--fd-hfov, 0deg) / 2) at 50% 100%,
      var(--gold-dim) 0 var(--fd-hfov, 0deg),
      transparent var(--fd-hfov, 0deg)
    );
  }
  /* The lens axis. Fades out toward the arc so it reads as a centre reference
     rather than a fourth edge competing with the wedge's own boundaries. */
  .fd-hfov-axis {
    position: absolute;
    left: 50%;
    bottom: 0;
    width: 1px;
    height: 100%;
    transform: translateX(-50%);
    background: linear-gradient(to top, var(--gold), transparent);
    opacity: 0.45;
  }
  .fd-hfov-cap {
    font-family: 'Roboto', sans-serif;
    font-size: 0.7rem;
    color: var(--gold);
    text-align: center;
    line-height: 1.35;
    max-width: 132px;
  }

  /* ===== slitScanCalc ===== */
  .ss-page .deck-col { flex: 1 1 210px; }
  .ss-page .res-table-wrap { overflow-x: auto; }
  .ss-table { width: 100%; }
  .ss-table tr.ss-sel td { background: var(--gold-dim); }
  /* Camera/Format + Slit Geometry share one row. */
  .ss-info-row { display: flex; gap: 1.5rem; align-items: stretch; }
  .ss-info-row > .res-section { flex: 1 1 0; min-width: 0; }
  @media (max-width: 760px) and (min-width: 761px) { .ss-info-row { flex-direction: column; } }
  /* Output Duration / Output Frame Rate grey out in Single Sweep mode. */
  .ss-page .text-input:disabled { opacity: 0.45; cursor: not-allowed; }
  .ss-page .dr-step:disabled { opacity: 0.4; cursor: not-allowed; }
  .ss-geo-warn { margin: 0; font-family: 'Roboto', sans-serif; font-size: 0.72rem; color: var(--text-muted); line-height: 1.4; }
  /* Hero card: the shot duration is the answer the tool exists to give, so it
     gets its own centered block above the detail cards. */
  .dr-output .res-section.ss-hero { align-items: center; text-align: center; gap: 0.35rem; }
  .dr-output .ss-hero .res-label { text-align: center; }
  .ss-hero-value {
    font-family: 'Roboto Mono', monospace;
    font-size: 2.1rem;
    line-height: 1.15;
    color: var(--gold);
    letter-spacing: 0.02em;
  }
  .ss-hero-cap { font-family: 'Roboto Mono', monospace; font-size: 0.8rem; color: var(--text); }
  .ss-hero-sub { font-family: 'Roboto', sans-serif; font-size: 0.72rem; color: var(--text-muted); letter-spacing: 0.04em; }

  /* ===== exposureTable ===== */
  .et-page .deck-col { flex: 1 1 200px; }
  .et-page .res-table-wrap { overflow-x: auto; }
  .et-table { width: 100%; }
  .et-heading { font-family: 'Roboto', sans-serif; font-size: 0.8rem; color: var(--gold); margin: 0 0 0.9rem; }
  .et-table tr.et-ref td { background: var(--gold-dim); }
  .et-error { color: var(--gold); }

  /* ===== flickerFree ===== */
  .ff-page { justify-content: flex-start; gap: 1.5rem; }
  .ff-fps-col { flex: 1 1 100%; }
  .ff-page .res-table-wrap { overflow-x: auto; }
  .ff-table { width: 100%; }
  .ff-result-note { margin-top: 0.6rem; text-align: center; }
  .ff-status-icon {
    display: block;
    text-align: center;
    font-size: 2.2rem;
    line-height: 1;
    margin-bottom: 0.5rem;
  }
  .ff-status-icon.ff-status-yes { color: var(--success); }
  .ff-status-icon.ff-status-no { color: var(--danger); }

  /* ===== lightPlanner ===== */
  .lb-page .deck-col { flex: 1 1 220px; }
  .lb-match-note { margin-top: 0.6rem; }

  /* ===== filmCalc ===== */
  /* Match the vertical gap between boxes to the visual gap under the
     header. That gap is header's bottom padding (1rem) + main's top
     padding (1rem) stacked together — a flat 2rem `gap` reads as bigger
     than that combined padding, since gap has no border/box-model overlap
     to absorb, so 1rem is the value that actually looks equivalent. */
  .film-page { gap: 1rem; }
  .film-page .dr-output { gap: 1rem; }
  .film-page .deck-col { flex: 1 1 220px; }
  .film-page .res-table-wrap { overflow-x: auto; }
  .film-table { width: 100%; }
  .film-summary-table { width: 100%; }
  .fc-note-star { color: var(--gold); }
  /* Format Summary + Approximate Resolution share one row, matching the
     Camera/Format + Lens Info pattern used elsewhere (e.g. lensPrep). */
  .film-summary-row { display: flex; gap: 1rem; align-items: stretch; width: 100%; }
  .film-summary-row > .res-section { min-width: 0; }
  .film-summary-row > .res-section:nth-child(1) { flex: 1 1 0; }
  .film-summary-row > .res-section:nth-child(2) { flex: 2 1 0; }
  @media (max-width: 760px) and (min-width: 761px) {
    .film-summary-row { flex-direction: column; }
  }
  .fc-rolls-controls { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; flex: 0 0 auto; }
  .fc-rolls-controls .export-select { width: 9rem; min-width: 9rem; }
  /* Roll/Cartridge Size label, its dropdown, and About Pricing share one line.
     The width has to be stated on .custom-dd here, not just the <select>:
     #fcRollsCard starts hidden, so the shared enhancer measures 0 and can't
     reproduce the select's width on the dropdown the way it does elsewhere.
     Without it .custom-dd keeps its base width:100% and takes a whole flex
     line to itself, breaking the row into three. */
  .fc-rolls-controls > .custom-dd { flex: 0 0 auto; width: 9rem; }
  /* The label text is swapped at runtime between "Roll Size" and "Cartridge
     Size", so keep it from breaking across two lines as a flex item. */
  .fc-rolls-controls > .fc-field-label { white-space: nowrap; }
  /* Kodak top-bar logo: sits inside the Film Format box, top-left. */
  .film-decks-row {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    display: flex;
    align-items: stretch;
    gap: 1rem;
  }
  .film-format-deck,
  .film-optional-deck {
    flex: 1 1 0;
    min-width: 0;
    max-width: none;
    margin: 0;
  }
  .film-optional-deck { position: relative; }
  .film-optional-reset-btn {
    position: absolute;
    top: 0.85rem;
    right: 1.25rem;
  }
  @media (max-width: 700px) and (min-width: 761px) {
    .film-decks-row { flex-direction: column; }
  }
  .film-top-logo-row {
    display: flex;
    padding: 0.85rem 1.25rem 0;
  }
  .film-top-logo-frame {
    position: relative;
    width: 100%;
    line-height: 0;
  }
  .film-top-logo {
    width: 100%;
    height: auto;
    display: block;
    -webkit-user-drag: none;
    user-select: none;
  }

  /* ===== printAssist ===== */
  .pa-page .deck-col { flex: 1 1 220px; }
  /* Both decks now lay their fields out horizontally, so the two columns are
     sized by what they actually have to hold rather than sharing evenly:
     Conversion needs room for the Mode select's longest option ("Custom
     Resolution -> Print Size"), and the inputs column has to fit three selects
     side by side in Camera Format. The bases sum to 680px, which clears the
     ~729px of inner width left at the 761px shell lock once the deck padding,
     gap, and divider are taken out. */
  .pa-page .pa-col-conversion { flex: 1 1 320px; }
  .pa-page .pa-col-inputs { flex: 1 1 360px; }
  /* The mode-switched groups were plain block divs, so their children stacked
     with no spacing and .pa-subdiv-head's align-self had no flex parent to
     act on. Matching .deck-col's own column layout fixes both. The global
     [hidden] { display: none !important } rule at the top of this sheet keeps
     this from un-hiding the two inactive groups. */
  .pa-page .pa-input-group { display: flex; flex-direction: column; gap: 0.7rem; }
  /* Custom DPI only ever holds 3-4 digits plus the stepper, so it takes a fixed
     narrow track and hands the rest of the row to Mode. */
  .pa-page .pa-dpi-field { flex: 0 0 6.5rem; }
  /* Model carries the longest strings in the format row ("V-Raptor 8K VV",
     "URSA Mini Pro 12K") while Format is short ("8K 2.37:1"), so the weights
     are split to match rather than left at an even third each. */
  .pa-page .pa-model-field { flex: 1.35 1 0; }
  .pa-page .pa-format-field { flex: 0.9 1 0; }
  /* Unit is a two-option select; the print dimensions deserve the width. */
  .pa-page .pa-unit-field { flex: 0 0 7rem; }
  /* Section label with a rotate icon beside it, matching the fc-col-head
     pattern in Format Compare and Shot Prep. inline-flex keeps the icon on the
     label's own row instead of dropping it to a line of its own, and align-self
     stops the label stretching across the full column width. */
  .pa-subdiv-head { display: inline-flex; align-items: center; gap: 0.4rem; align-self: flex-start; }
  .pa-page .res-table-wrap { overflow-x: auto; }
  .pa-table { width: 100%; }
  .pa-heading { font-family: 'Roboto', sans-serif; font-size: 0.8rem; color: var(--gold); margin: 0 0 0.9rem; }
  .pa-dpi-msg { margin: 0 0 0.75rem; }
  .pa-row { display: flex; gap: 1.75rem; align-items: center; flex-wrap: wrap; }
  .pa-row .dof-specs { flex: 1 1 200px; }
  .pa-preview-wrap { display: flex; flex-direction: column; align-items: center; gap: 0.55rem; }
  .pa-preview-box {
    border: 1px solid var(--gold); background: var(--gold-dim);
    min-width: 2px; min-height: 2px; max-width: 100%;
  }
  .pa-preview-cap { font-family: 'Roboto', sans-serif; font-size: 0.75rem; color: var(--gold); }

  /* ===== optimalViewing ===== */
  .ov-page .deck-col { flex: 1 1 210px; }
  .ov-page .res-table-wrap { overflow-x: auto; }
  .ov-table { width: 100%; }
  .ov-table tr.ov-close td { background: var(--gold-dim); }
  .ov-row { display: flex; gap: 1.75rem; align-items: center; flex-wrap: wrap; }
  .ov-row .dof-specs { flex: 1 1 280px; }
  .ov-preview-wrap { display: flex; flex-direction: column; align-items: center; gap: 0.55rem; }
  .ov-preview-box { border: 1px solid var(--gold); background: var(--gold-dim); max-width: 100%; }
  .ov-preview-cap { font-family: 'Roboto', sans-serif; font-size: 0.75rem; color: var(--gold); }
  .ov-range-notes { margin: 0 0 0.9rem; }
  .ov-table-note { margin-top: 0.6rem; }

  /* Pixels Per Inch panel, sharing a row with Display Geometry */
  .ov-metrics-row { display: flex; gap: 1.5rem; align-items: stretch; }
  .ov-metrics-row .res-section { width: auto; }
  .ov-ppi-section { flex: 0 0 260px; }
  .ov-geometry-section { flex: 1 1 0; min-width: 0; }
  .ov-ppi-table th:not(:first-child), .ov-ppi-table td:not(:first-child) { text-align: right; }
  @media (max-width: 760px) and (min-width: 761px) {
    .ov-metrics-row { flex-direction: column; }
    .ov-ppi-section { flex: none; width: 100%; }
  }
  .ov-units-label { display: inline-flex; align-items: center; gap: 0.5rem; }
  /* Keep the actions row (Units … hint … About / Reset) on one line — let
     the hint text truncate instead of wrapping the whole row. */
  .ov-page .deck-actions { flex-wrap: nowrap; }
  .ov-page .deck-actions-left { min-width: 0; flex: 1 1 auto; }
  .ov-page #ovHint { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .ov-page .deck-actions-right { flex: 0 0 auto; flex-wrap: nowrap; }

  /* ===== panoramaPlanner ===== */
  .pp-page { justify-content: flex-start; gap: 1.5rem; }
  .pp-col-format { flex: 1.3 1 240px; }
  .pp-col-pano { flex: 1 1 220px; }
  .pp-col-lens { flex: 1 1 200px; }
  .pp-controls .text-input { width: 100%; max-width: none; }
  .pp-col-head { display: flex; align-items: center; justify-content: center; gap: 0.4rem; }
  .pp-subdiv { margin-top: 0.4rem; }
  .pp-override { display: flex; flex-direction: column; gap: 0.5rem; width: 100%; }
  .pp-override .text-input, .pp-override .export-select { width: 100%; max-width: none; }
  .pp-pair { display: flex; gap: 0.5rem; width: 100%; }
  .pp-pair .fc-field { flex: 1 1 0; min-width: 0; }
  .pp-overlap-field { gap: 0.5rem; }
  .pp-overlap-range { width: 100%; }
  .pp-overlap-val { color: var(--gold); }
  .pp-overlap-controls { display: flex; flex-direction: column; gap: 0.7rem; width: 100%; }
  .pp-gang { align-self: flex-start; margin-top: 0.1rem; }
  .pp-overlap-controls[hidden], .pp-overlap-field[hidden], .pp-gang[hidden] { display: none; }
  .pp-hint {
    font-family: 'Roboto', sans-serif;
    font-size: 0.7rem;
    color: var(--text-muted);
    letter-spacing: 0.04em;
    line-height: 1.4;
  }
  .pp-divider-hr {
    border: 0;
    border-top: 1px solid var(--border);
    width: 100%;
    margin: 0.3rem 0;
  }
  .pp-seams {
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0.5;
  }
  .pp-seam {
    position: absolute;
    border-color: var(--frame-border);
    border-style: dashed;
  }
  .pp-seam-v {
    top: 0;
    bottom: 0;
    width: 0;
    border-width: 0;
    border-left-width: 2px;
    transform: translateX(-1px);
  }
  .pp-seam-h {
    left: 0;
    right: 0;
    height: 0;
    border-width: 0;
    border-top-width: 2px;
    transform: translateY(-1px);
  }
  #cropOverlapBox {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    width: 100%;
  }
  .pp-output {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
  }
  .pp-summary { margin: 0; }
  .pp-ff { color: var(--text-muted); }
  /* Custom gold stepper: hide native number spinners on this page (shared .dr-stepper styling). */
  .pp-page input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
  .pp-page input[type="number"]::-webkit-inner-spin-button,
  .pp-page input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }

  /* ===== panoToFlat ===== */
  .ptf-page { justify-content: flex-start; gap: 1.5rem; }
  .ptf-col-format { flex: 1.1 1 220px; }
  .ptf-col-lens { flex: 1.2 1 240px; }
  .ptf-col-result { flex: 1.1 1 230px; }
  .ptf-controls .text-input { width: 100%; max-width: none; }
  .ptf-col-head { display: flex; align-items: center; justify-content: center; gap: 0.4rem; }
  .ptf-divider-hr {
    border: 0;
    border-top: 1px solid var(--border);
    width: 100%;
    margin: 0.4rem 0;
  }
  .ptf-hint {
    font-family: 'Roboto', sans-serif;
    font-size: 0.7rem;
    color: var(--text-muted);
    letter-spacing: 0.04em;
    line-height: 1.4;
  }
  /* Focal length: slider + manual entry on one line. */
  .ptf-focal-row { display: flex; align-items: center; gap: 0.5rem; width: 100%; }
  .ptf-focal-row .ptf-focal-range { flex: 1 1 auto; width: auto; min-width: 0; }
  .ptf-focal-num { flex: 0 0 4.6rem; width: 4.6rem; max-width: none; text-align: right; }
  .ptf-focal-unit {
    flex: 0 0 auto;
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--text-muted);
  }
  .ptf-focal-read { color: var(--gold); }
  /* Typed value sits outside the slider's 8-300mm span: the slider is pinned to
     an end and is no longer the source of truth, so it reads as inactive. */
  .ptf-focal-range.ptf-range-pinned { opacity: 0.4; }
  .ptf-page input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
  .ptf-page input[type="number"]::-webkit-inner-spin-button,
  .ptf-page input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }

  .ptf-headline { display: flex; flex-direction: column; gap: 0.25rem; width: 100%; }
  .ptf-headline-val {
    font-family: 'Roboto', sans-serif;
    font-size: 1.35rem;
    line-height: 1.15;
    color: var(--gold);
    letter-spacing: 0.02em;
  }
  .ptf-headline-sub {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--text-muted);
    letter-spacing: 0.04em;
  }
  .ptf-headline .fc-row { font-size: 0.75rem; }

  .ptf-output {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
  }
  .ptf-stage-wrap {
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.6rem;
  }
  /* The panorama stage is locked to 2:1 — it IS a 360x180 equirectangular
     frame, so the aspect is part of the data, not a layout choice. Stays dark
     in light mode for the same reason .fc-rect-a does. */
  .ptf-stage {
    position: relative;
    width: 100%;
    max-width: 960px;
    aspect-ratio: 2 / 1;
    border: 1.5px solid var(--frame-border);
    background: #0a0a0a;
    overflow: hidden;
  }
  .ptf-pano-img {
    position: absolute;
    inset: 0;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    opacity: 0;
    transition: opacity 0.35s ease;
  }
  .ptf-pano-img.is-loaded { opacity: 0.55; }
  .ptf-svg-wrap { position: absolute; inset: 0; }
  .ptf-svg { display: block; width: 100%; height: 100%; }
  .ptf-grid line { stroke: #ebebeb; stroke-opacity: 0.14; stroke-width: 1; }
  .ptf-grid line.ptf-grid-major { stroke-opacity: 0.3; stroke-dasharray: 4 4; }
  .ptf-grid-label text {
    font-family: 'Roboto', sans-serif;
    font-size: 9px;
    fill: #ebebeb;
    fill-opacity: 0.4;
    letter-spacing: 0.06em;
  }
  /* Axis-aligned equirect pixel region the extraction has to read. */
  .ptf-bbox {
    fill: none;
    stroke: var(--current);
    stroke-width: 1.5;
    stroke-dasharray: 5 4;
    stroke-opacity: 0.85;
  }
  /* True projected footprint of the rectilinear frame. */
  .ptf-fp {
    fill: var(--frame-border);
    fill-opacity: 0.16;
    stroke: var(--frame-border);
    stroke-width: 2;
  }
  .ptf-axis line { stroke: var(--frame-border); stroke-width: 1.5; stroke-opacity: 0.7; }
  /* The stage stays dark in light mode, so the read-region dash keeps the dark-mode
     cyan rather than --current's light-mode navy, which would vanish against it. */
  :root[data-theme="light"] .ptf-stage { background: #0a0a0a; }
  :root[data-theme="light"] .ptf-bbox { stroke: #81ebeb; }
  .ptf-logo {
    position: absolute;
    left: 50%;
    bottom: 8px;
    transform: translateX(-50%);
    width: var(--symbol-watermark-formatcompare-pct, 10%);
    max-width: var(--symbol-watermark-formatcompare-max, 200px);
    height: auto;
    pointer-events: none;
    opacity: 0.8;
    filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.55));
  }
  /* Highlighted row in the size-reference table: the exact required width. */
  .res-table tr.ptf-target td { background: var(--gold-dim); }
  .ptf-eq {
    font-family: 'Roboto', sans-serif;
    color: var(--gold);
    letter-spacing: 0.03em;
    text-align: center;
  }
  @media (max-width: 760px) and (min-width: 761px) {
    .ptf-headline-val { font-size: 1.15rem; }
  }

  /* ===== formatChart ===== */
  .fch-page { justify-content: flex-start; gap: 1.5rem; }
  .fch-controls { width: 100%; }
  .fch-control-row { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 1rem; padding: 1.25rem; }
  .fch-set-field { flex: 1 1 320px; max-width: 460px; }
  .fch-set-title { color: var(--gold); }
  .fch-set-field .export-select { width: 100%; max-width: none; }
  .fch-export-field { flex: 1 1 280px; }
  .fch-actions { display: flex; gap: 0.6rem; flex-wrap: nowrap; align-items: center; }
  .fch-actions .export-select { flex: 1 1 auto; min-width: 0; }
  .fch-actions .export-btn { flex: 0 0 auto; white-space: nowrap; }
  .fch-output { width: 100%; display: flex; flex-direction: column; gap: 1rem; align-items: center; }
  .fch-chart {
    width: 100%; max-width: 960px; margin: 0 auto;
    border: 1px solid var(--border); background: #0a0a0a; line-height: 0;
    cursor: pointer; transition: border-color 0.18s ease;
  }
  .fch-chart:hover { border-color: var(--gold); }
  .fch-chart svg { display: block; width: 100%; height: auto; }
  .fch-error { padding: 2rem; color: var(--text-muted); font-family: 'Roboto', sans-serif; font-size: 0.85rem; line-height: 1.5; }
  .fch-output .fc-note { max-width: 960px; }

  /* Embiggened chart lightbox — reuses the site's lb-overlay/lb-caption
     pattern from the still gallery so the enlarged-view UX matches. */
  .fch-lb-chart { width: min(94vw, 1600px); line-height: 0; }
  .fch-lb-chart svg { display: block; width: 100%; height: auto; }

  /* Admin: set management row + per-item editor */
  .fch-set-admin-row {
    display: flex; gap: 0.5rem; flex-wrap: wrap;
    padding: 0 1.25rem 1.1rem;
  }
  .fch-item-admin { width: 100%; max-width: 1600px; margin: 0 auto; padding: 1.1rem 1.25rem; display: flex; flex-direction: column; gap: 0.85rem; }
  .fch-item-admin-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
  .fch-item-list { display: flex; flex-direction: column; gap: 0.4rem; }
  .fch-item-row {
    display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
    padding: 0.55rem 0.7rem; border: 1px solid var(--border); background: var(--bg);
  }
  .fch-item-row[draggable="true"] { cursor: grab; }
  .fch-item-row.is-dragging { opacity: 0.4; }
  .fch-item-row.drag-over { box-shadow: inset 0 0 0 2px var(--gold); }
  .fch-item-row-main { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
  .fch-item-row-label { font-family: 'Roboto', sans-serif; font-size: 0.82rem; color: var(--text); }
  .fch-item-row-meta { font-family: 'Roboto', sans-serif; font-size: 0.7rem; color: var(--text-muted); }
  .fch-item-row-actions { display: flex; gap: 0.4rem; flex: 0 0 auto; }
  .fch-item-empty { font-family: 'Roboto', sans-serif; font-size: 0.8rem; color: var(--text-muted); text-align: center; padding: 1rem 0; }
  .fch-item-row.is-hidden { opacity: 0.5; }

  /* Admin: Camera Colors editor */
  .fch-colors-list { display: flex; flex-direction: column; gap: 0.4rem; max-height: 50vh; overflow-y: auto; }
  .fch-colors-row {
    display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
    padding: 0.5rem 0.6rem; border: 1px solid var(--border); background: var(--bg);
  }
  .fch-colors-row-name { font-family: 'Roboto', sans-serif; font-size: 0.82rem; color: var(--text); flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 0.5rem; }
  .fch-colors-unset { color: var(--danger); font-size: 0.62rem; font-family: 'Roboto', sans-serif; letter-spacing: 0.05em; text-transform: uppercase; }
  .fch-colors-row-fields { display: flex; align-items: center; gap: 0.75rem; flex: 0 0 auto; }
  .fch-colors-field { display: flex; align-items: center; gap: 0.35rem; }
  .fch-colors-field label { font-family: 'Roboto', sans-serif; font-size: 0.62rem; color: var(--text-muted); letter-spacing: 0.05em; text-transform: uppercase; }
  .fch-colors-swatch { width: 1.8rem; height: 1.8rem; padding: 0; border: 1px solid var(--border); background: none; cursor: pointer; flex: 0 0 auto; }
  .fch-colors-hex {
    width: 5.5rem; max-width: 5.5rem; font-family: 'Roboto', sans-serif; font-size: 0.75rem;
    text-transform: uppercase; letter-spacing: 0.02em; padding: 0.3rem 0.4rem;
  }

  /* Generic small color-swatch input, reused wherever a plain color picker
     is needed outside a dedicated color-editor modal (e.g. titleCard's
     Title/Description/Logo color controls). */
  .color-input {
    width: 2.2rem; height: 2.2rem; padding: 0; border: 1px solid var(--border);
    background: none; cursor: pointer; flex: 0 0 auto;
  }
  .color-hex-input {
    width: 5.5rem; max-width: 5.5rem; font-family: 'Roboto', sans-serif; font-size: 0.75rem;
    text-transform: uppercase; letter-spacing: 0.02em; padding: 0.3rem 0.4rem;
  }

  /* ===== testCharts ===== */
  .tc-page { justify-content: flex-start; gap: 1.5rem; }
  .tc-controls { width: 100%; }
  .tc-col-frame { flex: 1 1 220px; }
  .tc-col-grid { flex: 1 1 260px; }
  .tc-stage {
    width: 100%; max-width: 960px; margin: 0 auto;
    display: flex; align-items: center; justify-content: center;
  }
  .tc-frame-wrap {
    width: 100%; display: flex; align-items: center; justify-content: center;
  }
  .tc-frame {
    width: 100%; max-width: 960px;
    border: 1.5px solid var(--frame-border);
    background: #0a0a0a;
    line-height: 0;
    overflow: hidden;
    transition: aspect-ratio 0.45s cubic-bezier(0.4, 0, 0.2, 1);
  }
  .tc-frame svg { display: block; width: 100%; height: auto; }
  @media (prefers-reduced-motion: reduce) {
    .tc-frame { transition: none; }
  }

  /* ===== equipment ===== */
  .eq-page { justify-content: flex-start; gap: 1.5rem; max-width: 960px; margin: 0 auto; padding: 1.5rem 1rem; width: 100%; min-width: 0; box-sizing: border-box; }
  .eq-toolbar { width: 100%; padding: 1.1rem 1.25rem; display: flex; flex-direction: column; gap: 0.85rem; }
  .eq-toolbar-row { display: flex; }
  .eq-admin-row { display: flex; gap: 0.6rem; flex-wrap: wrap; padding-top: 0.6rem; border-top: 1px solid var(--border); }
  .eq-message { text-align: center; color: var(--text-muted); font-family: 'Roboto', sans-serif; font-size: 0.85rem; padding: 2rem 0; }

  .eq-list { width: 100%; min-width: 0; display: flex; flex-direction: column; gap: 1.5rem; }
  .eq-category-block { display: flex; flex-direction: column; gap: 0.5rem; min-width: 0; }
  .eq-category-title {
    font-family: 'Roboto', sans-serif; font-size: 0.78rem; letter-spacing: 0.1em; text-transform: uppercase;
    color: var(--gold); border-bottom: 1px solid var(--border); padding-bottom: 0.4rem;
  }
  .eq-row {
    display: grid; grid-template-columns: 3.5rem 8rem 1fr auto; align-items: center; gap: 0.75rem;
    padding: 0.55rem 0.2rem; border-bottom: 1px solid var(--border); font-family: 'Roboto', sans-serif; font-size: 0.82rem;
    min-width: 0; max-width: 100%;
  }
  .eq-row > * { min-width: 0; }
  .eq-row:last-child { border-bottom: none; }
  .eq-row-qty { color: var(--current); font-weight: 700; }
  .eq-row-category { color: var(--text-muted); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .eq-row-desc { color: var(--text); overflow-wrap: break-word; word-break: break-word; }
  .eq-row-desc strong { color: var(--text); }
  .eq-row-admin-actions { display: flex; gap: 0.4rem; }
  @media (max-width: 640px) and (min-width: 761px) {
    .eq-row { grid-template-columns: 2.5rem 1fr auto; }
    .eq-row-category { display: none; }
  }

  .eq-packages .eq-category-block { gap: 0.75rem; }
  .eq-package-card {
    border: 1px solid var(--border); background: var(--surface); padding: 0.85rem 1rem;
    display: flex; flex-direction: column; gap: 0.35rem;
  }
  .eq-package-card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
  .eq-package-name { font-family: 'Roboto', sans-serif; font-size: 0.9rem; color: var(--text); }
  .eq-package-rate { font-family: 'Roboto', sans-serif; font-size: 0.8rem; color: var(--current); white-space: nowrap; }
  .eq-package-desc { font-family: 'Roboto', sans-serif; font-size: 0.78rem; color: var(--text-muted); line-height: 1.5; }
  .eq-package-meta { font-family: 'Roboto', sans-serif; font-size: 0.72rem; color: var(--text-muted); }

  .eq-form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.85rem; }
  @media (max-width: 560px) and (min-width: 761px) { .eq-form-grid { grid-template-columns: 1fr; } }
  .eq-computed-row {
    display: flex; gap: 1.5rem; font-family: 'Roboto', sans-serif; font-size: 0.8rem; color: var(--text-muted);
    padding: 0.5rem 0; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
  }
  .eq-computed-row strong { color: var(--current); }

  .eq-item-list-admin { display: flex; flex-direction: column; gap: 0.4rem; max-height: 55vh; overflow-y: auto; }
  .eq-item-admin-row {
    display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
    padding: 0.5rem 0.65rem; border: 1px solid var(--border); background: var(--bg);
    font-family: 'Roboto', sans-serif;
  }
  .eq-item-admin-row.is-hidden { opacity: 0.5; }
  .eq-item-admin-main { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
  .eq-item-admin-label { font-size: 0.82rem; color: var(--text); }
  .eq-item-admin-meta { font-size: 0.68rem; color: var(--text-muted); }
  .eq-item-admin-actions { display: flex; gap: 0.4rem; flex: 0 0 auto; }

  .eq-package-contents { display: flex; flex-direction: column; gap: 0.35rem; }
  .eq-package-content-row {
    display: flex; align-items: center; justify-content: space-between; gap: 0.6rem;
    padding: 0.4rem 0.6rem; border: 1px solid var(--border); background: var(--bg);
    font-family: 'Roboto', sans-serif; font-size: 0.8rem;
  }
  .eq-package-content-qty { width: 3.5rem; text-align: center; }

  .eq-cat-list { display: flex; flex-direction: column; gap: 0.35rem; margin-bottom: 0.6rem; }
  .eq-cat-row {
    display: flex; align-items: center; justify-content: space-between; gap: 0.6rem;
    padding: 0.45rem 0.6rem; border: 1px solid var(--border); background: var(--bg);
    font-family: 'Roboto', sans-serif; font-size: 0.8rem;
  }
  .eq-cat-row.is-hidden { opacity: 0.5; }
  .eq-cat-row-name { flex: 1 1 auto; min-width: 0; }
  .eq-cat-row-actions { display: flex; gap: 0.35rem; flex: 0 0 auto; }

  .eq-terms-list { display: flex; flex-direction: column; gap: 0.5rem; }
  .eq-term-row { display: grid; grid-template-columns: 1fr 5rem 5rem auto; gap: 0.5rem; align-items: center; }
  .eq-term-row input { width: 100%; }

  /* ===== equipment manager (/equipment/edit.html) =====
     Admin-only editor. Same 960px content width as the rest of the site; the
     list column is kept narrow so the editor still gets room for its two-up
     form grid. */
  .eqm-page {
    justify-content: flex-start; align-items: stretch; gap: 1.25rem;
    max-width: 960px; margin: 0 auto;
    padding: 1.5rem 1rem; width: 100%; min-width: 0; box-sizing: border-box;
    display: flex; flex-direction: column;
  }
  /* main is a centering flex column, so the single root wrapper has to be told
     to fill it and to stack its own children — the bar, the tabs and the body
     are grandchildren of main and get no layout from it. */
  .eqm-page > div {
    width: 100%; min-width: 0;
    display: flex; flex-direction: column; gap: 0.75rem;
  }
  .eqm-page > div > * { min-width: 0; }

  .eqm-bar {
    display: flex; align-items: flex-end; justify-content: space-between; gap: 1rem;
    flex-wrap: wrap; padding-bottom: 0.85rem; border-bottom: 1px solid var(--border);
  }
  .eqm-bar-title { display: flex; flex-direction: column; gap: 0.25rem; min-width: 0; }
  .eqm-bar-title h1 {
    font-family: 'Roboto', sans-serif; font-size: 1rem; letter-spacing: 0.1em;
    text-transform: uppercase; color: var(--gold); margin: 0;
  }
  .eqm-bar-actions { display: flex; gap: 0.5rem; align-items: center; flex: 0 0 auto; }
  .eqm-bar-actions a.export-btn { text-decoration: none; }
  .eqm-note {
    font-family: 'Roboto', sans-serif; font-size: 0.72rem; color: var(--text-muted);
    letter-spacing: 0.03em; line-height: 1.5;
  }

  .eqm-tabs { display: flex; gap: 0; border-bottom: 1px solid var(--border); }
  .eqm-tab {
    background: none; border: none; border-bottom: 2px solid transparent; cursor: pointer;
    font-family: 'Roboto Mono', monospace; font-size: 0.74rem; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--text-muted); padding: 0.6rem 1rem;
    transition: color 0.18s ease, border-color 0.18s ease;
  }
  .eqm-tab:hover { color: var(--gold); }
  .eqm-tab[aria-current="true"] { color: var(--gold); border-bottom-color: var(--gold); }
  .eqm-tab-count {
    font-size: 0.66rem; color: var(--text-muted); background: var(--surface);
    border: 1px solid var(--border); padding: 0.05rem 0.35rem; margin-left: 0.2rem;
  }

  .eqm-toolbar {
    display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap;
    padding: 0.9rem 0; border-bottom: 1px solid var(--border);
  }
  .eqm-toolbar .eqm-search { flex: 1 1 260px; min-width: 0; max-width: none; }
  .eqm-toolbar-sep { flex: 0 0 1px; align-self: stretch; background: var(--border); margin: 0 0.3rem; }
  .eqm-qty-label {
    font-family: 'Roboto Mono', monospace; font-size: 0.68rem; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--text-muted);
  }
  .eqm-qty { width: 3.75rem; max-width: 3.75rem; text-align: center; }

  .eqm-layout { display: grid; grid-template-columns: 300px 1fr; gap: 1.25rem; align-items: start; padding-top: 1.25rem; }
  @media (max-width: 860px) and (min-width: 761px) { .eqm-layout { grid-template-columns: 1fr; } }
  .eqm-single-col { padding-top: 1.25rem; max-width: 720px; }
  .eqm-list-col, .eqm-edit-col { min-width: 0; }

  .eqm-list {
    display: flex; flex-direction: column; border: 1px solid var(--border);
    background: var(--surface); max-height: 70vh; overflow-y: auto;
  }
  .eqm-list.is-reordering { cursor: grabbing; }
  .eqm-row {
    display: grid; grid-template-columns: 1.1rem 0.5rem 1fr; align-items: center; gap: 0.55rem;
    padding: 0.5rem 0.65rem; cursor: pointer; font-family: 'Roboto', sans-serif;
    border-bottom: 1px solid var(--border); background: var(--surface);
    transition: background 0.14s ease;
  }
  .eqm-row:last-child { border-bottom: none; }
  .eqm-row:hover { background: var(--surface-hover); }
  .eqm-row[aria-current="true"] { background: var(--gold-dim); }
  .eqm-row.is-dragging { opacity: 0.75; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45); }
  body.eqm-reordering { user-select: none; -webkit-user-select: none; }

  .eqm-grip { color: var(--text-muted); font-size: 0.7rem; line-height: 1; cursor: grab; letter-spacing: -0.15em; }
  .eqm-grip.is-off { visibility: hidden; cursor: default; }
  .eqm-row.is-dragging .eqm-grip { cursor: grabbing; }
  .eqm-dot { width: 0.45rem; height: 0.45rem; border-radius: 50%; background: var(--current); }
  .eqm-dot.out { background: var(--danger-soft); }
  .eqm-dot.hidden { opacity: 0.35; }
  .eqm-row-main { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
  .eqm-row-label {
    font-size: 0.8rem; color: var(--text); overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap;
  }
  .eqm-row-meta {
    font-size: 0.66rem; color: var(--text-muted); overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap;
  }

  .eqm-editor { border: 1px solid var(--border); background: var(--surface); padding: 1.1rem 1.25rem; }
  .eqm-editor-head {
    display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
    flex-wrap: wrap; padding-bottom: 0.75rem; border-bottom: 1px solid var(--border);
  }
  .eqm-editor-head h2 {
    font-family: 'Roboto', sans-serif; font-size: 0.9rem; letter-spacing: 0.04em;
    color: var(--gold); margin: 0; min-width: 0; overflow-wrap: break-word;
  }
  .eqm-editor-head-actions { display: flex; gap: 0.45rem; align-items: center; flex: 0 0 auto; }
  .eqm-editor-fields { display: flex; flex-direction: column; gap: 0.85rem; margin-top: 1rem; }
  .eqm-span-2 { grid-column: 1 / -1; }
  .eqm-danger { color: var(--danger-soft); border-color: var(--danger-soft); }
  .eqm-danger:hover { background: var(--danger); color: var(--bg); border-color: var(--danger); }

  .eqm-batch-note {
    margin-top: 0.85rem; padding: 0.55rem 0.75rem; border: 1px solid var(--current);
    background: rgba(129, 235, 235, 0.07); font-family: 'Roboto', sans-serif;
    font-size: 0.72rem; color: var(--text-muted); line-height: 1.55;
  }
  .eqm-linkish {
    background: none; border: none; padding: 0; cursor: pointer; color: var(--current);
    font: inherit; text-decoration: underline; text-underline-offset: 2px;
  }
  .eqm-linkish:hover { color: var(--gold); }

  .eqm-image-row { display: flex; gap: 0.6rem; align-items: center; }
  .eqm-image-row img {
    width: 64px; height: 64px; object-fit: cover; border: 1px solid var(--border);
    background: var(--bg); flex: 0 0 auto;
  }
  .eqm-image-row input[type="file"] {
    flex: 1 1 auto; min-width: 0; font-family: 'Roboto Mono', monospace;
    font-size: 0.7rem; color: var(--text-muted);
  }

  .eqm-pkg-add { display: flex; gap: 0.5rem; margin-top: 0.5rem; }
  .eqm-pkg-content-controls { display: flex; gap: 0.5rem; align-items: center; flex: 0 0 auto; }
  .eq-package-content-row.eqm-orphan { border-color: var(--danger-soft); color: var(--danger-soft); }

  .eqm-cat-add { display: flex; gap: 0.5rem; }
  /* The category name is a live input, not row text. It sits next to a run of
     buttons, so without a cursor and hover cue it reads as a static label and
     nobody discovers that renaming is just typing in it. */
  .eq-cat-row-name { cursor: text; }
  .eq-cat-row-name:hover { border-color: var(--gold); }
  .eqm-cat-count {
    font-family: 'Roboto Mono', monospace; font-size: 0.66rem; color: var(--text-muted);
    border: 1px solid var(--border); padding: 0.05rem 0.35rem; flex: 0 0 auto;
  }
  .eqm-term-head {
    display: grid; grid-template-columns: 1fr 5rem 5rem auto; gap: 0.5rem;
    font-family: 'Roboto Mono', monospace; font-size: 0.64rem; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.4rem;
  }
  /* The header's trailing cell has to reserve the same width the row's
     remove button occupies, or every column above it sits shifted. */
  .eqm-term-head > span:last-child { min-width: 4.2rem; }

  /* ===== exposureCompensator ===== */
  .ec-page { justify-content: flex-start; gap: 1.5rem; }
  .ec-col { flex: 1 1 300px; gap: 1rem; min-width: 0; }
  .ec-col-head {
    font-family: 'Roboto', sans-serif; font-size: 0.85rem; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--gold); text-align: center;
    align-self: stretch; padding-bottom: 0.55rem; border-bottom: 1px solid var(--border);
  }
  .ec-col.ec-target .ec-col-head { color: var(--current); }
  .ec-control { display: flex; flex-direction: column; gap: 0.4rem; width: 100%; }
  .ec-label {
    font-family: 'Roboto', sans-serif; font-size: 0.72rem; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--text-muted);
  }
  .ec-slider-row { display: flex; align-items: center; gap: 0.7rem; width: 100%; }
  .ec-slider { accent-color: var(--gold); cursor: pointer; flex: 1 1 auto; min-width: 0; }
  .ec-col.ec-target .ec-slider { accent-color: var(--current); }
  .ec-num { width: 5.5rem; max-width: 45%; text-align: right; flex: 0 0 auto; }
  /* Boost specificity so this reliably wins over .text-input's own width —
     equal-specificity ties depend on stylesheet order, which is fragile as
     the sheet grows. The shutter row below already did this implicitly via
     its two-class selector; this covers every other slider-paired field
     (fps, ISO, T-stop, etc.) the same way. */
  .ec-slider-row .ec-num { width: 5.5rem; max-width: 45%; }
  .ec-slider-full { width: 100%; }
  /* Shutter: angle field - slider - speed field */
  .ec-shutter-row { display: flex; align-items: center; gap: 0.6rem; width: 100%; }
  .ec-shutter-row .ec-num { width: 4.6rem; max-width: 32%; text-align: center; }
  .ec-shutter-caps { display: flex; justify-content: space-between; }
  .ec-shutter-caps span {
    font-family: 'Roboto', sans-serif; font-size: 0.62rem; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--text-muted);
  }
  /* Locked ND nomenclature preview */
  .ec-nd-preview {
    font-family: 'Roboto', sans-serif; font-size: 0.75rem; color: var(--text);
    border: 1px solid var(--border); background: var(--surface);
    padding: 0.4rem 0.6rem; text-align: center;
  }
  /* Result: exposure difference */
  .dr-output .ec-result .res-label { text-align: center; }
  .ec-diff {
    font-family: 'Roboto', sans-serif; font-size: 2rem; font-weight: 700;
    color: var(--gold); letter-spacing: 0.02em; text-align: center;
  }
  .ec-diff-note { font-family: 'Roboto', sans-serif; font-size: 0.85rem; color: var(--text); text-align: center; margin: 0; }
  @media (max-width: 760px) and (min-width: 761px) {
    .ec-page .deck-cols { grid-template-columns: 1fr; }
  }

  /* ===== colorScheme admin ===== */
  .cs-page { justify-content: flex-start; gap: 1.5rem; }
  .cs-deck { padding: 0; }
  .cs-deck-top {
    display: flex; align-items: center; justify-content: space-between;
    gap: 1rem; flex-wrap: wrap;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--border);
  }
  .cs-mode-row { display: flex; align-items: center; gap: 0.75rem; }
  .cs-grid { display: flex; flex-direction: column; }
  .cs-row {
    display: grid;
    grid-template-columns: 2rem 1fr 1fr auto;
    align-items: center;
    gap: 0.75rem 1rem;
    padding: 0.65rem 1.25rem;
    border-bottom: 1px solid var(--border);
  }
  .cs-row:last-child { border-bottom: none; }
  .cs-row-preview {
    width: 2rem; height: 2rem;
    border: 1px solid var(--border);
    flex: 0 0 auto;
  }
  .cs-row-label {
    font-family: 'Roboto', sans-serif; font-size: 0.78rem;
    color: var(--text); letter-spacing: 0.04em;
  }
  .cs-row-var {
    font-family: 'Roboto', sans-serif; font-size: 0.72rem;
    color: var(--text-muted); letter-spacing: 0.04em;
  }
  .cs-row-controls { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; justify-content: flex-end; }
  .cs-color-picker {
    width: 2.2rem; height: 2.2rem;
    padding: 0; border: 1px solid var(--border);
    background: none; cursor: pointer;
    flex: 0 0 auto;
  }
  .cs-hex-input { width: 12rem; max-width: 40vw; }
  .cs-alpha-label {
    font-family: 'Roboto', sans-serif; font-size: 0.68rem;
    color: var(--text-muted); letter-spacing: 0.06em; text-transform: uppercase;
  }
  .cs-alpha-slider { accent-color: var(--gold); width: 80px; cursor: pointer; }
  .cs-alpha-val {
    font-family: 'Roboto', sans-serif; font-size: 0.72rem;
    color: var(--text-muted); min-width: 2.5rem;
  }
  .cs-note-row { padding: 0.85rem 1.25rem; border-top: 1px solid var(--border); }
  .cs-note-row .fc-note { margin: 0; }
  .cs-note-row strong { color: var(--gold); font-weight: 500; }
  .cs-note-row code {
    font-family: 'Roboto', sans-serif; font-size: 0.78rem;
    color: var(--current); background: none;
  }
  .cs-copy-btn { color: var(--gold); border-color: var(--gold); }
  .cs-copy-btn:hover { background: var(--gold-dim); }
  .cs-save-btn { color: var(--current); border-color: var(--current); }
  .cs-save-btn:hover { background: rgba(129, 235, 235, 0.12); }
  .cs-save-btn:disabled { opacity: 0.6; cursor: not-allowed; }
  .cs-swatches { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 0; }
  .cs-swatch {
    display: flex; flex-direction: column; justify-content: flex-end;
    min-height: 90px; padding: 0.6rem 0.75rem;
    border: 1px solid rgba(0,0,0,0.08);
  }
  .cs-swatch-name {
    font-family: 'Roboto', sans-serif; font-size: 0.7rem;
    letter-spacing: 0.08em; text-transform: uppercase;
    line-height: 1.3;
  }
  .cs-swatch-val {
    font-family: 'Roboto', sans-serif; font-size: 0.72rem;
    font-weight: 700; letter-spacing: 0.03em;
    line-height: 1.4;
  }
  .cs-preview-section { gap: 0.75rem; }
  .cs-css-section { gap: 0.75rem; }
  .cs-css-out {
    font-family: 'Roboto', sans-serif; font-size: 0.75rem;
    color: var(--text); line-height: 1.6;
    white-space: pre; overflow-x: auto;
    background: var(--frame-bg); border: 1px solid var(--border);
    padding: 1rem 1.25rem;
    margin: 0;
  }
  @media (max-width: 760px) and (min-width: 761px) {
    .cs-row { grid-template-columns: 2rem 1fr; grid-template-rows: auto auto; }
    .cs-row-var { grid-column: 2; }
    .cs-row-controls { grid-column: 1 / -1; justify-content: flex-start; }
    .cs-hex-input { width: 100%; max-width: none; flex: 1 1 auto; }
    .cs-swatches { grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); }
  }

  /* ===== compass nav (site menu) ===== */
  .header-left { display: flex; align-items: center; gap: 1rem; }
  .header-right { display: flex; align-items: center; gap: 0.5rem; }
  .theme-toggle {
    height: var(--symbol-theme-toggle-size, 2rem);
    width: var(--symbol-theme-toggle-size, 2rem);
    display: inline-flex; align-items: center; justify-content: center;
    background: none; border: 0; border-radius: 0;
    color: var(--header-theme-toggle); cursor: pointer; padding: 0;
    transition: color .18s ease;
  }
  .theme-toggle:hover { color: var(--header-theme-toggle-hover); }
  .theme-toggle:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
  .theme-toggle svg { width: var(--symbol-theme-toggle-size, 2rem); height: var(--symbol-theme-toggle-size, 2rem); display: block; }
  .compass-nav { position: relative; display: inline-flex; align-items: center; }
  .compass-btn {
    height: var(--header-row);
    display: inline-flex; align-items: center; justify-content: center;
    background: none; border: 0; border-radius: 0;
    color: var(--header-compass); cursor: pointer; padding: 0;
    transition: color .18s ease;
  }
  .compass-nav:hover .compass-btn,
  .compass-nav.open .compass-btn { color: var(--header-compass-hover); }
  .compass-btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
  .compass-glyph { width: var(--symbol-compass-size, 3.2rem); height: var(--symbol-compass-size, 3.2rem); display: block; }
  .compass-needle { transform-box: fill-box; transform-origin: center; transition: transform .6s cubic-bezier(.22, 1, .36, 1); }
  .compass-nav:hover .compass-needle,
  .compass-nav.open .compass-needle { transform: rotate(135deg); }
  /* Xenolux emblem glyph, three layers: .emblem-frame (the eight corner marks)
     is fixed, .emblem-ring (the outer and inner arcs) turns one way, and
     .emblem-core (the central X) turns the other. See the note above
     EMBLEM_PATHS in phfx_2026_V01.js for why the corner marks can't rotate.
     Every layer is four-fold symmetric, so 45deg is the half-step for each:
     90deg would land back on itself and read as no movement at all.
     fill-box rather than view-box: both moving layers are centred in the
     viewBox to within 0.15 units (under 0.1px at the 3.2rem header size), and
     fill-box matches the compass rule above and has far wider support. */
  .emblem-ring,
  .emblem-core { transform-box: fill-box; transform-origin: center; transition: transform .6s cubic-bezier(.22, 1, .36, 1); }
  .compass-nav:hover .emblem-ring,
  .compass-nav.open .emblem-ring { transform: rotate(45deg); }
  .compass-nav:hover .emblem-core,
  .compass-nav.open .emblem-core { transform: rotate(-45deg); }
  @media (prefers-reduced-motion: reduce) {
    .compass-needle, .emblem-ring, .emblem-core { transition: none; }
  }

  .compass-menu {
    position: absolute; top: 100%; left: 0; margin-top: 0.45rem;
    min-width: 240px;
    /* Border keyed to the header rule rather than --border, so the dropdown
       reads as an extension of the bar it hangs from - the line under the
       header and the outline of the menu are literally the same colour and
       weight. Falls back to --border if the token is ever missing.

       --header-rule-width is shared with the header's own rule below, so
       changing one changes both. */
    background: var(--surface);
    border: var(--header-rule-width, 1px) solid var(--header-rule, var(--border));
    box-shadow: 0 14px 34px rgba(0, 0, 0, 0.5);
    padding: 0.35rem 0;
    opacity: 0; visibility: hidden; transform: translateY(-6px);
    transition: opacity .18s ease, transform .18s ease, visibility .18s ease;
    z-index: 50;
  }
  .compass-nav:hover .compass-menu,
  .compass-nav.open .compass-menu { opacity: 1; visibility: visible; transform: none; }
  /* invisible hover bridge so moving from the button to the menu doesn't close it */
  .compass-nav::after { content: ''; position: absolute; left: 0; top: 100%; width: 240px; height: 0.5rem; }

  .compass-link {
    display: flex; align-items: center; justify-content: space-between; gap: 1.2rem;
    width: 100%; box-sizing: border-box;
    padding: 0.6rem 1.1rem;
    font-family: 'Roboto', sans-serif; font-size: 0.78rem; font-weight: 400;
    letter-spacing: 0.12em; text-transform: uppercase;
    color: var(--text); background: none; border: 0; text-align: left;
    text-decoration: none; cursor: pointer; white-space: nowrap;
    transition: background .15s ease, color .15s ease;
  }
  .compass-link:hover, .compass-link:focus-visible { background: var(--gold-dim); color: var(--gold); outline: none; }
  .compass-chev { color: var(--text-muted); font-size: 1.05rem; line-height: 1; }
  .compass-link:hover .compass-chev { color: var(--gold); }

  /* Still menu item: label (navigates) + separate chevron (toggles submenu) */
  .compass-parent-row {
    display: flex;
    align-items: stretch;
    transition: background .15s ease;
  }
  .compass-parent-row:hover,
  .compass-group.sub-open > .compass-parent-row {
    background: var(--gold-dim);
  }
  .compass-parent-link {
    flex: 1 1 auto;
    min-width: 0;
  }
  .compass-parent-row:hover .compass-parent-link,
  .compass-group.sub-open > .compass-parent-row .compass-parent-link {
    background: none;
    color: var(--gold);
  }
  .compass-chev-btn {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    padding: 0.6rem 1.1rem 0.6rem 0.5rem;
    background: none;
    border: 0;
    cursor: pointer;
    color: var(--text-muted);
    transition: color .15s ease;
  }
  .compass-parent-row:hover .compass-chev-btn,
  .compass-group.sub-open > .compass-parent-row .compass-chev-btn {
    color: var(--gold);
  }
  .compass-chev-btn .compass-chev { font-size: 1.05rem; line-height: 1; }

  .compass-group { position: relative; }
  .compass-sub {
    position: absolute; left: 100%; top: -0.35rem; margin-left: 1px;
    min-width: 210px;
    background: var(--surface); border: 1px solid var(--border);
    box-shadow: 0 14px 34px rgba(0, 0, 0, 0.5);
    padding: 0.35rem 0;
    opacity: 0; visibility: hidden; transform: translateX(-6px);
    transition: opacity .16s ease, transform .16s ease, visibility .16s ease;
    z-index: 51;
  }
  .compass-group:hover > .compass-sub,
  .compass-group.sub-open > .compass-sub { opacity: 1; visibility: visible; transform: none; }

  @media (max-width: 640px) and (min-width: 761px) {
    .compass-menu { max-height: 76vh; overflow-y: auto; }
    .compass-sub {
      position: static; left: auto; top: auto; margin: 0 0 0 1rem;
      min-width: 0; border: 0; border-left: 1px solid var(--border); box-shadow: none;
      transform: none; opacity: 1; visibility: visible; display: none; padding: 0;
    }
    .compass-group.sub-open > .compass-sub { display: block; }
    .compass-sub .compass-link { padding-left: 1.4rem; font-size: 0.74rem; }
  }

  /* ===== gallery ===== */

  /* Hide Buy Phil a Coffee on gallery pages and article view pages.
     Header and main are SIBLINGS under <body> (phfx-header/phfx-footer use
     display:contents), so a plain descendant selector from .gallery-page
     could never reach into the header — :has() lets us match the header
     based on what's elsewhere in the document instead. */
  body:has(.gallery-page) .header-links a.header-link,
  body:has(.article-page) .header-links a.header-link,
  body:has(.panorama-page) .header-links a.header-link,
  body:has(.panorama-view-page) .header-links a.header-link { display: none; }

  .gallery-page {
    justify-content: flex-start;
    gap: 0;
    padding: 0;
    overflow-y: auto;
    align-items: stretch;
  }

  .gallery-header { display: none; }
  .gallery-rule { display: none; }

  .gallery-toolbar {
    width: 100%;
    max-width: calc(960px + 2rem); /* padding sits outside the 960px content
      column (matching the header's true 960px span) rather than shrinking it —
      box-sizing:border-box means padding would otherwise eat into max-width */
    margin: 0 auto;
    padding: 0.75rem 1rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
  }

  .gallery-toolbar-desc {
    font-family: 'Roboto', sans-serif;
    font-size: 0.78rem;
    letter-spacing: 0.04em;
    color: var(--current);
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .gallery-mode-btns {
    display: flex;
    gap: 0.35rem;
    flex: 0 0 auto;
  }

  .gallery-grid {
    width: 100%;
    max-width: calc(960px + 2rem);
    margin: 0 auto;
    padding: 0 1rem 2rem;
    display: grid;
    grid-template-columns: repeat(8, 1fr);
    gap: 0;
  }

  .gallery-strip {
    width: 100%;
    max-width: calc(960px + 2rem);
    margin: 0 auto;
    padding: 0 1rem 2rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0;
  }

  .gallery-thumb {
    position: relative;
    aspect-ratio: 1 / 1;
    overflow: hidden;
    cursor: pointer;
    background: var(--frame-bg);
  }

  .gallery-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }

  .gallery-thumb::before {
    content: '';
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0);
    transition: background 0.3s ease;
  }
  .gallery-thumb:hover::before { background: rgba(0, 0, 0, 0.55); }

  .gallery-thumb-number {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 1rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.85rem;
    letter-spacing: 0.04em;
    color: var(--gold);
    opacity: 0;
    transition: opacity 0.3s ease;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
    pointer-events: none;
  }
  .gallery-thumb:hover .gallery-thumb-number { opacity: 1; }

  .gallery-thumb-wrap {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
  }
  .gallery-thumb-wrap[draggable="true"] { cursor: grab; }
  .gallery-thumb-wrap.is-dragging { opacity: 0.4; }
  .gallery-thumb-wrap.drag-over .gallery-thumb { box-shadow: inset 0 0 0 2px var(--gold); }
  .gallery-thumb-controls {
    justify-content: center;
  }
  .gallery-thumb-add-card {
    aspect-ratio: 1 / 1;
  }

  /* Add-image modal status readout */
  .ai-status {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    border: 1px solid var(--border);
    background: var(--bg);
    padding: 0.75rem 1rem;
  }
  .ai-status[hidden] { display: none; }
  .ai-status-row {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
  }
  .ai-status-row > span:first-child { color: var(--text-muted); }
  .ai-status-row > span:last-child { color: var(--text); text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 60%; }
  .ai-status-row .ai-ok  { color: var(--current); }
  .ai-status-row .ai-bad { color: var(--danger); }

  .strip-item {
    cursor: pointer;
    margin-bottom: 0.5rem;
  }

  .strip-item .lb-img-frame { display: block; width: 100%; }
  .strip-item .lb-img-frame img.lb-img,
  .strip-item .lb-img-frame > img:not(.frame-logo) { width: 100%; height: auto; display: block; }

  .strip-caption {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--text-muted);
    text-align: left;
    padding: 0.3rem 0 0.75rem;
    letter-spacing: 0.04em;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
  }
  .strip-caption-left {
    flex: 1 1 auto;
    min-width: 0;
  }

  .lb-overlay {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 200;
    background: rgba(0, 0, 0, 0.75);
    align-items: center;
    justify-content: center;
  }

  .lb-overlay.active { display: flex; }

  .lb-gallery-name { display: none; }

  /* ── Fullscreen: strip back to the picture ──

     Everything goes: gallery name, index, caption and the control cluster. In
     fullscreen the picture is the whole point, and a counter or a row of
     buttons is the sort of thing you stop seeing but never stop noticing.

     Exit stays available by keyboard - Escape leaves fullscreen, F toggles it -
     and both work while nothing is on screen, so hiding the controls closes no
     doors. The watermark stays: it is the one piece of chrome that belongs to
     the image rather than to the interface. */
  .lb-overlay.is-fs .lb-gallery-name,
  .lb-overlay.is-fs .lb-index,
  .lb-overlay.is-fs .lb-below,
  .lb-overlay.is-fs .lb-controls { display: none; }

  /* No pointer either - a still cursor over a full-bleed picture is the last
     bit of interface left. */
  .lb-overlay.is-fs { cursor: none; }

  .lb-index {
    position: absolute;
    top: 0;
    left: 0;
    transform: translateY(-100%);
    padding-bottom: 0.2rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.72rem;
    color: var(--text-muted);
    letter-spacing: 0.06em;
    white-space: nowrap;
    pointer-events: none;
  }

  .lb-image-wrap {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    max-width: calc(100vw - 2rem);
    max-height: calc(100vh - 4rem);
  }

  .lb-img {
    display: block;
    max-width: 100%;
    max-height: calc(100vh - 6rem);
    width: auto;
    height: auto;
    object-fit: contain;
  }

  .lb-img-frame {
    position: relative;
    display: inline-block;
    line-height: 0;
  }

  /* pointer, not default: the images inside .lb-img-frame are pointer-events:none,
     so this layer is the hover target for the whole frame, and cursor resolves on
     the topmost element under the pointer — .strip-item's cursor:pointer never
     shows through. Every region this layer covers is genuinely clickable (strip
     opens the lightbox; lightbox halves page prev/next), so the hand is accurate.
     Right-click blocking is unaffected — that's the contextmenu listener in
     gallery.js, not this rule. */
  .img-protect {
    position: absolute;
    inset: 0;
    z-index: 10;
    cursor: pointer;
    -webkit-user-select: none;
    user-select: none;
  }

  .lb-img-frame img {
    -webkit-user-drag: none;
    user-drag: none;
    -webkit-user-select: none;
    user-select: none;
    pointer-events: none;
  }

  .lb-img-frame .frame-logo {
    pointer-events: none;
    width: var(--symbol-watermark-gallery-pct, 12%);
    max-width: var(--symbol-watermark-gallery-max, 640px);
    filter: none;
  }

  /* Login page's animated lock canvas (see /login/index.html inline script).
     The canvas's raster resolution stays 128x128 regardless — only the
     CSS display size scales with this token. */
  .login-lock-canvas {
    display: block;
    width: var(--symbol-login-lock-size, 128px);
    height: var(--symbol-login-lock-size, 128px);
  }

  .lb-controls {
    position: absolute;
    top: 0;
    right: 0;
    display: flex;
    align-items: center;
    gap: 0.1rem;
    transform: translateY(-100%);
    padding-bottom: 0.2rem;
  }

  .lb-btn {
    background: none;
    border: none;
    color: var(--text-muted);
    font-family: 'Roboto', sans-serif;
    font-size: 1rem;
    cursor: pointer;
    padding: 0.3rem 0.45rem;
    line-height: 1;
    transition: color 0.15s ease;
  }

  .lb-btn:hover { color: var(--text); }
  .lb-btn-close { font-size: 1.1rem; }
  .lb-btn-download { color: var(--current); }
  .lb-btn-download:hover { color: var(--text); }
  .lb-btn-fs { display: inline-flex; align-items: center; justify-content: center; }
  .lb-btn-fs svg { display: block; }
  .lb-btn-newwin,
  .lb-btn-share { display: inline-flex; align-items: center; justify-content: center; }
  .lb-btn-newwin svg,
  .lb-btn-share svg { display: block; }

  /* Strip image action buttons (bottom-right overlay) */
  .strip-caption-right {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex: 0 0 auto;
  }
  .strip-caption-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    color: var(--text-muted);
    cursor: pointer;
    padding: 0;
    line-height: 0;
    text-decoration: none;
    transition: color 0.15s ease;
  }
  .strip-caption-icon:hover { color: var(--text); }
  .strip-caption-icon svg { display: block; }
  .strip-copy-url-icon { color: var(--danger); }
  .strip-copy-url-icon:hover { opacity: 0.75; color: var(--danger); }

  /* Admin edit description link */
  .lb-edit-link {
    background: none;
    border: none;
    padding: 0;
    font-family: 'Roboto', sans-serif;
    font-size: 0.68rem;
    letter-spacing: 0.06em;
    color: var(--danger);
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: opacity 0.15s ease;
  }
  .lb-edit-link:hover { opacity: 0.75; }

  /* Admin reorder arrows in the lightbox (move image earlier/later in gallery order) */
  .lb-reorder-btn {
    background: none;
    border: none;
    padding: 0 0.35rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
    cursor: pointer;
    line-height: 0;
    transition: color 0.15s ease;
  }
  .lb-reorder-btn svg { display: block; }
  .lb-reorder-btn:hover { color: var(--gold); }
  .lb-reorder-btn:disabled { opacity: 0.3; cursor: not-allowed; }

  /* Lightbox controls left: sits left of the arrow buttons */
  .lb-controls-left {
    margin-right: 0.5rem;
    display: flex;
    align-items: center;
  }

  .lb-below {
    display: flex;
    justify-content: flex-end;
    width: 100%;
    padding-top: 0.4rem;
  }

  .lb-caption {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.1rem;
    flex: 1 1 auto;
    min-width: 0;
  }

  .lb-caption-title {
    font-family: 'Roboto', sans-serif;
    font-size: 0.8rem;
    color: var(--text);
    letter-spacing: 0.04em;
    text-align: right;
  }

  .lb-caption-subtitle {
    font-family: 'Roboto', sans-serif;
    font-size: 0.72rem;
    color: var(--text-muted);
    letter-spacing: 0.04em;
    text-align: right;
  }

  .lb-caption-date {
    font-family: 'Roboto', sans-serif;
    font-size: 0.68rem;
    color: var(--text-muted);
    letter-spacing: 0.04em;
    text-align: right;
  }

  .lb-caption-links {
    display: flex;
    gap: 0.75rem;
    margin-top: 0.2rem;
    justify-content: flex-end;
    flex-wrap: wrap;
  }

  .lb-caption-link {
    font-family: 'Roboto', sans-serif;
    font-size: 0.68rem;
    color: var(--current);
    text-decoration: none;
    letter-spacing: 0.04em;
    transition: color 0.15s ease;
  }

  .lb-caption-link:hover { color: var(--gold); }

  .gallery-message {
    width: 100%;
    text-align: center;
    padding: 3rem 1rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.82rem;
    color: var(--text-muted);
    letter-spacing: 0.06em;
  }

  /* Centered feedback toast (e.g. "Share URL Copied!") */
  .share-toast {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) scale(0.96);
    z-index: 400;
    background: var(--surface);
    border: 1px solid var(--gold);
    color: var(--gold);
    font-family: 'Roboto', sans-serif;
    font-size: 0.82rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: 0.85rem 1.5rem;
    box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6);
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.22s ease, transform 0.22s ease;
  }
  .share-toast.show {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
  }

  /* still lander gallery cards */
  .still-page .tool-grid {
    grid-template-columns: repeat(3, 1fr);
    gap: 0;
  }

  .still-page .tool-card {
    aspect-ratio: 2 / 1;
    min-height: 0;
  }
  .still-page .tool-card.has-image {
    border: 0;
  }

  .tool-card.has-image {
    position: relative;
    overflow: hidden;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
  }

  .tool-card.has-image::before {
    content: '';
    position: absolute;
    inset: 0;
    background: inherit;
    background-size: cover;
    background-position: center;
    filter: brightness(1);
    transition: filter 0.35s ease;
    z-index: 0;
  }

  .tool-card.has-image:hover::before {
    filter: brightness(0.35);
  }

  .tool-card.has-image .tool-name,
  .tool-card.has-image .tool-desc {
    position: relative;
    z-index: 1;
    opacity: 0;
    transition: opacity 0.35s ease;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
  }

  .tool-card.has-image:hover {
    transform: none;
    box-shadow: none;
    border-color: var(--border);
  }

  .tool-card.has-image:hover .tool-name,
  .tool-card.has-image:hover .tool-desc {
    opacity: 1;
  }

  /* ===== still lander card text — mirrors the motion lander =====

     Same sizes and the same three-line arrangement as .motion-card-title /
     -supra / -infra: a centred title on the card's mid-line, with optional
     lines anchored above and below it. The lines are siblings of the title,
     not children, and anchor to the mid-line from either side (bottom:50% /
     top:50%), so the title sits in exactly the same place whether neither,
     one, or both are present.

     Scoped to .still-page because .tool-card is shared with the tools lander,
     which keeps its own top-aligned name + description block. These rules sit
     after the .has-image block above so they win on order — that block sets
     position:relative on .tool-name at equal specificity. */
  .still-page .tool-card { position: relative; padding: 0; gap: 0; }
  .still-page .tool-card .tool-name {
    position: absolute;
    inset: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 1rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.85rem;
    font-weight: 400;
    letter-spacing: 0.04em;
    color: var(--gold);
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
    pointer-events: none;
  }
  .still-page .tool-card.has-image .tool-name { position: absolute; }

  .still-page .tool-card .still-card-supra,
  .still-page .tool-card .still-card-infra {
    position: absolute;
    left: 0;
    right: 0;
    z-index: 1;
    padding: 0 1rem;
    text-align: center;
    font-family: 'Roboto Mono', monospace;
    letter-spacing: 0.12em;
    color: var(--gold);
    opacity: 1;
    transition: opacity 0.35s ease;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
    pointer-events: none;
  }
  .still-page .tool-card .still-card-supra {
    bottom: 50%;
    margin-bottom: 0.95rem;
    font-size: 0.62rem;
    text-transform: uppercase;
  }
  .still-page .tool-card .still-card-infra {
    top: 50%;
    margin-top: 0.95rem;
    font-size: 0.66rem;
  }
  /* They follow the title: hidden until hover on a picture tile, always on
     for a bordered surface card. */
  .still-page .tool-card.has-image .still-card-supra,
  .still-page .tool-card.has-image .still-card-infra { opacity: 0; }
  .still-page .tool-card.has-image:hover .still-card-supra,
  .still-page .tool-card.has-image:hover .still-card-infra { opacity: 1; }

  /* The artwork lander rides .still-page but still renders a .tool-desc where
     the still lander now renders .still-card-infra. Give it the same treatment
     — without this it stays in normal flow inside a card whose padding is 0
     and lands flush in the top-left corner. */
  .still-page .tool-card .tool-desc {
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    margin-top: 0.95rem;
    z-index: 1;
    padding: 0 1rem;
    text-align: center;
    font-family: 'Roboto Mono', monospace;
    font-size: 0.66rem;
    letter-spacing: 0.12em;
    color: var(--gold);
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
    pointer-events: none;
  }

  /* ===== Reveal Titles — still / artwork cards =====

     Inverts the hover behaviour on picture tiles: every title shown, and the
     card under the pointer hides its own so the photograph is unobstructed.
     Only active while <main> carries .reveal-titles, which the header button
     toggles — a lander that never calls phfxRevealTitles() is unaffected.

     The dimming inverts with it. Note this card darkens via filter:brightness
     on a duplicated background layer rather than an opacity scrim like the
     motion and panorama cards — so the inversion is a brightness swap, not an
     opacity swap. */
  .reveal-titles .tool-card.has-image .tool-name,
  .reveal-titles .tool-card.has-image .tool-desc,
  .reveal-titles .tool-card.has-image .still-card-supra,
  .reveal-titles .tool-card.has-image .still-card-infra { opacity: 1; }

  .reveal-titles .tool-card.has-image:hover .tool-name,
  .reveal-titles .tool-card.has-image:hover .tool-desc,
  .reveal-titles .tool-card.has-image:hover .still-card-supra,
  .reveal-titles .tool-card.has-image:hover .still-card-infra { opacity: 0; }

  .reveal-titles .tool-card.has-image::before { filter: brightness(0.35); }
  .reveal-titles .tool-card.has-image:hover::before { filter: brightness(1); }

  @media (max-width: 760px) and (min-width: 761px) {
    .gallery-grid { grid-template-columns: repeat(4, 1fr); }
    .still-page .tool-grid { grid-template-columns: repeat(2, 1fr); }
  }
  @media (max-width: 440px) and (min-width: 761px) {
    .gallery-grid { grid-template-columns: repeat(3, 1fr); }
    .still-page .tool-grid { grid-template-columns: 1fr; }
  }

  /* ===== still-lander admin gallery management ===== */
  .gallery-admin-line {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    letter-spacing: 0.06em;
    color: var(--text-muted);
    text-decoration: none;
    cursor: pointer;
    transition: color 0.18s ease;
  }
  .gallery-admin-line:hover { color: var(--danger); }

  .intro-admin-controls {
    width: 100%;
    max-width: 960px;
    margin: -0.75rem auto 0;
    display: flex;
    gap: 0.5rem;
  }
  .intro-admin-controls[hidden] { display: none; }
  .intro-admin-controls .tool-card-admin-btn {
    flex: 0 0 auto;
    padding: 0 0.85rem;
    height: 1.9rem;
  }

  .tool-private-badge {
    position: absolute;
    top: 0.5rem;
    left: 0.5rem;
    z-index: 2;
    font-family: 'Roboto', sans-serif;
    font-size: 0.62rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--danger);
    background: rgba(0, 0, 0, 0.65);
    border: 1px solid var(--danger);
    padding: 0.15rem 0.4rem;
  }

  .tool-card { position: relative; }
  .tool-card.is-draggable { cursor: grab; }
  .tool-card.is-dragging { opacity: 0.4; }
  .tool-card.drag-over { border-color: var(--gold); box-shadow: inset 0 0 0 2px var(--gold); }

  /* Panorama grid reorder. Matches the gallery and tools conventions above:
     the tile being dragged fades, the drop target takes a 2px gold inset.
     Styled on .gallery-admin-item rather than .panorama-card because the
     draggable element is the wrap - the card carries a navigate-on-click that
     would fight a drag started on it. */
  .gallery-admin-item.is-dragging { opacity: 0.4; }
  .gallery-admin-item.drag-over .panorama-card { box-shadow: inset 0 0 0 2px var(--gold); }
  /* grab cursor only in edit mode, where the wrap exists at all. */
  .gallery-admin-item[draggable="true"] { cursor: grab; }
  .gallery-admin-item[draggable="true"].is-dragging { cursor: grabbing; }

  /* Admin controls overlay the bottom of the card rather than sitting below
     it. A flex column here made every card taller the moment edit mode was
     entered, which reflowed the whole grid — cards shifted under the cursor
     just as you went to click one. Absolutely positioned, edit mode costs no
     layout at all and the grid stays exactly where it was.

     Scoped to .gallery-admin-item so the tools page keeps the stacked
     arrangement its own cards were built around. */
  .gallery-admin-item { position: relative; display: block; }

  .gallery-admin-item .tool-card-admin-controls {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 6;
    gap: 0.35rem;
    padding: 0.45rem;
    /* Scrim so the buttons stay legible over a bright picture without
       hiding the card's own title and description entirely. */
    background: linear-gradient(to top, rgba(0, 0, 0, 0.88) 0%, rgba(0, 0, 0, 0.62) 60%, rgba(0, 0, 0, 0) 100%);
  }

  .gallery-admin-item .tool-card-admin-btn {
    height: 1.6rem;
    background: rgba(0, 0, 0, 0.55);
    font-size: 0.72rem;
  }

  .tool-card-admin-controls {
    display: flex;
    gap: 0.5rem;
  }

  .tool-card-admin-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 1 1 0;
    height: 1.7rem;
    background: var(--btn-admin-bg, transparent);
    border: 1px solid var(--btn-admin-border, var(--danger));
    color: var(--btn-admin-fg, var(--danger));
    font-family: 'Roboto', sans-serif;
    font-size: var(--btn-admin-size, 0.85rem);
    border-radius: var(--btn-admin-radius, 0);
    line-height: 1;
    cursor: pointer;
    transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
  }
  .tool-card-admin-btn:hover {
    background: var(--btn-admin-hover-bg, var(--danger));
    color: var(--btn-admin-hover-fg, var(--bg));
  }
  .tool-card-admin-btn.move-btn { flex: 0 0 2.2rem; }
  .tool-card-admin-btn:disabled {
    opacity: 0.3;
    cursor: not-allowed;
    background: none;
    color: var(--danger);
  }

  .gallery-add-card {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 1.9 / 1;
    min-height: 0;
    background: none;
    border: 1px dashed var(--border);
    color: var(--text-muted);
    cursor: pointer;
    font-family: 'Roboto', sans-serif;
    font-size: 2rem;
    font-weight: 300;
    transition: border-color 0.18s ease, color 0.18s ease, background 0.18s ease;
  }
  .gallery-add-card:hover {
    border-color: var(--gold);
    color: var(--gold);
    background: var(--gold-dim);
  }

  /* Reuse dof-modal styling pattern for the add/edit gallery dialog */
  .gm-modal { position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center; padding: 1.5rem; }
  .gm-modal[hidden] { display: none; }
  .gm-modal-backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.7); }
  .gm-modal-box {
    position: relative; max-width: 420px; width: 100%;
    max-height: 90vh; overflow-y: auto;
    background: var(--surface); border: 1px solid var(--gold); padding: 1.5rem 1.75rem;
    box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6);
    display: flex; flex-direction: column; gap: 0.9rem;
  }
  .gm-modal-close {
    position: absolute; top: 0.5rem; right: 0.7rem; background: none; border: 0;
    color: var(--text-muted); font-size: 1.4rem; line-height: 1; cursor: pointer; padding: 0.2rem;
    transition: color 0.18s ease;
  }
  .gm-modal-close:hover { color: var(--gold); }
  .gm-modal-title { font-family: 'Roboto', sans-serif; font-size: 0.95rem; letter-spacing: 0.04em; color: var(--gold); margin: 0 1.5rem 0 0; }
  .gm-field { display: flex; flex-direction: column; gap: 0.3rem; }
  .gm-field label {
    font-family: 'Roboto', sans-serif; font-size: 0.72rem; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--text-muted);
  }
  .gm-field .text-input { width: 100%; max-width: none; }
  .gm-field .text-input:disabled { opacity: 0.5; cursor: not-allowed; }
  .gm-modal-actions { display: flex; justify-content: flex-end; gap: 0.5rem; margin-top: 0.3rem; }
  .gm-error { font-family: 'Roboto', sans-serif; font-size: 0.72rem; color: var(--danger); min-height: 1em; }

  /* ===== cinematography (motion) lander ===== */
  .motion-page { justify-content: flex-start; gap: 1.25rem; }

  .motion-categories {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem 0.9rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.78rem;
    flex: 1 1 auto;
    min-width: 0;
  }
  .motion-categories-row {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
  }
  .motion-add-new-btn { flex: 0 0 auto; }
  .motion-add-new-btn[hidden] { display: none; }
  /* Filter bar states, three genuinely distinct steps:
       rest   muted — unselected filters recede
       hover  gold  — the site-wide "interactive" cue, same as buttons and nav
       active cyan  — the site-wide "you are here" cue, same as .subtitle
                      .current and .shop-status.is-available

     The original had rest cyan with BOTH hover and active gold, so pointing at
     an unselected filter made it identical to the selected one — the "where am
     I" signal was erased by the "what am I pointing at" signal. Gold and cyan
     also share s73/l71, so a cyan-rest row had every item at equal weight and
     no visible hierarchy.

     Shared with .shop-cat-link, which uses the same three steps. */
  .motion-cat-link {
    color: var(--text-muted);
    text-decoration: none;
    cursor: pointer;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    transition: color 0.15s ease;
  }
  .motion-cat-link:hover { color: var(--gold); }
  /* Active outranks hover: pointing at the selected filter must not change
     what it is telling you. */
  .motion-cat-link.active,
  .motion-cat-link.active:hover { color: var(--current); }
  .motion-cat-link:focus-visible { outline: 1px solid var(--gold); outline-offset: 3px; }

  .motion-grid {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0;
  }

  /* Mirrors .tool-card on the still lander: --surface with a border by
     default, and the title always readable. A poster adds .has-image, which
     drops the border and hides the title until hover so the picture carries
     the card. Without that split, a film with no title image rendered as a
     blank box with nothing on it. */
  .motion-card {
    position: relative;
    aspect-ratio: 2 / 1;
    overflow: hidden;
    cursor: pointer;
    background: var(--surface);
    border: 1px solid var(--border);
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    transition: border-color 0.18s ease, background 0.18s ease;
  }
  .motion-card.has-image { border: 0; }

  /* Hover scrim only where there's a picture to darken. On a plain surface
     card it would just muddy the panel, so those get the still lander's
     gold-border treatment instead. */
  .motion-card.has-image::before {
    content: '';
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0);
    transition: background 0.3s ease;
  }
  .motion-card.has-image:hover::before { background: rgba(0, 0, 0, 0.55); }
  .motion-card:not(.has-image):hover {
    border-color: var(--gold);
    background: var(--surface-hover);
  }

  .motion-card-title {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 1rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.85rem;
    letter-spacing: 0.04em;
    color: var(--gold);
    opacity: 1;
    transition: opacity 0.3s ease;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
    pointer-events: none;
  }
  /* Only a card carrying a picture hides its title until hover. */
  .motion-card.has-image .motion-card-title { opacity: 0; }
  .motion-card.has-image:hover .motion-card-title { opacity: 1; }

  /* Optional lines above and below the title.

     Anchored to the card's mid-line rather than stacked with the title:
     bottom:50% pins the top line's baseline edge to the centre and grows it
     upward, top:50% does the mirror. The title itself is a full-card flex box
     centring its text on that same mid-line, so it stays put whether either
     of these exists, one exists, or both do — which is the whole point.

     The margins clear half a line of the title (0.85rem text) plus a gap. A
     title long enough to wrap to two lines will start to crowd them; that's
     the trade for never moving the title. */
  .motion-card-supra,
  .motion-card-infra {
    position: absolute;
    left: 0;
    right: 0;
    padding: 0 1rem;
    text-align: center;
    font-family: 'Roboto Mono', monospace;
    letter-spacing: 0.12em;
    color: var(--gold);
    opacity: 1;
    transition: opacity 0.3s ease;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
    pointer-events: none;
  }
  .motion-card-supra {
    bottom: 50%;
    margin-bottom: 0.95rem;
    font-size: 0.62rem;
    text-transform: uppercase;
  }
  .motion-card-infra {
    top: 50%;
    margin-top: 0.95rem;
    font-size: 0.66rem;
  }
  /* They follow the title: hidden until hover on a picture tile, always on
     for a bordered surface card. */
  .motion-card.has-image .motion-card-supra,
  .motion-card.has-image .motion-card-infra { opacity: 0; }
  .motion-card.has-image:hover .motion-card-supra,
  .motion-card.has-image:hover .motion-card-infra { opacity: 1; }

  /* ===== Reveal Titles =====

     Inverts the hover behaviour on picture tiles: every title is shown, and
     pointing at a card hides its own so the photograph underneath is
     unobstructed. Scoped to .reveal-titles on <main>, toggled by the header
     button, so nothing changes for a visitor who never presses it.

     The scrim follows the same inversion — with the titles already legible
     there is nothing for it to improve, and it would only be darkening the
     picture the visitor is trying to look at.

     Bordered cards without a picture are deliberately untouched: their titles
     are always on regardless, so there is nothing to invert. */
  .reveal-titles .motion-card.has-image .motion-card-title,
  .reveal-titles .motion-card.has-image .motion-card-supra,
  .reveal-titles .motion-card.has-image .motion-card-infra { opacity: 1; }

  .reveal-titles .motion-card.has-image:hover .motion-card-title,
  .reveal-titles .motion-card.has-image:hover .motion-card-supra,
  .reveal-titles .motion-card.has-image:hover .motion-card-infra { opacity: 0; }

  .reveal-titles .motion-card.has-image::before { background: rgba(0, 0, 0, 0.55); }
  .reveal-titles .motion-card.has-image:hover::before { background: rgba(0, 0, 0, 0); }

  .motion-add-card {
    aspect-ratio: 2 / 1;
  }

  /* Video lightbox */
  .motion-lb {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 200;
    background: rgba(0, 0, 0, 0.85);
    align-items: center;
    justify-content: center;
    padding: 2.5rem 1.5rem 1.5rem;
  }
  .motion-lb.active { display: flex; }
  .motion-lb-inner {
    position: relative;
    width: 100%;
    max-width: 960px;
    aspect-ratio: 16 / 9;
    background: #000;
  }
  .motion-lb-inner iframe {
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
  }
  .motion-lb-close {
    position: absolute;
    top: -2.2rem;
    right: 0;
    background: none;
    border: none;
    color: var(--text);
    font-size: 1.7rem;
    line-height: 1;
    cursor: pointer;
    padding: 0.2rem;
    transition: color 0.15s ease;
  }
  .motion-lb-close:hover { color: var(--gold); }

  @media (max-width: 760px) and (min-width: 761px) {
    .motion-grid { grid-template-columns: repeat(2, 1fr); }
  }
  @media (max-width: 460px) and (min-width: 761px) {
    .motion-grid { grid-template-columns: 1fr; }
  }

  /* ===== panorama lander ===== */
  .panorama-page { justify-content: flex-start; gap: 1.25rem; }
  /* The add-new button lives in its own row so it can right-align above the
     grid. That row is empty for non-admins and outside edit mode, and an
     empty flex child still consumes one of .panorama-page's 1.25rem gaps -
     which is why the space under the category bar was double the motion
     page's. :has() removes the row from layout when its only button is
     hidden; :empty covers the same case if the button is ever removed rather
     than hidden. */
  .panorama-add-row {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    display: flex;
    justify-content: flex-end;
  }
  .panorama-add-row:empty,
  .panorama-add-row:has(> button[hidden]:only-child) { display: none; }
  .panorama-add-new-btn[hidden] { display: none; }

  .panorama-grid {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0;
  }
  .panorama-card {
    position: relative;
    aspect-ratio: 2 / 1;
    overflow: hidden;
    cursor: pointer;
    background: var(--frame-bg);
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
  }
  .panorama-card::before {
    content: '';
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0);
    transition: background 0.3s ease;
  }
  .panorama-card:hover::before { background: rgba(0, 0, 0, 0.55); }
  .panorama-card-title {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 1rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.85rem;
    letter-spacing: 0.04em;
    color: var(--gold);
    opacity: 0;
    transition: opacity 0.3s ease;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
    pointer-events: none;
  }
  .panorama-card:hover .panorama-card-title { opacity: 1; }

  /* Reveal Titles — panorama. Every title on, and the card under the pointer
     hides its own so the picture is unobstructed. The scrim inverts with it:
     it exists to make a title legible over a photograph, so with the titles
     already showing it belongs at rest and lifts on hover. */
  .reveal-titles .panorama-card-title { opacity: 1; }
  .reveal-titles .panorama-card:hover .panorama-card-title { opacity: 0; }
  .reveal-titles .panorama-card::before { background: rgba(0, 0, 0, 0.55); }
  .reveal-titles .panorama-card:hover::before { background: rgba(0, 0, 0, 0); }
  @media (max-width: 760px) and (min-width: 761px) { .panorama-grid { grid-template-columns: repeat(2, 1fr); } }
  @media (max-width: 460px) and (min-width: 761px) { .panorama-grid { grid-template-columns: 1fr; } }

  /* ===== panorama viewer ===== */
  /* A panorama with no thumbnail. The card used to fall back to the full
     equirect, which cost megabytes and stalled scrolling; it now renders
     empty. Marked with a dashed border so it reads as "nothing here yet"
     rather than as a broken image or a failed load. */
  .panorama-card.is-missing-thumb {
    background: var(--surface);
    border: 1px dashed var(--border);
  }
  .panorama-card.is-missing-thumb::after {
    content: 'no thumbnail';
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: 'Roboto Mono', monospace;
    font-size: 0.62rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
    pointer-events: none;
  }

  .panorama-view-page { justify-content: flex-start; gap: 1.5rem; }
  .pano-viewer-frame {
    position: relative;
    width: 100%;
    max-width: 960px;
    aspect-ratio: 3 / 2;
    margin: 0 auto;
    background: #000;
    overflow: hidden;
    cursor: grab;
    touch-action: none;
  }
  .pano-viewer-frame:active { cursor: grabbing; }
  .pano-viewer-frame canvas {
    width: 100%;
    height: 100%;
    display: block;
    opacity: 0;
    transition: opacity 1.0s ease;
  }
  .pano-viewer-frame.is-ready canvas {
    opacity: 1;
  }
  .pano-loading {
    position: absolute;
    inset: 0;
    z-index: 4;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.85rem;
    background: var(--frame-bg);
    pointer-events: none;
  }
  .pano-loading[hidden] { display: none; }
  .pano-spinner {
    width: 2.6rem;
    height: 2.6rem;
    border: 2px solid var(--border);
    border-top-color: var(--gold);
    border-radius: 50%;
    animation: pano-spin 0.9s linear infinite;
    transition: border-color 0.3s ease, box-shadow 0.3s ease;
  }
  .pano-spinner.is-done {
    border-color: var(--gold);
    animation: none;
    box-shadow: 0 0 12px var(--gold-dim), 0 0 4px var(--gold);
  }
  @keyframes pano-spin {
    to { transform: rotate(360deg); }
  }
  .pano-loading-label {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--gold);
  }
  @media (prefers-reduced-motion: reduce) {
    .pano-spinner { animation-duration: 2.4s; }
    .pano-viewer-frame canvas { transition: none; }
  }
  .pano-viewer-frame .frame-logo {
    width: var(--symbol-watermark-panoviewer-pct, 12%);
    max-width: var(--symbol-watermark-panoviewer-max, 640px);
    z-index: 5;
  }
  .pano-fs-btn {
    position: absolute;
    top: 0.6rem;
    right: 0.6rem;
    z-index: 6;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    background: rgba(0, 0, 0, 0.45);
    border: 1px solid var(--border);
    color: var(--text);
    cursor: pointer;
    transition: color 0.18s ease, border-color 0.18s ease;
  }
  .pano-fs-btn:hover { color: var(--gold); border-color: var(--gold); }
  .pano-fs-btn svg { display: block; fill: currentColor; }
  .pano-info-btn {
    position: absolute;
    top: 0.6rem;
    left: 0.6rem;
    z-index: 6;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.45);
    border: 1px solid var(--border);
    color: var(--text);
    cursor: pointer;
    transition: color 0.18s ease, border-color 0.18s ease, background 0.18s ease;
  }
  .pano-info-btn:hover { color: var(--gold); border-color: var(--gold); }
  .pano-info-btn.active { color: var(--gold); border-color: var(--gold); background: var(--gold-dim); }
  .pano-info-btn svg { display: block; }
  .pano-hint {
    position: absolute;
    bottom: 0.6rem;
    left: 0.75rem;
    z-index: 6;
    font-family: 'Roboto', sans-serif;
    font-size: 0.7rem;
    letter-spacing: 0.04em;
    color: var(--text);
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
    opacity: 0.85;
    transition: opacity 0.6s ease;
    pointer-events: none;
  }
  .pano-focal-readout {
    font-family: 'Roboto', sans-serif;
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    color: var(--gold);
    background: rgba(0, 0, 0, 0.45);
    border: 1px solid var(--border);
    padding: 0.3rem 0.55rem;
  }
  .pano-focal-readout[hidden] { display: none; }
  .pano-format-name-readout {
    font-family: 'Roboto', sans-serif;
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    color: var(--gold);
    background: rgba(0, 0, 0, 0.45);
    border: 1px solid var(--border);
    padding: 0.3rem 0.55rem;
  }
  .pano-format-name-readout[hidden] { display: none; }
  .pano-hud-topleft {
    position: absolute;
    top: 0.6rem;
    left: 0.6rem;
    z-index: 6;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    pointer-events: none;
  }
  .pano-orient-readout {
    font-family: 'Roboto', sans-serif;
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    color: var(--gold);
    background: rgba(0, 0, 0, 0.45);
    border: 1px solid var(--border);
    padding: 0.3rem 0.55rem;
  }
  .pano-orient-readout[hidden] { display: none; }
  .pano-ref-format-btn,
  .pano-hide-info-btn {
    font-family: 'Roboto', sans-serif;
    font-size: var(--btn-overlay-size, 0.72rem);
    letter-spacing: var(--btn-overlay-tracking, 0.04em);
    color: var(--btn-overlay-fg, var(--text));
    background: var(--btn-overlay-bg, rgba(0, 0, 0, 0.45));
    border: 1px solid var(--btn-overlay-border, var(--border));
    padding: var(--btn-overlay-pad-y, 0.3rem) var(--btn-overlay-pad-x, 0.55rem);
    border-radius: var(--btn-overlay-radius, 0);
    cursor: pointer;
    pointer-events: auto;
    text-align: left;
    transition: color 0.18s ease, border-color 0.18s ease;
  }
  .pano-ref-format-btn:hover,
  .pano-hide-info-btn:hover { color: var(--gold); border-color: var(--gold); }
  .pano-ref-format-btn[hidden],
  .pano-hide-info-btn[hidden] { display: none; }
  .pano-context-menu {
    position: absolute;
    z-index: 7;
    min-width: 180px;
    background: var(--surface);
    border: 1px solid var(--border);
    box-shadow: 0 14px 34px rgba(0, 0, 0, 0.5);
  }
  .pano-context-menu[hidden] { display: none; }
  .pano-context-item {
    display: block;
    width: 100%;
    text-align: left;
    padding: 0.55rem 1rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.78rem;
    letter-spacing: 0.03em;
    color: var(--text);
    background: none;
    border: 0;
    border-top: 1px solid var(--border);
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
  }
  .pano-context-item:first-child { border-top: 0; }
  .pano-context-item:hover { background: var(--gold-dim); color: var(--gold); }
  .pano-context-item.active { color: var(--gold); }
  .pano-context-divider { border-top: 1px solid var(--border); margin: 0.25rem 0; }

  .panorama-view-meta {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }
  .panorama-draft-banner {
    width: 100%;
    font-family: 'Roboto', sans-serif;
    font-size: 0.72rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--danger);
    border: 1px solid var(--danger);
    background: var(--danger-dim);
    padding: 0.5rem 0.85rem;
  }
  .panorama-masthead {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 1.5rem;
    flex-wrap: wrap;
  }
  .panorama-masthead-left {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    text-align: left;
  }
  .panorama-masthead-right {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.5rem;
    text-align: right;
  }
  .panorama-view-title {
    font-family: 'Roboto', sans-serif;
    font-size: 1rem;
    font-weight: 400;
    color: var(--current);
    letter-spacing: 0.05em;
    text-align: left;
  }
  .panorama-subtitle {
    font-family: 'Roboto', sans-serif;
    font-size: 0.85rem;
    color: var(--text-muted);
    letter-spacing: 0.02em;
  }
  .panorama-acq-date,
  .panorama-acq-height {
    font-family: 'Roboto', sans-serif;
    font-size: 0.8rem;
    color: var(--text-muted);
    letter-spacing: 0.04em;
    white-space: nowrap;
  }
  .panorama-admin-controls {
    display: flex;
    justify-content: flex-start;
    gap: 0.5rem;
  }
  .panorama-admin-controls[hidden] { display: none; }
  .panorama-description {
    font-family: 'Roboto', sans-serif;
    font-size: 0.92rem;
    line-height: 1.7;
    color: var(--text);
    text-align: left;
    white-space: pre-line;
  }

  /* ===== insights ===== */
  .insights-page { justify-content: flex-start; gap: 1.25rem; }

  .insights-categories-row {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
  }
  .insights-categories {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem 0.9rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.78rem;
    flex: 1 1 auto;
    min-width: 0;
  }
  /* Three distinct steps, matching .motion-cat-link and .shop-cat-link:
     unselected filters recede at --text-muted, gold marks the one under the
     pointer as it does on every other control, and --current marks the one in
     force — which is what that token is named for.

     This previously ran cyan at rest with BOTH hover and active gold, so
     pointing at an unselected filter made it identical to the selected one. */
  .insights-cat-link {
    color: var(--text-muted);
    text-decoration: none;
    cursor: pointer;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    transition: color 0.15s ease;
  }
  .insights-cat-link:hover { color: var(--gold); }
  /* Active outranks hover: pointing at the selected filter must not change
     what it is telling you. */
  .insights-cat-link.active,
  .insights-cat-link.active:hover { color: var(--current); }
  .insights-cat-link:focus-visible { outline: 1px solid var(--gold); outline-offset: 3px; }
  .insights-add-new-btn { flex: 0 0 auto; }
  .insights-add-new-btn[hidden] { display: none; }

  .insights-grid {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1rem;
  }
  @media (max-width: 760px) and (min-width: 761px) { .insights-grid { grid-template-columns: repeat(2, 1fr); } }
  @media (max-width: 460px) and (min-width: 761px) { .insights-grid { grid-template-columns: 1fr; } }

  .insights-entry-card {
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--border);
    overflow: hidden;
    text-decoration: none;
    color: var(--text);
    cursor: pointer;
    transition: border-color 0.18s ease, transform 0.18s ease;
  }
  .insights-entry-card:hover { border-color: var(--gold); transform: translateY(-2px); }
  .insights-entry-thumb {
    position: relative;
    aspect-ratio: 16 / 9;
    background: var(--frame-bg);
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
  }
  .insights-entry-body { padding: 0.9rem 1rem; display: flex; flex-direction: column; gap: 0.4rem; }
  .insights-entry-cat {
    font-family: 'Roboto', sans-serif;
    font-size: 0.65rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--current);
  }
  .insights-entry-title {
    font-family: 'Roboto', sans-serif;
    font-size: 0.9rem;
    color: var(--gold);
    letter-spacing: 0.02em;
    line-height: 1.35;
  }
  .insights-entry-date {
    font-family: 'Roboto', sans-serif;
    font-size: 0.68rem;
    color: var(--text-muted);
    letter-spacing: 0.06em;
  }
  .insights-entry-subtitle {
    font-family: 'Roboto', sans-serif;
    font-size: 0.8rem;
    color: var(--text-muted);
    line-height: 1.45;
  }
  .insights-entry-admin-controls { margin-top: 0.3rem; }

  /* Individual article page */
  .article-page { justify-content: flex-start; gap: 0; padding: 0; align-items: stretch; }
  .article-hero {
    width: 100%;
    max-height: 46vh;
    overflow: hidden;
    background: var(--frame-bg);
  }
  .article-hero img { width: 100%; height: 100%; object-fit: cover; display: block; max-height: 46vh; }
  .article-body-wrap {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    padding: 2.5rem 1.25rem 4rem;
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
  }
  .article-draft-banner {
    width: 100%;
    font-family: 'Roboto', sans-serif;
    font-size: 0.72rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--danger);
    border: 1px solid var(--danger);
    background: var(--danger-dim);
    padding: 0.5rem 0.85rem;
  }
  .article-masthead {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 1.5rem;
    flex-wrap: wrap;
  }
  .article-masthead-left {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
  }
  .article-masthead-right {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.5rem;
  }
  .article-masthead-rule {
    width: 100%;
    border: 0;
    border-top: 1px solid var(--border);
    margin: 0;
  }
  .article-title {
    font-family: 'Roboto', sans-serif;
    font-size: 1rem;
    font-weight: 400; /* h1 defaults to bold; the header's title span never was */
    color: var(--current);
    letter-spacing: 0.05em; /* matches .subtitle's letter-spacing, which the
      header's title span inherits since it doesn't set its own */
  }
  .article-title-date {
    font-family: 'Roboto', sans-serif;
    font-size: 0.8rem;
    color: var(--text-muted);
    letter-spacing: 0.04em;
    white-space: nowrap;
  }
  .article-meta {
    font-family: 'Roboto', sans-serif;
    font-size: 0.85rem;
    color: var(--text-muted);
    letter-spacing: 0.04em;
  }
  .article-meta-row {
    display: flex;
    align-items: baseline;
    justify-content: flex-start;
    flex-wrap: wrap;
    gap: 0.5rem;
  }
  .article-meta .article-subtitle { color: var(--current); }
  .article-meta .article-date { color: var(--text-muted); }
  .article-meta .article-meta-dot { color: var(--text-muted); }
  .article-admin-controls {
    display: flex;
    justify-content: flex-start;
    gap: 0.5rem;
  }
  .article-admin-controls[hidden] { display: none; }
  .article-content {
    font-family: 'Roboto', sans-serif;
    font-size: 0.8rem;
    line-height: 1.75;
    color: var(--text);
  }
  /* Pasted content arrives carrying inline style attributes from wherever it
     came from - a computed font-size in px, a font-family, a weight. Inline
     styles beat any stylesheet rule, so those fragments render at their
     original size while text typed directly follows .article-content, and an
     entry ends up with two or three sizes in it.

     This forces typography back to the article's own. !important is needed
     because nothing else outranks an inline style. Scoped to descendants of
     .article-content, so the editor chrome and the figure/caption components
     that set their own sizes are untouched.

     Colour is deliberately NOT included: link colour and the chapter-link
     styling are legitimate inline cases, and text colour rarely arrives
     wrong. If pasted colour becomes a problem, add `color: inherit` here.

     Figures, captions, compare labels and chapter headings are styled by
     class rather than inline, so they never match these selectors and keep
     their own typography untouched. */
  .article-content [style*="font-size"],
  .article-content [style*="font-family"],
  .article-content [style*="font-weight"] {
    font-size: inherit !important;
    font-family: inherit !important;
    font-weight: inherit !important;
  }
  .article-content p { margin: 0 0 1rem; }
  .article-content h2, .article-content h3 {
    color: var(--gold);
    font-family: 'Roboto', sans-serif;
    font-size: inherit;
    font-weight: 700;
    letter-spacing: 0.02em;
    margin: 1.5rem 0 0.75rem;
  }
  .article-content img { max-width: 100%; height: auto; display: block; margin: 0.5rem auto; }
  .article-content .article-chapter-link {
    color: inherit;
    text-decoration: none;
  }
  .article-content .article-chapter-link:hover {
    text-decoration: underline;
  }
  .article-chapter-top {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-left: 0.6rem;
    background: none;
    border: 0;
    padding: 0;
    color: var(--text-muted);
    cursor: pointer;
    vertical-align: middle;
    transition: color 0.18s ease;
  }
  .article-chapter-top:hover { color: var(--gold); }
  .article-chapter-top svg { width: 0.85em; height: 0.85em; display: block; }
  .article-content .article-img-frame {
    display: block;
    margin: 1rem auto;
    max-width: 100%;
  }
  .article-content .article-img-frame img {
    max-width: 100%;
    height: auto;
    display: block;
    margin: 0;
  }
  .article-content .article-img-frame .img-protect { cursor: pointer; }
  .article-content a { color: var(--current); }

  /* Figure-wrapped images (Insert/Edit Image in the article editor):
     image = full content width; thumb = fixed small size matching the
     still-gallery lightbox thumb convention, click to embiggen. */
  .article-figure { margin: 1.5rem 0; }
  .article-figure--image { display: block; }
  .article-figure--image .article-figure-media {
    display: block;
    text-align: center;
  }
  .article-figure--image .article-figure-media img,
  .article-figure--image .article-figure-media .article-img-frame {
    max-width: 100%;
    height: auto;
  }

  .article-figure--thumb { display: block; width: 220px; margin: 1.5rem auto; }
  .article-figure--thumb .article-figure-media,
  .article-figure--thumb .article-figure-media .article-img-frame { width: 220px; height: 220px; }
  .article-figure--thumb .article-figure-media img {
    width: 220px;
    height: 220px;
    object-fit: cover;
    display: block;
    margin: 0;
  }

  .article-figure--video { display: block; margin: 1.5rem 0; }
  .article-figure--video .article-figure-media { display: block; }
  .article-figure--video .article-embed { margin: 0; }

  /* Image compare (before/after slider) */
  .article-figure--compare { display: block; margin: 1.5rem 0; }
  .article-figure--compare .article-figure-media { display: block; }

  /* Gallery-style thumbnails (Insert Thumb): simple inline-block elements,
     no wrapping container needed — each is sized to exactly 1/8 of its
     containing line, so the browser's own text flow wraps to a new row
     after 8 automatically, matching the still-gallery's 8-per-row sizing
     without any JS-managed grid bookkeeping. */
  .article-thumb-item {
    display: inline-block;
    vertical-align: top;
    width: 12.5%;
    aspect-ratio: 1 / 1;
    position: relative;
    overflow: hidden;
    cursor: pointer;
    background: var(--frame-bg);
    font-size: 1rem;
  }
  .article-thumb-item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    -webkit-user-drag: none;
    user-select: none;
  }
  .article-thumb-item::before {
    content: '';
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0);
    transition: background 0.3s ease;
    pointer-events: none;
  }
  .article-thumb-item:hover::before { background: rgba(0, 0, 0, 0.45); }

  /* Caption shown only in the embiggened lightbox for a Thumb item — never
     on the thumbnail itself. Reuses the shared #articleLbOverlay. */
  .lb-thumb-caption {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    transform: translateY(100%);
    padding-top: 0.5rem;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.9rem;
    color: var(--gold);
  }
  .lb-thumb-caption[hidden] { display: none; }
  .lb-thumb-caption--left   { align-items: flex-start; text-align: left; }
  .lb-thumb-caption--center { align-items: center; text-align: center; }
  .lb-thumb-caption--right  { align-items: flex-end; text-align: right; }
  .article-compare {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    background: var(--frame-bg);
    cursor: ew-resize;
    touch-action: none;
    -webkit-user-select: none;
    user-select: none; /* prevent accidental text-selection highlighting on
                          the labels (or anything else) while dragging the
                          handle across them */
  }
  .article-compare .article-compare-img {
    position: absolute;
    top: -2px;
    left: -2px;
    width: calc(100% + 4px);
    height: calc(100% + 4px); /* explicit size is required here — <img> is a
                                 replaced element, and under position:absolute
                                 an unset width/height falls back to the
                                 image's intrinsic (full-resolution) size
                                 rather than solving to fit the inset edges */
    margin: 0;      /* must override .article-content img's margin:0.5rem auto —
                       that's the actual source of the top/bottom gap: it wins
                       on specificity against a plain .article-compare-img
                       selector, and margin on an absolutely-positioned element
                       shows up as a visible gap around it */
    max-width: none;
    object-fit: cover;
    display: block;
    pointer-events: none;
    -webkit-user-select: none;
    user-select: none;
    -webkit-user-drag: none;
  }
  .article-compare-layer-2 {
    position: absolute;
    inset: 0;
    /* No z-index here — it must stay in normal DOM/paint order (after image 1
       + label 1) so that wherever this layer's clip-path reveals it, it
       naturally paints OVER image 1 and label 1 beneath it, and wherever the
       clip hides it, image 1 and label 1 show through untouched. */
  }
  .article-compare-label {
    position: absolute;
    top: 8px;
    color: var(--text);
    font-family: 'Roboto', sans-serif;
    font-size: 0.90rem;
    letter-spacing: 0.04em;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
    pointer-events: none;
    white-space: pre-line; /* respects literal newlines typed into the label
                              textarea, without needing to convert them to
                              <br> tags when building the HTML */
    /* No z-index — see .article-compare-layer-2 note above: labels must stay
       in plain paint order so the slider correctly swipes over them. */
  }
  .article-compare-label-1 { left: 8px; text-align: left; }
  .article-compare-label-2 { right: 8px; text-align: right; }
  .article-compare-handle {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 2px;
    background: var(--gold);
    transform: translateX(-50%);
    pointer-events: none;
    z-index: 4;
  }
  .article-compare-handle-grip {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 2.4rem;
    height: 2.4rem;
    border-radius: 50%;
    background: var(--gold);
    color: var(--bg);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.1rem;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.55);
  }
  .article-compare .frame-logo {
    width: var(--symbol-watermark-gallery-pct, 12%);
    max-width: var(--symbol-watermark-gallery-max, 640px);
    z-index: 5;
  }

  .article-figure-media {
    position: relative;
    display: block;
  }
  .article-figure-media::before {
    content: '';
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0);
    transition: background 0.3s ease;
    pointer-events: none;
    z-index: 2;
  }
  .article-figure-media[data-desc1]:hover::before { background: rgba(0, 0, 0, 0.45); }
  .article-figure-media[data-desc1]::after {
    content: attr(data-desc1);
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 1rem;
    color: var(--gold);
    font-family: 'Roboto', sans-serif;
    font-size: 0.82rem;
    letter-spacing: 0.02em;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
    opacity: 0;
    transition: opacity 0.3s ease;
    pointer-events: none;
    z-index: 3;
  }
  .article-figure-media[data-desc1]:hover::after { opacity: 1; }

  .article-figcaption {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    text-align: right;
    gap: 0.15rem;
    margin-top: 0.5rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.9rem;
    color: var(--gold);
    letter-spacing: 0.02em;
  }
  .article-figcaption--left   { align-items: flex-start; text-align: left; }
  .article-figcaption--center { align-items: center; text-align: center; }
  .article-figcaption--right  { align-items: flex-end; text-align: right; }
  .figcap-line { line-height: 1.4; }
  .article-content ul, .article-content ol { padding-left: 1.4rem; margin: 0 0 1rem; }
  .article-content hr { border: 0; border-top: 1px solid var(--border); margin: -0.5rem 0 0.5rem; }
  .article-embed {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    background: #000;
    margin: 1rem 0;
  }
  .article-embed iframe, .article-embed video { width: 100%; height: 100%; border: 0; display: block; }
  .article-message {
    width: 100%;
    text-align: center;
    padding: 3rem 1rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.82rem;
    color: var(--text-muted);
    letter-spacing: 0.06em;
  }

  /* Chapters dropdown: sits inline after Subtitle • Date. */
  .article-chapters-dropdown {
    position: relative;
  }
  .article-chapters-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    background: none;
    border: 1px solid var(--border);
    color: var(--text-muted);
    font-family: 'Roboto', sans-serif;
    font-size: 0.8rem;
    padding: 0.4rem 0.75rem;
    cursor: pointer;
    transition: color 0.18s ease, border-color 0.18s ease;
  }
  .article-chapters-toggle:hover,
  .article-chapters-toggle.active { color: var(--gold); border-color: var(--gold); }
  .article-chapters-chevron { transition: transform 0.2s ease; }
  .article-chapters-toggle.active .article-chapters-chevron { transform: rotate(180deg); }
  .article-chapters-list {
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    z-index: 20;
    min-width: 240px;
    max-width: calc(100vw - 2.5rem);
    max-height: 60vh;
    overflow-y: auto;
    background: var(--surface);
    border: 1px solid var(--border);
    box-shadow: 0 14px 34px rgba(0, 0, 0, 0.5);
  }
  .article-chapters-list[hidden] { display: none; }
  .article-chapters-link {
    display: block;
    padding: 0.6rem 1rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.85rem;
    color: var(--text);
    text-decoration: none;
    border-top: 1px solid var(--border);
    transition: background 0.15s ease, color 0.15s ease;
  }
  .article-chapters-link:first-child { border-top: 0; }
  .article-chapters-link:hover { background: var(--gold-dim); color: var(--gold); }

  /* WYSIWYG editor */
  .wysiwyg-page { justify-content: flex-start; gap: 1.25rem; max-width: 860px; margin: 0 auto; width: 100%; }
  .wysiwyg-fields {
    width: 100%;
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
  }

  /* Collapsible "Article Details" block (Title through Share Image) so the
     admin can minimize it and focus on the body while writing. */
  .wysiwyg-meta {
    width: 100%;
    border: 1px solid var(--border);
    background: var(--surface);
  }
  .wysiwyg-meta > summary { list-style: none; cursor: pointer; }
  .wysiwyg-meta > summary::-webkit-details-marker { display: none; }
  .wysiwyg-meta-summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.85rem 1.1rem;
  }
  .wysiwyg-meta-label {
    font-family: 'Roboto', sans-serif;
    font-size: 0.78rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-muted);
  }
  .wysiwyg-meta-toggle { color: var(--gold); font-size: 0.85rem; font-family: 'Roboto', sans-serif; }
  .wysiwyg-meta-summary:hover .wysiwyg-meta-toggle { color: var(--text); }
  .wysiwyg-meta-toggle::after { content: 'Expand'; }
  .wysiwyg-meta[open] .wysiwyg-meta-toggle::after { content: 'Minimize'; }
  .wysiwyg-meta-body {
    padding: 0 1.1rem 1.1rem;
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
  }
  .wysiwyg-row { display: flex; gap: 0.75rem; flex-wrap: wrap; }
  .wysiwyg-row .gm-field { flex: 1 1 200px; min-width: 0; }

  /* Toolbar + error + Delete/Cancel/Save stay pinned to the top of the
     scroll area while the article body scrolls underneath them. */
  .wysiwyg-sticky-bar {
    position: sticky;
    top: 0;
    z-index: 15;
    width: 100%;
    background: var(--surface);
    border: 1px solid var(--border);
    box-shadow: 0 8px 16px rgba(0, 0, 0, 0.35);
  }
  .wysiwyg-sticky-bar .wysiwyg-toolbar {
    border: 0;
  }
  .wysiwyg-sticky-bar .gm-error {
    padding: 0 1.1rem;
  }
  .wysiwyg-sticky-bar .wysiwyg-actions {
    padding: 0.75rem 1.1rem;
    margin: 0;
    border-top: 1px solid var(--border);
  }
  .wysiwyg-sticky-bar + .wysiwyg-editor {
    border-top: 0;
  }
  .wysiwyg-toolbar {
    width: 100%;
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    padding: 0.6rem;
    border: 1px solid var(--border);
    border-bottom: 0;
    background: var(--surface);
  }
  .wysiwyg-btn {
    background: var(--btn-wysiwyg-bg, var(--bg));
    border: 1px solid var(--btn-wysiwyg-border, var(--border));
    color: var(--btn-wysiwyg-fg, var(--text));
    font-family: 'Roboto', sans-serif;
    font-size: var(--btn-wysiwyg-size, 0.78rem);
    letter-spacing: var(--btn-wysiwyg-tracking, 0.03em);
    padding: var(--btn-wysiwyg-pad-y, 0.45rem) var(--btn-wysiwyg-pad-x, 0.7rem);
    border-radius: var(--btn-wysiwyg-radius, 0);
    cursor: pointer;
    transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
  }
  .wysiwyg-btn:hover { border-color: var(--gold); color: var(--gold); }
  .wysiwyg-btn.active { border-color: var(--gold); color: var(--gold); background: var(--gold-dim); }
  .wysiwyg-btn svg { display: block; }
  .wysiwyg-divider { width: 1px; align-self: stretch; background: var(--border); margin: 0 0.15rem; }
  .wysiwyg-editor {
    width: 100%;
    min-height: 420px;
    border: 1px solid var(--border);
    background: var(--bg);
    color: var(--text);
    padding: 1.25rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.95rem;
    line-height: 1.7;
    outline: none;
  }
  .wysiwyg-editor:focus { border-color: var(--gold); }
  .wysiwyg-editor p { margin: 0 0 1rem; }
  .wysiwyg-editor h2, .wysiwyg-editor h3 { color: var(--gold); font-family: 'Roboto', sans-serif; font-size: inherit; font-weight: 700; margin: 1rem 0 0.6rem; }
  .wysiwyg-editor hr { border: 0; border-top: 1px solid var(--border); margin: -0.5rem 0 0.5rem; }
  .wysiwyg-editor img { max-width: 100%; height: auto; cursor: pointer; }
  .wysiwyg-editor ul, .wysiwyg-editor ol { padding-left: 1.4rem; margin: 0 0 1rem; }
  .wysiwyg-editor .article-embed { border: 1px dashed var(--border); cursor: pointer; }
  .wysiwyg-actions {
    width: 100%;
    display: flex;
    justify-content: space-between;
    gap: 0.75rem;
    flex-wrap: wrap;
  }
  .wysiwyg-actions-left, .wysiwyg-actions-right { display: flex; gap: 0.5rem; flex-wrap: wrap; }
  .wysiwyg-denied {
    font-family: 'Roboto', sans-serif;
    font-size: 0.82rem;
    color: var(--text-muted);
    padding: 2rem 0;
    text-align: center;
  }

  /* ===== catalog ===== */
  .catalog-page { justify-content: flex-start; gap: 1.25rem; }
  .catalog-draft-banner {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    font-family: 'Roboto', sans-serif;
    font-size: 0.72rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--danger);
    border: 1px solid var(--danger);
    background: var(--danger-dim);
    padding: 0.5rem 0.85rem;
  }

  /* Rental date bar */
  .catalog-datebar {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    display: flex;
    align-items: flex-end;
    gap: 1rem;
    flex-wrap: wrap;
    border: 1px solid var(--border);
    background: var(--surface);
    padding: 1rem 1.25rem;
  }
  .catalog-date-field { display: flex; flex-direction: column; gap: 0.3rem; }
  .catalog-date-field label {
    font-family: 'Roboto', sans-serif; font-size: 0.7rem; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--text-muted);
  }
  .catalog-date-field input[type="date"] {
    background: var(--bg); border: 1px solid var(--border); color: var(--text);
    font-family: 'Roboto', sans-serif; font-size: 0.8rem; padding: 0.5rem 0.6rem;
  }
  .catalog-days-readout {
    font-family: 'Roboto', sans-serif; font-size: 0.8rem; color: var(--gold);
    letter-spacing: 0.04em; white-space: nowrap;
  }
  .catalog-search { flex: 1 1 200px; min-width: 160px; }
  .catalog-search input { width: 100%; max-width: none; }

  /* Layout: sidebar + grid */
  .catalog-layout {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    display: flex;
    align-items: flex-start;
    gap: 1.5rem;
  }
  .catalog-sidebar {
    flex: 0 0 190px;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    position: sticky;
    top: 0;
  }
  .catalog-cat-link {
    display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
    padding: 0.45rem 0.6rem;
    font-family: 'Roboto', sans-serif; font-size: 0.76rem; letter-spacing: 0.03em;
    color: var(--text-muted); text-decoration: none; cursor: pointer; background: none; border: 0;
    text-align: left; width: 100%;
    transition: background 0.15s ease, color 0.15s ease;
  }
  .catalog-cat-link:hover { color: var(--text); background: var(--gold-dim); }
  .catalog-cat-link.active { color: var(--gold); background: var(--gold-dim); }
  .catalog-cat-count { color: var(--text-muted); font-size: 0.7rem; }
  .catalog-cat-link.active .catalog-cat-count { color: var(--gold); }

  .catalog-main { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 1.75rem; }
  .catalog-section-heading {
    display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
    font-family: 'Roboto', sans-serif; font-size: 0.85rem; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--gold);
    padding-bottom: 0.5rem; border-bottom: 1px solid var(--border);
  }
  .catalog-section-count { color: var(--text-muted); text-transform: none; letter-spacing: 0.02em; font-size: 0.75rem; }

  .catalog-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 1rem;
  }
  @media (max-width: 900px) { .catalog-grid { grid-template-columns: repeat(3, 1fr); } }
  @media (max-width: 640px) and (min-width: 761px) { .catalog-grid { grid-template-columns: repeat(2, 1fr); } }
  @media (max-width: 440px) and (min-width: 761px) { .catalog-grid { grid-template-columns: 1fr; } }

  .catalog-card {
    position: relative;
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--border);
    overflow: hidden;
    transition: border-color 0.18s ease;
  }
  .catalog-card:hover { border-color: var(--gold); }
  .catalog-card-thumb {
    position: relative;
    aspect-ratio: 4 / 3;
    background: var(--frame-bg);
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .catalog-card-thumb-placeholder {
    font-family: 'Roboto', sans-serif; font-size: 0.7rem; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--text-muted);
  }
  .catalog-card-unavail {
    position: absolute; inset: 0; background: rgba(0,0,0,0.55);
    display: flex; align-items: center; justify-content: center;
    font-family: 'Roboto', sans-serif; font-size: 0.7rem; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--danger-soft);
  }
  .catalog-card-body { padding: 0.75rem 0.9rem; display: flex; flex-direction: column; gap: 0.3rem; flex: 1 1 auto; }
  .catalog-card-name { font-family: 'Roboto', sans-serif; font-size: 0.88rem; color: var(--text); line-height: 1.3; }
  .catalog-card-sub {
    font-family: 'Roboto', sans-serif; font-size: 0.7rem; letter-spacing: 0.04em;
    color: var(--text-muted); text-transform: uppercase;
  }
  .catalog-card-meta { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-top: 0.2rem; }
  .catalog-card-rate { font-family: 'Roboto', sans-serif; font-size: 0.82rem; color: var(--gold); }
  .catalog-card-qty { font-family: 'Roboto', sans-serif; font-size: 0.68rem; color: var(--text-muted); }
  .catalog-card-actions { display: flex; align-items: center; gap: 0.4rem; margin-top: 0.5rem; }
  .catalog-add-btn {
    flex: 1 1 auto;
    background: var(--btn-affirm-bg, transparent);
    border: 1px solid var(--btn-affirm-border, var(--gold));
    color: var(--btn-affirm-fg, var(--gold));
    font-family: 'Roboto', sans-serif;
    font-size: var(--btn-affirm-size, 0.75rem);
    letter-spacing: var(--btn-affirm-tracking, 0.05em);
    padding: var(--btn-affirm-pad-y, 0.45rem) var(--btn-affirm-pad-x, 0.6rem);
    border-radius: var(--btn-affirm-radius, 0);
    cursor: pointer; transition: all 0.18s ease;
  }
  .catalog-add-btn:hover { background: var(--btn-affirm-hover-bg, var(--gold-dim)); }
  .catalog-add-btn:disabled { opacity: 0.35; cursor: not-allowed; }
  .catalog-qty-stepper { display: flex; align-items: center; gap: 0.3rem; flex: 1 1 auto; }
  .catalog-qty-stepper button {
    background: var(--bg); border: 1px solid var(--border); color: var(--text);
    width: 1.7rem; height: 1.7rem; cursor: pointer; font-family: 'Roboto', sans-serif;
  }
  .catalog-qty-stepper button:hover { border-color: var(--gold); color: var(--gold); }
  .catalog-qty-stepper span { font-family: 'Roboto', sans-serif; font-size: 0.8rem; min-width: 1.4rem; text-align: center; }

  .catalog-card-admin-controls { padding: 0 0.9rem 0.75rem; display: flex; gap: 0.5rem; }

  .catalog-add-new-btn { flex: 0 0 auto; }

  .catalog-empty {
    font-family: 'Roboto', sans-serif; font-size: 0.82rem; color: var(--text-muted);
    text-align: center; padding: 2.5rem 1rem;
  }

  @media (max-width: 760px) and (min-width: 761px) {
    .catalog-layout { flex-direction: column; }
    .catalog-sidebar {
      flex: none; width: 100%; position: static;
      flex-direction: row; flex-wrap: wrap; gap: 0.4rem;
    }
    .catalog-cat-link { width: auto; }
  }

  /* Quote panel (sticky bottom drawer) */
  .catalog-quote-panel {
    position: sticky;
    bottom: 0;
    z-index: 10;
    width: 100%;
    background: var(--surface);
    border: 1px solid var(--gold);
    box-shadow: 0 -8px 24px rgba(0,0,0,0.4);
    margin-top: 1rem;
  }
  .catalog-quote-toggle-row {
    display: flex; align-items: center; justify-content: space-between; gap: 1rem;
    padding: 0.85rem 1.1rem;
    cursor: pointer;
    background: none; border: 0; width: 100%; text-align: left;
  }
  .catalog-quote-toggle-left { display: flex; align-items: center; gap: 0.75rem; }
  .catalog-quote-label {
    font-family: 'Roboto', sans-serif; font-size: 0.8rem; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--gold);
  }
  .catalog-quote-count {
    font-family: 'Roboto', sans-serif; font-size: 0.72rem; color: var(--text-muted);
  }
  .catalog-quote-total-preview {
    font-family: 'Roboto', sans-serif; font-size: 0.9rem; color: var(--gold); font-weight: 700;
  }
  .catalog-quote-chevron { color: var(--text-muted); transition: transform 0.2s ease; }
  .catalog-quote-panel.open .catalog-quote-chevron { transform: rotate(180deg); }

  .catalog-quote-body {
    border-top: 1px solid var(--border);
    padding: 1.1rem;
    display: flex;
    flex-direction: column;
    gap: 1rem;
    max-height: 55vh;
    overflow-y: auto;
  }
  .catalog-quote-body[hidden] { display: none; }

  .catalog-quote-fields { display: flex; gap: 0.75rem; flex-wrap: wrap; }
  .catalog-quote-fields .gm-field { flex: 1 1 200px; min-width: 0; }

  .catalog-quote-lines { display: flex; flex-direction: column; gap: 0.5rem; }
  .catalog-quote-line {
    display: grid;
    grid-template-columns: 1fr auto auto auto auto;
    align-items: center;
    gap: 0.6rem;
    padding: 0.55rem 0;
    border-bottom: 1px solid var(--border);
  }
  .catalog-quote-line:last-child { border-bottom: 0; }
  .catalog-quote-line-name {
    font-family: 'Roboto', sans-serif; font-size: 0.82rem; color: var(--text);
    display: flex; flex-direction: column; gap: 0.1rem; min-width: 0;
  }
  .catalog-quote-line-type {
    font-family: 'Roboto', sans-serif; font-size: 0.65rem; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--text-muted);
  }
  .catalog-quote-line-qty input {
    width: 3.4rem; max-width: none; text-align: center;
  }
  .catalog-quote-line-rate {
    font-family: 'Roboto', sans-serif; font-size: 0.78rem; color: var(--text-muted); white-space: nowrap;
  }
  .catalog-quote-line-discount { display: flex; align-items: center; gap: 0.3rem; }
  .catalog-quote-line-discount input { width: 3.6rem; max-width: none; text-align: center; }
  .catalog-quote-line-discount select { padding: 0.4rem; }
  .catalog-quote-line-total {
    font-family: 'Roboto', sans-serif; font-size: 0.85rem; color: var(--gold); white-space: nowrap; text-align: right;
    min-width: 4.5rem;
  }
  .catalog-quote-line-remove {
    background: none; border: 0; color: var(--danger); cursor: pointer; font-size: 1rem; line-height: 1; padding: 0 0.3rem;
  }
  .catalog-quote-line-remove:hover { opacity: 0.7; }

  .catalog-quote-summary {
    display: flex; flex-direction: column; gap: 0.35rem;
    padding-top: 0.75rem; border-top: 1px solid var(--border);
    align-items: flex-end;
  }
  .catalog-quote-summary-row {
    display: flex; gap: 1rem; font-family: 'Roboto', sans-serif; font-size: 0.8rem; color: var(--text-muted);
  }
  .catalog-quote-summary-row.total { font-size: 1.05rem; color: var(--gold); font-weight: 700; }
  .catalog-quote-summary-row > span:last-child { min-width: 5rem; text-align: right; }

  .catalog-quote-actions { display: flex; justify-content: flex-end; gap: 0.5rem; flex-wrap: wrap; }
  .catalog-quote-status { font-family: 'Roboto', sans-serif; font-size: 0.72rem; color: var(--current); text-align: right; }
  .catalog-quote-empty { font-family: 'Roboto', sans-serif; font-size: 0.8rem; color: var(--text-muted); text-align: center; padding: 1rem 0; }

  /* Admin: saved quotes browser modal */
  .catalog-saved-list { display: flex; flex-direction: column; gap: 0.4rem; max-height: 50vh; overflow-y: auto; }
  .catalog-saved-row {
    display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
    padding: 0.55rem 0.7rem; border: 1px solid var(--border); background: var(--bg);
  }
  .catalog-saved-row-main { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
  .catalog-saved-row-label { font-family: 'Roboto', sans-serif; font-size: 0.82rem; color: var(--text); }
  .catalog-saved-row-meta { font-family: 'Roboto', sans-serif; font-size: 0.7rem; color: var(--text-muted); }
  .catalog-saved-row-actions { display: flex; gap: 0.4rem; flex: 0 0 auto; }

  /* Printable quote (catalog/quote.html) */
  .catalog-quote-doc {
    width: 100%;
    max-width: 780px;
    margin: 0 auto;
    background: var(--surface);
    border: 1px solid var(--border);
    padding: 2rem 2.25rem;
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
  }
  .catalog-quote-doc-header {
    display: flex; justify-content: space-between; align-items: flex-start; gap: 1.5rem; flex-wrap: wrap;
    border-bottom: 1px solid var(--border); padding-bottom: 1rem;
  }
  .catalog-quote-doc-brand { font-family: 'Roboto', sans-serif; font-size: 1.1rem; letter-spacing: 0.15em; color: var(--gold); text-transform: uppercase; }
  .catalog-quote-doc-meta { font-family: 'Roboto', sans-serif; font-size: 0.78rem; color: var(--text-muted); text-align: right; display: flex; flex-direction: column; gap: 0.2rem; }
  .catalog-quote-doc-client { font-family: 'Roboto', sans-serif; font-size: 0.95rem; color: var(--text); }
  .catalog-quote-doc-dates { font-family: 'Roboto', sans-serif; font-size: 0.82rem; color: var(--text-muted); }
  .catalog-quote-doc-table { width: 100%; border-collapse: collapse; font-family: 'Roboto', sans-serif; font-size: 0.8rem; }
  .catalog-quote-doc-table th {
    text-align: left; font-size: 0.68rem; letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--text-muted); font-weight: 400; padding: 0.5rem 0.6rem; border-bottom: 1px solid var(--border);
  }
  .catalog-quote-doc-table td { padding: 0.5rem 0.6rem; border-bottom: 1px solid var(--border); color: var(--text); }
  .catalog-quote-doc-table th:last-child, .catalog-quote-doc-table td:last-child { text-align: right; }
  .catalog-quote-doc-totals { display: flex; flex-direction: column; align-items: flex-end; gap: 0.3rem; }
  .catalog-quote-doc-totals-row { display: flex; gap: 1.5rem; font-family: 'Roboto', sans-serif; font-size: 0.85rem; color: var(--text-muted); }
  .catalog-quote-doc-totals-row.grand { font-size: 1.15rem; color: var(--gold); font-weight: 700; }
  .catalog-quote-doc-totals-row > span:last-child { min-width: 6rem; text-align: right; }
  .catalog-quote-doc-actions { display: flex; justify-content: flex-end; gap: 0.5rem; }
  .catalog-quote-doc-notfound { font-family: 'Roboto', sans-serif; font-size: 0.85rem; color: var(--text-muted); text-align: center; padding: 3rem 1rem; }

  @media print {
    header, footer, .catalog-quote-doc-actions, .phfx-scrollbar-track { display: none !important; }
    body, main { height: auto !important; overflow: visible !important; }
    .catalog-quote-doc { border: 0; padding: 0; max-width: 100%; }
  }

  /* ===== Share Preview modal (footer admin feature) ===== */
  .spv-card { border: 1px solid var(--border); background: var(--bg); max-width: 420px; overflow: hidden; }
  .spv-card img { display: block; width: 100%; aspect-ratio: 1.91 / 1; object-fit: cover; background: var(--frame-bg); }
  .spv-card-body { padding: 0.85rem 1rem; }
  /* Domain line of the preview card, with the favicon beside it the way a
     real link preview and a browser tab both present it. */
  .spv-domain-row { display: flex; align-items: center; gap: 0.4rem; margin-bottom: 0.3rem; }
  .spv-domain { font-family: 'Roboto', sans-serif; font-size: 0.68rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted); }
  /* Real colours, not a themed mask: this is a QA view of the actual icon.
     Selector carries .spv-card so it outranks `.spv-card img` above, which
     would otherwise stretch the favicon to the card's full width at 1.91:1. */
  .spv-card img.spv-favicon {
    display: block;
    width: 16px;
    height: 16px;
    aspect-ratio: auto;
    flex: 0 0 auto;
    object-fit: contain;
    background: none;
  }
  .spv-card img.spv-favicon[hidden] { display: none; }
  .spv-noicon { font-family: 'Roboto Mono', monospace; font-size: 0.6rem; color: var(--danger, #eb1313); flex: 0 0 auto; }
  .spv-noicon[hidden] { display: none; }
  .spv-title { font-family: 'Roboto', sans-serif; font-size: 0.92rem; font-weight: 600; color: var(--text); line-height: 1.35; margin-bottom: 0.3rem; }
  .spv-desc { font-family: 'Roboto', sans-serif; font-size: 0.8rem; color: var(--text-muted); line-height: 1.4; }
  .spv-noimg { display: flex; align-items: center; justify-content: center; aspect-ratio: 1.91 / 1; background: var(--frame-bg); color: var(--text-muted); font-family: 'Roboto', sans-serif; font-size: 0.7rem; letter-spacing: 0.05em; text-transform: uppercase; }
  .spv-heading { font-family: 'Roboto', sans-serif; font-size: 0.72rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-muted); margin: 1.1rem 0 0.6rem; }
  .spv-heading:first-child { margin-top: 0; }
  .spv-table-wrap { border: 1px solid var(--border); }
  .spv-table { width: 100%; border-collapse: collapse; font-family: 'Roboto', sans-serif; font-size: 0.72rem; }
  .spv-table td { padding: 0.45rem 0.6rem; border-bottom: 1px solid var(--border); vertical-align: top; }
  .spv-table tr:last-child td { border-bottom: none; }
  .spv-k { color: var(--text-muted); white-space: nowrap; width: 1%; }
  .spv-v { color: var(--text); word-break: break-all; }
  .spv-missing { color: var(--danger); font-style: italic; }

  /* ===== about ===== */
  .ab-page { justify-content: flex-start; gap: 1rem; }
  .ab-section {
    padding: 1.25rem;
    display: flex;
    flex-direction: column;
    gap: 1rem;
  }
  .ab-section-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
  }
  .ab-prose {
    font-family: 'Roboto', sans-serif;
    font-size: 0.8rem;
    line-height: 1.7;
    color: var(--text);
  }
  .ab-prose p { margin: 0 0 1rem; }
  .ab-prose p:last-child { margin-bottom: 0; }
  /* Bullet blocks in admin-entered prose (see renderProse in about.js). Tight
     leading between items so a long list of credits stays scannable, and a
     gold marker to tie it to the rest of the palette. */
  .ab-prose-list {
    margin: 0 0 1rem;
    padding-left: 1.1rem;
    list-style: disc;
  }
  .ab-prose-list:last-child { margin-bottom: 0; }
  .ab-prose-list li { margin: 0 0 0.4rem; padding-left: 0.15rem; }
  .ab-prose-list li:last-child { margin-bottom: 0; }
  .ab-prose-list li::marker { color: var(--gold); }

  /* Tabs inside the About Phil Holland deck. Same interaction and aria-current
     hook as .eqm-tab on the equipment page, but set in Roboto at mixed case
     rather than uppercase mono: four labels as long as "Notable
     Accomplishments" in uppercase mono would overrun the 60% column on their
     own. flex-wrap is the backstop — at the 761 lock they do fall to a second
     row, and wrapping reads better than clipping or a scrollbar. */
  .ab-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 0;
    border-bottom: 1px solid var(--border);
  }
  .ab-tab {
    background: none; border: none; border-bottom: 2px solid transparent; cursor: pointer;
    font-family: 'Roboto', sans-serif; font-size: 0.72rem; letter-spacing: 0.04em;
    color: var(--text-muted); padding: 0.5rem 0.75rem;
    transition: color 0.18s ease, border-color 0.18s ease;
  }
  .ab-tab:first-child { padding-left: 0; }
  .ab-tab:hover { color: var(--gold); }
  .ab-tab[aria-current="true"] { color: var(--gold); border-bottom-color: var(--gold); }
  .ab-tab:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }
  /* A tab switched off, shown only to an admin in edit mode so it can be
     selected and switched back on. Dimmed and tagged so it is never mistaken
     for something a visitor can see. */
  .ab-tab.is-tab-hidden { opacity: 0.55; }
  /* Tab editor, admin edit mode only. Sits under the tab bar rather than
     inline with it, so the tabs stay a clean row and the controls read as
     acting on whichever tab is currently selected. */
  .ab-tab-admin {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
    margin: 0.5rem 0 0.25rem;
  }
  .ab-tab-admin[hidden] { display: none; }

  /* A section switched off, shown only to an admin in edit mode so it can be
     switched back on. Same treatment as a hidden tab: dimmed and tagged, never
     mistakable for something a visitor can see. */
  .ab-section.is-section-hidden { opacity: 0.55; }
  .ab-section.is-section-hidden .res-label::after {
    content: 'hidden';
    margin-left: 0.5rem;
    font-size: 0.58rem; letter-spacing: 0.08em; text-transform: uppercase;
    color: #eb1313; vertical-align: middle;
  }
  /* The head becomes a row so the visibility button can sit beside its label
     rather than pushing the section's content down. */
  .ab-section-head {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
  }
  .ab-sec-vis-btn { margin-left: auto; }

  /* An external link with no logo renders as a button rather than a bare text
     badge, matching the Client List link - .export-btn is an inline-flex, so
     an anchor wearing it needs its centring and text-decoration back. */
  .ab-link-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    text-decoration: none;
    white-space: nowrap;
  }
  /* No URL yet: still visible to an admin, but never looks pressable. */
  .ab-link-btn.is-inert,
  .ab-link-badge.is-inert { opacity: 0.5; pointer-events: none; }

  /* Logo slot in the External Links editor. */
  .ab-ext-logo-cell {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex: 0 0 auto;
  }
  /* Preview drawn as a mask like the badge itself, so a file that would render
     blank on the page renders blank here too - the preview cannot flatter a
     logo the page will not show. */
  .ab-ext-logo-preview {
    display: inline-block;
    width: 2.2rem;
    height: 1.1rem;
    background: var(--text);
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    -webkit-mask-position: center;  mask-position: center;
    -webkit-mask-size: contain;     mask-size: contain;
    flex: 0 0 auto;
  }
  .ab-ext-logo-status {
    font-family: 'Roboto Mono', monospace;
    font-size: 0.62rem;
    color: var(--text-muted);
    white-space: nowrap;
  }
  .ab-tab.is-tab-hidden::after {
    content: 'hidden';
    margin-left: 0.4rem;
    font-size: 0.58rem; letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--text-muted); border: 1px solid var(--border);
    padding: 0 0.25rem;
  }
  /* Checkbox row in the tab edit modal. */
  .ab-tab-vis {
    display: flex; align-items: center; gap: 0.5rem; cursor: pointer;
    font-family: 'Roboto', sans-serif; font-size: 0.85rem; color: var(--text);
  }
  .ab-tab-vis input { cursor: pointer; }

  /* Three 4:3 photos stacked vertically in their own deck, beside the About
     Phil Holland tabs (see .ab-split-bio).

     --ab-bio-h is a ceiling on each frame's height, not a target: a frame is
     width:100% of its slot and 4/3, so it only reaches the cap once the
     column's inner width passes 320px. Below that the column is what limits
     it. At 22.5% the inner width is ~174px, giving ~130px-tall frames.

     The cap sits on .ab-bio-slot rather than the frame so the photo and its
     admin button share one width and stay aligned. */
  .ab-bio-photo-wrap {
    --ab-bio-h: 240px;
    width: 100%;
    display: flex; flex-direction: column; gap: 1rem; align-items: center;
  }
  .ab-bio-slot {
    width: 100%;
    max-width: calc(var(--ab-bio-h) * 4 / 3);
    display: flex; flex-direction: column; gap: 0.6rem;
  }
  .ab-bio-slot[hidden] { display: none; }
  .ab-bio-photo-frame {
    position: relative;
    width: 100%;
    aspect-ratio: 4 / 3;
    border: 1px solid var(--border);
    background: var(--bg);
    overflow: hidden;
  }
  .ab-bio-photo-frame img {
    width: 100%; height: 100%; object-fit: cover; display: block;
    -webkit-user-drag: none; user-select: none;
  }
  .ab-bio-empty {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    text-align: center; padding: 1rem;
    font-family: 'Roboto', sans-serif; font-size: 0.75rem; color: var(--text-muted);
  }
  /* When the photo column stands down entirely — no photos and not editing —
     the row becomes one column rather than a half-width deck beside a gap. */
  .ab-split-bio:has(> .ab-section[hidden]) {
    grid-template-columns: minmax(0, 1fr);
  }
  /* Admin controls for one photo: Change, up, down, Remove. They wrap,
     because the column can be as narrow as ~174px at small viewports where
     four buttons on one line would run past the frame. Change takes the
     leftover width; the arrows and Remove stay at their content size. */
  .ab-bio-actions { display: flex; flex-wrap: wrap; gap: 0.4rem; }
  .ab-bio-change-btn { flex: 1 1 auto; width: auto; }
  .ab-bio-remove-btn,
  .ab-bio-move-btn { flex: 0 0 auto; }
  /* Sits under the last photo, matched to the frame width above it. */
  .ab-bio-add-btn { width: 100%; max-width: calc(var(--ab-bio-h) * 4 / 3); }

  /* Links and Organizations sit side by side, each taking half the measure.

     max-width here, not on the halves: .control-deck already carries
     max-width:960px, so without a cap on the wrapper each half could take the
     full 960 and the pair would run to 1920 - twice the page measure. The
     960 belongs to the ROW; the columns then split it.

     A grid of two equal columns rather than flex, so both halves are the same
     width regardless of how much each contains - one link against a dozen
     org badges would otherwise size the columns to their content.
     minmax(0,1fr) lets a column shrink below its content width; plain 1fr has
     an auto minimum and a long org name would push the split off balance.

     align-items:stretch keeps the two decks the same height even when one has
     fewer rows, so they read as a matched pair. */
  .ab-split {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1rem;
    align-items: stretch;
    width: 100%;
    max-width: 960px;
  }
  /* The halves must not re-apply the deck's own 960 cap. */
  .ab-split > .ab-section { min-width: 0; max-width: none; }
  /* Three-up variant. Organizations keeps half the row - it holds three logos
     that total ~319px and would wrap to two lines in an even third. The other
     two boxes hold a couple of buttons each and split the remaining half. */
  .ab-split-3 { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 2fr); }
  /* Four-up variant. Organizations still takes half the row — 3fr against three
     1fr columns is 3/6 — so the logo strip keeps the width it needs. The three
     button boxes now split the other half three ways instead of two, landing at
     ~152px each inside the 960 cap, or ~112px at the 761 lock. That is enough
     for one short button but not for a two-word one on a single line, which is
     why .ab-btn-tight exists below. */
  .ab-split-4 { grid-template-columns: repeat(3, minmax(0, 1fr)) minmax(0, 3fr); }
  /* Prose against bio photos. The photo column is given a straight percentage
     rather than an fr share, because fr divides what's left AFTER the 1rem gap
     is taken out — so 2fr of 10 landed at 19.7% of the measure, not 20%. A
     percentage resolves against the grid container itself, making this exactly
     22.5% of the 960 cap (216px), with the prose column taking the remainder. */
  .ab-split-bio { grid-template-columns: minmax(0, 1fr) 22.5%; }

  /* In a narrow column an .export-btn's default padding forces its label to
     wrap mid-phrase or overflow. Shrinking the horizontal padding and allowing
     a centred two-line label keeps the button inside its box without needing a
     shorter caption. Scoped to .ab-split-4 so the roomier layouts are
     untouched. */
  .ab-split-4 .ab-links-row a.export-btn.ab-btn-tight {
    padding-left: 0.6rem;
    padding-right: 0.6rem;
    text-align: center;
    line-height: 1.25;
    white-space: normal;
  }

  .ab-links-row { display: flex; gap: 0.75rem; flex-wrap: wrap; align-items: center; }
  .ab-links-row a.export-btn { text-decoration: none; }
  /* Brand mark for a link that has a logo, used as the <span> inside
     .ab-link-badge. The mask image is set inline per record from the
     uploaded file, so no default is declared here — a stylesheet default
     pointing at a fixed path is exactly what left an invisible-but-
     space-occupying badge behind when that file went missing.
     [hidden] needs restating: the element is display:block here, which would
     otherwise beat the attribute. */
  .ab-link-logo {
    flex: 0 0 auto;
    display: block;
    height: 2rem;
    width: calc(2rem * var(--ar, 1.98));
    background-color: var(--text-muted);
    -webkit-mask-repeat: no-repeat;  mask-repeat: no-repeat;
    -webkit-mask-position: center;   mask-position: center;
    -webkit-mask-size: contain;      mask-size: contain;
    transition: background-color 0.18s ease;
  }
  .ab-link-logo:hover { background-color: var(--gold); }
  .ab-link-logo[hidden] { display: none; }
  .ab-orgs {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
  }
  /* Organization logo. Height is fixed so a row of them shares one baseline;
     width comes from the per-logo aspect ratio set inline, which means the
     row is laid out correctly before any image decodes and never reflows.
     --scale is an optional per-logo multiplier for marks that read small at
     the shared height - a near-square badge against a wide wordmark.

     Masked onto a colour rather than shown as an <img>: the source art is a
     mix of black and grey on transparency, and a mask discards colour so they
     all come out matching each other and the theme. */
  .ab-org-logo {
    flex: 0 0 auto;
    height: calc(2rem * var(--scale, 1));
    width: calc(2rem * var(--scale, 1) * var(--ar, 3));
    background-color: var(--text-muted);
    -webkit-mask-repeat: no-repeat;  mask-repeat: no-repeat;
    -webkit-mask-position: center;   mask-position: center;
    -webkit-mask-size: contain;      mask-size: contain;
    transition: background-color 0.18s ease;
  }
  .ab-org-logo:hover { background-color: var(--gold); }
  /* An organization with a link renders as an <a> with these same classes, so
     the anchor defaults have to be neutralised — otherwise a linked badge
     picks up an underline the unlinked ones don't have. */
  a.ab-org-logo, a.ab-org-tag { text-decoration: none; }
  a.ab-org-tag:hover { border-color: var(--gold); color: var(--gold); }
  .ab-org-tag {
    font-family: 'Roboto', sans-serif; font-size: 0.75rem; letter-spacing: 0.04em;
    color: var(--text); border: 1px solid var(--border); background: var(--bg);
    padding: 0.35rem 0.7rem;
  }

  /* Organization rows in the Links & Organizations modal. Name and link share
     the row and wrap to their own lines when it gets tight, with the reorder
     and remove controls held together at the end. Scoped by id because
     .sc-item-row carries no styling of its own. */
  #abOrgsList .ab-org-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0;
    border-top: 1px solid var(--border);
  }
  #abOrgsList .ab-org-row:first-child { border-top: 0; padding-top: 0; }
  #abOrgsList .ab-org-name { flex: 1 1 40%; min-width: 0; max-width: none; }
  #abOrgsList .ab-org-url  { flex: 1 1 40%; min-width: 0; max-width: none; }
  #abOrgsList .ab-org-row-actions { display: flex; gap: 0.35rem; flex: 0 0 auto; }

  /* ===================== lutBuilder tool ===================== */
  .lutb-page { justify-content: flex-start; gap: 1.5rem; }
  .lutb-page input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
  .lutb-page input[type="number"]::-webkit-inner-spin-button,
  .lutb-page input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
  .lutb-page .export-btn:disabled { opacity: 0.4; cursor: not-allowed; }
  .lutb-page .export-btn:disabled:hover { border-color: var(--border); color: var(--text-muted); }

  .lutb-col-chain { flex: 1.4 1 340px; min-width: 300px; }
  .lutb-col-export { flex: 1 1 220px; min-width: 200px; }
  .lutb-chain { display: flex; flex-direction: column; gap: 0.8rem; }

  .lutb-stage { display: flex; flex-direction: column; gap: 0.45rem; }
  .lutb-stage + .lutb-stage { padding-top: 0.8rem; border-top: 1px dashed var(--border); }
  .lutb-mix { display: flex; align-items: center; gap: 0.6rem; }
  .lutb-mix .fc-field-label { flex: 0 0 auto; }
  .lutb-mix .extraction-range { flex: 1 1 auto; width: auto; min-width: 0; }
  .lutb-toggle { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; }
  .lutb-toggle input[type="checkbox"] { accent-color: var(--gold); cursor: pointer; margin: 0; width: 14px; height: 14px; flex: 0 0 auto; }
  .lutb-toggle .fc-field-label { cursor: pointer; }
  .lutb-stage.is-off .export-select,
  .lutb-stage.is-off .lutb-mix { opacity: 0.4; }
  .lutb-stage.is-off .lutb-toggle .fc-field-label { opacity: 0.55; }

  .lutb-title { width: 100%; max-width: none; }
  .lutb-size-row { display: flex; align-items: center; gap: 0.5rem; }
  .lutb-size-row .export-select { flex: 1 1 auto; width: auto; }
  .lutb-size-custom { width: 4.2rem; max-width: none; text-align: center; }
  .lutb-preview-field { min-width: 220px; }

  .lutb-preview-section { max-width: 960px; width: 100%; margin: 0 auto; }
  .lutb-preview-head { display: flex; align-items: center; gap: 0.9rem; width: 100%; }
  .lutb-preview-head .res-label { flex: 1 1 auto; }
  .lutb-compare-toggle { flex: 0 0 auto; }
  .lutb-preview-wrap {
    position: relative; width: 100%;
    background: var(--frame-bg); border: 1px solid var(--border);
    overflow: hidden; cursor: col-resize;
    touch-action: none; user-select: none; -webkit-user-select: none;
  }
  .lutb-preview-wrap canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
  .lutb-preview-wrap.is-solo { cursor: default; }
  .lutb-divider { position: absolute; top: 0; bottom: 0; left: 50%; width: 1px; background: var(--gold); pointer-events: none; }
  .lutb-divider::after {
    content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: 20px; height: 20px; border-radius: 50%;
    background: rgba(10, 10, 10, 0.75); border: 1px solid var(--gold);
  }
  .lutb-pv-tag {
    position: absolute; top: 0.5rem;
    font-family: 'Roboto', sans-serif; font-size: 0.62rem; letter-spacing: 0.12em;
    color: #ebebeb; background: rgba(0, 0, 0, 0.55);
    padding: 0.15rem 0.45rem; pointer-events: none;
  }
  .lutb-pv-in { left: 0.5rem; }
  .lutb-pv-out { right: 0.5rem; }

  .lutb-summary { grid-template-columns: repeat(4, 1fr); max-width: 960px; }
  @media (max-width: 600px) and (min-width: 761px) { .lutb-summary { grid-template-columns: 1fr 1fr; } }
  @media (max-width: 760px) and (min-width: 761px) {
    .lutb-setup { grid-template-columns: 1fr; }
    .lutb-preview-field { min-width: 0; width: 100%; }
  }

  /* ===== lensData ===== */
  .ld-page .deck-col { flex: 1 1 220px; }
  .ld-setup { align-items: stretch; }
  .ld-col-select { flex: 3 1 420px; }
  .ld-col-select .fc-fields { width: 100%; }
  /* Status / error line under the deck. Hidden entirely when empty so it
     doesn't leave a gap in the normal case. */
  .ld-hint {
    width: 100%;
    max-width: 960px;
    margin: -0.75rem auto 0;
    text-align: center;
  }
  .ld-hint:empty { display: none; }
  .ld-page .dr-controls .export-select { width: 100%; }

  /* Lander: series overview beside the coverage diagram */
  .ld-lander-row {
    display: grid;
    grid-template-columns: 1.35fr 1fr;
    gap: 1.5rem;
    align-items: start;
  }
  .ld-lander-row > .res-section { min-width: 0; }

  .ld-series-head {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    width: 100%;
  }
  .ld-series-title {
    font-family: 'Roboto', sans-serif;
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    color: var(--gold);
    line-height: 1.3;
  }
  .ld-series-sub {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-muted);
  }

  .ld-badges { display: flex; flex-wrap: wrap; gap: 0.4rem; width: 100%; }
  .ld-badge {
    font-family: 'Roboto', sans-serif;
    font-size: 0.7rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--current);
    border: 1px solid var(--border);
    background: var(--bg);
    padding: 0.15rem 0.5rem;
    line-height: 1.5;
  }

  .ld-specs { width: 100%; }
  .ld-specs .dof-spec { grid-template-columns: minmax(120px, 42%) 1fr; }

  .ld-notes {
    font-family: 'Roboto', sans-serif;
    font-size: 0.8rem;
    line-height: 1.55;
    color: var(--text);
    width: 100%;
  }
  .ld-notes:empty { display: none; }
  .ld-sources {
    font-family: 'Roboto', sans-serif;
    font-size: 0.7rem;
    letter-spacing: 0.03em;
    line-height: 1.5;
    color: var(--text-muted);
    width: 100%;
  }
  .ld-sources:empty { display: none; }
  .ld-sources a { color: var(--gold); text-decoration: none; }
  .ld-sources a:hover { text-decoration: underline; }

  /* Coverage diagram — image circle against reference format rectangles */
  .ld-cov-stage {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--frame-bg);
    border: 1px solid var(--border);
    padding: 0.5rem;
  }
  .ld-cov-svg { width: 100%; max-width: 340px; height: auto; display: block; }
  .ld-cov-circle {
    fill: rgba(235, 208, 129, 0.07);
    stroke: var(--gold);
    stroke-width: 1.25;
  }
  .ld-cov-illum {
    fill: none;
    stroke: var(--gold);
    stroke-width: 1;
    stroke-dasharray: 3 3;
    opacity: 0.55;
  }
  .ld-cov-rect { fill: none; stroke-width: 1.1; }
  .ld-cov-rect.is-covered { stroke: #81ebeb; }
  .ld-cov-rect.is-short { stroke: #eb6d6d; stroke-dasharray: 4 3; }
  .ld-cov-centre { fill: var(--gold); }

  .ld-cov-legend {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    width: 100%;
  }
  .ld-leg-item {
    display: grid;
    grid-template-columns: 0.9rem 1fr auto auto;
    align-items: center;
    gap: 0.5rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.72rem;
    color: var(--text-muted);
  }
  .ld-leg-swatch {
    width: 0.7rem;
    height: 0.7rem;
    border: 1px solid var(--border);
    display: inline-block;
  }
  .ld-leg-swatch.is-covered { border-color: #81ebeb; background: rgba(129, 235, 235, 0.18); }
  .ld-leg-swatch.is-short { border-color: #eb6d6d; background: rgba(235, 109, 109, 0.15); }
  .ld-leg-swatch.is-tight { border-style: dashed; }
  .ld-leg-name { color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .ld-leg-diag { color: var(--text-muted); font-variant-numeric: tabular-nums; }
  .ld-leg-flag { letter-spacing: 0.06em; text-transform: uppercase; font-size: 0.68rem; }
  .ld-leg-flag.is-covered { color: var(--current); }
  .ld-leg-flag.is-short { color: var(--danger-soft); }

  .ld-cov-note {
    font-family: 'Roboto', sans-serif;
    font-size: 0.7rem;
    line-height: 1.5;
    color: var(--text-muted);
    width: 100%;
  }

  /* Spec table */
  .ld-page .res-table-wrap { overflow-x: auto; }
  .ld-table { width: 100%; }
  .ld-table td { font-variant-numeric: tabular-nums; }

  /* Iris glyphs in the Max / Min aperture headers. Stroke inherits the header
     colour so they follow the light/dark theme without a second rule. */
  .ld-ap-icon {
    width: 0.72rem;
    height: 0.72rem;
    vertical-align: -0.11em;
    margin-left: 0.3rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.2;
    overflow: visible;
  }
  .ld-ap-icon .ld-ap-dot { fill: currentColor; stroke: none; }
  .ld-row-announced td { color: var(--text-muted); }
  .ld-row-announced .tier { color: var(--text-muted); }
  .ld-tag {
    margin-left: 0.45rem;
    font-size: 0.62rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-muted);
    border: 1px solid var(--border);
    padding: 0.05rem 0.35rem;
    font-weight: 400;
  }

  @media (max-width: 860px) {
    .ld-lander-row { grid-template-columns: 1fr; }
  }
  @media (max-width: 480px) and (min-width: 761px) {
    .ld-leg-item { grid-template-columns: 0.9rem 1fr auto; }
    .ld-leg-diag { display: none; }
  }

  /* ----- lensData admin editing ----- */
  .ld-admin-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    width: 100%;
  }
  .ld-admin-row[hidden] { display: none; }
  .ld-admin-series {
    max-width: 960px;
    margin: -0.5rem auto 0;
  }
  .ld-danger-btn { color: var(--danger-soft); border-color: var(--danger-dim); }
  .ld-danger-btn:hover { border-color: var(--danger); color: var(--danger); background: var(--danger-dim); }
  .ld-admin-status {
    font-family: 'Roboto', sans-serif;
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    color: var(--success);
    margin-left: 0.25rem;
  }
  .ld-admin-status.is-error { color: var(--danger); }

  .res-table th.ld-admin-col,
  .res-table td.ld-admin-col { text-align: right; width: 1%; white-space: nowrap; }
  .ld-row-btn {
    background: var(--btn-ldrow-bg, transparent);
    border: 1px solid var(--btn-ldrow-border, var(--border));
    color: var(--btn-ldrow-fg, var(--text-muted));
    font-family: 'Roboto', sans-serif;
    font-size: var(--btn-ldrow-size, 0.68rem);
    letter-spacing: var(--btn-ldrow-tracking, 0.06em);
    padding: var(--btn-ldrow-pad-y, 0.15rem) var(--btn-ldrow-pad-x, 0.55rem);
    border-radius: var(--btn-ldrow-radius, 0);
    cursor: pointer;
    transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
  }
  .ld-row-btn:hover { border-color: var(--gold); color: var(--gold); background: var(--gold-dim); }

  .ld-cov-empty {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--text-muted);
    padding: 2rem 1rem;
    text-align: center;
  }

  /* Wider dialog for the spec-heavy series and lens editors */
  .gm-modal-box.gm-wide { max-width: 640px; }
  .ld-gm-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.75rem 1rem;
  }
  .ld-gm-sub {
    font-family: 'Roboto', sans-serif;
    font-size: 0.72rem;
    letter-spacing: 0.06em;
    color: var(--current);
    margin-top: -0.5rem;
  }
  .ld-gm-grid .export-select { width: 100%; }
  .ld-gm-grid .custom-dd { width: 100%; }
  .ld-ta { min-height: 8rem; }
  .gm-field code {
    font-family: 'Roboto Mono', monospace;
    font-size: 0.9em;
    color: var(--gold);
    text-transform: none;
    letter-spacing: 0;
  }

  .ld-permount-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
  }
  .ld-permount-toggle { text-transform: none; letter-spacing: 0.04em; font-size: 0.7rem; }
  .ld-mount-rows { display: flex; flex-direction: column; gap: 0.4rem; }
  .ld-mount-rows[hidden] { display: none; }
  .ld-mount-row {
    display: grid;
    grid-template-columns: 4.5rem 1fr;
    align-items: center;
    gap: 0.6rem;
  }
  .ld-mount-label {
    font-family: 'Roboto', sans-serif;
    font-size: 0.72rem;
    letter-spacing: 0.06em;
    color: var(--gold);
  }
  .ld-mount-row .text-input { width: 100%; max-width: none; }
  .ld-announce-toggle { text-transform: none; letter-spacing: 0.03em; font-size: 0.72rem; }

  .gm-modal-actions .ld-danger-btn { margin-right: auto; }

  @media (max-width: 560px) and (min-width: 761px) {
    .ld-gm-grid { grid-template-columns: 1fr; }
  }

  /* =========================================================================
     SHOP  (/shop/index.html, /shop/product.html, /shop/edit.html)

     Ported from the Alan Holland "work" section and rebuilt on PHFX tokens.
     Nothing here introduces a colour or a radius of its own: gold for the
     call to action, --current for available, --text-muted for sold out,
     square corners throughout, so a theme switch reaches all of it.

     The 900px breakpoint is the mobile-facing one. The shell locks mobile at
     761px (see the viewport meta), which is below 900, so a phone always
     evaluates these rules while a full desktop window does not. That is why
     the two-column product layout collapses at 900 and not at some smaller
     number that would never fire.
     ========================================================================= */

  /* ---- shared bits ---- */

  .shop-mark { width: 2.5rem; height: 2.5rem; color: var(--border); flex: 0 0 auto; }

  .shop-state {
    grid-column: 1 / -1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.9rem;
    padding: 3rem 1rem;
    text-align: center;
    color: var(--text-muted);
    font-family: 'Roboto', sans-serif;
    font-size: 0.85rem;
  }
  .shop-state.is-loading .shop-mark { animation: shopPulse 1.6s ease-in-out infinite; }
  @keyframes shopPulse { 0%, 100% { opacity: 0.35; } 50% { opacity: 0.9; } }
  .shop-state-action { text-align: center; margin-top: 0.5rem; }

  .shop-count {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    letter-spacing: 0.04em;
    color: var(--text-muted);
  }

  .shop-no-img {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    background: var(--frame-bg);
  }

  .shop-status {
    display: inline-block;
    font-family: 'Roboto', sans-serif;
    font-size: 0.7rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    padding: 0.15rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: 0;
    line-height: 1.4;
  }
  .shop-status.is-available { color: var(--current); border-color: var(--current); }
  .shop-status.is-sold      { color: var(--text-muted); }

  /* A draft is admin-only by construction — shopItems.cgi never sends one to
     the public — so this is loud on purpose. It exists to stop an unlisted
     item being mistaken for a live one while it is being worked on. */
  .shop-draft-pill {
    display: inline-block;
    font-family: 'Roboto', sans-serif;
    font-size: 0.7rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    padding: 0.15rem 0.5rem;
    border: 1px dashed var(--warning);
    color: var(--warning);
    line-height: 1.4;
  }

  .shop-admin-line {
    color: var(--text-muted);
    text-decoration: none;
    transition: color 0.18s ease;
  }
  .shop-admin-line:hover { color: var(--gold); }

  /* ---- toast ---- */

  .shop-toast {
    position: fixed;
    left: 50%;
    bottom: 3.5rem;
    transform: translateX(-50%);
    z-index: 120;
    background: var(--surface);
    border: 1px solid var(--gold);
    color: var(--text);
    font-family: 'Roboto', sans-serif;
    font-size: 0.78rem;
    letter-spacing: 0.03em;
    padding: 0.6rem 1rem;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
    animation: shopToastIn 0.18s ease;
    max-width: min(30rem, 90vw);
    text-align: center;
  }
  .shop-toast.is-err { border-color: var(--danger); color: var(--danger-soft); }
  @keyframes shopToastIn {
    from { opacity: 0; transform: translate(-50%, 0.4rem); }
    to   { opacity: 1; transform: translate(-50%, 0); }
  }

  /* ---- lander: filter bar ---- */

  .shop-page { justify-content: flex-start; gap: 1rem; }

  .shop-bar {
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
  }
  /* Category links, not bordered pills — this mirrors .motion-cat-link, which
     is already the site's filter-bar convention. Kept as its own selector
     rather than reusing the motion class, so the two can diverge without one
     lander silently restyling the other. */
  .shop-categories {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem 0.9rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.78rem;
    flex: 1 1 auto;
    min-width: 0;
  }
  .shop-cat-link {
    color: var(--text-muted);
    text-decoration: none;
    cursor: pointer;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    transition: color 0.15s ease;
  }
  /* Three distinct steps, matching .motion-cat-link: unselected filters recede
     at --text-muted, gold marks the one under the pointer as it does on every
     other control, and --current marks the one in force — which is what that
     token is named for.

     This previously ran gold-at-rest with an underline on hover. That worked,
     but it spent the site's loudest colour on every filter at once, leaving
     the selected item distinguished by hue alone and the row with no
     hierarchy. The underline is dropped because hue now carries all three
     states on its own. */
  .shop-cat-link:hover { color: var(--gold); }
  /* Active outranks hover: pointing at the selected filter must not change
     what it is telling you. */
  .shop-cat-link.active,
  .shop-cat-link.active:hover { color: var(--current); }
  .shop-cat-link:focus-visible { outline: 1px solid var(--gold); outline-offset: 3px; }

  /* ---- lander: cards ---- */

  /* .shop-grid rides on .tool-grid for its column counts, so the shop tracks
     the rest of the site's landers automatically. Only the card differs. */
  .shop-card {
    position: relative;
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0;
    text-decoration: none;
    color: var(--text);
    overflow: hidden;
    transition: border-color 0.18s ease, transform 0.18s ease,
                box-shadow 0.18s ease, background 0.18s ease;
  }
  .shop-card:hover {
    border-color: var(--gold);
    background: var(--surface-hover);
    transform: translateY(-2px);
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35);
  }
  .shop-card:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

  .shop-card-frame {
    position: relative;
    display: block;
    width: 100%;
    aspect-ratio: 4 / 3;
    background: var(--frame-bg);
    overflow: hidden;
  }
  .shop-card-frame img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform 0.3s ease;
  }
  .shop-card:hover .shop-card-frame img { transform: scale(1.03); }

  /* Sold-out items stay in the grid rather than disappearing — the back
     catalogue is worth showing — but they read as past tense. */
  .shop-card.is-sold .shop-card-frame img { opacity: 0.55; }
  .shop-card.is-draft { border-style: dashed; }

  .shop-card-tag {
    position: absolute;
    top: 0.5rem;
    left: 0.5rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.65rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    padding: 0.12rem 0.45rem;
    background: rgba(10, 10, 10, 0.78);
    border: 1px solid var(--border);
    line-height: 1.4;
  }
  .shop-card-tag.is-available { color: var(--current); border-color: var(--current); }
  .shop-card-tag.is-sold      { color: var(--text-muted); }

  .shop-card-draft {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.65rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    padding: 0.12rem 0.45rem;
    background: rgba(10, 10, 10, 0.78);
    border: 1px dashed var(--warning);
    color: var(--warning);
    line-height: 1.4;
  }

  .shop-card-body {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    padding: 0.85rem 1rem 1rem;
  }
  .shop-card-title {
    font-family: 'Roboto', sans-serif;
    font-size: 0.9rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    color: var(--gold);
  }
  .shop-card-meta {
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    color: var(--text-muted);
  }

  /* ---- product page ---- */

  .shop-product-page {
    justify-content: flex-start;
    gap: 1.5rem;
    max-width: 960px;
    margin: 0 auto;
    padding: 1.5rem 1rem;
    width: 100%;
    min-width: 0;
  }

  .shop-product {
    width: 100%;
    display: grid;
    grid-template-columns: 1.15fr 1fr;
    gap: 1.75rem;
    align-items: start;
  }

  /* --shop-aspect is set per item by shopProduct.js from the product's own
     ratio field, and inherited by both the stage and the thumbnails so the
     strip always echoes the frame above it. The fallback matters only if the
     column is ever rendered without the variable. */
  .shop-product-media {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    min-width: 0;
  }

  /* Images are click-through so the surrounding element is the right-click
     target — the browser then offers no "Save image as". Paired with the
     contextmenu and dragstart handlers in shopCommon.js's protectFrame().
     Mirrors the .lb-img-frame img treatment used by the still gallery. */
  .shop-protected img {
    pointer-events: none;
    -webkit-user-drag: none;
    user-drag: none;
    -webkit-user-select: none;
    user-select: none;
  }

  .shop-stage {
    width: 100%;
    aspect-ratio: var(--shop-aspect, 4 / 3);
    background: var(--frame-bg);
    border: 1px solid var(--border);
    overflow: hidden;
    cursor: zoom-in;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .shop-stage img { width: 100%; height: 100%; object-fit: contain; display: block; }
  .shop-stage:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

  /* The strip spans the stage exactly.

     It used to be a wrapping flex row of fixed 3.6rem squares, which meant the
     row almost never ended flush with the stage above it — four thumbnails
     left 479px of dead space on the right at the 761 lock. Equal 1fr columns,
     counted to match the number of images, make every full row total the
     stage width whatever that width happens to be.

     shopProduct.js sets --shop-thumb-cols from the image count. The fallback
     of 4 only matters if the strip is ever rendered without it. */
  .shop-thumbs {
    display: grid;
    grid-template-columns: repeat(var(--shop-thumb-cols, 4), 1fr);
    gap: 0.4rem;
  }

  /* Echoes the stage's ratio rather than hardcoding one, but capped in
     height: at the lock two images would otherwise give a pair of 361x271
     thumbnails, taller than they are useful. Past the cap the box simply
     grows wider than its ratio and object-fit crops it, which keeps the row
     flush either way. */
  .shop-thumb {
    width: 100%;
    min-width: 0;
    aspect-ratio: var(--shop-aspect, 4 / 3);
    max-height: 6rem;
    padding: 0;
    background: var(--frame-bg);
    border: 1px solid var(--border);
    border-radius: 0;
    cursor: pointer;
    overflow: hidden;
    transition: border-color 0.18s ease, opacity 0.18s ease;
    opacity: 0.6;
  }
  .shop-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .shop-thumb:hover { opacity: 1; border-color: var(--gold); }
  .shop-thumb[aria-current="true"] { opacity: 1; border-color: var(--gold); }

  .shop-product-info { display: flex; flex-direction: column; gap: 0.85rem; min-width: 0; }
  .shop-product-info h1 {
    font-family: 'Roboto', sans-serif;
    font-size: 1.25rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    color: var(--gold);
  }
  .shop-product-sub {
    font-family: 'Roboto', sans-serif;
    font-size: 0.8rem;
    color: var(--text-muted);
    letter-spacing: 0.04em;
    margin-top: -0.5rem;
  }
  .shop-product-status { display: flex; gap: 0.4rem; flex-wrap: wrap; }

  .shop-prose {
    font-family: 'Roboto', sans-serif;
    font-size: 0.85rem;
    line-height: 1.65;
    color: var(--text);
  }
  .shop-prose p + p { margin-top: 0.7rem; }
  .shop-prose a { color: var(--gold); }

  .shop-spec-list {
    border-top: 1px solid var(--border);
    font-family: 'Roboto', sans-serif;
    font-size: 0.8rem;
  }
  .shop-spec-row {
    display: grid;
    grid-template-columns: 8rem 1fr;
    gap: 0.75rem;
    padding: 0.5rem 0;
    border-bottom: 1px solid var(--border);
  }
  .shop-spec-row dt {
    color: var(--text-muted);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-size: 0.7rem;
    padding-top: 0.1rem;
  }
  .shop-spec-row dd { color: var(--text); overflow-wrap: break-word; }

  /* ---- buy box ---- */

  .shop-buy-box {
    border: 1px solid var(--border);
    background: var(--surface);
    padding: 1rem 1.1rem;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
  }

  /* External references. Same structure as the buy box but no background fill
     and no border, so it reads as a footnote to the item rather than a second
     purchase panel. The distinction matters: nothing in here should look like
     a place to spend money. */
  .shop-ref-box {
    margin-top: 1rem;
    padding-top: 0.9rem;
    border-top: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }
  .shop-ref-links { display: flex; flex-direction: column; gap: 0.35rem; }
  .shop-ref-link {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.3rem 0;
    color: var(--text-muted);
    font-family: 'Roboto', sans-serif;
    font-size: 0.78rem;
    letter-spacing: 0.03em;
    text-decoration: none;
    transition: color 0.18s ease;
  }
  .shop-ref-link:hover { color: var(--gold); }
  /* Outbound arrow, distinct from the buy links' forward arrow. */
  .shop-ref-link .arr { flex: 0 0 auto; opacity: 0.6; }
  .shop-ref-link:hover .arr { opacity: 1; }
  .shop-buy-box.is-sold { opacity: 0.85; }
  .shop-buy-label {
    font-family: 'Roboto', sans-serif;
    font-size: 0.7rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-muted);
  }
  .shop-buy-price {
    font-family: 'Roboto', sans-serif;
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    color: var(--current);
  }
  .shop-buy-note {
    font-family: 'Roboto', sans-serif;
    font-size: 0.8rem;
    line-height: 1.55;
    color: var(--text-muted);
  }
  .shop-buy-note a { color: var(--gold); }
  .shop-buy-note-tail { margin-top: 0.2rem; }

  .shop-buy-links { display: flex; flex-direction: column; gap: 0.4rem; }

  /* The first link is the primary call to action and is the only gold-filled
     control on the page, so there is never a question about where to click.
     Any others are alternative places the same item is listed. */
  .shop-buy-link {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.55rem 0.85rem;
    border: 1px solid var(--border);
    color: var(--text-muted);
    font-family: 'Roboto', sans-serif;
    font-size: 0.8rem;
    letter-spacing: 0.05em;
    text-decoration: none;
    transition: all 0.18s ease;
  }
  .shop-buy-link:hover { border-color: var(--gold); color: var(--gold); }
  .shop-buy-link .arr { transition: transform 0.18s ease; }
  .shop-buy-link:hover .arr { transform: translateX(3px); }

  .shop-buy-link.is-primary {
    border-color: var(--gold);
    background: var(--gold-dim);
    color: var(--gold);
    font-weight: 700;
    padding: 0.7rem 0.9rem;
  }
  .shop-buy-link.is-primary:hover { background: var(--gold); color: var(--bg); }

  .shop-back-link {
    font-family: 'Roboto', sans-serif;
    font-size: 0.78rem;
    letter-spacing: 0.05em;
    color: var(--text-muted);
    text-decoration: none;
    align-self: flex-start;
    transition: color 0.18s ease;
  }
  .shop-back-link:hover { color: var(--gold); }

  /* ---- lightbox ---- */

  .shop-lightbox {
    position: fixed;
    inset: 0;
    z-index: 110;
    background: rgba(0, 0, 0, 0.92);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2.5rem;
    cursor: zoom-out;
  }
  .shop-lightbox img { max-width: 100%; max-height: 100%; object-fit: contain; display: block; }
  .shop-lightbox button {
    position: absolute;
    background: none;
    border: 0;
    color: var(--text-muted);
    cursor: pointer;
    line-height: 1;
    padding: 0.4rem 0.7rem;
    transition: color 0.18s ease;
  }
  .shop-lightbox button:hover { color: var(--gold); }
  .shop-lightbox-close { top: 0.6rem; right: 0.9rem; font-size: 2rem; }
  .shop-lightbox-prev  { left: 0.5rem;  top: 50%; transform: translateY(-50%); font-size: 3rem; }
  .shop-lightbox-next  { right: 0.5rem; top: 50%; transform: translateY(-50%); font-size: 3rem; }
  .shop-lightbox-caption {
    position: absolute;
    bottom: 1rem;
    left: 50%;
    transform: translateX(-50%);
    font-family: 'Roboto', sans-serif;
    font-size: 0.75rem;
    letter-spacing: 0.1em;
    color: var(--text-muted);
  }

  /* ---- manager ---- */

  .shop-manage-page {
    justify-content: flex-start;
    gap: 1.25rem;
    max-width: 960px;
    margin: 0 auto;
    padding: 1.5rem 1rem;
    width: 100%;
    min-width: 0;
  }
  .shop-manage-page > div { width: 100%; min-width: 0; }

  .shop-admin-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    padding: 0.9rem 1.1rem;
    margin-bottom: 1.25rem;
  }
  .shop-admin-title { display: flex; align-items: baseline; gap: 0.75rem; flex-wrap: wrap; }
  .shop-admin-heading {
    font-family: 'Roboto', sans-serif;
    font-size: 0.95rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    color: var(--gold);
  }
  .shop-admin-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
  .shop-admin-actions .export-btn { text-decoration: none; }

  /* List left, editor right — the source site's arrangement. `align-items:
     start` keeps the list from stretching to the editor's height, which is
     what makes the sticky rule below have somewhere to travel. */
  .shop-admin-layout {
    display: grid;
    grid-template-columns: 260px 1fr;
    gap: 1.75rem;
    align-items: start;
    padding-bottom: 2rem;
  }
  .shop-admin-layout > div { min-width: 0; }

  /* <main> is the scroll container in the PHFX shell, not the window, so a
     long editor would scroll the list clean off the top. Sticking the column
     to the top of the scrollport keeps the catalogue reachable while working
     far down an item. The list caps its own height and scrolls internally so
     a large catalogue can't outgrow the viewport and defeat the point. */
  .shop-list-col { position: sticky; top: 0; }

  .shop-admin-list {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    max-height: 24rem;
    overflow-y: auto;
    border: 1px solid var(--border);
    background: var(--surface);
    padding: 0.4rem;
  }
  .shop-admin-list.is-dragging { cursor: grabbing; }

  .shop-admin-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.35rem 0.5rem;
    border: 1px solid transparent;
    cursor: pointer;
    transition: border-color 0.18s ease, background 0.18s ease;
  }
  .shop-admin-row:hover { background: var(--surface-hover); }
  .shop-admin-row[aria-current="true"] { border-color: var(--gold); background: var(--gold-dim); }
  .shop-admin-row.dragging { opacity: 0.5; }
  .shop-admin-row.is-draft { opacity: 0.72; }

  .shop-admin-row .grip {
    color: var(--text-muted);
    font-size: 0.75rem;
    letter-spacing: -0.2em;
    cursor: grab;
    flex: 0 0 auto;
    /* Without this a drag starting on the grip selects the list text instead. */
    user-select: none;
    -webkit-user-select: none;
    touch-action: none;
  }
  .shop-dot { width: 0.5rem; height: 0.5rem; border-radius: 50%; flex: 0 0 auto; }
  .shop-dot.is-available { background: var(--current); }
  .shop-dot.is-sold      { background: var(--text-muted); }
  /* Hollow, because N/A is the absence of a claim rather than a third claim. */
  .shop-dot.is-na        { background: transparent; box-shadow: inset 0 0 0 1px var(--border); }

  .shop-admin-pick {
    flex: 1 1 auto;
    min-width: 0;
    background: none;
    border: 0;
    padding: 0;
    text-align: left;
    color: var(--text);
    font-family: 'Roboto', sans-serif;
    font-size: 0.82rem;
    cursor: pointer;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .shop-admin-flag {
    flex: 0 0 auto;
    font-family: 'Roboto', sans-serif;
    font-size: 0.65rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--warning);
  }
  .shop-list-hint {
    font-family: 'Roboto', sans-serif;
    font-size: 0.72rem;
    line-height: 1.5;
    color: var(--text-muted);
    margin: 0.5rem 0 0;
  }

  .shop-editor {
    border: 1px solid var(--border);
    background: var(--surface);
    padding: 1.25rem;
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
  }
  .shop-editor-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 1rem;
    flex-wrap: wrap;
  }
  .shop-editor-head h2 {
    font-family: 'Roboto', sans-serif;
    font-size: 1rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    color: var(--gold);
  }
  .shop-editor-head-actions { display: flex; gap: 0.4rem; flex-wrap: wrap; }

  .shop-editor-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.85rem; }
  .shop-editor-grid .span-2 { grid-column: 1 / -1; }
  .shop-editor-grid .text-input,
  .shop-editor-grid .export-select { width: 100%; max-width: none; }

  .shop-editor-section {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    align-items: flex-start;
    padding-top: 1rem;
    border-top: 1px solid var(--border);
  }
  .shop-section-label {
    font-family: 'Roboto', sans-serif;
    font-size: 0.72rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
  }
  .shop-editor-section > div,
  .shop-editor-section > p { width: 100%; }

  .shop-editor-footer {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
  }
  .shop-editor-footer .export-btn { text-decoration: none; }

  .shop-check {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.78rem;
    color: var(--text);
    cursor: pointer;
  }
  .shop-check input { accent-color: var(--gold); cursor: pointer; }

  /* Label / value / up / delete. The two text columns share the space so a
     long value doesn't squeeze its own label out of readability. */
  .shop-pair-row {
    display: grid;
    grid-template-columns: 1fr 1.4fr auto auto;
    gap: 0.4rem;
    align-items: center;
    margin-bottom: 0.4rem;
  }
  .shop-pair-row .text-input { width: 100%; max-width: none; }

  .shop-img-row {
    display: grid;
    grid-template-columns: 3rem 1fr auto auto;
    gap: 0.4rem;
    align-items: center;
    margin-bottom: 0.4rem;
  }
  .shop-img-row .text-input { width: 100%; max-width: none; }
  .shop-img-thumb {
    width: 3rem;
    height: 3rem;
    object-fit: cover;
    background: var(--frame-bg);
    border: 1px solid var(--border);
    display: block;
  }

  .shop-danger-btn { color: var(--danger-soft); border-color: var(--border); }
  .shop-danger-btn:hover { border-color: var(--danger); color: var(--danger); background: var(--danger-dim); }

  .shop-drop-zone {
    width: 100%;
    border: 1px dashed var(--border);
    background: var(--frame-bg);
    color: var(--text-muted);
    font-family: 'Roboto', sans-serif;
    font-size: 0.78rem;
    text-align: center;
    padding: 1.4rem 1rem;
    cursor: pointer;
    transition: all 0.18s ease;
  }
  .shop-drop-zone:hover,
  .shop-drop-zone.over { border-color: var(--gold); color: var(--gold); }

  .shop-slug-note {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    font-family: 'Roboto', sans-serif;
    font-size: 0.68rem;
    line-height: 1.5;
    color: var(--text-muted);
  }
  .shop-slug-addr { color: var(--text-muted); word-break: break-all; }
  .shop-slug-addr strong { color: var(--gold); font-weight: 400; }

  .shop-linkish {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: var(--gold);
    text-decoration: underline;
    cursor: pointer;
  }

  /* ---- narrow (this is the phone case: the shell locks mobile at 761) ---- */

  @media (max-width: 900px) {
    .shop-product { grid-template-columns: 1fr; gap: 1.25rem; }
    .shop-spec-row { grid-template-columns: 7rem 1fr; }
    .shop-editor-grid { grid-template-columns: 1fr; }
    .shop-pair-row { grid-template-columns: 1fr 1.2fr auto auto; }
    .shop-lightbox { padding: 1.25rem; }

    /* Still two columns — just a narrower one. At the 761 lock the content
       box is 729px, so 260 + a 2rem gap would leave the editor at 437px and
       its label/value pairs around 141px, too tight to read an address in.
       200 + 1rem gives the editor 513px and those fields 180 / 215. */
    .shop-admin-layout { grid-template-columns: 200px 1fr; gap: 1rem; }
    .shop-admin-list { max-height: 20rem; }
    .shop-editor { padding: 1rem; }
    .shop-editor-head-actions .export-btn { padding: 0.5rem 0.6rem; }
  }
