/* ============================================================================
   SHIELD Studio — design tokens
   ============================================================================
   Three layers, in cascade order:

     1. BRAND CONSTANTS   identical on every surface. Straight off
                          "Shield Studio Brand Identity.jpeg". Component rules
                          never read these directly — the semantic tokens do.
     2. [data-surface="studio"]   the dark SHIELD Studio skin. Public + viewer.
     3. [data-surface="console"]  the inverted light skin. Admin.

   templates/base.html picks the surface from `is_admin`, which is
   `is_admin_for_display()` (src/factory.py) — false while an admin is using
   "View as", so a preview shows the real viewer skin instead of lying about it.

   Studio is also the value on bare :root, so a template that forgets the
   attribute still renders as the public face rather than unstyled.

   There is no prefers-color-scheme branch any more: the skin is decided by
   role, not by OS preference. See
   docs/website-design/rebrand/PLAN-shield-studio-retheme.md, decision D6.

   Every colour pair introduced here carries its measured WCAG 2.1 contrast
   ratio in a comment. The brand accents are tuned for a near-black canvas and
   mostly pass as published on `studio`; on `console` each one needs a darkened
   variant, because #22D6C0 on white is 1.8:1.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   1. Brand constants
   ------------------------------------------------------------------------ */
:root {
  /* Palette — Shield Studio Brand Identity, "COLOR PALETTE" row */
  --brand-charcoal:  #010101;
  --brand-graphite:  #151a20;
  --brand-slate:     #1f2730;
  --brand-white:     #ffffff;
  --brand-cyan:      #22d6c0;  /* Vibrant Cyan — fill and decoration only on light */
  --brand-teal:      #0a9d8c;  /* Medium Teal */
  --brand-deep-teal: #2f726b;  /* Deep Teal */
  --brand-gray:      #888888;  /* Cool Gray — 3.5:1 on white: hairlines, never body copy */

  /* Semantic accents — "SEMANTIC ACCENTS" row. Values as published; the
     console surface below substitutes darkened variants for the four that
     fail 4.5:1 against white. */
  --brand-success:   #22c55e;
  --brand-warning:   #ffb020;
  --brand-attention: #7c5cff;
  --brand-critical:  #ff4d4d;
  --brand-info:      #3b82f6;

  /* Studio canvas and ink, lifted verbatim from SHIELD_Studio_Landing_Page.html
     so the app chrome and the landing page agree to the hex. */
  --studio-canvas:   #02070c;
  --studio-surface:  #0a131a;
  --studio-sunk:     #01050a;
  --studio-raised:   #111c25;
  --studio-paper:    #f4f2ef;
  --studio-ink-2:    #d4d1ce;
  --studio-ink-3:    #c5c2bf;
  --studio-line:     #30363c;
  --studio-line-2:   #39434b;
  --studio-line-hi:  #4e616b;
  --studio-teal-hi:  #5fe6d6;
  --brand-on-teal:   #001113;  /* ink for text on any teal fill, either skin */

  /* Surface-independent by design, not by omission ------------------------- */

  /* Letterbox behind a <video>. Black on both skins — that is the convention
     for video, not a theme decision. */
  --media-letterbox: #000;
  /* The <iframe> preview of an uploaded HTML document. Those files bring their
     own background and overwhelmingly assume white paper; the app skin must
     not bleed into them. Same reasoning as the sandboxed page frame — see
     templates/_page_frame.html. */
  --document-paper: #ffffff;
  /* A pill sitting on top of an arbitrary image (the gallery counter). It has
     to work over any photograph, so it is fixed on both skins. */
  --scrim:    rgba(1, 1, 1, 0.62);
  --on-scrim: #ffffff;

  /* The code editor's ink. Pinned, not themed, for the same reason as
     --document-paper: CodeMirror renders on white in both console themes, so
     ink that follows the theme goes light-on-white the moment an admin
     switches to dark. Every value below is measured against white. See the
     .CodeMirror block at the foot of this file. */
  --code-ink-muted:  #5a6570;  /* 6.2:1 — gutter numbers; vendor ships #999, 2.9:1 */
  --code-error:      #b3261e;  /* 6.5:1 — vendor ships #f00, 4.0:1 */
  --code-ok:         #126a33;  /* 6.7:1 */
  --code-string-alt: #a83800;  /* 6.5:1 — vendor ships #f50, 3.2:1 */
  --code-bracket:    #666633;  /* 6.0:1 — vendor ships #997, 2.9:1 */

  /* Type. Inter is the brand's only display face — the Source Serif 4 of the
     "Vault & Vellum" pass is gone. JetBrains Mono carries code, data and the
     footer strip. */
  --font-ui:      "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  --font-display: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  --font-mono:    "JetBrains Mono", ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;

  /* Shape */
  --r-control: 4px;
  --r-card: 8px;
  --r-pill: 9999px;

  /* Spacing and measure */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --container-max: 1200px;
  --sidebar-w: 280px;
  --passe: 12px;
}

/* ---------------------------------------------------------------------------
   2. Studio — the dark skin. Public landing, public folders, viewer library,
   sitemap, sign-in, errors, and the page frame when a viewer is reading.
   ------------------------------------------------------------------------ */
:root,
[data-surface="studio"] {
  color-scheme: dark;

  --bg:             var(--studio-canvas);
  --surface:        var(--studio-surface);
  --surface-sunk:   var(--studio-sunk);
  --surface-raised: var(--studio-raised);

  --ink:            var(--studio-paper);  /* 17.1:1 on --bg */
  --ink-muted:      var(--studio-ink-3);  /* 10.5:1 on --bg */

  --hairline:       var(--studio-line);
  --hairline-hover: var(--studio-line-hi);

  /* Accent. One value serves both as link text and as a solid fill, because
     component rules use --primary for both (.btn-primary, .sidebar-page-link
     .active, the files-drawer toggle). 11.2:1 as text on --bg; 11.4:1 as a
     fill under --on-primary. */
  --primary:         var(--brand-cyan);
  --primary-hover:   var(--studio-teal-hi);
  --on-primary:      var(--brand-on-teal);
  --primary-wash:    rgba(34, 214, 192, 0.10);
  --on-primary-wash: rgba(0, 17, 19, 0.18);
  /* Decoration only — glows, rules, icon strokes. Never small text. */
  --accent-bright:   var(--brand-cyan);
  /* Solid chrome — the header bar and sidebar rail. On a near-black canvas
     that reads as a *raised* panel carrying the ordinary ink; console gets
     the inverse, a slate bar carrying light ink. */
  --chrome:            var(--studio-raised);
  --on-chrome:         var(--ink);            /* 14.6:1 */
  --on-chrome-muted:   var(--studio-ink-3);   /*  9.7:1 */
  --on-chrome-accent:  var(--brand-cyan);     /*  9.4:1 */
  --chrome-line:       rgba(244, 242, 239, 0.10);
  --chrome-hover:      rgba(244, 242, 239, 0.08);

  /* The brand semantic accents pass against a near-black canvas as published,
     so studio uses them unmodified. */
  --danger:       var(--brand-critical);   /* 6.2:1 */
  --danger-wash:  rgba(255, 77, 77, 0.14);
  --success:      var(--brand-success);    /* 8.9:1 */
  --success-wash: rgba(34, 197, 94, 0.14);
  --warning:      var(--brand-warning);    /* 12.3:1 */
  --warning-wash: rgba(255, 176, 32, 0.14);
  --info:         #60a5fa;                 /* 7.9:1 — the published #3b82f6 is 5.5:1 on the
                                              canvas but only 4.3:1 on its own chip wash */
  --info-wash:    rgba(59, 130, 246, 0.14);

  /* Solid destructive button — fixed across both skins: a dark red that
     carries white text on white, on slate and on near-black alike. */
  --danger-solid: #b3261e;
  --on-danger:    #ffffff;

  --focus-ring: var(--brand-cyan);
  /* A wash of --surface, for hover on the inverted preview banner (which is
     --ink ground with --surface ink). Dark here, because --surface is dark. */
  --surface-wash: rgba(2, 7, 12, 0.16);

  /* The sandboxed document frame. On a near-black canvas a white document is
     already the strongest edge on the screen, so the job is to make it read
     as MOUNTED rather than cut out: a lighter rule than the page hairline,
     plus a faint teal rim. */
  --frame-line: var(--studio-line-2);
  --frame-rim:  0 0 0 1px rgba(34, 214, 192, 0.08);

  /* The sandboxed document frame. On a near-black canvas a white document is
     already the strongest edge on the screen, so the job is to make it read
     as MOUNTED rather than cut out: a lighter rule than the page hairline,
     plus a faint teal rim. */
  --frame-line: var(--studio-line-2);
  --frame-rim:  0 0 0 1px rgba(34, 214, 192, 0.08);

  /* Black has no shadow to cast; depth on this skin comes from the hairline
     and a faint glow, not from a drop shadow. */
  --shadow-pop: 0 20px 50px -12px rgba(0, 0, 0, 0.75);
  --backdrop:   rgba(0, 0, 0, 0.68);
}

/* ---------------------------------------------------------------------------
   3. Console — the inverted light skin. Admin only: folder browser, manage,
   people, access requests, email templates, responses.
   ------------------------------------------------------------------------ */
[data-surface="console"] {
  color-scheme: light;

  --bg:             #f7f8f8;
  --surface:        var(--brand-white);
  --surface-sunk:   #f1f3f3;
  --surface-raised: #e8ebec;

  --ink:            var(--brand-graphite);  /* 15.9:1 on white */
  --ink-muted:      #5a6570;                /* 6.2:1 on white — NOT --brand-gray, which is 3.5:1 */

  --hairline:       #e3e6e8;
  --hairline-hover: #c3cace;

  /* Deep Teal, not Vibrant Cyan: --primary is text as often as it is a fill,
     and #22d6c0 is 1.8:1 on white. Deep Teal is 5.6:1 as a link on --surface
     and 5.6:1 as a fill under white --on-primary. Medium Teal (3.4:1) is a
     fill colour only, and lives in --accent-bright. */
  --primary:         var(--brand-deep-teal);
  --primary-hover:   #245853;
  --on-primary:      var(--brand-white);
  --primary-wash:    rgba(47, 114, 107, 0.08);
  --on-primary-wash: rgba(255, 255, 255, 0.22);
  /* Medium Teal — large CTA fills carrying near-black text (8.9:1), rules and
     icon strokes. Never small text. */
  --accent-bright:   var(--brand-teal);
  /* Solid chrome — the "dark accents" half of the brief: header bar, sidebar
     rail, active rows. Slate against a near-white canvas. */
  --chrome:            var(--brand-slate);
  --on-chrome:         var(--studio-paper);  /* 13.5:1 */
  --on-chrome-muted:   #9aa5b0;              /*  6.0:1 — --ink-muted is 1.2:1 here */
  /* Vibrant Cyan, not the Deep Teal used elsewhere on this skin: Deep Teal on
     slate is 2.7:1. The rule that cyan is a fill-only colour applies to
     WHITE grounds; on the slate bar it is the one that works. */
  --on-chrome-accent:  var(--brand-cyan);    /*  8.2:1 */
  --chrome-line:       rgba(244, 242, 239, 0.14);
  --chrome-hover:      rgba(244, 242, 239, 0.08);

  /* Darkened variants — each brand accent fails 4.5:1 against white at its
     published value (#ff4d4d 3.0:1, #22c55e 2.3:1, #ffb020 1.8:1, #3b82f6
     3.7:1), so console substitutes a compliant tone and keeps the published
     hue for the matching wash. */
  --danger:       #b3261e;  /* 6.5:1 */
  --danger-wash:  rgba(255, 77, 77, 0.10);
  --success:      #126a33;  /* 6.7:1 on white; #15803d was 4.2:1 on the raised
                               surface and 4.5:1 on its own chip wash */
  --success-wash: rgba(34, 197, 94, 0.14);
  --warning:      #8a5a00;  /* 5.5:1 */
  --warning-wash: rgba(255, 176, 32, 0.18);
  --info:         #1d4ed8;  /* 6.3:1 */
  --info-wash:    rgba(59, 130, 246, 0.10);

  --danger-solid: #b3261e;
  --on-danger:    #ffffff;

  /* Medium Teal: 3.4:1 against white and 4.5:1 against the slate header — it
     clears the 3:1 WCAG 2.2 focus-appearance threshold on both. */
  --focus-ring: var(--brand-teal);
  /* --surface is white here, so the preview banner hover wash is white. */
  --surface-wash: rgba(255, 255, 255, 0.15);

  /* The sandboxed document frame. Here the problem is the opposite: a white
     document on a near-white canvas has almost no edge of its own, so the
     line has to be visible and the document gets a soft lift. */
  --frame-line: var(--hairline-hover);
  --frame-rim:  0 8px 24px -10px rgba(21, 26, 32, 0.22);

  --shadow-pop: 0 10px 25px -5px rgba(21, 26, 32, 0.12);
  --backdrop:   rgba(21, 26, 32, 0.48);
}

