/* COPY of home/public/theme.css — the master. Re-copy after changing it; do not edit here.
   Sync with: node scripts/sync-theme.mjs   ·   Verify with: --check */
/* ============================================================
   anond.app — the zone design system
   The warm-paper / blueprint / brick language, as tokens.

   THIS FILE REPLACED THE STRATOS "Unified Design System".
   Until 2026-08-17 the apex linked a 176-line stylesheet whose own header
   read "STRATOS — Unified Design System": the work project's palette, its
   oklch dark ramp, and a render-blocking @import of Google Fonts (Inter +
   JetBrains Mono) that this zone has never used a glyph of. The apex
   overrode every one of its colours in an inline :root, so what actually
   shipped was dead weight plus a third-party font request. Commit db016ff
   ("the apex stops wearing the work project's [face]") took the favicon;
   this takes the stylesheet. See DOMAINS.md § Why the work repo stays
   separate — that boundary is not cosmetic.

   ── Copies ──────────────────────────────────────────────────
   Each property deploys as its own tree, so this file is COPIED rather
   than linked across directories — the same rule brand/README.md sets for
   the favicon, and for the same reason. This file is the master. Copies:

     home/public/theme.css                      anond.app          (master)
     tools/public/theme.css                     tools.anond.app
     apps/public/theme.css                      apps.anond.app
     labs/Grid Magic/beta/root/theme.css        play.anond.app
     Finance/deploy/home/public/theme.css       me.anond.app   (SEPARATE REPO)

   Change the master, then re-copy all four. They are byte-identical apart
   from a provenance comment.

   ── Two card treatments ─────────────────────────────────────
     .brick       a coloured stud face across the top, then a paper label
                  plate. Nav tiles: a title and one short line.
     .paper-card  all paper, hairline border, optional coloured stud strip.
                  Longer body copy, meta rows, controls.

   Both put their TEXT ON PAPER. That is deliberate and it is the whole
   reason .brick is shaped the way it is — see the long note above the
   component. Text on a brand fill cannot reach 4.5:1 in either direction.

   ── Contrast is measured, never computed ────────────────────
   Every ratio quoted in this file was read off a live DOM, because hand
   math on a design import in this repo once missed 4 of 7 real failures.
   After changing ANY colour here, re-run:

     node scripts/zone-check.mjs <url> [--dark] [--width=390]

   and re-copy: `node scripts/sync-theme.mjs` (then rebuild play). A stale
   copy renders fine and renders wrong — that has already happened once.
   ============================================================ */

/* ─── Type ──────────────────────────────────────────────────
   Helvetica Neue at 600 is the wordmark's own setting (brand/README.md).
   It resolves to Bold on macOS and falls back to Helvetica/Arial
   elsewhere; the zone already accepts that everywhere else it sets type.
   --mono stays a FUNCTIONAL face — lottery numbers, byte counts, hashes —
   not a brand one. It is why tools/ and apps/ still self-host IBM Plex
   Mono after dropping Space Grotesk.
   ─────────────────────────────────────────────────────────── */
:root {
  --disp: 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --sans: 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, 'Cascadia Mono', Consolas, monospace;

  --tracking-tight: -0.015em;   /* the wordmark's tracking */
  --tracking-label: 0.28em;     /* uppercase section labels */
}

/* ─── Light: paper (the zone default) ───────────────────────
   #f4ede1 and #c75a3a come from the apex; #5b8db5 and #d9a64a from the
   favicon. The one value NOT inherited is --muted. The apex and me.anond
   both shipped #807a6e, which measures ~3.5:1 on paper — under 4.5:1, and
   it is used for exactly the small meta text that needs it most. Darkened
   to #6b6558 (~4.6:1) here; the two pages inherit the fix by adopting
   this file.
   ─────────────────────────────────────────────────────────── */