/* ---------------------------------------------------------------------------
   4. Console, dark. Light and dark are both available to an admin (D6), so
   surface and theme are two independent axes: surface is the role and is not
   a preference, theme is the preference and applies only to the console. A
   viewer is always on the studio skin; a viewer has no toggle.

   The values are the studio skin's, so there is no third palette to keep in
   sync — but the studio *surface language* (the gradient canvas, the teal
   eyebrow, the 14px radius) is not reused. This is the console in the dark,
   not the Studio: the console is a dense tool and keeps its own geometry.

   Written twice on purpose. The attribute arm is the explicit choice; the
   media arm is the default for an admin who has never touched the toggle,
   and it has to work with JavaScript off, so it cannot be collapsed into the
   first. Only the mapping is duplicated — every value is a brand constant
   declared once in layer 1.
   ------------------------------------------------------------------------ */

[data-surface="console"][data-theme="dark"] {
  color-scheme: dark;

  --bg:             var(--studio-canvas);
  --surface:        var(--studio-surface);
  --surface-sunk:   var(--studio-sunk);
  --surface-raised: var(--studio-raised);

  --ink:            var(--studio-paper);   /* 17.1:1 on --bg */
  --ink-muted:      var(--studio-ink-3);   /* 10.5:1 on --bg */

  --hairline:       var(--studio-line);
  --hairline-hover: var(--studio-line-hi);

  --primary:         var(--brand-cyan);
  --primary-hover:   var(--studio-teal-hi);
  --on-primary:      var(--brand-on-teal);
  --primary-wash:    rgba(34, 214, 192, 0.10);
  --on-primary-wash: rgba(0, 17, 19, 0.18);
  --accent-bright:   var(--brand-cyan);

  /* Slate is nearly invisible against a near-black canvas, so the chrome
     becomes a raised panel carrying ordinary ink — the same inversion the
     studio skin uses. */
  --chrome:            var(--studio-raised);
  --on-chrome:         var(--studio-paper);
  --on-chrome-muted:   var(--studio-ink-3);
  --on-chrome-accent:  var(--brand-cyan);
  --chrome-line:       rgba(244, 242, 239, 0.10);
  --chrome-hover:      rgba(244, 242, 239, 0.08);

  --danger:       var(--brand-critical);   /* 6.2:1 */
  --danger-wash:  rgba(255, 77, 77, 0.14);
  --success:      var(--brand-success);    /* 8.9:1 */
  --success-wash: rgba(34, 197, 94, 0.14);
  --warning:      var(--brand-warning);    /* 12.3:1 */
  --warning-wash: rgba(255, 176, 32, 0.14);
  --info:         #60a5fa;                 /* 7.9:1 */
  --info-wash:    rgba(59, 130, 246, 0.14);

  --danger-solid: #b3261e;
  --on-danger:    #ffffff;

  --focus-ring:   var(--brand-cyan);
  --surface-wash: rgba(2, 7, 12, 0.16);

  --shadow-pop: 0 20px 50px -12px rgba(0, 0, 0, 0.75);
  --backdrop:   rgba(0, 0, 0, 0.68);
}

@media (prefers-color-scheme: dark) {
  [data-surface="console"]:not([data-theme="light"]) {
    color-scheme: dark;

    --bg:             var(--studio-canvas);
    --surface:        var(--studio-surface);
    --surface-sunk:   var(--studio-sunk);
    --surface-raised: var(--studio-raised);

    --ink:            var(--studio-paper);
    --ink-muted:      var(--studio-ink-3);

    --hairline:       var(--studio-line);
    --hairline-hover: var(--studio-line-hi);

    --primary:         var(--brand-cyan);
    --primary-hover:   var(--studio-teal-hi);
    --on-primary:      var(--brand-on-teal);
    --primary-wash:    rgba(34, 214, 192, 0.10);
    --on-primary-wash: rgba(0, 17, 19, 0.18);
    --accent-bright:   var(--brand-cyan);

    --chrome:            var(--studio-raised);
    --on-chrome:         var(--studio-paper);
    --on-chrome-muted:   var(--studio-ink-3);
    --on-chrome-accent:  var(--brand-cyan);
    --chrome-line:       rgba(244, 242, 239, 0.10);
    --chrome-hover:      rgba(244, 242, 239, 0.08);

    --danger:       var(--brand-critical);
    --danger-wash:  rgba(255, 77, 77, 0.14);
    --success:      var(--brand-success);
    --success-wash: rgba(34, 197, 94, 0.14);
    --warning:      var(--brand-warning);
    --warning-wash: rgba(255, 176, 32, 0.14);
    --info:         #60a5fa;
    --info-wash:    rgba(59, 130, 246, 0.14);

    --danger-solid: #b3261e;
    --on-danger:    #ffffff;

    --focus-ring:   var(--brand-cyan);
    --surface-wash: rgba(2, 7, 12, 0.16);

    --frame-line: var(--studio-line-2);
    --frame-rim:  0 0 0 1px rgba(34, 214, 192, 0.08);

    --shadow-pop: 0 20px 50px -12px rgba(0, 0, 0, 0.75);
    --backdrop:   rgba(0, 0, 0, 0.68);
  }
}

* { box-sizing: border-box; }

/* The `hidden` attribute must always win, even when a later class rule sets
   `display` on the same element (e.g. the page-view files drawer). The
   browser's own [hidden] rule is only specificity (0,1,0) and loses to any
   equal-specificity author rule that comes after it. */
[hidden] { display: none !important; }

body {
  margin: 0;
  font-family: var(--font-ui);
  font-size: 16px;
  color: var(--ink);
  background: var(--bg);
  line-height: 1.5;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2 {
  font-family: var(--font-display);
  color: var(--ink);
  line-height: 1.2;
  text-wrap: balance;
}
h1 { font-size: 2rem; font-weight: 600; letter-spacing: -0.01em; margin: 0 0 var(--sp-4); }
h2 { font-size: 1.5rem; font-weight: 600; }

a { color: var(--primary); }

:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: 2px;
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ============================================================================
   Primitives
   ========================================================================== */

.icon { display: inline-block; vertical-align: middle; flex-shrink: 0; }

/* --- Buttons ------------------------------------------------------------- */

.btn-primary,
.btn-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: 0.5rem 1rem;
  border-radius: var(--r-control);
  border: 1px solid transparent;
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.01em;
  cursor: pointer;
  text-decoration: none;
  transition: background-color 0.15s, border-color 0.15s, color 0.15s;
}
.btn-primary { background: var(--primary); color: var(--on-primary); }
.btn-primary:hover { background: var(--primary-hover); }
.btn-secondary { background: var(--surface); color: var(--ink); border-color: var(--hairline); }
.btn-secondary:hover { border-color: var(--hairline-hover); }
.btn-primary[disabled], .btn-secondary[disabled] { opacity: 0.55; cursor: not-allowed; }

/* Link that behaves like a button (JS-wired actions). */
.link-button {
  background: none;
  border: none;
  padding: 0;
  color: var(--primary);
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  text-decoration: none;
}
.link-button:hover { text-decoration: underline; text-underline-offset: 2px; }
.link-button-danger { color: var(--danger); }

/* Compact icon-only affordance. */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border: 1px solid transparent;
  border-radius: var(--r-control);
  background: none;
  color: var(--ink-muted);
  cursor: pointer;
  transition: color 0.12s, border-color 0.12s, background-color 0.12s;
}
.icon-btn:hover { color: var(--ink); border-color: var(--hairline); background: var(--surface-sunk); }

/* --- Inputs ------------------------------------------------------------- */

input[type="text"],
input[type="email"],
input[type="password"],
input[type="search"],
input[type="url"],
input[type="number"],
select,
textarea {
  font: inherit;
  font-size: 0.9375rem;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-control);
  padding: 0.5rem 0.7rem;
  transition: border-color 0.15s;
}
input:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--primary);
}
/* No extra fade. --ink-muted at 0.7 is 3.1:1 on the console — placeholder
   text is text, and it is already muted relative to what gets typed into the
   field (--ink). Firefox applies its own default placeholder opacity, which
   `opacity: 1` also has to override. */
input::placeholder { color: var(--ink-muted); opacity: 1; }
/* A folder picker (TODO-ui-design-walkthrough.md) is the first <select> in
   the app — otherwise unstyled, it would look out of place next to every
   text input around it. */
select { cursor: pointer; }

/* --- Status chips ----------------------------------------------------------
   Small uppercase labels with a ~10% tint fill, no border (DESIGN.md). */

.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.15rem 0.5rem;
  border-radius: var(--r-pill);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  background: var(--surface-raised);
  color: var(--ink-muted);
  white-space: nowrap;
}
.chip--success { background: var(--success-wash); color: var(--success); }
.chip--warning { background: var(--warning-wash); color: var(--warning); }
.chip--danger  { background: var(--danger-wash);  color: var(--danger); }
.chip--info    { background: var(--info-wash);    color: var(--info); }

/* --- Avatar (initials) ------------------------------------------------- */

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  flex-shrink: 0;
  border-radius: var(--r-pill);
  background: var(--primary-wash);
  color: var(--primary);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

/* --- Card ------------------------------------------------------------- */

.card {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  padding: var(--sp-5);
}

/* Section heading with a leading icon (folder browser, access panel). */
.section-heading {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: var(--sp-6) 0 var(--sp-4);
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--ink);
}
.section-heading:first-child { margin-top: 0; }
.section-heading .icon { color: var(--ink-muted); }

/* ============================================================================
   App shell — header, main, flashes  (base.html)
   ========================================================================== */

.site-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--sp-4);
  padding: 0 var(--sp-6);
  height: 60px;
  background: var(--surface);
  border-bottom: 1px solid var(--hairline);
  position: sticky;
  top: 0;
  z-index: 100;
  flex-shrink: 0;
}

.site-header__lead { display: flex; align-items: center; gap: var(--sp-6); min-width: 0; }

.site-title {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--primary);
  text-decoration: none;
  white-space: nowrap;
}

.site-nav { display: flex; gap: var(--sp-5); }
.site-nav a {
  display: inline-flex;
  align-items: center;
  height: 60px;
  color: var(--ink-muted);
  font-size: 0.875rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-decoration: none;
  border-bottom: 2px solid transparent;
  transition: color 0.15s, border-color 0.15s;
}
.site-nav a:hover { color: var(--ink); }
.site-nav a[aria-current="page"] { color: var(--primary); border-bottom-color: var(--primary); }

.site-account { display: flex; align-items: center; gap: var(--sp-4); min-width: 0; }
.site-account__who {
  font-size: 0.8125rem;
  color: var(--ink-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 22ch;
}
.site-account__out {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--ink-muted);
  font-size: 0.8125rem;
  font-weight: 600;
  text-decoration: none;
}
.site-account__out:hover { color: var(--primary); }

@media (max-width: 640px) {
  .site-header { height: auto; padding: var(--sp-3) var(--sp-4); flex-wrap: wrap; }
  .site-header__lead { gap: var(--sp-3); flex-wrap: wrap; }
  .site-nav { gap: var(--sp-3); flex-wrap: wrap; }
  .site-nav a { height: auto; }
  .site-account__who { display: none; }
}

.site-main {
  width: 100%;
  max-width: 960px;
  margin: 0 auto;
  padding: var(--sp-6) var(--sp-5);
}
.site-main-wide { max-width: var(--container-max); }

.flashes { list-style: none; margin: 0; padding: var(--sp-3) var(--sp-6) 0; }
.flash {
  padding: 0.6rem 0.85rem;
  border-radius: var(--r-control);
  margin-bottom: var(--sp-2);
  font-size: 0.875rem;
}
.flash-success { background: var(--success-wash); color: var(--success); }
.flash-error   { background: var(--danger-wash);  color: var(--danger); }
.flash-info    { background: var(--info-wash);    color: var(--info); }
.flash-warning { background: var(--warning-wash); color: var(--warning); }

/* ============================================================================
   Breadcrumbs
   ========================================================================== */

.breadcrumbs {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: var(--sp-3);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--ink-muted);
}
.breadcrumbs a { color: var(--ink-muted); text-decoration: none; }
.breadcrumbs a:hover { color: var(--primary); }
.breadcrumbs > span:last-child,
.breadcrumbs > a:last-child { color: var(--ink); }

.breadcrumb-sep {
  font-size: 0;
  margin: 0 0.35rem;
  line-height: 0;
}
.breadcrumb-sep::after {
  content: "";
  display: inline-block;
  width: 13px;
  height: 13px;
  vertical-align: -2px;
  background-color: var(--ink-muted);
  opacity: 0.7;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* ============================================================================
   Folder browser  (folder_view.html)
   ========================================================================== */

.folder-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-bottom: var(--sp-6);
}
.folder-header h1 { margin: 0; }
.folder-actions { display: flex; align-items: center; gap: var(--sp-3); }

.new-folder-form {
  display: flex;
  gap: var(--sp-2);
  align-items: center;
  margin-bottom: var(--sp-5);
}
.new-folder-form input[type="text"] { flex: 1; max-width: 280px; }

/* Access-control card (was .permissions-panel). */
.permissions-panel {
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  padding: var(--sp-5);
  margin-bottom: var(--sp-6);
  background: var(--surface);
}
.permissions-panel h2 {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--font-ui);
  font-size: 0.875rem;
  font-weight: 600;
  margin: 0 0 var(--sp-1);
  padding-bottom: var(--sp-3);
  border-bottom: 1px solid var(--hairline);
}
.permissions-panel h2 .icon { color: var(--ink-muted); }
.permissions-hint { color: var(--ink-muted); font-size: 0.8125rem; margin: var(--sp-3) 0 var(--sp-4); }

.grants-list { list-style: none; margin: 0 0 var(--sp-4); padding: 0; }
.grants-list li {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  font-size: 0.875rem;
  border-bottom: 1px solid var(--hairline);
}
.grants-list li:last-child { border-bottom: none; }

.grant-form { display: flex; gap: var(--sp-2); }
.grant-form input[type="email"] { flex: 1; max-width: 280px; }

/* The email-preset picker (admin_email_templates.html). These rows are
   <button> elements that had no rule of their own, so they fell through to
   the UA's ButtonFace: a light grey in the light console, where it passed
   unnoticed, and a mid grey under `color-scheme: dark` that put the row's
   meta line at 3.0:1 and its chip at 2.1:1. Pre-existing; only the dark
   console (D6) made it visible. */
.email-type-btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  width: 100%;
  padding: var(--sp-3);
  border: 1px solid transparent;
  border-radius: var(--r-card);
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color 0.12s, border-color 0.12s;
}
.email-type-btn:hover {
  background: var(--surface-sunk);
  border-color: var(--hairline);
}
.email-type-btn.is-selected {
  background: var(--primary-wash);
  border-color: var(--primary);
}

/* People rows (admin_people.html) — avatar · name/meta · chip + actions,
   now wrapped in a <details>/<summary> (TODO-stacey note 23) so each row can
   expand into a "can see" / "checked out" panel. The grid that used to live
   on `li.people-row` itself now lives on the <summary>; the <li> is just a
   block-level wrapper around the whole disclosure. Selector carries an
   extra class so it beats the `.grants-list li` base. */
.grants-list li.people-row { display: block; padding: 0; }
.people-row__details { padding: var(--sp-3) 0; }
.people-row__summary {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--sp-3);
  align-items: center;
  cursor: pointer;
  list-style: none;
}
.people-row__summary::-webkit-details-marker { display: none; }
.people-row__body { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.people-row__name {
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--ink);
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.people-row__meta { font-size: 0.75rem; color: var(--ink-muted); }
.people-row__you {
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-muted);
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
  padding: 0 0.4rem;
}
.people-row__end { display: flex; align-items: center; gap: var(--sp-3); }
@media (max-width: 560px) {
  .people-row__summary { grid-template-columns: auto minmax(0, 1fr); }
  .people-row__end { grid-column: 2; justify-content: flex-start; flex-wrap: wrap; }
}

/* The "can see" / "checked out" disclosure panel (TODO-stacey note 23). */
.people-row__panel {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-4);
  margin-top: var(--sp-3);
  padding: var(--sp-3) 0 0 calc(2.25rem + var(--sp-3)); /* aligns under the name, past the avatar */
}
@media (max-width: 560px) {
  .people-row__panel { grid-template-columns: 1fr; padding-left: 0; }
}
.people-row__panel-heading {
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-muted);
  margin: 0 0 var(--sp-2);
}
.people-row__panel-empty { font-size: 0.8125rem; color: var(--ink-muted); margin: 0; }
.people-access-list { list-style: none; margin: 0; padding: 0; font-size: 0.8125rem; }
.people-access-list li { padding: 0.2rem 0; }
.people-access-path { color: var(--ink-muted); margin-right: 0.25rem; }

/* Subfolder tiles. */
.tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--sp-3);
  margin-bottom: var(--sp-6);
}
.folder-tile-wrap { position: relative; }
.tile,
.folder-tile {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  background: var(--surface);
  text-decoration: none;
  color: var(--ink);
  transition: border-color 0.12s;
}
.tile:hover,
.folder-tile:hover { border-color: var(--hairline-hover); }
.tile-icon { color: var(--primary); opacity: 0.75; }
.tile-name { font-size: 0.875rem; font-weight: 600; overflow-wrap: anywhere; }

.tile-delete-btn {
  position: absolute;
  top: var(--sp-2);
  right: var(--sp-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  line-height: 1;
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink-muted);
  cursor: pointer;
  font-size: 0.9rem;
  opacity: 0;
  transition: opacity 0.1s, color 0.12s, border-color 0.12s;
}
.folder-tile-wrap:hover .tile-delete-btn,
.tile-delete-btn:focus-visible { opacity: 1; }
.tile-delete-btn:hover { color: var(--danger); border-color: var(--danger); }

/* Same treatment as .tile-delete-btn, offset left of it (TODO-stacey note
   20) so a folder tile can show both toggles without overlapping. */
.tile-public-btn {
  position: absolute;
  top: var(--sp-2);
  right: calc(var(--sp-2) + 1.5rem + var(--sp-1));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  line-height: 1;
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink-muted);
  cursor: pointer;
  font-size: 0.9rem;
  opacity: 0;
  transition: opacity 0.1s, color 0.12s, border-color 0.12s;
}
.folder-tile-wrap:hover .tile-public-btn,
.tile-public-btn:focus-visible { opacity: 1; }
.tile-public-btn:hover { color: var(--primary); border-color: var(--primary); }

/* Same treatment again, offset a further step left (TODO-stacey note 27) so
   a folder tile can show public + archive + delete without overlapping. */
.tile-archive-btn {
  position: absolute;
  top: var(--sp-2);
  right: calc(var(--sp-2) + 3rem + calc(var(--sp-1) * 2));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  line-height: 1;
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink-muted);
  cursor: pointer;
  font-size: 0.9rem;
  opacity: 0;
  transition: opacity 0.1s, color 0.12s, border-color 0.12s;
}
.folder-tile-wrap:hover .tile-archive-btn,
.tile-archive-btn:focus-visible { opacity: 1; }
.tile-archive-btn:hover { color: var(--ink); border-color: var(--ink-muted); }

/* Dropzone. */
.dropzone {
  border: 2px dashed var(--hairline);
  border-radius: var(--r-card);
  padding: var(--sp-6);
  text-align: center;
  color: var(--ink-muted);
  background: var(--surface);
  margin-bottom: var(--sp-6);
  transition: border-color 0.15s, background 0.15s;
}
.dropzone p { margin: 0.25rem 0; }
.dropzone p strong { color: var(--ink); }
.dropzone-hint { font-size: 0.8125rem; }
.dropzone-active { border-color: var(--primary); background: var(--primary-wash); color: var(--primary); }

.upload-status { list-style: none; margin: 0 0 var(--sp-4); padding: 0; }
.upload-status-line {
  padding: 0.5rem 0.7rem;
  border-radius: var(--r-control);
  font-size: 0.8125rem;
  margin-bottom: var(--sp-2);
}
.upload-status-pending { background: var(--surface-sunk); color: var(--ink-muted); }
.upload-status-success { background: var(--success-wash); color: var(--success); }
.upload-status-error   { background: var(--danger-wash);  color: var(--danger); }

/* Document / record cards. */
.page-list { list-style: none; margin: 0; padding: 0; }
.page-card {
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  padding: var(--sp-4);
  margin-bottom: var(--sp-3);
  background: var(--surface);
  transition: border-color 0.12s;
}
.page-card:hover { border-color: var(--hairline-hover); }
.page-card > a { text-decoration: none; color: var(--ink); }
.page-card h2 {
  font-family: var(--font-ui);
  font-size: 1rem;
  font-weight: 600;
  margin: 0 0 0.15rem;
}
.page-card > p { margin: 0.15rem 0; font-size: 0.875rem; color: var(--ink-muted); }
.page-meta {
  color: var(--ink-muted);
  font-size: 0.8125rem;
  margin: 0;
  /* The uploaded filename lives in the `title` tooltip here, not printed —
     TODO-ui-design-walkthrough.md. It's still one hover away, and always on
     the Manage page. */
  cursor: default;
}

/* Card meta row: the "Updated …" line on the left, admin actions on the
   right (TODO-ui-design-walkthrough.md — replaces the old single middot-
   chained line that put "Delete" one misclick from "Make public"). */
.page-card__meta-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: 0.35rem;
}
.page-card__actions { display: flex; align-items: center; gap: var(--sp-1); flex-shrink: 0; }

/* A "link"-kind folder entry (TODO-stacey note 34) — same card shell, just
   the heading icon needs inline spacing page_card's plain-text h2 doesn't. */
.page-card--link h2 { display: flex; align-items: center; gap: 0.4rem; }
.page-card--link h2 .icon { color: var(--ink-muted); flex-shrink: 0; }

.icon-btn--active { color: var(--primary); }
.icon--filled { fill: currentColor; }

/* "More actions" overflow menu — a native <details>/<summary> disclosure,
   so opening/closing needs no JS. static/js/upload.js's initCardMenus()
   only adds "close the others" / "close on outside click" on top. */
.card-menu { position: relative; }
.card-menu > summary {
  list-style: none;
  -webkit-user-select: none;
  user-select: none;
}
.card-menu > summary::-webkit-details-marker,
.card-menu > summary::marker { display: none; content: ""; }
.card-menu[open] > summary { color: var(--ink); border-color: var(--hairline); background: var(--surface-sunk); }

.card-menu__list {
  list-style: none;
  position: absolute;
  z-index: 20;
  top: calc(100% + 4px);
  right: 0;
  min-width: 180px;
  margin: 0;
  padding: var(--sp-1);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-pop);
}
.card-menu__item {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  padding: 0.45rem 0.6rem;
  border: none;
  border-radius: var(--r-control);
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: 0.8125rem;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.card-menu__item:hover, .card-menu__item:focus-visible { background: var(--surface-sunk); outline: none; }
.card-menu__item .icon { color: var(--ink-muted); flex-shrink: 0; }
.card-menu__item--danger { color: var(--danger); }
.card-menu__item--danger:hover, .card-menu__item--danger:focus-visible { background: var(--danger-wash); }
.card-menu__item--danger .icon { color: inherit; }
.card-menu__divider { height: 1px; margin: var(--sp-1) 0.6rem; background: var(--hairline); }

.empty-state {
  color: var(--ink-muted);
  padding: var(--sp-5);
  text-align: center;
}

/* Shared error / not-found layout (templates/errors/*). */
.error-page {
  max-width: 26rem;
  margin: clamp(var(--sp-6), 12vh, 8rem) auto;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
}
.error-page__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  border-radius: var(--r-pill);
  background: var(--surface-sunk);
  color: var(--ink-muted);
}
.error-page h1 { margin: 0; }
.error-page p { margin: 0; color: var(--ink-muted); }
.error-page .btn-secondary { margin-top: var(--sp-3); }