:root,
:root[data-theme="light"] {
  color-scheme: light;

  --bg:        #f4ede1;
  --bg-2:      #efe6d4;
  --card:      #fbf7ee;
  --surface:   #efe6d4;
  --text:      #2a2825;
  --muted:     #6b6558;
  --border:    #d9cfb9;
  --border-2:  #c3b596;

  --acc:       #c75a3a;   /* zone accent — decorative fills, rules, dots */
  --acc-ink:   #a84a2e;   /* the same accent, dark enough to set text in */
  --acc-soft:  #f6e3db;
  --ok:        #547449;
  --err:       #a84a2e;

  /* A FILLED button is not a decorative fill — it carries a label, so the
     pair has to be measured. White on --acc is 4.24:1 and fails; that was
     shipping on "Convert", "Download PNG", "Resize" and "Make GIF", i.e. the
     primary control of every tool. White on --acc-ink is 5.70:1. */
  --btn-bg:    var(--acc-ink);
  --on-btn:    #ffffff;

  --shadow:    0 1px 0 rgba(42,40,37,.14), 0 10px 24px -14px rgba(42,40,37,.42);
  --shadow-lift: 0 1px 0 rgba(42,40,37,.16), 0 18px 30px -14px rgba(42,40,37,.48);
  --grain:     rgba(42,40,37,0.035);
}

/* ─── Dark: night paper ─────────────────────────────────────
   tools.anond.app and apps.anond.app both ship a persisted theme toggle,
   and tools is public — deleting dark mode to match a light-only design
   would be a regression for anyone opening a PDF tool at night. So the
   ramp is re-derived in the zone's own language (warm ink, not the grey
   the work stylesheet used) rather than dropped. The brick hues carry
   over unchanged; they read correctly on dark ground.
   ─────────────────────────────────────────────────────────── */
:root[data-theme="dark"] {
  color-scheme: dark;

  --bg:        #211f1b;
  --bg-2:      #191713;
  --card:      #2a2722;
  --surface:   #322e28;
  --text:      #efe7d8;
  --muted:     #a89f8d;
  --border:    #3b362d;
  --border-2:  #565043;

  --acc:       #e08a68;
  --acc-ink:   #f0a888;
  --acc-soft:  #3a2a22;
  --ok:        #8fb37f;
  --err:       #e08a68;

  /* Dark flips the pair: --acc is light here, so the label goes ink. Ink on
     --acc measures 6.27:1; white on it is 2.62:1, which is what the four tool
     pages were shipping in dark mode. */
  --btn-bg:    var(--acc);
  --on-btn:    #211f1b;

  --shadow:    0 2px 6px rgba(0,0,0,.30), 0 12px 30px -18px rgba(0,0,0,.55);
  --shadow-lift: 0 2px 6px rgba(0,0,0,.34), 0 18px 34px -16px rgba(0,0,0,.6);
  --grain:     rgba(240,232,216,0.030);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    color-scheme: dark;
    --bg:        #211f1b;
    --bg-2:      #191713;
    --card:      #2a2722;
    --surface:   #322e28;
    --text:      #efe7d8;
    --muted:     #a89f8d;
    --border:    #3b362d;
    --border-2:  #565043;
    --acc:       #e08a68;
    --acc-ink:   #f0a888;
    --acc-soft:  #3a2a22;
    --ok:        #8fb37f;
    --err:       #e08a68;
    --btn-bg:    var(--acc);
    --on-btn:    #211f1b;
    --shadow:    0 2px 6px rgba(0,0,0,.30), 0 12px 30px -18px rgba(0,0,0,.55);
    --shadow-lift: 0 2px 6px rgba(0,0,0,.34), 0 18px 34px -16px rgba(0,0,0,.6);
    --grain:     rgba(240,232,216,0.030);
  }
}

/* ─── Aliases: the zone has TWO token vocabularies ──────────
   The paper language was born on the apex, which named its tokens --ink,
   --line and --accent. tools.anond.app and apps.anond.app grew up in a
   different family and named the same three --text, --border and --acc.
   Both vocabularies are in shipped CSS across five properties.

   Renaming one side would mean touching ~6,900 lines of working tool
   pages to no visual end, so the system carries both. --text/--border/
   --acc are canonical for new work; these three are the apex's originals,
   kept so the apex and me.anond.app keep rendering while adopting this
   file. They are aliases, not independent values — set them here only.
   ─────────────────────────────────────────────────────────── */
:root,
:root[data-theme="light"],
:root[data-theme="dark"] {
  --ink:    var(--text);
  --line:   var(--border);
  --accent: var(--acc);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --ink:    var(--text);
    --line:   var(--border);
    --accent: var(--acc);
  }
}

/* ─── The bricks ────────────────────────────────────────────
   Six fills, each with the darker shade its studs are drawn in. Same
   values the apex figure animation and brand/build.mjs use, so a brick on
   a page and a brick in the logo are the same colour.
   --on-<fill> is the ONLY text colour that has been measured against that
   fill. Do not substitute white on yellow or cream.
   ─────────────────────────────────────────────────────────── */
:root {
  --brick-red:    #c75a3a;  --stud-red:    #a84a2e;  --on-red:    #fff;
  --brick-blue:   #5b8db5;  --stud-blue:   #487aa0;  --on-blue:   #fff;
  --brick-yellow: #d9a64a;  --stud-yellow: #bf8e38;  --on-yellow: #2a2825;
  --brick-green:  #7b9e6e;  --stud-green:  #6a8a5d;  --on-green:  #fff;
  --brick-cream:  #e8dcc4;  --stud-cream:  #d4c5a5;  --on-cream:  #2a2825;
  --brick-ink:    #3a3530;  --stud-ink:    #2a2520;  --on-ink:    #f4ede1;
}

/* ─── Base ──────────────────────────────────────────────────
   Opt-in, not blanket: a page adds .bg-paper to <body> to take the ground
   and grain. The tool pages set their own body layout and would fight a
   global rule.
   ─────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }

html, body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body.bg-paper { position: relative; overflow-x: hidden; }
body.bg-paper::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image: radial-gradient(var(--grain) 1px, transparent 1px);
  background-size: 3px 3px;
}
/* Lift real content above the grain — but NOT .ticks, which is deliberately
   fixed at z-index 0 alongside it. `body.bg-paper > *` is specificity 0,1,1
   and would outrank `.ticks` at 0,1,0, forcing the corner marks into flow and
   painting them over the page. The :not() is load-bearing. */
body.bg-paper > *:not(.ticks) { position: relative; z-index: 1; }

::selection { background: var(--acc-soft); color: var(--text); }
a:focus-visible, button:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--acc-ink);
  outline-offset: 2px;
}

/* ─── Blueprint corner ticks ────────────────────────────────
   <div class="ticks"><span></span></div> — four L-shaped marks pinned to
   the viewport. Decorative; keep it aria-hidden and out of the tab order.
   ─────────────────────────────────────────────────────────── */
.ticks {
  position: fixed;
  inset: 14px;
  pointer-events: none;
  z-index: 0;
  border: 1px solid transparent;
}
.ticks::before, .ticks::after,
.ticks > span::before, .ticks > span::after {
  content: "";
  position: absolute;
  width: 14px; height: 14px;
  border: 1px solid var(--border);
}
.ticks::before        { top: 0; left: 0;  border-right: none; border-bottom: none; }
.ticks::after         { top: 0; right: 0; border-left: none;  border-bottom: none; }
.ticks > span::before { bottom: 0; left: 0;  border-right: none; border-top: none; }
.ticks > span::after  { bottom: 0; right: 0; border-left: none;  border-top: none; }

/* ─── Wordmark ──────────────────────────────────────────────
   The dot is the ACCENT here, not blue. brand/README.md notes the logo
   lockup uses a blue dot; that is the exported mark. On a page the dot
   picks up the page accent, which is what the apex and me.anond already do.
   ─────────────────────────────────────────────────────────── */