/* ============================================================================
   Forms — login, advanced upload, invite
   ========================================================================== */

.upload-form { display: flex; flex-direction: column; gap: var(--sp-4); max-width: 480px; }
.upload-form label {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--ink);
}
.upload-form input[type="text"],
.upload-form input[type="email"],
.upload-form input[type="password"],
.upload-form input[type="file"] { font-weight: 400; }

.dev-login-note {
  background: var(--surface-sunk);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  padding: 0.75rem 1rem;
  font-size: 0.8125rem;
  color: var(--ink-muted);
}
.dev-login-hint { color: var(--ink-muted); font-size: 0.8125rem; font-weight: 400; }
.auth-card .dev-login-hint { text-align: center; }
.btn-block { width: 100%; }

/* --- Auth screens: login, finish-signin ------------------------------- */

.auth-shell {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  max-width: none;
  padding: var(--sp-6) var(--sp-4);
}
.auth-card {
  width: 100%;
  max-width: 400px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  padding: var(--sp-6);
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}
.auth-brand { text-align: center; }
.auth-brand__name {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--primary);
}
.auth-brand__tag {
  margin: 0.35rem 0 0;
  font-size: 0.8125rem;
  color: var(--ink-muted);
}

.auth-form { display: flex; flex-direction: column; gap: var(--sp-4); }
.auth-form label,
.auth-form__field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--ink);
}
.auth-form__field > label,
.auth-form__labelrow label { display: block; }
.auth-form input { width: 100%; }
.auth-form__labelrow { display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-3); }
.auth-form__labelrow a { font-weight: 500; }

.auth-divider {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  color: var(--ink-muted);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.auth-divider::before,
.auth-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--hairline);
}

.auth-google { flex-shrink: 0; }

.auth-note {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  margin: 0;
  font-size: 0.75rem;
  color: var(--ink-muted);
}
.auth-note .icon { color: var(--ink-muted); }

.auth-status {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 0.75rem 0.9rem;
  border-radius: var(--r-control);
  background: var(--surface-sunk);
  color: var(--ink-muted);
  font-size: 0.875rem;
}
.auth-spinner {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  border: 2px solid var(--hairline-hover);
  border-top-color: var(--primary);
  border-radius: 50%;
  animation: auth-spin 0.7s linear infinite;
}
@keyframes auth-spin { to { transform: rotate(360deg); } }

/* ============================================================================
   Page view  (page_view.html + _page_frame.html) — restyled further in Phase 3
   ========================================================================== */

body.page-fullscreen { overflow: hidden; background: var(--bg); }

/* One fixed-position header holding both rows below — the bar always, the
   breadcrumb (TODO-ui-design-walkthrough.md #19) only when the page sits in
   a folder. `--page-header-h` (set on <body> by page_view.html) is how
   .page-fullscreen__frame below knows how much room to leave, whichever
   rows are actually present. */
.page-fullscreen__header {
  position: fixed;
  /* Sits below the preview banner (TODO-stacey note 22) when one is shown;
     --preview-banner-h is 0px otherwise, so this is a no-op normally. */
  top: var(--preview-banner-h, 0px);
  left: 0;
  right: 0;
  z-index: 2147483000;
  background: var(--surface);
  border-bottom: 1px solid var(--hairline);
}

/* Admin "View as" preview banner (TODO-stacey note 22). The base (non-fixed)
   rule works as-is on every shell page (base.html just includes it in
   normal flow, above <main>); .preview-banner--fixed is added only on
   page_view.html, whose own header is itself position:fixed and would
   otherwise render right underneath a static banner instead of being
   pushed down by it. */
.preview-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  height: 32px;
  padding: 0 var(--sp-3);
  background: var(--ink);
  color: var(--surface);
  font-size: 0.8125rem;
}
.preview-banner--fixed {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 2147483001;
}
.preview-banner__text strong { font-weight: 700; }
.preview-banner__exit {
  flex-shrink: 0;
  border: 1px solid var(--surface);
  background: transparent;
  color: var(--surface);
  border-radius: var(--r-control);
  padding: 0.2rem 0.7rem;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.preview-banner__exit:hover { background: var(--surface-wash); }

.page-fullscreen__bar {
  height: 44px;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 0 var(--sp-3);
}

.page-fullscreen__title {
  flex: 1;
  min-width: 0;
  text-align: center;
  font-family: var(--font-display);
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.page-fullscreen__actions { display: flex; align-items: center; gap: var(--sp-2); }
.page-actions-toggle { display: none; }

/* Real "you are here" trail (TODO-ui-design-walkthrough.md #19 / D16) — a
   viewer on a page three folders deep previously had no way to tell where
   it sat, or to step up one level; `← Back` always jumped all the way to
   /library. Every crumb here is a genuine link: the admin folder browser
   for an admin, `/library#folder-<id>` for a viewer (static/js/sidebar.js
   expands that folder — and every ancestor down to it — and scrolls it
   into view on load). */
.page-fullscreen__crumbs {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0 var(--sp-3) 0.5rem;
  font-size: 0.75rem;
  color: var(--ink-muted);
  overflow-x: auto;
  white-space: nowrap;
}
.page-fullscreen__crumbs a { color: var(--ink-muted); text-decoration: none; }
.page-fullscreen__crumbs a:hover { color: var(--primary); text-decoration: underline; }
/* `.page-fullscreen__crumb-sep` deliberately has NO rule. It used to carry
   `opacity: 0.6`, which put it at 2.6:1 on the console and 4.47:1 on studio:
   it inherits --ink-muted, which is already near the AA floor, so anything
   dimmer than the crumbs themselves fails. It reads as a separator because
   of what it is, not because it is quieter. */

/* This element IS the sandboxed <iframe>. Everything here is on the element —
   border, radius, rim — and none of it reaches the document inside, which
   keeps every colour it shipped with. The radius clips the document's corners
   because a replaced element is painted inside its border box.

   The border has opposite jobs on the two grounds, so it is a token rather
   than a fixed value and follows the console's light/dark toggle with
   everything else: against a near-white canvas a white document has almost no
   edge of its own and needs a visible line plus a soft lift; against a
   near-black one it is already the strongest edge on the screen, and what it
   needs is a rim that reads as a mount rather than a cut-out. */
.page-fullscreen__frame {
  position: fixed;
  top: var(--page-header-h, 44px);
  left: 10px;
  width: calc(100vw - 20px);
  height: calc(100vh - var(--page-header-h, 44px) - 10px);
  border: 1px solid var(--frame-line);
  border-radius: var(--r-card);
  background: var(--surface);
  box-shadow: var(--frame-rim);
}

.page-fullscreen__back,
.page-fullscreen__responses,
.page-fullscreen__delete {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.35rem 0.8rem;
  border-radius: var(--r-control);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
}
.page-fullscreen__back {
  color: var(--ink-muted);
  background: none;
  border: 1px solid transparent;
}
.page-fullscreen__back:hover { color: var(--primary); }
.page-fullscreen__home {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.35rem;
  border-radius: var(--r-control);
  color: var(--ink-muted);
  background: none;
  border: 1px solid transparent;
  text-decoration: none;
  cursor: pointer;
}
.page-fullscreen__home:hover { color: var(--primary); }
.page-fullscreen__nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.35rem;
  border-radius: var(--r-control);
  color: var(--ink-muted);
  background: none;
  border: 1px solid transparent;
  cursor: pointer;
}
.page-fullscreen__nav-toggle:hover { color: var(--primary); }
.page-fullscreen__nav-toggle[aria-expanded="true"] {
  color: var(--on-primary);
  background: var(--primary);
  border-color: var(--primary);
}
.page-fullscreen__responses {
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--hairline);
}
.page-fullscreen__responses:hover { border-color: var(--primary); }
.page-fullscreen__delete {
  color: var(--danger);
  background: var(--surface);
  border: 1px solid var(--hairline);
}
.page-fullscreen__delete:hover { border-color: var(--danger); }

@media (max-width: 800px) {
  .page-fullscreen__bar {
    gap: var(--sp-2);
    padding: 0 var(--sp-2);
  }
  .page-fullscreen__title {
    text-align: left;
    font-size: 0.875rem;
  }
  .page-fullscreen__back {
    padding-inline: 0.45rem;
    white-space: nowrap;
  }
  .page-actions-toggle {
    display: inline-flex;
    align-items: center;
    padding: 0.35rem 0.6rem;
    border: 1px solid var(--hairline);
    border-radius: var(--r-control);
    background: var(--surface);
    color: var(--ink);
    font: inherit;
    font-size: 0.8125rem;
    font-weight: 600;
    cursor: pointer;
  }
  .page-actions-toggle[aria-expanded="true"] {
    color: var(--on-primary);
    background: var(--primary);
    border-color: var(--primary);
  }
  .page-fullscreen__actions {
    display: none;
    position: fixed;
    z-index: 2147483002;
    top: calc(var(--page-header-h, 44px) + var(--sp-2));
    right: var(--sp-2);
    width: min(18rem, calc(100vw - 2 * var(--sp-2)));
    max-height: calc(100dvh - var(--page-header-h, 44px) - 2 * var(--sp-2));
    overflow-y: auto;
    flex-direction: column;
    align-items: stretch;
    padding: var(--sp-2);
    border: 1px solid var(--hairline);
    border-radius: var(--r-card);
    background: var(--surface);
    box-shadow: var(--shadow-pop);
  }
  .page-fullscreen__actions--open { display: flex; }
  .page-fullscreen__actions > a,
  .page-fullscreen__actions > button {
    justify-content: flex-start;
    width: 100%;
    min-height: 2.5rem;
  }
  .page-fullscreen__delete {
    margin-top: var(--sp-1);
    border-top-color: var(--danger);
  }
}

/* "Respond" — app-owned feedback/approval control (TODO-stacey note 17). A
   filled button so it reads as the primary action available to a viewer on
   this screen. */
.page-fullscreen__respond {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.35rem 0.9rem;
  border-radius: var(--r-control);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1;
  color: var(--on-primary);
  background: var(--primary);
  border: 1px solid var(--primary);
  cursor: pointer;
}
.page-fullscreen__respond:hover { background: var(--primary-hover); border-color: var(--primary-hover); }

/* The Respond panel reuses the shared .modal shell (see ui.js / confirmDialog)
   for its frame, backdrop and actions row; these rules only add the form
   controls it needs on top. */
.page-respond { width: min(32rem, calc(100vw - 2 * var(--sp-4))); }
.page-respond__choices {
  margin: 0 0 var(--sp-3);
  padding: 0;
  border: none;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.page-respond__choices legend {
  padding: 0;
  margin-bottom: var(--sp-2);
  font-weight: 600;
  font-size: 0.8125rem;
  color: var(--ink-muted);
}
.page-respond__choices label {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: 0.9375rem;
}
.page-respond__field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--ink-muted);
}
.page-respond__field textarea {
  font: inherit;
  font-weight: 400;
  resize: vertical;
  min-height: 5rem;
}
.page-respond__hint { font-weight: 400; }
.page-respond__error, .modal__error {
  margin: var(--sp-2) 0 0;
  font-size: 0.8125rem;
  color: var(--danger);
}