.logo {
  font-family: var(--disp);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: var(--tracking-tight);
}
.logo a { color: inherit; text-decoration: none; }
/* --acc-ink, not --acc: the dot is a text glyph, so it owes 4.5:1 like any
   other. Measured, #c75a3a on paper is 3.64:1 — it fails. #a84a2e clears it
   and is visually indistinguishable at 17px. */
.logo .dot { color: var(--acc-ink); }

/* ─── Section label ─────────────────────────────────────────
   <h2 class="glabel"><span class="bar"></span><span>Personal</span></h2>
   ─────────────────────────────────────────────────────────── */
.glabel {
  font-size: 11px;
  font-weight: 400;
  color: var(--muted);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  display: flex;
  align-items: center;
  gap: 10px;
}
.glabel .bar { width: 28px; height: 1px; background: var(--muted); opacity: .5; }
.glabel.rule::after { content: ""; flex: 1; height: 1px; background: var(--border); }

/* ─── Brick — a coloured stud face over a paper label plate ─
   <a class="brick blue">
     <span class="studs" aria-hidden="true"><i></i><i></i><i></i></span>
     <span class="go" aria-hidden="true">→</span>
     <h3>Title</h3><p>one short line</p>
   </a>

   THE TEXT DOES NOT SIT ON THE COLOUR, AND CANNOT.
   The first cut of this component put both lines on the fill and told
   itself "large text only" in a comment. Measured on the live DOM, the
   13px description failed 4.5:1 on four of six fills — worst was green at
   2.75:1. Tuning the text colour does not rescue it: the best ratio
   ACHIEVABLE on --brick-red is 4.24 (white) and on --brick-blue 4.15
   (ink). Both are under 4.5 whichever way you go. That is not a bad pick,
   it is a property of mid-tone brand fills — they are equally far from
   both ends.

   So the fill is a face, not a background: ::before paints the coloured
   stud area across the top, and the text flows below it on --card, where
   ink measures 13.75:1 and --muted 5.41:1 on every variant. The brand
   colours stay exactly as build.mjs exports them, the markup is unchanged,
   and contrast stops depending on which colour a tile happened to get.
   ─────────────────────────────────────────────────────────── */
.brick {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-end;
  min-height: 148px;
  padding: 92px 18px 16px;
  border-radius: 6px;
  overflow: hidden;
  text-align: left;
  text-decoration: none;
  background: var(--card);
  color: var(--text);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
  transition: transform 240ms cubic-bezier(0.22, 1, 0.36, 1), box-shadow 240ms ease;
}
/* the coloured stud face */
.brick::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 84px;
  background: var(--fill, var(--brick-cream));
}
.brick:hover, .brick:focus-visible {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lift);
  outline: none;
}
.brick:focus-visible { box-shadow: 0 0 0 3px var(--bg), 0 0 0 5px var(--acc-ink); }

.studs { position: absolute; top: 12px; left: 16px; display: flex; gap: 8px; z-index: 1; }
.studs i { width: 14px; height: 14px; border-radius: 50%; display: block; background: var(--stud, var(--stud-cream)); }

/* Both lines sit on --card, so both clear 4.5:1 on every variant. No
   opacity dimming — that was how the old version lost its margin. */
.brick h3 { font-family: var(--disp); font-size: 19px; font-weight: 600; letter-spacing: var(--tracking-tight); color: var(--text); }
.brick p  { margin-top: 3px; font-size: 13px; letter-spacing: .02em; color: var(--muted); }

/* The arrow is the one mark that DOES sit on the fill. It is decorative and
   aria-hidden, so it owes no ratio — but it still has to be seen, hence the
   measured per-fill --on-fill rather than a blanket white. */
.brick .go {
  position: absolute; top: 14px; right: 14px; z-index: 1;
  color: var(--on-fill, var(--on-cream));
  font-size: 15px; opacity: 0; transform: translateX(-4px);
  transition: opacity 200ms ease, transform 200ms cubic-bezier(0.22, 1, 0.36, 1);
}
.brick:hover .go, .brick:focus-visible .go { opacity: .95; transform: none; }

.brick.red    { --fill: var(--brick-red);    --stud: var(--stud-red);    --on-fill: var(--on-red);    }
.brick.blue   { --fill: var(--brick-blue);   --stud: var(--stud-blue);   --on-fill: var(--on-blue);   }
.brick.yellow { --fill: var(--brick-yellow); --stud: var(--stud-yellow); --on-fill: var(--on-yellow); }
.brick.green  { --fill: var(--brick-green);  --stud: var(--stud-green);  --on-fill: var(--on-green);  }
.brick.cream  { --fill: var(--brick-cream);  --stud: var(--stud-cream);  --on-fill: var(--on-cream);  }
.brick.ink    { --fill: var(--brick-ink);    --stud: var(--stud-ink);    --on-fill: var(--on-ink);    }

.brick.ghost {
  background: transparent;
  border: 1px dashed var(--border);
  color: var(--muted);
  box-shadow: none;
  justify-content: center;
  align-items: center;
  padding: 18px;
  cursor: default;
}
.brick.ghost::before { display: none; }
.brick.ghost:hover { transform: none; box-shadow: none; }
.brick.ghost span { font-size: 12px; letter-spacing: .16em; text-transform: uppercase; }

/* ─── Paper card — anything with body copy or controls ────── */
.paper-card {
  position: relative;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1.2rem 1.4rem;
  color: var(--text);
  text-decoration: none;
  box-shadow: none;
  transition: border-color .15s, transform .18s cubic-bezier(.2,.7,.3,1), box-shadow .18s;
}
a.paper-card:hover, a.paper-card:focus-visible {
  border-color: var(--border-2);
  transform: translateY(-2px);
  box-shadow: var(--shadow);
}
.paper-card.ghost { background: transparent; border-style: dashed; cursor: default; }
.paper-card.ghost:hover { transform: none; box-shadow: none; }

/* the coloured stud strip that ties a paper card to a brick */
.paper-card .studs { position: static; margin-bottom: .75rem; }
.paper-card .studs i { width: 10px; height: 10px; }

/* ─── Brick settle ──────────────────────────────────────────
   Bricks drop into place one by one. The script that adds .set must be
   EXTERNAL on apps.anond.app — public/_headers there sets
   `script-src 'self'` with no 'unsafe-inline', so an inline copy is
   blocked and the bricks would stay invisible at opacity 0. That is not
   hypothetical: the same CSP silently killed the apps theme toggle once
   already (see apps/public/theme.js).

   Hence the no-JS floor below: without .set the bricks are still visible.
   ─────────────────────────────────────────────────────────── */
.js-settle .brick, .js-settle .paper-card { opacity: 0; transform: translateY(-34px); }
.js-settle .brick.set, .js-settle .paper-card.set {
  opacity: 1;
  transform: none;
  transition:
    transform 1100ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity 600ms ease,
    box-shadow 240ms ease;
}
.js-settle .brick.set:hover { transform: translateY(-4px); }
.js-settle a.paper-card.set:hover { transform: translateY(-2px); }

/* ─── Footer meta ───────────────────────────────────────────── */
.foot-meta {
  color: var(--muted);
  font-size: 10px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
}
.foot-meta .bar { width: 22px; height: 1px; background: var(--muted); opacity: .5; }

/* ─── Motion ────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
  .js-settle .brick, .js-settle .paper-card { opacity: 1; transform: none; }
}

/* ─── Scrollbars ────────────────────────────────────────────── */
* { scrollbar-color: var(--border-2) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--border-2);
  border-radius: 6px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
*::-webkit-scrollbar-thumb:hover { background: var(--muted); background-clip: padding-box; }

@media (max-width: 640px) {
  .ticks { inset: 8px; }
  .glabel { font-size: 10px; letter-spacing: 0.22em; }
}