.page-fullscreen__files {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0.8rem;
  border-radius: var(--r-control);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--hairline);
  cursor: pointer;
}
.page-fullscreen__files:hover { border-color: var(--primary); }
.page-fullscreen__files[aria-expanded="true"] {
  color: var(--on-primary);
  background: var(--primary);
  border-color: var(--primary);
}
.page-fullscreen__files-count {
  font-size: 0.6875rem;
  padding: 0.1rem 0.35rem;
  border-radius: var(--r-pill);
  background: var(--primary-wash);
  color: var(--primary);
}
/* When the button is open it fills with --primary, and the count pill has to
   sit on top of that fill. A translucent wash of --on-primary was 3.6:1 on the
   console: the wash lightens Deep Teal far enough that white stops working on
   it, and no alpha both stays visible as a pill and keeps 4.5:1. Inverting the
   pill solves it by construction on either skin — the two colours are already
   a checked pair, just swapped. */
.page-fullscreen__files[aria-expanded="true"] .page-fullscreen__files-count {
  background: var(--on-primary);
  color: var(--primary);
}

/* Supporting-files drawer — an overlay pinned to the right edge of the
   framed area (TODO-stacey note 9). It sits ON TOP of the iframe rather
   than resizing it, so the embedded page's own layout never reflows. */
.page-files-drawer {
  position: fixed;
  top: var(--page-header-h, 44px);
  right: 10px;
  bottom: 10px;
  width: min(320px, calc(100vw - 20px));
  z-index: 2147483001;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-pop);
  overflow-y: auto;
}
.page-files-drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.page-files-drawer__head h2 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1rem;
}
.page-files-drawer__close {
  border: none;
  background: none;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  color: var(--ink-muted);
}
.page-files-drawer__list { list-style: none; margin: 0; padding: 0; }
.page-file {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: var(--sp-2) 0;
  border-top: 1px solid var(--hairline);
  font-size: 0.8125rem;
}
.page-file__name { font-weight: 600; color: var(--ink); }
.page-file__badge {
  margin-left: 0.35rem;
  font-size: 0.65rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 0.05rem 0.3rem;
  border-radius: var(--r-control);
  background: var(--warning-wash);
  color: var(--warning);
}
.page-file__desc, .page-file__meta { color: var(--ink-muted); }
.page-file__actions { display: flex; gap: var(--sp-3); margin-top: 0.15rem; }
.page-file__action { color: var(--primary); font-weight: 600; text-decoration: none; }
.page-file__action:hover { text-decoration: underline; }
.page-files-drawer__all {
  margin-top: auto;
  padding-top: var(--sp-2);
  border-top: 1px solid var(--hairline);
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--primary);
  text-decoration: none;
}
.page-files-drawer__all:hover { text-decoration: underline; }

/* Left-edge nav drawer (TODO-stacey note 28) — same overlay treatment as the
   supporting-files drawer above, mirrored to the left edge so it never
   collides with that drawer (right) or the video pop-out (bottom-left). Wraps
   the same _sidebar.html partial the shell screens use; the nested overrides
   below strip .sidebar's own fixed-column styling (width/border/background)
   since this drawer already supplies its own. */
.page-nav-drawer {
  position: fixed;
  top: var(--page-header-h, 44px);
  left: 10px;
  bottom: 10px;
  width: min(320px, calc(100vw - 20px));
  z-index: 2147483001;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-pop);
  overflow-y: auto;
}
.page-nav-drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.page-nav-drawer__head h2 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1rem;
}
.page-nav-drawer__close {
  border: none;
  background: none;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  color: var(--ink-muted);
}
.page-nav-drawer__body { min-height: 0; }
.page-nav-drawer .sidebar {
  position: static;
  top: auto;
  width: auto;
  height: auto;
  padding: 0;
  border-right: none;
  background: none;
  overflow: visible;
}

/* Walkthrough-video pop-out (TODO-stacey note 10) — a compact overlay pinned
   to the BOTTOM-LEFT of the framed area, so it never overlaps the
   full-height files drawer on the right. Same "overlay, don't resize the
   iframe" rule, so the embedded page never reflows. */
.page-video {
  position: fixed;
  left: 10px;
  bottom: 10px;
  width: min(380px, calc(100vw - 20px));
  z-index: 2147483001;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-pop);
  overflow: hidden;
}
.page-video__head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 0.35rem 0.35rem 0.35rem 0.7rem;
  border-bottom: 1px solid var(--hairline);
}
.page-video__title {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-display);
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.page-video__counter {
  flex: none;
  font-size: 0.75rem;
  color: var(--ink-muted);
  white-space: nowrap;
}
/* Forward/back through multiple videos on one page (TODO-stacey note 31) —
   hidden by static/js's render() whenever there's only one, so a
   single-video page looks exactly like it did before this note. */
.page-video__nav {
  flex: none;
  border: none;
  background: none;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  color: var(--ink-muted);
  padding: 0.1rem 0.2rem;
}
.page-video__nav:hover { color: var(--ink); }
.page-video__close {
  flex: none;
  border: none;
  background: none;
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
  color: var(--ink-muted);
}
.page-video__close:hover { color: var(--ink); }
.page-video__stage {
  position: relative;
  background: var(--media-letterbox);
  aspect-ratio: 16 / 9;
}
.page-video__el { width: 100%; height: 100%; display: block; }
.page-video__play {
  position: absolute;
  inset: 0;
  margin: auto;
  width: max-content;
  height: max-content;
  padding: 0.6rem 1.1rem;
  border: none;
  border-radius: var(--r-pill);
  font: inherit;
  font-weight: 600;
  color: var(--on-primary);
  background: var(--primary);
  cursor: pointer;
}

/* Image gallery lightbox (TODO-stacey note 30, reused for the Slides
   lightbox — TODO-stacey note 32) — a <dialog>, same base as .modal
   (backdrop, centering) but overridden wider/edge-to-edge since this frames
   an image instead of a form. The `display` override below MUST be scoped
   to `[open]`: an unqualified `.page-gallery { display: flex }` is an
   AUTHOR-origin rule, which always wins the cascade over the UA
   stylesheet's `dialog:not([open]) { display: none }` regardless of
   specificity — so closing the dialog (Esc, the close button, or
   `.close()`) would clear the `open` attribute but the element stayed
   visually displayed via flex, permanently covering the screen. Confirmed
   via Playwright while building the Slides viewer (note 32); the fix
   benefits the original Gallery equally. */
.page-gallery[open] {
  /* Full screen (TODO-stacey note 35). Documented as shipped on 2026-09-16,
     but the change never reached git; restored 2026-09-30 after the notes
     35-44 Playwright walk caught it. tests/test_gallery_css.py now guards
     it. The UA <dialog> max-width/max-height and the .modal margin, border,
     radius and shadow are all cleared so the image owns the screen. */
  width: 100vw;
  height: 100vh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: none;
  border-radius: 0;
  box-shadow: none;
  background: var(--surface);
  display: flex;
  align-items: center;
  justify-content: center;
}
.page-gallery__img {
  display: block;
  max-width: 100%;
  max-height: 100vh;
  object-fit: contain;
  border-radius: 0;
}
.page-gallery__close {
  position: absolute;
  top: var(--sp-3);
  right: var(--sp-3);
  border: none;
  background: none;
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  color: var(--ink-muted);
}
.page-gallery__close:hover { color: var(--ink); }
.page-gallery__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 2.5rem;
  height: 2.5rem;
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  box-shadow: var(--shadow-pop);
}
.page-gallery__nav:hover { border-color: var(--primary); color: var(--primary); }
.page-gallery__nav--prev { left: var(--sp-3); }
.page-gallery__nav--next { right: var(--sp-3); }
.page-gallery__counter {
  position: absolute;
  bottom: var(--sp-3);
  left: 50%;
  transform: translateX(-50%);
  padding: 0.15rem 0.6rem;
  border-radius: var(--r-pill);
  background: var(--scrim);
  color: var(--on-scrim);
  font-size: 0.75rem;
  font-weight: 600;
}

/* ============================================================================
   Admin: manage page  (admin_page_manage.html)
   ========================================================================== */

.manage-section {
  margin: var(--sp-5) 0;
  padding: var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
}
.manage-section h2 {
  margin-top: 0;
  font-family: var(--font-display);
  font-size: 1.125rem;
}
/* Section heading + a right-aligned action (e.g. "Download current HTML" on
   the Replace-HTML section — TODO-ui-design-walkthrough.md). */
.manage-section__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
.manage-section__head h2 { margin-bottom: 0; }
.manage-section__head .link-button { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.8125rem; }
.manage-hint { color: var(--ink-muted); font-size: 0.875rem; margin: 0 0 var(--sp-3); }
/* TODO-stacey note 69: one row per upstream on Manage's "Data connections". */
.checkbox-row,
.upload-form label.checkbox-row { display: flex; flex-direction: row; align-items: flex-start; gap: var(--sp-2); font-weight: 400; }
.checkbox-row input { margin-top: 0.3rem; flex: none; }
.checkbox-row .manage-hint { margin: 0; }

.source-list { list-style: none; margin: var(--sp-3) 0 0; padding: 0; }
.source-row {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  border-top: 1px solid var(--hairline);
}
.source-row__handle {
  cursor: grab;
  color: var(--ink-muted);
  user-select: none;
  padding-top: 0.1rem;
}
.source-row__body { flex: 1; min-width: 0; }
.source-row__title { font-weight: 600; }
.source-row__meta { font-size: 0.8125rem; color: var(--ink-muted); margin: 0.15rem 0; }
.source-row__actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); font-size: 0.8125rem; }
.source-row.dragging { opacity: 0.5; }
.source-row--internal .source-row__title::after {
  content: "internal";
  margin-left: 0.4rem;
  font-size: 0.65rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 0.05rem 0.3rem;
  border-radius: var(--r-control);
  background: var(--warning-wash);
  color: var(--warning);
}

.source-empty {
  margin-top: var(--sp-3);
  padding: var(--sp-3);
  border: 1px dashed var(--hairline-hover);
  border-radius: var(--r-control);
  background: var(--warning-wash);
  color: var(--warning);
  font-size: 0.875rem;
}

/* A page with no supporting documents yet is the normal state right after
   upload, not a problem — no warning color/icon (TODO-ui-design-walkthrough.md). */
.no-sources-note {
  display: inline-block;
  margin-top: 0.25rem;
  font-size: 0.8125rem;
  font-style: italic;
  color: var(--ink-muted);
}
.no-sources-note:hover { color: var(--ink); }

/* The one destructive action on the folder-browser screen, set apart at the
   bottom rather than beside "New Folder" / "Advanced upload…"
   (TODO-ui-design-walkthrough.md). */
.danger-zone {
  margin-top: var(--sp-6);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--hairline);
}
.danger-zone .link-button-danger { display: inline-flex; align-items: center; gap: 0.35rem; }

/* ============================================================================
   Admin: page responses  (admin_submissions.html)
   ========================================================================== */

.submission-fields {
  border-collapse: collapse;
  margin: var(--sp-3) 0;
  font-size: 0.875rem;
  width: 100%;
  max-width: 720px;
}
.submission-fields th,
.submission-fields td {
  border: 1px solid var(--hairline);
  padding: 0.4rem 0.6rem;
  text-align: left;
  vertical-align: top;
}
.submission-fields th { width: 30%; background: var(--surface-sunk); font-weight: 600; }
.submission-fields td { word-break: break-word; }

/* Freeform `text` on a submission — a typed message or a page's serialized
   output. Rendered as inert, escaped text (never as HTML), so a fragment is
   readable and copyable but cannot execute. */
.submission-text {
  margin: var(--sp-2) 0 var(--sp-3);
  padding: var(--sp-3);
  max-width: 720px;
  max-height: 22rem;
  overflow: auto;
  background: var(--surface-sunk);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  font-size: 0.8125rem;
  white-space: pre-wrap;
  word-break: break-word;
}

/* ============================================================================
   Viewer sidebar shell  (user_shell.html) — laid out fully in Phase 3
   ========================================================================== */

.site-shell {
  display: flex;
  flex: 1;
  min-height: 0;
  max-width: none;
  margin: 0;
  padding: 0;
}

.sidebar {
  width: var(--sidebar-w);
  flex-shrink: 0;
  position: sticky;
  top: 60px;
  height: calc(100vh - 60px);
  overflow-y: auto;
  padding: var(--sp-4) var(--sp-3);
  border-right: 1px solid var(--hairline);
  background: var(--surface-sunk);
}

.sidebar-main {
  flex: 1;
  min-width: 0;
  padding: var(--passe);
}

.shell-nav-toggle,
.shell-nav-backdrop,
.shell-nav-head { display: none; }

.library-canvas {
  min-height: calc(100% - 2 * var(--passe));
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  padding: clamp(var(--sp-5), 4vw, calc(var(--sp-6) * 1.5));
}
.library-welcome { max-width: 60ch; margin-bottom: var(--sp-6); }
.library-welcome h1 { margin-bottom: var(--sp-1); }
.library-welcome__email {
  font-family: var(--font-ui);
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--ink-muted);
  margin: 0 0 var(--sp-3);
}
.library-welcome p { color: var(--ink-muted); }

.library-search,
.sidebar-search {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-4);
  padding: 0.45rem 0.75rem;
  max-width: 32ch;
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink-muted);
}
.library-search input,
.sidebar-search input {
  border: none;
  outline: none;
  background: none;
  color: var(--ink);
  font: inherit;
  width: 100%;
}
.sidebar-search {
  max-width: none;
  margin: 0 0 var(--sp-4);
  border-radius: var(--r-control);
}
.sidebar-search-empty {
  margin: calc(-1 * var(--sp-2)) var(--sp-2) var(--sp-3);
  color: var(--ink-muted);
  font-size: 0.8125rem;
  font-style: italic;
}

.ink-muted { color: var(--ink-muted); }

.featured { max-width: 640px; margin-bottom: var(--sp-6); }
.featured .section-heading {
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--hairline);
}
.featured .tile-name small { font-weight: 400; }

.recent { max-width: 640px; }
.recent .section-heading {
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--hairline);
}
.recent-list { list-style: none; margin: 0; padding: 0; }
.recent-item + .recent-item { border-top: 1px solid var(--hairline); }
.recent-item a {
  display: flex;
  gap: var(--sp-3);
  align-items: flex-start;
  padding: var(--sp-3) var(--sp-2);
  border-radius: var(--r-control);
  text-decoration: none;
  color: inherit;
}
.recent-item a:hover { background: var(--surface-sunk); }
.recent-item__icon { color: var(--ink-muted); margin-top: 1px; }
.recent-item__body { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.recent-item__title { font-size: 0.9375rem; font-weight: 600; color: var(--ink); }
.recent-item__meta { font-size: 0.75rem; color: var(--ink-muted); }

.sidebar-tree, .sidebar-top-pages, .sidebar-folders, .sidebar-folder-children {
  list-style: none;
  margin: 0;
  padding: 0;
}
.sidebar-top-pages {
  margin-bottom: var(--sp-3);
  padding-bottom: var(--sp-3);
  border-bottom: 1px solid var(--hairline);
}

.sidebar-page-link {
  display: block;
  padding: 0.4rem 0.5rem;
  border-radius: var(--r-control);
  color: var(--ink);
  text-decoration: none;
  font-size: 0.875rem;
}
.sidebar-page-link:hover { background: var(--surface-raised); }
.sidebar-page-link.active { background: var(--primary); color: var(--on-primary); }

/* A "link"-kind sidebar entry (TODO-stacey note 34) — same row, plus its
   external-link glyph inline before the label instead of block text. */
.sidebar-page-link--external {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}
.sidebar-page-link--external .icon { color: var(--ink-muted); flex-shrink: 0; }

/* Admin-only "not updated in a while" flag (TODO-stacey note 29) — colour
   change plus an icon, not colour alone (accessibility). */
.sidebar-page-link--stale {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--warning);
}
.sidebar-page-link--stale .icon { color: inherit; flex-shrink: 0; }
.sidebar-page-link--stale.active { color: var(--on-primary); }

.sidebar-folder-toggle {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  width: 100%;
  padding: 0.4rem 0.5rem;
  border: none;
  background: none;
  text-align: left;
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
  color: var(--ink);
  border-radius: var(--r-control);
  min-height: 2.75rem;
}
.sidebar-folder-toggle:hover { background: var(--surface-raised); }
.sidebar-folder-icon { color: var(--ink-muted); display: inline-flex; }
/* A folder toggles in place, a page navigates — the caret is the tell
   (TODO-ui-design-walkthrough.md). Rotates 90° open via the same
   .sidebar-folder--expanded class static/js/sidebar.js already sets. */
.sidebar-folder-caret {
  color: var(--ink-muted);
  display: inline-flex;
  flex-shrink: 0;
  transition: transform 0.12s;
}
.sidebar-folder--expanded > .sidebar-folder-toggle .sidebar-folder-caret,
.sidebar-folder--expanded > .sidebar-folder-row > .sidebar-folder-toggle .sidebar-folder-caret {
  transform: rotate(90deg);
}

/* Admin-only split row (TODO-stacey note 24): the name becomes its own link
   to the folder's admin page, the caret stays a toggle-only button. Two
   independent hit targets that should still read as one row. */
.sidebar-folder-row {
  display: flex;
  align-items: center;
  gap: 0.15rem;
}
.sidebar-folder-caret-btn {
  width: auto;
  padding: 0.4rem 0.35rem;
  flex-shrink: 0;
  min-width: 2.75rem;
}
.sidebar-folder-link {
  flex: 1;
  min-width: 0;
  padding: 0.4rem 0.5rem;
  border-radius: var(--r-control);
  color: var(--ink);
  text-decoration: none;
  font-size: 0.875rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-height: 2.75rem;
  display: flex;
  align-items: center;
}
.sidebar-folder-link:hover { background: var(--surface-raised); }

/* Admin drag-and-drop in the sidebar (TODO-stacey note 66). A dragged page
   dims like a reordered source row; the folder row (or top-level list)
   under the pointer gets the same primary wash as the upload drop zone.
   While any drag is on, the top-level list gets room and a label so a page
   can be dropped there even when it's empty. */
.sidebar-page-link[draggable="true"] { cursor: grab; }
.sidebar-page-link.dragging { opacity: 0.5; }
.sidebar-folder-row.sidebar-drop-target,
.sidebar-top-pages.sidebar-drop-target {
  background: var(--primary-wash);
  outline: 2px dashed var(--primary);
  outline-offset: -2px;
  border-radius: var(--r-control);
}
.sidebar-dragging .sidebar-top-pages[data-folder-url-id] {
  min-height: 2.75rem;
  border-radius: var(--r-control);
}
.sidebar-dragging .sidebar-top-pages[data-folder-url-id]::before {
  content: "Top level";
  display: block;
  padding: 0.4rem 0.5rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--ink-muted);
}

.sidebar-folder-children {
  /* Indent bumped and the guide line darkened a step so a page three levels
     deep reads as clearly nested, not almost flush with a top-level one
     (TODO-ui-design-walkthrough.md). */
  margin-left: var(--sp-4);
  padding-left: var(--sp-3);
  border-left: 1px solid var(--hairline-hover);
}

.sidebar-empty, .sidebar-empty-state {
  color: var(--ink-muted);
  font-size: 0.8125rem;
  padding: 0.4rem 0.5rem;
}
/* "Shared with you" bucket (TODO-stacey note 21) — a flat list of directly-
   granted pages below the folder tree, same row style as .sidebar-page-link
   but under its own label so it doesn't read as part of the tree above it. */
.sidebar-section-label {
  margin: var(--sp-3) 0 0.25rem;
  padding: 0 0.5rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--ink-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
/* A note, not a fake row that looks like it belongs in the list next to it
   (TODO-ui-design-walkthrough.md). */
.sidebar-empty { font-style: italic; }

/* The Archive section (TODO-stacey note 27) — a real .sidebar-folder row so
   it collapses via the same JS as any other folder, just set apart with a
   rule + top margin so it doesn't read as one more entry in the tree above
   it, the same way "Shared with you" is set apart by its label instead. */
.sidebar-archive-section {
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--hairline);
}
.sidebar-archive-root > .sidebar-folder-toggle { color: var(--ink-muted); }

@media (max-width: 800px) {
  .site-shell { display: block; }
  body.shell-nav-open { overflow: hidden; }
  .shell-nav-toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    margin: var(--sp-3) var(--sp-4) 0;
    padding: 0.55rem 0.8rem;
    border: 1px solid var(--hairline);
    border-radius: var(--r-control);
    background: var(--surface);
    color: var(--ink);
    font: inherit;
    font-size: 0.875rem;
    font-weight: 600;
    cursor: pointer;
  }
  .shell-nav-backdrop {
    position: fixed;
    inset: 0;
    z-index: 198;
    width: 100%;
    height: 100%;
    border: 0;
    background: var(--backdrop);
    cursor: default;
  }
  .sidebar:not(.sidebar--embedded) {
    position: fixed;
    z-index: 199;
    inset: 0 auto 0 0;
    width: min(88vw, 320px);
    height: 100dvh;
    padding-top: var(--sp-3);
    border-right: 1px solid var(--hairline);
    box-shadow: var(--shadow-pop);
    transform: translateX(-105%);
    transition: transform 0.18s ease;
  }
  .sidebar.sidebar--open { transform: translateX(0); }
  .shell-nav-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: var(--sp-3);
    padding-bottom: var(--sp-2);
    border-bottom: 1px solid var(--hairline);
  }
  .shell-nav-close {
    border: 0;
    background: none;
    color: var(--ink-muted);
    font: inherit;
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
  }
  .sidebar--embedded {
    position: static;
    width: auto;
    height: auto;
    transform: none;
    box-shadow: none;
  }
  .sidebar-main { padding-top: var(--sp-3); }
}

/* ============================================================================
   Sitemap  (sitemap.html)
   ========================================================================== */

.sitemap-intro { color: var(--ink-muted); margin-top: 0.25rem; }
.sitemap-section-heading {
  margin: var(--sp-5) 0 var(--sp-2);
  font-family: var(--font-ui);
  font-size: 0.75rem;
  color: var(--ink-muted);
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.sitemap ul { list-style: none; margin: 0; padding: 0; }
/* Indent bumped and the guide line darkened a step, same reasoning and same
   values as .sidebar-folder-children — a page three levels deep needs to
   read as nested, not almost flush with a top-level one
   (TODO-ui-design-walkthrough.md). Selector is ".sitemap ul.sitemap-children",
   not just ".sitemap-children": ".sitemap ul" above is a class+element
   selector, which outranks a class-only one — a plain ".sitemap-children"
   rule here would lose its margin/padding to that reset and go flush
   (border-left would still show, margin/padding silently wouldn't — which
   is exactly why this was "too subtle" instead of simply absent). */
.sitemap ul.sitemap-children {
  margin: 0.25rem 0 var(--sp-2) var(--sp-4);
  padding-left: var(--sp-3);
  border-left: 1px solid var(--hairline-hover);
}
.sitemap-top-pages {
  margin-bottom: var(--sp-3);
  padding-bottom: var(--sp-3);
  border-bottom: 1px solid var(--hairline);
}

.sitemap-folder-name {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.4rem 0.5rem;
  font-weight: 600;
  font-size: 0.9375rem;
}
.sitemap-folder-name .icon { color: var(--ink-muted); }

.sitemap-page a {
  display: block;
  padding: 0.35rem 0.5rem;
  border-radius: var(--r-control);
  color: var(--primary);
  text-decoration: none;
  font-size: 0.875rem;
}
.sitemap-page a:hover { background: var(--surface-sunk); text-decoration: underline; }

/* A note, not a fake row that looks like it belongs beside a real page link
   (TODO-ui-design-walkthrough.md). */
.sitemap-empty {
  color: var(--ink-muted);
  font-size: 0.8125rem;
  font-style: italic;
  padding: 0.35rem 0.5rem;
}

/* ============================================================================
   Modal + toast  (static/js/ui.js) — the only elements with a shadow
   ========================================================================== */

.modal {
  width: min(28rem, calc(100vw - 2 * var(--sp-4)));
  margin: auto;
  padding: var(--sp-5);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-pop);
}
.modal::backdrop { background: var(--backdrop); }

.modal__title {
  margin: 0 0 var(--sp-2);
  font-family: var(--font-display);
  font-size: 1.125rem;
  font-weight: 600;
}
.modal__body {
  margin: 0 0 var(--sp-4);
  color: var(--ink-muted);
  font-size: 0.9375rem;
}
.modal__confirm-field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin-bottom: var(--sp-4);
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--ink-muted);
}
.modal__confirm-field input { width: 100%; }
.modal__actions { display: flex; justify-content: flex-end; gap: var(--sp-2); }
.modal__danger {
  background: var(--danger-solid);
  border-color: var(--danger-solid);
  color: var(--on-danger);
}
.modal__danger:hover { background: var(--danger-solid); filter: brightness(0.92); }
.modal__danger[disabled] { filter: none; }

.toasts {
  position: fixed;
  bottom: var(--sp-4);
  right: var(--sp-4);
  z-index: 2147483001;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  max-width: min(24rem, calc(100vw - 2 * var(--sp-4)));
}
.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--hairline);
  border-left-width: 3px;
  border-radius: var(--r-control);
  background: var(--surface);
  color: var(--ink);
  font-size: 0.875rem;
  box-shadow: var(--shadow-pop);
}
.toast--success { border-left-color: var(--success); }
.toast--error   { border-left-color: var(--danger); }
.toast--info    { border-left-color: var(--primary); }
.toast__msg { flex: 1; }

/* ============================================================================
   Section note + public landing page (TODO-stacey note 16)
   ========================================================================== */

/* A one-line caption under a .section-heading (e.g. the admin folder view's
   "Public pages" / "Documents" split). */
.section-note {
  margin: calc(-1 * var(--sp-2)) 0 var(--sp-4);
  color: var(--ink-muted);
  font-size: 0.8125rem;
}

.landing {
  max-width: 44rem;
  margin: 0 auto;
}
.landing__title { margin: 0 0 var(--sp-2); }
.landing__lead {
  margin: 0 0 var(--sp-5);
  color: var(--ink-muted);
  font-size: 1rem;
}
.landing__list { list-style: none; margin: 0 0 var(--sp-5); padding: 0; }
.landing__item {
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  margin-bottom: var(--sp-3);
  background: var(--surface);
  transition: border-color 0.12s;
}
.landing__item:hover { border-color: var(--hairline-hover); }
.landing__item > a {
  display: block;
  padding: var(--sp-4);
  text-decoration: none;
  color: var(--ink);
}
.landing__item-title { display: block; font-weight: 600; }
.landing__item-desc {
  display: block;
  margin-top: 0.15rem;
  font-size: 0.875rem;
  color: var(--ink-muted);
}
.landing__cta {
  padding-top: var(--sp-4);
  border-top: 1px solid var(--hairline);
  color: var(--ink-muted);
  font-size: 0.875rem;
}
.toast__close {
  flex-shrink: 0;
  background: none;
  border: none;
  padding: 0;
  color: var(--ink-muted);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}
.toast__close:hover { color: var(--ink); }

/* In-browser HTML editor (TODO-stacey note 33), and the React source editor
   (note 68) that shares its look. */
#html-editor-shell,
#react-editor-shell {
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  overflow: hidden;
  margin-bottom: var(--sp-3);
}
#html-editor-host .CodeMirror,
#react-editor-host .CodeMirror {
  height: 420px;
  font-family: var(--font-mono);
  font-size: 0.8125rem;
}
#html-editor-details > summary { cursor: pointer; margin-bottom: var(--sp-3); }
#html-editor-details > .manage-hint { margin-top: 0; }
/* Monospace: esbuild's message draws a caret under the offending column. */
.react-editor-error {
  white-space: pre-wrap;
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  margin-bottom: var(--sp-3);
}
/* The line a build error points at. Pinned code token for the same reason
   as the overrides below: the editor pane stays white in the dark console. */
.CodeMirror .react-editor-error-line {
  background: rgba(179, 38, 30, 0.12);  /* --code-error at 12% */
}
/* CodeMirror 5 contrast overrides. Eight of the twenty-three colours the
   vendored default theme ships fail 4.5:1 against white paper, including the
   gutter numbers (#999, 2.9:1) and, worse, the two that mark a mistake —
   .cm-error and .cm-invalidchar at #f00, 4.0:1. Pre-existing, not introduced
   by the SHIELD Studio pass; fixed here because the vendored stylesheet is
   not ours to edit.

   Every colour here is a pinned --code-* token rather than a theme token.
   The vendored stylesheet gives .CodeMirror a white background unconditionally,
   so the editor pane stays white even when an admin switches the console to
   dark (D6) — ink that followed the theme would go light-on-white and become
   unreadable exactly when it is needed. A dark code pane for the dark console
   is a larger job: all twenty-three syntax hues are tuned for white paper and
   would each need a dark counterpart.

   The doubled selectors are for specificity, not scoping: the templates that
   use the editor load `vendor/codemirror5/lib/codemirror.css` AFTER this
   stylesheet, so anything that merely ties loses on order. Most of the
   vendor rules are `.cm-s-default .cm-error` (0,2,0), so these need (0,3,0)
   to win — hence `.CodeMirror.CodeMirror .cm-*`, which matches the same
   element twice rather than inventing a class. */
.CodeMirror.CodeMirror .CodeMirror-linenumber { color: var(--code-ink-muted); }
.CodeMirror.CodeMirror .cm-error,
.CodeMirror.CodeMirror .cm-invalidchar,
.CodeMirror.CodeMirror .cm-negative  { color: var(--code-error); }
.CodeMirror.CodeMirror .cm-positive,
.CodeMirror.CodeMirror .cm-quote     { color: var(--code-ok); }
.CodeMirror.CodeMirror .cm-string-2  { color: var(--code-string-alt); }
.CodeMirror.CodeMirror .cm-bracket   { color: var(--code-bracket); }
.CodeMirror.CodeMirror .cm-hr        { color: var(--code-ink-muted); }
.html-editor-preview {
  width: 100%;
  height: 420px;
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  background: var(--document-paper);
}

/* ============================================================================
   Console chrome  —  [data-surface="console"] only
   ============================================================================
   Phase 3 of the SHIELD Studio re-theme: the "dark accents" half of the
   brief. The console's canvas is near-white, and its two pieces of persistent
   chrome — the header bar and the sidebar rail — go solid Slate, so the admin
   side reads as the photographic negative of the viewer side rather than as a
   different product.

   Everything inside that chrome has to be re-inked: the default --ink-muted
   is 1.2:1 on Slate, and the Deep Teal that carries links on white is 2.7:1
   there. The --on-chrome-* tokens are the chrome's own ink set, and both
   themes define them, so every rule here works unchanged when the admin
   switches to the dark console — where --chrome resolves to a raised panel
   instead of Slate, and the same tokens follow it.

   Scoped to [data-surface="console"], so the studio surface is untouched.
   ========================================================================== */

[data-surface="console"] .site-header {
  background: var(--chrome);
  border-bottom-color: var(--chrome-line);
}
[data-surface="console"] .site-title { color: var(--on-chrome-accent); }
[data-surface="console"] .site-nav a { color: var(--on-chrome-muted); }
[data-surface="console"] .site-nav a:hover { color: var(--on-chrome); }
[data-surface="console"] .site-nav a[aria-current="page"] {
  color: var(--on-chrome-accent);
  border-bottom-color: var(--on-chrome-accent);
}
[data-surface="console"] .site-account__who { color: var(--on-chrome-muted); }
[data-surface="console"] .site-account__out { color: var(--on-chrome-muted); }
[data-surface="console"] .site-account__out:hover { color: var(--on-chrome-accent); }
/* "Preview as anonymous visitor" is a .link-button in the header. */
[data-surface="console"] .site-header .link-button { color: var(--on-chrome-accent); }
[data-surface="console"] .site-header .icon-btn { color: var(--on-chrome-muted); }
[data-surface="console"] .site-header .icon-btn:hover {
  color: var(--on-chrome);
  background: var(--chrome-hover);
  border-color: var(--chrome-line);
}

/* The light/dark toggle (D6) shows the ACTION, not the state: a moon while
   light, meaning "click to go dark", and a sun while dark. Swapped in CSS
   rather than JS so it is already right at first paint — _theme_boot.html has
   resolved data-theme by the time this parses. */
[data-surface="console"] .theme-toggle__to-light { display: none; }
[data-surface="console"][data-theme="dark"] .theme-toggle__to-dark { display: none; }
[data-surface="console"][data-theme="dark"] .theme-toggle__to-light { display: inline-flex; }

/* --- The rail ------------------------------------------------------------ */

[data-surface="console"] .sidebar {
  background: var(--chrome);
  border-right-color: var(--chrome-line);
}
[data-surface="console"] .sidebar-search {
  background: var(--chrome-hover);
  border-color: var(--chrome-line);
  color: var(--on-chrome-muted);
}
[data-surface="console"] .sidebar-search input { color: var(--on-chrome); }
[data-surface="console"] .sidebar-search input::placeholder { color: var(--on-chrome-muted); }

[data-surface="console"] .sidebar-page-link,
[data-surface="console"] .sidebar-folder-toggle,
[data-surface="console"] .sidebar-folder-link { color: var(--on-chrome); }
[data-surface="console"] .sidebar-page-link:hover,
[data-surface="console"] .sidebar-folder-toggle:hover,
[data-surface="console"] .sidebar-folder-link:hover { background: var(--chrome-hover); }

[data-surface="console"] .sidebar-folder-icon,
[data-surface="console"] .sidebar-folder-caret,
[data-surface="console"] .sidebar-page-link--external .icon { color: var(--on-chrome-muted); }

[data-surface="console"] .sidebar-top-pages { border-bottom-color: var(--chrome-line); }
[data-surface="console"] .sidebar-folder-children { border-left-color: var(--chrome-line); }

[data-surface="console"] .sidebar-empty,
[data-surface="console"] .sidebar-empty-state,
[data-surface="console"] .sidebar-search-empty,
[data-surface="console"] .sidebar-section-label { color: var(--on-chrome-muted); }

/* The drawer's own header, which lives INSIDE the rail at phone widths and so
   sits on the chrome too. .shell-nav-toggle is deliberately absent: that
   button is in .sidebar-main, on the page canvas, not in the drawer. */
[data-surface="console"] .shell-nav-head {
  color: var(--on-chrome);
  border-bottom-color: var(--chrome-line);
}
[data-surface="console"] .shell-nav-close { color: var(--on-chrome-muted); }
[data-surface="console"] .shell-nav-close:hover { color: var(--on-chrome); }

/* The page-view nav drawer is the rail in a different container:
   `.page-nav-drawer .sidebar` deliberately sets `background: none` so the
   tree inherits the panel. Putting the rail on Slate therefore has to move
   the PANEL, not the tree inside it — otherwise a dark block floats in a
   white card with white gutters, and every rail-ink rule above lands on
   white. Slate here also matches the phone drawer, which is the rail itself. */
[data-surface="console"] .page-nav-drawer {
  background: var(--chrome);
  border-color: var(--chrome-line);
}
[data-surface="console"] .page-nav-drawer__head h2 { color: var(--on-chrome); }
[data-surface="console"] .page-nav-drawer__close { color: var(--on-chrome-muted); }
[data-surface="console"] .page-nav-drawer__close:hover { color: var(--on-chrome); }

/* The selected row. Medium Teal rather than the Deep Teal --primary: on Slate
   that fill is 2.7:1 against the rail, under the 3:1 a state indicator needs.
   Medium Teal is 4.5:1 against it and carries near-black at 8.9:1. */
[data-surface="console"] .sidebar-page-link.active {
  background: var(--accent-bright);
  color: var(--brand-on-teal);
}

/* Note 29's staleness flag. The console's --warning is a dark amber tuned for
   white and is 1.1:1 on Slate; on the chrome it takes the brand's published
   value, which is 9.7:1 there. The icon carries the meaning either way. */
[data-surface="console"] .sidebar-page-link--stale { color: var(--brand-warning); }
[data-surface="console"] .sidebar-page-link--stale.active { color: var(--brand-on-teal); }

/* ============================================================================
   Studio surface language  —  [data-surface="studio"] only
   ============================================================================
   Phase 1 of the SHIELD Studio re-theme. Phase 0 gave this surface its
   palette; this section gives it the *materials* — the glowing canvas, the
   gradient card, the teal eyebrow — none of which a token layer can express
   on its own.

   Everything is scoped to `[data-surface="studio"]`, so the admin console is
   untouched by all of it. The values are the reference's own, from
   `docs/website-design/rebrand/shield-labs design.zip` →
   `SHIELD_Studio_Landing_Page.html`, re-expressed for a scrolling page
   instead of that file's fixed 1536x1024 stage.

   The landing page is deliberately NOT rebuilt here — that is Phase 2, and it
   waits on decisions and assets this phase does not need. What it gets now is
   the same materials as every other studio screen, so Phase 2 becomes a
   layout change on top of a finished surface rather than a fresh start.

   Colour literals below appear only inside custom-property declarations; no
   component rule in this section carries a raw colour.
   ========================================================================== */

[data-surface="studio"] {
  /* Rounder than the console. The console is a dense file manager where a
     14px radius on every row reads as bubbly; the studio surfaces are fewer
     and larger, and the reference's own cards are 17px on a 1536px stage. */
  --r-control: 8px;
  --r-card: 14px;
  --r-panel: 20px;

  /* The canvas — two teal washes over a diagonal near-black. */
  --studio-canvas-art:
    radial-gradient(60vw 70vh at 30% 28%, rgba(0, 85, 103, 0.12), transparent 60%),
    radial-gradient(42vw 46vh at 78% 6%, rgba(0, 89, 105, 0.07), transparent 65%),
    linear-gradient(135deg, #02060b 0%, #02070c 54%, #01060b 100%);

  /* A card is a vertical gradient over that canvas, not a flat fill, so the
     glow behind it shows through — the reference's own treatment.

     Contrast note: the washes lift the canvas from L 0.0019 to at most
     L 0.0045, which moves --ink from 18.1:1 to 17.2:1 and --ink-muted from
     11.4:1 to 10.9:1. Both stay far above AA, which is the only reason a
     glow is allowed to sit under body text at all. The DOM contrast crawler
     resolves `background-color` and so measures the flat value; the figures
     above are the bound on how much it over-reports. */
  --studio-card:       linear-gradient(180deg, rgba(5, 13, 20, 0.78), rgba(2, 8, 13, 0.92));
  --studio-card-hover: linear-gradient(180deg, rgba(7, 18, 25, 0.90), rgba(3, 10, 15, 0.96));

  /* Depth on a near-black canvas comes from a teal glow, not a drop shadow. */
  --studio-glow:       0 0 18px rgba(34, 214, 192, 0.10);
  --studio-glow-focus: 0 0 0 3px rgba(34, 214, 192, 0.18);

  /* Header veil. Opaque enough to read as a bar with no backdrop-filter. */
  --studio-veil:     rgba(2, 7, 12, 0.82);
  --studio-selection: rgba(34, 214, 192, 0.28);
}

/* --- The canvas ---------------------------------------------------------
   Painted on a fixed pseudo-element rather than `background-attachment:
   fixed`, which iOS Safari sizes to the document instead of the viewport.

   The solid colour moves to the root element so it becomes the canvas
   background in the spec's sense; `body::before` at `z-index: -1` then paints
   above it and below all content. Putting the solid on `body` instead would
   let it propagate to the canvas and bury the gradient. `body` is a flex
   container, but a fixed-position pseudo-element is out of flow, so it never
   becomes a flex item. */
html[data-surface="studio"] { background-color: var(--bg); }
[data-surface="studio"] body { background-color: transparent; }
[data-surface="studio"] body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: var(--studio-canvas-art);
}

/* --- Type ---------------------------------------------------------------
   The reference sets its brand line at weight 650 with -1.6px tracking at
   57px. Only static Inter instances are loaded (400/500/600/700), so 650
   would round anyway — 700 with tighter tracking is the honest equivalent. */
[data-surface="studio"] h1 {
  font-weight: 700;
  letter-spacing: -0.025em;
}

/* A section heading becomes the reference's teal eyebrow rather than a
   heading in body ink. 13px, so it needs 4.5:1 — teal on the canvas is
   11.2:1. */
[data-surface="studio"] .section-heading {
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--primary);
}
[data-surface="studio"] .section-heading .icon { color: var(--primary); }

[data-surface="studio"] ::selection {
  background: var(--studio-selection);
  color: var(--ink);
}

/* --- Chrome -------------------------------------------------------------- */

[data-surface="studio"] .site-header {
  background: var(--studio-veil);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

[data-surface="studio"] .sidebar {
  background: var(--surface-sunk);
}

/* --- Cards and panels ---------------------------------------------------- */

[data-surface="studio"] .card,
[data-surface="studio"] .tile,
[data-surface="studio"] .folder-tile,
[data-surface="studio"] .landing__item,
[data-surface="studio"] .page-card {
  background: var(--studio-card);
  border-color: var(--studio-line-2);
}

[data-surface="studio"] .tile:hover,
[data-surface="studio"] .folder-tile:hover,
[data-surface="studio"] .landing__item:hover,
[data-surface="studio"] .page-card:hover {
  background: var(--studio-card-hover);
  border-color: var(--studio-line-hi);
}

/* The two large panels. Same material, bigger radius, and the auth card gets
   the glow because it is the one thing on an otherwise empty screen. */
[data-surface="studio"] .library-canvas,
[data-surface="studio"] .auth-card {
  background: var(--studio-card);
  border-color: var(--studio-line-2);
  border-radius: var(--r-panel);
}
[data-surface="studio"] .auth-card { box-shadow: var(--studio-glow); }

[data-surface="studio"] .error-page__icon {
  background: var(--primary-wash);
  color: var(--primary);
}

/* --- Controls ------------------------------------------------------------ */

[data-surface="studio"] input[type="text"],
[data-surface="studio"] input[type="email"],
[data-surface="studio"] input[type="password"],
[data-surface="studio"] input[type="search"],
[data-surface="studio"] input[type="url"],
[data-surface="studio"] input[type="number"],
[data-surface="studio"] select,
[data-surface="studio"] textarea {
  background: var(--surface-sunk);
  border-color: var(--studio-line-2);
}
[data-surface="studio"] input:focus,
[data-surface="studio"] select:focus,
[data-surface="studio"] textarea:focus {
  border-color: var(--primary);
  box-shadow: var(--studio-glow-focus);
}

/* The primary button is the reference's ENTER STUDIO treatment: a teal fill
   carrying near-black text, lifted off the canvas by its own glow. */
[data-surface="studio"] .btn-primary { box-shadow: var(--studio-glow); }

/* Secondary sits *on* the canvas rather than on a panel of its own, so it is
   an outline, not a fill — a solid --surface chip would read as a hole. */
[data-surface="studio"] .btn-secondary {
  background: transparent;
  border-color: var(--studio-line-2);
}
[data-surface="studio"] .btn-secondary:hover {
  background: var(--studio-card);
  border-color: var(--studio-line-hi);
}

/* --- Sitemap -------------------------------------------------------------
   Two things that were invisible on a light ground and are not on this one. */

/* The list had no surface of its own, which was fine on white and reads as
   unanchored on a gradient. `nav.sitemap` already exists in the markup, so
   this needs no template change. */
[data-surface="studio"] .sitemap {
  display: block;
  margin-top: var(--sp-4);
  padding: var(--sp-5);
  background: var(--studio-card);
  border: 1px solid var(--studio-line-2);
  border-radius: var(--r-panel);
}

/* Every row was --primary, so a full sitemap rendered as a column of teal.
   Teal is the accent on this surface — section titles, the primary action,
   focus — not the body. Document titles read in ink like every other list in
   the app (.recent-item__title, .tile-name), and go teal on hover. */
[data-surface="studio"] .sitemap-page a { color: var(--ink); }
[data-surface="studio"] .sitemap-page a:hover { color: var(--primary); }

[data-surface="studio"] .library-search,
[data-surface="studio"] .sidebar-search {
  background: var(--surface-sunk);
  border-color: var(--studio-line-2);
}
[data-surface="studio"] .library-search:focus-within,
[data-surface="studio"] .sidebar-search:focus-within {
  border-color: var(--primary);
  box-shadow: var(--studio-glow-focus);
}

/* ============================================================================
   Print
   ============================================================================
   The studio skin is near-black, and so is the console in dark mode. Printed
   as-is that is a full-bleed black page: wasteful, usually unreadable once a
   printer's dark-ink handling is done with it, and on many drivers the light
   ink drops out entirely.

   So print re-grounds both skins to paper. This is not the console's light
   palette — it is a third, deliberately plain rendering: white ground, near-
   black ink, hairlines that survive a monochrome printer, and no chrome.

   Tokens are reassigned rather than component rules overridden, so anything
   added later inherits the print treatment without a second pass. The two
   surface attributes are both matched, since either can be the dark one.
   ========================================================================== */

@media print {
  :root,
  [data-surface="studio"],
  [data-surface="console"],
  [data-surface="console"][data-theme="dark"] {
    color-scheme: light;

    --bg:             #ffffff;
    --surface:        #ffffff;
    --surface-sunk:   #ffffff;
    --surface-raised: #ffffff;

    --ink:            #111111;
    --ink-muted:      #444444;

    --hairline:       #999999;
    --hairline-hover: #777777;

    /* Ink, not a fill: a teal CTA prints as a grey slab with white text on
       it, which is worse than plain type. */
    --primary:         #14554e;
    --primary-hover:   #14554e;
    --on-primary:      #ffffff;
    --primary-wash:    transparent;
    --accent-bright:   #14554e;

    --chrome:           #ffffff;
    --on-chrome:        #111111;
    --on-chrome-muted:  #444444;
    --on-chrome-accent: #14554e;
    --chrome-line:      #999999;
    --chrome-hover:     transparent;

    --danger:  #8c1d16;
    --success: #0d4f26;
    --warning: #6b4600;
    --info:    #17389e;
    --danger-wash: transparent;
    --success-wash: transparent;
    --warning-wash: transparent;
    --info-wash: transparent;

    --frame-line: #999999;
    --frame-rim:  none;
    --shadow-pop: none;
    --studio-card:       #ffffff;
    --studio-card-hover: #ffffff;
    --studio-glow:       none;
  }

  /* The canvas gradient is a fixed pseudo-element; printed it would tile
     across every page. */
  [data-surface="studio"] body::before { display: none !important; }

  body { background: var(--bg) !important; }

  /* Chrome that means nothing on paper. The preview banner goes too: a
     printed page is not a preview of anything. */
  .site-header,
  .sidebar,
  .shell-nav-toggle,
  .shell-nav-backdrop,
  .preview-banner,
  .page-fullscreen__header,
  .page-files-drawer,
  .page-nav-drawer,
  .page-video,
  .page-audio,
  .page-gallery,
  .toast-stack,
  .card-menu,
  .page-card__actions,
  .tile-delete-btn,
  .tile-public-btn,
  .tile-archive-btn { display: none !important; }

  /* The page view is a fixed, viewport-sized iframe. Unpinned it prints as
     one page of whatever fits; left fixed it prints as a black band. Neither
     is good, but a framed first page beats a band. */
  .page-fullscreen__frame {
    position: static !important;
    width: 100% !important;
    height: auto !important;
    min-height: 60vh;
    border-color: var(--hairline) !important;
    box-shadow: none !important;
  }

  .site-shell { display: block !important; }
  .sidebar-main { padding: 0 !important; }
  .library-canvas,
  .card,
  .tile,
  .page-card,
  .landing__item,
  .sitemap {
    border-color: var(--hairline) !important;
    box-shadow: none !important;
    break-inside: avoid;
  }

  /* A link that says "click here" is useless on paper. */
  .site-main a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 0.75em;
    color: var(--ink-muted);
    word-break: break-all;
  }

  a { text-decoration: underline; }
}

/* Name search page (TODO-stacey note 67) — reuses .library-search for the
   box and .recent-list for results. */
.search-form .library-search { max-width: 48ch; margin: 0 0 var(--sp-6); }
.search-results .section-heading { font-size: 1rem; }
.search-result__file { font-family: var(--font-mono, ui-monospace, monospace); }
.sidebar-search-empty a { font-style: normal; }
