/* ---------- theme tokens ----------
   This block is THE SKIN SURFACE. A skin in skins.css is nothing but a second
   copy of these declarations under a `[data-skin="..."]` selector — which is
   why the list has to stay complete and why nothing outside it may hardcode a
   chrome colour. If a skin can't reach it, a skin can't restyle it, and the
   page ends up half-repainted.

   What is deliberately NOT here: the sixteen category colours (`.c-*`/`.la-*`)
   and the four rarity marks (`.lr-*`). Category = colour is the app's semantic
   layer, and a skin that recoloured it would make higher levels HARDER to read
   — a green Herbalist skin fighting green category chips. Skins own chrome;
   meaning owns its own palette. Don't move those in here. */
:root {
  --surface-0: #131419;
  --surface-1: #191b21;
  --surface-2: #20222a;
  --surface-3: #2a2c36;
  --border: rgba(255,255,255,0.08);
  --border-strong: rgba(255,255,255,0.18);
  --border-accent: #5b9cf2;
  --text-primary: #eeece6;
  --text-secondary: #9c9a95;
  --text-muted: #6b6a64;
  --text-accent: #7db2f5;
  --text-success: #6fd196;
  --bg-success: rgba(111,209,150,0.14);

  /* ---- the radius scale ----
     There were FIFTEEN distinct literal border-radius values in this file and
     only one token. Cards alone used three (8, 10 and 12), which is why two
     cards sitting in the same grid could round differently.

     Five steps, and the names say what they are FOR rather than how big they
     are, because "which radius does a card use" has to have one answer:

       --radius-sm    nested inside a card — thumbnails, inline media, chips
       --radius       CONTROLS: buttons, inputs, selects. 71 uses, unchanged.
       --radius-md    CARDS. Every card, everywhere. One answer.
       --radius-lg    page-level surfaces: modals, overlays, big panels
       --radius-pill  fully round ends; was written as `999px` in 34 places

     --radius-sm was already being used NINE times in styles.css and badge.css
     and was never defined, so all nine rendered through a `6px` fallback that
     no skin could reach. It is a real step in the scale; it just never got
     written down. */
  --radius-sm: 6px;
  --radius: 8px;
  --radius-md: 10px;
  --radius-lg: 12px;
  --radius-pill: 999px;
  --font-sans: -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-voice: Georgia, "Times New Roman", serif;

  /* The accent button's own two colours. These were literals (#0d1117 on the
     label, #6ea8f4 on hover), which meant every skin that changed
     --border-accent produced a primary button with unreadable text over it. */
  --accent-fg: #0d1117;
  --accent-hover: #6ea8f4;

  /* The status family — the four chrome colours that had no token and so were
     the one part of the frame a skin structurally could not reach.

     The line drawn here matters more than the tokens: only APP-WIDE CHROME is
     tokenized. `--danger` is the destructive-action red that every page's
     remove/delete control uses; `--xp` and `--coin` are gamification, which
     lives in the top bar on all twelve pages. Page-local semantic markers are
     NOT here and must not be moved here — the loot table's origin tags, the
     wizard's edited/guess flags and the visibility chips are all meaning
     rather than frame, in the same category as `.c-*`, and a skin recolouring
     them would be a skin changing what the page says. Some of them share a hex
     with a token by coincidence; that's not a reason to merge them. */
  --danger: #e88;
  --xp: #9dc0f0;
  --coin: #e8c877;

  /* The fourth member of that family, and it was MISSING. `--warn-text` was
     referenced twice — by `.shop-warn` and by the Market's debug strip — and
     defined nowhere, so both rendered through their hardcoded `#dbc08a`
     fallback and no skin could reach either. Exactly the half-repainted page
     this block's own comment warns about. It belongs here for the same reason
     `--danger` does: a warning is app-wide chrome, not a page's own meaning.
     The tint and edge are derived-looking but stated, because a warning strip
     needs a surface a skin can lift or drop with the rest of its chrome. */
  --warn-text: #dbc08a;
  --warn-bg: rgba(224,189,82,0.09);
  --warn-border: rgba(224,189,82,0.32);

  /* Two layers the base theme leaves empty and a skin fills in.
     --app-bg is the page's background-image stack: this is where the separate
     BACKGROUNDS axis plugs in, and it is image-free by contract (gradients and
     patterns only), which is what keeps the nothing-loads-from-a-CDN rule
     intact and means a background costs zero bytes and zero requests.
     --chrome-bg is the top bar and sidebar, split from --surface-1 so a skin
     can make the chrome translucent over its background without also making
     every card translucent. */
  --app-bg: none;
  --chrome-bg: var(--surface-1);

  /* Skins that want a lit look set this; everything else leaves it as `none`
     so the default theme has no glow anywhere. Reserved for active/selected
     state only — a glow on every panel reads as a nightclub, not as focus. */
  --glow: none;

  /* The scrollbar's three tokens. DERIVED from the text scale rather than given
     literal values, so every skin already restyles the scrollbar without adding
     a line — Emberlight's bar is warm, Arcane's is violet, for free. A future
     scrollbar skin overrides these three and nothing else.
     They must resolve to OPAQUE colours: a translucent thumb over a patterned
     background is exactly the low-contrast bar this replaces. `--text-muted` and
     `--text-secondary` are solid hexes in :root and in all three skins. */
  --sb-size: 10px;
  --sb-thumb: var(--text-muted);
  --sb-thumb-hover: var(--text-secondary);
}

* { box-sizing: border-box; }

/* ---------- scrollbars ----------
   Slim, opaque, no track, theme-coloured. The app had none of this: every
   scrollbar was the OS default, which on a dark page is a pale grey slab with a
   grey trough — the single loudest piece of chrome on screen and the only one
   that ignored the skin entirely.

   The rules it follows, all of them deliberate:

   * NO TRACK. A trough is a permanent 10px stripe down every scrollable panel
     saying nothing. The thumb alone communicates position and size.
   * FULLY OPAQUE THUMB. A translucent bar over a patterned background is the
     low-contrast mess this replaces — and `--app-bg` means there IS a pattern
     behind it on most skins.
   * CONTRAST FROM A SHADOW, not from a background. A dark halo under the thumb
     separates it from whatever it crosses without painting a box around it,
     which is what lets the track go away.
   * ARROWS ARE DRAWN WITH GRADIENTS. Two crossed linear-gradients make a
     triangle, so there is no `url()` and nothing to load — the same
     math-drawn-only rule the backgrounds follow. Styling `::-webkit-scrollbar`
     at all removes the platform arrows, so they have to be rebuilt or they
     simply vanish.

   Chrome and Edge get all of it. Firefox has no arrow pseudo-element and no
   shadow — `scrollbar-width`/`scrollbar-color` is the whole standard API — so it
   gets the slim opaque thumb and a transparent track, which is the important
   half. Safari matches Chrome. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--sb-thumb) transparent;
}

::-webkit-scrollbar {
  width: var(--sb-size);
  height: var(--sb-size);
}
::-webkit-scrollbar-track,
::-webkit-scrollbar-corner {
  background: transparent;
}
::-webkit-scrollbar-thumb {
  background: var(--sb-thumb);
  border-radius: 10px;
  /* The inset border is what makes a 10px gutter carry a 6px bar: it reserves
     transparent padding inside the thumb instead of shrinking the hit area, so
     the bar reads slim while staying comfortable to grab. */
  border: 2px solid transparent;
  background-clip: content-box;
  box-shadow: 0 0 3px rgba(0,0,0,0.55);
}
::-webkit-scrollbar-thumb:hover { background: var(--sb-thumb-hover); background-clip: content-box; }
::-webkit-scrollbar-thumb:active { background: var(--text-primary); background-clip: content-box; }

/* Arrows. `display: block` is required — an unstyled button collapses to zero
   and the ends of the bar go blank. */
::-webkit-scrollbar-button {
  display: block;
  background-repeat: no-repeat;
  background-color: transparent;
  height: var(--sb-size);
  width: var(--sb-size);
}
::-webkit-scrollbar-button:vertical:decrement {
  background-image:
    linear-gradient(to bottom left, transparent 50%, var(--sb-thumb) 50%),
    linear-gradient(to bottom right, transparent 50%, var(--sb-thumb) 50%);
  background-size: 50% 45%, 50% 45%;
  background-position: 15% 55%, 85% 55%;
}
::-webkit-scrollbar-button:vertical:increment {
  background-image:
    linear-gradient(to top left, transparent 50%, var(--sb-thumb) 50%),
    linear-gradient(to top right, transparent 50%, var(--sb-thumb) 50%);
  background-size: 50% 45%, 50% 45%;
  background-position: 15% 45%, 85% 45%;
}
::-webkit-scrollbar-button:horizontal:decrement {
  background-image:
    linear-gradient(to top right, transparent 50%, var(--sb-thumb) 50%),
    linear-gradient(to bottom right, transparent 50%, var(--sb-thumb) 50%);
  background-size: 45% 50%, 45% 50%;
  background-position: 55% 15%, 55% 85%;
}
::-webkit-scrollbar-button:horizontal:increment {
  background-image:
    linear-gradient(to top left, transparent 50%, var(--sb-thumb) 50%),
    linear-gradient(to bottom left, transparent 50%, var(--sb-thumb) 50%);
  background-size: 45% 50%, 45% 50%;
  background-position: 45% 15%, 45% 85%;
}
::-webkit-scrollbar-button:hover { filter: brightness(1.6); }
/* The doubled buttons Chrome puts at the far end of each axis. Left as blank
   space rather than a second pair of arrows nobody uses. */
::-webkit-scrollbar-button:vertical:start:increment,
::-webkit-scrollbar-button:vertical:end:decrement,
::-webkit-scrollbar-button:horizontal:start:increment,
::-webkit-scrollbar-button:horizontal:end:decrement {
  display: none;
}

body {
  font-family: var(--font-sans);
  background-color: var(--surface-0);
  /* Fixed, so a tiled pattern doesn't visibly crawl or seam while a long page
     scrolls under it — the ambience is meant to sit behind the app, not ride
     along with it. */
  background-image: var(--app-bg);
  background-size: var(--app-bg-size, auto);
  background-position: var(--app-bg-pos, 0 0);
  /* Tiling is opt-in now. The smooth backgrounds set no-repeat and size
     themselves to the viewport; only the geometric ones want a repeat. */
  background-repeat: var(--app-bg-repeat, repeat);
  background-attachment: fixed;
  color: var(--text-primary);
  margin: 0;
  padding: 0;
  font-size: 13px;
  line-height: 1.6;
}

input, select, textarea, button {
  font-family: var(--font-sans);
  font-size: 13px;
  border: 0.5px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 7px 10px;
  background: var(--surface-2);
  color: var(--text-primary);
  /* Native popups — the select's own dropdown list and the date picker — are
     drawn by the browser, not by this stylesheet. Without this they render in
     light mode, which is how a muted select ended up as pale text on white. */
  color-scheme: dark;
}
textarea { resize: vertical; }

/* Belt and braces for the dropdown list: a select's closed state is often
   deliberately muted (the Camp panel controls), but the options themselves
   must always be legible, so they get their own colours rather than
   inheriting the control's. */
option {
  background-color: var(--surface-2);
  color: var(--text-primary);
}

button {
  cursor: pointer;
  background: var(--surface-2);
}
button:hover { background: var(--surface-3); }
button.btn-plain { background: transparent; border-color: transparent; }
button.btn-plain:hover { background: var(--surface-2); }
button.btn-primary { background: var(--border-accent); border-color: var(--border-accent); color: var(--accent-fg); font-weight: 600; }
button.btn-primary:hover { background: var(--accent-hover); }
button.btn-danger { color: var(--danger); }
button.btn-danger:hover { background: rgba(230,110,110,0.12); }
button:disabled { opacity: 0.4; cursor: not-allowed; }

/* A BUTTON HAS THREE SIZES, AND THAT LIST IS CLOSED — the same rule
   `.view-switch` already follows, generalised to the control it was always
   going to be needed on. Before this, four page blocks each invented their own:
   `4px 6px/12px` in the Codex, `4px 10px/12px` on the loot commands, `12px`
   alone on the taxonomy head and `2px 8px/11px` in the import wizard.

     (default)  6px 10px / 13px — a button on its own in a form or a toolbar
     .btn-sm    4px 9px / 12px  — inside a card, or in a dense row of actions
     .btn-xs    2px 8px / 11px  — inside a control strip, where the button is
                                  a modifier on something else on screen

   They differ in padding and type size only, so a button can be resized without
   being restyled — and `.btn-plain`, `.btn-primary` and `.btn-danger` compose
   with all three. A fourth size belongs here, never as a page-local override of
   `.btn-plain`. */
button.btn-sm { padding: 4px 9px; font-size: 12px; }
button.btn-xs { padding: 2px 8px; font-size: 11px; }

/* AN ANCHOR WEARING A BUTTON CLASS GOT NOTHING. The control baseline is on the
   selector `input, select, textarea, button`, and `.btn-plain` and friends are
   qualified with `button` — so `<a class="btn-plain">` matched none of it and
   rendered as ordinary link text. Thirteen anchors do this, and it only ever
   LOOKED right where a page had separately restated the baseline for its own
   anchors (`.loot-command-row a.btn-plain` did, `.codex-card-actions a` did,
   most didn't).

   This gives anchors the same starting point, so "looks like a button" stops
   depending on which page you are on. `inline-flex` rather than the inline
   default, because these all carry an icon beside their label. */
a.btn-plain, a.btn-primary, a.btn-danger, a.btn-sm, a.btn-xs {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-sans); font-size: 13px; line-height: 1.2;
  padding: 6px 10px;
  border: 0.5px solid transparent;
  border-radius: var(--radius);
  text-decoration: none; cursor: pointer;
  color: var(--text-accent);
}
a.btn-plain:hover, a.btn-sm:hover, a.btn-xs:hover { background: var(--surface-2); }
a.btn-primary {
  background: var(--border-accent); border-color: var(--border-accent);
  color: var(--accent-fg); font-weight: 600;
}
a.btn-primary:hover { background: var(--accent-hover); }
a.btn-danger { color: var(--danger); }
a.btn-danger:hover { background: rgba(230,110,110,0.12); }
a.btn-sm { padding: 4px 9px; font-size: 12px; }
a.btn-xs { padding: 2px 8px; font-size: 11px; }

/* Full width, for a button that sits at the foot of a detail panel and acts on
   the whole thing. This was `style="width:100%"` in FIFTEEN places across six
   page scripts — five of them also carrying `margin-top:6px`, which is why the
   gap above a Delete button was 6px on some panels and 0 on others.

   The rule the inline styles were breaking, and the one worth keeping: a
   COMPUTED value belongs inline (`width:${pct}%` on a progress bar has to be),
   a CONSTANT one belongs in a class. An inline constant is a rule no skin can
   reach, no test can find and nothing else can reuse. */
button.btn-block { width: 100%; }
button.btn-block + button.btn-block { margin-top: 6px; }

label { font-size: 11px; color: var(--text-muted); text-transform: lowercase; }

/* ---------- read-only display + edit toggle ----------
   Every editable surface in the app has two states: a calm read-only render
   for browsing, and the existing input form revealed by a pencil. Saving is
   unchanged — fields still save on `change`; "Done" only collapses the form
   back to the read-only view. */
.edit-toggle {
  border: none;
  background: transparent;
  color: var(--text-muted);
  padding: 3px 6px;
  border-radius: var(--radius);
  flex-shrink: 0;
  line-height: 1;
}
.edit-toggle:hover { color: var(--text-primary); background: var(--surface-2); }
.edit-toggle i { font-size: 13px; }
/* Muted until hovered, so a page of entries doesn't read as a row of buttons. */
.edit-toggle.subtle { opacity: 0.4; }
.edit-toggle.subtle:hover { opacity: 1; }

.is-editing { border-color: var(--border-accent); }

/* Several inputs are styled borderless-until-hover so they read as headings
   while browsing. Inside an open editor that's the wrong signal — show them
   as the fields they are. */
.is-editing .recipe-name-input,
.is-editing .recipe-desc-input,
.is-editing .journal-title-input,
.is-editing .journal-weather input {
  border-color: var(--border-strong);
  background: var(--surface-2);
  color: var(--text-primary);
}

.edit-done-row {
  display: flex;
  justify-content: flex-end;
  gap: 6px;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 0.5px solid var(--border);
}

.ro-field { margin-bottom: 12px; }
.ro-field > label { display: block; margin-bottom: 2px; }
.ro-value { font-size: 13px; color: var(--text-primary); white-space: pre-wrap; word-break: break-word; }
.ro-value.empty { color: var(--text-muted); font-style: italic; }
.ro-inline { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* ---------- .chip ----------
   A small pill stating one fact. Four components did this: `.ro-chip` (28 uses,
   a record's field in read mode), `.search-chip` (a search filter — metrically
   IDENTICAL, 11px/3px 9px/pill, a pure duplicate), `.record-chip` and
   `.quest-cat-chip` (category labels at 10px, differing from each other only by
   1px of padding and a radius nobody chose).

   Two sizes and one behavioural hook, and that list is closed:

     (default)     a fact in a detail panel or a header
     .chip-sm      a label on a card, where the card's own title leads
     .chip-action  the chip is a CONTROL you press, not a label you read

   `.chip-action` exists so a future effect can target "chips you can press"
   separately from "chips that state a fact" — different function, different
   product, rather than one blanket "all chips" effect. Nothing sells against it
   yet; the hook is the point.

   TWO THINGS DELIBERATELY LEFT OUT of this collapse:

   `.loot-chip` is a BUTTON containing a rarity dot, a name and a price. It is
   an interactive stat block, not a text pill, and folding it in would mean one
   component covering both "a word" and "a control with three parts" — the same
   argument that kept the Loot Table's own card grid separate.

   `.vis-chip` was never a sixth chip. It is already a modifier worn alongside
   this one (`class="chip vis-chip vis-public"`), which is why it has always
   inherited the chip effects for free. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--text-secondary);
  border: 0.5px solid var(--border);
}
.chip.strong { color: var(--text-primary); }
/* Smaller, and border-less because these carry a `.c-*` category colour that
   supplies their own background — an outline on top of a tint reads as two
   boxes. */
.chip-sm { font-size: 10px; padding: 2px 8px; border-color: transparent; }
.chip-action { cursor: pointer; }
.chip-action:hover { border-color: var(--border-strong); color: var(--text-primary); }
.ro-head-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.ro-title { font-size: 15px; font-weight: 500; margin: 0; word-break: break-word; }

/* read-only renders of block-structured bodies (Spellbook steps/notes) */
.recipe-desc-ro { font-size: 12px; color: var(--text-secondary); margin: 2px 0 14px; }
.ro-block { margin-bottom: 14px; }
.ro-block-title { font-size: 13px; font-weight: 600; margin: 0 0 6px; }
.ro-steps { margin: 0; padding-left: 20px; font-size: 13px; line-height: 1.8; }
.ro-notes { font-size: 13px; line-height: 1.7; color: var(--text-secondary); white-space: pre-wrap; margin: 0; }
.ingredient-need { font-size: 10px; color: var(--text-muted); margin: 0; }

/* an edit form that replaces a read-only section in place, rather than a
   whole card — needs its own frame so the boundary is obvious */
.edit-shell {
  background: var(--surface-1);
  border: 0.5px solid var(--border-accent);
  border-radius: var(--radius);
  padding: 12px;
  margin-bottom: 8px;
}
.fn-sciname-ro { font-size: 12px; font-style: italic; color: var(--text-secondary); }
.fn-obs-date-ro { font-size: 12px; font-weight: 500; color: var(--text-secondary); }
.fn-observation.is-editing { border-color: var(--border-accent); }

/* ---------- category colors ----------
   THE PALETTE IS TUNED FOR SEPARATION, NOT FOR PRETTINESS. Sixteen categories
   have to be told apart at chip size, in a legend, and as a 3px stripe down a
   card edge — so what matters is the distance between any two, not how nice any
   one looks alone.

   The first sixteen were all pastel tints of roughly the same value: every
   colour sat between 64% and 79% lightness, so hue was doing ALL the work and
   several pairs were doing none. `slate` (212°) and `blue` (210°) were the same
   hue at the same lightness and differed only in saturation; `gray` (45°) and
   `sand` (44°) likewise. At 9px in a chip those are one colour.

   The current set spans 58%–84% lightness, and the rule it satisfies is: no two
   colours are within 25° of hue AND 8 points of lightness AND 25 points of
   saturation. So any pair that is close in one dimension is far apart in
   another. Minimum contrast against `--surface-1` is 5.4:1, comfortably past AA
   for body text — the old palette scored higher on raw contrast and far worse on
   the thing that actually matters here.

   Generated from an HSL spec so the two blocks can't drift; if you retune, keep
   `.c-*` and `.la-*` in step and re-check both rules.
   Adding a SEVENTEENTH means updating FOUR places: `.c-*` and `.la-*` here,
   CATEGORY_COLORS in server.js, and PALETTE in each page script. */
.c-crimson  { background: rgba(184,25,38,0.18); color: #df6d76; }
.c-coral    { background: rgba(235,74,25,0.18); color: #eda791; }
.c-bronze   { background: rgba(137,83,36,0.18); color: #c59163; }
.c-amber    { background: rgba(223,156,22,0.18); color: #eac886; }
.c-sand     { background: rgba(204,173,102,0.18); color: #e6dcc7; }
.c-gray     { background: rgba(255,255,255,0.09); color: #a3a3a3; }
.c-lime     { background: rgba(192,220,56,0.18); color: #dde7a6; }
.c-green    { background: rgba(74,190,45,0.18); color: #9cd98c; }
.c-teal     { background: rgba(28,156,124,0.18); color: #5ed4b7; }
.c-cyan     { background: rgba(13,196,242,0.18); color: #89dcf0; }
.c-blue     { background: rgba(30,86,169,0.18); color: #6a96d7; }
.c-slate    { background: rgba(115,148,186,0.18); color: #cad3dd; }
.c-indigo   { background: rgba(27,27,222,0.18); color: #8d8de8; }
.c-purple   { background: rgba(138,64,221,0.18); color: #caaeea; }
.c-magenta  { background: rgba(185,39,196,0.18); color: #d788dd; }
.c-pink     { background: rgba(235,51,137,0.18); color: #f0a8ca; }

.cat-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; }
/* The active profession chip (Professions v2) — a subtle ring + play glyph so the
   one you're currently playing as reads apart from the rest of your professions. */
.chip.is-active-prof { outline: 1.5px solid currentColor; outline-offset: 1px; font-weight: 600; }
.chip.is-active-prof i { font-size: 9px; opacity: 0.85; }
.active-pick.picked { outline: 1.5px solid currentColor; outline-offset: 1px; }

/* ---------- top bar ---------- */
.topbar {
  position: sticky;
  top: 0;
  z-index: 900;
  height: 48px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 16px 0 12px;
  background: var(--chrome-bg);
  border-bottom: 0.5px solid var(--border);
}
.topbar-brand {
  display: flex;
  align-items: center;
  gap: 8px;
  /* Equal share with .topbar-right — see the note there. */
  flex: 1 1 0;
  min-width: 0;
  text-decoration: none;
  color: var(--text-primary);
}
.topbar-logo {
  width: 26px;
  height: 26px;
  border-radius: 7px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(45,181,150,0.18);
  color: #6fd6ba;
  flex-shrink: 0;
}
.topbar-logo i { font-size: 15px; }
.topbar-name { font-size: 13px; font-weight: 600; letter-spacing: 0.01em; white-space: nowrap; }
.topbar-brand:hover .topbar-name { color: var(--text-accent); }
/* The right spacer is the same width as the brand so the search box sits
   optically centred rather than just "after the logo". */
/* Both sides take an equal share so the search stays centred no matter what
   they contain. It used to be a fixed 158px spacer, which worked while the
   right side held only the account chip — coins and a bell overflowed it and
   pushed the chip onto a second row. */
.topbar-right {
  flex: 1 1 0;
  /* NOT min-width: 0. That's what let this side be crushed below its own
     contents at in-between widths, so the coin pill, the bell and the account
     chip started overlapping each other and the search box. Leaving it at the
     default `auto` means flex refuses to shrink it past what it holds, and the
     search — which has plenty of slack — gives up the space instead. */
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
}

/* The search box RESTS SMALL so it stops crowding the coin pill / bell / chip
   at in-between widths (the user's note: "it covers many things"). It was a
   greedy 500px; 260 is plenty to read as a search box. The input itself does
   NOT grow on focus — `.topbar-right` grows to fill the row, so there is no
   free space for it to claim — but it doesn't need to: the results dropdown
   below carries its own comfortable width, so a small box still shows full
   results the moment you type. The ≤560px rule further down fills the row. */
.topbar-search { flex: 0 1 260px; min-width: 0; margin: 0 auto; position: relative; }
.search-box {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--surface-2);
  border: 0.5px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 0 10px;
  height: 32px;
}
.search-box:focus-within { border-color: var(--border-accent); }
.search-box > i { font-size: 15px; color: var(--text-muted); flex-shrink: 0; }
.search-box input {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: none;
  padding: 0;
  font-size: 13px;
  height: 100%;
}
.search-box input:focus { outline: none; }
.search-box input::-webkit-search-cancel-button { filter: invert(0.6); }
.search-kbd {
  font-family: var(--font-sans);
  font-size: 10px;
  color: var(--text-muted);
  border: 0.5px solid var(--border);
  border-radius: 4px;
  padding: 2px 5px;
  flex-shrink: 0;
  white-space: nowrap;
}

/* ---------- search results panel ---------- */
/* Anchored to the box's RIGHT edge and given its own comfortable width, so the
   now-small input still opens full-width, readable results — it grows leftward
   rather than being pinned to the 260px input. Capped to the viewport so it
   never overflows on a narrow screen. */
.search-results {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  left: auto;
  width: 380px;
  min-width: 100%;
  max-width: min(420px, calc(100vw - 24px));
  max-height: 68vh;
  overflow-y: auto;
  background: var(--surface-1);
  border: 0.5px solid var(--border-strong);
  border-radius: 10px;
  box-shadow: 0 12px 36px rgba(0,0,0,0.55);
  padding: 6px;
  z-index: 950;
}
.search-chips {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  padding: 4px 4px 8px;
  border-bottom: 0.5px solid var(--border);
  margin-bottom: 4px;
}
/* The search filters are chips now — .chip plus .chip-action, because they are
   controls you press rather than labels you read. Only the count badge and the
   selected state are this strip's own. */
.search-chips .chip span { color: var(--text-muted); margin-left: 3px; }
.search-chips .chip.active { background: var(--surface-3); border-color: var(--border-strong); color: var(--text-primary); }

.search-group { margin-bottom: 6px; }
.search-group-head {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  padding: 6px 8px 4px;
}
.search-group-head i { font-size: 12px; }
.search-group-count { margin-left: auto; letter-spacing: 0; }

.search-row {
  display: flex;
  gap: 9px;
  align-items: flex-start;
  padding: 7px 8px;
  border-radius: var(--radius);
  text-decoration: none;
  color: inherit;
}
.search-row.active { background: var(--surface-2); }
.search-row-icon {
  width: 24px;
  height: 24px;
  border-radius: 5px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  margin-top: 1px;
}
.search-row-main { display: flex; flex-direction: column; min-width: 0; flex: 1; gap: 1px; }
.search-row-name { font-size: 13px; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.search-row-sub { font-size: 11px; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.search-row-snippet {
  font-size: 11px;
  color: var(--text-secondary);
  line-height: 1.55;
  margin-top: 3px;
  padding-left: 7px;
  border-left: 2px solid var(--border);
}
.search-matched-in {
  display: block;
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin-bottom: 1px;
}
.search-results mark { background: rgba(91,156,242,0.28); color: var(--text-primary); border-radius: 2px; padding: 0 1px; }

.search-more {
  width: 100%;
  text-align: left;
  font-size: 11px;
  color: var(--text-accent);
  background: transparent;
  border: none;
  padding: 5px 8px;
}
.search-more:hover { background: var(--surface-2); }
.search-foot {
  font-size: 10px;
  color: var(--text-muted);
  padding: 7px 8px 3px;
  border-top: 0.5px solid var(--border);
  margin-top: 4px;
  display: flex;
  gap: 4px;
  align-items: center;
}
.search-foot kbd {
  font-family: var(--font-sans);
  border: 0.5px solid var(--border);
  border-radius: 3px;
  padding: 1px 4px;
}

/* ---------- app shell ---------- */
.app-shell {
  display: flex;
  min-height: calc(100vh - 48px);
}

.sidebar {
  width: 170px;
  flex-shrink: 0;
  background: var(--chrome-bg);
  border-right: 0.5px solid var(--border);
  padding: 14px 12px 20px;
  display: flex;
  flex-direction: column;
  /* Pinned under the top bar so navigation stays reachable however far a long
     page scrolls. */
  position: sticky;
  top: 48px;
  height: calc(100vh - 48px);
  overflow-y: auto;
}

/* The drawer's two pieces, both injected by account.js and both inert on a wide
   screen — the rail is always visible up there, so the toggle has nothing to
   toggle and the scrim has nothing to cover. They are `display:none` /
   `pointer-events:none` rather than absent so the narrow-width rules only have
   to switch them on. */
.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  padding: 0;
  background: transparent;
  border: none;
  color: var(--text-secondary);
  border-radius: var(--radius);
}
.nav-toggle:hover { color: var(--text-primary); background: var(--surface-2); }
.nav-toggle i { font-size: 20px; }
.nav-scrim {
  position: fixed;
  inset: 48px 0 0 0;
  background: rgba(0,0,0,0.55);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s ease;
  z-index: 950;
}

/* The logo is the menu toggle now (account.js swaps its glyph to ☰ / ✕ and
   intercepts the click). It reads as pressable, and no longer navigates home. */
.topbar-brand { cursor: pointer; }
.topbar-brand:hover .topbar-logo { background: rgba(45,181,150,0.30); color: #8fe6ce; }
/* Desktop: collapsing hides the rail so `.main` (flex:1) fills the frame. On a
   phone the sidebar is an overlay drawer (`nav-open`) instead, so this rule must
   stay desktop-only or it would hide that drawer. */
@media (min-width: 861px) {
  :root.nav-collapsed .sidebar { display: none; }
}

.nav-list { display: flex; flex-direction: column; gap: 2px; }
.nav-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border-radius: var(--radius);
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 13px;
  border: 0.5px solid transparent;
}
.nav-item i { font-size: 15px; }
.nav-item:hover { background: var(--surface-2); }
.nav-item.active {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text-primary);
  font-weight: 500;
}
.nav-item.disabled { opacity: 0.4; cursor: default; }
.nav-item.disabled:hover { background: transparent; }
.nav-footer { margin-top: auto; padding-top: 12px; border-top: 0.5px solid var(--border); }

.main { flex: 1; padding: 20px 24px; min-width: 0; }
/* `align-items: flex-start` rather than centre: the title block is now two lines
   on most pages, and a button vertically centred against a two-line block sits
   oddly low. */
.page-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 16px; }
.page-header h1 { font-size: 20px; margin: 0; }
/* The title block takes the row's spare width so the description gets a full
   line, and the action button keeps its own size. Without `flex: 1` the wrapper
   is sized by its widest child and a 70-character sentence wraps after eight
   words with half the row empty beside it. */
.page-header > div:first-child { flex: 1; min-width: 0; }
.page-header > .btn-primary,
.page-header > button,
.page-header > .market-balance { flex-shrink: 0; }
.page-desc {
  font-size: 12px;
  color: var(--text-muted);
  margin: 3px 0 0;
  max-width: 78ch;
  line-height: 1.5;
}

/* ---------- the minimalist switch ----------
   `data-hints="off"` on <html>, set from the paint cache before the body paints.

   THE LIST BELOW WAS BUILT BY READING EACH STRING, not by guessing at class
   names — the first draft named five classes that don't exist in this codebase,
   which would have shipped a switch that silently hid four things instead of
   nine. Grep before adding to it.

   WHAT GETS HIDDEN: copy that TEACHES. Sentences a fluent user has already read
   once and will never need again.

   WHAT DELIBERATELY DOES NOT, even though it looks the same shape:
   * `.composer-consent` and `.copy-note` state a PRIVACY CONSEQUENCE of the
     button you are about to press ("attaching posts a snapshot", "the copy is
     yours and private"). Hiding a tutorial saves you a sentence; hiding this
     changes what you knew when you shared something. Different kind of text.
   * `.wiz-mode-hint` is decision support inside the import wizard — it is read
     at the moment of choosing, every time, not once.
   * `.journal-hint` is an empty state. Hidden, the panel is just blank.
   * `.inv-result-note` and `.map-coord-note` report STATE ("3 of 7 shown",
     "accuracy not recorded"), which is data, not explanation.

   Every page description hidden here is also on its `<h1>`'s `title`, so the
   switch compresses the app rather than deleting from it. */
:root[data-hints="off"] .page-desc,
:root[data-hints="off"] .tax-hint,
:root[data-hints="off"] .quest-hint,
:root[data-hints="off"] .import-hint,
:root[data-hints="off"] .field-hint,
:root[data-hints="off"] .admin-hint,
:root[data-hints="off"] .vis-hint,
:root[data-hints="off"] .shop-slot-hint,
:root[data-hints="off"] .profile-note { display: none; }

/* ---------- stat cards ---------- */
.stat-row { display: flex; gap: 10px; margin-bottom: 16px; }
.stat-card {
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-md);
  padding: 10px 14px;
  flex: 1;
}
.stat-card .s-label { font-size: 11px; color: var(--text-muted); }
.stat-card .s-value { font-size: 20px; font-weight: 600; margin-top: 2px; }

/* ---------- inventory layout ---------- */
.inventory-layout { display: flex; gap: 20px; align-items: flex-start; }
.inventory-main { flex: 1; min-width: 0; }
.inventory-side { width: 230px; flex-shrink: 0; }
.side-label { font-size: 12px; color: var(--text-muted); margin: 0 0 8px; }

/* `.tabs` / `.tab-btn` used to live here — a SECOND segmented control doing the
   same job as `.view-switch` with a different look, and the two were handed out
   inconsistently: the Market's five tabs and the profile's three already used
   `.view-switch` for exactly the "which subset of content" question `.tabs` was
   supposedly for. Everything is `.view-switch` now (see the loot table section
   for the component and its three sizes). Inventory, Quests, Codex, Field
   Notes, login and the export dialog changed appearance to match: loose text
   buttons became a contained pill track.

   `.tabs .spacer { flex: 1 }` went with it. It was a no-op inside a
   background-less strip and would have stretched the pill track across the
   window; the three trailing spacers in the markup are gone. */

.toolbar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; flex-wrap: wrap; }
.toolbar input[type="search"] { flex: 1; }

.tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
  gap: 8px;
}
.tile {
  aspect-ratio: 1;
  border-radius: var(--radius);
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  cursor: pointer;
  border: 1px solid transparent;
  background: var(--surface-2);
}
.tile i { font-size: 20px; }
.tile:hover { outline: 1px solid var(--border-strong); }
.tile.selected { outline: 2px solid var(--border-accent); }
.tile.need {
  background: rgba(255,255,255,0.03);
  border: 1px dashed var(--border-strong);
  opacity: 0.55;
}
.tile .qty-badge {
  position: absolute;
  bottom: 3px;
  right: 5px;
  font-size: 10px;
  font-weight: 600;
  color: var(--text-primary);
}
/* A wanted tile still shows its target amount, just quieter (the tile is already
   dashed + dimmed, so the number needn't shout that it isn't held). */
.tile .qty-badge.is-need { color: var(--text-secondary); font-weight: 500; }
/* Freshness dot — top-left corner, only when a held perishable is going off.
   Amber for "use soon", danger for "past its life". The one signal worth this
   little room. */
.tile-fresh {
  position: absolute; top: 4px; left: 4px;
  width: 7px; height: 7px; border-radius: 50%;
}
.tile-fresh.due { background: #dbc08a; }
.tile-fresh.over { background: var(--danger); }

/* ---------- detail / edit panel ---------- */
.detail-panel {
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: 12px;
  padding: 16px;
  min-height: 320px;
}

/* ---- DetailModal (shared.js) — Option B ----
   A record's home-page detail, shown as a full-width centred modal instead of a
   cramped side rail. The page's own detail element is MOVED into `.detail-modal-slot`,
   so it keeps its id, styling and wiring; these rules just give it a roomy shell
   and a scrim. Modal z-band is [1000,1099]; the scrim sits at the floor of it. */
.detail-modal-overlay {
  position: fixed; inset: 0; z-index: 1000;
  display: grid; place-items: start center;
  padding: 28px 20px;
  overflow-y: auto;
  background: color-mix(in srgb, #000 46%, transparent);
  animation: detail-modal-in 0.16s ease;
}
@supports not (background: color-mix(in srgb, #000 46%, transparent)) {
  .detail-modal-overlay { background: rgba(0,0,0,0.5); }
}
.detail-modal-overlay.hidden { display: none; }
@keyframes detail-modal-in { from { opacity: 0; } }
.detail-modal {
  position: relative;
  width: min(680px, 100%);
  margin: auto;
  background: var(--surface-1);
  border: 0.5px solid var(--border-strong);
  border-radius: var(--radius-lg);
  box-shadow: 0 30px 70px -20px rgba(0,0,0,0.6);
  animation: detail-modal-pop 0.18s ease;
}
/* EDIT MODE WANTS A FORM WIDTH, NOT THE READ WIDTH. Read content (a recipe with
   two columns, a quest with objectives) earns the full 680px; an edit form is a
   single column of inputs, and at 680 a name field is 600px and a quantity field
   ~470px — the old 230px rail form stretched into a room too big for it. When the
   panel is being edited, narrow the modal to a comfortable measure. */
.detail-modal:has(> .detail-modal-slot > .detail-panel.is-editing) { width: min(520px, 100%); }
@keyframes detail-modal-pop { from { opacity: 0; transform: translateY(8px); } }
.detail-modal-close {
  position: absolute; top: 12px; right: 12px; z-index: 2;
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--surface-2); border: 0.5px solid var(--border);
  color: var(--text-secondary); cursor: pointer;
  display: flex; align-items: center; justify-content: center; font-size: 15px;
}
.detail-modal-close:hover { color: var(--text-primary); }
/* The relocated panel fills the modal — its home-page 380px fixed width and its
   card border/shadow are the rail's clothes, not the modal's. */
.detail-modal-slot > .detail-panel,
.detail-modal-slot > .fn-profile {
  width: auto; border: none; border-radius: var(--radius-lg);
  box-shadow: none; min-height: 0; padding: 20px 22px;
}
/* THE CLOSE X SHARES THE TOP-RIGHT CORNER WITH THE PAGE'S OWN EDIT/SHARE
   CONTROLS. Every converted detail leads with a flex header whose controls sit
   at the right — exactly where the X is — so without this the X paints over the
   edit pencil and swallows its clicks (the reason editing looked lost). Reserve
   the corner, the same fix .loot-modal-head already uses for its own edit-toggle.
   Scoped to the modal, so these headers are untouched on their normal pages. */
.detail-modal-slot .ro-head-row,
.detail-modal-slot .fg-d-head { padding-right: 34px; }
/* A quantity/size is a small number: it shouldn't stretch to fill the modal
   next to its unit dropdown. In the rail it was naturally narrow; here it grows
   unless told not to, so it forms a tidy group with the unit and leaves the rest
   of the row as space. */
.detail-modal-slot .amount-row { max-width: 300px; }
.detail-modal-slot .amount-row input[type="number"] { flex: 1 1 auto; }
/* The board/list behind a modal shouldn't scroll under it on desktop. */
body.detail-modal-open { overflow: hidden; }
.detail-panel .field { margin-bottom: 10px; }
.detail-panel .field label { display: block; margin-bottom: 4px; }
.detail-panel input, .detail-panel select, .detail-panel textarea { width: 100%; }
.detail-panel textarea { height: 60px; }
.detail-icon-preview {
  width: 38px; height: 38px;
  border-radius: var(--radius);
  display: flex; align-items: center; justify-content: center;
  margin-bottom: 12px;
}
.detail-icon-preview i { font-size: 20px; }
.detail-actions { display: flex; gap: 6px; margin-top: 12px; }
.detail-actions .btn-toggle { flex: 1; font-size: 12px; }
.detail-actions .btn-delete { width: 38px; flex-shrink: 0; }

/* ---------- category editor ---------- */
.icon-btn {
  border: none;
  background: transparent;
  padding: 0 0 0 4px;
  color: var(--text-muted);
  font-size: 11px;
  vertical-align: middle;
}
.icon-btn:hover { color: var(--text-primary); background: transparent; }
.category-editor {
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  padding: 10px;
  margin-top: 6px;
}
.category-editor input { width: 100%; margin-top: 4px; }
.category-editor-actions { display: flex; gap: 6px; margin-top: 10px; }
.category-editor-actions button { flex: 1; font-size: 12px; }
/* ---------- the colour picker ----------
   A COLOUR PICKER MUST SHOW THE COLOUR. This one never did.

   Every call site writes `class="color-swatch c-teal"`, and `.c-*` is built for
   a CHIP: a translucent tint for the background and the solid hue for the text.
   So the swatch was painting an 18%-alpha wash over whatever surface it sat on —
   sixteen barely-different greys, in the one control whose entire job is letting
   you compare sixteen colours. It also meant the swatch changed appearance
   depending on the panel behind it.

   `currentColor` is the fix and it needs no change at any call site: `.c-*`
   already sets `color` to the solid hue, so the swatch simply paints its own
   text colour as its background. Two classes beat one, so this wins over `.c-*`
   without depending on source order.

   Bigger, too — 22px circles are under the 24px hit target and too small to
   judge a hue against its neighbours. */
.color-swatch-row { display: flex; flex-wrap: wrap; gap: 7px; margin: 9px 0 2px; }
.color-swatch-row .color-swatch {
  width: 28px;
  height: 28px;
  border-radius: 8px;
  padding: 0;
  background: currentColor;
  /* The ring is drawn OUTSIDE the swatch so it never eats into the colour —
     a border would shrink the only thing being judged. */
  border: none;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.45);
  cursor: pointer;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.color-swatch-row .color-swatch:hover { transform: translateY(-2px); }
/* Selected reads two ways at once, because one of them fails on some colours: a
   light ring is invisible against `sand`, and a dark one against `bronze`. The
   dark inner ring plus the light outer ring means whichever the colour defeats,
   the other still shows. */
.color-swatch-row .color-swatch.selected {
  box-shadow: 0 0 0 2px var(--surface-1), 0 0 0 4px var(--text-primary);
  transform: translateY(-2px);
}
.color-swatch-row .color-swatch:focus-visible {
  outline: 2px solid var(--text-accent);
  outline-offset: 3px;
}

/* ---------- icon picker ---------- */
.icon-picker-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 4px;
  margin: 6px 0 10px;
  max-height: 168px;
  overflow-y: auto;
}
.icon-swatch {
  aspect-ratio: 1;
  border-radius: var(--radius);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  border: 2px solid transparent;
  opacity: 0.55;
}
.icon-swatch img { width: 15px; height: 15px; object-fit: contain; }
.icon-swatch i { font-size: 15px; }
.icon-swatch:hover { opacity: 0.8; }
.icon-swatch.selected { opacity: 1; border-color: var(--border-accent); }
.icon-swatch.custom-more {
  border: 0.5px dashed var(--border-strong);
  color: var(--text-muted);
  opacity: 1;
  background: transparent;
}

/* ---------- spellbook ---------- */
.spellbook-layout { display: flex; gap: 20px; align-items: flex-start; }
.spellbook-side { width: 220px; flex-shrink: 0; }
.spellbook-side input[type="search"] { width: 100%; margin-bottom: 8px; }
/* The gap this button used to carry as an inline margin-top. It belongs to the
   column's rhythm, not to the button — the same control in a toolbar row wants
   no top margin at all. */
.spellbook-side > .btn-block { margin-top: 8px; }
.recipe-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 480px;
  overflow-y: auto;
}
.recipe-list-item {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  padding: 8px 10px;
  border-radius: var(--radius);
  color: var(--text-secondary);
  cursor: pointer;
  border: 0.5px solid transparent;
}
.recipe-list-item:hover { background: var(--surface-2); }
.recipe-list-item.active {
  background: var(--surface-1);
  border-color: var(--border);
  color: var(--text-primary);
  font-weight: 500;
}
.recipe-list-item .cat-dot { flex-shrink: 0; }
.recipe-list-item .recipe-list-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.recipe-card {
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-md);
  padding: 20px;
  min-height: 400px;
}
.recipe-card-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 4px; }
.recipe-name-input {
  font-size: 15px;
  font-weight: 500;
  background: transparent;
  border: 0.5px solid transparent;
  padding: 2px 4px;
  width: 100%;
}
.recipe-name-input:hover, .recipe-name-input:focus { border-color: var(--border-strong); background: var(--surface-1); }
.recipe-header-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.badge {
  font-size: 12px;
  padding: 3px 10px;
  border-radius: var(--radius);
  white-space: nowrap;
}
.badge-craftable { color: var(--text-success); background: var(--bg-success); }
.badge-uncraftable { color: var(--text-muted); background: var(--surface-1); border: 0.5px solid var(--border); }
.recipe-desc-input {
  font-size: 12px;
  color: var(--text-secondary);
  background: transparent;
  border: 0.5px solid transparent;
  padding: 2px 4px;
  width: 100%;
  margin-bottom: 14px;
}
.recipe-desc-input:hover, .recipe-desc-input:focus { border-color: var(--border-strong); background: var(--surface-1); }
.section-label { font-size: 12px; color: var(--text-muted); margin: 0 0 8px; }
.ingredient-row { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 18px; }
.ingredient-tile { text-align: center; width: 64px; }
.ingredient-icon {
  width: 44px; height: 44px;
  border-radius: var(--radius);
  display: flex; align-items: center; justify-content: center;
  margin: 0 auto 4px;
}
.ingredient-icon i, .ingredient-icon img { font-size: 20px; }
.ingredient-icon.need { background: rgba(255,255,255,0.03); border: 1px dashed var(--border-strong); }
.ingredient-name { font-size: 11px; color: var(--text-secondary); margin: 0 0 4px; word-break: break-word; }

/* AN EDITABLE INGREDIENT IS A DIFFERENT SIZE OF THING.
   `.ingredient-tile` is 64px, which fits an icon and a wrapped name — exactly
   what the read view needs. The editor put a number field, a unit `<select>` and
   a remove button inside that same 64px: measured, the controls ran to 147px and
   overflowed 41px onto the NEXT tile's icon, on every tile in the row.
   Widened to fit its contents rather than shrinking the controls, because a
   36px number field and a 70px unit name are already at their floor. The unit
   picker takes the slack so the row never has to wrap inside a tile. */
.ingredient-tile.is-editing { width: 156px; }
.ingredient-qty-row { display: flex; align-items: center; gap: 3px; justify-content: center; }
.ingredient-qty-row input { width: 42px; flex: 0 0 auto; padding: 3px 4px; font-size: 11px; text-align: center; }
.ingredient-tile.is-editing .ingredient-qty-row .unit-select { flex: 1 1 auto; min-width: 0; max-width: none; }
.ingredient-remove { padding: 2px 5px; font-size: 11px; color: var(--text-muted); }
.ingredient-remove:hover { color: var(--danger); }
/* An ingredient whose inventory item was deleted — the line survives (it keeps
   its name), it just isn't stocked, and offers a one-tap way back into inventory
   as a needed item. */
.ingredient-tile.is-orphan .ingredient-name { color: var(--text-muted); font-style: italic; }
.ingredient-readd {
  display: inline-flex; align-items: center; gap: 3px; margin: 0 0 4px;
  padding: 2px 7px; font-size: 10px; border-radius: var(--radius-pill);
  background: var(--surface-3); color: var(--text-secondary); cursor: pointer; border: none;
}
.ingredient-readd:hover { color: var(--text-primary); }
.add-ingredient-tile {
  width: 44px; height: 44px;
  border-radius: var(--radius);
  border: 0.5px dashed var(--border-strong);
  color: var(--text-muted);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  margin: 0 auto;
}
.add-ingredient-tile:hover { color: var(--text-primary); border-color: var(--text-primary); }

/* The eating-safety note for a foraged/home-made ingredient (Professions v2 era):
   a full-width warn strip below the ingredient tiles, plus a per-tile mark. */
.ingredient-warn-banner {
  flex-basis: 100%;
  margin: 6px 0 0;
  padding: 8px 10px;
  font-size: 11px;
  line-height: 1.45;
  color: var(--warn-text);
  background: var(--warn-bg);
  border: 0.5px solid var(--warn-border);
  border-radius: var(--radius-md);
  display: flex;
  gap: 8px;
  align-items: flex-start;
}
.ingredient-warn-banner i { flex-shrink: 0; margin-top: 1px; }
.ing-warn-mark { color: var(--warn-text); font-size: 11px; }
.ingredient-tile.is-foraged .ingredient-name { color: var(--warn-text); }
.add-ingredient-form {
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  padding: 10px;
  margin-bottom: 18px;
  width: 100%;
}
.add-ing-controls { display: flex; gap: 8px; align-items: center; }
.add-ing-controls input[type="text"] { flex: 1; }
.add-ing-controls input[type="number"] { width: 60px; }
.ing-search-results {
  margin-top: 8px;
  max-height: 190px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ing-result-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-radius: var(--radius);
  cursor: pointer;
  font-size: 13px;
}
.ing-result-row:hover { background: var(--surface-2); }
.ing-result-icon {
  width: 22px; height: 22px;
  border-radius: 4px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.ing-result-icon i, .ing-result-icon img { font-size: 12px; }
.ing-result-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ing-result-tag { font-size: 10px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.03em; flex-shrink: 0; }
.ing-result-create { color: var(--text-accent); border: 0.5px dashed var(--border-strong); }
.ing-result-create:hover { background: var(--surface-2); }
.ing-result-hint { font-size: 12px; color: var(--text-muted); padding: 6px 8px; }
.steps-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }
.step-row { display: flex; align-items: center; gap: 8px; }
.step-num { font-size: 13px; color: var(--text-muted); width: 18px; flex-shrink: 0; text-align: right; }
.step-input { flex: 1; font-size: 13px; }
.step-remove { padding: 4px 8px; font-size: 12px; color: var(--text-muted); }
.step-remove:hover { color: var(--danger); }
.recipe-delete-btn { color: var(--danger); }

.blocks-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 10px; }
.block {
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  padding: 10px;
}
.block-header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.block-type-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 3px 8px;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--text-muted);
}
.block-type-badge i { font-size: 11px; }
.block-type-badge.type-steps { color: var(--text-accent); }
/* THIS WAS BROKEN AND LOOKED DELIBERATE. `--c-amber` is not a custom property
   — the palette is delivered as the CLASS `.c-amber`, so `var(--c-amber)` has
   never resolved and this badge rendered through its `--text-secondary`
   fallback: plain grey, beside a `type-steps` badge that is accent blue. The
   two block types were meant to read as a pair and only one was coloured.
   The literal is `.c-amber`'s own value, and it stays a literal on purpose —
   which block type this is is MEANING, and the token block at the top of this
   file is explicit that semantic markers do not become skin-reachable. */
.block-type-badge.type-notes { color: #eac886; }
.block-title-input {
  flex: 1;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-primary);
  background: transparent;
  border: 0.5px solid transparent;
  padding: 3px 4px;
}
.block-title-input:hover, .block-title-input:focus { border-color: var(--border-strong); background: var(--surface-2); }
.block-title-input::placeholder { font-weight: 400; color: var(--text-muted); }
.block-actions { display: flex; align-items: center; gap: 1px; flex-shrink: 0; }
.block-action-btn { padding: 3px 6px; font-size: 12px; color: var(--text-muted); }
.block-action-btn:hover:not(:disabled) { color: var(--text-primary); }
.block-action-btn.remove:hover:not(:disabled) { color: var(--danger); }
.block-action-btn:disabled { opacity: 0.25; cursor: default; }
.block-notes-textarea { width: 100%; min-height: 60px; font-size: 13px; }

.add-block-row { margin-top: 2px; }
.add-block-chooser { display: flex; align-items: center; gap: 8px; }
.add-block-label { font-size: 12px; color: var(--text-muted); }
.chooser-btn { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; }
.chooser-btn i { font-size: 13px; }

/* ---------- codex ---------- */
.codex-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 10px;
  margin-top: 12px;
}
.codex-card {
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-md);
  padding: 12px;
  display: flex;
  gap: 10px;
  align-items: flex-start;
}
.codex-card-icon {
  width: 32px; height: 32px;
  border-radius: var(--radius);
  background: var(--surface-2);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  color: var(--text-accent);
}
.codex-card-body { flex: 1; min-width: 0; }
.codex-card-name { font-size: 13px; font-weight: 600; margin-bottom: 3px; word-break: break-word; }
.codex-card-meta { display: flex; gap: 8px; font-size: 11px; color: var(--text-muted); flex-wrap: wrap; margin-bottom: 4px; }
.codex-format-tag { text-transform: uppercase; letter-spacing: 0.03em; }
.codex-card-desc { font-size: 12px; color: var(--text-secondary); }
.codex-card-actions { display: flex; flex-direction: column; gap: 2px; flex-shrink: 0; }
/* The one surviving size override, and it earns it: this is a NARROW VERTICAL
   COLUMN of icon-only buttons beside a card, where the scale's horizontal
   padding would make the column wider than the icons need. Type size comes from
   the scale; only the horizontal squeeze is local. */
.codex-card-actions .btn-plain, .codex-card-actions a { padding: 4px 6px; font-size: 12px; }
.codex-card-editing { flex-direction: column; width: 100%; }
.codex-card-editing .field { margin-bottom: 8px; width: 100%; }
.codex-card-editing input, .codex-card-editing textarea { width: 100%; }

.codex-add-form {
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  padding: 14px;
  margin-top: 10px;
  max-width: 360px;
}
.codex-add-form .field { margin-bottom: 10px; }
.codex-add-form input { width: 100%; }
.codex-add-form .tab-btn { font-size: 12px; padding: 5px 10px; }

.import-panel {
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  max-width: 560px;
}
.import-hint { font-size: 12px; color: var(--text-secondary); margin: 0 0 12px; line-height: 1.6; }
.import-actions { display: flex; gap: 8px; margin-bottom: 14px; }
.import-upload-row { display: flex; gap: 8px; align-items: center; }
.import-upload-row input[type="file"] { flex: 1; font-size: 12px; }
.import-status-msg { font-size: 12px; margin: 10px 0 0; line-height: 1.6; }
.import-status-ok { color: var(--text-success); }
.import-status-error { color: var(--danger); }
.manual-copy-box {
  width: 100%;
  height: 160px;
  font-family: monospace;
  font-size: 11px;
  margin-top: 6px;
}

/* ---------- field notes ---------- */
.fn-header-row { display: flex; gap: 16px; align-items: flex-start; margin-bottom: 16px; }
.fn-icon-preview {
  width: 64px; height: 64px;
  border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  overflow: hidden;
}
.fn-main-photo {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
  border: 2px solid currentColor;
  box-sizing: border-box;
  display: block;
}
.fn-identity-col { flex: 1; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.fn-sciname-input { font-style: italic; }
.fn-category-row { display: flex; align-items: center; gap: 8px; }
.fn-category-select { width: 200px; }
.fn-icon-trigger, .icon-trigger {
  width: 30px; height: 30px;
  flex-shrink: 0;
  border-radius: var(--radius);
  border: 0.5px solid var(--border);
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  cursor: pointer;
}
.fn-icon-trigger:hover, .icon-trigger:hover { border-color: var(--border-accent); }
.fn-link-pills { display: flex; flex-direction: column; gap: 4px; }
.fn-link-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  padding: 5px 8px;
  border-radius: var(--radius);
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  width: fit-content;
}
.fn-link-pill:hover { background: var(--surface-3); }

.fn-linked-wrap { display: flex; align-items: center; gap: 10px; }
.fn-linked-tile { position: relative; width: 44px; height: 44px; flex-shrink: 0; }
.fn-linked-tooltip {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 6px;
  z-index: 10;
  background: var(--surface-2);
  border: 0.5px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 8px 10px;
  font-size: 11px;
  color: var(--text-secondary);
  white-space: nowrap;
  box-shadow: 0 4px 12px rgba(0,0,0,0.4);
}
.fn-linked-tile:hover .fn-linked-tooltip { display: block; }
.fn-tooltip-name { font-size: 12px; font-weight: 600; color: var(--text-primary); margin-bottom: 2px; }
.fn-linked-info { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.fn-linked-hint { font-size: 10px; color: var(--text-muted); }

.fn-obs-header-row { display: flex; align-items: center; justify-content: space-between; margin: 20px 0 8px; }
.fn-observation {
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  padding: 10px;
  margin-bottom: 10px;
}
.fn-obs-toprow { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.fn-obs-date { font-size: 12px; }
.fn-obs-remove { margin-left: auto; padding: 2px 6px; font-size: 12px; color: var(--text-muted); background: transparent; border: none; }
.fn-obs-remove:hover { color: var(--danger); }
.fn-obs-note { width: 100%; min-height: 50px; font-size: 13px; margin-bottom: 8px; }
.fn-obs-media-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.fn-obs-media-item { cursor: pointer; }
.fn-obs-media-thumb { width: 44px; height: 44px; object-fit: cover; border-radius: var(--radius); border: 0.5px solid var(--border); display: block; }
.fn-obs-media-thumb.is-main { outline: 2px solid var(--border-accent); outline-offset: 1px; }
.fn-obs-media-thumb:hover { border-color: var(--border-accent); }

/* ---------- field notes: profile photo gallery ---------- */
.fn-photo-thumb-wrap { cursor: pointer; aspect-ratio: 1; }
.fn-photo-thumb { width: 100%; height: 100%; object-fit: cover; border-radius: var(--radius); border: 0.5px solid var(--border); display: block; }
.fn-photo-thumb.is-main { outline: 2px solid var(--border-accent); outline-offset: 1px; }
.fn-photo-thumb:hover { border-color: var(--border-accent); }

/* ---------- friends ---------- */
.friend-add-row { display: flex; gap: 8px; align-items: center; }
.friend-add-row input { flex: 1; min-width: 0; }
.friend-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-1);
  margin-bottom: 6px;
}
.friend-row.is-open { border-color: var(--border-accent); }
.friend-avatar {
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  flex-shrink: 0;
  font-size: 15px;
}
.friend-name { flex: 1; min-width: 0; font-size: 13px; color: var(--text-primary); }
.friend-handle { color: var(--text-muted); font-size: 11px; margin-left: 5px; }
.friend-actions { display: flex; gap: 6px; flex-shrink: 0; }

.shared-card {
  border: 0.5px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-1);
  padding: 12px 14px;
  margin-bottom: 10px;
}
.shared-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 6px; }
.shared-title { font-size: 13px; margin: 0; flex: 1; min-width: 0; }
.shared-sub { font-size: 11px; color: var(--text-muted); text-transform: lowercase; margin: 10px 0 4px; }
.shared-list { margin: 0; padding-left: 18px; font-size: 13px; color: var(--text-secondary); }

@media (max-width: 860px) {
  .friend-row { flex-wrap: wrap; }
  .friend-actions { width: 100%; justify-content: flex-end; }
  .friend-add-row { flex-wrap: wrap; }
  .friend-add-row input, .friend-add-row button { width: 100%; }
}

/* ---------- coins, clicker, notifications ---------- */
/* It is an <a> to the Market (the user's call: the money should lead to the
   shop). `text-decoration` is stated because the control baseline in this
   project covers button/input/select/textarea and never anchors — the same
   trap that left thirteen `a.btn-*` unstyled once. */
.coin-total {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: var(--coin);
  padding: 3px 8px;
  border-radius: var(--radius-pill);
  background: rgba(214,164,91,0.12);
  white-space: nowrap;
  text-decoration: none;
}
/* A ring, not a background and not a filter. A background would be overridden
   by the currency's own `.c-*` class and flash the wrong colour; a filter would
   open a stacking context and the two bought currency effects paint from
   pseudo-elements inside this pill. box-shadow does neither. */
a.coin-total:hover { box-shadow: 0 0 0 1px currentColor; }
.coin-total i { font-size: 13px; }

/* Sits to the left of each page title. The cooldown is drawn as a dimmed,
   un-pressable state with the seconds left — you can see it's working rather
   than wondering why clicking does nothing. */
.coin-clicker {
  background: rgba(214,164,91,0.12);
  border: 0.5px solid rgba(214,164,91,0.35);
  color: var(--coin);
  width: 30px; height: 30px;
  border-radius: 50%;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  flex-shrink: 0;
  transition: transform 0.08s ease, opacity 0.2s ease;
}
.coin-clicker:hover:not(:disabled) { transform: scale(1.08); background: rgba(214,164,91,0.22); }
.coin-clicker:active:not(:disabled) { transform: scale(0.94); }
.coin-clicker.is-cooling {
  opacity: 0.4;
  cursor: not-allowed;
  border-style: dashed;
}
.coin-clicker.is-cooling::after {
  content: var(--cool, '');
  position: absolute;
  font-size: 9px;
  transform: translateY(14px);
  color: var(--text-muted);
}
/* The clicker sits immediately left of the title, so the two have to travel
   together — the header is space-between, and without this the button lands
   against the far edge with the title alone on the other side. */
.page-header { position: relative; gap: 10px; }
.page-header h1 .coin-clicker { margin-right: 10px; vertical-align: middle; }

.notif { position: relative; }
.notif-bell {
  background: transparent;
  border: none;
  color: var(--text-secondary);
  font-size: 15px;
  cursor: pointer;
  padding: 4px 6px;
  position: relative;
}
.notif-bell:hover { color: var(--text-primary); }
.notif-badge {
  position: absolute;
  top: 0; right: 0;
  background: #d66b5b;
  color: #fff;
  font-size: 9px;
  min-width: 14px;
  height: 14px;
  border-radius: var(--radius-pill);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 3px;
}
.notif-panel {
  position: absolute;
  top: 100%; right: 0;
  margin-top: 6px;
  width: 260px;
  max-height: 320px;
  overflow-y: auto;
  background: var(--surface-2);
  border: 0.5px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: 0 6px 20px rgba(0,0,0,0.45);
  z-index: 60;
}
.notif-item { display: flex; gap: 9px; align-items: center; padding: 9px 11px; border-bottom: 0.5px solid var(--border); }
.notif-item:last-child { border-bottom: none; }
.notif-item.is-unread { background: rgba(255,255,255,0.03); }
.notif-item.is-group { cursor: pointer; align-items: flex-start; }
.notif-item.is-actionable { cursor: pointer; }
.notif-item.is-actionable:hover { background: rgba(255,255,255,0.05); }
.notif-item.is-group:hover { background: rgba(255,255,255,0.04); }
.notif-expand { margin-left: 6px; color: var(--text-accent, #7aa7e0); }
.notif-members { margin-top: 6px; border-left: 2px solid var(--border); padding-left: 8px; }
.notif-member { display: flex; justify-content: space-between; gap: 8px; font-size: 10px; color: var(--text-muted); padding: 2px 0; }
.notif-member-xp { color: var(--xp); flex-shrink: 0; }
.notif-icon { width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 13px; flex-shrink: 0; }
.notif-text { min-width: 0; }
.notif-title { font-size: 12px; color: var(--text-primary); }
.notif-when { font-size: 10px; color: var(--text-muted); }
/* The bell panel's footer link to the full page. */
.notif-see-all { display: block; text-align: center; padding: 9px; font-size: 11px; color: var(--text-accent); text-decoration: none; border-top: 0.5px solid var(--border); }
.notif-see-all:hover { background: rgba(255,255,255,0.04); }

/* ---------- the notifications PAGE ---------- */
.notif-page-list {
  max-width: 620px; background: var(--surface-1);
  border: 0.5px solid var(--border); border-radius: var(--radius-md); overflow: hidden;
}
.notif-page-item {
  display: flex; gap: 11px; align-items: center; padding: 11px 14px;
  border-bottom: 0.5px solid var(--border); text-decoration: none; color: inherit;
}
.notif-page-item:last-child { border-bottom: none; }
a.notif-page-item.is-actionable { cursor: pointer; }
a.notif-page-item.is-actionable:hover { background: var(--surface-2); }
.notif-page-item .notif-title { font-size: 13px; }
.notif-page-item .notif-when { font-size: 11px; }

/* ---------- the appearance panel ----------
   The UI *for* the cosmetic system, which is why it lives here and not in
   skins.css: that file is the overlay a skin applies, this is a feature's own
   chrome and has to keep working with every skin including a broken one.

   Fixed rather than absolute like .notif-panel, because the gear is injected
   into .topbar-right at runtime and the panel is taller than the top bar can
   usefully contain — anchoring to the viewport keeps it clear of the sticky
   bar's own overflow. Above the top bar's z-index 900 for the same reason. */
.cos-panel {
  position: fixed;
  top: 54px;
  right: 12px;
  width: 300px;
  max-height: calc(100vh - 70px);
  overflow-y: auto;
  background: var(--surface-1);
  border: 0.5px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: 0 10px 32px rgba(0,0,0,0.5);
  z-index: 950;
  padding: 12px 13px 10px;
}
.cos-panel-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.cos-panel-head strong { font-size: 13px; }
.cos-section { margin-bottom: 14px; }
.cos-section h4 { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); margin: 0 0 3px; }
.cos-note { font-size: 11px; color: var(--text-muted); margin: 0 0 7px; line-height: 1.45; }
.cos-items { display: flex; flex-direction: column; gap: 4px; }

.cos-item {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  text-align: left;
  padding: 7px 9px;
  background: var(--surface-2);
  border: 0.5px solid transparent;
  border-radius: var(--radius);
}
.cos-item:hover:not(:disabled) { background: var(--surface-3); }
.cos-item.active { border-color: var(--border-accent); background: var(--surface-3); }
/* A locked row stays visible and readable rather than being hidden or greyed to
   illegibility — its whole job is telling you what earns it, and a row you
   can't read teaches nothing. Dimmed enough to read as unavailable, not enough
   to stop it being a sentence. */
.cos-item.locked { opacity: 0.55; cursor: default; }
.cos-item-main { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.cos-item-label { font-size: 12px; color: var(--text-primary); display: flex; align-items: center; gap: 5px; }
.cos-item-label i { font-size: 11px; color: var(--text-muted); }
.cos-item-blurb { font-size: 10px; color: var(--text-muted); line-height: 1.4; }

/* Three stacked bars reading ground / accent / text — the three decisions that
   actually distinguish one skin from another, in the order you notice them. */
.cos-swatch { display: flex; flex-direction: column; gap: 2px; flex-shrink: 0; }
.cos-swatch i { display: block; width: 16px; height: 5px; border-radius: 2px; }

.cos-foot { font-size: 10px; color: var(--text-muted); margin: 4px 0 0; text-align: center; }

/* ---------- the Market ----------
   Shop conventions here are the ones players already read without a legend,
   rather than anything invented: a rarity-tinted card edge, an Owned badge
   distinct from an Equipped state, price shown with the currency glyph, and
   locked stock left visible with its condition stated. */
.market-tabs { margin-bottom: 16px; flex-wrap: wrap; }
.market-balance { display: flex; align-items: baseline; gap: 7px; }
.market-balance-num { display: inline-flex; align-items: center; gap: 6px; font-size: 20px; }
.market-balance-num b { font-weight: 600; }
.market-balance-label { font-size: 11px; color: var(--text-muted); }
.market-coin { display: inline-flex; align-items: center; gap: 3px; background: none; padding: 0; }

.shop-slot { margin-bottom: 26px; }
.shop-slot-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.shop-slot-head h3 { font-size: 13px; margin: 0; }
.shop-slot-worn { font-size: 11px; color: var(--text-muted); }
.shop-slot-hint { font-size: 11px; color: var(--text-muted); margin: 2px 0 10px; }

.shop-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(215px, 1fr)); gap: 10px; }
.shop-card {
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  /* The rarity edge. One property carries the tier, so the card doesn't need a
     coloured background fighting whatever skin is on. */
  border-top: 2px solid var(--rar, var(--border-strong));
  border-radius: var(--radius-md);
  padding: 11px 12px;
  display: flex; flex-direction: column; gap: 5px;
}
.shop-card.is-owned { background: var(--surface-2); }
.shop-card.is-equipped { border-color: var(--border-accent); }
.shop-card-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.shop-rarity { font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--rar, var(--text-muted)); }
.shop-owned { font-size: 10px; color: var(--text-success); display: inline-flex; align-items: center; gap: 3px; }
.shop-card h4 { margin: 0; font-size: 13px; font-weight: 600; }
.shop-blurb { margin: 0; font-size: 11px; color: var(--text-secondary); line-height: 1.45; flex: 1; }
/* The honest label. An effect is allowed to cost you legibility — that's a
   legitimate choice — but it is not allowed to cost you legibility quietly. */
.shop-warn {
  margin: 0; font-size: 10px; color: var(--warn-text, #dbc08a);
  display: flex; gap: 5px; align-items: flex-start; line-height: 1.4;
}
.shop-warn i { font-size: 12px; flex-shrink: 0; margin-top: 1px; }
.shop-actions { margin-top: 4px; }
.shop-btn {
  width: 100%; font-size: 12px; padding: 7px 10px;
  background: var(--surface-3); border: 0.5px solid var(--border-strong);
  border-radius: var(--radius); color: var(--text-primary);
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
}
.shop-btn:hover { border-color: var(--border-accent); }
.shop-btn.buy { color: var(--coin); }
.shop-btn.is-on { background: var(--border-accent); color: var(--accent-fg); border-color: var(--border-accent); font-weight: 600; }
.shop-locked { display: flex; align-items: center; gap: 5px; font-size: 10px; color: var(--text-muted); padding: 7px 0; }

.shop-columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; align-items: start; }
.shop-col h3 { font-size: 13px; margin: 0 0 3px; }

/* ---------- loadout ----------
   A rack of tiles per slot, not a column of dropdowns. The native <select> was
   closing before its options could be clicked, and an <option> can't hold a
   live preview anyway — so every owned effect shows as a tile you can see
   moving, with the worn one lit. */
.loadout { display: flex; flex-direction: column; gap: 10px; }
.loadout-row {
  background: var(--surface-1); border: 0.5px solid var(--border);
  border-radius: var(--radius); padding: 11px 13px;
}
.loadout-slot { margin-bottom: 9px; }
.loadout-slot b { display: block; font-size: 13px; }
.loadout-slot span { font-size: 10px; color: var(--text-muted); }
.loadout-row .empty-state a { color: var(--text-accent); }

.lo-rack { display: grid; grid-template-columns: repeat(auto-fill, minmax(126px, 1fr)); gap: 8px; }
.lo-tile {
  display: flex; flex-direction: column; gap: 0;
  padding: 8px 8px 7px; text-align: left;
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-top: 2px solid var(--rar, var(--border));
  border-radius: var(--radius);
  position: relative;
}
.lo-tile:hover { border-color: var(--border-strong); background: var(--surface-3); }
.lo-tile.is-on { border-color: var(--border-accent); background: var(--surface-3); }
.lo-tile .fxp { height: 44px; margin-bottom: 6px; }
.lo-tile-name { font-size: 11px; font-weight: 600; line-height: 1.25; color: var(--text-primary); }
.lo-tile-worn {
  display: inline-flex; align-items: center; gap: 3px;
  font-size: 10px; color: var(--text-success); margin-top: 2px;
}
/* "Nothing" is a real choice, so it gets a tile rather than being expressed by
   un-clicking the active one — which is undiscoverable. */
.lo-tile.lo-none { justify-content: flex-start; }
.lo-none-mark {
  height: 44px; margin-bottom: 6px; border-radius: 6px;
  background: var(--surface-0); border: 0.5px dashed var(--border-strong);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-muted); font-size: 15px;
}

/* ---------- per-page skin picker ----------
   Skins are per page, and with the top-bar gear gone the choice used to be
   implicit — you skinned whatever page you were on. Here it has to be explicit,
   and the row doubles as an overview of which pages you've already themed. */
.skin-section h3 { font-size: 13px; margin: 0 0 3px; }
.skin-pages { display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 6px; }
.skin-page {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; text-align: left;
  background: var(--surface-1); border: 0.5px solid var(--border);
  border-radius: var(--radius); color: var(--text-secondary);
}
.skin-page:hover { border-color: var(--border-strong); color: var(--text-primary); }
.skin-page.active { border-color: var(--border-accent); background: var(--surface-2); color: var(--text-primary); }
.skin-page i { font-size: 15px; flex-shrink: 0; }
.skin-page span { font-size: 12px; line-height: 1.2; }
.skin-page em {
  display: block; font-style: normal; font-size: 10px;
  color: var(--text-muted); margin-top: 1px;
}
.skin-target { color: var(--text-accent); }
.skin-all { margin-top: 10px; }

/* currency identity */
.cur-name { width: 100%; font-size: 15px; padding: 9px 11px; }
.cur-preview { margin-top: 10px; font-size: 11px; color: var(--text-muted); display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.cur-preview-name { color: var(--text-secondary); }
.cur-icons { display: grid; grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)); gap: 5px; }
.cur-icon {
  aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
  font-size: 15px; background: var(--surface-2); border: 0.5px solid transparent;
  border-radius: var(--radius); color: var(--text-secondary);
}
.cur-icon:hover { color: var(--text-primary); }
.cur-icon.active { border-color: var(--border-accent); color: var(--text-accent); background: var(--surface-3); }
.cur-colors { display: flex; flex-wrap: wrap; gap: 6px; }
.cur-color { width: 30px; height: 30px; border-radius: 50%; border: 2px solid transparent; padding: 0; }
.cur-color.active { border-color: var(--text-primary); }

.intensity-list { max-width: 460px; }

/* ---------- .empty-state ----------
   "There is nothing here yet", which the app said in FOURTEEN different
   components: .search-empty .tile-empty .detail-empty .recipe-list-empty
   .recipe-empty .codex-empty .fn-photo-empty .notif-empty .loadout-empty
   .shelf-empty .camp-empty .loot-empty .record-empty — every one of them
   `color: var(--text-muted)` and a font-size between 11 and 13px, four of them
   with a padding in rem and two of them italic for no reason anything else
   shared. Forty-three call sites, one idea.

   Two axes, because the real variation is only ever these two:

     (default)  a centred block where a list would have been
     .inline    a line of text inside a panel that has other content — left
                aligned, smaller, no vertical padding to push that content down
     .span      it lives in a grid and must cross every column

   NOT italic. Two of the thirteen were and eleven weren't; plain wins on
   count, and it matches the voice the rest of the app is written in.

   `.camp-map-empty` deliberately survives as its own rule — it is an absolute
   overlay centred on the map tile, not a block in a flow, and folding it in
   here would mean a modifier used exactly once. */
.empty-state {
  color: var(--text-muted);
  font-size: 13px;
  text-align: center;
  padding: 24px 8px;
  margin: 0;
  line-height: 1.5;
}
.empty-state.inline {
  text-align: left;
  font-size: 12px;
  padding: 4px 0;
}
.empty-state.span { grid-column: 1 / -1; }

/* ---------- .notice ----------
   A standing strip that tells you about the STATE OF THE PAGE — not about
   something you just did. That is the whole line between this and
   `.market-toast`: a toast reports an event and leaves, a notice describes a
   condition and stays until the condition does.

   It started life as `.market-debug`, one bespoke block for one banner. The
   admin dashboard then needed the identical thing to say the same switch was
   on, which is the bar for making a component: the second call site is real
   and wants it verbatim. Variants carry the colour; the shape is shared, so a
   third one is a modifier rather than another block.

   Deliberately NOT tokenized per variant beyond the status colours already in
   :root — an `.notice-info` that invented its own blue would be a fourth
   status colour nothing else uses. */
.notice {
  display: flex; gap: 9px; align-items: flex-start;
  border: 0.5px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 10px 13px; margin-bottom: 14px;
  font-size: 12px; line-height: 1.5;
  background: var(--surface-2); color: var(--text-secondary);
}
.notice i { font-size: 15px; flex-shrink: 0; margin-top: 1px; }
.notice b { color: var(--text-primary); }
.notice-warn {
  background: var(--warn-bg);
  border-color: var(--warn-border);
  color: var(--warn-text);
}

/* Above the 1000 modal band on purpose — a toast is a report on what just
   happened, and one that renders behind a dialog is a report nobody reads.
   See the layering bands at the top of effects.css. */
.market-toast {
  position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%);
  background: var(--surface-3); border: 0.5px solid var(--border-strong);
  border-radius: var(--radius); padding: 10px 16px; font-size: 12px;
  z-index: 1010; box-shadow: 0 8px 26px rgba(0,0,0,0.45);
}
.market-toast.bad { border-color: var(--danger); color: var(--danger); }

@media (max-width: 700px) {
  .loadout-row { grid-template-columns: 1fr; gap: 6px; }
  .loadout-now { display: none; }
}

/* ---------- profile tabs + activity ---------- */
.profile-tabs { margin: 16px 0 14px; }
.profile-tab-body { min-height: 120px; }
/* The Posts tab reuses the feed's post cards; keep them to a readable column. */
.profile-posts { display: flex; flex-direction: column; gap: 12px; max-width: 620px; }
/* A profession you've actually worked reads brighter than one you haven't —
   the sheet should show what you've been doing at a glance. */
.profile-prof { opacity: 0.55; }
.profile-prof.has-xp, .profile-prof.is-mine { opacity: 1; }
.xp-of { opacity: 0.7; }

.activity-list { display: flex; flex-direction: column; }
.activity-row {
  display: flex;
  gap: 10px;
  padding: 9px 2px;
  border-bottom: 0.5px solid var(--border);
  align-items: flex-start;
}
.activity-row:last-child { border-bottom: none; }
.activity-row.is-group { cursor: pointer; }
.activity-row.is-group:hover { background: rgba(255,255,255,0.03); }
.activity-icon {
  width: 26px; height: 26px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px;
  flex-shrink: 0;
}
.activity-main { min-width: 0; flex: 1; }
.activity-title { font-size: 13px; color: var(--text-primary); }
.activity-title a { color: var(--text-primary); text-decoration: none; border-bottom: 0.5px solid var(--border-strong); }
.activity-title a:hover { color: var(--text-accent, #7aa7e0); }
.activity-meta { display: flex; align-items: center; gap: 8px; font-size: 10px; color: var(--text-muted); margin-top: 2px; }
.activity-xp { color: var(--xp); }
.activity-coins { color: var(--coin); display: inline-flex; align-items: center; gap: 2px; }
.activity-members { margin-top: 6px; border-left: 2px solid var(--border); padding-left: 8px; }

/* ---------- levels ---------- */
.xp-bar { height: 5px; border-radius: var(--radius-pill); background: var(--surface-2); overflow: hidden; margin-top: 5px; }
.xp-fill { height: 100%; background: var(--cls-color, #5b8dd6); border-radius: var(--radius-pill); }
.xp-line { display: flex; align-items: baseline; gap: 6px; font-size: 10px; color: var(--text-muted); margin-top: 3px; }
.level-pill {
  font-size: 10px;
  font-weight: 600;
  padding: 1px 6px;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--text-secondary);
}

/* ---------- shared browsing ----------
   Deliberately almost no new styling: the source switch IS `.view-switch`, so
   it inherits whatever that component looks like now and whatever it looks like
   later. Only the count badge and the author chip are new, and both are small
   because they're attribution, not headlines. */
/* Mine/System segmented buttons sit beside the People popover trigger. They
   share a row and wrap gracefully; the authors no longer live in this row at
   all (they were what overflowed it past a handful of people). */
.source-switch-wrap { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.source-switch { margin-bottom: 0; }
.source-people-btn { gap: 5px; }
.source-people-btn .filters-badge { background: var(--surface-1); color: var(--text-muted); }
.source-people-btn.active .filters-badge { background: var(--accent-fg); color: #fff; }
/* The popover, reusing the Filters shell but sized for a scrollable people list. */
.source-people-pop { max-height: 320px; overflow: hidden; }
.source-people-pop.hidden { display: none; }
.source-people-search { width: 100%; margin-bottom: 8px; }
.source-people-list { display: flex; flex-direction: column; gap: 2px; max-height: 250px; overflow-y: auto; }
.source-person {
  display: flex; align-items: center; gap: 7px; width: 100%;
  padding: 5px 7px; border-radius: var(--radius); font-size: 12px;
  color: var(--text-secondary); background: none; border: none; cursor: pointer; text-align: left;
}
.source-person:hover { background: var(--surface-2); }
.source-person.active { background: var(--surface-3); color: var(--text-primary); }
.source-person-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.source-empty { font-size: 12px; color: var(--text-muted); padding: 6px; margin: 0; }
.source-count {
  font-size: 10px;
  color: var(--text-muted);
  background: var(--surface-1);
  border-radius: var(--radius-pill);
  padding: 0 5px;
  margin-left: 2px;
}
.author-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 10px;
  padding: 1px 7px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
}
.author-chip i { font-size: 11px; }
/* A record you're only reading gets a quiet band down its left edge — enough
   to tell it apart from your own at a glance, without shouting. */
.is-shared { border-left: 2px solid var(--border-strong); }

/* ---------- the feed ----------
   A post is a card with an author, some words, and any number of attachment
   cards. The attachment deliberately reads as a QUOTE rather than as part of
   the post — inset, its own surface, its own border — because it describes
   somebody's record at a moment in time and the post is the thing being said
   about it. */
/* A reading column, not the full width of a desktop window. A feed is a single
   vertical conversation and a 1400px-wide post is unreadable — every social app
   settles somewhere near this for the same reason.
   CENTRED, matching Camp's Town Center. It was left-aligned on the theory that
   it should sit under the page title where the eye already is; in practice a
   640px column pinned to the left of a 1400px window reads as a layout that
   failed to fill, not as a deliberate measure — there is nothing beside it to
   justify the asymmetry. */
#feedPane { max-width: 640px; margin-inline: auto; }

/* Tighter vertically than the rest of the app on purpose: elsewhere a card is a
   record you study, here it is one of many you scroll past. */
#feedPane .settings-block { margin-bottom: 12px; }
#feedPane .view-switch { margin-bottom: 12px; }

.composer { display: flex; flex-direction: column; gap: 9px; }

/* Collapsed: one line, like every inline composer worth copying. Everything
   below the stub is hidden rather than removed, so nothing staged is lost and
   focus can be handed straight to the textarea on expand. */
.composer-stub {
  display: none;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 9px 12px;
  border-radius: var(--radius-pill);
  border: 0.5px solid var(--border);
  background: var(--surface-2);
  color: var(--text-muted);
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: text;
}
.composer-stub:hover { border-color: var(--border-strong); color: var(--text-secondary); }
/* COLLAPSED IS "EVERYTHING BUT THE STUB", stated as a rule rather than as a
   list. It used to name the four children it knew about, so when the profession
   chips were added they simply stayed on screen — a row of eleven chips sitting
   under a one-line "Share something…", advertising a form that wasn't open. The
   staged media strip had the same hole.

   A list has to be maintained by whoever adds the next control and silently
   fails when they don't; this can't be forgotten. The mention type-ahead is a
   child too and is correctly hidden by it — there is nothing to type into. */
.composer.is-collapsed { padding-top: 10px; padding-bottom: 10px; }
.composer.is-collapsed > :not(.composer-stub) { display: none; }
.composer.is-collapsed .composer-stub { display: flex; }
.composer textarea {
  width: 100%;
  resize: vertical;
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  font: inherit;
  font-size: 13px;
  padding: 9px 11px;
}
.composer textarea:focus { outline: none; border-color: var(--border-accent, var(--text-accent)); }
.composer-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.composer-attach { display: flex; gap: 6px; align-items: center; }
.composer-right { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.composer-count { font-size: 10px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.composer-atts { display: flex; flex-wrap: wrap; gap: 6px; }
.composer-consent { font-size: 10px; color: var(--text-muted); line-height: 1.5; margin: 0; }

/* Profession tags. Unpicked chips are dimmed rather than outlined, so the
   picked ones read as the state and the row doesn't look like a toolbar. */
.composer-profs { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
.composer-profs-label {
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-muted); margin-right: 3px;
}
/* Shared by the post composer and the inventory item editor — same control,
   same question ("which craft is this?"), so it should not look like two. */
.prof-picker { display: flex; flex-wrap: wrap; gap: 5px; }
.prof-pick { cursor: pointer; border: 0; opacity: 0.45; font: inherit; font-size: 10px; }
.prof-pick:hover:not(:disabled) { opacity: 0.8; }
.prof-pick.picked { opacity: 1; }
.prof-pick:disabled { cursor: default; opacity: 0.2; }

/* ---------- reposts ----------
   The line above says who passed it on; the card below is the original, in the
   ORIGINAL author's colours — so a repost looks like holding up somebody
   else's work rather than like writing it. */
.repost-by {
  display: flex; align-items: center; gap: 5px;
  font-size: 10px; color: var(--text-muted); margin-bottom: 7px;
}
.repost {
  border: 0.5px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-1);
  padding: 10px 12px;
  margin-top: 2px;
}
.repost .post-head { margin-bottom: 6px; }
.repost .post-body { font-size: 12px; margin-bottom: 0; }
.post-act.is-on { color: var(--text-accent); border-color: currentColor; }
.draft-att {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; padding: 3px 6px 3px 8px; border-radius: var(--radius-pill);
  background: var(--surface-3); color: var(--text-secondary);
}
.draft-att-x {
  border: 0; background: none; color: var(--text-muted);
  cursor: pointer; font-size: 13px; line-height: 1; padding: 0 2px;
}
.draft-att-x:hover { color: var(--danger); }

/* A post reads in the POSTER'S colours — `.brand-card` + `data-skin-card`
   resolves the skin token block in skins.css against this element, so every
   `var(--surface-*)` and `var(--text-*)` below picks up theirs instead of
   yours. Nothing here names a colour; that's what makes it work. */
.post {
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-md);
  padding: 11px 14px;
  margin-bottom: 8px;
  color: var(--text-primary);
}
.post-head { display: flex; align-items: center; gap: 10px; margin-bottom: 7px; }
.post-who { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.post-name { font-size: 13px; color: var(--text-primary); }
.post-when { font-size: 10px; color: var(--text-muted); }
.post-del, .post-edit { flex-shrink: 0; }
.post-edited { font-size: 10px; color: var(--text-muted); font-style: italic; flex-shrink: 0; }
.post-body { font-size: 13px; line-height: 1.55; margin: 0 0 9px; white-space: pre-wrap; word-break: break-word; }

/* Inline post editor — body + audience, in place. */
.post-edit-form { margin: 0 0 10px; display: flex; flex-direction: column; gap: 8px; }
.post-edit-body {
  width: 100%; font-size: 13px; line-height: 1.5; resize: vertical; min-height: 56px;
  padding: 8px 10px; border-radius: var(--radius); border: 0.5px solid var(--border-strong);
  background: var(--surface-2); color: var(--text-primary);
}
.post-edit-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.post-edit-vis { flex-shrink: 0; }
.post-edit-actions { display: flex; gap: 6px; margin-left: auto; }
.post-edit-err { font-size: 12px; color: var(--danger); margin: 0; }
.post-media { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 6px; margin-top: 9px; }
.post-media-tile {
  padding: 0; border: 0; background: none; cursor: zoom-in;
  border-radius: var(--radius-sm); overflow: hidden; line-height: 0;
  position: relative;
}
/* A fixed aspect so a portrait phone photo and a landscape one sit in the same
   grid without one of them dictating the row height. */
.post-media-thumb { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; }
.post-media .media-thumb-wrap { display: block; position: relative; }

/* Staged media in the composer, before the post exists. */
.composer-media { display: flex; flex-wrap: wrap; gap: 6px; }
.draft-media { position: relative; display: inline-block; line-height: 0; }
.draft-media-thumb {
  width: 62px; height: 62px; object-fit: cover; display: block;
  border-radius: var(--radius-sm); border: 0.5px solid var(--border);
}
.draft-media-x {
  position: absolute; top: -5px; right: -5px;
  width: 17px; height: 17px; line-height: 1;
  border-radius: 50%; border: 0.5px solid var(--border-strong);
  background: var(--surface-1); color: var(--text-secondary);
  cursor: pointer; font-size: 12px; padding: 0;
}
.draft-media-x:hover { color: var(--danger); }
.composer-file {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 28px; border-radius: var(--radius-sm);
  border: 0.5px solid var(--border-strong); background: var(--surface-2);
  color: var(--text-secondary); cursor: pointer;
}
.composer-file:hover { color: var(--text-primary); border-color: var(--text-accent); }

/* ---------- the "post this?" offer ----------
   A quiet corner card, not a modal. It never blocks what you were doing, it
   dismisses itself, and the whole point of the Settings toggle is that it must
   never become something you have to fight. Toast band, above the top bar. */
.share-offer {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 1010;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px;
  border-radius: var(--radius, 8px);
  background: var(--surface-1);
  border: 0.5px solid var(--border-strong);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
  font-size: 12px;
  color: var(--text-secondary);
  max-width: min(92vw, 420px);
}
.share-offer-text b { color: var(--text-primary); font-weight: 600; }
.share-offer-yes {
  text-decoration: none;
  font-size: 12px;
  padding: 4px 12px;
  border-radius: var(--radius-pill);
  background: var(--border-accent, var(--text-accent));
  color: var(--accent-fg);
  white-space: nowrap;
}
.share-offer-yes:hover { background: var(--accent-hover); }
.share-offer-no {
  border: 0; background: none; color: var(--text-muted);
  font: inherit; font-size: 12px; cursor: pointer; white-space: nowrap;
}
.share-offer-no:hover { color: var(--text-secondary); }

/* ---------- direct messages ----------
   A list beside a thread on a desktop; the thread alone on a phone, where the
   list would eat the whole screen. */
.dm-layout { display: grid; grid-template-columns: 240px 1fr; gap: 12px; align-items: start; }
/* `min-width: 0` on both tracks. A grid item defaults to `min-width: auto`,
   which refuses to shrink below its content — so the thread header's negative
   bleed margins pushed the whole page 1px wide at 390 and broke the
   zero-overflow guarantee. This is the fix for that whole family of bug. */
.dm-list, .dm-thread { min-width: 0; }
.dm-list { display: flex; flex-direction: column; gap: 4px; }
.dm-row {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 8px 10px; border-radius: var(--radius-sm);
  border: 0.5px solid transparent; background: none;
  font: inherit; text-align: left; cursor: pointer;
}
.dm-row:hover { background: var(--surface-2); }
.dm-row.is-open { background: var(--surface-2); border-color: var(--border-strong); }
.dm-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.dm-row-top { display: flex; align-items: baseline; gap: 6px; }
.dm-row-name { font-size: 12px; color: var(--text-primary); }
.dm-row-when { font-size: 10px; color: var(--text-muted); margin-left: auto; }
.dm-row-preview {
  font-size: 11px; color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dm-row.is-unread .dm-row-name { font-weight: 600; }
.dm-row.is-unread .dm-row-preview { color: var(--text-secondary); }
.dm-row-dot {
  flex-shrink: 0; min-width: 17px; height: 17px; padding: 0 5px;
  border-radius: var(--radius-pill); background: var(--border-accent, var(--text-accent));
  color: var(--accent-fg); font-size: 10px; line-height: 17px; text-align: center;
}
.dm-badge {
  min-width: 16px; height: 16px; padding: 0 4px; margin-left: 5px;
  border-radius: var(--radius-pill); background: var(--border-accent, var(--text-accent));
  color: var(--accent-fg); font-size: 10px; line-height: 16px; text-align: center;
}

.dm-thread {
  border: 0.5px solid var(--border); border-radius: var(--radius);
  background: var(--surface-1); padding: 12px 14px;
  display: flex; flex-direction: column; min-height: 380px;
  /* The header bleeds to the card edges with negative margins; this stops that
     bleed from extending the page's scrollable area. The thread has its own
     inner scroller, so nothing is lost. */
  overflow: hidden;
}
/* The header wears THEIR skin — the same branding the feed uses, so you can
   see whose conversation you're in without reading the name. */
.dm-head {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 11px; margin: -12px -14px 10px;
  border-bottom: 0.5px solid var(--border);
  border-radius: var(--radius) var(--radius) 0 0;
  background: var(--surface-1);
}
.dm-head-text { display: flex; flex-direction: column; }
.dm-head-name { font-size: 13px; color: var(--text-primary); }
.dm-head-handle { font-size: 10px; color: var(--text-muted); }

.dm-scroll { flex: 1; overflow-y: auto; max-height: 46vh; display: flex; flex-direction: column; gap: 9px; padding-right: 3px; }
.dm-msg { display: flex; flex-direction: column; align-items: flex-start; max-width: 76%; }
.dm-msg.is-mine { align-self: flex-end; align-items: flex-end; }
.dm-bubble {
  padding: 7px 11px; border-radius: 13px;
  background: var(--surface-2); color: var(--text-primary);
  font-size: 12px; line-height: 1.5; word-break: break-word; white-space: pre-wrap;
}
.dm-msg.is-mine .dm-bubble { background: var(--border-accent, var(--text-accent)); color: var(--accent-fg); }
/* A mention inside your own bubble sits on the accent, where the usual chip
   colours would vanish. */
.dm-msg.is-mine .dm-bubble .mention { background: rgba(0, 0, 0, 0.18); color: inherit; }
.dm-msg.is-mine .dm-bubble .body-link { color: inherit; }
.dm-when { font-size: 10px; color: var(--text-muted); margin-top: 2px; }
.dm-msg .att-card { max-width: 340px; }

.dm-atts { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.dm-write { display: flex; gap: 6px; margin-top: 10px; align-items: center; position: relative; }
.dm-input {
  flex: 1; min-width: 0;
  background: var(--surface-2); border: 0.5px solid var(--border);
  border-radius: var(--radius-pill); color: var(--text-primary);
  font: inherit; font-size: 12px; padding: 7px 13px;
}
.dm-input:focus { outline: none; border-color: var(--border-accent, var(--text-accent)); }
.dm-closed { margin-top: 10px; font-style: italic; }

.body-link { color: var(--text-accent); text-decoration: underline; text-underline-offset: 2px; }

@media (max-width: 860px) {
  /* The list would take the whole screen next to a thread, so it stacks. */
  .dm-layout { grid-template-columns: 1fr; }
  .dm-scroll { max-height: 58vh; }
  .dm-msg { max-width: 88%; }
}

.settings-toggle-row { display: flex; flex-direction: column; gap: 6px; }
.settings-toggle { display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 13px; color: var(--text-primary); }
.settings-toggle input { width: 15px; height: 15px; accent-color: var(--border-accent, var(--text-accent)); cursor: pointer; }
.post-profs { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 9px; }

/* Giving. Not "likes" — each of these costs the giver something, so they read
   as deliberate buttons rather than as a row of taps. */
.post-acts {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin-top: 10px; padding-top: 9px; border-top: 0.5px solid var(--border);
}
.post-act {
  display: inline-flex; align-items: center; gap: 5px;
  font: inherit; font-size: 11px;
  padding: 3px 10px; border-radius: var(--radius-pill);
  border: 0.5px solid var(--border-strong);
  background: transparent; color: var(--text-secondary);
  cursor: pointer;
}
.post-act:hover:not(:disabled) { border-color: currentColor; color: var(--text-primary); }
.post-act b { font-variant-numeric: tabular-nums; }
/* Given already: still visible, because seeing that you gave is the point of
   having given. Just no longer pressable. */
.post-act.is-given { opacity: 0.55; cursor: default; border-style: dashed; }
.post-act.xp { color: var(--xp); }
.post-act-count {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11px; color: var(--text-secondary); font-variant-numeric: tabular-nums;
}
.post-act-count.xp { color: var(--xp); }
.post-acts.is-mine { border-top-color: transparent; padding-top: 4px; }

/* Aggregate coin tally: the total across everyone's currencies, click for the
   breakdown by name. Neutral coins glyph because the currencies are mixed. */
.post-coin-tally { color: var(--coin); cursor: pointer; }
.post-coin-tally b { font-variant-numeric: tabular-nums; }
.coin-breakdown-pop { position: fixed; z-index: 1050; min-width: 150px; padding: 8px;
  display: flex; flex-direction: column; gap: 5px; background: var(--surface-1);
  border: 1px solid var(--border); border-radius: var(--radius-md); box-shadow: 0 12px 32px rgba(0,0,0,0.45); }
.coin-breakdown-row { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: currentColor; }
.coin-breakdown-row b { color: var(--text-primary); font-variant-numeric: tabular-nums; }

/* An @name. Deliberately not a link — there is no public profile page to send
   anyone to, and a chip that looked clickable and wasn't would be worse than
   one that plainly isn't. */
.mention {
  color: var(--text-accent);
  background: var(--surface-3);
  border-radius: 4px;
  padding: 0 3px;
}

/* ---------- comment threads ----------
   Inside the post card, so they inherit the poster's skin along with it. */
.thread { margin-top: 10px; border-top: 0.5px solid var(--border); padding-top: 9px; }
.thread-list { display: flex; flex-direction: column; gap: 9px; }
.comment { display: flex; gap: 8px; align-items: flex-start; }
.comment-main { flex: 1; min-width: 0; }
.comment-head { display: flex; align-items: baseline; gap: 7px; }
.comment-who { font-size: 11px; color: var(--text-primary); }
.comment-when { font-size: 10px; color: var(--text-muted); }
.comment-x {
  margin-left: auto; border: 0; background: none; color: var(--text-muted);
  cursor: pointer; font-size: 13px; line-height: 1; padding: 0 3px;
}
.comment-x:hover { color: var(--danger); }
/* `break-word` matters here: a comment is free text and somebody will paste a
   URL with no spaces in it. */
.comment-body { font-size: 12px; line-height: 1.5; color: var(--text-secondary); word-break: break-word; }

/* ---------- the @ type-ahead ----------
   Anchored to whichever input it was attached to, so both the post composer
   and every comment box get the identical component. */
.composer, .thread-write { position: relative; }
.mention-menu {
  position: absolute;
  left: 0;
  top: 100%;
  z-index: 60;              /* attached popup band, same as the notif panel */
  min-width: 240px;
  max-width: 100%;
  background: var(--surface-1);
  border: 0.5px solid var(--border-strong);
  border-radius: var(--radius-sm);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  overflow: hidden;
}
.mention-opt {
  display: flex;
  align-items: baseline;
  gap: 7px;
  width: 100%;
  padding: 7px 10px;
  border: 0;
  background: none;
  color: var(--text-secondary);
  font: inherit;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
}
.mention-opt:hover, .mention-opt.active { background: var(--surface-3); color: var(--text-primary); }
.mention-opt-name { color: var(--text-primary); }
.mention-opt-handle { color: var(--text-muted); font-size: 11px; }
.mention-opt-tag {
  margin-left: auto;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-accent);
  white-space: nowrap;
}

.thread-write { display: flex; gap: 6px; margin-top: 10px; }
.thread-input {
  flex: 1; min-width: 0;
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-pill);
  color: var(--text-primary);
  font: inherit; font-size: 12px;
  padding: 6px 12px;
}
.thread-input:focus { outline: none; border-color: var(--border-accent, var(--text-accent)); }
.thread-send, .thread-attach { flex-shrink: 0; }
.thread-attach.is-on { color: var(--text-accent); }
/* The reply's record picker: two selects that appear under the write row when
   the paperclip is pressed, and the staged-record chip once one is chosen. */
.thread-picker { display: flex; gap: 6px; margin-top: 6px; flex-wrap: wrap; }
.thread-picker.hidden { display: none; }
.thread-picker select { flex: 1; min-width: 0; }
.thread-att:not(:empty) { margin-top: 6px; }

/* An attachment card. The left edge carries the record's own category colour,
   which is the app's semantic layer doing its job one level down. */
/* `flex-wrap` and a full-width detail row: the card is a row until it is
   expanded, at which point the snapshot drops underneath rather than squeezing
   in beside the thumbnail. */
.att-card {
  display: flex;
  gap: 11px;
  align-items: center;
  flex-wrap: wrap;
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-left: 2px solid var(--cls-color, var(--border-strong));
  border-radius: var(--radius-sm);
  padding: 9px 11px;
  margin-top: 8px;
}
.att-shot { width: 54px; height: 54px; flex-shrink: 0; border-radius: var(--radius-sm); overflow: hidden; }
.att-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.att-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.att-kind {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-muted);
}
.att-name { font-size: 13px; color: var(--text-primary); }
.att-name a { color: inherit; text-decoration: none; }
.att-name a:hover { color: var(--text-accent); text-decoration: underline; }
/* The arrow is the only thing distinguishing a linked attachment from an
   unlinked one, so it stays visible rather than appearing on hover — on a touch
   screen there is no hover to reveal it. */
.att-go { font-size: 11px; margin-left: 3px; opacity: 0.65; vertical-align: -1px; }
.att-sub { font-size: 11px; color: var(--text-secondary); }
.att-foot { display: flex; align-items: center; gap: 8px; margin-top: 4px; flex-wrap: wrap; }
.att-foot .chip { margin: 0; }
.att-more {
  display: inline-flex; align-items: center; gap: 3px;
  background: transparent; border: none; padding: 0;
  font: inherit; font-size: 10px; color: var(--text-muted); cursor: pointer;
}
.att-more:hover { color: var(--text-primary); }
.att-more i { font-size: 12px; }

/* The expanded snapshot. Full width of the card, below everything else. */
.att-detail {
  flex-basis: 100%;
  margin-top: 9px;
  padding-top: 9px;
  border-top: 0.5px solid var(--border);
  font-size: 12px;
  color: var(--text-secondary);
}
.att-row { display: flex; gap: 8px; padding: 2px 0; }
.att-row span { color: var(--text-muted); min-width: 96px; flex-shrink: 0; }
.att-row b { font-weight: 500; color: var(--text-primary); }
.att-block { margin-top: 8px; }
.att-block-head {
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--text-muted); margin: 0 0 3px;
}
.att-detail ul { margin: 0; padding-left: 18px; }
.att-detail li { padding: 1px 0; }
.att-desc { margin: 0 0 6px; font-style: italic; }
.att-note { margin: 0; line-height: 1.55; white-space: pre-wrap; }

/* ---------- the hover profile card ----------
   Every block below is its own class on purpose — `.pc-head`, `.pc-profs`,
   `.pc-stats`, `.pc-rel` — so a later skin can restyle any one of them
   without the others. One layout for now; the seams are what makes a second one
   cheap.
   Band 950-999, the transient-overlay band from the table at the top of
   effects.css: it appears over the page, belongs to no control permanently, and
   must clear the top bar's 900. */
.profile-card {
  position: absolute;
  z-index: 960;
  width: 288px;
  max-width: calc(100vw - 16px);
  background: var(--surface-1);
  border: 0.5px solid var(--border-strong);
  border-radius: var(--radius-lg);
  box-shadow: 0 14px 40px rgba(0,0,0,0.55);
  padding: 13px;
}
.pc-head { display: flex; align-items: center; gap: 11px; }
.pc-who { display: flex; flex-direction: column; min-width: 0; }
.pc-name { font-size: 13px; font-weight: 600; color: var(--text-primary); }
.pc-handle { font-size: 11px; color: var(--text-muted); }
.pc-level { font-size: 10px; color: var(--text-secondary); margin-top: 2px; }

.pc-profs { margin-top: 11px; display: flex; flex-direction: column; gap: 5px; }
.pc-prof { display: grid; grid-template-columns: 1fr auto; gap: 3px 8px; align-items: center; }
.pc-prof-name {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11px; padding: 1px 7px; border-radius: var(--radius-pill); justify-self: start;
}
.pc-prof-lvl { font-size: 11px; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.pc-prof-bar {
  grid-column: 1 / -1; height: 3px; border-radius: 2px;
  background: var(--surface-3); overflow: hidden;
}
.pc-prof-bar i { display: block; height: 100%; background: var(--xp); }

.pc-stats {
  margin-top: 11px; padding-top: 10px;
  border-top: 0.5px solid var(--border);
  display: flex; flex-direction: column; gap: 7px;
}
.pc-stat { display: flex; align-items: baseline; gap: 7px; font-size: 11px; }
.pc-stat > i { font-size: 13px; color: var(--text-muted); flex-shrink: 0; }
.pc-stat-main { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.pc-stat-main b { font-size: 12px; font-weight: 600; color: var(--text-primary); }
.pc-stat-main em { font-style: normal; font-size: 10px; color: var(--text-muted); }
.pc-stat-note { font-size: 10px; color: var(--text-muted); flex-shrink: 0; }

/* The card is a PREVIEW, not a menu — its action links are gone (the user's
   call), because a person's name now navigates on its own. All that is left of
   that row is the relationship, which is information rather than a control. */
.pc-rel {
  margin-top: 11px; padding-top: 9px;
  border-top: 0.5px solid var(--border);
  font-size: 10px; color: var(--text-muted);
}

/* Anything that opens a card says so on hover. */
[data-card-user] { cursor: pointer; }

/* ---------- take-a-copy wizard ---------- */
.copy-modal {
  background: var(--surface-1);
  border: 0.5px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 20px 22px;
  max-width: 520px;
  width: 92vw;
  max-height: 84vh;
  overflow-y: auto;
  position: relative;
}
.copy-title { font-size: 15px; margin: 0 0 6px; }
.copy-note { font-size: 11px; color: var(--text-muted); margin: 6px 0; line-height: 1.5; }
.copy-sub { font-size: 11px; color: var(--text-muted); text-transform: lowercase; margin: 14px 0 6px; }
.copy-modes { display: flex; gap: 0; border: 0.5px solid var(--border); border-radius: var(--radius); overflow: hidden; margin: 10px 0; }
.copy-items { display: flex; flex-direction: column; gap: 8px; }
.copy-item {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px 10px;
  align-items: center;
  padding: 8px 10px;
  background: var(--surface-2);
  border-radius: var(--radius);
}
.copy-item-name { font-size: 13px; }
.copy-item-qty { color: var(--text-muted); font-size: 11px; }
.copy-item-note { grid-column: 1 / -1; font-size: 10px; color: var(--text-muted); }
/* A "similar name" guess is the one that can quietly merge two things, so it
   is the only outcome that gets a colour. */
.copy-item-note.is-warn { color: #f0cf9d; }
.copy-btn { font-size: 11px; padding: 3px 8px; }
@media (max-width: 860px) {
  .copy-item { grid-template-columns: 1fr; }
}

/* ---------- visibility control ----------
   A three-way choice with consequences, so it's a segmented control rather
   than a dropdown: all three states and the active one are visible without
   opening anything. The hint under it changes with the selection, because the
   difference between "friends" and "public" is the whole decision. */
.vis-switch { display: flex; gap: 0; border: 0.5px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.vis-opt {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  padding: 7px 6px;
  font-size: 12px;
  background: var(--surface-1);
  color: var(--text-secondary);
  border: none;
  border-right: 0.5px solid var(--border);
  cursor: pointer;
  white-space: nowrap;
}
.vis-opt:last-child { border-right: none; }
.vis-opt:hover { background: var(--surface-2); color: var(--text-primary); }
.vis-opt.active { background: var(--surface-2); color: var(--text-primary); font-weight: 600; }
/* Sharing states get colour; private is the quiet default and stays neutral,
   so the eye is drawn to what's exposed rather than what isn't. */
.vis-opt.active[data-vis="friends"] { box-shadow: inset 0 -2px 0 #5b8dd6; }
.vis-opt.active[data-vis="public"] { box-shadow: inset 0 -2px 0 #d6a45b; }
/* Capped by an attachment — a friends-only asset can't ride a public post. The
   title says which. Dimmed and not hoverable, so it reads as unavailable, not
   broken. */
.vis-opt.is-capped { opacity: 0.4; cursor: not-allowed; }
.vis-opt.is-capped:hover { background: none; color: var(--text-muted); }
.vis-hint { font-size: 11px; color: var(--text-muted); margin-top: 5px; }

/* Shown in read mode only when a record is actually shared — a "Private" badge
   on every record is noise, and the useful signal is noticing what's exposed. */
.vis-chip i { font-size: 12px; margin-right: 3px; }
.vis-chip.vis-friends { background: rgba(91,141,214,0.16); color: #9dc0f0; }
.vis-chip.vis-public { background: rgba(214,164,91,0.16); color: #f0cf9d; }

/* ---------- video tiles ----------
   A video thumbnail is the <video> element itself seeking to its own first
   frame — no poster files are generated, so there's nothing to keep in sync.
   The wrapper exists only to hang the play badge over it, and inherits the
   grid sizing from whichever thumb class the caller passed. */
.media-thumb-wrap { position: relative; display: block; width: 100%; height: 100%; line-height: 0; }
.media-thumb-wrap video { background: #000; }
.media-play-badge {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 15px;
  text-shadow: 0 1px 4px rgba(0,0,0,0.9);
  pointer-events: none;
  line-height: 1;
}
/* The observation strip's thumbs are a fixed 44px square rather than filling a
   grid cell, so the wrapper has to match or the badge floats off the tile. */
.fn-obs-media-row .media-thumb-wrap { width: 44px; height: 44px; }

/* ---------- photo lightbox modal ---------- */
.photo-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.8);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}
.photo-modal { position: relative; display: flex; flex-direction: column; align-items: center; gap: 14px; max-width: 90vw; }
.photo-modal-img { max-width: 90vw; max-height: 70vh; object-fit: contain; border-radius: var(--radius); background: var(--surface-1); }
/* Same class for both, so a clip and a still open at the same size. A video
   carries its own controls, which need the black backdrop to stay legible. */
video.photo-modal-img { background: #000; }
.photo-modal-close {
  position: absolute;
  top: -36px; right: 0;
  background: transparent;
  border: none;
  color: #fff;
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
  padding: 4px 8px;
}
.photo-modal-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background: rgba(0,0,0,0.55);
  border: none;
  color: #fff;
  font-size: 26px;
  width: 40px; height: 40px;
  border-radius: 50%;
  cursor: pointer;
}
.photo-modal-prev { left: -20px; }
.photo-modal-next { right: -20px; }
.photo-modal-actions { display: flex; gap: 10px; }

/* ---------- journal ---------- */
.journal-layout { display: flex; gap: 20px; align-items: flex-start; }
.journal-side { width: 190px; flex-shrink: 0; display: flex; flex-direction: column; gap: 8px; }
.journal-side-tools { display: flex; flex-direction: column; gap: 8px; }
.journal-side-tools.hidden-tools { display: none; }

/* ---------- the shelf ----------
   Whose journal you're reading. Each row is a book seen spine-on: a coloured
   edge in the owner's avatar colour, then their name. It reads as a shelf
   rather than a list of names, which is the point — a journal belongs to
   somebody, and the page used to pretend otherwise. */
.shelf { display: flex; flex-direction: column; gap: 3px; margin-bottom: 6px; }
.shelf-book {
  display: flex;
  align-items: stretch;
  gap: 8px;
  padding: 0;
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  text-align: left;
  overflow: hidden;
  min-height: 38px;
}
.shelf-book:hover { border-color: var(--border-accent); }
.shelf-book.active { background: var(--surface-2); border-color: var(--border-accent); }
/* The spine. Its width is the only thing separating "a list item" from "a
   book on a shelf", which is a good trade for four pixels. */
.shelf-spine { width: 5px; flex-shrink: 0; }
.shelf-text { display: flex; flex-direction: column; justify-content: center; padding: 5px 8px 5px 2px; min-width: 0; }
.shelf-name { font-size: 12px; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.shelf-sub { font-size: 10px; color: var(--text-muted); }
.shelf-label { font-size: 10px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; margin: 8px 0 2px; }

/* ---------- the book ----------
   A bound left edge and a running head, so an entry reads as a page rather
   than a record in a detail pane. The binding is a gradient rather than an
   image — same reasoning as the math-drawn backgrounds. */
.book {
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-1);
  overflow: hidden;
  position: relative;
}
.book-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 16px 9px 26px;
  border-bottom: 0.5px solid var(--border);
  background: var(--surface-2);
}
.book-owner { font-size: 12px; color: var(--text-primary); display: flex; align-items: center; gap: 8px; }
.book-readonly {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  border: 0.5px solid var(--border-strong);
  border-radius: var(--radius-pill);
  padding: 1px 6px;
}
.book-folio { font-size: 10px; color: var(--text-muted); }
.book-page {
  position: relative;
  padding-left: 10px;
}
/* The binding: a soft inner shadow down the left edge of the page. */
.book-page::before {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 14px;
  background: linear-gradient(to right, rgba(0,0,0,0.28), rgba(0,0,0,0));
  pointer-events: none;
  z-index: 1;
}
.book-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 16px 9px 26px;
  border-top: 0.5px solid var(--border);
  background: var(--surface-2);
}
.book-foot:empty { display: none; }
.book-turn {
  background: transparent;
  border: none;
  color: var(--text-secondary);
  font-size: 11px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 6px;
  border-radius: var(--radius);
}
.book-turn:hover:not(:disabled) { background: var(--surface-1); color: var(--text-primary); }
.book-turn:disabled { opacity: 0.35; cursor: default; }
.book-folio-sm { font-size: 10px; color: var(--text-muted); }

@media (max-width: 860px) {
  .book-head, .book-foot { padding-left: 16px; }
  .book-page { padding-left: 4px; }
  .book-page::before { width: 8px; }
}
.journal-side input[type="search"] { width: 100%; }
.journal-main { flex: 1; min-width: 0; }
.journal-list { display: flex; flex-direction: column; gap: 2px; max-height: 480px; overflow-y: auto; }
.journal-list-item {
  display: flex;
  flex-direction: column;
  padding: 6px 8px;
  border-radius: var(--radius);
  cursor: pointer;
  border: 0.5px solid transparent;
  color: var(--text-secondary);
}
.journal-list-item:hover { background: var(--surface-2); }
.journal-list-item.active {
  background: var(--surface-1);
  border-color: var(--border);
  color: var(--text-primary);
  font-weight: 500;
}
.journal-list-date { font-size: 12px; }
.journal-list-title {
  font-size: 11px;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.journal-card {
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-md);
  padding: 24px;
  min-height: 400px;
}
.journal-head-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.journal-date-heading {
  font-family: var(--font-voice);
  font-size: 20px;
  font-weight: 400;
  margin: 0;
  cursor: pointer;
  border-bottom: 1px dashed transparent;
}
.journal-date-heading:hover { border-bottom-color: var(--border-strong); }
.journal-date-input { font-family: var(--font-voice); font-size: 20px; padding: 2px 6px; }
.journal-weather { display: flex; align-items: center; gap: 5px; color: var(--text-muted); flex-shrink: 0; }
.journal-weather i { font-size: 13px; }
.journal-weather input {
  width: 150px;
  font-size: 12px;
  background: transparent;
  border: 0.5px solid transparent;
  color: var(--text-muted);
  padding: 3px 4px;
}
.journal-weather input:hover, .journal-weather input:focus { border-color: var(--border-strong); background: var(--surface-1); color: var(--text-primary); }

.journal-title-input {
  font-family: var(--font-voice);
  font-size: 15px;
  width: 100%;
  background: transparent;
  border: 0.5px solid transparent;
  padding: 2px 4px;
  margin: 2px 0 14px;
}
.journal-title-input:hover, .journal-title-input:focus { border-color: var(--border-strong); background: var(--surface-1); }

.journal-meta { display: flex; gap: 20px; flex-wrap: wrap; margin-bottom: 10px; }
.journal-meta .field { flex: 1; min-width: 240px; }
.journal-meta .field label, .journal-card > .field > label { display: block; margin-bottom: 4px; }
.journal-loc-row { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.journal-loc-type { width: 140px; flex-shrink: 0; }
.journal-loc-name { flex: 1; min-width: 120px; }
.journal-loc-coord { width: 96px; }
.journal-hint { font-size: 11px; color: var(--text-muted); margin-top: 4px; }

.journal-tag-row { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.journal-tag {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-size: 11px;
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  padding: 3px 4px 3px 10px;
  border-radius: 12px;
  color: var(--text-secondary);
}
.journal-tag-remove {
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-size: 13px;
  line-height: 1;
  padding: 0 4px;
}
.journal-tag-remove:hover { color: var(--danger); background: transparent; }
.journal-tag-input { width: 80px; font-size: 11px; padding: 3px 8px; border-radius: 12px; border-style: dashed; }

.journal-note-pill-row { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.journal-note-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  padding: 3px 8px;
  border-radius: var(--radius-pill);
  background: var(--surface-1);
  border: 0.5px solid var(--border);
}
.journal-note-pill a { text-decoration: none; }
.journal-note-pill a:hover { text-decoration: underline; }
.journal-note-icon {
  width: 20px; height: 20px;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.journal-link-btn { font-size: 12px; color: var(--text-muted); }
.journal-link-btn:hover { color: var(--text-primary); }

/* journal read-only renders */
.journal-weather-ro { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--text-muted); }
.journal-title-ro { font-family: var(--font-voice); font-size: 15px; color: var(--text-secondary); margin: 2px 0 10px; }
.journal-meta-ro { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-bottom: 4px; }
.journal-loc-ro { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: var(--text-muted); }
.journal-loc-ro i { font-size: 13px; }
.journal-text-ro { font-family: var(--font-voice); font-size: 15px; line-height: 1.8; white-space: pre-wrap; margin: 0; }
.jr-ro-block { position: relative; padding-right: 30px; margin-bottom: 6px; }
.jr-ro-block.is-obs {
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 30px 10px 10px;
}
.jr-block-edit { position: absolute; top: 4px; right: 2px; }

.journal-block { background: var(--surface-1); }
.journal-block-spacer { flex: 1; }
.block-type-badge.type-text { color: var(--text-secondary); }
.block-type-badge.type-images { color: var(--text-accent); }
.block-type-badge.type-observation { color: var(--text-success); }
.journal-text-body {
  width: 100%;
  min-height: 90px;
  font-family: var(--font-voice);
  font-size: 15px;
  line-height: 1.8;
}
.journal-obs-toprow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
.jr-obs-date { flex-shrink: 0; }
.jr-obs-link { display: flex; align-items: center; gap: 6px; flex: 1; min-width: 220px; }
.jr-obs-link .add-ingredient-form { margin-bottom: 0; }

/* mirrored-from-journal marker on the Field Notes side */
.fn-obs-source {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  color: var(--text-muted);
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 2px 9px;
}
.fn-obs-source i { font-size: 12px; }
.fn-obs-source a { text-decoration: none; }
.fn-obs-source a:hover { text-decoration: underline; }

/* ---------- camp (dashboard) ---------- */
.camp-layout { display: flex; flex-direction: column; gap: 12px; }

/* Scrolls with everything else. Capped rather than full-width so the square
   stays a sensible size, and centred so it sits over the middle of the
   two-column panel grid below rather than hugging the left edge. */
/* Map and Town Center share the top row. The map column is capped at the width
   that used to be its centred maximum, so the square keeps exactly the height
   it had — the feed then matches that height rather than setting it.
   `minmax(0, …)` on both tracks: a grid item defaults to `min-width: auto` and
   refuses to shrink below its content, which is what put 1px of overflow on the
   DM layout.

   THE FEED HAS A FLOOR, AND BELOW IT THE ROW BREAKS RATHER THAN CRUSHING IT.
   A post is a badge, a name, a body and a row of five or six action buttons;
   under about 330px those buttons wrap into a stack, the media tiles fall to one
   per line and the card stops reading as a card. So the second track carries
   that floor as its own minimum (`minmax(330px, 1fr)`), and below the width where
   both minimums fit, the Town Center drops to its own full-width row and the map
   centres itself over the panel grid.

   Why a breakpoint and not `auto-fit`, which would wrap on its own: a dropped
   Town Center has no map beside it to take a height from — its contents are
   absolutely positioned, so it would collapse to its floor. It needs an explicit
   height in exactly the case where it wrapped, and an item cannot ask whether it
   wrapped. One media query states both halves of that together.

   1120px is derived, not picked: 170px sidebar + 48px main padding + the 560px
   map + a 12px gap + the 330px floor. If the sidebar or that floor changes, this
   number moves with them. */
.camp-top {
  display: grid;
  grid-template-columns: minmax(0, 560px) minmax(330px, 1fr);
  gap: 12px;
  align-items: stretch;
}
.camp-map-card {
  width: 100%;
  min-width: 0;
}
.camp-panels {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  align-items: stretch;
}
/* The Town Center matches the map's height instead of setting it: the card is
   a flex column, and only the feed inside it scrolls. `min-height: 0` on the
   scroller is the flex equivalent of the grid rule above — without it a flex
   child won't shrink below its content and the panel grows the page instead of
   scrolling. */
/* The panel is a positioning context with NO in-flow content — everything is in
   `.camp-town-inner`, absolutely positioned. That is what makes the map decide
   the row height: a grid row is as tall as its tallest item's content, and this
   item's content contributes nothing. The card then stretches to whatever the
   map set, and the feed scrolls inside it. */
/* Padding moves to the inner box, since the outer one no longer wraps anything
   in flow — `14px` is `.camp-card`'s own value, kept in step so the panel
   lines up with every other card on the page. */
.camp-town { position: relative; min-width: 0; overflow: hidden; min-height: 300px; padding: 0; }
.camp-town-inner {
  position: absolute;
  inset: 0;
  padding: 14px;
  display: flex;
  flex-direction: column;
}
.camp-town-filters { flex-shrink: 0; margin-bottom: 8px; }
.camp-town-feed {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding-right: 4px;
}
/* Tighter than the Socialize column — this is a panel someone glances at. */
.camp-town-feed .post { padding: 9px 11px; margin-bottom: 7px; }
.camp-town-feed .post-media { grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); }

/* The wrap, stated in one place. It comes AFTER the base rules on purpose: an
   override that precedes what it overrides only works by accident of
   specificity, and the assertion that finds `.camp-town`'s base block by name
   reads whichever comes first.

   Every narrower width matches this too, so the old 860px stacking rule is gone
   — a stacked Town Center and a dropped one are the same case, and both need the
   explicit height for the same reason: there is no map on the row to take a
   height from and the panel's contents are out of flow. `max-height` would do
   nothing against a zero-height content box. */
@media (max-width: 1120px) {
  .camp-top { grid-template-columns: minmax(0, 1fr); }
  /* Alone on its row the map would stretch to the full content width and take a
     square height to match, so it keeps its cap and centres instead. */
  .camp-map-card { max-width: 560px; margin-inline: auto; }
  .camp-town { height: 62vh; min-height: 380px; }
  /* A READING COLUMN, once there is nothing to share the row with. Beside the
     map the panel is naturally column-width and needs no cap; dropped below it
     the panel is the full content width — 867px at 1100 — and a post stretched
     across that is a line of text nobody wants to read. Same 640px cap and the
     same reason as `#feedPane` on Socialize, centred so the column sits under
     the map rather than against the left edge. The filters travel with it, or
     the switch floats away from the posts it filters.
     Kept in THIS query rather than a second one with the same condition: the
     assertion for this layout reads the first `@media (max-width: 1120px)`
     block by name, and a second would shadow it. */
  .camp-town-feed,
  .camp-town-filters { max-width: 640px; margin-inline: auto; width: 100%; }
}

.camp-panel { min-height: 300px; display: flex; flex-direction: column; }
.camp-panel > .camp-card-head { flex-shrink: 0; }

.camp-card {
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-md);
  padding: 14px;
  min-width: 0;
}
.camp-card-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.camp-card-title { font-size: 13px; font-weight: 500; }
.camp-card-head a { font-size: 12px; text-decoration: none; }
.camp-card-head a:hover { text-decoration: underline; }
.camp-head-actions { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
/* Deliberately quiet: it's a dashboard control, not a form field, so it stays
   out of the way until you look for it. */
.camp-view-select {
  font-size: 11px;
  padding: 2px 4px;
  background: transparent;
  border: 0.5px solid transparent;
  color: var(--text-muted);
  max-width: 132px;
}
.camp-view-select:hover { border-color: var(--border); color: var(--text-secondary); }
.camp-view-select:focus { border-color: var(--border-strong); color: var(--text-primary); }

.camp-inv-groups { display: flex; flex-direction: column; gap: 10px; flex: 1; min-height: 0; }
.camp-inv-group { display: flex; flex-direction: column; min-height: 0; flex: 1; }
.camp-inv-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin-bottom: 5px;
}
.camp-inv-label span {
  background: var(--surface-2);
  border-radius: var(--radius-pill);
  padding: 1px 7px;
  letter-spacing: 0;
}
/* Full lists, scrolled rather than truncated — a "+37" tile hides exactly the
   thing the dashboard is for. */
.camp-inv-scroll {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(42px, 1fr));
  gap: 6px;
  overflow-y: auto;
  align-content: start;
  flex: 1;
  min-height: 84px;
  padding-right: 2px;
}
.camp-inv-scroll .tile { text-decoration: none; }

.camp-spellbook { display: flex; gap: 8px; align-items: flex-start; flex: 1; min-height: 0; }
.camp-recipe-list { width: 96px; flex-shrink: 0; display: flex; flex-direction: column; gap: 2px; overflow-y: auto; max-height: 100%; }
.camp-recipe-row {
  font-size: 11px;
  text-align: left;
  padding: 5px 6px;
  border-radius: var(--radius);
  background: transparent;
  border: 0.5px solid transparent;
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.camp-recipe-row:hover { background: var(--surface-2); }
.camp-recipe-row.active {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text-primary);
  font-weight: 500;
}
.camp-recipe-more { font-size: 10px; color: var(--text-muted); padding: 4px 6px; }
.camp-recipe-card {
  flex: 1;
  min-width: 0;
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 10px;
  text-decoration: none;
  display: block;
}
.camp-recipe-card:hover { border-color: var(--border-strong); }
.camp-recipe-name { font-size: 12px; font-weight: 500; color: var(--text-primary); margin: 0 0 4px; }
.camp-recipe-ings { font-size: 11px; color: var(--text-secondary); margin: 0 0 6px; line-height: 1.5; }
.camp-recipe-status { font-size: 11px; color: var(--text-muted); margin: 0; }
.camp-recipe-status.is-craftable { color: var(--text-success); }
.camp-recipe-status i { font-size: 12px; vertical-align: -1px; }

.camp-map-wrap { position: relative; }
/* Square at every width, with a floor so it stays usable on a narrow window. */
#campMap {
  aspect-ratio: 1;
  width: 100%;
  min-height: 300px;
  border-radius: var(--radius);
  border: 0.5px solid var(--border);
  background: var(--surface-2);
}
.camp-map-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  color: var(--text-muted);
  pointer-events: none;
}
.camp-map-pin { box-shadow: 0 1px 4px rgba(0,0,0,0.5); }

.camp-journal { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow-y: auto; }
.camp-journal-entry { display: block; text-decoration: none; color: inherit; }
.camp-journal-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.camp-journal-date { font-family: var(--font-voice); font-size: 13px; color: var(--text-primary); }
.camp-journal-weather { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--text-muted); flex-shrink: 0; }
.camp-journal-title { font-family: var(--font-voice); font-size: 12px; color: var(--text-secondary); margin: 2px 0 0; }
.camp-journal-body {
  font-family: var(--font-voice);
  font-size: 12px;
  line-height: 1.7;
  color: var(--text-secondary);
  margin: 6px 0 8px;
}
.camp-journal-body.is-empty { font-style: italic; color: var(--text-muted); }
.camp-journal-entry:hover .camp-journal-date { text-decoration: underline; }
.camp-journal-more { font-size: 11px; color: var(--text-muted); margin: 8px 0 0; }

/* Leaflet popups, restyled from their white default */
.leaflet-popup-content-wrapper {
  background: var(--surface-2);
  color: var(--text-primary);
  border: 0.5px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: 0 6px 20px rgba(0,0,0,0.5);
}
.leaflet-popup-content { margin: 10px 12px; font-family: var(--font-sans); }
.leaflet-popup-tip { background: var(--surface-2); border: 0.5px solid var(--border-strong); }
.leaflet-popup-close-button { color: var(--text-muted) !important; }
.leaflet-popup-close-button:hover { color: var(--text-primary) !important; }
.camp-popup { min-width: 150px; }
.camp-popup p { margin: 0 0 3px; }
.camp-popup-name { font-size: 13px; font-weight: 600; }
.camp-popup-meta { font-size: 11px; color: var(--text-muted); }
.camp-popup-coords { font-family: monospace; font-size: 11px; color: var(--text-secondary); }
.camp-popup-notes { font-size: 11px; color: var(--text-secondary); line-height: 1.5; margin-top: 5px; }
.camp-popup-link { font-size: 11px; display: inline-block; margin-top: 6px; }

.camp-quests { display: flex; flex-direction: column; gap: 3px; overflow-y: auto; flex: 1; min-height: 0; }
.camp-quest-row {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 7px; border-radius: var(--radius);
  text-decoration: none;
}
.camp-quest-row:hover { background: var(--surface-2); }
.camp-quest-row.not-started { opacity: 0.6; }
.camp-quest-row.not-started:hover { opacity: 1; }
.camp-quest-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.camp-quest-name { font-size: 12px; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.camp-quest-main .quest-bar { flex: none; width: 100%; }
.camp-quest-main .quest-bar-fill.job { background: var(--text-accent); }
.camp-quest-frac { font-size: 10px; color: var(--text-muted); flex-shrink: 0; }

.camp-fn-strip {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
  gap: 8px;
  align-content: start;
  overflow-y: auto;
  flex: 1;
  min-height: 0;
}
.camp-fn-item { text-decoration: none; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.camp-fn-tile { overflow: hidden; }
.camp-fn-name {
  font-size: 10px;
  color: var(--text-secondary);
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.camp-fn-item:hover .camp-fn-name { color: var(--text-primary); }

@media (max-width: 900px) {
  .camp-panels { grid-template-columns: 1fr; }
  /* The map KEEPS its cap and its centring here, unlike before. It is a square,
     so full width is also full height — at 900px that is a 680px map nobody
     asked for, pushing everything below it off the screen. */
}

/* ---------- quests ---------- */
.page-sub { font-size: 12px; color: var(--text-muted); margin: 3px 0 0; }
/* Was its own near-copy of `.toolbar` differing only in gap (10 vs 8) and
   bottom margin (18 vs 12) — arbitrary drift, not a decision. It extends the
   shared toolbar now and keeps only the extra breathing room above the board. */
.quest-toolbar { margin-bottom: 18px; }
.quest-controls { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.quest-search { width: 200px; }
.quest-filter { font-size: 12px; padding: 5px 9px; }
/* The kind tabs are the primary switch on this page — Quests and Jobs are two
   different boards — so they get a little more weight than the filters beside
   them, which only narrow whichever board you are on. */
.quest-toolbar .tabs { margin-right: 4px; }
.inv-result-note { font-size: 11px; color: var(--text-muted); margin: -6px 0 10px; }

.quest-layout { display: flex; gap: 20px; align-items: flex-start; }
.quest-board { flex: 1; min-width: 0; }
.quest-detail { width: 380px; flex-shrink: 0; }

/* ---- status rails ----
   The board reads top to bottom as a quest log: what you are doing, what you
   could do, what you have done. The rail header is the one piece of chrome that
   makes that legible at a glance — an icon, a name, a count and a rule running
   to the edge, which is what a game's section header is and costs nothing. */
.quest-rail { margin-bottom: 22px; }
.quest-rail-head {
  display: flex; align-items: center; gap: 8px;
  margin: 0 0 10px;
  color: var(--text-secondary);
}
.quest-rail-head i { font-size: 13px; color: var(--text-muted); }
.quest-rail-head h2 {
  font-size: 11px; font-weight: 600; margin: 0;
  text-transform: uppercase; letter-spacing: 0.09em;
}
.quest-rail-count {
  font-size: 10px; font-variant-numeric: tabular-nums;
  padding: 1px 7px; border-radius: 8px;
  background: var(--surface-2); color: var(--text-muted);
}
.quest-rail-rule { flex: 1; height: 1px; background: var(--border); }
.quest-rail[data-rail="active"] .quest-rail-head i,
.quest-rail[data-rail="active"] .quest-rail-head h2 { color: var(--text-accent); }

/* Category survives as a divider inside a rail, not as the top-level split. */
.quest-subsection { margin-bottom: 14px; }
.quest-divider {
  display: flex; align-items: center; gap: 6px;
  font-size: 10px; color: var(--text-muted);
  margin: 0 0 6px;
}
.quest-divider span { width: 7px; height: 7px; border-radius: 2px; flex-shrink: 0; }

.quest-section { margin-bottom: 18px; }
.quest-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 8px; }

/* ---- the hero card ----
   Wide rather than gridded: an objective list wants a full line of text, and two
   heroes side by side at 300px each would wrap every step. `auto-fill` with a
   large minimum means a very wide window gets two columns and everything else
   gets one, without a breakpoint. */
/* `min(420px, 100%)` rather than a bare 420px: a bare track minimum is a floor
   the grid will honour by overflowing its container, which is exactly how this
   put 44px of horizontal scroll on a 390px phone. The `min()` lets the track
   collapse to the column width once the column is the smaller of the two. */
.quest-heroes { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(420px, 100%), 1fr)); gap: 10px; }
.quest-hero {
  position: relative;
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  /* The category colour runs down the leading edge — the one place a card can
     carry a colour at full strength without competing with its own text. */
  border-left: 3px solid var(--cls-color, var(--text-accent));
  border-radius: 10px;
  padding: 12px 14px;
  cursor: pointer;
}
.quest-hero:hover { background: var(--surface-2); }
.quest-hero.selected { border-color: var(--border-accent); border-left-color: var(--cls-color, var(--text-accent)); box-shadow: var(--glow); }
.quest-hero-head { display: flex; align-items: flex-start; gap: 10px; padding-right: 22px; }
.quest-hero-plaque {
  width: 34px; height: 34px; border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.quest-hero-title { flex: 1; min-width: 0; }
/* WRAPS RATHER THAN TRUNCATES. The hero is the card you are meant to act on and
   the name is how you recognise which quest it is — "Cook the winter in…" and
   "Finish the jade cab…" are both useless. It gets two lines and then clips,
   which at this size is four or five words more than an ellipsis allows.
   The board column is narrow whenever the detail panel is open, so this is the
   normal case, not the edge one. */
.quest-hero-title h3 {
  font-size: 15px; font-weight: 600; margin: 0 0 3px;
  line-height: 1.25;
  overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.quest-hero-sub { display: flex; align-items: center; gap: 6px; margin: 0; flex-wrap: wrap; }
/* was .quest-cat-chip — now `.chip .chip-sm`. */
.quest-due { font-size: 10px; color: var(--text-muted); }
.quest-hero-count {
  font-size: 20px; font-weight: 600; line-height: 1;
  color: var(--text-primary); font-variant-numeric: tabular-nums; flex-shrink: 0;
}
.quest-hero-count span { font-size: 12px; color: var(--text-muted); font-weight: 400; }
.quest-hero-flavor {
  font-size: 11px; font-style: italic; color: var(--text-secondary);
  margin: 8px 0 0; line-height: 1.5;
}
.quest-hero-steps { list-style: none; margin: 9px 0 0; padding: 0; }
.quest-hero-steps li {
  display: flex; align-items: center; gap: 7px;
  font-size: 12px; color: var(--text-secondary);
  padding: 2px 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.quest-hero-steps li.more { color: var(--text-muted); font-size: 10px; padding-left: 21px; }
.quest-hero-ready {
  display: flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--text-success);
  margin: 10px 0 0;
}
.quest-cbox.mini { width: 13px; height: 13px; border-width: 1.5px; border-radius: 3px; font-size: 9px; }
.quest-hero-foot { display: flex; align-items: center; gap: 7px; margin-top: 11px; flex-wrap: wrap; }
/* A little taller here than on a tile — this is the card you are meant to read
   the progress off, and 4px of colour on a 420px run is not a progress bar. */
.quest-hero-foot .quest-pips, .quest-hero-foot .quest-bar { flex: 1; min-width: 60px; height: 6px; }
.quest-hero-foot .quest-pip { height: 6px; }

.quest-card {
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-left: 2px solid var(--cls-color, var(--border));
  border-radius: var(--radius-md);
  padding: 9px 10px;
  cursor: pointer;
  position: relative;
}
.quest-card:hover { background: var(--surface-2); }
/* Same convention as inventory tiles: dashed and faded means not started. The
   leading edge keeps its solid category colour — that stripe is what tells the
   two weights apart from across the page, so it shouldn't go dashed too. */
.quest-card.not-started { border-style: dashed; border-left-style: solid; opacity: 0.62; }
.quest-card.not-started:hover { opacity: 0.9; }
.quest-card.selected { border-color: var(--border-accent); border-style: solid; border-left-color: var(--cls-color, var(--border-accent)); opacity: 1; }
.quest-card-pencil { position: absolute; top: 5px; right: 4px; }

.quest-card-row1 { display: flex; align-items: center; gap: 8px; margin-bottom: 7px; padding-right: 18px; }
.quest-icon {
  width: 22px; height: 22px;
  border-radius: 5px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.quest-card h3 {
  font-size: 13px; font-weight: 500; margin: 0; flex: 1;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.status-pill { font-size: 9px; padding: 2px 6px; border-radius: 8px; flex-shrink: 0; white-space: nowrap; }
.status-pill.available { background: var(--surface-2); color: var(--text-muted); }
.status-pill.progress { background: rgba(125,178,245,0.16); color: var(--text-accent); }
.quest-card.quest .status-pill.progress { background: rgba(232,166,136,0.16); color: #e8a688; }
.status-pill.complete { background: var(--bg-success); color: var(--text-success); }

.quest-card-row2 { display: flex; align-items: center; gap: 6px; }
.quest-tag {
  font-size: 10px; padding: 1px 7px; border-radius: 8px;
  white-space: nowrap; flex-shrink: 0;
}
/* The "+N more professions" counter on a small card. A count, not a category,
   so it is deliberately neutral rather than any `.c-*` colour. */
.quest-tag-more {
  background: var(--surface-3);
  color: var(--text-muted);
  cursor: default;
}
.quest-bar { flex: 1; height: 4px; background: var(--surface-3); border-radius: 2px; overflow: hidden; min-width: 24px; }
.quest-bar-fill { height: 100%; background: #e8a688; }
.quest-card.job .quest-bar-fill { background: var(--text-accent); }
.quest-card.complete .quest-bar-fill { background: var(--text-success); }

/* A COUNTABLE BAR. One cell per step, so "3 of 5" is readable without the pill
   beside it — which is what a game draws and what a continuous bar can't say.
   `flex: 1` on each cell rather than a fixed width, so a two-step quest and a
   ten-step quest occupy the same run and the row never reflows. */
.quest-pips { display: flex; gap: 2px; flex: 1; min-width: 24px; }
.quest-pip {
  flex: 1;
  height: 4px;
  border-radius: 2px;
  background: var(--surface-3);
}
.quest-pip.on { background: #e8a688; }
.quest-card.job .quest-pip.on, .quest-hero.job .quest-pip.on { background: var(--text-accent); }
.quest-card.complete .quest-pip.on { background: var(--text-success); }

.quest-recur { font-size: 10px; color: var(--text-muted); flex-shrink: 0; }

.collapse-group { margin-top: 20px; border-top: 0.5px solid var(--border); padding-top: 12px; }
.collapse-header {
  display: flex; align-items: center; gap: 6px;
  cursor: pointer; color: var(--text-secondary); font-size: 12px; user-select: none;
}
.collapse-header:hover { color: var(--text-primary); }
.collapse-header .chevron { font-size: 13px; color: var(--text-muted); transition: transform 0.15s; }
.collapse-group.open .chevron { transform: rotate(90deg); }
.collapse-count { color: var(--text-muted); }
.collapse-body { margin-top: 10px; }
.collapse-body .quest-card { opacity: 0.62; }
.collapse-body .quest-card:hover { opacity: 0.92; }

/* ---- the detail panel, as a quest scroll ----
   Same panel component as everywhere else, given a header block and three named
   sections. The headings are the point: "objectives", "rewards", "requirements"
   are what a player scans for, and they were four unlabelled fields in the order
   the code happened to write them. */
.quest-scroll-head {
  margin: -2px -2px 12px;
  padding: 0 0 11px;
  border-bottom: 0.5px solid var(--border);
  /* A hairline of the category colour under the title, so the panel and the card
     you clicked are visibly the same record. */
  box-shadow: inset 0 -1px 0 0 var(--cls-color, transparent);
}
.quest-detail-icon {
  width: 34px; height: 34px; border-radius: 8px;
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.quest-subtype {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  font-size: 11px; color: var(--text-muted); margin: 6px 0 0 44px;
}
.quest-flavor { font-size: 12px; color: var(--text-secondary); font-style: italic; margin: 9px 0 0; line-height: 1.55; }

.quest-block { margin-bottom: 16px; }
.quest-block-head {
  display: flex; align-items: center; gap: 6px;
  font-size: 10px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--text-muted);
  margin: 0 0 8px;
}
.quest-block-head i { font-size: 13px; }
.quest-block-count {
  margin-left: auto;
  letter-spacing: 0; text-transform: none;
  font-variant-numeric: tabular-nums;
}
/* Rewards is the one block worth tinting — it's the answer to "why would I do
   this", and a game puts that in gold. */
.quest-block.reward .quest-block-head { color: var(--xp); }
.req-row { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--text-secondary); margin-bottom: 5px; }
.req-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--text-success); flex-shrink: 0; }
.req-dot.missing { background: #e0664f; }
.req-row.missing { color: #e0a08f; }
.req-short { font-size: 10px; color: var(--text-muted); }
.quest-delta { font-size: 10px; font-weight: 600; min-width: 24px; }
.quest-delta.up { color: var(--text-success); }
.quest-delta.down { color: #e0a08f; }

.quest-step-row {
  display: flex; align-items: center; gap: 9px;
  padding: 7px 0; border-bottom: 0.5px solid var(--border); font-size: 13px; cursor: pointer;
}
.quest-step-row:last-of-type { border-bottom: none; }
.quest-cbox {
  width: 15px; height: 15px; border: 1.5px solid var(--text-muted);
  border-radius: 4px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center; font-size: 10px;
}
.quest-cbox.done { background: var(--text-success); border-color: var(--text-success); color: var(--surface-0); }
.quest-step-row.done > span:last-child { color: var(--text-muted); text-decoration: line-through; }
.quest-hint { font-size: 10px; color: var(--text-muted); margin: 6px 0 0; }

.quest-actions { display: flex; gap: 8px; margin-top: 14px; }
.btn-complete {
  flex: 1;
  background: var(--bg-success);
  border: 0.5px solid rgba(111,209,150,0.35);
  color: var(--text-success);
  border-radius: var(--radius);
  padding: 9px 0;
  font-size: 13px; font-weight: 600;
}
.btn-complete:hover { background: rgba(111,209,150,0.24); }
.quest-abandon { border: 0.5px solid var(--border-strong); color: var(--text-muted); padding: 9px 14px; }
.quest-abandon:hover { color: #e0a08f; border-color: #5a3a34; }

.quest-banner { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.quest-banner-icon {
  width: 30px; height: 30px; border-radius: 50%;
  background: var(--bg-success); color: var(--text-success);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.quest-banner.abandoned .quest-banner-icon { background: var(--surface-2); color: var(--text-muted); }
.quest-banner .t1 { display: block; font-size: 13px; font-weight: 600; }
.quest-banner .t2 { display: block; font-size: 11px; color: var(--text-muted); }
.quest-applied-row { font-size: 12px; color: var(--text-secondary); padding: 2px 0; }

/* quest edit bits */
.quest-row-edit { display: flex; gap: 6px; align-items: center; margin-bottom: 5px; }
.quest-row-edit select, .quest-row-edit input[type="text"] { flex: 1; min-width: 0; }
.quest-qty { width: 56px; flex-shrink: 0; }
.quest-outcome-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin: 8px 0 5px; }
.quest-prof-picker { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.quest-prof-option { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; opacity: 0.45; }
.quest-prof-option.on { opacity: 1; }
.quest-prof-option input { width: 12px; height: 12px; padding: 0; accent-color: var(--border-accent); }
.quest-add-prof { font-size: 11px; color: var(--text-muted); padding: 2px 6px; }
.quest-recur-row { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-secondary); }
.quest-recur-row input[type="checkbox"] { width: 13px; height: 13px; padding: 0; accent-color: var(--border-accent); }
.quest-recur-num { width: 56px; }

/* ---------- completion wizard ---------- */
.wizard-overlay {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.72);
  display: flex; align-items: center; justify-content: center;
  z-index: 1000; padding: 24px;
}
.wizard {
  background: var(--surface-1);
  border: 0.5px solid var(--border-strong);
  border-radius: 12px;
  width: 540px; max-width: 100%; max-height: 100%;
  display: flex; flex-direction: column;
  box-shadow: 0 12px 40px rgba(0,0,0,0.6);
}
.wizard-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 18px 18px 0; }
.wizard-title { font-size: 15px; font-weight: 600; margin: 0; }
.wizard-sub { font-size: 11px; color: var(--text-muted); margin: 3px 0 0; }
.wizard-progress { display: flex; gap: 4px; padding: 14px 18px 0; }
.wizard-dot { flex: 1; height: 3px; border-radius: 2px; background: var(--surface-3); }
.wizard-dot.done { background: rgba(111,209,150,0.5); }
.wizard-dot.active { background: var(--border-accent); }
.wizard-body { padding: 16px 18px; overflow-y: auto; flex: 1; min-height: 0; }
.wizard-body .field { margin-bottom: 12px; }
.wizard-body .field > label { display: block; margin-bottom: 4px; }
.wizard-body input[type="text"], .wizard-body input[type="date"], .wizard-body select, .wizard-body textarea { width: 100%; }
.wizard-lead { font-size: 12px; color: var(--text-secondary); line-height: 1.6; margin: 0 0 14px; }
.wizard-foot { display: flex; align-items: center; gap: 8px; padding: 0 18px 18px; }
.wizard-foot-spacer { flex: 1; }

.wizard-picks { display: flex; flex-direction: column; gap: 6px; }
.wizard-pick {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 11px;
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
  cursor: pointer;
}
.wizard-pick.on { border-color: var(--border-accent); }
.wizard-pick input { width: 13px; height: 13px; padding: 0; accent-color: var(--border-accent); flex-shrink: 0; }
.wizard-pick-ic {
  width: 26px; height: 26px; border-radius: 6px; background: var(--surface-3);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0; color: var(--text-secondary);
}
.wizard-pick-text .t1 { display: block; font-size: 13px; }
.wizard-pick-text .t2 { display: block; font-size: 11px; color: var(--text-muted); }

.wizard-summary { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.wizard-summary li {
  display: flex; align-items: center; gap: 9px;
  font-size: 12px; color: var(--text-secondary);
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 10px;
}
.wizard-summary li i { color: var(--text-muted); font-size: 13px; }

/* ---------- map ---------- */
/* ============================================================
   FULL-FRAME MAP — the map IS the page. A deliberate exception to the app's
   normal padded, scrolling `.main`: here the map fills the whole content frame
   (sidebar → top bar → right/bottom edges) and every other element floats above
   it. No page scroll. Chrome is readable over the map because it carries its own
   card background.
   ============================================================ */
body.map-page { overflow: hidden; }

/* ============================================================================
   THE MAP SHELL (Pokémon-GO navigation) — the map is the base, subpages float
   on top as a stack of full-frame layers. Center button = menu / X; a back
   button pops one; layers are embedded iframes of the existing pages.
   ============================================================================ */
.shell-stack { position: fixed; inset: 0; z-index: 960; pointer-events: none; }
.shell-layer {
  position: fixed; inset: 0; background: var(--surface-0);
  pointer-events: none; opacity: 0; transition: opacity 0.16s ease;
}
/* A hidden layer must NOT capture clicks — the persistent menu layer sits over the
   map at opacity 0, and with pointer-events:auto it silently ate every map click
   (the Layers button "stopped working"). Only a shown layer is interactive. */
.shell-layer.shown { opacity: 1; pointer-events: auto; }
.shell-frame { width: 100%; height: 100%; border: 0; display: block; }

/* The main menu — a full-frame, fat-finger nav grid, with the app's TOP BAR
   relocated into its header (search + account chip). */
.shell-menu { display: flex; flex-direction: column; background: var(--surface-1); }
.shell-menu-head { flex-shrink: 0; border-bottom: 0.5px solid var(--border); }
.shell-menu-head .topbar { position: static; display: flex; width: 100%; border-bottom: 0; }
.shell-menu-head .topbar-brand { display: none; } /* the hamburger/logo is retired here */
/* The tiles FILL the space: a responsive number of columns, and rows share the
   height equally (grid-auto-rows: 1fr against the flex-sized container), so the
   whole nav fits without scrolling and each tile grows to use what's there. The
   bottom padding reserves room for the center menu button that floats over it.
   overflow-y:auto stays only as a safety net for an extreme (tiny/landscape)
   viewport — in normal use the rows resolve to exactly the available height, so
   no scrollbar appears. min-height:0 lets the flex child actually shrink. */
.shell-menu-grid {
  flex: 1; min-height: 0; overflow-y: auto; padding: 20px 20px 96px; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  grid-auto-rows: 1fr; gap: 14px;
}
.shell-menu-item {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  min-height: 88px; background: var(--surface-2); border: 0.5px solid var(--border);
  border-radius: var(--radius-md); color: var(--text-primary); cursor: pointer;
  transition: transform 0.08s ease, border-color 0.12s ease;
}
.shell-menu-item:hover { border-color: var(--border-strong); background: var(--surface-3, var(--surface-2)); }
.shell-menu-item:active { transform: scale(0.96); }
.shell-menu-ico { font-size: 26px; color: var(--border-accent); display: flex; }
.shell-menu-ico img { width: 26px; height: 26px; }
.shell-menu-label { font-size: 13px; font-weight: 600; }

/* The center menu button — your active profession's icon + colour, X while open. */
.shell-center {
  position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
  width: 66px; height: 66px; border-radius: 50%; z-index: 971;
  border: 3px solid var(--surface-1); color: #fff;
  background: var(--border-accent);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  box-shadow: 0 4px 18px rgba(0,0,0,0.55);
  transition: transform 0.1s ease;
}
.shell-center i { font-size: 26px; }
.shell-center img { width: 28px; height: 28px; }
.shell-center:active { transform: translateX(-50%) scale(0.94); }
.shell-center:hover { filter: brightness(1.14); box-shadow: 0 6px 24px rgba(0,0,0,0.62); }
/* A base element fills SOLID with its colour; a custom class (mix) uses its element
   colour as an OPAQUE base with the gradient sheen on top (set as a background-image
   inline) — never see-through to the map (the gradient tints are only ~18% alpha).
   White icon reads on both. */
.shell-center[class*="la-"] { background-color: var(--cls-color); }
.shell-center.is-open { background-color: var(--surface-2); background-image: none; color: var(--text-primary); border-color: var(--border-strong); }
.shell-center.is-open:hover { filter: brightness(1.1); }

/* Back button — top-left, pops one layer. It FLOATS over the page as you scroll
   (great), so embedded pages reserve a top strip (--shell-top) that keeps their
   header/filters clear of it at the top of scroll. */
.shell-back {
  position: fixed; top: 10px; left: 12px; z-index: 971;
  width: 42px; height: 42px; border-radius: 50%;
  background: var(--surface-1); border: 0.5px solid var(--border-strong); color: var(--text-primary);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  box-shadow: 0 2px 12px rgba(0,0,0,0.5);
}
.shell-back.hidden { display: none; }

/* The map now carries no top bar / sidebar / hamburger / help — it fills the whole
   frame (its top bar moved into the menu). */
html.shell-active body.map-page .app-shell { min-height: 100vh; }
html.shell-active body.map-page .main.map-full { height: 100vh; }
html.shell-active body.map-page .sidebar { display: none; }
body.map-page .tut-fab { display: none; } /* help "?" retired for now (no home in the new layout) */

/* EMBEDDED MODE — a page shown inside a shell layer hides its own chrome so only
   its content fills the frame; the shell provides the menu + back. */
html.embedded .topbar,
html.embedded .sidebar,
html.embedded .nav-scrim,
html.embedded .qc-fab,
html.embedded .tut-fab { display: none !important; }
html.embedded .app-shell { min-height: 100vh; }
html.embedded body { overflow-y: auto; }
/* Reserve a strip at the top so the shell's floating back button doesn't cover
   the page's header / filters / tabs at the top of scroll (it still floats over
   the content as you scroll down). */
html.embedded .main { padding-top: 56px; }

/* ---- Map-view controls: ROUND buttons (fat-finger UX). Top-of-screen margins
   stay at their defaults; map view only — other views are untouched. ---- */
/* Zoom + locate: round buttons in a top-right column, not the boxed bar. */
body.map-page .leaflet-bar { border: none; box-shadow: none; background: transparent; }
body.map-page .leaflet-bar a {
  width: 44px; height: 44px; line-height: 44px; border-radius: 50%;
  background: var(--surface-2); color: var(--text-primary);
  border: 0.5px solid var(--border); box-shadow: 0 2px 8px rgba(0,0,0,0.42);
  margin: 0 0 9px 0; font-size: 20px; text-align: center;
}
/* Override Leaflet's squared first/last-child corners so the zoom pair reads as
   two full circles (higher specificity than .leaflet-bar a:first-child). */
body.map-page .leaflet-bar a:first-child,
body.map-page .leaflet-bar a:last-child { border-radius: 50%; }
body.map-page .leaflet-bar a:hover { background: var(--surface-3, var(--surface-2)); }
body.map-page .map-locate a { font-size: 20px; }
/* Layers: an icon-only round button (matches zoom/locate). */
body.map-page .map-layers-toggle {
  width: 44px; height: 44px; border-radius: 50%; padding: 0; justify-content: center;
  font-size: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.42);
}
body.map-page .map-layers-toggle span { display: none; }
body.map-page .map-layers-ctl.is-open .map-layers-toggle { border-radius: 50%; }
body.map-page .map-layers-panel { margin-top: 8px; }
/* Bottom FABs set further in from the edges (2-3x) — the qc-fab offsets live with
   its own rule below; the center button here. */
body.map-page .shell-center { bottom: 28px; }
.main.map-full {
  padding: 0;
  position: relative;
  height: calc(100vh - 48px);   /* 48px = the top bar */
  overflow: hidden;
}
.main.map-full #map {
  position: absolute;
  inset: 0;
  height: 100%;
  width: 100%;
  border: none;
  border-radius: 0;
  background: var(--surface-1);
}
#map.placing { cursor: crosshair; }
/* The theme wash over the map tiles. z-index 399 sits ABOVE the leaflet tile pane
   (200) and BELOW the overlay/marker panes (400/600), so it colours the ground but
   never the pins, routes or popups. #map sets no z-index, so its panes share this
   stacking context and interleave with this sibling. Off unless a theme is worn. */
.map-theme-tint {
  position: absolute; inset: 0; z-index: 399; pointer-events: none;
  background: var(--border-accent); opacity: 0; mix-blend-mode: overlay;
  transition: opacity 0.35s ease;
}
html[data-theme] .map-theme-tint { opacity: 0.34; }

/* floating title + description + Add pin, top-left */
.map-chrome {
  position: absolute;
  top: 12px; left: 12px;
  z-index: 900;  /* above Leaflet's markers (600) and popups (700); below modals (1000) */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  max-width: min(340px, calc(100% - 24px));
}
.map-chrome .map-page-header {
  display: block;
  margin: 0;
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-md);
  padding: 8px 14px;
  box-shadow: 0 2px 12px rgba(0,0,0,0.4);
}
.map-chrome .page-desc { margin: 3px 0 0; font-size: 12px; color: var(--text-secondary); }
.map-addpin-btn { box-shadow: 0 2px 12px rgba(0,0,0,0.4); }
/* On a phone the full-width chrome reached across and hid the top-right Layers
   button. Cap it so it always leaves room for that control; the title and
   description simply wrap inside the narrower box. */
@media (max-width: 560px) {
  .map-chrome { max-width: calc(100% - 112px); }
}

/* the details, MOVED onto a floating panel — same content, new home. It blocks
   the map on the left only while something is selected. */
.map-detail-float {
  position: absolute;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  z-index: 910;
  width: min(380px, calc(100% - 48px));
  max-height: min(82vh, 660px);
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: 0 10px 40px rgba(0,0,0,0.6);
  overflow-y: auto;
  padding: 16px;
}
.map-detail-float.hidden { display: none; }
.map-detail-close {
  position: absolute; top: 10px; right: 10px; z-index: 1;
  background: var(--surface-2); border: 0.5px solid var(--border); border-radius: 50%;
  width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
  color: var(--text-secondary); cursor: pointer; font-size: 15px;
}
.map-detail-close:hover { color: var(--text-primary); }
.map-detail { min-height: 0; }

/* the map-based adventure site picker banner */
.map-pick-banner {
  position: absolute;
  top: 12px; left: 50%;
  transform: translateX(-50%);
  z-index: 920;
  display: flex; align-items: center; gap: 12px;
  max-width: min(560px, calc(100% - 24px));
  background: var(--surface-1);
  border: 0.5px solid var(--border-accent);
  border-radius: var(--radius-md);
  padding: 8px 14px;
  box-shadow: 0 4px 18px rgba(0,0,0,0.5);
  font-size: 12px;
}
.map-pick-text { flex: 1; min-width: 0; }
.map-pick-count { color: var(--text-secondary); flex-shrink: 0; white-space: nowrap; }

.layer-list { display: flex; flex-direction: column; gap: 1px; }
.layer-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 8px;
  border-radius: var(--radius);
  font-size: 12px;
  color: var(--text-secondary);
  cursor: pointer;
}
.layer-row:hover { background: var(--surface-2); }
.layer-row input[type="checkbox"] { width: 13px; height: 13px; padding: 0; accent-color: var(--border-accent); flex-shrink: 0; }
.layer-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.layer-count { font-size: 11px; color: var(--text-muted); flex-shrink: 0; }
.layer-row-journal { margin-top: 6px; padding-top: 8px; border-top: 0.5px solid var(--border); border-radius: 0; }
.layer-journal-dot { display: inline-flex; width: 8px; justify-content: center; color: var(--text-muted); }
.layer-journal-dot i { font-size: 12px; }

.map-hint {
  position: absolute;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 905;  /* above Leaflet markers, in the map-overlay band */
  background: var(--surface-2);
  border: 0.5px solid var(--border-accent);
  border-radius: var(--radius-pill);
  padding: 6px 14px;
  font-size: 12px;
  display: flex;
  align-items: center;
  gap: 6px;
  box-shadow: 0 4px 14px rgba(0,0,0,0.5);
}
.map-hint button { padding: 0 2px; font-size: 12px; color: var(--text-accent); }
.map-hint-loc { color: var(--text-muted); }

.map-icon-row { display: flex; align-items: center; gap: 8px; }
.map-coords { font-family: monospace; font-size: 12px; }
/* How the coordinate was obtained and how wrong it might be — quiet, but it
   sits with the number rather than somewhere else, because it's part of it. */
.map-coord-note { font-size: 11px; color: var(--text-muted); margin-top: 3px; }
.map-coord-row { display: flex; gap: 6px; }
.map-coord-row input { width: 50%; }

/* Marker glyphs. Declared after the category color block on purpose: these
   need a solid backdrop so they stay legible on both the dark and the standard
   basemap, which overrides c-*'s translucent background. */
.map-pin-wrap { background: none; border: none; }
.map-pin {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  background: var(--surface-0);
  border: 1.5px solid currentColor;
  border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg);
  box-shadow: 0 2px 6px rgba(0,0,0,0.55);
}
.map-pin i, .map-pin img { transform: rotate(45deg); }
.map-pin.selected { outline: 2px solid var(--border-accent); outline-offset: 2px; }
.map-journal-pin {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  background: var(--surface-0);
  color: var(--text-secondary);
  border: 1.5px dashed var(--border-strong);
  border-radius: var(--radius);
  box-shadow: 0 2px 6px rgba(0,0,0,0.55);
}
.map-journal-pin i { font-size: 13px; }
.map-journal-pin.selected { outline: 2px solid var(--border-accent); outline-offset: 2px; }
.map-obs-pin {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  background: var(--surface-0);
  color: var(--text-success);
  border: 1.5px dashed var(--text-success);
  border-radius: 50%;
  box-shadow: 0 2px 6px rgba(0,0,0,0.55);
}
.map-obs-pin i { font-size: 12px; }
.map-obs-pin.selected { outline: 2px solid var(--border-accent); outline-offset: 2px; }

.map-obs-list { display: flex; flex-direction: column; gap: 4px; }
.map-obs-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px 8px;
  border-radius: var(--radius);
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  text-decoration: none;
}
.map-obs-row:hover { border-color: var(--border-strong); }
.map-obs-row-head { display: flex; justify-content: space-between; gap: 8px; align-items: baseline; }
.map-obs-row-name { font-size: 12px; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.map-obs-row-date { font-size: 10px; color: var(--text-muted); flex-shrink: 0; }
.map-obs-row-note { font-size: 11px; color: var(--text-secondary); line-height: 1.5; }

/* observation location line, shown on both sides of the mirror */
.obs-loc { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--text-muted); }
.obs-loc i { font-size: 12px; }
.obs-loc a { text-decoration: none; }
.obs-loc a:hover { text-decoration: underline; }
.obs-loc-coords { font-family: monospace; }
.obs-loc-editor .journal-loc-row { gap: 6px; }

/* Leaflet's own chrome, restyled to match the app instead of its white default */
.leaflet-container { background: var(--surface-1); font-family: var(--font-sans); }
.leaflet-bar a, .leaflet-bar a:hover {
  background: var(--surface-2);
  color: var(--text-primary);
  border-bottom-color: var(--border);
}
.leaflet-bar a:hover { background: var(--surface-3); }
.leaflet-bar { border: 0.5px solid var(--border); }
.leaflet-control-layers {
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-primary);
}
.leaflet-control-layers-toggle { filter: invert(0.85); }
.leaflet-control-layers-expanded { padding: 8px 10px; font-size: 12px; }

/* "Center on my location" control — a focus button in the zoom stack. Inherits
   .leaflet-bar a chrome above; this just centres the icon and shows the busy
   state while a fix is in flight. */
.map-locate a { display: flex; align-items: center; justify-content: center; font-size: 15px; }
/* The auto-follow toggle glows accent while it's actively following. */
.map-follow.is-active { color: #fff; background: var(--border-accent) !important; }
.map-follow.is-active:hover { background: var(--accent-hover) !important; }
.map-locate a.is-busy { opacity: 0.55; cursor: progress; }
.map-locate a.is-busy .ti { animation: map-locate-spin 0.9s linear infinite; }
@keyframes map-locate-spin { to { transform: rotate(360deg); } }

/* The transient you-are-here marker: a filled dot with a soft ring, plus the
   accuracy circle drawn as a Leaflet vector (styled via its path className). */
.map-you-wrap { background: none; border: none; }
.map-you-dot {
  display: block;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--border-accent);
  border: 2px solid #fff;
  box-shadow: 0 0 0 2px rgba(0,0,0,0.35), 0 1px 4px rgba(0,0,0,0.5);
}
.map-you-ring {
  stroke: var(--border-accent);
  stroke-width: 1;
  stroke-opacity: 0.7;
  fill: var(--border-accent);
  fill-opacity: 0.12;
}
/* The reach circle — how far the player can interact (stop-visit radius). Dashed
   and faint so a 2 km ring reads as a boundary, not a filled blob. */
.map-reach-ring {
  stroke: var(--border-accent);
  stroke-width: 1.5;
  stroke-opacity: 0.6;
  stroke-dasharray: 6 7;
  fill: var(--border-accent);
  fill-opacity: 0.04;
}

/* Quick-capture — the field "log this" button and its launcher (quickcapture.js).
   FAB sits in the persistent-chrome band just below the top bar; on phones it
   drops to the thumb zone (bottom-right), where one-handed field capture happens.
   The launcher is a full-screen modal (band 1000). */
.qc-fab {
  position: fixed;
  top: 64px;
  right: 16px;
  z-index: 900;
  width: 48px;
  height: 48px;
  border-radius: var(--radius-pill);
  background: var(--border-accent);
  color: var(--accent-fg);
  border: none;
  box-shadow: 0 2px 10px rgba(0,0,0,0.4);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
}
.qc-fab:hover { background: var(--accent-hover); }
/* ON THE MAP, the top-right corner belongs to the Layers control (Leaflet's
   top-right), so the capture button sat right on top of it and hid it. There
   it moves to the bottom-right — exactly where it already goes on phones — and
   the "?" help button steps up to clear it. Scoped to the map page so every
   other page keeps the top-right capture button. */
/* The quick-collect FAB sits bottom-right, a touch smaller than the shell's
   center menu button (66px). It absorbed the old "Add marker" job. */
body.map-page .qc-fab { top: auto; bottom: 32px; right: 32px; width: 54px; height: 54px; z-index: 907; }

/* ── THE "ACTIVE THINGS" BUTTON + MODAL ─────────────────────────────────────
   A sibling of the quick-collect FAB (a scroll/quill), mirrored to the opposite
   side: on the map it sits bottom-LEFT, symmetric with quick-collect bottom-right
   around the center menu button. Surface fill + accent icon so it reads as the
   OTHER control, not a second capture button. z-index is above the overlays so it
   stays an X-to-close even while a detail panel is up. */
.act-fab {
  position: fixed; top: 120px; right: 16px; z-index: 1010;
  width: 48px; height: 48px; border-radius: var(--radius-pill);
  background: var(--surface-2); border: 0.5px solid var(--border-strong);
  color: var(--border-accent); font-size: 20px;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  box-shadow: 0 2px 12px rgba(0,0,0,0.42); transition: background 0.12s ease, transform 0.08s ease;
}
.act-fab:hover { background: var(--surface-3); }
.act-fab:active { transform: scale(0.94); }
.act-fab.is-open { color: var(--text-primary); }
html.embedded .act-fab { display: none; }         /* embedded pages inherit the shell's chrome */
@media (max-width: 860px) { .act-fab { top: auto; bottom: 20px; left: 16px; right: auto; width: 52px; height: 52px; } }
body.map-page .act-fab { top: auto; bottom: 32px; left: 32px; right: auto; width: 54px; height: 54px; z-index: 907; }
body.map-page .act-fab.is-open { z-index: 1010; }  /* lift above a summoned detail while open */

/* A popover, not a full modal — transparent scrim (click-away closes), and the
   panel is ANCHORED to the button and grows out of it like a speech bubble. */
.act-overlay { position: fixed; inset: 0; z-index: 1000; background: transparent; }
.act-overlay.hidden { display: none; }
/* Small on purpose — a quick glance, not a page. Compact type, tight rows, scrolls
   inside rather than growing to fill the screen. Anchored bottom-left ABOVE the
   button on the map (the button is its rounded base — the connecting corner is
   squared and overlaps the FAB so the two read as one half-capsule); off-map it
   hangs from the top-right button instead. */
.act-panel {
  position: fixed; top: 190px; right: 16px;        /* off-map: below the top-right FAB, with a gap */
  transform-origin: top right;
  background: var(--surface-1); border: 0.5px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: 0 12px 40px rgba(0,0,0,0.5);
  width: min(360px, calc(100vw - 32px)); max-height: min(64vh, 540px);
  display: flex; flex-direction: column; overflow: hidden;
  animation: act-bubble 0.2s cubic-bezier(0.18, 0.9, 0.32, 1.15);
}
body.map-page .act-panel {
  top: auto; right: auto; bottom: 100px; left: 32px;  /* map: above the bottom-left FAB, with a gap */
  transform-origin: bottom left;
  border-radius: var(--radius-lg);
}
@media (max-width: 860px) {
  body:not(.map-page) .act-panel {
    top: auto; right: auto; bottom: 88px; left: 16px;
    transform-origin: bottom left;
    border-radius: var(--radius-lg);
  }
}
/* Grows out of the button from its anchored corner — the "sphere becomes a
   half-capsule / speech bubble" the user asked for. */
@keyframes act-bubble { from { opacity: 0; transform: scale(0.28); } to { opacity: 1; transform: none; } }
.act-loading { padding: 22px; text-align: center; color: var(--text-secondary); font-size: 12px; }
.act-head { padding: 12px 14px 10px; border-bottom: 0.5px solid var(--border); flex-shrink: 0; }
.act-head-row { display: flex; align-items: center; justify-content: space-between; }
.act-heading { font-size: 13px; font-weight: 600; color: var(--text-primary); display: inline-flex; align-items: center; gap: 6px; }
.act-heading i { color: var(--border-accent); }
.act-close { background: none; border: none; color: var(--text-secondary); font-size: 15px; cursor: pointer; padding: 2px; }
.act-close:hover { color: var(--text-primary); }
.act-class { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.act-class > span { font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); flex-shrink: 0; }
.act-class-sel { flex: 1; font-size: 12px; padding: 5px 8px; }
.act-class-empty { display: inline-block; margin-top: 8px; font-size: 11px; color: var(--border-accent); text-decoration: none; }
.act-class-empty:hover { text-decoration: underline; }

.act-empty { padding: 26px 18px; text-align: center; color: var(--text-secondary); font-size: 12px; line-height: 1.5; }
.act-list { overflow-y: auto; padding: 10px; display: flex; flex-direction: column; gap: 8px; }
.act-card { background: var(--surface-2); border: 0.5px solid var(--border); border-radius: var(--radius-md); overflow: hidden; }
.act-card.is-open { border-color: var(--border-strong); }
.act-card-head { display: flex; align-items: stretch; }
.act-open { flex: 1; min-width: 0; display: flex; align-items: center; gap: 10px; padding: 9px 10px; background: none; border: none; color: var(--text-primary); cursor: pointer; text-align: left; }
.act-open:hover .act-card-title { color: var(--border-accent); text-decoration: underline; }
.act-card-ico { font-size: 20px; color: var(--border-accent); flex-shrink: 0; display: flex; }
.act-card-ico img { width: 20px; height: 20px; }
.act-card-titles { display: flex; flex-direction: column; min-width: 0; }
.act-card-title { font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.act-card-sub { font-size: 10px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.03em; }
.act-toggle { flex-shrink: 0; background: none; border: none; border-left: 0.5px solid var(--border); color: var(--text-secondary); width: 34px; cursor: pointer; }
.act-toggle:hover { color: var(--text-primary); }
.act-card.is-open .act-toggle i { transform: rotate(180deg); }
.act-toggle i { transition: transform 0.14s ease; display: inline-block; }
.act-prog { height: 4px; background: var(--surface-0); margin: 0 10px 9px; border-radius: var(--radius-pill); overflow: hidden; }
.act-prog > span { display: block; height: 100%; background: var(--border-accent); border-radius: inherit; transition: width 0.2s ease; }
.act-body { padding: 2px 12px 12px; font-size: 12px; color: var(--text-secondary); border-top: 0.5px solid var(--border); }
.act-body-empty { font-size: 12px; color: var(--text-muted); margin: 8px 0 0; }
/* The read-only Blocks preview runs small inside the accordion. */
.act-body .ro-block { margin-top: 8px; }
.act-body .blk-steps { margin: 4px 0 0; padding-left: 4px; list-style: none; font-size: 12px; }
.act-adv-parts { display: flex; flex-wrap: wrap; gap: 6px 12px; margin: 8px 0 0; }
.act-adv-part { font-size: 11px; color: var(--text-secondary); }
.act-adv-part b { color: var(--text-primary); }
.act-adv-steps { list-style: none; margin: 8px 0 0; padding: 0; }
.act-adv-step { display: flex; align-items: flex-start; gap: 6px; font-size: 12px; padding: 2px 0; }
.act-adv-step i { color: var(--text-muted); margin-top: 1px; }
.act-adv-step.done { color: var(--text-muted); text-decoration: line-through; }
.act-adv-step.done i { color: var(--text-success, var(--border-accent)); }

/* Help "?" moves up beside the Layers control (top-right, same row). The map
   starts below the 48px top bar, so the Leaflet controls sit ~58px down. */
body.map-page .tut-fab { top: 58px; right: 92px; bottom: auto; z-index: 906; }
/* The + badge on the crystal ball. The button is already `position: fixed`,
   which is itself a containing block for absolute children — no extra
   positioning rule needed, and none added, because a transform or filter here
   would open a stacking context the bought FAB effects paint inside. */
.qc-fab .qc-fab-add {
  position: absolute;
  right: 7px;
  bottom: 7px;
  font-size: 11px;
  line-height: 1;
  border-radius: var(--radius-pill);
  background: var(--accent-fg);
  color: var(--border-accent);
}

.qc-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(0,0,0,0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}
.qc-panel {
  position: relative;
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 22px;
  width: 100%;
  max-width: 520px;
  /* A Wizard ID preview shows a full-width photo, which on a portrait shot can
     run taller than the viewport — let the panel scroll rather than overflow. */
  max-height: calc(100vh - 32px);
  overflow-y: auto;
}
.qc-close {
  position: absolute;
  top: 12px;
  right: 12px;
  background: none;
  border: none;
  color: var(--text-secondary);
  font-size: 15px;
  cursor: pointer;
}
.qc-heading { font-size: 20px; font-weight: 600; margin: 0 0 16px; color: var(--text-primary); }

/* THE LAUNCHER MENU fills its panel and never scrolls: the panel takes a definite
   height (a tall card on desktop, the viewport on a phone) and the two button
   groups flex to share it, so every option is reachable without a scrollbar. Only
   the launcher is treated this way — the forms it opens keep the panel's default
   auto-height + scroll (a portrait Wizard-ID photo still needs to scroll). */
.qc-panel:has(.qc-launcher) { display: flex; flex-direction: column; height: min(680px, calc(100dvh - 24px)); overflow: hidden; }
.qc-panel:has(.qc-launcher) .qc-body { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.qc-launcher { display: flex; flex-direction: column; gap: 12px; flex: 1; min-height: 0; }
.qc-launcher .qc-heading { flex: 0 0 auto; margin: 0; }
/* The three wizard entries and the four quick actions grow to fill the space; the
   per-item margin-bottom is replaced by the flex gap so spacing stays even. The
   big-row is a 2×2 grid whose rows share its flexed height (grid-auto-rows: 1fr)
   so the buttons stretch to fit rather than overflowing it. */
.qc-launcher .qc-wiz-launch { flex: 1 1 auto; min-height: 56px; margin: 0; }
.qc-launcher .qc-tw-launch { margin: 0; }
.qc-launcher .qc-big-row { flex: 2 1 auto; min-height: 0; grid-auto-rows: 1fr; }
.qc-launcher .qc-big { min-height: 0; justify-content: center; padding: 12px 10px; }
.qc-big-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.qc-big-row-4 { grid-template-columns: repeat(2, 1fr); }
.qc-collect-amount { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.qc-big {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 22px 10px;
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  cursor: pointer;
}
.qc-big:hover { background: var(--surface-3); border-color: var(--border-accent); }
.qc-big.is-busy { opacity: 0.6; cursor: progress; }
.qc-big-ico { font-size: 26px; color: var(--border-accent); }
.qc-big-label { font-size: 15px; font-weight: 600; }
.qc-big-sub { font-size: 11px; color: var(--text-secondary); }

.qc-back {
  background: none;
  border: none;
  color: var(--text-secondary);
  font-size: 12px;
  cursor: pointer;
  padding: 0 0 12px;
}
.qc-dest-grid { display: grid; gap: 8px; }
.qc-dest {
  display: flex;
  align-items: center;
  gap: 12px;
  text-align: left;
  padding: 12px 14px;
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  cursor: pointer;
}
.qc-dest:hover { background: var(--surface-3); border-color: var(--border-accent); }
.qc-dest-ico { font-size: 20px; color: var(--border-accent); width: 24px; text-align: center; }
.qc-dest-text { display: flex; flex-direction: column; }
.qc-dest-label { font-size: 13px; font-weight: 600; }
.qc-dest-hint { font-size: 11px; color: var(--text-secondary); }

/* The in-popup quick form */
.qc-form { display: flex; flex-direction: column; gap: 12px; }
.qc-field { display: flex; flex-direction: column; gap: 4px; }
.qc-label { font-size: 11px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.03em; }
.qc-form .qc-type,
.qc-form .qc-name,
.qc-form .qc-noteinput { width: 100%; }
.qc-form-actions { margin-top: 4px; }
.qc-form-actions .qc-save { width: 100%; }

/* ---- Dream up a quest: input, the inventory opt-in, and the refine loop ---- */
/* The textarea here isn't inside a .qc-form, so it needs its own full width —
   without this it fell back to the browser's ~20-col default and read as broken. */
.qc-gen-input { width: 100%; margin-top: 4px; }
/* Nature vs Civilized — the WORLD the quest lives in. A pair of equal buttons,
   the active one accented, so the choice reads at a glance before weaving. */
.qc-gen-world { display: flex; gap: 8px; margin-top: 10px; }
.qc-gen-world-btn { flex: 1; display: flex; flex-direction: column; align-items: center;
  gap: 2px; padding: 10px 8px; font-size: 13px; font-weight: 600; text-align: center;
  background: var(--surface-2); border: 0.5px solid var(--border); border-radius: var(--radius-md);
  color: var(--text-secondary); cursor: pointer; }
.qc-gen-world-btn:hover { border-color: var(--border-accent); }
.qc-gen-world-btn i { font-size: 20px; }
.qc-gen-world-sub { font-size: 11px; font-weight: 400; color: var(--text-muted); }
.qc-gen-world-btn.is-on { color: var(--border-accent); border-color: var(--border-accent);
  background: var(--surface-3); }
.qc-gen-world-btn.is-on .qc-gen-world-sub { color: var(--text-secondary); }

.qc-gen-check { display: flex; align-items: flex-start; gap: 8px; font-size: 12px;
  color: var(--text-secondary); cursor: pointer; padding: 8px 0 2px; }
.qc-gen-check input { margin-top: 1px; flex-shrink: 0; }
.qc-gen-check b { color: var(--text-primary); }
.qc-gen-refine { display: flex; flex-direction: column; gap: 6px; margin-top: 14px;
  padding-top: 12px; border-top: 0.5px solid var(--border); }
.qc-gen-refine-label { font-size: 11px; color: var(--text-secondary);
  text-transform: uppercase; letter-spacing: 0.03em; }
.qc-gen-refine-input { width: 100%; }
.qc-gen-refine-actions { display: flex; gap: 8px; }
.qc-gen-refine-actions .btn-plain { flex: 1; }

/* ---- harvest wizard (source picker + yields) ---- */
.qc-src-list { display: flex; flex-direction: column; gap: 4px; max-height: 340px; overflow-y: auto; margin-top: 6px; }
.qc-src-row { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 8px 10px; background: var(--surface-2); border: 0.5px solid var(--border);
  border-radius: var(--radius-md); color: var(--text-primary); cursor: pointer; }
.qc-src-row:hover { background: var(--surface-3); border-color: var(--border-accent); }
.qc-src-name { flex: 1; font-size: 13px; }
.qc-src-n { font-size: 11px; color: var(--text-muted); }
.qc-yields-form { display: flex; flex-direction: column; gap: 6px; }
.qc-harvest-prov { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; padding-top: 8px; border-top: 0.5px solid var(--border); }
.qc-yrow { display: flex; align-items: center; gap: 8px; padding: 4px 2px; }
.qc-yic { display: inline-flex; width: 20px; justify-content: center; }
.qc-yname { flex: 1; font-size: 13px; }
.qc-yrow .qc-yq, .qc-yrow .qc-ynq { width: 52px; }
.qc-yrow .qc-yu, .qc-yrow .qc-ynu { width: 48px; }
.qc-ynew .qc-ynname { flex: 1; }
.qc-yrm { border: 0; background: none; color: var(--text-muted); cursor: pointer; padding: 2px; }
.qc-yrm:hover { color: var(--danger); }
.qc-yadd { align-self: flex-start; }

.qc-photo-slot:empty { display: none; }
.qc-photo-preview { display: flex; gap: 12px; align-items: flex-start; padding: 8px; background: var(--surface-2); border: 0.5px solid var(--border); border-radius: var(--radius-md); }
.qc-thumb { width: 72px; height: 72px; object-fit: cover; border-radius: var(--radius-sm); flex-shrink: 0; }
.qc-photo-meta { display: flex; flex-direction: column; gap: 8px; }
.qc-attached { font-size: 12px; color: var(--text-success); display: inline-flex; align-items: center; gap: 4px; }
.qc-photo-btns { display: flex; gap: 6px; }

.qc-loc { font-size: 12px; color: var(--text-secondary); display: flex; align-items: center; gap: 5px; min-height: 16px; }
.qc-loc.is-on { color: var(--text-success); }
.qc-loc.is-off { color: var(--text-muted); }
.qc-loc-acc { color: var(--text-muted); }
.qc-loc:empty { display: none; }
.qc-site-toggle { display: flex; align-items: flex-start; gap: 8px; font-size: 12px; color: var(--text-secondary); cursor: pointer; padding: 4px 0; }
.qc-site-toggle input { margin-top: 2px; flex-shrink: 0; }
.qc-site-toggle b { color: var(--text-primary); }

.qc-note { font-size: 12px; color: var(--text-secondary); margin: 8px 0 0; }
.qc-uploading { display: flex; align-items: center; gap: 6px; }
.qc-spin { display: inline-block; animation: map-locate-spin 0.9s linear infinite; }

.qc-success { text-align: center; padding: 8px 0; }
.qc-success-ico { font-size: 26px; color: var(--text-success); display: block; margin-bottom: 4px; }
.qc-success-actions { display: flex; flex-direction: column; gap: 8px; margin-top: 16px; }
.qc-success-actions .btn-plain,
.qc-success-actions .btn-primary { width: 100%; justify-content: center; }

/* Dream up a quest — the LLM quest creator's preview */
.qg-steps { margin: 10px 0; padding-left: 20px; display: flex; flex-direction: column; gap: 5px; font-size: 13px; color: var(--text-primary); }
.qg-steps li { line-height: 1.45; }
.qg-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; }

/* ---- Professions v2: custom-class gradient chip + the mix wizard ---- */
/* A custom class is a MIX of elements. Its chip background is a SOFT blend of the
   component elements' own .c-* tints and its text is the dominant element's pastel
   (both set inline from Professions.present), so it sits at the same value and hue
   as the base-element chips rather than shouting over them. Used on .chip,
   .quest-tag and .tax-swatch; the icon inherits the dominant pastel. */
.prof-mix i { color: inherit; }

.classwiz-rows { display: flex; flex-direction: column; gap: 8px; margin: 10px 0; }
.classwiz-row { display: grid; grid-template-columns: 16px minmax(0, 1fr) 88px 38px 20px; align-items: center; gap: 8px; }
.classwiz-swatch { width: 16px; height: 16px; border-radius: var(--radius-sm); border: 0.5px solid var(--border); }
.classwiz-el, .classwiz-pct, .classwiz-name { width: 100%; }
.classwiz-pctlabel { font-size: 12px; color: var(--text-secondary); text-align: right; }
.classwiz-del { border: 0; background: none; color: var(--text-muted); cursor: pointer; padding: 2px; }
.classwiz-del:hover { color: var(--danger); }
.classwiz-add { margin: 2px 0 6px; }
.classwiz-total { font-size: 12px; color: var(--text-secondary); margin: 6px 0; }
.classwiz-total.over { color: var(--danger); }
.classwiz-preview { display: flex; justify-content: center; margin: 14px 0; }
.classwiz-chip { font-size: 13px; }
.classwiz-namelabel { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--text-secondary); margin-bottom: 4px; }
.classwiz-err { color: var(--danger); font-size: 12px; margin: 8px 0 0; }
.classwiz-err.hidden { display: none; }
.classwiz-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }

/* ---- Wizard ID (the vision field scientist) ---- */
.qc-opt { color: var(--text-muted); font-weight: 400; text-transform: none; letter-spacing: 0; }
.qc-wiz-launch {
  display: flex; align-items: center; gap: 14px; width: 100%; text-align: left;
  padding: 14px 16px; margin-bottom: 14px; cursor: pointer;
  background: linear-gradient(135deg, var(--surface-3), var(--surface-2));
  border: 0.5px solid var(--border-accent); border-radius: var(--radius-md); color: var(--text-primary);
}
.qc-wiz-launch:hover { border-color: var(--accent-hover); background: var(--surface-3); }
.qc-wiz-launch-ico { font-size: 26px; color: var(--border-accent); flex-shrink: 0; }
.qc-wiz-launch-text { display: flex; flex-direction: column; flex: 1; }
.qc-wiz-launch-label { font-size: 15px; font-weight: 600; }
.qc-wiz-launch-sub { font-size: 11px; color: var(--text-secondary); }
.qc-wiz-launch-cam { font-size: 20px; color: var(--text-muted); flex-shrink: 0; }
/* Task Wizard shares the featured-button styling; a different icon tint tells the
   two "wizard" entries apart at a glance (identify vs the record wizard). */
.qc-tw-launch { margin-bottom: 16px; }
.qc-tw-launch .qc-wiz-launch-ico { color: var(--xp, var(--border-accent)); }

.qc-wiz-progress { font-size: 11px; font-weight: 600; color: var(--border-accent); margin: -10px 0 10px; }
.qc-wiz-shot { margin: 4px 0 8px; }
.qc-wiz-shot img { display: block; width: 100%; height: auto; border-radius: var(--radius-md); border: 0.5px solid var(--border); }
.qc-wiz-shot-box { position: relative; }
.qc-wiz-boxes { position: absolute; inset: 0; pointer-events: none; }
.qc-wiz-box { position: absolute; border: 2px dashed rgba(255,255,255,0.5); border-radius: 4px; box-shadow: 0 0 0 1px rgba(0,0,0,0.45); }
.qc-wiz-box.is-on { border-color: var(--border-accent); border-style: dashed; box-shadow: 0 0 0 2px rgba(0,0,0,0.55); }
.qc-wiz-box-n {
  position: absolute; top: -10px; left: -2px; min-width: 16px; height: 16px; padding: 0 3px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 700; color: var(--accent-fg); background: var(--border-accent);
  border-radius: var(--radius-pill);
}
.qc-wiz-safety { color: var(--warn-text, var(--text-secondary)); display: flex; align-items: flex-start; gap: 6px; }
.qc-wiz-nothing { text-align: center; font-size: 13px; padding: 8px 0; }
.qc-wiz-actions { display: flex; flex-direction: column; gap: 8px; }
.qc-wiz-actions .btn-primary, .qc-wiz-actions .btn-plain { width: 100%; justify-content: center; }

.qc-wiz-checks { display: flex; flex-direction: column; gap: 6px; margin: 8px 0; }
.qc-wiz-check {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px; cursor: pointer;
  background: var(--surface-2); border: 0.5px solid var(--border); border-radius: var(--radius-md);
}
.qc-wiz-check:hover { border-color: var(--border-accent); }
.qc-wiz-check-n {
  flex-shrink: 0; width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 700; color: var(--accent-fg); background: var(--border-accent); border-radius: var(--radius-pill);
}
.qc-wiz-check-main { display: flex; flex-direction: column; flex: 1; }
.qc-wiz-check-label { font-size: 13px; font-weight: 600; }
.qc-wiz-check-meta { font-size: 11px; color: var(--text-secondary); text-transform: capitalize; }
.qc-wiz-conf { font-size: 10px; padding: 1px 5px; border-radius: var(--radius-pill); }
.qc-wiz-conf.high { color: var(--text-success); background: rgba(120,190,120,0.12); }
.qc-wiz-conf.med { color: var(--warn-text, var(--text-secondary)); background: rgba(210,180,110,0.12); }
.qc-wiz-conf.low { color: var(--text-muted); background: var(--surface-3); }

.qc-wiz-ids { display: flex; flex-direction: column; gap: 6px; margin: 8px 0; }
.qc-wiz-idopt {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px; cursor: pointer;
  background: var(--surface-2); border: 0.5px solid var(--border); border-radius: var(--radius-md);
}
.qc-wiz-idopt:hover { border-color: var(--border-accent); }
.qc-wiz-idmain { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.qc-wiz-idname { font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.qc-wiz-idextra { font-size: 11px; color: var(--text-secondary); font-style: italic; }
.qc-wiz-idnote { font-size: 11px; color: var(--text-muted); margin-top: 2px; }
.qc-wiz-reasoning { font-size: 12px; color: var(--text-secondary); line-height: 1.4; margin: 4px 0 10px; padding: 8px 10px; background: var(--surface-2); border-left: 2px solid var(--border-accent); border-radius: var(--radius-sm); }
.qc-wiz-reasoning-h { color: var(--border-accent); font-weight: 600; }
.qc-wiz-reasoning-h i { font-size: 12px; }
.qc-wiz-idbadge { font-size: 10px; font-style: normal; color: var(--border-accent); border: 0.5px solid var(--border-accent); border-radius: var(--radius-pill); padding: 0 6px; }
.qc-wiz-idname .qc-wiz-conf { margin-left: auto; }
.qc-wiz-idcustom { width: 100%; }

.qc-wiz-foot { display: flex; justify-content: center; gap: 16px; margin-top: 10px; }
.qc-wiz-restart { color: var(--text-muted); }
.qc-wiz-restart:hover { color: var(--text-primary); }

.qc-wiz-assoc { margin: 10px 0; padding: 10px; background: var(--surface-2); border: 0.5px solid var(--border); border-radius: var(--radius-md); }
.qc-wiz-assoc .qc-note:first-child { margin: 0 0 6px; color: var(--border-accent); }
.qc-wiz-assoc .qc-wiz-sight { margin-top: 6px; }
.qc-wiz-dexline { display: flex; align-items: center; gap: 8px; padding: 6px 4px; font-size: 12px; color: var(--text-primary); }
.qc-wiz-dexline > i { color: var(--border-accent); }
.qc-wiz-dexacts { display: flex; gap: 8px; flex-wrap: wrap; margin: 4px 0 2px; }
.qc-wiz-dexhint { margin: 4px 0 0; font-style: italic; }
.qc-wiz-dexdone { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-success); padding: 4px; text-decoration: none; }
/* The whole-job "Add to my collection" button and the box of what it minted. */
.qc-wiz-collect { margin: 8px 0 4px; }
.qc-wiz-collected {
  display: flex; flex-direction: column; gap: 2px; margin: 8px 0;
  padding: 6px 10px; background: var(--surface-2); border-radius: var(--radius-md);
}
.qc-wiz-ownlink { display: flex; align-items: center; gap: 8px; padding: 6px 4px; color: var(--text-primary); font-size: 12px; text-decoration: none; }
.qc-wiz-ownlink:hover { color: var(--border-accent); }
.qc-wiz-ownkind { margin-left: auto; font-size: 10px; color: var(--text-muted); }
.qc-wiz-sci { font-style: italic; margin-top: 0; }

.qc-wiz-kinds { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 8px 0; }
.qc-wiz-kind {
  display: flex; align-items: center; gap: 10px; padding: 12px; text-align: left; cursor: pointer;
  background: var(--surface-2); border: 0.5px solid var(--border); border-radius: var(--radius-md); color: var(--text-primary);
}
.qc-wiz-kind:hover { background: var(--surface-3); border-color: var(--border-accent); }
.qc-wiz-kind i { font-size: 20px; color: var(--border-accent); flex-shrink: 0; }
.qc-wiz-kind-t { display: flex; flex-direction: column; }
.qc-wiz-kind-l { font-size: 13px; font-weight: 600; }
.qc-wiz-kind-s { font-size: 11px; color: var(--text-secondary); }

@media (max-width: 860px) {
  .qc-wiz-kinds { grid-template-columns: 1fr; }
  .qc-fab { top: auto; bottom: 20px; right: 16px; width: 56px; height: 56px; font-size: 26px; }
  .qc-big-row { grid-template-columns: 1fr; }
}
/* Reserve the top-right corner for the FAB so a page-header's right-aligned
   action (Add pin, New quest, the Market balance) never sits under it. Desktop
   only — the FAB drops to the bottom on phones, freeing the corner. */
@media (min-width: 861px) {
  .page-header { padding-right: 64px; }
}

/* Loot specimen photos — the Pokédex image. Reuses the field-note thumbnail
   classes; these just lay out the gallery and space the editor block. */
.loot-modal-photos { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.loot-edit-photos { margin-top: 12px; display: flex; flex-direction: column; gap: 8px; }

/* Camp pin popup — a small photo strip for a glance; opens the lightbox. */
.camp-popup-photos { display: flex; flex-wrap: wrap; gap: 4px; margin: 6px 0; }
.camp-popup-thumb { cursor: pointer; line-height: 0; }
.camp-popup-thumb-img { width: 40px; height: 40px; object-fit: cover; border-radius: var(--radius-sm); display: block; }

/* The layers control (Leaflet's top-right corner) defaults to z-index 1000 —
   ABOVE the detail float (910), so when a wide float opened on a phone the layers
   toggle sat over the float's close button and stole its clicks. Drop just this
   corner into the documented map-overlay band [900,949], below the float; the
   bottom-left zoom/locate corner stays at Leaflet's default so it remains
   reachable OVER an open float. Verified with elementFromPoint on the X. */
.leaflet-top.leaflet-right { z-index: 905; }
/* The on-map layers panel (map.js) — a collapsible, Google-Maps-style control
   that unifies base map, overlays, your marker layers and shared markers. */
.map-layers-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--surface-2);
  color: var(--text-primary);
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  padding: 6px 10px;
  font-size: 12px;
  cursor: pointer;
  box-shadow: 0 1px 4px rgba(0,0,0,0.3);
}
.map-layers-toggle:hover { background: var(--surface-3); }
.map-layers-ctl.is-open .map-layers-toggle { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.map-layers-panel {
  width: 240px;
  max-height: 62vh;
  overflow-y: auto;
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 4px 16px rgba(0,0,0,0.45);
  padding: 10px;
  margin-top: 4px;
}
.mlp-search { display: flex; align-items: center; gap: 6px; padding: 0 0 8px; color: var(--text-muted); }
.mlp-search input { flex: 1; min-width: 0; }
.mlp-section { margin-bottom: 8px; }
.mlp-section:last-child { margin-bottom: 0; }
.mlp-head {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
  padding: 3px 2px;
  margin: 0 0 4px;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-secondary);
}
.mlp-head:hover { color: var(--text-primary); }
.mlp-head .ti { font-size: 12px; }
.mlp-section.collapsed .mlp-body { display: none; }
.mlp-section.collapsed.search-open .mlp-body { display: block; }
/* The section header row: the collapse toggle takes the space, an All/None
   control rides the end (checkbox sections only). */
.mlp-head-row { display: flex; align-items: center; gap: 4px; margin: 0 0 4px; }
.mlp-head-row .mlp-head { width: auto; flex: 1; min-width: 0; margin: 0; }
.mlp-allnone {
  flex-shrink: 0; background: none; border: 0.5px solid var(--border);
  border-radius: var(--radius-pill); cursor: pointer; padding: 1px 8px;
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--text-secondary);
}
.mlp-allnone:hover { color: var(--text-primary); border-color: var(--text-secondary); }
.mlp-allnone:disabled { opacity: 0.4; cursor: default; }
.mlp-row { display: flex; align-items: center; gap: 8px; padding: 4px 2px; font-size: 12px; cursor: pointer; border-radius: var(--radius-sm); }
.mlp-row:hover { background: var(--surface-2); }
.mlp-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mlp-count { font-size: 10px; color: var(--text-muted); }
.mlp-shared-dot { width: 16px; text-align: center; }
.mlp-shared-dot.mlp-friends { color: var(--text-accent); }
.mlp-shared-dot.mlp-live { color: var(--text-success); }

/* live friend presence — a moving, named dot, distinct from static pins */
.map-presence-wrap { background: none; border: none; }
.map-presence { background: transparent !important; display: flex; flex-direction: column; align-items: center; gap: 2px; }
.map-presence-dot { position: relative; width: 22px; height: 22px; border-radius: 50%;
  background: var(--surface-1); color: inherit; display: flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 700; box-shadow: 0 0 0 2px currentColor, 0 2px 6px rgba(0,0,0,0.55); }
.map-presence-dot::before { content: ''; position: absolute; inset: -3px; border-radius: 50%;
  border: 2px solid currentColor; opacity: 0.55; animation: presencePulse 2s ease-out infinite; }
@keyframes presencePulse { 0% { transform: scale(0.7); opacity: 0.6; } 100% { transform: scale(1.6); opacity: 0; } }
.map-presence-name { font-size: 10px; font-weight: 600; color: var(--text-primary);
  background: var(--surface-1); border: 0.5px solid var(--border); border-radius: var(--radius-pill);
  padding: 0 6px; white-space: nowrap; box-shadow: 0 1px 4px rgba(0,0,0,0.4); }
.mlp-shared-tag { font-size: 9px; text-transform: uppercase; letter-spacing: 0.03em; padding: 1px 5px; border-radius: var(--radius-pill); background: var(--surface-3); color: var(--text-secondary); }

/* Public-lands category swatch — a filled square keyed to the polygon colour, so
   the legend and the map agree at a glance. */
.mlp-cat-dot { width: 12px; height: 12px; border-radius: 2px; flex-shrink: 0; opacity: 0.85; }

/* The public-lands popup: the unit name, then its manager in the category colour. */
.map-lands-popup { min-width: 120px; }
.map-lands-popup .map-lands-mgr { display: block; font-size: 11px; margin-top: 2px; }

/* Shared markers are read-only and drawn with a dashed teardrop so they read as
   someone else's, not yours. */
.map-pin-shared { border-style: dashed; }

/* Crafting — the recipe status badge, the craft bar, and the lot-picking wizard. */
.badge-making { background: var(--xp); color: var(--surface-0); }
.recipe-craft-bar { display: flex; align-items: center; gap: 10px; margin-top: 14px; }
.recipe-craft-hint { font-size: 11px; color: var(--text-muted); }
.recipe-gather-bar { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-top: 10px; }

.craft-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(0,0,0,0.55);
  display: none;
  align-items: center;
  justify-content: center;
  padding: 16px;
}
.craft-overlay.open { display: flex; }
.craft-modal {
  position: relative;
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 22px;
  width: 100%;
  max-width: 480px;
  max-height: 86vh;
  overflow-y: auto;
}
.craft-close {
  position: absolute;
  top: 12px;
  right: 12px;
  background: none;
  border: none;
  color: var(--text-secondary);
  font-size: 15px;
  cursor: pointer;
}
.craft-title { font-size: 20px; margin: 0 0 4px; display: flex; align-items: center; gap: 8px; }
.craft-sub { font-size: 12px; color: var(--text-secondary); margin: 0 0 16px; }
.craft-field { display: flex; flex-direction: column; gap: 4px; margin-bottom: 16px; }
.craft-field span { font-size: 11px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.03em; }
.craft-ingredients { display: flex; flex-direction: column; gap: 10px; }
.craft-ingredient {
  border: 0.5px solid var(--border);
  border-left: 2px solid var(--text-muted);
  border-radius: var(--radius-md);
  padding: 10px 12px;
  background: var(--surface-2);
}
.craft-ingredient.ok { border-left-color: var(--text-success); }
.craft-ingredient.short { border-left-color: var(--danger); }
.craft-ing-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 6px; }
.craft-ing-name { font-size: 13px; font-weight: 600; }
.craft-ing-need { font-size: 12px; color: var(--text-secondary); }
.craft-lot { display: flex; align-items: center; gap: 10px; padding: 5px 0; }
.craft-lot-info { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
.craft-lot-name { font-size: 13px; display: flex; align-items: center; gap: 5px; }
.craft-lot-note { font-size: 11px; color: var(--text-secondary); }
.craft-lot-have { font-size: 10px; color: var(--text-muted); }
.craft-lot-qty { width: 64px; text-align: center; }
.craft-no-lots { font-size: 11px; color: var(--text-muted); margin: 4px 0 0; }
.craft-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 18px; }
.craft-done { text-align: center; }
.craft-done-ico { font-size: 26px; color: var(--xp); display: block; margin-bottom: 6px; }
.craft-done .craft-title { justify-content: center; }
.craft-done .craft-actions { justify-content: center; }

/* A product's lineage (its frozen birth record) and an item's history log. */
.lineage-box {
  border: 0.5px solid var(--border);
  border-left: 2px solid var(--xp);
  border-radius: var(--radius-md);
  padding: 10px 12px;
  background: var(--surface-2);
}
.lineage-recipe { font-size: 12px; color: var(--text-secondary); margin: 0 0 8px; }
.lineage-inputs { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.lineage-input { display: flex; justify-content: space-between; align-items: center; gap: 10px; font-size: 13px; }
.lineage-input-name { display: flex; align-items: center; gap: 5px; }
.lineage-input-qty { font-size: 12px; color: var(--text-muted); }
.lineage-edited { font-size: 10px; color: var(--danger); text-transform: none; letter-spacing: 0; margin-left: 6px; }

.item-history { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.item-hist-row { display: flex; gap: 10px; font-size: 12px; }
.item-hist-when { color: var(--text-muted); flex-shrink: 0; min-width: 68px; }
.item-hist-text { color: var(--text-secondary); }

/* "Currently making" — the first account-status surface, on the hover card and
   the profile header. */
.pc-making { font-size: 11px; color: var(--text-secondary); margin: 8px 0 2px; display: flex; align-items: center; gap: 5px; }
.pc-making i { color: var(--xp); }
.pc-making strong { color: var(--text-primary); }
.profile-status { font-size: 12px; color: var(--text-secondary); margin-top: 8px; display: inline-flex; align-items: center; gap: 6px; }
.profile-status i { color: var(--xp); }
.profile-status strong { color: var(--text-primary); }

/* The lineage editor (edit-with-shame) */
.lineage-edit-btn { margin-left: 6px; }
.lineage-edit-field { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; }
.lineage-edit-field span { font-size: 11px; color: var(--text-secondary); }
.lineage-edit-inputs { display: flex; flex-direction: column; gap: 6px; }
.lineage-edit-input { display: flex; gap: 6px; align-items: center; }
.lineage-edit-input .lin-name { flex: 1; min-width: 0; }
.lineage-edit-input .lin-qty { width: 60px; text-align: center; }
.lineage-edit-input .lin-unit { font-size: 11px; color: var(--text-muted); min-width: 24px; }
.lineage-edit-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 12px; }

/* Consolidated lot stacks — "Walnuts ×2, from 2 sources", expandable to lots. */
.lot-stack { padding: 0; overflow: hidden; display: flex; flex-direction: column; }
.lot-stack-head {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
  padding: 12px 14px;
  color: var(--text-primary);
}
.lot-stack-head:hover { background: var(--surface-2); }
.lot-stack-title { flex: 1; min-width: 0; }
.lot-stack-title h3 { margin: 0; font-size: 15px; display: flex; align-items: center; gap: 6px; }
.lot-stack-count { font-size: 12px; color: var(--text-muted); font-weight: 400; }
.lot-stack-sub { font-size: 11px; color: var(--text-secondary); }
.lot-stack-chevron { color: var(--text-muted); font-size: 15px; }
.lot-rows { border-top: 0.5px solid var(--border); display: flex; flex-direction: column; }
.lot-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  background: none;
  border: none;
  border-bottom: 0.5px solid var(--border);
  text-align: left;
  cursor: pointer;
  padding: 8px 14px;
  color: var(--text-primary);
  font-size: 12px;
}
.lot-row:last-child { border-bottom: none; }
.lot-row:hover, .lot-row.selected { background: var(--surface-2); }
.lot-row-qty { font-weight: 600; min-width: 46px; }
.lot-row-where { flex: 1; min-width: 0; color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lot-row .ti { color: var(--text-muted); font-size: 13px; }

/* "Not in the Loot Table yet — add it" offer on an unlinked item. */
.loot-link-offer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 12px;
  margin: 4px 0 12px;
  background: var(--surface-2);
  border: 0.5px dashed var(--border);
  border-radius: var(--radius-md);
  font-size: 12px;
  color: var(--text-secondary);
}
.loot-link-offer i { color: var(--coin); }
.leaflet-control-layers label { font-size: 12px; text-transform: none; color: var(--text-primary); }
.leaflet-control-attribution {
  background: rgba(19,20,25,0.75) !important;
  color: var(--text-muted);
  font-size: 10px;
}
.leaflet-control-attribution a { color: var(--text-secondary); }

/* ---------- export to chat ---------- */
.export-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: 24px;
}
.export-modal {
  background: var(--surface-1);
  border: 0.5px solid var(--border-strong);
  border-radius: 12px;
  padding: 20px;
  width: 680px;
  max-width: 100%;
  max-height: 100%;
  display: flex;
  flex-direction: column;
  gap: 12px;
  box-shadow: 0 12px 40px rgba(0,0,0,0.6);
}
.export-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.export-title { font-size: 15px; font-weight: 500; margin: 0; }
.export-sub { font-size: 12px; color: var(--text-secondary); margin: 4px 0 0; line-height: 1.6; max-width: 54ch; }
.export-close {
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-size: 20px;
  line-height: 1;
  padding: 0 6px;
  flex-shrink: 0;
}
.export-close:hover { color: var(--text-primary); background: transparent; }

.export-label { font-size: 11px; color: var(--text-muted); text-transform: lowercase; }
.export-controls { display: flex; flex-direction: column; gap: 10px; }
.export-format { display: flex; align-items: center; gap: 6px; }
.export-format .tab-btn { font-size: 12px; }

.export-sections { display: flex; flex-direction: column; gap: 1px; }
.export-section-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 8px;
  border-radius: var(--radius);
  font-size: 12px;
  cursor: pointer;
}
.export-section-row:hover { background: var(--surface-2); }
.export-section-row input[type="checkbox"] { width: 13px; height: 13px; padding: 0; accent-color: var(--border-accent); flex-shrink: 0; }
.export-section-name { font-weight: 500; width: 92px; flex-shrink: 0; }
.export-section-summary { flex: 1; color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.export-section-size { color: var(--text-muted); font-size: 11px; flex-shrink: 0; }
.export-note { font-size: 11px; color: var(--text-muted); margin: 0; }

.export-preview-wrap { display: flex; flex-direction: column; gap: 4px; min-height: 0; flex: 1; }
.export-preview-head { display: flex; align-items: baseline; justify-content: space-between; }
.export-size { font-size: 11px; color: var(--text-muted); }
.export-preview {
  width: 100%;
  flex: 1;
  min-height: 180px;
  font-family: monospace;
  font-size: 11px;
  line-height: 1.6;
  resize: none;
  background: var(--surface-0);
}
.export-actions { display: flex; align-items: center; gap: 8px; }
.export-status { font-size: 12px; color: var(--text-success); }

/* ---------- loot table ---------- */

/* The `.c-*` category classes set a background *and* a text colour, which is
   right for a chip but not for a card that needs an accent border, a tinted
   icon well and a coloured bar all from one class. These expose the same nine
   palette colours as custom properties instead, so a card can use them freely.
   Same colours as `.c-*` above — keep the two blocks in step. */
.la-crimson  { --cls-color: #df6d76; --cls-tint: rgba(184,25,38,0.18); }
.la-coral    { --cls-color: #eda791; --cls-tint: rgba(235,74,25,0.18); }
.la-bronze   { --cls-color: #c59163; --cls-tint: rgba(137,83,36,0.18); }
.la-amber    { --cls-color: #eac886; --cls-tint: rgba(223,156,22,0.18); }
.la-sand     { --cls-color: #e6dcc7; --cls-tint: rgba(204,173,102,0.18); }
.la-gray     { --cls-color: #a3a3a3; --cls-tint: rgba(255,255,255,0.09); }
.la-lime     { --cls-color: #dde7a6; --cls-tint: rgba(192,220,56,0.18); }
.la-green    { --cls-color: #9cd98c; --cls-tint: rgba(74,190,45,0.18); }
.la-teal     { --cls-color: #5ed4b7; --cls-tint: rgba(28,156,124,0.18); }
.la-cyan     { --cls-color: #89dcf0; --cls-tint: rgba(13,196,242,0.18); }
.la-blue     { --cls-color: #6a96d7; --cls-tint: rgba(30,86,169,0.18); }
.la-slate    { --cls-color: #cad3dd; --cls-tint: rgba(115,148,186,0.18); }
.la-indigo   { --cls-color: #8d8de8; --cls-tint: rgba(27,27,222,0.18); }
.la-purple   { --cls-color: #caaeea; --cls-tint: rgba(138,64,221,0.18); }
.la-magenta  { --cls-color: #d788dd; --cls-tint: rgba(185,39,196,0.18); }
.la-pink     { --cls-color: #f0a8ca; --cls-tint: rgba(235,51,137,0.18); }

/* Rarity is fixed data from the spreadsheet, not a user-editable category, so
   it gets its own four colours rather than a slot in the shared palette. */
.lr-common    { --rar-color: #8a8a8a; }
.lr-uncommon  { --rar-color: #6fd196; }
.lr-rare      { --rar-color: #8ab6e2; }
.lr-legendary { --rar-color: #e0bd52; }

/* TWO AXES, ONE ROW. The browse rail sits left; search + display + filters sit
   right and wrap under it on a phone. The five dropdowns moved into a popover, so
   the header no longer wraps into a wall of selects. */
.loot-toolbar { display: flex; gap: 10px; flex-wrap: wrap; align-items: center;
  justify-content: space-between; margin-bottom: 16px; }
.loot-browse { flex-wrap: wrap; }
.loot-controls { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.loot-search { width: 220px; }
.loot-display { flex: 0 0 auto; }

.loot-filter-wrap { position: relative; }
.loot-filter-btn { display: inline-flex; align-items: center; gap: 6px; }
.loot-filter-badge { display: inline-flex; align-items: center; justify-content: center;
  min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px;
  background: var(--accent-fg); color: #fff; font-size: 10px; font-weight: 600; }
.loot-filter-pop { position: absolute; right: 0; top: calc(100% + 6px); z-index: 50;
  width: 240px; max-width: calc(100vw - 16px); padding: 12px; display: flex; flex-direction: column; gap: 9px;
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: var(--radius-md); box-shadow: 0 12px 32px rgba(0,0,0,0.4);
  /* Six-plus fields on a short phone must scroll inside the popover, not push the
     Clear button off the bottom out of reach. */
  max-height: 66svh; overflow-y: auto; }
.loot-filter-field { display: flex; flex-direction: column; gap: 3px; }
.loot-filter-field > span { font-size: 11px; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.04em; }
.loot-filter { font-size: 12px; padding: 5px 9px; color: var(--text-secondary); width: 100%; }
.loot-filter-clear { align-self: flex-start; }
.loot-now-toggle {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; padding: 6px 11px; color: var(--text-secondary);
}
.loot-now-toggle.on { color: var(--text-success); border-color: var(--text-success); background: var(--bg-success); }

/* GENERIC FILTERS POPOVER — the Loot Table pattern, factored out so Inventory,
   Quests and Field Notes fold their filter/sort selects into one button instead of
   a wrapping row of dropdowns. The Loot Table keeps its own `.loot-filter-*`
   classes (already shipped); new pages use these. Same look, one component. */
.filters-wrap { position: relative; }
.filters-btn { display: inline-flex; align-items: center; gap: 6px; }
.filters-badge { display: inline-flex; align-items: center; justify-content: center;
  min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px;
  background: var(--accent-fg); color: #fff; font-size: 10px; font-weight: 600; }
.filters-pop { position: absolute; right: 0; top: calc(100% + 6px); z-index: 50;
  width: 240px; max-width: calc(100vw - 16px); padding: 12px; display: flex; flex-direction: column; gap: 9px;
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: var(--radius-md); box-shadow: 0 12px 32px rgba(0,0,0,0.4);
  max-height: 66svh; overflow-y: auto; }
.filters-field { display: flex; flex-direction: column; gap: 3px; }
.filters-field > span { font-size: 11px; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.04em; }
.filters-select { font-size: 12px; padding: 5px 9px; color: var(--text-secondary); width: 100%; }
.filters-clear { align-self: flex-start; }

/* On a phone the toolbar is tall enough that the Filters BUTTON can sit below the
   fold, and a popover anchored to it (top:100%; right:0) then opens off-screen — left
   AND below — where its options can't be reached. Anchor both filter popovers to the
   VIEWPORT as a bottom sheet instead: full width (8px margins), pinned to the bottom
   above the home indicator, capped height with its own scroll. Always on-screen. */
@media (max-width: 860px) {
  .loot-filter-pop, .filters-pop {
    position: fixed; left: 8px; right: 8px; width: auto; max-width: none;
    top: auto; bottom: calc(8px + env(safe-area-inset-bottom, 0px));
    max-height: 72svh;
  }
}

/* Field Notes' inline Sort (its only extra control — no loose filters to fold, so
   it stays visible rather than hiding behind a popover for one dropdown). */
.fn-sort-wrap { display: inline-flex; align-items: center; gap: 6px; }
.fn-sort-label { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
.fn-sort { width: auto; }

/* THE SEGMENTED CONTROL HAS THREE SIZES, AND THAT LIST IS CLOSED.
   Default is the page-level one (Inventory's layouts, the Loot Table's views).
   `.compact` is for a control sitting inside a card. `.micro` is for a control
   inside a PANEL — a set of filters that is navigation for something else on
   screen, not the thing you came to look at.
   The three differ in padding, type size and icon size only; nothing else about
   the component changes, so a switch can be resized without being restyled.
   If a fourth is ever needed, add it here rather than overriding
   `.view-switch-btn` from a page's own block. */
/* THE TRACK HUGS ITS BUTTONS. It is a block-level flex container, so without
   `fit-content` it stretches to the full width of whatever it sits in — which
   went unnoticed because until now the only standalone ones were the Market's
   and the profile's. Measured: the Market's tab track was 1052px wide with
   567px of EMPTY TRACK trailing off to the right. That is the flex default,
   not a decision, and a segmented control with half of it empty reads as a
   container that failed to fill. `max-width: 100%` keeps `flex-wrap` doing its
   job when the buttons genuinely do not fit. */
.view-switch {
  display: flex; gap: 3px; padding: 3px;
  border-radius: var(--radius-md);
  background: var(--surface-1); border: 0.5px solid var(--border);
  flex-wrap: wrap;
  width: fit-content; max-width: 100%;
}
.view-switch-btn {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; padding: 6px 12px;
  background: transparent; border-color: transparent; color: var(--text-secondary);
  white-space: nowrap;
}
.view-switch-btn:hover { background: var(--surface-2); }
.view-switch-btn.active { background: var(--surface-3); color: var(--text-primary); }
.loot-view-forbidden { color: #a86464; }
.loot-view-forbidden.active { background: rgba(201,79,79,0.18); color: #d98b8b; }

.loot-note { font-size: 12px; color: var(--text-muted); margin: 0 0 14px; }
.loot-caveat { font-size: 11px; color: var(--text-muted); margin: 8px 0 0; line-height: 1.45; }

/* shared bits: class tag, rarity, value, season, tracked badge */
.loot-class-tag {
  font-size: 10px; padding: 2px 9px; border-radius: var(--radius-pill); white-space: nowrap;
  background: var(--cls-tint, var(--surface-2)); color: var(--cls-color, var(--text-secondary));
}
.loot-plain-tag { font-size: 10px; padding: 2px 9px; border-radius: var(--radius-pill); background: var(--surface-2); color: var(--text-secondary); white-space: nowrap; }
.loot-season-tag {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 10px; padding: 2px 9px; border-radius: var(--radius-pill);
  background: var(--bg-success); color: var(--text-success); white-space: nowrap;
}
.loot-rarity { display: flex; flex-direction: column; align-items: flex-end; gap: 1px; font-size: 9px; letter-spacing: 0.5px; text-transform: uppercase; color: var(--rar-color, var(--text-muted)); line-height: 1.2; }
.loot-rarity-glyph { font-size: 9px; }
.loot-rarity-text { font-size: 11px; color: var(--rar-color, var(--text-secondary)); white-space: nowrap; }
.loot-est { font-size: 10px; color: var(--text-secondary); white-space: nowrap; font-variant-numeric: tabular-nums; }

.loot-season { display: flex; gap: 2px; }
.loot-season-dot { width: 5px; height: 10px; border-radius: 1px; background: var(--surface-3); }
.loot-season-dot.on { background: var(--cls-color, var(--text-secondary)); }

/* Solid = have it, dashed = on the list but not collected. The same vocabulary
   the inventory tiles use, so it needs no separate explaining. */
.loot-track {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 10px; padding: 1px 7px; border-radius: var(--radius-pill); white-space: nowrap;
}
.loot-track.have { background: var(--bg-success); color: var(--text-success); border: 1px solid rgba(111,209,150,0.45); }
.loot-track.need { background: transparent; color: var(--text-secondary); border: 1px dashed var(--border-strong); }
.loot-track i { font-size: 11px; }

/* GRID */
.loot-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 11px; }
.loot-card {
  position: relative;
  display: block; width: 100%; text-align: left;
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-left: 3px solid var(--cls-color, var(--border));
  border-radius: var(--radius-md);
  padding: 12px;
}
.loot-card:hover { background: var(--surface-2); }
.loot-card.is-need { border-left-style: dashed; }
.loot-card-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.loot-card-icon {
  width: 30px; height: 30px; border-radius: 7px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--cls-tint, var(--surface-2)); color: var(--cls-color, var(--text-secondary));
}
.loot-card h3 { font-size: 13px; font-weight: 600; margin: 0 0 2px; line-height: 1.3; }
.loot-card-loc { font-size: 11px; color: var(--text-muted); margin: 0 0 9px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.loot-card-meta { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.loot-card .loot-track { position: absolute; top: 10px; right: 10px; }
.loot-card:has(.loot-track) .loot-rarity { display: none; }

/* TIERS */
.loot-tier { margin-bottom: 22px; }
.loot-tier-head {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 8px; padding-bottom: 6px;
  border-bottom: 1px solid var(--rar-color, var(--border));
  color: var(--rar-color, var(--text-secondary));
}
.loot-tier-glyph { font-size: 11px; letter-spacing: 2px; }
.loot-tier-name { font-size: 12px; text-transform: uppercase; letter-spacing: 1px; font-weight: 600; }
.loot-tier-count { font-size: 11px; color: var(--text-muted); }
.loot-tier.lr-legendary .loot-row { border-color: rgba(224,189,82,0.35); }
.loot-row {
  display: flex; align-items: center; gap: 11px; width: 100%; text-align: left;
  background: var(--surface-1); border: 0.5px solid var(--border);
  border-radius: 9px; padding: 7px 12px; margin-bottom: 5px;
}
.loot-row:hover { background: var(--surface-2); }
.loot-row.is-need { border-style: dashed; }
.loot-row-icon { width: 20px; flex-shrink: 0; text-align: center; color: var(--cls-color, var(--text-secondary)); }
.loot-row-name { font-size: 13px; font-weight: 500; width: 220px; flex-shrink: 0; }
.loot-row-loc { font-size: 11px; color: var(--text-muted); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.loot-row .loot-est { width: 105px; text-align: right; }

/* TABLE */
.loot-table-wrap { overflow-x: auto; }
.loot-table { width: 100%; border-collapse: collapse; }
.loot-table th {
  text-align: left; font-size: 10px; font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.6px; color: var(--text-muted);
  padding: 8px 9px; border-bottom: 1px solid var(--border-strong);
  white-space: nowrap; cursor: pointer; user-select: none;
}
.loot-table th:hover { color: var(--text-secondary); }
.loot-table th[data-sort]::after { content: " ↕"; opacity: 0.4; }
.loot-table th.sorted { color: var(--text-primary); }
.loot-table th.sorted[data-dir="asc"]::after { content: " ↑"; opacity: 1; }
.loot-table th.sorted[data-dir="desc"]::after { content: " ↓"; opacity: 1; }
.loot-table td { padding: 8px 9px; border-bottom: 0.5px solid var(--border); font-size: 12px; vertical-align: middle; }
.loot-table tbody tr { cursor: pointer; }
.loot-table tbody tr:hover td { background: var(--surface-1); }
.loot-td-name { font-weight: 500; }
.loot-td-loc { font-size: 10px; color: var(--text-muted); }
.loot-td-conf { font-size: 11px; color: var(--text-muted); }
.loot-td-untracked { color: var(--text-muted); }

/* MARKET VALUE */
.loot-mv-row {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  background: transparent; border-color: transparent;
  padding: 6px 10px; border-radius: 8px; margin-bottom: 3px;
}
.loot-mv-row:hover { background: var(--surface-1); }
.loot-mv-rank { font-size: 11px; color: var(--text-muted); width: 28px; text-align: right; flex-shrink: 0; font-variant-numeric: tabular-nums; }
.loot-mv-name { display: flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 500; width: 250px; flex-shrink: 0; }
.loot-mv-name > i { color: var(--cls-color, var(--text-secondary)); }
.loot-mv-track { position: relative; flex: 1; min-width: 60px; height: 15px; background: var(--surface-1); border-radius: 4px; overflow: hidden; }
.loot-mv-bar {
  position: absolute; top: 2px; bottom: 2px; border-radius: 3px;
  background: linear-gradient(90deg, var(--cls-tint, var(--surface-3)), var(--cls-color, var(--text-secondary)));
}
.loot-mv-val { font-size: 11px; color: var(--text-secondary); width: 115px; text-align: right; flex-shrink: 0; font-variant-numeric: tabular-nums; }

/* CLASSES */
.loot-class-section { margin-bottom: 10px; }
.loot-class-head {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 11px 15px; border-radius: 10px;
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-left: 3px solid var(--cls-color, var(--border));
}
.loot-class-head:hover { background: var(--surface-2); }
.loot-class-icon { color: var(--cls-color, var(--text-secondary)); display: flex; }
.loot-class-name { font-size: 15px; font-weight: 600; color: var(--cls-color, var(--text-primary)); flex: 1; }
.loot-class-stats { font-size: 11px; color: var(--text-secondary); text-align: right; }
.loot-class-chev { font-size: 12px; color: var(--text-muted); transition: transform 0.15s; }
.loot-class-section.open .loot-class-chev { transform: rotate(90deg); }
.loot-class-body { display: none; flex-wrap: wrap; gap: 6px; padding: 9px 2px 4px; }
.loot-class-section.open .loot-class-body { display: flex; }
.loot-chip {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12px; padding: 5px 11px; border-radius: 8px;
  background: var(--surface-1); border: 0.5px solid var(--border);
}
.loot-chip:hover { background: var(--surface-2); }
.loot-chip.is-need { border-style: dashed; }
.loot-chip.is-have { border-color: rgba(111,209,150,0.45); }
.loot-chip-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; background: var(--rar-color, var(--text-muted)); }
.loot-chip-val { font-size: 10px; color: var(--text-muted); font-variant-numeric: tabular-nums; }

/* FORBIDDEN + PARKED */
.loot-warn {
  display: flex; gap: 12px; align-items: flex-start;
  background: rgba(201,79,79,0.10); border: 1px solid rgba(201,79,79,0.35);
  border-radius: 10px; padding: 13px 16px; margin-bottom: 16px;
  font-size: 13px; color: #d3a2a2; line-height: 1.5;
}
.loot-warn > i { font-size: 20px; color: #c94f4f; flex-shrink: 0; margin-top: 1px; }
.loot-warn b { color: #e8c0c0; }
.loot-forbidden-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 11px; }
.loot-forbidden-card {
  position: relative; overflow: hidden;
  background: linear-gradient(160deg, #241b1b, #1e1717);
  border: 1px solid rgba(201,79,79,0.4); border-radius: 10px; padding: 13px;
}
.loot-forbidden-card::after {
  content: "\00d7"; position: absolute; right: -4px; bottom: -30px;
  font-size: 68px; font-weight: 700; color: rgba(201,79,79,0.08); pointer-events: none;
}
.loot-forbidden-card h3 { font-size: 13px; font-weight: 600; margin: 0 0 3px; color: #e8c0c0; }
.loot-forbidden-region { font-size: 11px; color: var(--text-muted); margin: 0 0 7px; }
.loot-forbidden-why { font-size: 12px; color: #c98a8a; margin: 0; line-height: 1.45; }
.loot-parked-card { background: var(--surface-1); border: 1px dashed var(--border-strong); border-radius: 10px; padding: 12px; opacity: 0.8; }
.loot-parked-card h3 { font-size: 13px; margin: 0 0 3px; }
.loot-parked-card p { font-size: 12px; color: var(--text-muted); margin: 0; }

/* SPECIMEN MODAL */
.loot-modal-overlay {
  position: fixed; inset: 0; z-index: 950;
  background: rgba(0,0,0,0.6);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.loot-modal {
  position: relative;
  width: 430px; max-width: 100%; max-height: 88vh; overflow-y: auto;
  background: var(--surface-1);
  border: 0.5px solid var(--border-strong);
  border-top: 3px solid var(--cls-color, var(--border-strong));
  border-radius: 14px; padding: 20px;
}
.loot-modal-close {
  position: absolute; top: 13px; right: 13px;
  width: 26px; height: 26px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 7px; color: var(--text-secondary);
}
.loot-modal-head { display: flex; align-items: center; gap: 12px; margin-bottom: 11px; padding-right: 30px; }
.loot-modal-icon {
  width: 40px; height: 40px; border-radius: 9px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--cls-tint, var(--surface-2)); color: var(--cls-color, var(--text-secondary));
}
.loot-modal-head h2 { font-size: 15px; font-weight: 600; margin: 0; line-height: 1.25; }
.loot-modal-tags { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-bottom: 13px; }
.loot-modal-block { margin-bottom: 14px; }
.loot-modal-block .section-label { margin-bottom: 4px; }
.loot-gate {
  display: flex; gap: 8px; align-items: flex-start;
  background: rgba(224,180,79,0.08); border: 1px solid rgba(224,180,79,0.25);
  border-radius: 8px; padding: 8px 11px; margin: 0 0 13px;
  font-size: 12px; color: #dbc08a; line-height: 1.4;
}
.loot-gate > i { flex-shrink: 0; margin-top: 2px; }
.loot-gate.open { background: var(--bg-success); border-color: rgba(111,209,150,0.3); color: var(--text-success); }

.loot-origin {
  display: flex; gap: 8px; align-items: flex-start;
  font-size: 11px; line-height: 1.45; margin: 0 0 12px;
  padding: 7px 11px; border-radius: 8px;
}
.loot-origin > i { flex-shrink: 0; margin-top: 2px; }
.loot-origin.manual { background: rgba(224,180,79,0.08); border: 1px solid rgba(224,180,79,0.22); color: #dbc08a; }
.loot-origin.edited { background: rgba(90,155,205,0.10); border: 1px solid rgba(90,155,205,0.25); color: #8ab6e2; }
.loot-origin b { color: inherit; }

.loot-modal-head .edit-toggle { margin-left: auto; margin-right: 26px; flex-shrink: 0; }
.edit-done-row .spacer { flex: 1; }
.loot-modal-season { display: flex; gap: 3px; }
.loot-modal-month { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px; }
.loot-modal-month .bar { width: 100%; height: 15px; border-radius: 2px; background: var(--surface-3); }
.loot-modal-month.on .bar { background: var(--cls-color, var(--text-secondary)); }
.loot-modal-month .lbl { font-size: 9px; color: var(--text-muted); }
.loot-modal-month.now .lbl { color: var(--text-primary); font-weight: 700; }
.loot-season-text { font-size: 11px; color: var(--text-muted); margin: 6px 0 0; }
.loot-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.loot-stat { background: var(--surface-2); border-radius: 8px; padding: 7px 11px; }
.loot-stat .sl { display: block; font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted); }
.loot-stat .sv { display: block; font-size: 13px; margin-top: 1px; font-variant-numeric: tabular-nums; }

.loot-commands { border-top: 0.5px solid var(--border); padding-top: 14px; margin-bottom: 0; }
.loot-command-row { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.loot-command-label {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--text-secondary); width: 108px; flex-shrink: 0;
}
.loot-command-state { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 5px; margin-right: auto; }
/* Size now comes from `.btn-sm` on the controls themselves, and the anchor
   baseline this block used to restate is on `a.btn-plain` app-wide — see the
   button scale near the top of the file. Nothing page-local survives here. */
.loot-link-pill {
  font-size: 11px; padding: 2px 9px; border-radius: var(--radius-pill);
  background: var(--surface-2); color: var(--text-accent); text-decoration: none;
  max-width: 170px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.loot-link-pill:hover { background: var(--surface-3); }
/* Some pills are buttons (same-page navigation to another specimen) — reset the
   button chrome so they read identically to the anchor pills. */
button.loot-link-pill { border: 0; cursor: pointer; font-family: inherit; line-height: 1.5; }
.loot-link-pill.is-flat { color: var(--text-secondary); cursor: default; }
.loot-yieldof-list { display: flex; flex-wrap: wrap; gap: 6px; }
.dex-yield-chip.loot-yieldlink { border: 1px solid var(--accent-fg); cursor: pointer;
  color: var(--text-accent); font-family: inherit; }
.dex-harvest-links { display: block; margin-top: 4px; }
.loot-cmd-status { font-size: 12px; margin: 10px 0 0; line-height: 1.45; }
.loot-cmd-status.good { color: var(--text-success); }
.loot-cmd-status.note { color: #dbc08a; }
.loot-cmd-status.bad { color: #d98b8b; }

/* ---------- shared record cards + list ---------- */
/*
   Inventory and Field Notes both browse a flat list of records, so their Grid
   and List layouts are one component parameterised by content rather than two
   near-identical copies. The Loot Table keeps its own `.loot-*` grid because it
   carries genuinely different furniture (rarity marks, season strips, market
   bars) — this is the plainer pair.

   Both pages keep their original icon tiles as a third layout; these are
   additions, not replacements.
*/

/* `align-items: start` so a card with a photo doesn't stretch its neighbours
   to match — mixed heights read better than a row of half-empty cards. */
.record-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 11px; align-items: start; }

/* ---- Containers (Theme 7): the "By container" grouped view ---- */
.group-toggle.active { background: var(--surface-2); border-color: var(--border-strong); color: var(--text-primary); }
.group-toggle.active i { color: var(--border-accent); }
/* When grouping, #cardGrid stops being a grid and stacks the panels. */
.record-grid.is-grouped { display: block; }
.ctr-groups { display: flex; flex-direction: column; gap: 14px; }
.ctr-panel { border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface-1); overflow: hidden; }
.ctr-panel-head {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; border-bottom: 1px solid var(--border); background: var(--surface-2);
}
.ctr-panel-title { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; font-weight: 600; }
.ctr-panel-title i { font-size: 15px; }
/* The container's colour carries the whole panel: its border and header background. */
.ctr-panel.ctr-colored { border-color: var(--cls-color); }
.ctr-panel.ctr-colored .ctr-panel-head { background: var(--cls-tint); border-bottom-color: var(--cls-color); }
.ctr-panel.ctr-colored .ctr-panel-title { color: var(--cls-color); }
.ctr-panel-head.ctr-unsorted .ctr-panel-title { color: var(--text-secondary); }
.ctr-panel-loc { display: inline-flex; align-items: center; gap: 3px; font-size: 11px; color: var(--text-muted); }
.ctr-panel-loc i { font-size: 12px; }
.ctr-panel-count {
  margin-left: auto; min-width: 20px; text-align: center; font-size: 11px; font-weight: 600;
  color: var(--text-secondary); background: var(--surface-1); border: 0.5px solid var(--border);
  border-radius: 999px; padding: 1px 7px;
}
.ctr-panel-head .icon-btn { margin-left: 4px; }
.ctr-panel-head.ctr-unsorted .ctr-panel-count { margin-left: auto; }
.ctr-panel-grid, .ctr-panel-tiles { padding: 10px; }
/* A container panel rendered in list layout: its own full-width table. */
.ctr-panel-list { width: 100%; border-collapse: collapse; }
.ctr-panel-list th { position: static; }
.ctr-add-row { margin-top: 12px; }
.ctr-empty { display: flex; flex-direction: column; gap: 12px; align-items: flex-start; }

/* The container chip on cards and in the detail read view. */
.record-container {
  display: inline-flex; align-items: center; gap: 4px; font-size: 11px;
  color: var(--text-secondary); max-width: 100%;
}
.record-container i { font-size: 12px; }
.record-container.unsorted { color: var(--text-muted); }

/* A tile shows its container as a coloured foot-bar (clipped to the tile radius). */
.tile.has-ctr { box-shadow: inset 0 -4px 0 var(--cls-color); }

/* ---- Tool maintenance ---- */
.maint-state { font-weight: 600; }
.maint-state.over { color: var(--danger); }
.maint-state.due { color: #dbc08a; }
.maint-state.fresh { color: var(--text-secondary); }
.maint-actions { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.maint-interval-input { max-width: 90px; }
.maint-every { align-self: center; }
.maint-log { margin-top: 8px; display: flex; flex-direction: column; gap: 3px; }
.maint-log-row { font-size: 11px; color: var(--text-muted); display: flex; align-items: center; gap: 5px; }
.maint-log-row i { font-size: 12px; color: var(--text-secondary); }

/* Product role chip: final product vs component. */
.record-prodclass { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--text-secondary); }
.record-prodclass i { font-size: 12px; }
.record-prodclass.pc-final { color: #7fc9a0; }
.record-prodclass.pc-component { color: #b7a7e0; }

/* The floating container editor (rename/recolor/relocate/delete). */
.ctr-manager-scrim {
  position: fixed; inset: 0; z-index: 1060; display: flex; align-items: center; justify-content: center;
  padding: 20px; background: rgba(0,0,0,0.5);
}
.ctr-manager {
  width: 100%; max-width: 360px; background: var(--surface-1);
  border: 0.5px solid var(--border-strong); border-radius: var(--radius-md); padding: 16px;
  max-height: 90vh; overflow-y: auto;
}
.ctr-manager-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.ctr-manager-head h3 { font-size: 15px; margin: 0; }
.ctr-manager .color-swatches { display: flex; flex-wrap: wrap; gap: 6px; }
.ctr-manager .color-swatch { width: 22px; height: 22px; border-radius: 6px; }
.ctr-manager-actions { display: flex; align-items: center; justify-content: space-between; margin-top: 14px; }

/* Drag a card onto a panel to file it there. */
.ctr-panel [draggable="true"] { cursor: grab; }
.ctr-panel [draggable="true"].dragging { opacity: 0.4; }
.ctr-panel.drag-over { outline: 2px dashed var(--border-accent); outline-offset: -2px; }
.ctr-panel.drag-over .ctr-panel-head { background: var(--surface-1); }
.ctr-add-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.ctr-tidy-btn i { font-size: 15px; }

/* The Inventory Wizard — file loose items one at a time. */
.tidy-wizard .tidy-progress { font-size: 11px; color: var(--text-muted); text-align: center; margin-bottom: 10px; }
.tidy-item { display: flex; align-items: center; gap: 10px; padding: 12px; margin-bottom: 12px;
  background: var(--surface-2); border: 0.5px solid var(--border); border-radius: var(--radius-md); }
.tidy-item-icon { font-size: 20px; width: 34px; height: 34px; display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-1); border-radius: 8px; }
.tidy-item-name { font-size: 15px; font-weight: 600; display: flex; flex-direction: column; gap: 2px; }
.tidy-item-name .field-hint { font-weight: 400; }
.tidy-ctr-list { display: flex; flex-direction: column; gap: 6px; max-height: 46vh; overflow-y: auto; }
.tidy-ctr-btn {
  display: flex; align-items: center; gap: 8px; text-align: left; width: 100%;
  padding: 9px 11px; font-size: 13px; background: var(--surface-2);
  border: 0.5px solid var(--border); border-radius: var(--radius-md); color: var(--text-primary); cursor: pointer;
}
.tidy-ctr-btn:hover { border-color: var(--border-strong); background: var(--surface-1); }
.tidy-ctr-btn i { font-size: 15px; }
.tidy-ctr-btn .field-hint { margin-left: auto; }
.tidy-actions { display: flex; align-items: center; justify-content: space-between; margin-top: 14px; }
.tidy-done { text-align: center; }
.tidy-done-icon { font-size: 26px; color: var(--border-accent); margin-bottom: 6px; }
.tidy-done h3 { font-size: 20px; margin: 0 0 4px; }

.record-card {
  position: relative;
  display: flex; flex-direction: column; gap: 2px;
  width: 100%; text-align: left;
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-left: 3px solid var(--cls-color, var(--border-strong));
  border-radius: var(--radius-md);
  padding: 11px 12px;
}
.record-card:hover { background: var(--surface-2); }
/* Dashed = wanted but not held. Same vocabulary as the inventory tiles and the
   loot rows, so it needs no separate explaining. */
.record-card.is-need { border-left-style: dashed; }
.record-card.selected { border-color: var(--border-accent); border-left-color: var(--cls-color, var(--border-accent)); background: var(--surface-2); }

.record-card-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; margin-bottom: 7px; }
.record-icon {
  width: 30px; height: 30px; border-radius: 7px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--cls-tint, var(--surface-2)); color: var(--cls-color, var(--text-secondary));
}
.record-card h3 { font-size: 13px; font-weight: 600; margin: 0; line-height: 1.3; }
.record-sub {
  font-size: 11px; color: var(--text-muted); margin: 1px 0 0; line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.record-sub.sci { font-style: italic; }
.record-sub.empty { opacity: 0.6; }
.record-meta { display: flex; align-items: center; justify-content: space-between; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
/* was .record-chip — now `.chip .chip-sm`, which carries all of it. */
.record-count { font-size: 10px; color: var(--text-muted); white-space: nowrap; font-variant-numeric: tabular-nums; }

/* ---------- the quantity, and the state beside it ----------
   These were ONE control: a pill reading "✓ 6 lb" for a held item and the word
   "needed" for a wanted one. Two problems fell out of that. The quantity — the
   single most-scanned number on the page — was set at 10px inside a chip, and a
   wanted item showed no quantity AT ALL, because the pill was busy saying
   "needed" instead. So how much you want of something you don't have was simply
   not on the card.

   Split in two: the state is a badge that says only whether you hold it, and the
   figures line carries the number at a size worth reading. Every item now shows
   its quantity, held or not. */
.record-state {
  width: 22px; height: 22px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0; font-size: 12px;
}
.record-state.have { background: var(--bg-success); color: var(--text-success); border: 1px solid rgba(111,209,150,0.45); }
.record-state.need { background: transparent; color: var(--text-muted); border: 1px dashed var(--border-strong); }

.record-figures {
  display: flex; align-items: baseline; gap: 10px;
  margin-top: 9px; flex-wrap: wrap;
}
.record-qty {
  display: inline-flex; align-items: baseline; gap: 3px;
  color: var(--text-primary);
  /* Tabular figures so a column of cards has its numbers on a common rhythm
     rather than jittering with the digit widths. */
  font-variant-numeric: tabular-nums;
}
.record-qty b { font-size: 15px; font-weight: 600; line-height: 1; }
.record-qty span { font-size: 10px; color: var(--text-muted); }
/* A wanted item's number is a target rather than a holding, so it reads quieter
   — the badge above already says which it is, and this keeps a shelf of things
   you don't have from looking like a shelf of things you do. */
.record-card.is-need .record-qty b { color: var(--text-secondary); font-weight: 500; }

/* "Wanted by" — the recipes and open quests that reference this item. Rows are
   links, because the whole point is that the old chip named no destination. */
.wanted-row {
  display: flex; align-items: center; gap: 7px;
  padding: 6px 0;
  border-bottom: 0.5px solid var(--border);
  font-size: 12px; color: var(--text-secondary);
  text-decoration: none;
}
.wanted-row:last-of-type { border-bottom: none; }
.wanted-row:hover { color: var(--text-primary); }
.wanted-row > i { font-size: 13px; color: var(--text-muted); flex-shrink: 0; }
.wanted-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wanted-need { font-size: 11px; color: var(--text-muted); font-variant-numeric: tabular-nums; flex-shrink: 0; }
.wanted-flag {
  font-size: 10px; padding: 1px 6px; border-radius: var(--radius-pill); flex-shrink: 0;
  background: rgba(224,180,79,0.14); color: #dbc08a;
}

/* One small flag, only when there's something to act on. Silence is the
   default state — a card with nothing to say should say nothing. */
.record-flag {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 10px; padding: 1px 7px; border-radius: var(--radius-pill); white-space: nowrap;
  background: rgba(224,180,79,0.14); color: #dbc08a;
}
.record-flag i { font-size: 11px; }

/* How the item was obtained — gathered / bought / crafted. Muted and icon-led:
   it's a provenance fact rather than a category, so it never borrows a category
   colour. Shared by the grid card's meta row and the list's Source column. */
.record-source {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 10px; color: var(--text-muted); white-space: nowrap;
}
.record-source i { font-size: 11px; }

/* Freshness — drawn only when a HELD perishable is going off, so it stays a
   signal and not a label on every jar. `due` is the amber "use soon" (the same
   tone as record-flag); `over` escalates to danger. It rides the figures line
   because it's about the stock's condition, not what wants it. */
.record-fresh {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 10px; padding: 1px 7px; border-radius: var(--radius-pill); white-space: nowrap;
}
.record-fresh i { font-size: 11px; }
.record-fresh.due { background: rgba(224,180,79,0.14); color: #dbc08a; }
.record-fresh.over { background: rgba(217,139,139,0.16); color: var(--danger); }
/* The same freshness signal, inline in the list's Qty cell (no room for a pill). */
.record-list-fresh { font-size: 12px; vertical-align: middle; }
.record-list-fresh.due { color: #dbc08a; }
.record-list-fresh.over { color: var(--danger); }

/* photo-led variant, for Field Notes */
.record-card.has-photo { padding: 0; overflow: hidden; }
.record-photo { width: 100%; aspect-ratio: 4 / 3; background: var(--surface-2); display: flex; align-items: center; justify-content: center; }
.record-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.record-photo > i { font-size: 26px; color: var(--cls-color, var(--text-muted)); }
/* An entry with no photo yet gets a short tinted band rather than a 4:3 void —
   most entries start photoless, so this is the common case, not the exception. */
.record-photo.no-photo { aspect-ratio: 5 / 2; background: var(--cls-tint, var(--surface-2)); }
.record-card.has-photo .record-body { padding: 10px 12px 12px; }

/* LIST */
.record-list-wrap { overflow-x: auto; }
.record-list { width: 100%; border-collapse: collapse; }
.record-list th {
  text-align: left; font-size: 10px; font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.6px; color: var(--text-muted);
  padding: 8px 9px; border-bottom: 1px solid var(--border-strong);
  white-space: nowrap; user-select: none;
}
.record-list th[data-sort] { cursor: pointer; }
.record-list th[data-sort]:hover { color: var(--text-secondary); }
.record-list th[data-sort]::after { content: " ↕"; opacity: 0.4; }
.record-list th.sorted { color: var(--text-primary); }
.record-list th.sorted[data-dir="asc"]::after { content: " ↑"; opacity: 1; }
.record-list th.sorted[data-dir="desc"]::after { content: " ↓"; opacity: 1; }
.record-list td { padding: 7px 9px; border-bottom: 0.5px solid var(--border); font-size: 12px; vertical-align: middle; }
.record-list tbody tr { cursor: pointer; }
.record-list tbody tr:hover td { background: var(--surface-1); }
.record-list tbody tr.selected td { background: var(--surface-2); }
.record-list-name { font-weight: 500; display: flex; align-items: center; gap: 8px; }
.record-list-thumb {
  width: 26px; height: 26px; border-radius: 5px; flex-shrink: 0; object-fit: cover;
  display: flex; align-items: center; justify-content: center;
  background: var(--cls-tint, var(--surface-2)); color: var(--cls-color, var(--text-secondary));
}
.record-list-sub { font-size: 10px; color: var(--text-muted); }
.record-list-sub.sci { font-style: italic; }
.record-list-num { font-variant-numeric: tabular-nums; }
.record-list-muted { color: var(--text-muted); }

/* The layout switch sits with the filters rather than on its own row — these
   pages have far fewer controls than the Loot Table. */
/* Extends .toolbar; it was a verbatim copy of it plus align-items and wrap,
   both of which .toolbar now carries. Nothing of its own survives except the
   two layout rules below. */
.browse-toolbar .spacer { flex: 1; }
/* the count note carries a negative top margin for its standalone position */
.browse-toolbar .inv-result-note { margin: 0; text-align: right; }
.view-switch.compact { padding: 2px; }
.view-switch.compact .view-switch-btn { padding: 5px 9px; font-size: 12px; gap: 5px; }

/* Micro — filters inside a panel. Eleven feed filters at the compact size took
   four lines out of the Town Center, which is most of a post: the filters were
   bigger than the thing they filter. The icon carries the meaning at this size,
   so it stays full-sized while the label shrinks. */
.view-switch.micro { padding: 2px; gap: 2px; border-radius: 8px; }
.view-switch.micro .view-switch-btn {
  padding: 3px 7px;
  font-size: 11px;
  gap: 4px;
  border-radius: 6px;
}
.view-switch.micro .view-switch-btn i { font-size: 12px; }

/* ---------- taxonomy manager (Codex) ---------- */

.tax-layout { display: flex; gap: 20px; align-items: flex-start; }
.tax-nav { width: 210px; flex-shrink: 0; display: flex; flex-direction: column; gap: 2px; }
.tax-nav-item {
  display: flex; align-items: center; gap: 8px; text-align: left;
  padding: 9px 11px; border-radius: 8px;
  background: transparent; border-color: transparent; color: var(--text-secondary);
  font-size: 13px;
}
.tax-nav-item:hover { background: var(--surface-2); }
.tax-nav-item.active { background: var(--surface-2); border-color: var(--border-strong); color: var(--text-primary); }
.tax-nav-label { flex: 1; min-width: 0; }
.tax-nav-count { font-size: 11px; color: var(--text-muted); font-variant-numeric: tabular-nums; }

.tax-body { flex: 1; min-width: 0; }
.tax-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
.tax-head h3 { font-size: 15px; font-weight: 600; margin: 0 0 3px; }
.tax-hint { font-size: 12px; color: var(--text-secondary); margin: 0; line-height: 1.45; max-width: 60ch; }
/* Size from `.btn-sm`, baseline from `a.btn-plain`. Only the wrap rule is this
   header's own — the label carries a page name and must not break across lines. */
.tax-head .btn-plain { white-space: nowrap; }

.tax-list { display: flex; flex-direction: column; gap: 3px; margin-bottom: 10px; }
.tax-row { display: flex; align-items: center; gap: 11px; padding: 8px 10px; border-radius: 8px; }
.tax-row:hover { background: var(--surface-1); }
.tax-swatch {
  width: 28px; height: 28px; border-radius: 7px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center; font-size: 13px;
}
.tax-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.tax-row-label { font-size: 13px; font-weight: 500; }
.tax-row-note { font-size: 11px; color: var(--text-muted); }
.tax-row-id { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 10px; opacity: 0.75; }
.tax-row-actions { display: flex; align-items: center; gap: 1px; flex-shrink: 0; }
.tax-move { padding: 3px 5px; color: var(--text-muted); }
.tax-move:disabled { opacity: 0.25; }

.tax-editor {
  background: var(--surface-1); border: 0.5px solid var(--border-strong);
  border-radius: 10px; padding: 13px; margin: 3px 0;
}
.tax-editor-top { display: flex; gap: 9px; align-items: center; }
.tax-label-input { flex: 1; font-size: 13px; }
.tax-icon-btn { border: 0.5px solid var(--border-strong); cursor: pointer; }
.tax-editor .icon-picker-grid { margin-top: 8px; }
.tax-editor-actions { display: flex; align-items: center; gap: 8px; margin-top: 11px; }
.tax-editor-actions .spacer { flex: 1; }
.tax-editor-actions .btn-danger { font-size: 12px; }
/* Where the Delete would have been on a built-in entry. */
.tax-builtin {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 10px; color: var(--text-muted); line-height: 1.4;
  max-width: 62%;
}
.tax-builtin i { font-size: 12px; flex-shrink: 0; }
.tax-add { font-size: 12px; color: var(--text-accent); }

@media (max-width: 800px) {
  .tax-layout { flex-direction: column; }
  .tax-nav { width: 100%; flex-direction: row; flex-wrap: wrap; }
}

/* ---------- loot import wizard ---------- */

.wizard-overlay .loot-wizard { width: 640px; max-width: 100%; border-top-color: var(--border-accent); }
.wiz-title { font-size: 20px; font-weight: 600; margin: 0 0 12px; padding-right: 32px; display: flex; align-items: center; gap: 8px; }

/* The global `label { text-transform: lowercase }` is right for the standalone
   captions the other pages use, but this wizard wraps whole sentences, typed
   input and — critically — specimen names in labels, and lowercasing a name
   misrepresents the data. Reset it here, then put the house style back on just
   the small field captions. */
.wiz-mode, .wiz-row, .wiz-inline-check, .wiz-field-row, .wiz-field { text-transform: none; }
.wiz-field > span { text-transform: lowercase; }

.wiz-steps { display: flex; align-items: center; gap: 7px; margin-bottom: 16px; font-size: 11px; color: var(--text-muted); flex-wrap: wrap; }
.wiz-steps i { font-size: 11px; opacity: 0.5; }
.wiz-step.current { color: var(--text-primary); font-weight: 600; }
.wiz-step.done { color: var(--text-secondary); }

.wiz-choice {
  display: flex; gap: 13px; align-items: flex-start;
  border: 0.5px solid var(--border); border-radius: 10px;
  padding: 13px 15px; margin-bottom: 9px; background: var(--surface-2);
}
.wiz-choice > i { font-size: 20px; color: var(--text-accent); flex-shrink: 0; margin-top: 2px; }
.wiz-choice > div { flex: 1; min-width: 0; }
.wiz-choice b { display: block; font-size: 13px; margin-bottom: 2px; }
.wiz-choice span { display: block; font-size: 12px; color: var(--text-secondary); line-height: 1.45; }
.wiz-choice[data-act] { cursor: pointer; }
.wiz-choice[data-act]:hover { background: var(--surface-3); border-color: var(--border-strong); }
.wiz-choice textarea { width: 100%; margin: 9px 0 8px; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px; }
.wiz-upload { display: flex; gap: 8px; align-items: center; margin-top: 9px; flex-wrap: wrap; }
.wiz-upload input[type="file"] { flex: 1; min-width: 150px; font-size: 12px; }

.wiz-readout {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  background: var(--surface-2); border-radius: 9px; padding: 10px 13px;
  font-size: 12px; color: var(--text-secondary); margin-bottom: 12px;
}
.wiz-readout b { color: var(--text-primary); }
.wiz-dot { width: 3px; height: 3px; border-radius: 50%; background: var(--text-muted); }

.wiz-modes { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; }
.wiz-mode {
  display: flex; gap: 10px; align-items: flex-start; cursor: pointer;
  border: 0.5px solid var(--border); border-radius: 9px; padding: 10px 13px;
  background: var(--surface-2);
}
.wiz-mode:hover { background: var(--surface-3); }
.wiz-mode.selected { border-color: var(--border-accent); background: var(--surface-3); }
.wiz-mode.danger.selected { border-color: #c98a8a; }
.wiz-mode input { margin-top: 3px; flex-shrink: 0; }
.wiz-mode-body { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
.wiz-mode-body b { font-size: 13px; }
.wiz-mode-hint { font-size: 11px; color: var(--text-secondary); line-height: 1.4; }
.wiz-mode-impact { font-size: 11px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.wiz-mode.selected .wiz-mode-impact { color: var(--text-accent); }

.wiz-warn {
  display: flex; gap: 11px; align-items: flex-start;
  background: rgba(201,79,79,0.10); border: 1px solid rgba(201,79,79,0.35);
  border-radius: 9px; padding: 12px 14px; margin-bottom: 12px;
  font-size: 12px; color: #d3a2a2; line-height: 1.5;
}
.wiz-warn > i { font-size: 15px; color: #c94f4f; flex-shrink: 0; margin-top: 1px; }
.wiz-warn b { color: #e8c0c0; }
.wiz-warn > div > div { margin-top: 5px; }
.wiz-inline-check { display: flex; align-items: center; gap: 7px; cursor: pointer; color: var(--text-primary); }
.wiz-reassure { color: var(--text-success); }

.wiz-problems { margin-bottom: 12px; font-size: 12px; }
.wiz-problems summary { cursor: pointer; color: var(--text-secondary); }
.wiz-problems ul { margin: 7px 0 0; padding-left: 18px; color: var(--text-secondary); max-height: 150px; overflow-y: auto; }
.wiz-problems li { margin-bottom: 3px; line-height: 1.45; }
.wiz-problem-where { color: var(--text-muted); }
.wiz-problem-more { color: var(--text-muted); font-style: italic; }

.wiz-section { margin-bottom: 18px; }
.wiz-section-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 5px; }
.wiz-section-head .section-label { margin: 0; }
.wiz-bulk { display: flex; gap: 2px; }
/* Size comes from `.btn-xs` on the buttons themselves now; only the colour is
   this strip's own. */
.wiz-bulk .btn-plain { color: var(--text-accent); }
.wiz-scroll { max-height: 290px; overflow-y: auto; padding-right: 3px; }

.wiz-row {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 7px 9px; border-radius: 7px; cursor: pointer;
}
.wiz-row:hover { background: var(--surface-2); }
.wiz-row input[type="checkbox"] { margin-top: 3px; flex-shrink: 0; }
.wiz-row-main { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
.wiz-row-name { font-size: 12px; font-weight: 500; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.wiz-row-note { font-size: 11px; color: var(--text-muted); line-height: 1.4; }
.wiz-row-manual { background: rgba(224,180,79,0.05); }
.wiz-rename { font-size: 12px; display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.wiz-rename s { color: var(--text-muted); }
.wiz-rename i { font-size: 12px; color: var(--text-muted); }

.wiz-tag-manual, .wiz-tag-edited { font-size: 10px; padding: 1px 7px; border-radius: var(--radius-pill); text-transform: uppercase; letter-spacing: 0.4px; white-space: nowrap; }
.wiz-tag-manual { background: rgba(224,180,79,0.15); color: #dbc08a; }
.wiz-tag-guess { font-size: 10px; padding: 1px 7px; border-radius: var(--radius-pill); background: rgba(224,180,79,0.15); color: #dbc08a; white-space: nowrap; }
.wiz-tag-edited { background: rgba(90,155,205,0.18); color: #8ab6e2; }

.wiz-change { border-radius: 8px; }
.wiz-change.expanded { background: var(--surface-2); margin-bottom: 5px; }
.wiz-change-head { align-items: center; }
.wiz-expand { padding: 2px 6px; color: var(--text-muted); flex-shrink: 0; }

.wiz-fields { padding: 2px 9px 10px 34px; }
.wiz-field-row {
  display: grid; grid-template-columns: 18px minmax(88px, auto) 1fr 14px 1fr;
  gap: 8px; align-items: center; padding: 4px 0; font-size: 11px; cursor: pointer;
}
.wiz-field-row > i { color: var(--text-muted); font-size: 12px; }
.wiz-field-name { color: var(--text-secondary); }
.wiz-field-name i { font-size: 11px; color: #8ab6e2; }
.wiz-field-from { color: var(--text-muted); text-decoration: line-through; word-break: break-word; }
.wiz-field-to { color: var(--text-primary); word-break: break-word; }
.wiz-field-row.locally-edited .wiz-field-from { text-decoration: none; color: #8ab6e2; }
.wiz-edit-note { font-size: 11px; color: var(--text-muted); margin: 7px 0 0; line-height: 1.45; }

.wiz-summary { margin: 0 0 12px; padding-left: 20px; font-size: 13px; line-height: 1.7; }
.wiz-summary b { color: var(--text-primary); }
.wiz-nothing { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text-secondary); padding: 14px 0; }
.wiz-nothing i { color: var(--text-success); font-size: 15px; }

.wiz-form { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; margin-bottom: 10px; }
.wiz-field { display: flex; flex-direction: column; gap: 3px; }
.wiz-field.wiz-wide { grid-column: 1 / -1; }
.wiz-field > span { font-size: 11px; color: var(--text-muted); }
.wiz-field input, .wiz-field select { width: 100%; }

.wiz-actions { display: flex; align-items: center; gap: 9px; margin-top: 16px; padding-top: 13px; border-top: 0.5px solid var(--border); }
.wiz-actions .spacer { flex: 1; }
.wiz-tally { font-size: 12px; color: var(--text-secondary); font-variant-numeric: tabular-nums; }

@media (max-width: 620px) {
  .wiz-form { grid-template-columns: 1fr; }
  .wiz-field-row { grid-template-columns: 18px 1fr; }
  .wiz-field-row > i { display: none; }
}

/* Camp preview panel */
.camp-loot { display: flex; flex-direction: column; gap: 3px; }
.camp-loot-row {
  display: flex; align-items: center; gap: 9px;
  padding: 5px 8px; border-radius: 7px; text-decoration: none;
  border-left: 2px solid var(--cls-color, var(--border));
  color: var(--text-primary);
}
.camp-loot-row:hover { background: var(--surface-2); }
.camp-loot-row.is-need { border-left-style: dashed; }
.camp-loot-icon { color: var(--cls-color, var(--text-secondary)); display: flex; flex-shrink: 0; }
.camp-loot-name { font-size: 12px; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.camp-loot-val { font-size: 11px; color: var(--text-muted); flex-shrink: 0; font-variant-numeric: tabular-nums; }
.camp-loot-more { font-size: 11px; color: var(--text-muted); padding: 5px 8px; text-decoration: none; }
.camp-loot-more:hover { color: var(--text-accent); }

@media (max-width: 720px) {
  .loot-row-name { width: 140px; }
  .loot-row-loc, .loot-row .loot-class-tag { display: none; }
  .loot-mv-name { width: 150px; }
  .loot-mv-val { width: 90px; }
  .loot-command-label { width: 100%; }
}

/* ---------- phones and narrow windows ---------- */
/*
   The app was built desktop-first: a 170px sidebar plus ~48px of padding plus a
   230–300px detail column is ~450–520px of fixed chrome before any content, so
   every page overflowed horizontally below about 800px. Paired with the
   viewport meta tag now on every page, this block is what makes it usable on a
   phone over the LAN.

   860px is the breakpoint because that's where the widest two-column layout
   (quests, 300px detail) starts squeezing its content column below ~400px.
*/
@media (max-width: 860px) {
  /* THE NAV BECOMES A DRAWER, not a horizontally scrolling strip.
     The strip was eleven items in a hidden-overflow row with nothing saying it
     scrolled, the active item usually off screen, and a drag gesture competing
     with the page's own vertical scroll. The drawer shows every destination at
     once, keeps the desktop order and the same active state, and leaves the
     content underneath alone.
     It keeps `flex-direction: column` from the base rule — this is the same rail,
     moved, not a different component. */
  .app-shell { flex-direction: column; }
  .sidebar {
    position: fixed;
    top: 48px;
    left: 0;
    bottom: 0;
    width: 232px;
    max-width: 82vw;
    height: auto;
    padding: 10px 10px 18px;
    border-right: 0.5px solid var(--border);
    background: var(--surface-1);
    box-shadow: 6px 0 24px rgba(0,0,0,0.45);
    overflow-y: auto;
    overscroll-behavior: contain;
    /* Off-canvas by default. `translate` rather than `display` so it animates,
       and `visibility` so a closed drawer is not focusable — a keyboard user
       tabbing into an invisible menu is the classic version of this bug. */
    transform: translateX(-100%);
    visibility: hidden;
    transition: transform 0.22s ease, visibility 0.22s;
    /* Both drawer and scrim sit in the 950-999 transient-overlay band from the
       table at the top of effects.css — a nav drawer is exactly that. The scrim
       takes the floor of the band and the panel one above it, so the two stay
       ordered without reaching outside. */
    z-index: 951;
  }
  :root.nav-open .sidebar { transform: translateX(0); visibility: visible; }
  .nav-item { white-space: nowrap; }
  .nav-footer { margin-top: auto; }

  .nav-toggle { display: inline-flex; }
  :root.nav-open .nav-scrim { opacity: 1; pointer-events: auto; }

  .main { padding: 14px 14px 32px; }
  /* The map page keeps its full-frame padding-free layout on mobile too — it is
     the exception that owns the whole frame at every width. */
  .main.map-full { padding: 0; }
  .page-header { flex-wrap: wrap; gap: 8px; }

  /* Every two-column page becomes one column, detail under its list. */
  .inventory-layout, .spellbook-layout, .journal-layout,
  .quest-layout, .syslib-pane { flex-direction: column; }
  .inventory-side, .spellbook-side, .journal-side,
  .quest-detail, .syslib-rail { width: 100%; }
  /* The *main* halves were missing from that list. It didn't show until the
     List layout gained a column: a flex item with no width takes its content's
     size, so the wide table dragged the whole page sideways instead of
     scrolling inside its own `.record-list-wrap`. min-width:0 is the half that
     actually lets a flex child shrink below its content. */
  .inventory-main, .spellbook-main, .journal-main,
  .quest-board { width: 100%; min-width: 0; }

  /* Toolbars wrap instead of pushing the page wide. */
  .toolbar, .quest-toolbar, .loot-controls, .browse-toolbar { flex-wrap: wrap; }
  .toolbar input[type="search"], .loot-search { flex: 1 1 100%; min-width: 0; }
  .tabs { overflow-x: auto; }

  /* Denser cards so two still fit across a phone. */
  .record-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; }
  .loot-grid { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 8px; }
}

/* The top bar's search is the one control that can't wrap — it shares a fixed
   48px row with the wordmark. Below 560px the wordmark goes, leaving the field
   the full width. */
@media (max-width: 560px) {
  .topbar { gap: 8px; padding: 0 10px; }
  .topbar-name { display: none; }
  .search-kbd { display: none; }
  /* With the wordmark gone the brand is a 26px logo in a 158px box, and the
     right slot now holds an account chip rather than being empty — so the
     spacer widths that keep the search optically centred on a desktop are
     exactly what pushes the row past the screen edge on a phone. Both sides
     shrink to their contents and the search takes what's left. */
  /* Both sides drop their equal-share basis and collapse to their contents;
     the search takes whatever is left. Without the `flex` override the new
     basis wins and the three of them try to split the row in thirds. */
  .topbar-brand { flex: 0 0 auto; }
  .topbar-right { flex: 0 0 auto; gap: 4px; }
  .topbar-search { flex: 1 1 auto; margin: 0; min-width: 0; }
  /* The input fills the row at this width, so the results panel just matches it
     rather than keeping the desktop right-anchored fixed width — which, anchored
     to a wide box's right edge, ran off the LEFT of the screen. */
  .search-results { left: 0; right: 0; width: auto; min-width: 0; max-width: none; }
  /* The coin count keeps its number; the pill just gets tighter. A currency
     you can't see is one you forget you're collecting. */
  .coin-total { padding: 2px 6px; font-size: 11px; }
  .account-name { display: none; }
  .record-grid { grid-template-columns: repeat(auto-fill, minmax(135px, 1fr)); }
}

/* Touch targets: a 26px icon button is fine with a mouse and awkward with a
   thumb. Only applied on genuinely touch-primary devices so desktop layouts
   keep their density. */
@media (hover: none) and (pointer: coarse) {
  .edit-toggle, .tax-move, .loot-modal-close, .wiz-expand { min-width: 34px; min-height: 34px; }
  .nav-item, .tab-btn, .view-switch-btn { min-height: 36px; }
  .record-list td { padding: 10px 9px; }
}

/* ---------- misc ---------- */
.hidden { display: none !important; }
a { color: var(--text-accent); }

/* ---------- accounts ---------- */

/* The login page has no shell — no top bar, no sidebar — because it's the one
   page you can reach without an account, and drawing the app's furniture
   around an empty state would just be a tease. */
.auth-body {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.auth-shell { width: 100%; max-width: 380px; }
.auth-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  justify-content: center;
  margin-bottom: 18px;
}
.auth-brand-name { font-size: 15px; font-weight: 500; letter-spacing: 0.01em; }
.auth-card {
  background: var(--surface-1);
  border: 0.5px solid var(--border-strong);
  border-radius: var(--radius-lg);
  padding: 20px;
}
/* "Request access" — the invite-only helper below the card, collapsed by default. */
.auth-request { margin-top: 14px; }
.auth-request-toggle { width: 100%; background: none; border: 0; color: var(--text-secondary);
  font-size: 12px; cursor: pointer; padding: 8px; display: flex; align-items: center; justify-content: center; gap: 6px; }
.auth-request-toggle:hover { color: var(--text-primary); }
.auth-request-toggle b { color: var(--accent-hover); font-weight: 600; }
.auth-request-form { display: flex; flex-direction: column; gap: 5px; margin-top: 6px; padding: 16px;
  background: var(--surface-1); border: 0.5px solid var(--border-strong); border-radius: var(--radius-lg); }
.auth-request-form[hidden] { display: none; }
.auth-request-form input, .auth-request-form textarea { width: 100%; }
.auth-request-form label { margin-top: 6px; }
.auth-request-form button[type="submit"] { margin-top: 12px; }
.auth-request-intro { font-size: 12px; color: var(--text-secondary); line-height: 1.6; margin: 0 0 4px; }
.auth-request-row { display: flex; gap: 8px; }
.auth-request-row .auth-field { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.auth-request-done { margin-top: 12px; padding: 12px; border-radius: var(--radius-md); line-height: 1.5;
  background: rgba(74,157,91,0.12); color: var(--text-success); font-size: 13px; text-align: center; }
.auth-request-full { margin-top: 12px; padding: 12px; border-radius: var(--radius-md); line-height: 1.5;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text-secondary); font-size: 12px; text-align: center; }
/* A `.view-switch` modifier now, not its own control. The two halves split the
   full width because Sign in / Create account is a choice between equals. */
/* The one switch that deliberately fills its container rather than hugging its
   buttons: Sign in / Create account is a choice between two equals on a narrow
   card, so the halves split the width. */
.auth-tabs { margin-bottom: 14px; width: 100%; }
.auth-tabs .view-switch-btn { flex: 1; justify-content: center; }
.auth-intro { font-size: 12px; color: var(--text-secondary); line-height: 1.6; margin: 0 0 14px; }
.auth-form { display: flex; flex-direction: column; gap: 5px; }
.auth-form input { width: 100%; }
.auth-form label { margin-top: 6px; }
/* `display: contents` so the label and input inside sit in the form's own
   flex flow rather than in a nested box — the wrapper exists only to hide the
   pair together. Which is why `hidden` has to be spelled out: the UA
   stylesheet's `[hidden] { display: none }` loses to any display rule of ours,
   so it needs at least as much specificity and a later position. */
.auth-form .auth-field { display: contents; }
.auth-form .auth-field[hidden], .auth-tabs[hidden] { display: none; }
.auth-form button[type="submit"] { margin-top: 14px; }
.auth-optional { color: var(--text-muted); text-transform: none; font-style: italic; }
.auth-error {
  font-size: 12px;
  color: var(--danger);
  background: rgba(230,110,110,0.10);
  border: 0.5px solid rgba(230,110,110,0.28);
  border-radius: var(--radius);
  padding: 7px 10px;
  margin: 10px 0 0;
}
.auth-note { font-size: 11px; color: var(--text-muted); margin: 12px 0 0; text-align: center; }

/* The change-password dialog borrows the export dialog's frame — same overlay,
   same card — rather than growing a second modal system. */
.auth-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: 24px;
}
.auth-modal {
  background: var(--surface-1);
  border: 0.5px solid var(--border-strong);
  border-radius: 12px;
  padding: 20px;
  width: 380px;
  max-width: 100%;
  box-shadow: 0 12px 40px rgba(0,0,0,0.6);
}

/* .topbar-right was an empty 158px spacer holding the search box centred; the
   account chip now lives in it, so it keeps the same width. */
.account { position: relative; display: flex; justify-content: flex-end; }
.account-chip {
  display: flex;
  align-items: center;
  gap: 7px;
  max-width: 100%;
  padding: 4px 8px 4px 4px;
  background: transparent;
  border-color: transparent;
}
.account-chip:hover { background: var(--surface-2); }
.account-chip > i { font-size: 13px; color: var(--text-muted); flex-shrink: 0; }
/* Falls back to a neutral well with initials in it; adding an .la-* class (the
   avatar the user picked) swaps in that colour without changing anything else. */
.account-avatar {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--cls-tint, var(--surface-3));
  color: var(--cls-color, var(--text-secondary));
  font-size: 10px;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.account-avatar i { font-size: 13px; }
.account-name {
  font-size: 12px;
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.account-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 190px;
  background: var(--surface-1);
  border: 0.5px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(0,0,0,0.55);
  padding: 5px;
  z-index: 900;
  max-width: calc(100vw - 16px); max-height: 80svh; overflow-y: auto;
}
.account-menu-head { padding: 6px 8px 8px; border-bottom: 0.5px solid var(--border); margin-bottom: 5px; }
.account-menu-name { font-size: 13px; }
.account-menu-sub { font-size: 11px; color: var(--text-muted); }
.account-menu-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  text-align: left;
  font-size: 12px;
  color: var(--text-secondary);
  background: transparent;
  border-color: transparent;
  padding: 7px 8px;
}
.account-menu-item:hover { background: var(--surface-2); color: var(--text-primary); }
.account-menu-item i { font-size: 13px; }
/* Most entries are <a>s now that the menu is navigation; Sign out is still a
   <button>. Same row either way. */
a.account-menu-item { text-decoration: none; box-sizing: border-box; }
a.account-menu-head { display: block; text-decoration: none; color: inherit; border-radius: 6px; }
a.account-menu-head:hover { background: var(--surface-2); }

/* ---------- units ---------- */

/* A number and its unit are one value, so they sit on one row and read as one
   thing. The number takes the space; the unit picker stays just wide enough. */
.amount-row { display: flex; gap: 6px; align-items: center; }
.amount-row input[type="number"] { flex: 1; min-width: 0; }
.unit-select { flex: 0 0 auto; width: auto; min-width: 82px; max-width: 130px; }
.field-hint { color: var(--text-muted); text-transform: none; font-style: italic; margin-left: 4px; }

/* Amber, not red: units that can't be compared are *unknown*, not a shortage.
   Red here would say "you're short" about something that may be well stocked. */
.ingredient-need.mismatch { color: #d9a441; }

/* The amount asked for by a quest requirement, next to the item name. */
.req-amount { color: var(--text-secondary); font-size: 11px; }

/* Unit pickers inside the tight editor rows on Spellbook and Quests. */
.ingredient-qty-row .unit-select,
.quest-row-edit .unit-select { min-width: 70px; max-width: 96px; font-size: 11px; padding: 4px 6px; }
.add-ing-controls .add-ing-unit { min-width: 78px; }

/* Size on a card: a fact about the object, not a warning, so it's quiet and
   sits beside the category rather than competing with the flag. */
.record-size {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 10px;
  color: var(--text-muted);
}
.record-size i { font-size: 11px; }

/* ---------- profile (the character sheet) ---------- */

.profile-settings-link { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; text-decoration: none; }
.profile-head {
  display: flex;
  align-items: flex-start;
  gap: 18px;
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: 12px;
  padding: 18px;
  margin-bottom: 14px;
}
.profile-head-text { flex: 1; min-width: 0; }
/* In edit mode the header holds three stacked fields instead of prose. */
.profile-head.editing .profile-head-text { display: flex; flex-direction: column; gap: 3px; }
.profile-head.editing .profile-head-text input { width: 100%; max-width: 420px; }
.profile-head.editing label:not(:first-child) { margin-top: 8px; }

/* The avatar uses the .la-* custom properties rather than .c-*, because it
   wants a tinted well with a saturated glyph, not a solid chip. */
.profile-avatar {
  border-radius: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  background: var(--cls-tint, var(--surface-3));
  color: var(--cls-color, var(--text-secondary));
  border: 0.5px solid var(--border);
}
.profile-avatar-initials { font-size: 26px; font-weight: 600; letter-spacing: 0.02em; }

/* ---------- the badge ----------
   The composited avatar. This is BASE CHROME, not a cosmetic: every account has
   a badge, including one that owns nothing, so the structure lives here and the
   bought parts live in badge.css. With that file absent this still draws the
   tinted disc with an icon in it that the avatar always was.

   It sits on the same element as .account-avatar / .profile-avatar and must
   come after both — four bought avatar effects are keyed to those two
   selectors, so the element keeps its old class and gains this one rather than
   being renamed out from under them.

   NO z-index ANYWHERE, and no `isolation`, `transform`, `filter` or `opacity`
   on .bdg. Two effects (Gold Standard's rank ring, the badgeRing flourish) draw
   themselves as pseudo-elements at z-index:-1, and per the layering table at
   the top of effects.css a -1 child vanishes the instant its parent creates a
   stacking context. The five layers below are all absolutely positioned, so
   they already paint in DOM order — bg, face, frame, ring, mark — and asking
   for a z-index would buy nothing and cost those two effects. */
.bdg {
  position: relative;
  width: var(--bdg-size, 24px);
  height: var(--bdg-size, 24px);
  border-radius: 50%;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--cls-tint, var(--surface-3));
  color: var(--cls-color, var(--text-secondary));
  vertical-align: middle;
}
.bdg-bg, .bdg-face, .bdg-frame, .bdg-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
}
.bdg-bg { overflow: hidden; }
.bdg-face { display: flex; align-items: center; justify-content: center; }
.bdg-face i { font-size: calc(var(--bdg-size, 24px) * 0.5); line-height: 1; }
.bdg-initials {
  font-size: calc(var(--bdg-size, 24px) * 0.38);
  font-weight: 600;
  letter-spacing: 0.02em;
  line-height: 1;
}
/* The ring is the only layer allowed outside the disc. It overhangs on an
   absolutely-positioned child, so it paints past the edge without taking any
   space — which is what lets it sit in a top bar whose spacers are fixed. */
.bdg-ring { inset: calc(var(--bdg-size, 24px) * -0.1); }
/* The profession mark. Only drawn at 34px and up (see badge.js) — below that
   it is four pixels of colour and reads as a smudge. */
.bdg-mark {
  position: absolute;
  right: -4%;
  bottom: -4%;
  width: 36%;
  height: 36%;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-1);
  box-shadow: 0 0 0 1px var(--border-strong);
  color: var(--cls-color, var(--text-secondary));
  pointer-events: none;
}
.bdg-mark i { font-size: calc(var(--bdg-size, 24px) * 0.19); line-height: 1; }

.profile-name { font-size: 20px; font-weight: 500; margin: 0 0 2px; }
.profile-title { font-size: 13px; color: var(--text-secondary); font-family: var(--font-voice); font-style: italic; }
.profile-title.empty { color: var(--text-muted); }
.profile-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--text-muted);
  margin-top: 8px;
}
/* The separator belongs to the item that follows it, so a wrapped row never
   leaves a lone middle dot at the end of a line. */
.profile-meta > span + span:not(.admin-tag)::before { content: '·'; margin-right: 6px; color: var(--text-muted); }
.profile-meta i { font-size: 12px; }
.profile-classes { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 10px; }
.profile-bio {
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: 12px;
  padding: 14px 18px;
  margin-bottom: 22px;
}
.profile-note { font-size: 11px; color: var(--text-muted); margin: 0 0 12px; max-width: 66ch; line-height: 1.6; }
.profile-edit-block {
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: 12px;
  padding: 14px 18px;
  margin-bottom: 14px;
}
.profile-edit-block textarea { width: 100%; }
/* The shared picker is `repeat(6, 1fr)` with square cells, sized for the
   Inventory detail panel — in a full-width block that makes 100px buttons.
   Here it packs to a fixed swatch size instead and the row count follows. */
.profile-edit-block .icon-picker-grid {
  margin-top: 6px;
  grid-template-columns: repeat(auto-fill, minmax(36px, 1fr));
  max-height: 200px;
}

.profile-stats {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 10px;
  margin-bottom: 26px;
}
/* Each block is a link to the page it counts — a stat you can't get to is
   trivia rather than a way in. */
.profile-stat {
  display: block;
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
  text-decoration: none;
  color: inherit;
}
.profile-stat:hover { border-color: var(--border-strong); background: var(--surface-2); }
.profile-stat-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin-bottom: 9px;
}
.profile-stat-head i { font-size: 13px; }
.profile-stat-rows { display: flex; flex-direction: column; gap: 3px; }
.profile-stat-row { display: flex; align-items: baseline; gap: 6px; }
.profile-stat-row b { font-size: 20px; font-weight: 500; min-width: 2ch; }
.profile-stat-row span { font-size: 11px; color: var(--text-secondary); }

/* ---- achievements (profile) ---- */
.ach-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr)); gap: 10px; }
.ach-card { display: flex; gap: 12px; padding: 12px; background: var(--surface-1);
  border: 0.5px solid var(--border); border-radius: var(--radius-md); }
.ach-card.ach-locked { opacity: 0.62; }
.ach-badge { flex-shrink: 0; width: 46px; height: 46px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; font-size: 20px;
  background: var(--surface-2); border: 2px solid var(--border); color: var(--text-secondary); }
/* Medal tints — the ring and glow climb bronze → master. */
.ach-bronze .ach-badge { border-color: #b06f3a; color: #cd8a51; background: rgba(176,111,58,0.12); }
.ach-silver .ach-badge { border-color: #9aa2ac; color: #c3ccd6; background: rgba(154,162,172,0.14); }
.ach-gold .ach-badge { border-color: #e0a94f; color: #f0c473; background: rgba(224,169,79,0.15); }
.ach-platinum .ach-badge { border-color: #5fbccb; color: #8fd6e2; background: rgba(95,188,203,0.15); }
.ach-master .ach-badge { border-color: #b892f0; color: #d3bcff; background: rgba(184,146,240,0.18);
  box-shadow: 0 0 12px rgba(184,146,240,0.4); }
.ach-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.ach-head { display: flex; align-items: center; gap: 8px; }
.ach-name { font-size: 13px; font-weight: 600; color: var(--text-primary); }
.ach-medal { font-size: 9px; text-transform: uppercase; letter-spacing: 0.05em;
  padding: 1px 7px; border-radius: var(--radius-pill); background: var(--surface-2); color: var(--text-secondary); }
.ach-gold .ach-medal { color: #f0c473; } .ach-platinum .ach-medal { color: #8fd6e2; }
.ach-master .ach-medal { color: #d3bcff; }
.ach-desc { font-size: 11px; color: var(--text-secondary); margin: 0; }
.ach-pips { display: flex; gap: 4px; margin: 3px 0 1px; }
.ach-pip { width: 100%; height: 4px; border-radius: 2px; background: var(--surface-3); }
.ach-pip.on { background: var(--border-accent); }
.ach-master .ach-pip.on { background: #b892f0; }
.ach-bar { height: 5px; background: var(--surface-3); border-radius: 3px; overflow: hidden; }
.ach-bar > span { display: block; height: 100%; background: var(--border-accent); }
.ach-master .ach-bar > span { background: #b892f0; }
.ach-count { font-size: 11px; color: var(--text-muted); margin: 1px 0 0; }
.ach-count b { color: var(--text-primary); }

.profile-professions { display: flex; flex-direction: column; gap: 1px; background: var(--border); border: 0.5px solid var(--border); border-radius: 10px; overflow: hidden; }
/* A grid, not a flex row — the row used to be one line, and once it grew a
   progress bar and a values line those became flex *siblings* squeezed in
   beside the name instead of spanning underneath it. Three columns for the
   first line; anything else takes a full-width row of its own. */
.profile-prof {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 3px 10px;
  padding: 9px 14px;
  background: var(--surface-1);
}
.profile-prof .xp-bar,
.profile-prof .xp-line { grid-column: 1 / -1; }
/* A profession you've claimed gets its colour as a spine; the rest stay quiet. */
.profile-prof.is-mine { box-shadow: inset 2px 0 0 var(--cls-color); }
.profile-prof-icon { color: var(--cls-color); display: flex; }
.profile-prof-name { font-size: 13px; flex: 1; min-width: 0; }
.profile-prof-counts { font-size: 11px; color: var(--text-muted); text-align: right; }
.profile-prof-counts b { font-weight: 500; color: var(--text-secondary); }

.profile-class-picker { display: flex; flex-wrap: wrap; gap: 6px; }
.class-pick { border: 0.5px solid transparent; opacity: 0.45; cursor: pointer; }
.class-pick.picked { opacity: 1; border-color: var(--text-primary); }
.class-pick:hover { opacity: 0.8; }

/* The class HERO — the character-sheet headline under your name in edit mode.
   Your active profession IS your class, so it gets the identity treatment: a
   colour/gradient band, a big icon well, the name, level and an XP bar. */
.class-hero {
  position: relative; display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 12px 14px 12px 18px; margin: 4px 0 10px;
  border: 0.5px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface-1); overflow: hidden;
}
/* The identity strip down the left edge — the solid class colour, or a mix's
   full gradient set inline. */
.class-hero-band { position: absolute; left: 0; top: 0; bottom: 0; width: 5px; background: var(--cls-color, var(--text-accent)); }
.class-hero-icon {
  flex-shrink: 0; width: 46px; height: 46px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--cls-tint, var(--surface-2)); color: var(--cls-color, var(--text-primary));
}
.class-hero-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.class-hero-eyebrow { font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }
.class-hero-name { font-size: 20px; font-weight: 600; color: var(--text-primary); line-height: 1.1; }
.class-hero-text .level-pill { align-self: flex-start; margin-top: 3px; }
.class-hero-xp { flex: 1 1 130px; min-width: 130px; }
.class-hero-empty { opacity: 0.75; }
.class-hero-empty .class-hero-icon { background: var(--surface-2); color: var(--text-muted); }
.class-hero-empty .class-hero-band { background: var(--border-strong); }

/* The element's authored identity — vibe, description, and the real-world crafts
   it covers — as a quiet expandable under the hero (base elements only). */
.class-lore { margin: -4px 0 10px; border: 0.5px solid var(--border); border-radius: var(--radius-md); background: var(--surface-1); overflow: hidden; }
.class-lore-sum { display: flex; align-items: center; gap: 8px; padding: 9px 12px; cursor: pointer; font-size: 12px; color: var(--text-primary); list-style: none; }
.class-lore-sum::-webkit-details-marker { display: none; }
.class-lore-sum > i:first-child { color: var(--cls-color, var(--text-accent)); }
.class-lore-vibe { color: var(--text-secondary); font-style: italic; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.class-lore-chev { margin-left: auto; color: var(--text-muted); transition: transform 0.15s; flex-shrink: 0; }
.class-lore[open] .class-lore-chev { transform: rotate(180deg); }
.class-lore[open] .class-lore-vibe { white-space: normal; }
.class-lore-blurb { margin: 0; padding: 2px 12px 9px; font-size: 12px; color: var(--text-secondary); line-height: 1.5; }
.class-lore-egs { display: flex; flex-wrap: wrap; gap: 5px; padding: 0 12px 12px; }

/* ---------- settings ---------- */

.settings-block { margin-bottom: 28px; max-width: 560px; }
.settings-row { display: flex; flex-direction: column; gap: 4px; }
.settings-row label { margin-top: 8px; }
.settings-row input { max-width: 320px; }
.settings-row button[type="submit"] { margin-top: 14px; align-self: flex-start; }
.settings-saved { font-size: 11px; color: var(--text-success); margin: 8px 0 0; }
.settings-danger {
  flex-direction: row;
  align-items: center;
  gap: 16px;
  margin-top: 26px;
  padding-top: 18px;
  border-top: 0.5px solid var(--border);
}
.settings-danger-title { font-size: 13px; }
.settings-danger .profile-note { margin: 2px 0 0; }
.settings-danger button { flex-shrink: 0; }

@media (max-width: 560px) {
  /* Narrow enough that these facts wrap onto separate lines, where a leading
     separator reads as a bullet nobody asked for. The gap does the work. */
  .profile-meta { gap: 4px 12px; }
  .profile-meta > span + span:not(.admin-tag)::before { content: none; }
}

@media (max-width: 860px) {
  .profile-head { flex-wrap: wrap; gap: 12px; }
  .profile-prof { flex-wrap: wrap; }
  .profile-prof-counts { text-align: left; width: 100%; padding-left: 25px; }
  .settings-danger { flex-direction: column; align-items: flex-start; }
}

/* ---------- admin dashboard ---------- */

.admin-lede { font-size: 12px; color: var(--text-secondary); max-width: 68ch; margin: 0 0 20px; line-height: 1.7; }
.admin-section { margin-bottom: 28px; }
.admin-h2 {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin: 0 0 10px;
}
.admin-accounts { display: flex; flex-direction: column; gap: 10px; }
/* ---- play-bots ---- */
.admin-bot-activity { margin-top: 14px; }
.admin-bot-activity > summary { cursor: pointer; font-size: 12px; color: var(--text-secondary); user-select: none; }
.admin-activity-log { margin-top: 8px; max-height: 320px; overflow-y: auto; display: flex; flex-direction: column; gap: 1px; }
.admin-activity-row { display: flex; align-items: baseline; gap: 8px; font-size: 11px; padding: 3px 0; border-bottom: 0.5px solid var(--border); }
.admin-activity-when { color: var(--text-muted); flex-shrink: 0; width: 64px; }
.admin-activity-who { color: var(--text-secondary); flex-shrink: 0; width: 96px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.admin-activity-what { color: var(--text-primary); flex: 1; min-width: 0; }
.admin-activity-what.is-error { color: var(--danger); }
.admin-bot-llm { margin-bottom: 12px; }
.admin-bot-llm-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; background: var(--surface-1); border: 0.5px solid var(--border); border-radius: 10px; padding: 10px 12px; }
.admin-bot-llm-row.is-on { border-color: var(--border-accent); box-shadow: 0 0 0 1px var(--border-accent) inset; }
.admin-bot-llm-row.is-off { opacity: 0.85; }
.admin-bot-llm-row .admin-bot-sub { flex: 1; min-width: 220px; }
.admin-bots { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); gap: 10px; }
.admin-bot { background: var(--surface-1); border: 0.5px solid var(--border); border-radius: 10px; padding: 12px; }
.admin-bot.is-on { border-color: var(--border-accent); box-shadow: 0 0 0 1px var(--border-accent) inset; }
.admin-bot-head { display: flex; align-items: flex-start; gap: 10px; }
.admin-bot-id { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.admin-bot-name { font-size: 13px; font-weight: 600; color: var(--text-primary); display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.admin-bot-sub { font-size: 11px; color: var(--text-secondary); }
.admin-bot-meta { font-size: 11px; color: var(--text-muted); }
.admin-bot-power { flex-shrink: 0; }
.admin-bot-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 10px; padding-top: 8px; border-top: 0.5px solid var(--border); }
.admin-bot-interact { font-size: 11px; color: var(--text-secondary); display: flex; align-items: center; gap: 6px; }
.admin-bot-last { font-size: 11px; color: var(--text-muted); text-align: right; }

.admin-account {
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
}
.admin-account-head { display: flex; align-items: center; gap: 10px; }
.admin-account-id { flex: 1; min-width: 0; }
.admin-account-name { font-size: 13px; display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.admin-account-sub { font-size: 11px; color: var(--text-muted); }
.admin-tag {
  font-size: 9px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 2px 6px;
  border-radius: 4px;
  background: rgba(91,156,242,0.16);
  color: var(--text-accent);
}
.admin-tag.muted { background: var(--surface-3); color: var(--text-muted); }
.admin-account-actions { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
.admin-account-actions button { font-size: 11px; display: flex; align-items: center; gap: 5px; }
.admin-account-counts {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 0.5px solid var(--border);
}
.admin-count { font-size: 11px; color: var(--text-muted); }
.admin-count b { font-size: 13px; font-weight: 500; color: var(--text-primary); margin-right: 3px; }
.admin-account-foot { font-size: 10px; color: var(--text-muted); margin-top: 8px; }

.admin-grid { display: flex; flex-direction: column; gap: 1px; background: var(--border); border: 0.5px solid var(--border); border-radius: 10px; overflow: hidden; }
.admin-fact { display: flex; gap: 14px; padding: 9px 14px; background: var(--surface-1); }
.admin-fact-key { font-size: 11px; color: var(--text-muted); width: 130px; flex-shrink: 0; }
.admin-fact-value { font-size: 12px; word-break: break-all; }

.admin-toggle { margin-top: 14px; }
.admin-switch { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--text-primary); text-transform: none; cursor: pointer; }
.admin-switch input { width: 15px; height: 15px; accent-color: var(--border-accent); margin: 0; }
.admin-hint { font-size: 11px; color: var(--text-muted); margin: 6px 0 0 23px; max-width: 60ch; line-height: 1.6; }

/* Whitelist / allowlist editor (Parameters → invite-only). */
.admin-allowlist { margin: 8px 0 0 23px; max-width: 40ch; }
.admin-allowlist-add { display: flex; gap: 6px; }
.admin-allowlist-add input { flex: 1; min-width: 0; }
.admin-allowlist-list { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.admin-allowlist-list li { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--text-primary);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-md); padding: 4px 6px 4px 10px; }
.admin-allowlist-list li span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.admin-allowlist-list li.admin-hint { display: block; background: none; border: 0; padding: 0; margin: 8px 0 0; }

/* Accounts sub-tabs + the access-request queue. */
.admin-subtabs { margin-bottom: 16px; }
.admin-requests { display: flex; flex-direction: column; gap: 10px; }
.admin-request { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-md); padding: 12px; }
.admin-request-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.admin-request-name { font-weight: 600; color: var(--text-primary); }
.admin-request-email { color: var(--text-secondary); font-size: 12px; margin-left: 8px; }
.admin-request-msg { font-size: 12px; color: var(--text-secondary); font-style: italic; margin: 8px 0 0; }
.admin-request-status { font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; padding: 2px 8px; border-radius: 999px; white-space: nowrap; }
.admin-request-status.s-pending { background: rgba(224,169,79,0.15); color: var(--text-secondary); }
.admin-request-status.s-approved { background: rgba(74,157,91,0.15); color: var(--text-success); }
.admin-request-status.s-denied { background: rgba(230,110,110,0.12); color: var(--danger); }
.admin-request-actions { display: flex; gap: 6px; margin-top: 10px; }

.admin-ranges { display: flex; flex-direction: column; gap: 14px; margin-top: 12px; }
.admin-range-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.admin-range-label { display: flex; flex-direction: column; gap: 2px; font-size: 13px; color: var(--text-primary); }
.admin-range-hint { font-size: 11px; color: var(--text-secondary); }
.admin-range-input { display: flex; align-items: center; gap: 6px; }
.admin-range-input input { width: 96px; }
.admin-range-input input.is-saved { border-color: var(--text-success); }
.admin-range-unit { font-size: 12px; color: var(--text-muted); }

@media (max-width: 860px) {
  .admin-account-head { flex-wrap: wrap; }
  .admin-account-actions { width: 100%; justify-content: flex-end; }
  .admin-fact { flex-direction: column; gap: 2px; }
  .admin-fact-key { width: auto; }
}

/* On a phone the top bar is already tight — the name goes, the avatar stays. */
@media (max-width: 860px) {
  .topbar-right { width: auto; }
  .account-name { display: none; }
}

/* Layer conflicts, stated rather than implied. A rack of six tiles looks like a
   radio group; these two lines are what tell you it isn't. */
.shop-replaces {
  margin: 0; font-size: 10px; color: var(--text-accent);
  display: flex; gap: 5px; align-items: flex-start; line-height: 1.4;
}
.shop-replaces i { font-size: 12px; flex-shrink: 0; margin-top: 1px; }
.lo-note { margin: 0 0 8px; font-size: 11px; color: var(--text-muted); line-height: 1.45; }

/* Time-gated effects say whether they're showing RIGHT NOW. A seasonal cosmetic
   that renders nothing for nine months is indistinguishable from a broken one
   unless the shop tells you which it is. */
.shop-when {
  margin: 0; font-size: 10px; color: var(--text-muted);
  display: flex; gap: 5px; align-items: flex-start; line-height: 1.4;
}
.shop-when.is-live { color: var(--text-success); }
.shop-when i { font-size: 12px; flex-shrink: 0; margin-top: 1px; }

/* ---------- the style guide (styleguide.html) ----------
   Specimens for every component, in whatever skin is equipped. This block is
   the ONLY place in the app allowed to style `.sg-*`, and nothing outside
   styleguide.html uses it — the guide observes the design system rather than
   being part of it, so if this block vanished the app would be unchanged.

   It follows its own rules, which is the point: tokens for every colour, the
   radius scale, px padding, and the shared components for anything it isn't
   specifically demonstrating. */
.sg-link { margin: -6px 0 12px; }
.sg-toc { margin-bottom: 20px; }
.sg-section { margin: 0 0 34px; scroll-margin-top: 74px; }
.sg-h2 {
  font-size: 15px; font-weight: 600; margin: 0 0 4px;
  padding-bottom: 6px; border-bottom: 0.5px solid var(--border);
}
.sg-h3 { font-size: 12px; font-weight: 600; color: var(--text-secondary); margin: 16px 0 8px; }
.sg-note { font-size: 12px; color: var(--text-muted); line-height: 1.5; margin: 0 0 14px; max-width: 70ch; }

.sg-spec {
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-md);
  padding: 11px 12px;
  margin-bottom: 10px;
}
.sg-spec-label { font-size: 11px; color: var(--text-muted); margin-bottom: 8px; }
.sg-spec-demo { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 9px; }
.sg-spec-code {
  display: block; font-size: 11px; line-height: 1.5;
  color: var(--text-secondary);
  background: var(--surface-0);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 6px 8px;
  overflow-x: auto; white-space: pre;
}
.sg-spec-note { font-size: 11px; color: var(--text-muted); margin-top: 7px; line-height: 1.5; }

.sg-row { display: flex; gap: 14px; flex-wrap: wrap; }
.sg-stack > .sg-spec { max-width: 640px; }
.sg-radius { display: flex; flex-direction: column; gap: 3px; font-size: 11px; width: 132px; }
.sg-radius-box {
  width: 100%; height: 46px;
  background: var(--surface-2);
  border: 0.5px solid var(--border-strong);
  margin-bottom: 4px;
}
.sg-radius b { font-size: 11px; }
.sg-radius span { color: var(--text-accent); }
.sg-radius em { color: var(--text-muted); font-style: normal; line-height: 1.4; }

.sg-type-row { display: flex; align-items: baseline; gap: 14px; padding: 5px 0; border-bottom: 0.5px solid var(--border); }
.sg-type-size { font-size: 11px; color: var(--text-muted); width: 44px; flex-shrink: 0; }
.sg-type-row em { font-style: normal; font-size: 11px; color: var(--text-muted); margin-left: auto; text-align: right; }

.sg-swatches { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(190px, 100%), 1fr)); gap: 8px; }
.sg-swatch { display: flex; align-items: center; gap: 8px; font-size: 11px; min-width: 0; }
.sg-swatch-chip {
  width: 26px; height: 26px; flex-shrink: 0;
  border-radius: var(--radius-sm);
  border: 0.5px solid var(--border-strong);
}
.sg-swatch b { font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Scoped to the VALUE span only. As `.sg-swatch span` this had specificity
   0,1,1 and beat `.c-teal` at 0,1,0 — so the category swatches rendered in
   --text-muted and CHANGED WITH THE SKIN, which is the one thing the palette
   must never do. Found by this page auditing itself. */
.sg-swatch-val { color: var(--text-muted); margin-left: auto; white-space: nowrap; }

.sg-cat-chip { background: none; margin-left: auto; padding: 2px 7px; border-radius: var(--radius-pill); }

.sg-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(230px, 100%), 1fr)); gap: 10px; }
.sg-grid > .sg-spec { max-width: 640px; }

.sg-audit { display: flex; flex-direction: column; gap: 4px; }
.sg-check {
  display: flex; align-items: center; gap: 9px;
  font-size: 12px; padding: 6px 10px;
  border-radius: var(--radius-sm);
  background: var(--surface-1);
  border: 0.5px solid var(--border);
}
.sg-check.ok i { color: var(--text-success); }
.sg-check.bad { border-color: var(--warn-border); background: var(--warn-bg); }
.sg-check.bad i { color: var(--warn-text); }
.sg-check span { margin-left: auto; color: var(--text-muted); text-align: right; }

/* ---------- SVG icons from the two vendored sets ----------
   See public/icons.js for the prefix scheme. Two render paths, and the
   difference is the whole point of having two sets:

   `.icon-svg` is a MASK. The glyph is a stencil and `currentColor` paints
   through it, so a flat fantasy icon tints with skins and category colours
   exactly as a Tabler icon does. An <img> cannot do that, which is why the
   flat set is not one.

   `.icon-img` is a real image and KEEPS ITS OWN COLOURS. Tinting a full-colour
   illustration destroys the thing you picked it for. */
.icon-svg {
  display: inline-block;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center;  mask-position: center;
  -webkit-mask-size: contain;     mask-size: contain;
  vertical-align: -0.15em;
  flex-shrink: 0;
}
.icon-img {
  display: inline-block;
  object-fit: contain;
  vertical-align: -0.15em;
  flex-shrink: 0;
}

/* ---------- the icon picker ----------
   A curated tab plus two searchable sets. See public/icons.js: the curated list
   is a grid you SCAN, the SVG sets are 8,600 icons you SEARCH — a scroll list
   of that many is not a picker, it is a haystack. */
.icon-picker-tabs { margin-bottom: 6px; }
.icon-picker-search { width: 100%; margin-bottom: 6px; }
.icon-picker-note { margin: 5px 0 0; }
.icon-picker .icon-picker-grid { max-height: 210px; overflow-y: auto; }
/* Licence credit for the two vendored SVG sets — CC BY 3.0 and CC BY-SA 4.0
   both require attribution, so it travels with the picker rather than living in
   a file nobody opens. Shown only while its set is on screen. */
.icon-picker-credit { font-size: 10px; color: var(--text-muted); margin: 4px 0 0; }

/* Icon picker as a floating MODAL (replaces the space-eating inline grid). In an
   edit panel the field is now just a small TRIGGER — the record's current icon —
   and the grid opens on demand, giving the panel its vertical space back. */
.icon-trigger { position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; padding: 0; border-radius: var(--radius-md); border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text-primary); cursor: pointer; }
.icon-trigger:hover { border-color: var(--border-strong); }
.icon-trigger .icon-trigger-hint { position: absolute; right: -5px; bottom: -5px; width: 17px; height: 17px;
  display: inline-flex; align-items: center; justify-content: center; font-size: 10px; border-radius: 50%;
  background: var(--surface-1); border: 1px solid var(--border); color: var(--text-secondary); }
.icon-picker-modal { position: fixed; inset: 0; z-index: 1010; display: none; padding: 16px;
  align-items: center; justify-content: center; background: rgba(8,10,14,0.55); }
.icon-picker-modal.is-open { display: flex; }
.icon-picker-modal-card { width: min(440px, 100%); max-height: 82vh; overflow-y: auto;
  background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: 14px; box-shadow: 0 12px 40px rgba(0,0,0,0.45); }
.icon-picker-modal-head { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-bottom: 10px; font-weight: 600; color: var(--text-primary); }
.icon-picker-modal .icon-picker .icon-picker-grid { max-height: 48vh; }

/* ---------- forbidden / parked, now editable ----------
   Both lists could only arrive by import until now. The actions sit in the
   card's head rather than below its text so the card still reads as a warning
   first and a row of controls second. */
.loot-side-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.loot-side-head h3 { margin: 0; }
.loot-side-actions { display: flex; gap: 2px; flex-shrink: 0; opacity: 0; transition: opacity 0.12s; }
.loot-forbidden-card:hover .loot-side-actions,
.loot-parked-card:hover .loot-side-actions,
.loot-side-actions:focus-within { opacity: 1; }
.loot-side-add { margin-top: 8px; }

/* ---------- dragging a quest step ----------
   The grip is the only handle: making the whole row draggable would fight the
   text input inside it, where you need to select text. */
.step-row { position: relative; }
.step-grip {
  cursor: grab; color: var(--text-muted); display: flex; align-items: center;
  flex-shrink: 0; font-size: 13px;
}
.step-grip:active { cursor: grabbing; }
.step-row:hover .step-grip { color: var(--text-secondary); }
.step-row.is-dragging { opacity: 0.4; }
/* An insertion LINE rather than a highlighted row: the question a drop answers
   is "between which two?", and tinting a row says "onto this one" instead. */
.step-row.drop-above::before,
.step-row.drop-below::after {
  content: ''; position: absolute; left: 0; right: 0; height: 2px;
  background: var(--border-accent); border-radius: var(--radius-pill);
}
.step-row.drop-above::before { top: -1px; }
.step-row.drop-below::after { bottom: -1px; }

/* ---------- the admin award editor ----------
   One table read from either end: grant to an account, or change what gates an
   award for everyone. Rows rather than cards — there are 86 of them and the
   scan is "find the one I mean", not "browse". */
.award-list { display: flex; flex-direction: column; gap: 2px; max-height: 420px; overflow-y: auto; }
.award-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; gap: 10px;
  padding: 7px 10px; border-radius: var(--radius-sm);
  border: 0.5px solid transparent;
}
.award-row:hover { background: var(--surface-1); border-color: var(--border); }
.award-name { display: flex; align-items: center; gap: 6px; min-width: 0; flex-wrap: wrap; font-size: 12px; }
.award-condition { font-size: 11px; color: var(--text-muted); white-space: nowrap; }
.award-actions { display: flex; align-items: center; gap: 4px; justify-content: flex-end; }
.award-owned { font-size: 11px; color: var(--text-success); display: inline-flex; align-items: center; gap: 3px; }
/* An override is a deliberate departure from what shipped, so it says so. */
.award-overridden { color: var(--warn-text); background: var(--warn-bg); }

/* ---------- loadout & skin presets ----------
   Rows, not cards: a preset is a name plus what it holds, and the scan is
   "which one do I want", not "browse the artwork". */
.preset-list { display: flex; flex-direction: column; gap: 4px; max-width: 720px; }
.preset-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 9px 12px;
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-md);
}
.preset-name { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.preset-name b { font-size: 13px; }
.preset-parts { font-size: 11px; color: var(--text-muted); }
.preset-actions { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
.preset-label { flex-shrink: 0; }

/* ============================================================
   THE TASK WIZARD — the crystal-ball Camp panel and its modal.

   The ball is drawn in CSS, not an icon glyph, so the gaze animation lives on
   the same element and it reads as a real orb. It idles with a slow float and
   an inner shimmer (the user's "simple animations that idle and loop"), and the
   click plays a brief brighten-and-spark before the modal opens.
   ============================================================ */
.tw-panel-body { display: flex; }
.tw-panel-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 14px 8px 6px;
}
.tw-ball {
  position: relative;
  width: 96px;
  height: 96px;
  border: none;
  background: none;
  cursor: pointer;
  padding: 0;
  animation: tw-float 5s ease-in-out infinite;
}
.tw-ball-orb {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background:
    radial-gradient(circle at 34% 30%, rgba(255,255,255,0.55), rgba(255,255,255,0) 42%),
    radial-gradient(circle at 68% 74%, rgba(157,192,240,0.6), rgba(157,192,240,0) 55%),
    radial-gradient(circle at 50% 50%, #6f5bd0, #2a2350 78%);
  box-shadow: 0 6px 22px rgba(111,91,208,0.35), inset 0 -8px 18px rgba(0,0,0,0.4);
}
/* A drifting shine across the glass, on a loop offset from the float. */
.tw-ball-shine {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(circle at 30% 26%, rgba(255,255,255,0.7), rgba(255,255,255,0) 30%);
  opacity: 0.5;
  animation: tw-shimmer 6s ease-in-out infinite;
}
.tw-ball-spark {
  position: absolute;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #fff;
  opacity: 0;
  box-shadow: 0 0 6px 1px rgba(255,255,255,0.8);
}
.tw-ball-spark-a { top: 14%; left: 24%; animation: tw-spark 4.5s ease-in-out infinite; }
.tw-ball-spark-b { top: 30%; right: 18%; animation: tw-spark 4.5s ease-in-out 1.4s infinite; }
.tw-ball-spark-c { bottom: 22%; left: 40%; animation: tw-spark 4.5s ease-in-out 2.8s infinite; }
.tw-ball.is-gazing { animation: tw-gaze 0.85s ease-in-out; }
.tw-ball.is-gazing .tw-ball-orb { box-shadow: 0 6px 30px rgba(157,192,240,0.75), inset 0 -8px 18px rgba(0,0,0,0.4); }
.tw-ball.is-gazing .tw-ball-spark { animation: tw-spark-burst 0.85s ease-out; }

@keyframes tw-float { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }
@keyframes tw-shimmer { 0%,100% { opacity: 0.35; transform: translate(0,0); } 50% { opacity: 0.6; transform: translate(3px,2px); } }
@keyframes tw-spark { 0%,80%,100% { opacity: 0; } 88% { opacity: 1; } }
@keyframes tw-spark-burst { 0% { opacity: 0; transform: scale(0.4); } 40% { opacity: 1; transform: scale(1.6); } 100% { opacity: 0; transform: scale(2.2); } }
@keyframes tw-gaze {
  0% { transform: scale(1); }
  35% { transform: scale(1.08) rotate(-3deg); }
  70% { transform: scale(1.03) rotate(2deg); }
  100% { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .tw-ball, .tw-ball-shine, .tw-ball-spark { animation: none; }
  .tw-ball.is-gazing { animation: none; }
}

.tw-tagline { font-size: 12px; color: var(--text-secondary); margin: 0; text-align: center; }
.tw-tagline.is-tidy { color: var(--text-muted); }
.tw-temp-btns { display: flex; gap: 6px; flex-wrap: wrap; justify-content: center; }

/* ---- the modal ---- */
.tw-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(0,0,0,0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}
.tw-modal {
  position: relative;
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 22px;
  width: 100%;
  max-width: 460px;
}
.tw-close {
  position: absolute;
  top: 12px;
  right: 12px;
  background: none;
  border: none;
  color: var(--text-secondary);
  font-size: 15px;
  cursor: pointer;
}
.tw-loading { display: flex; align-items: center; gap: 8px; color: var(--text-secondary); font-size: 13px; margin: 12px 0; }
.tw-spin { display: inline-block; animation: map-locate-spin 0.9s linear infinite; }

.tw-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 2px; }
.tw-title { font-size: 15px; font-weight: 600; color: var(--text-primary); display: inline-flex; align-items: center; gap: 6px; }
.tw-title .ti { color: #b7a4f0; }
.tw-tally { font-size: 11px; color: var(--xp); background: var(--surface-2); border-radius: var(--radius-pill); padding: 2px 8px; }
.tw-tally .ti-coin { color: var(--coin); }
.tw-progress { font-size: 11px; color: var(--text-muted); margin: 0 0 12px; text-transform: uppercase; letter-spacing: 0.03em; }

.tw-card {
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-md);
  padding: 16px;
}
.tw-card.tw-flash { animation: tw-cardflash 0.5s ease-out; }
@keyframes tw-cardflash { 0% { box-shadow: 0 0 0 2px rgba(157,192,240,0.6); } 100% { box-shadow: 0 0 0 0 rgba(157,192,240,0); } }

.tw-asset { display: flex; align-items: center; gap: 10px; padding-bottom: 12px; border-bottom: 0.5px solid var(--border); margin-bottom: 12px; }
.tw-asset-ico { font-size: 20px; color: var(--text-secondary); width: 26px; text-align: center; flex-shrink: 0; }
.tw-asset-text { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.tw-asset-kind { font-size: 10px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
.tw-asset-name { font-size: 13px; font-weight: 600; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tw-reward { font-size: 11px; color: var(--xp); flex-shrink: 0; }
.tw-reward .ti-coin { color: var(--coin); }

.tw-prompt { font-size: 15px; font-weight: 600; color: var(--text-primary); margin: 0 0 3px; }
.tw-hint { font-size: 12px; color: var(--text-secondary); margin: 0 0 12px; }
/* "Something fun" mode */
.tw-fun-list { display: flex; flex-direction: column; gap: 8px; }
.tw-fun-item { display: flex; align-items: center; gap: 10px; padding: 10px 12px;
  background: var(--surface-2); border: 0.5px solid var(--border); border-radius: var(--radius-md); }
.tw-fun-ico { flex-shrink: 0; font-size: 15px; color: var(--border-accent); }
.tw-fun-text { flex: 1; font-size: 13px; color: var(--text-primary); line-height: 1.4; }
.tw-fun-tag { flex-shrink: 0; font-size: 9px; text-transform: uppercase; letter-spacing: 0.05em;
  padding: 2px 7px; border-radius: var(--radius-pill); background: var(--surface-3); color: var(--text-secondary); }
.tw-fun-tag-so-close { background: rgba(224,169,79,0.18); color: #f0c473; }
.tw-fun-tag-in-season { background: rgba(74,157,91,0.15); color: var(--text-success); }
.tw-fun-actions { margin-top: 14px; display: flex; justify-content: center; }
.tw-empty { font-size: 12px; color: var(--text-muted); text-align: center; padding: 12px; }
.tw-input { margin-bottom: 10px; }
.tw-input .tw-text, .tw-input .tw-select { width: 100%; }
.tw-open { font-size: 11px; color: var(--text-muted); display: inline-flex; align-items: center; gap: 4px; }
.tw-open:hover { color: var(--text-secondary); }

.tw-actions { display: flex; align-items: center; gap: 8px; margin-top: 16px; }
.tw-actions .tw-skip { margin-right: auto; }
.tw-actions .tw-fill { min-width: 120px; justify-content: center; }

/* Two remembered filters above the card: "no item needed" and the opt-in
   shelf-life stream. Small, quiet, chip-shaped. */
.tw-filters { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.tw-filter {
  font-size: 11px; color: var(--text-muted);
  background: var(--surface-2); border: 0.5px solid var(--border);
  border-radius: var(--radius-pill); padding: 3px 10px;
  display: inline-flex; align-items: center; gap: 5px; cursor: pointer;
}
.tw-filter:hover { color: var(--text-secondary); }
.tw-filter.is-on { color: var(--accent-fg); border-color: var(--accent-fg); background: var(--surface-1); }

/* A shelf-life review's two answers. Wider than a chip — they're the action. */
.tw-choice-row { display: flex; gap: 8px; }
.tw-choice-btn { flex: 1; justify-content: center; font-size: 13px; }
.tw-choice-btn .ti { margin-right: 4px; }

/* The one-line discoverability pointer on the "all caught up" screen. */
.tw-shelf-tip {
  font-size: 12px; color: var(--text-muted);
  background: none; border: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: 5px; margin: 0 0 12px;
}
.tw-shelf-tip:hover { color: var(--accent-fg); }

.tw-done { text-align: center; padding: 10px 0; }
.tw-done-ico { font-size: 26px; color: #b7a4f0; display: block; margin-bottom: 6px; }
.tw-heading { font-size: 20px; font-weight: 600; margin: 0 0 6px; color: var(--text-primary); }
.tw-done-note { font-size: 13px; color: var(--text-secondary); margin: 0; }
.tw-done-actions { display: flex; gap: 8px; justify-content: center; margin-top: 18px; }

.tw-toast {
  position: fixed;
  left: 50%;
  bottom: 28px;
  transform: translateX(-50%);
  z-index: 1090;
  background: var(--surface-3, #262932);
  color: var(--text-primary);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 8px 16px;
  font-size: 13px;
  box-shadow: 0 6px 22px rgba(0,0,0,0.4);
}
.tw-toast.hidden { display: none; }

/* ---- Quest Wizard screens ---- */
.tw-back { background: none; border: none; color: var(--text-secondary); font-size: 12px; cursor: pointer; padding: 0 0 10px; }
.tw-choices { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 14px; }
.tw-choice {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 16px 8px;
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  cursor: pointer;
  text-align: center;
}
.tw-choice:hover { background: var(--surface-3); border-color: var(--border-accent); }
.tw-choice.is-on { border-color: var(--border-accent); box-shadow: inset 0 0 0 1px var(--border-accent); }
.tw-choice-ico { font-size: 20px; color: #b7a4f0; }
.tw-choice-label { font-size: 13px; font-weight: 600; }
.tw-choice-sub { font-size: 10px; color: var(--text-secondary); }

.tw-qwname-label { display: flex; flex-direction: column; gap: 4px; font-size: 11px; color: var(--text-secondary); margin: 12px 0; text-transform: none; }
.tw-qwname { width: 100%; }
.tw-qwsteps { margin: 0 0 4px; padding-left: 20px; display: flex; flex-direction: column; gap: 8px; }
.tw-qwsteps li { font-size: 13px; color: var(--text-primary); }
.tw-qwsteps li .ti { color: var(--text-muted); margin-right: 4px; }

@media (max-width: 480px) {
  .tw-choices { grid-template-columns: 1fr; }
}

/* ============================================================
   ADVENTURES — the Quests-page tab. A quest with places: plan and manage here,
   walk it on the map. Reuses .detail-panel, .chip and .quest-bar.
   ============================================================ */
.adv-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
.adv-sub { font-size: 12px; color: var(--text-secondary); margin: 0; }
.adv-layout { display: grid; grid-template-columns: 1fr 380px; gap: 16px; align-items: start; }

.adv-list { display: flex; flex-direction: column; gap: 8px; }
.adv-card {
  display: flex; align-items: center; gap: 10px; text-align: left;
  padding: 12px; background: var(--surface-2); border: 0.5px solid var(--border);
  border-radius: var(--radius-md); color: var(--text-primary); cursor: pointer;
}
.adv-card:hover { border-color: var(--border-accent); }
.adv-card.active { border-color: var(--border-accent); box-shadow: inset 0 0 0 1px var(--border-accent); }
.adv-card-ico { width: 30px; height: 30px; border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center; font-size: 15px; flex-shrink: 0; }
.adv-card-main { display: flex; flex-direction: column; gap: 5px; min-width: 0; flex: 1; }
.adv-card-name { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.adv-card-meta { display: flex; align-items: center; gap: 8px; }
.adv-card-prog { font-size: 11px; color: var(--text-muted); }

.adv-detail { align-self: start; }
.adv-d-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.adv-d-name { font-size: 20px; margin: 0 0 4px; }
.adv-d-desc { font-size: 13px; color: var(--text-secondary); margin: 0 0 12px; }
.adv-name-in { width: 100%; font-size: 15px; margin-bottom: 8px; }
.adv-desc-in { width: 100%; margin-bottom: 12px; }

.adv-lifecycle { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
.adv-section { border-top: 0.5px solid var(--border); padding-top: 12px; margin-top: 4px; }
.adv-section-h { font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); margin: 0 0 8px; }
.adv-section-h span { color: var(--text-secondary); }
.adv-hint { font-size: 11px; color: var(--text-muted); margin: 6px 0 0; }
.adv-empty { font-size: 12px; color: var(--text-muted); margin: 0 0 8px; }

.adv-wp-list { list-style: none; margin: 0 0 8px; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.adv-wp { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: var(--surface-2); border-radius: var(--radius-sm); }
.adv-wp.is-done { opacity: 0.7; }
.adv-wp-ico { color: var(--text-muted); }
.adv-wp.is-done .adv-wp-ico { color: var(--text-success); }
.adv-wp-main { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; }
.adv-wp-name { font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.adv-wp.is-done .adv-wp-name { text-decoration: line-through; }
.adv-wp-tag { font-size: 10px; color: var(--text-muted); display: inline-flex; align-items: center; gap: 3px; }

.adv-eff-rows { display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; }
.adv-eff-row { display: grid; grid-template-columns: 92px 64px 1fr auto; gap: 6px; align-items: center; }
.adv-eff-check { display: flex; align-items: center; gap: 8px; padding: 6px 0; font-size: 13px; color: var(--text-primary); cursor: pointer; }
.adv-eff-check.is-on span { color: var(--text-success); }

.adv-gi-group { margin-bottom: 8px; }
.adv-gi-group-h { font-size: 11px; color: var(--text-secondary); margin: 0 0 4px; display: flex; align-items: center; gap: 6px; }
.adv-gi-row { display: flex; align-items: center; gap: 7px; padding: 4px 0; }
/* A fixed icon slot, so the row never reserves room it then leaves empty: a
   missing item or a bare icon falls back to a package glyph inside this box
   rather than a gap the text spills into. */
.adv-gi-ico {
  flex: 0 0 20px; width: 20px; height: 20px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm); background: var(--surface-2);
  color: var(--text-secondary);
}
.adv-gi-ico i, .adv-gi-ico img { font-size: 15px; width: 15px; height: 15px; }
/* The name takes the room and ellipsizes rather than fighting the quantity and
   the select for space — "there isn't enough space for the text" was the row
   letting a fixed-width select crush a flexible name. */
.adv-gi-name { flex: 1; min-width: 0; font-size: 13px; color: var(--text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.adv-gi-qty { flex-shrink: 0; font-size: 12px; color: var(--text-muted); }
.adv-gi-site { flex: 0 1 132px; min-width: 0; }
.adv-gi-check { gap: 7px; }

.adv-wp-num { width: 18px; height: 18px; border-radius: 50%; background: var(--border-accent); color: var(--accent-fg); font-size: 10px; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.adv-wp.is-done .adv-wp-num { background: var(--text-success); }
.adv-wp-actions { display: flex; gap: 2px; flex-shrink: 0; }
.adv-quest-link { font-size: 13px; color: var(--text-primary); }
.adv-quest-link:hover { text-decoration: underline; }

.adv-picker { background: var(--surface-1); border: 0.5px solid var(--border); border-radius: var(--radius-md); padding: 10px; margin-top: 6px; }
.adv-picker-h { font-size: 11px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--text-muted); margin: 0 0 8px; }
.adv-pick-row { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; padding: 8px; background: var(--surface-2); border: 0.5px solid var(--border); border-radius: var(--radius-sm); color: var(--text-primary); cursor: pointer; margin-bottom: 6px; font-size: 13px; }
.adv-pick-row:hover { border-color: var(--border-accent); }
.adv-pick-row span { flex: 1; }

.adv-site-add-row { display: flex; gap: 8px; flex-wrap: wrap; }
.adv-steps { display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; }
.adv-step-row { display: flex; gap: 6px; align-items: center; }
.adv-step-text { flex: 1; }

/* quest tick-time effects reuse the adventure look */
.quest-eff-check { display: flex; align-items: center; gap: 8px; padding: 6px 0; font-size: 13px; color: var(--text-primary); cursor: pointer; }
.quest-eff-check.is-on span { color: var(--text-success); }
.q-eff-row { display: grid; grid-template-columns: 92px 64px 1fr auto; gap: 6px; align-items: center; margin-bottom: 6px; }

@media (max-width: 900px) {
  .adv-layout { grid-template-columns: 1fr; }
}

/* ---- site & adventure-waypoint markers on the map ---- */
.map-site-marker {
  display: flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 6px;
  background: #2a2350; border: 1.5px solid #b7a4f0; color: #d9ccff; font-size: 11px;
  box-shadow: 0 2px 6px rgba(0,0,0,0.45);
}
/* A public site you haven't discovered yet — dimmed, dashed. */
.map-site-marker.undiscovered { background: #23232b; border-style: dashed; border-color: #6b6a80; color: #8a889c; opacity: 0.9; }
/* A private loot site — amber, to read apart from the purple public sites. */
.map-site-marker.is-loot { background: #3a2f14; border-color: #e0b64c; color: #ffe6a3; }
.map-wp-marker.is-loot { background: #c79328; border-color: #fff2cf; }
/* Within reach right now (best-effort, from your last fix). */
.map-site-marker.in-range { box-shadow: 0 0 0 3px rgba(183,164,240,0.45), 0 2px 6px rgba(0,0,0,0.45); }
.map-wp-marker.in-range { box-shadow: 0 0 0 4px rgba(157,192,240,0.55), 0 2px 6px rgba(0,0,0,0.45); }
.map-wp-marker {
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--border-accent); color: var(--accent-fg);
  font-size: 13px; font-weight: 700; border: 2px solid #fff;
  box-shadow: 0 2px 6px rgba(0,0,0,0.45);
}
.map-wp-marker.is-done { background: var(--text-success); border-color: #eafaea; }
.map-popup .map-popup-name { font-weight: 600; font-size: 13px; margin: 0 0 4px; }
.map-popup .map-popup-note { font-size: 12px; color: var(--text-secondary); margin: 0 0 4px; }
.map-popup .map-popup-coords { font-size: 11px; color: var(--text-muted); margin: 0; }
.map-popup .map-loot-edit { display: flex; gap: 6px; align-items: center; margin: 6px 0; }
.map-popup .map-loot-name { flex: 1; min-width: 0; }
.map-popup .map-popup-done { font-size: 12px; color: var(--text-success); margin: 4px 0 0; display: flex; align-items: center; gap: 4px; }
.map-wp-checkoff { margin-top: 6px; }
.map-popup .map-popup-msg { font-size: 12px; margin: 8px 0 0; padding: 6px 8px; border-radius: var(--radius-sm); line-height: 1.45; }
.map-popup .map-popup-msg.is-err { background: rgba(217,139,139,0.16); color: #e0a5a5; }
.map-popup .map-popup-msg.is-ok { background: rgba(127,191,127,0.16); color: var(--text-success); }
.map-popup .map-popup-msg[hidden] { display: none; }
.map-mark-site a { display: flex; align-items: center; justify-content: center; font-size: 15px; }

/* ---- Stops: a deliberately unmistakable marker + its floating panel ---- */
/* A glowing crystal orb that idles with a soft pulse — unlike any other marker. */
.map-stop-wrap { background: none; border: none; }
.map-stop-marker { position: relative; display: grid; place-items: center; width: 40px; height: 40px; }
/* NO GLOW AND NO ANIMATION. There are thousands of stops and a pulsing aura on
   each one cost real frames while adding nothing you could not read from the
   solid orb — the user's call, and the third time in this project that a
   per-element animation turned out to be the expensive part. The marker is now
   a flat shape: identity from the violet fill, state from the ring colour. */
.map-stop-orb {
  position: relative; z-index: 2; display: grid; place-items: center;
  width: 27px; height: 27px; border-radius: 50%; color: #fff;
  background: #7c5cd6;
  border: 2px solid rgba(255,255,255,0.8);
}
/* In range: the ring turns green (go). The violet body keeps the identity. */
.map-stop-marker.in-range .map-stop-orb { border-color: #4fd18b; background: #5aa86f; }
.map-stop-marker.on-cooldown .map-stop-orb { opacity: 0.45; }

/* The floating panel — its own thing, violet-accented, in the map overlay band. */
/* The real-estate "Load stops in this area" pill — floats top-centre over the map
   (inside .main.map-full, which is position:relative). Below the stop panel. */
.map-area-btn {
  position: absolute; z-index: 900; left: 50%; top: 14px; transform: translateX(-50%);
  display: inline-flex; align-items: center; gap: 7px; white-space: nowrap;
  padding: 8px 16px; border-radius: var(--radius-pill);
  background: var(--surface-1); border: 0.5px solid var(--border);
  color: var(--text-primary); font-size: 13px; font-weight: 600;
  box-shadow: 0 4px 16px rgba(0,0,0,0.35); cursor: pointer;
}
.map-area-btn:hover:not(:disabled) { border-color: var(--border-accent); }
.map-area-btn.is-empty { color: var(--text-muted); font-weight: 500; cursor: default; box-shadow: 0 2px 10px rgba(0,0,0,0.25); }
.map-area-btn.hidden { display: none; }
@media (max-width: 860px) { .map-area-btn { font-size: 12px; max-width: calc(100% - 24px); } }

.map-stop-panel {
  position: absolute; z-index: 942; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: min(320px, calc(100% - 48px));
  background: var(--surface-1); border: 0.5px solid var(--border);
  border-top: 3px solid #a78bfa; border-radius: 14px;
  padding: 18px 16px 14px; text-align: center;
  box-shadow: 0 10px 34px rgba(0,0,0,0.45);
}
.map-stop-panel.hidden { display: none; }
.map-stop-close { position: absolute; top: 8px; right: 8px; background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 15px; }
.map-stop-close:hover { color: var(--text-secondary); }
.map-stop-orb-lg {
  display: inline-grid; place-items: center; width: 52px; height: 52px; border-radius: 50%; color: #fff;
  background: radial-gradient(circle at 34% 28%, #e9e0ff, #7c5cd6 72%);
  border: 2px solid rgba(255,255,255,0.7);
  box-shadow: 0 0 16px rgba(150,110,240,0.7), inset 0 0 8px rgba(255,255,255,0.4);
  margin-bottom: 10px;
}
.map-stop-name { font-size: 15px; font-weight: 600; color: var(--text-primary); margin: 0 0 4px; }
.map-stop-cat { font-size: 12px; color: var(--text-secondary); margin: 0 0 10px; }
/* Who manages the ground, in the public-lands layer's own colours. A statement of
   fact about the manager — never a claim that gathering is allowed. */
.map-stop-land { font-size: 11px; margin: -4px 0 10px; color: var(--text-muted); }
.map-stop-land.land-blm { color: #e0a94f; }
.map-stop-land.land-forest { color: #4a9d5b; }
.map-stop-land.land-offlimits { color: #c06b6b; }
.map-stop-status { font-size: 12px; margin: 0 0 12px; display: flex; align-items: center; justify-content: center; gap: 5px; }
.map-stop-here { color: var(--text-success); }
.map-stop-far { color: var(--text-muted); }
.map-stop-actions .map-stop-visit { width: 100%; justify-content: center; }
.map-stop-msg { font-size: 12px; margin: 10px 0 0; padding: 6px 8px; border-radius: var(--radius-sm); line-height: 1.45; }
.map-stop-msg.is-err { background: rgba(217,139,139,0.16); color: #e0a5a5; }
.map-stop-msg.is-ok { background: rgba(127,191,127,0.16); color: var(--text-success); }
.map-stop-msg[hidden] { display: none; }
/* Admin: place a stop from the map chrome; name/move/delete from the panel. */
.map-addstop-btn { margin-top: 8px; }
.map-stop-admin { margin-top: 12px; padding-top: 12px; border-top: 0.5px solid var(--border); text-align: left; }
.map-stop-name-edit { width: 100%; font-size: 13px; margin-bottom: 8px; }
.map-stop-admin-row { display: flex; gap: 6px; align-items: center; }
.map-stop-admin-row .map-stop-rename { margin-right: auto; }

/* ---- recipe tools (non-consumed equipment) ---- */
.section-hint { font-weight: 400; text-transform: none; color: var(--text-muted); font-size: 11px; letter-spacing: 0; }
.recipe-tools { display: flex; flex-wrap: wrap; gap: 6px; }
.tools-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.tool-chip { display: inline-flex; align-items: center; gap: 4px; }
.tool-del { background: none; border: none; color: inherit; cursor: pointer; font-size: 13px; padding: 0 0 0 2px; opacity: 0.7; }
.tool-del:hover { opacity: 1; }
.tools-add { display: flex; gap: 6px; }
.tools-add input { flex: 1; }

/* ---- per-page tutorial ("?" help) ---- */
.tut-fab {
  position: fixed; right: 16px; bottom: 20px; z-index: 900;
  width: 40px; height: 40px; border-radius: var(--radius-pill);
  background: var(--surface-3, #262932); color: var(--text-secondary);
  border: 0.5px solid var(--border); cursor: pointer;
  display: flex; align-items: center; justify-content: center; font-size: 20px;
  box-shadow: 0 2px 10px rgba(0,0,0,0.4);
}
.tut-fab:hover { color: var(--text-primary); }
@media (max-width: 860px) { .tut-fab { bottom: 84px; } } /* clear the capture button */

.tut-overlay {
  position: fixed; inset: 0; z-index: 1000;
  background: rgba(0,0,0,0.55);
  display: flex; align-items: center; justify-content: center; padding: 16px;
}
.tut-modal {
  position: relative; background: var(--surface-1);
  border: 0.5px solid var(--border); border-radius: var(--radius-lg);
  padding: 22px; width: 100%; max-width: 440px;
}
.tut-close { position: absolute; top: 12px; right: 12px; background: none; border: none; color: var(--text-secondary); font-size: 15px; cursor: pointer; }
.tut-title { font-size: 20px; font-weight: 600; margin: 0 0 6px; color: var(--text-primary); display: flex; align-items: center; gap: 8px; }
.tut-title .ti { color: #b7a4f0; }
.tut-intro { font-size: 13px; color: var(--text-secondary); margin: 0 0 12px; }
.tut-points { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 8px; }
.tut-points li { font-size: 13px; color: var(--text-primary); line-height: 1.5; }
.tut-actions { display: flex; justify-content: flex-end; margin-top: 18px; }

/* ============================================================
   FIELD GUIDES — authored how-to guides + the recipe library.
   ============================================================ */
.fg-view { margin-top: 14px; }
.fg-layout { display: grid; grid-template-columns: 300px 1fr; gap: 16px; align-items: start; }
/* Option B: the detail is a modal, so the list column fills the page. */
.fg-layout.fg-modal { display: block; }
.fg-layout.fg-modal .fg-list { max-width: 620px; }
.fg-list { display: flex; flex-direction: column; gap: 8px; }
.fg-card {
  display: flex; align-items: center; gap: 10px; text-align: left;
  padding: 12px; background: var(--surface-2); border: 0.5px solid var(--border);
  border-radius: var(--radius-md); color: var(--text-primary); cursor: pointer;
}
.fg-card:hover { border-color: var(--border-accent); }
.fg-card.active { border-color: var(--border-accent); box-shadow: inset 0 0 0 1px var(--border-accent); }
.fg-card-ico { color: #b7a4f0; flex-shrink: 0; }
.fg-card-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.fg-card-title { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fg-card-sum { font-size: 11px; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.fg-detail { align-self: start; }
.fg-d-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.fg-d-title { font-size: 20px; margin: 0; }
.fg-d-sum { font-size: 13px; color: var(--text-secondary); margin: 4px 0 14px; }
.fg-title-in { width: 100%; font-size: 15px; margin-bottom: 8px; }
.fg-sum-in { width: 100%; margin-bottom: 14px; }

.fg-block { margin-bottom: 16px; }
.fg-block-title { font-size: 15px; margin: 0 0 6px; }
.fg-block-body { font-size: 13px; line-height: 1.7; color: var(--text-secondary); white-space: pre-wrap; margin: 0; }

.fg-block-edit { background: var(--surface-2); border: 0.5px solid var(--border); border-radius: var(--radius-md); padding: 10px; margin-bottom: 10px; }
.fg-block-edit-head { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; }
.fg-b-title { flex: 1; }
.fg-b-body { width: 100%; }
.fg-media-del { background: rgba(0,0,0,0.55); color: #fff; border: none; border-radius: 50%; width: 18px; height: 18px; font-size: 12px; cursor: pointer; position: absolute; top: 2px; right: 2px; line-height: 1; }
.fg-block-edit .fn-obs-media-item { position: relative; }
.fg-add-row { display: flex; gap: 8px; margin: 4px 0 16px; }

.fg-recipe-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr)); gap: 12px; }
.fg-recipe-card {
  display: flex; flex-direction: column; gap: 4px; padding: 14px;
  background: var(--surface-2); border: 0.5px solid var(--border); border-radius: var(--radius-md);
  color: var(--text-primary);
}
.fg-recipe-card:hover { border-color: var(--border-accent); }
.fg-recipe-ico { color: #b7a4f0; font-size: 20px; }
.fg-recipe-name { font-size: 13px; font-weight: 600; }
.fg-recipe-desc { font-size: 12px; color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.fg-recipe-meta { font-size: 11px; color: var(--text-muted); margin-top: 2px; }

@media (max-width: 760px) { .fg-layout { grid-template-columns: 1fr; } }

/* ============================================================================
   BLOCKS — the shared content component (public/blocks.js)
   ----------------------------------------------------------------------------
   One set of rules for written content wherever it appears: guides, recipes,
   quests. Deliberately built on the existing `.ro-block` / `.ro-steps` chrome
   rather than beside it, so a block reads the same as everything else on the
   card it sits in and a page skin still reaches it.

   Every class here is prefixed `.blk-`. That is not a naming preference — this
   is a SHARED component, and the `.view-switch-btn` bug is what a shared class
   costs when a page queries it document-wide. The JS scopes every selector to
   a container and a per-page data prefix; the CSS stays unscoped only because
   nothing here changes layout outside its own block.
   ========================================================================== */

.blk-block { margin-bottom: 14px; }
.blk-list { display: flex; flex-direction: column; gap: 10px; }
.blk-steps { margin: 0; }
.blk-step-text { white-space: pre-wrap; }

/* THE ITEM ON THE LINE. Drawn with the item's own icon through Icons.markup,
   so a step's ingredient looks like the same object it is on the Inventory page
   rather than a generic label. A chip, because it is a fact about the line and
   not a control — the tick is the control. */
.blk-ing {
  display: inline-flex; align-items: center; gap: 5px;
  margin-left: 8px; padding: 2px 8px 2px 5px;
  border-radius: var(--radius-pill); font-size: 11px; white-space: nowrap;
  background: var(--surface-3); color: var(--text-secondary);
  border: 0.5px solid var(--border);
  vertical-align: middle;
  transition: opacity .28s ease, filter .28s ease;
}
.blk-ing-ico { display: inline-flex; align-items: center; line-height: 1; }
.blk-ing-ico i, .blk-ing-ico img { font-size: 13px; }
.blk-ing-qty { font-weight: 600; }
.blk-ing-name { color: var(--text-muted); }
.blk-ing-add .blk-ing-qty { color: var(--xp); }
.blk-ing-spend .blk-ing-qty { color: var(--coin); }
.blk-ing-destroy .blk-ing-qty { color: var(--danger); }

/* SPENT. The line stays legible as a record of what it cost — the whole point
   of having ticked it — but reads unmistakably as used up. */
.blk-ing.is-spent {
  opacity: 0.42;
  filter: grayscale(1);
  border-style: dashed;
}
.blk-ing.is-spent .blk-ing-qty { text-decoration: line-through; color: var(--text-muted); }

/* The consume beat: clones of the chips, drawn in a detached fixed layer at
   their old screen position and flown to the checkbox. Band 940 — a one-shot
   celebration, above the top bar and below modals. The layer never intercepts
   a click, so the next tick is always available. */
.blk-fly-layer { position: fixed; inset: 0; pointer-events: none; z-index: 940; }
.blk-fly {
  position: fixed; pointer-events: none;
  animation: blk-fly-in .5s cubic-bezier(.5, -0.2, .4, 1) forwards;
}
@keyframes blk-fly-in {
  0%   { transform: translate(0, 0) scale(1); opacity: 1; }
  70%  { opacity: 0.9; }
  100% { transform: translate(var(--blk-dx), var(--blk-dy)) scale(0.3); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .blk-fly { animation: none; opacity: 0; } }

/* Tickable steps — a checklist you are working, rather than a recipe you read */
.blk-tickable { list-style: none; padding-left: 0; }
.blk-tickable .blk-step {
  display: flex; align-items: flex-start; gap: 8px;
  font-size: 13px; line-height: 1.6; padding: 3px 0;
}
.blk-tickable .blk-step.done .blk-step-text { text-decoration: line-through; color: var(--text-muted); }
.blk-tick {
  background: none; border: none; padding: 0; cursor: pointer; line-height: 1;
  color: var(--text-secondary); font-size: 15px; flex-shrink: 0;
}
.blk-tick:hover { color: var(--border-accent); }
.blk-step.done .blk-tick { color: var(--border-accent); }

/* ---- the editor ---- */
.blk-edit {
  background: var(--surface-2); border: 0.5px solid var(--border);
  border-radius: var(--radius-md); padding: 10px;
}
.blk-edit-head { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; }
.blk-edit-kind { color: var(--text-muted); font-size: 13px; flex-shrink: 0; }
.blk-b-title { flex: 1; min-width: 0; }
.blk-b-body { width: 100%; }
.blk-add-row { display: flex; gap: 8px; flex-wrap: wrap; margin: 10px 0 14px; }

.blk-items { display: flex; flex-direction: column; gap: 6px; }
.blk-item-row { display: flex; gap: 6px; align-items: center; }
.blk-item-num { font-size: 11px; color: var(--text-muted); width: 16px; flex-shrink: 0; text-align: right; }
.blk-item-text { flex: 1; min-width: 0; }
.blk-ings { display: flex; flex-direction: column; gap: 4px; margin: 4px 0 2px 22px; }
.blk-ing-row { display: flex; gap: 6px; align-items: center; }
.blk-ing-row .blk-ing-op { width: 84px; flex-shrink: 0; }
.blk-ing-row .blk-ing-qty { width: 64px; flex-shrink: 0; }
.blk-ing-row .blk-ing-item { flex: 1; min-width: 0; }

.blk-media-del {
  background: rgba(0,0,0,0.55); color: #fff; border: none; border-radius: 50%;
  width: 18px; height: 18px; font-size: 12px; cursor: pointer;
  position: absolute; top: 2px; right: 2px; line-height: 1;
}
.blk-edit-media .fn-obs-media-item { position: relative; }

@media (max-width: 560px) {
  .blk-ing-row { flex-wrap: wrap; }
  .blk-ing-row .blk-ing-item { flex-basis: 100%; }
}

/* A guide you are WORKING, not just reading — the same bar a recipe has, under
   the word that fits a guide. Both states can be on at once, deliberately. */
.fg-work-bar { display: flex; gap: 10px; align-items: center; margin: 10px 0 16px; flex-wrap: wrap; }
.fg-work-prog { font-size: 12px; color: var(--text-muted); }
.fg-card-flag { color: var(--text-success); font-size: 12px; flex-shrink: 0; margin-left: auto; }

/* ============================================================================
   ASSET SOCIAL — the credit strip on a recipe, field note, journal entry or
   guide. Deliberately built out of the feed's own `.post-act` chrome rather
   than a parallel set: it is the same gesture, so it should be the same button.
   Only the wrapper is new.
   ========================================================================== */
.asoc { border-top: 0.5px solid var(--border); margin-top: 16px; padding-top: 12px; }
.asoc-by { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.asoc-by-name { font-size: 12px; color: var(--text-secondary); cursor: pointer; }
.asoc-profs { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }
.asoc-acts { flex-wrap: wrap; }
.asoc-thread.hidden { display: none; }
.asoc-tag-pick { display: flex; gap: 6px; flex-wrap: wrap; }
.asoc-tag { cursor: pointer; opacity: 0.45; border: 0.5px solid transparent; }
.asoc-tag.is-on { opacity: 1; border-color: var(--border-accent); }

/* ============================================================================
   GATHER + RESERVE — the crafting loop's two new pieces.
   The gather dialog reuses .craft-overlay / .craft-modal, so a craft and a
   gather are the same object wearing different contents.
   ========================================================================== */
.gather-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 18px; }
.gather-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; background: var(--surface-2);
  border: 0.5px solid var(--border); border-radius: var(--radius-md);
}
.gather-row.is-short { border-color: var(--warn-border); background: var(--warn-bg); }
.gather-ico { display: inline-flex; align-items: center; color: var(--text-secondary); flex-shrink: 0; }
.gather-main { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.gather-name { font-size: 13px; font-weight: 600; }
.gather-have { font-size: 11px; color: var(--text-muted); }
.gather-qty { width: 68px; flex-shrink: 0; text-align: right; }
.gather-need { font-size: 11px; color: var(--text-muted); flex-shrink: 0; }
.gather-ok { color: var(--text-success); font-size: 15px; flex-shrink: 0; }
.gather-flag { font-size: 10px; color: var(--warn-text); white-space: nowrap; flex-shrink: 0; }

/* What this run holds. Shown only while making — it is the only time it exists. */
.recipe-reserve { margin: 14px 0 4px; }
.reserve-row { display: flex; gap: 6px; flex-wrap: wrap; }
.reserve-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px 4px 6px; border-radius: var(--radius-pill);
  background: var(--surface-3); border: 0.5px solid var(--border);
  font-size: 11px; white-space: nowrap;
}
.reserve-chip.is-used { opacity: 0.45; filter: grayscale(1); border-style: dashed; }
.reserve-ico { display: inline-flex; align-items: center; }
.reserve-name { color: var(--text-secondary); }
.reserve-count { font-weight: 600; }
.reserve-count s { color: var(--text-muted); font-weight: 400; }

@media (max-width: 560px) {
  .gather-row { flex-wrap: wrap; }
  .gather-main { flex-basis: 100%; order: -1; }
}

/* ============================================================================
   ADVENTURE COMPLETE — the one moment on the Quests page that stops you.
   Band 1000: a modal overlay. Above the celebration band (940) on purpose, so a
   cosmetic Moment plays BEHIND it rather than over the words.
   ========================================================================== */
.adv-done-overlay {
  position: fixed; inset: 0; z-index: 1000;
  background: rgba(0,0,0,0.62);
  display: flex; align-items: center; justify-content: center; padding: 16px;
  animation: adv-done-fade .22s ease-out;
}
.adv-done-overlay.is-leaving { opacity: 0; transition: opacity .2s ease; }
@keyframes adv-done-fade { from { opacity: 0; } to { opacity: 1; } }
.adv-done {
  position: relative; text-align: center;
  background: var(--surface-1); border: 0.5px solid var(--border-accent);
  border-radius: var(--radius-lg); padding: 30px 28px 24px;
  width: 100%; max-width: 400px;
  box-shadow: 0 0 60px -12px var(--glow);
  animation: adv-done-rise .32s cubic-bezier(.2,.9,.3,1.2);
}
@keyframes adv-done-rise { from { transform: translateY(14px) scale(0.96); opacity: 0; } to { transform: none; opacity: 1; } }
.adv-done-burst {
  width: 64px; height: 64px; margin: 0 auto 14px;
  border-radius: 50%; display: flex; align-items: center; justify-content: center;
  background: var(--bg-success); color: var(--text-success); font-size: 26px;
  animation: adv-done-pulse 1.6s ease-in-out infinite;
}
@keyframes adv-done-pulse { 0%,100% { box-shadow: 0 0 0 0 var(--bg-success); } 50% { box-shadow: 0 0 0 14px transparent; } }
.adv-done-kicker {
  font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-muted); margin: 0 0 6px;
}
.adv-done-name { font-size: 20px; margin: 0 0 8px; }
.adv-done-sub { font-size: 12px; color: var(--text-secondary); margin: 0 0 20px; }
@media (prefers-reduced-motion: reduce) {
  .adv-done, .adv-done-overlay, .adv-done-burst { animation: none; }
}
/* The editor's inert tick — shows what a steps block becomes when you work it.
   Without it, a steps block is a column of text inputs and reads as prose. */
.blk-item-ghost { color: var(--text-muted); font-size: 13px; line-height: 1; flex-shrink: 0; opacity: 0.6; }

/* The quest run bar — start/stop and what you are carrying for it. Same idea as
   the recipe craft bar; a quest that costs something should show what it holds. */
.quest-run-bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 12px; margin-bottom: 14px;
  background: var(--surface-2); border: 0.5px solid var(--border); border-radius: var(--radius-md);
}
.quest-run-bar.is-running { border-color: var(--border-accent); }
.quest-run-state { font-size: 11px; color: var(--text-success); display: inline-flex; align-items: center; gap: 5px; }
.quest-run-bar .reserve-row { flex-basis: 100%; }
/* The map-post kind picker's reminder line, and the observation form's
   conditional "new field note name" field. */
.qc-kind-note { display: flex; align-items: center; gap: 6px; }
.qc-obs-newname { transition: none; }

/* ============================================================================
   RECORD HISTORY — the Jira/Confluence-shaped timeline. Reuses the craft modal
   shell, so a history and a gather are the same object with different contents.
   ========================================================================== */
.hist-link {
  background: none; border: none; cursor: pointer; padding: 4px 0;
  color: var(--text-muted); font-size: 11px;
  display: inline-flex; align-items: center; gap: 5px;
}
.hist-link:hover { color: var(--text-secondary); }
.hist-modal { max-width: 560px; }
.hist-list { list-style: none; margin: 0; padding: 0; }
.hist-row {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 9px 0; border-bottom: 0.5px solid var(--border);
}
.hist-row:last-child { border-bottom: none; }
.hist-ico {
  width: 24px; height: 24px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%; background: var(--surface-2);
  color: var(--text-secondary); font-size: 12px;
}
.hist-coin .hist-ico { color: var(--coin); }
.hist-xp .hist-ico { color: var(--xp); }
.hist-complete .hist-ico { color: var(--text-success); }
.hist-copied-by .hist-ico { color: var(--border-accent); }
.hist-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.hist-what { font-size: 12px; }
.hist-what em { color: var(--text-muted); font-style: normal; font-size: 11px; }
.hist-sub { font-size: 11px; color: var(--text-muted); overflow-wrap: anywhere; }
.hist-when { font-size: 11px; color: var(--text-muted); white-space: nowrap; flex-shrink: 0; }
.hist-link-row { margin-top: 6px; }

/* ============================================================================
   CAMP: A CHOSEN LAYOUT, FILLED WITH PANELS (public/camp-layout.js)
   ----------------------------------------------------------------------------
   The grid's columns, rows and template-areas are all set from JS, because the
   layout is a CHOICE from an explicit catalogue rather than a fixed shape. What
   lives here is everything that is true of every layout — chiefly the
   responsive standard, which is the reason the catalogue exists at all:

     wide   (>1100px)  the layout's own grid, exactly as drawn
     medium (860-1100) two columns, slots in reading order
     phone  (<860px)   one column, slots in reading order

   Because slot order is authored (a, b, c…), "in order" is always sensible and
   no layout needs a special case. Free-form spans could make no such promise,
   which is why they went.
   ========================================================================== */
.camp-grid {
  display: grid;
  gap: 12px;
  align-items: stretch;
}
.camp-grid > .camp-card {
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.camp-grid > .camp-card.is-hidden-panel { display: none; }

.camp-arrange-row { display: flex; gap: 8px; margin-left: auto; }

/* MEDIUM AND PHONE: the authored areas stop applying and the grid falls back to
   flow, which is why camp-layout.js also sets `order` on every placed card.
   Heights go back to the content — a fixed row unit on a narrow screen is a
   scroll trap, and the map is the only panel that needs a floor. */
@media (max-width: 1100px) {
  .camp-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    grid-template-areas: none !important;
    grid-template-rows: none !important;
    grid-auto-rows: minmax(190px, auto);
  }
  .camp-grid > .camp-card { grid-area: auto !important; }
}
@media (max-width: 860px) {
  .camp-grid {
    grid-template-columns: minmax(0, 1fr) !important;
    grid-auto-rows: auto;
  }
  .camp-grid > .camp-card { min-height: 190px; }
}

/* ---- the arrange dialog ---- */
.cl-modal { max-width: 640px; }
.cl-layouts { display: flex; flex-direction: column; gap: 12px; }
.cl-group { display: flex; flex-direction: column; gap: 5px; }
.cl-group-n {
  font-size: 9px; text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--text-muted);
}
.cl-group-row { display: flex; gap: 8px; flex-wrap: wrap; }

/* A layout preview is the layout, drawn small. Nothing explains a grid shape
   like the shape. */
.cl-prev {
  background: none; border: 0.5px solid var(--border); border-radius: var(--radius);
  padding: 6px; cursor: pointer; width: 92px;
  display: flex; flex-direction: column; gap: 5px; align-items: stretch;
}
.cl-prev:hover { border-color: var(--border-strong); }
.cl-prev.active { border-color: var(--border-accent); box-shadow: inset 0 0 0 1px var(--border-accent); }
.cl-prev-grid { display: grid; gap: 2px; height: 46px; }
.cl-prev-cell { background: var(--surface-3); border-radius: 2px; }
.cl-prev.active .cl-prev-cell { background: var(--border-accent); }
.cl-prev-label { font-size: 9px; color: var(--text-muted); text-align: center; }

.cl-slots { display: flex; flex-direction: column; gap: 6px; }
.cl-slot { display: flex; align-items: center; gap: 8px; }
.cl-slot select { flex: 1; min-width: 0; }
.cl-slot-tag {
  width: 22px; height: 22px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm); background: var(--surface-3);
  font-size: 10px; color: var(--text-secondary);
}
.cl-hint { font-size: 11px; color: var(--text-muted); margin: 8px 0 0; }

/* The feed filter, as one control instead of eleven. Same on Camp and
   Socialize — they are the same component, and a filter that reads one way in
   one place and another way elsewhere is two answers to one question. */
.town-filter {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 8px; border-radius: var(--radius);
  background: var(--surface-2); border: 0.5px solid var(--border);
  color: var(--text-muted); font-size: 12px;
}
.town-filter select {
  background: none; border: none; padding: 3px 0;
  color: var(--text-primary); font-size: 12px; min-width: 96px;
}

/* ============================================================================
   COLLAPSIBLE DETAIL RAILS (SharedBrowse.Rail)
   ----------------------------------------------------------------------------
   Only rails collapse: Inventory (230px) and Quests (380px), where the list is
   what you scan and the record is a preview of one row. Spellbook, Journal and
   Field Guides are NOT rails — the record is what the page is for — so they
   have no control here.

   Collapsed leaves a thin edge strip carrying the record's name, because a
   panel that vanishes entirely takes with it the only thing on screen saying
   which row is selected.
   ========================================================================== */
.rail-collapsed > .detail-panel,
.rail-collapsed > .inventory-side { display: none; }

/* ONE toggle, always present, always a sibling of the panel. Open it is a
   slim spine; collapsed it carries the record's name. */
.rail-toggle {
  flex: 0 0 auto; align-self: stretch;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  width: 20px; padding: 10px 0;
  background: var(--surface-2); border: 0.5px solid var(--border);
  border-radius: var(--radius-md); cursor: pointer;
  color: var(--text-muted); font-size: 12px;
}
.rail-toggle.is-collapsed { width: 34px; }
.rail-toggle:hover { border-color: var(--border-accent); color: var(--text-primary); }
.rail-strip-label {
  writing-mode: vertical-rl; text-orientation: mixed;
  font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-height: 260px;
}
/* On a phone the layouts already stack, so a vertical edge strip would be a
   34px-wide bar across a 375px screen. It goes back to a normal row. */
@media (max-width: 860px) {
  .rail-toggle, .rail-toggle.is-collapsed { width: 100%; flex-direction: row; justify-content: center; padding: 8px; }
  .rail-strip-label { writing-mode: horizontal-tb; max-height: none; }
}

/* ============================================================================
   THE FLOATING DETAIL CARD (public/detailcard.js)
   ----------------------------------------------------------------------------
   A record opened from somewhere that isn't its own page. Band 1000 — a modal
   overlay taking the whole screen, below toasts so a result still reports
   itself over the card.
   ========================================================================== */
.dc-overlay {
  position: fixed; inset: 0; z-index: 1000;
  background: rgba(0,0,0,0.55);
  display: none; align-items: center; justify-content: center; padding: 20px;
}
.dc-overlay.open { display: flex; }
.dc-card {
  position: relative;
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-lg);
  width: 100%; max-width: 780px; max-height: 88vh;
  display: flex; flex-direction: column;
}
.dc-head {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 18px 20px 12px;
  border-bottom: 0.5px solid var(--border);
}
.dc-titles { flex: 1; min-width: 0; }
.dc-title { font-size: 20px; margin: 0; }
.dc-sub { font-size: 12px; color: var(--text-secondary); margin: 3px 0 0; }
.dc-close {
  background: none; border: none; cursor: pointer;
  color: var(--text-secondary); font-size: 20px; line-height: 1; padding: 0 2px;
}
.dc-close:hover { color: var(--text-primary); }
/* Only the BODY scrolls, so the title and the close button stay reachable in a
   long recipe — the whole reason this is a card and not a page. */
.dc-body { overflow-y: auto; padding: 16px 20px 20px; }

@media (max-width: 640px) {
  .dc-overlay { padding: 0; }
  .dc-card { max-width: none; max-height: 100vh; height: 100vh; border-radius: 0; }
}

/* ---------------- System Catalogue ----------------
   Reference records that belong to no account. Deliberately its own page and its
   own look: nothing here is editable, so nothing here should look editable. */
.sysl-banner {
  font-size: 12px; color: var(--text-secondary);
  background: var(--surface-2); border: 0.5px solid var(--border);
  border-left: 3px solid var(--border-accent);
  border-radius: var(--radius-md); padding: 10px 12px; margin-bottom: 12px;
}
.sysl-banner strong { color: var(--text-primary); }
.sysl-tabnum { font-size: 10px; color: var(--text-muted); margin-left: 3px; }
.sysl-count { font-size: 11px; color: var(--text-muted); margin-left: auto; }
.sysl-card { cursor: pointer; }
.sysl-card-head { display: flex; justify-content: flex-end; margin-bottom: 4px; }
.sysl-name { font-size: 13px; font-weight: 600; color: var(--text-primary); margin: 0 0 2px; }
.sysl-sci { font-size: 11px; font-style: italic; color: var(--text-muted); margin: 0 0 6px; }
.sysl-where { font-size: 10px; color: var(--text-secondary); margin: 0 0 7px; letter-spacing: 0.02em; }
.sysl-marks { display: flex; flex-wrap: wrap; gap: 4px; }
.sysl-flag-p { color: #c06b6b; }
.sysl-flag-k { color: var(--text-success); }
.sysl-flag-e { color: #a78bfa; }
.sysl-detail {
  margin-top: 12px; position: relative;
  background: var(--surface-1); border: 0.5px solid var(--border);
  border-radius: var(--radius-md); padding: 16px;
}
.sysl-detail.hidden { display: none; }
.sysl-close { position: absolute; top: 10px; right: 10px; background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 15px; }
.sysl-detail-name { font-size: 20px; font-weight: 600; color: var(--text-primary); margin: 0 0 2px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sysl-note { font-size: 12px; color: var(--text-secondary); margin: 8px 0 12px; max-width: 70ch; line-height: 1.5; }
.sysl-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 8px; font-size: 12px; color: var(--text-secondary); }
.sysl-label { display: block; font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); margin-bottom: 1px; }
.sysl-season { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 12px; }
.sysl-month { font-size: 9px; padding: 2px 6px; border-radius: var(--radius-pill); background: var(--surface-2); color: var(--text-muted); }
.sysl-month.on { background: var(--border-accent); color: var(--accent-fg); }
.sysl-id { font-size: 10px; color: var(--text-muted); margin: 10px 0 0; font-family: ui-monospace, monospace; }

/* Admin editor on the catalogue detail panel. Plain stacked fields — reuses the
   base input/select styling; only layout lives here. */
.sysl-detail-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.sysl-danger { color: var(--danger); }
.sysl-form { display: flex; flex-direction: column; gap: 10px; margin-top: 8px; }
.sysl-f { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.sysl-f > span { font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); }
.sysl-form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.sysl-flags { display: flex; flex-wrap: wrap; gap: 10px 16px; }
.sysl-chk { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-secondary); }

/* Two controls in a page header: keep them on one row, wrap on a phone. */
.page-header-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

/* ---------------- System Dex ----------------
   The Pokedex view of the system catalogue, shared by Field Notes, the Spellbook
   and the Loot Table. An undiscovered card shows a "?" and nothing else — the
   server never sent anything else, so there is nothing here to leak. */
.system-dex.hidden { display: none; }
.dex-status { margin-bottom: 12px; }
.dex-status-line { font-size: 13px; color: var(--text-secondary); display: flex; align-items: center; gap: 6px; }
.dex-status-line strong { color: var(--text-primary); font-size: 15px; }
.dex-pct { margin-left: auto; font-size: 12px; color: var(--text-muted); }
.dex-bar { height: 5px; border-radius: var(--radius-pill); background: var(--surface-2); margin-top: 6px; overflow: hidden; }
.dex-bar > span { display: block; height: 100%; background: var(--border-accent); border-radius: var(--radius-pill); }
.dex-card { cursor: pointer; }
.dex-locked {
  cursor: default; display: grid; place-items: center; gap: 4px;
  min-height: 104px; border-style: dashed; opacity: 0.65;
}
.dex-qmark { font-size: 26px; font-weight: 700; color: var(--text-muted); line-height: 1; }
.dex-locked-label { font-size: 10px; color: var(--text-muted); margin: 0; text-transform: uppercase; letter-spacing: 0.04em; }
/* The copy action on a dex card: the point of the system views, so it sits on the
   card rather than only in the detail. */
.dex-card .btn-xs, .dex-card .dex-open { margin-top: 8px; width: 100%; justify-content: center; }
/* When a card offers more than one copy action, the wrapper is transparent — the
   buttons stack full-width on a card (via the rule above) and flow inline in the
   detail float's action row, without a nested box fighting either layout. */
.dex-copy-actions { display: contents; }
.dex-detail-actions { margin-top: 12px; }
.dex-open { display: inline-flex; align-items: center; gap: 4px; text-decoration: none; }
.fn-system-origin { font-size: 11px; color: var(--text-muted); margin-top: 3px; display: flex; align-items: center; gap: 4px; }

/* ---------------- Coverage heat map + the admin candidate list ----------------
   A DEBUG LAYER with its own switch: it recolours every stop, so it must never
   be something you end up wearing without meaning to. Bands come from the real
   distribution (min 23 · median 224 · max 613), not an even split. */
.map-addstop-btn.is-on { border-color: var(--border-accent); color: var(--text-primary); }
.map-stop-marker.heat-0 .map-stop-orb { background: #4b5563; }
.map-stop-marker.heat-1 .map-stop-orb { background: #2f6f8f; }
.map-stop-marker.heat-2 .map-stop-orb { background: #4a9d5b; }
.map-stop-marker.heat-3 .map-stop-orb { background: #e0a94f; }
.map-stop-marker.heat-4 .map-stop-orb { background: #c0563f; }
.map-stop-marker.heat-none .map-stop-orb { background: #2a2f3a; border-style: dashed; }

.map-stop-dex { border-top: 0.5px solid var(--border); margin-top: 10px; padding-top: 9px; text-align: left; }
.map-stop-dex-loading { font-size: 11px; color: var(--text-muted); margin: 0; }
.map-stop-dex-axes { font-size: 10px; color: var(--text-muted); margin: 0 0 5px; line-height: 1.4; }
.map-stop-dex-count { font-size: 12px; color: var(--text-secondary); margin: 0 0 6px; }
.map-stop-dex-count b { color: var(--text-primary); font-size: 15px; }
.map-stop-dex-season { color: var(--text-muted); font-size: 10px; margin-left: 5px; }
.map-stop-dex-tally { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 7px; }
.map-stop-dex-list { max-height: 240px; overflow-y: auto; margin-top: 7px; }
.map-stop-dex-list.hidden { display: none; }
.map-stop-dex-row { display: flex; align-items: center; gap: 6px; padding: 3px 0; font-size: 11px; text-decoration: none; color: var(--text-secondary); }
.map-stop-dex-row:hover { color: var(--text-primary); }
.map-stop-dex-name.off-season { opacity: 0.5; }

/* ---------------- The floating detail / reward card ----------------
   Details float over the page instead of sitting under a thousand-card grid, and
   the loot box reuses the same card so an unlock looks like what you browse.
   Map z-band [900,949] — above markers and popups, below modals. */
/* Above the map shell's centre menu button (971) so a loot-box reward isn't
   pierced by it; still below true modals (1000). */
.dex-float { position: fixed; inset: 0; z-index: 980; display: none; align-items: center; justify-content: center;
  background: rgba(8,10,14,0.66); padding: 20px; overflow-y: auto; }
.dex-float.is-open { display: flex; }
.dex-float-card { position: relative; width: min(400px, calc(100% - 24px)); background: var(--surface-1);
  border: 1px solid var(--border); border-radius: 16px; padding: 18px; box-shadow: 0 18px 48px rgba(0,0,0,0.5); }
.dex-float-card.lr-legendary { border-color: #e0a94f; box-shadow: 0 0 22px rgba(224,169,79,0.28), 0 18px 48px rgba(0,0,0,0.5); }
.dex-float-card.lr-rare { border-color: #7c9ff0; box-shadow: 0 0 18px rgba(124,159,240,0.22), 0 18px 48px rgba(0,0,0,0.5); }
.dex-float-card.lr-uncommon { border-color: #4a9d5b; }
.dex-float-headline { font-size: 15px; font-weight: 600; color: var(--text-primary); margin: 0 0 12px; text-align: center; }
/* The just-learned line, mid-reveal: highlighted, and .fx-pop does the wiggle+slam. */
.dex-reveal { color: var(--text-success); }
.reward-earn { display: flex; justify-content: center; gap: 18px; font-size: 15px; color: var(--text-secondary); margin: 12px 0 12px; }
.reward-earn i { color: var(--coin); }
.reward-earn i.ti-sparkles { color: var(--xp); }
.dex-sentinel { text-align: center; font-size: 10px; color: var(--text-muted); padding: 8px 0; min-height: 8px; }

/* Discovery progress on a dex card and its detail. */
.dex-level { font-size: 10px; color: var(--text-muted); background: var(--surface-2); border-radius: var(--radius-pill); padding: 1px 6px; }
.dex-level.is-complete { color: var(--text-success); }
.dex-card.dex-partial { border-style: dashed; }
.dex-unknown { color: var(--text-muted); opacity: 0.6; font-style: italic; font-size: 11px; }
.dex-detail-progress { font-size: 12px; color: var(--text-secondary); margin: 4px 0 10px; }
.dex-detail-hint { color: var(--text-muted); font-size: 11px; }
.sysl-season-unknown { margin-top: 12px; }

/* ---------------- Observation status (the science axis of the dex) ---------------- */
.dex-seen { color: var(--text-success); font-size: 12px; }
.dex-status-obs { font-size: 11px; color: var(--text-success); margin-left: 8px; }
.dex-observed { font-size: 12px; color: var(--text-success); background: rgba(74,157,91,0.1);
  border-radius: var(--radius-md); padding: 7px 9px; margin: 10px 0; display: block; }
.dex-observed b { color: var(--text-primary); }
.dex-obs-notes { font-size: 11px; color: var(--text-secondary); margin-top: 3px; }
.dex-obs-link { color: var(--text-secondary); text-decoration: underline; }
.dex-obs-link:hover { color: var(--text-primary); }
.dex-detail-actions { display: flex; flex-wrap: wrap; gap: 6px; }

/* ---- the harvest axis (yields) ---- */
.dex-yields { margin: 10px 0; padding: 8px 9px; border-radius: var(--radius-md);
  background: rgba(224,169,79,0.08); border: 1px solid rgba(224,169,79,0.22); }
.dex-yields-head { font-size: 11px; color: var(--text-secondary); text-transform: uppercase;
  letter-spacing: 0.04em; margin: 0 0 6px; display: flex; align-items: center; gap: 5px; }
.dex-yields-list { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 8px; }
.dex-yield-chip { display: inline-flex; align-items: center; gap: 5px; font-size: 12px;
  color: var(--text-primary); background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-md); padding: 3px 8px; }
.dex-yield-chip .icon-svg { background: currentColor; }
.dex-harvest-form { display: flex; flex-direction: column; gap: 4px; }
.dex-harvest-row { display: flex; align-items: center; gap: 8px; font-size: 13px;
  padding: 4px 2px; }
.dex-harvest-row .dex-harvest-ic { display: inline-flex; }
.dex-harvest-row .dex-harvest-ic .icon-svg { background: currentColor; }
.dex-harvest-name { flex: 1; }
.dex-harvest-qty, .dex-hq { width: 58px; }
.dex-harvest-note { font-size: 11px; color: var(--text-muted); margin: 4px 0 2px; }
.dex-harvest-actions { display: flex; gap: 6px; margin-top: 4px; }
.dex-harvest-result { color: var(--danger); margin-top: 4px; }
.dex-harvest-done { font-size: 13px; color: var(--text-success); }
.dex-harvest-done b { color: var(--text-primary); }
.dex-yields-hint { font-size: 11px; color: var(--text-muted); margin: 6px 0 0; }

/* Community field notes — every OTHER account's notes for this specimen (read-only).
   Empty (no community notes) collapses to nothing so the card gains no chrome. */
.dex-community:empty { display: none; }
.dex-community-inner { margin: 10px 0; padding: 8px 9px; border-radius: var(--radius-md);
  background: var(--surface-2); border: 1px solid var(--border); text-align: left; }
.dex-community-head { font-size: 11px; color: var(--text-secondary); text-transform: uppercase;
  letter-spacing: 0.04em; margin: 0 0 7px; display: flex; align-items: center; gap: 5px; }
.dex-community-count { margin-left: auto; font-size: 11px; color: var(--text-muted);
  background: var(--surface-1); border-radius: 999px; padding: 1px 7px; }
.dex-community-list { display: flex; flex-direction: column; gap: 8px; }
.dex-comm-row { border-top: 1px solid var(--border); padding-top: 7px; }
.dex-comm-row:first-child { border-top: 0; padding-top: 0; }
.dex-comm-who { display: flex; align-items: center; gap: 6px; font-size: 12px;
  color: var(--text-primary); font-weight: 600; }
.dex-comm-friend { font-size: 10px; font-weight: 500; color: var(--text-secondary);
  background: var(--surface-1); border-radius: 999px; padding: 0 6px; }
.dex-comm-desc { font-size: 12px; color: var(--text-secondary); margin: 4px 0 0; }
.dex-comm-obs { font-size: 12px; color: var(--text-primary); margin: 4px 0 0; font-style: italic; }
.dex-comm-date { font-style: normal; color: var(--text-muted); font-size: 11px; }
.dex-comm-thumbs { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; }
.dex-comm-thumb { width: 46px; height: 46px; object-fit: cover; border-radius: var(--radius-sm);
  cursor: pointer; display: block; }

/* Inventory "New item" fork menu (harvest vs blank) */
.inv-add-menu { position: fixed; z-index: 1050; min-width: 230px; padding: 6px;
  display: flex; flex-direction: column; gap: 4px; background: var(--surface-1);
  border: 1px solid var(--border); border-radius: var(--radius-md); box-shadow: 0 12px 32px rgba(0,0,0,0.4);
  /* Never wider than the screen, never taller than it — a tall menu (5 options) on a
     short phone must scroll, not run its last option (Container) off the bottom where
     it can't be tapped. JS also clamps its top/right into the viewport. */
  max-width: calc(100vw - 16px); max-height: calc(100svh - 24px); overflow-y: auto; }
.inv-add-opt { display: flex; align-items: center; gap: 10px; text-align: left;
  padding: 8px 10px; background: none; border: 0; border-radius: var(--radius-sm);
  color: var(--text-primary); cursor: pointer; }
.inv-add-opt:hover { background: var(--surface-2); }
.inv-add-opt i { font-size: 20px; color: var(--border-accent); }
.inv-add-opt span { display: flex; flex-direction: column; }
.inv-add-opt b { font-size: 13px; }
.inv-add-opt small { font-size: 11px; color: var(--text-secondary); }
/* The map's "Add marker" chooser reuses .inv-add-menu / .inv-add-opt and adds a
   heading and a footnote (loot sites are placed from an adventure, not here). */
.map-add-title { font-size: 11px; color: var(--text-muted); margin: 0; padding: 2px 8px 4px; }
.map-add-note {
  font-size: 11px; color: var(--text-secondary); margin: 4px 0 0; padding: 6px 10px 2px;
  border-top: 0.5px solid var(--border); display: flex; align-items: center; gap: 6px;
}
.map-add-note i { font-size: 13px; color: var(--text-muted); flex-shrink: 0; }
.map-add-note a { color: var(--text-accent); }

/* ---------------- Dex category icons + observe-forward card header ----------------
   The category icon is big and top-left on every card, colour-keyed per group so a
   plant reads differently from a mineral at a glance — high-visibility, and the one
   signal a locked card can honestly give. */
.dex-card-top { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 6px; }
.dex-cat { display: grid; place-items: center; width: 34px; height: 34px; border-radius: var(--radius-md);
  font-size: 20px; background: var(--surface-2); color: var(--text-secondary); }
.dex-cat[data-group="plants"]   { color: #4a9d5b; background: rgba(74,157,91,0.12); }
.dex-cat[data-group="fungi"]    { color: #c07b4a; background: rgba(192,123,74,0.12); }
.dex-cat[data-group="minerals"] { color: #7c9ff0; background: rgba(124,159,240,0.12); }
.dex-cat[data-group="insects"]  { color: #e0a94f; background: rgba(224,169,79,0.12); }
.dex-cat[data-group="animals"]  { color: #b98bd6; background: rgba(185,139,214,0.12); }
.dex-cat[data-group="marine"]   { color: #4bb0c0; background: rgba(75,176,192,0.12); }
.dex-cat[data-group="basics"]   { color: var(--text-muted); }
.dex-locked .dex-cat { opacity: 0.5; }
.dex-card-badges { display: flex; align-items: center; gap: 6px; }
.dex-qmark-badge { font-size: 20px; font-weight: 700; color: var(--text-muted); }
.dex-rarity-tag { font-size: 9px; }
/* OBSERVE is the headline now. Filled + green when logged; a hollow prompt when not. */
.dex-seen { color: var(--text-success); font-size: 15px; }
.dex-unseen { color: var(--text-muted); opacity: 0.55; font-size: 15px; }
.dex-card:hover .dex-unseen { opacity: 0.9; }

/* The big top-centre category icon on the floating detail / reward card — the
   "wait, what is this?" moment. Colour OpenMoji, centred and large, with the
   kind label ("Snake", "Mineral") right under it answering the question. */
.dex-float-icon { display: grid; place-items: center; width: 68px; height: 68px; margin: 0 auto 4px;
  border-radius: 50%; background: var(--surface-2); }
.dex-float-icon .icon-img, .dex-float-icon .icon-svg { width: 52px !important; height: 52px !important; }
.dex-float-kind { text-align: center; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--text-muted); margin: 0 0 6px; }
.dex-cat .icon-img, .dex-cat .icon-svg { display: block; }

/* ---------------- Dex sub-category filter bar (the taxonomy merge, browse side) ---------------- */
/* The Codex Pokédex state bar — scopes the whole dex to one region's catalogue.
   Sits above the dex module's own status line + controls. */
.dex-statebar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px; margin: 0 0 12px; }
.dex-statebar .dex-statesel { flex: 0 0 auto; }
.dex-statebar .dex-statesel select { min-width: 160px; }
.dex-statebar .btn-sm { margin-left: auto; }
.dex-controls { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px; margin: 4px 0 10px; }
.dex-ctl { display: flex; flex-direction: column; gap: 3px; }
.dex-ctl > span { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
.dex-ctl select { font-size: 12px; padding: 5px 8px; color: var(--text-secondary); }
.dex-sortdir { display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0; color: var(--text-secondary); }
.dex-sortdir:hover { color: var(--text-primary); }
.dex-catbar { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 12px; }
.dex-catchip { display: inline-flex; align-items: center; gap: 5px; font-size: 11px;
  padding: 4px 10px; border-radius: var(--radius-pill); border: 0.5px solid var(--border);
  background: var(--surface-1); color: var(--text-secondary); cursor: pointer; }
.dex-catchip:hover { border-color: var(--border-strong); color: var(--text-primary); }
.dex-catchip.is-on { background: var(--border-accent); border-color: var(--border-accent); color: var(--accent-fg); }
.dex-catchip .icon-img, .dex-catchip .icon-svg { width: 14px !important; height: 14px !important; }
.dex-catchip-n { font-size: 9px; opacity: 0.7; }

/* A field note's inherited system category, on its card. */
.chip.fn-syscat { display: inline-flex; align-items: center; gap: 4px; }
.chip.fn-syscat .icon-img, .chip.fn-syscat .icon-svg { width: 12px !important; height: 12px !important; }

/* ── The SYSTEM library (systemlib.js) — seeded recipes/quests, read in full ── */
.syslib-lead { font-size: 13px; color: var(--text-secondary); margin: 0 0 8px; }
.syslib-lead b { color: var(--text-primary); }
.syslib-have { font-size: 11px; color: var(--text-muted); margin: 0 0 8px; }

/* Two-pane the library builds for itself (the Quests page). The Spellbook reuses
   its own rail + card, so it never renders this wrapper. */
.syslib-pane { display: flex; gap: 20px; align-items: flex-start; }
.syslib-rail { width: 240px; flex-shrink: 0; }
.syslib-detail { flex: 1; min-width: 0; }

/* The search sitting atop the rows, styled like the recipe rail's own search. */
.syslib-search { width: 100%; margin-bottom: 8px; }
.syslib-rows { display: flex; flex-direction: column; gap: 2px; max-height: 520px; overflow-y: auto; }

/* A rail row — deliberately the shape of a recipe/quest row so the System tab
   reads as more of the same list. */
.syslib-row {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  padding: 8px 10px; border-radius: var(--radius); border: 0.5px solid transparent;
  background: transparent; color: var(--text-secondary); cursor: pointer;
}
.syslib-row:hover { background: var(--surface-2); }
.syslib-row.active { background: var(--surface-1); border-color: var(--border); color: var(--text-primary); }
.syslib-row-ico { flex: 0 0 auto; display: inline-flex; color: var(--border-accent); }
.syslib-row-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.syslib-row-name { font-size: 13px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.syslib-row-profs { display: flex; flex-wrap: wrap; gap: 3px; }
.syslib-row-profs .chip { font-size: 9px; padding: 1px 5px; }
.syslib-row-mine { flex: 0 0 auto; color: var(--text-success); font-size: 13px; }

.syslib-card { padding: 16px; }
.syslib-head { display: flex; align-items: flex-start; gap: 12px; }
.syslib-icon { flex: 0 0 auto; color: var(--border-accent); }
.syslib-title { flex: 1 1 auto; min-width: 0; }
.syslib-title h3 { font-size: 15px; margin: 0 0 6px; }
.syslib-profs { display: flex; flex-wrap: wrap; gap: 4px; }
.syslib-action { flex: 0 0 auto; }
.syslib-meta { display: flex; flex-wrap: wrap; gap: 12px; margin: 12px 0 0; font-size: 11px; color: var(--text-muted); }
.syslib-meta span { display: inline-flex; align-items: center; gap: 4px; }
.syslib-desc { font-size: 13px; color: var(--text-secondary); margin: 10px 0 4px; }
.syslib-ings { list-style: none; margin: 4px 0 0; padding: 0; }
.syslib-ings li { font-size: 13px; padding: 3px 0; border-bottom: 1px solid var(--border); }
.syslib-ings li:last-child { border-bottom: 0; }
.syslib-ing-amt { display: inline-block; min-width: 70px; color: var(--text-primary); font-weight: 600; }
.syslib-recipe-link { font-size: 12px; color: var(--text-muted); margin-top: 10px; }
.syslib-err { font-size: 12px; color: var(--danger); margin-top: 8px; }

/* ── THE MARKET "CLASSES" TAB — profession cosmetic ladders on a timeline ──────
   Compact, no blurbs; one class at a time; every reward pinned to its level on a
   dotted vertical timeline (the videogame reward-track look). */
.cls-wrap { display: flex; flex-direction: column; gap: 12px; }
.cls-picker { display: flex; flex-wrap: wrap; gap: 6px; }
.cls-pick { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 600;
  padding: 5px 9px; border-radius: var(--radius-pill); border: 0.5px solid var(--border);
  background: var(--surface-2); color: var(--text-secondary); cursor: pointer; }
.cls-pick i { color: var(--cls-color, var(--text-secondary)); }
.cls-pick.is-on { border-color: var(--cls-color, var(--border-accent)); color: var(--text-primary);
  background: color-mix(in srgb, var(--cls-color, var(--surface-3)) 18%, var(--surface-2)); }
.cls-pick-lvl { font-size: 9px; background: var(--surface-0); color: var(--text-muted);
  border-radius: var(--radius-pill); padding: 1px 5px; }
.cls-head { display: flex; align-items: baseline; gap: 10px; padding: 8px 12px; border-radius: var(--radius-md);
  border-left: 3px solid var(--cls-color, var(--border-accent));
  background: color-mix(in srgb, var(--cls-color, var(--surface-2)) 12%, var(--surface-1)); }
.cls-head-name { font-size: 15px; font-weight: 700; color: var(--text-primary); }
.cls-head-lvl { font-size: 11px; color: var(--text-secondary); }
.cls-filter { align-self: flex-start; }

/* the dotted timeline */
.cls-timeline { position: relative; padding-left: 18px; }
.cls-timeline::before { content: ''; position: absolute; left: 5px; top: 6px; bottom: 6px; width: 0;
  border-left: 2px dotted var(--border-strong); }
.cls-rung { position: relative; padding: 6px 0 14px; }
.cls-rung-mark { display: flex; align-items: center; gap: 8px; margin: 0 0 8px -18px; }
.cls-node { width: 12px; height: 12px; border-radius: 50%; background: var(--surface-1);
  border: 2px solid var(--border-strong); flex-shrink: 0; box-shadow: 0 0 0 3px var(--surface-1); }
.cls-rung.is-reached .cls-node { background: var(--border-accent); border-color: var(--border-accent); }
.cls-lvl { font-size: 11px; font-weight: 700; color: var(--text-muted); letter-spacing: 0.03em; }
.cls-rung.is-reached .cls-lvl { color: var(--text-secondary); }
.cls-rung-items { display: flex; flex-wrap: wrap; gap: 8px; }

/* one compact reward tile */
.cls-tile { position: relative; width: 84px; padding: 6px 6px 5px; display: flex; flex-direction: column;
  align-items: center; gap: 5px; background: var(--surface-2); border: 0.5px solid var(--border);
  border-radius: var(--radius-md); color: var(--text-primary); cursor: pointer; text-align: center; }
.cls-tile:hover:not(.is-locked) { border-color: var(--border-accent); }
.cls-tile.is-worn { border-color: var(--border-accent); box-shadow: inset 0 0 0 1px var(--border-accent); }
.cls-tile.is-locked { opacity: 0.5; cursor: default; }
.cls-tile-preview { width: 100%; height: 46px; border-radius: var(--radius-sm); overflow: hidden;
  display: flex; align-items: center; justify-content: center; background: var(--surface-0); }
.cls-tile-name { font-size: 10px; line-height: 1.2; color: var(--text-secondary);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cls-tile-type { position: absolute; top: 3px; left: 3px; font-size: 9px; color: var(--text-muted);
  background: var(--surface-1); border-radius: var(--radius-pill); width: 15px; height: 15px;
  display: flex; align-items: center; justify-content: center; }
.cls-tile-worn { position: absolute; top: 3px; right: 3px; font-size: 9px; color: #fff;
  background: var(--border-accent); border-radius: 50%; width: 15px; height: 15px;
  display: flex; align-items: center; justify-content: center; }
.cls-tile-lock { position: absolute; top: 3px; right: 3px; font-size: 10px; color: var(--text-muted); }
/* the previews inside a tile */
.cls-swatch { display: flex; width: 100%; height: 100%; }
.cls-swatch i { flex: 1; height: 100%; }
.cls-bgswatch { display: block; width: 100%; height: 100%;
  background: var(--app-bg, var(--surface-1)); background-size: var(--app-bg-size, cover);
  background-position: var(--app-bg-pos, center); background-repeat: var(--app-bg-repeat, no-repeat); }
.cls-bdg { display: flex; align-items: center; justify-content: center; }
.cls-fx { transform: scale(0.62); transform-origin: center; pointer-events: none; }

/* Profession THEME mode — the one-switch control (Loadout + Classes tab). */
.theme-mode { display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  padding: 10px 12px; margin-bottom: 12px; border-radius: var(--radius-md);
  border: 0.5px solid var(--border); background: var(--surface-2); }
.theme-mode.is-on { border-color: var(--border-accent);
  background: color-mix(in srgb, var(--border-accent) 10%, var(--surface-2)); }
.theme-mode-toggle { display: inline-flex; align-items: center; gap: 7px; font-size: 13px;
  font-weight: 600; color: var(--text-primary); cursor: pointer; }
.theme-mode-toggle i { color: var(--border-accent); }
.theme-mode select { font-size: 12px; padding: 5px 8px; }
.theme-mode select:disabled { opacity: 0.5; }
.theme-mode-auto { display: inline-flex; align-items: center; gap: 7px; font-size: 12px;
  color: var(--text-secondary); cursor: pointer; }
.theme-mode-auto i { color: var(--border-accent); }
.theme-mode-resolved { font-size: 12px; color: var(--text-secondary); }
.theme-mode-resolved b { color: var(--text-primary); }
.theme-mode-hint { font-size: 11px; color: var(--text-muted); flex: 1 1 200px; }
/* While a theme is worn the manual loadout is overridden — dim it so it's clear. */
.loadout.is-themed { opacity: 0.55; }

/* ── Character-creation wizard (wizard.html, first-run) ───────────────────────
   A full-frame, MANDATORY "roll a character" moment — the shell pushes it as a
   layer for a new account and it can't be skipped. Built from the design system:
   reuses .chip / .color-swatch / .icon-picker-grid / la-and-c colour classes / radius tokens,
   type on the 8-step scale. Leads with COLOUR; default names sit light. */
.wizard-page { background: var(--app-bg, var(--surface-0)); }
.wiz-root { min-height: 100vh; display: flex; align-items: flex-start; justify-content: center; padding: 24px 16px; }
.wiz-card { width: 100%; max-width: 720px; background: var(--surface-1); border: 0.5px solid var(--border);
  border-radius: var(--radius-lg); padding: 24px; }
.wiz-head { margin-bottom: 20px; }
.wiz-steps { display: flex; gap: 8px; }
.wiz-step { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-muted); flex: 0 0 auto; }
.wiz-step.is-on { color: var(--text-primary); font-weight: 600; }
.wiz-step.is-done { color: var(--text-secondary); }
.wiz-step + .wiz-step { padding-left: 8px; border-left: 0.5px solid var(--border); }
.wiz-step-dot { display: inline-grid; place-items: center; width: 20px; height: 20px; border-radius: 50%;
  background: var(--surface-3, var(--surface-2)); font-size: 11px; }
.wiz-step.is-on .wiz-step-dot { background: var(--border-accent); color: #fff; }
.wiz-step.is-done .wiz-step-dot { background: var(--border-accent); color: #fff; }
@media (max-width: 560px) { .wiz-step-name { display: none; } }

.wiz-title { font-size: 20px; margin: 0 0 6px; }
.wiz-note { font-size: 13px; color: var(--text-secondary); margin: 0 0 16px; line-height: 1.5; }
.wiz-sub { display: block; font-size: 11px; color: var(--text-muted); margin: 12px 0 6px; text-transform: uppercase; letter-spacing: 0.05em; }

/* Name step — the flashy opening. */
.wiz-center { text-align: center; padding: 12px 0 4px; }
.wiz-kicker { font-size: 11px; text-transform: uppercase; letter-spacing: 0.18em; color: var(--text-muted); margin: 0 0 8px; }
.wiz-hero-title { font-size: 26px; font-weight: 700; margin: 0 0 12px; letter-spacing: 0.01em; }
.wiz-lead { font-size: 15px; color: var(--text-secondary); line-height: 1.55; max-width: 46ch; margin: 0 auto 20px; }
.wiz-namefield { max-width: 360px; margin: 0 auto; text-align: left; }

.wiz-field { display: block; margin-bottom: 12px; }
.wiz-field > span { display: block; font-size: 11px; color: var(--text-muted); margin-bottom: 4px; text-transform: uppercase; letter-spacing: 0.04em; }
.wiz-field em { font-style: normal; text-transform: none; letter-spacing: 0; color: var(--text-muted); }
.wiz-field input, .wiz-field textarea { width: 100%; }

/* Classes step — the colour-led gallery. */
.wiz-classgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr)); gap: 10px; margin: 12px 0 4px; }
.wiz-customs { margin-bottom: 10px; }
.wiz-class { position: relative; display: flex; flex-direction: column; text-align: left; padding: 0; overflow: hidden;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-md); cursor: pointer;
  transition: border-color 0.12s ease, box-shadow 0.12s ease, transform 0.12s ease; }
.wiz-class:hover { border-color: var(--border-strong); transform: translateY(-1px); }
.wiz-class.is-on { border-color: var(--cls-color, var(--border-accent)); box-shadow: 0 0 0 1px var(--cls-color, var(--border-accent)), 0 6px 18px rgba(0,0,0,0.35); }
.wiz-class-band { display: block; height: 6px; background: var(--cls-color, var(--border-accent)); opacity: 0.9; }
.wiz-class-body { display: block; padding: 10px 12px 12px; }
.wiz-class-name { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); margin-bottom: 4px; }
.wiz-class-vibe { display: block; font-size: 13px; font-weight: 600; color: var(--cls-color, var(--text-primary)); margin-bottom: 8px; line-height: 1.35; }
.wiz-egchips { display: flex; flex-wrap: wrap; gap: 4px; }
.wiz-egchip { font-size: 10px; padding: 1px 7px; border-radius: var(--radius-pill); background: var(--surface-3, var(--surface-1)); color: var(--text-secondary); }
.wiz-class-check { position: absolute; top: 8px; right: 8px; width: 20px; height: 20px; border-radius: 50%;
  display: grid; place-items: center; background: var(--cls-color, var(--border-accent)); color: #fff; font-size: 12px; opacity: 0; transform: scale(0.6); transition: opacity 0.12s ease, transform 0.12s ease; }
.wiz-class.is-on .wiz-class-check { opacity: 1; transform: scale(1); }
.wiz-mixbtn { margin-bottom: 4px; }

/* Active step. */
.wiz-active-grid { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
.wiz-active-opt { position: relative; display: inline-flex; align-items: center; gap: 8px; padding: 8px 12px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius); cursor: pointer; }
.wiz-active-opt.is-on { border-color: var(--border-accent); box-shadow: 0 0 0 1px var(--border-accent); }
.wiz-active-check { display: none; color: var(--border-accent); font-size: 15px; }
.wiz-active-opt.is-on .wiz-active-check { display: inline-flex; }
.wiz-why { background: var(--surface-2); border: 0.5px solid var(--border); border-radius: var(--radius-md); padding: 14px 16px; }
.wiz-why-head { font-size: 13px; font-weight: 600; color: var(--text-primary); margin: 0 0 8px; display: flex; align-items: center; gap: 6px; }
.wiz-why-head i { color: var(--xp); }
.wiz-why-list { margin: 0; padding-left: 18px; }
.wiz-why-list li { font-size: 12px; color: var(--text-secondary); line-height: 1.5; margin-bottom: 6px; }

/* Look step. */
.wiz-look .wiz-preview { display: flex; justify-content: center; margin: 4px 0 16px; }
.wiz-icons { max-height: 220px; overflow-y: auto; }

.wiz-actions { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 24px; }
.wiz-nexthint { color: var(--text-primary); background: var(--surface-2); border: 0.5px solid var(--border); border-radius: var(--radius); padding: 10px 12px; margin-top: 14px; }
.wiz-nexthint i { color: var(--xp); }

/* The custom-class mixer, a modal over the wizard. Modal z-band [1000]. */
.wiz-overlay { position: fixed; inset: 0; z-index: 1000; background: rgba(8,10,14,0.66);
  display: flex; align-items: center; justify-content: center; padding: 20px; overflow-y: auto; }
.wiz-mixpanel { width: min(440px, calc(100% - 24px)); background: var(--surface-1);
  border: 0.5px solid var(--border); border-radius: var(--radius-lg); padding: 20px; }
.wiz-mixrows { display: flex; flex-direction: column; gap: 8px; margin-bottom: 8px; }
.wiz-mixrow { display: flex; align-items: center; gap: 8px; }
.wiz-mixswatch { width: 15px; height: 15px; border-radius: 50%; flex-shrink: 0; }
.wiz-mixrow select { flex: 1 1 auto; min-width: 0; }
.wiz-mixpct { font-size: 12px; width: 40px; text-align: right; color: var(--text-secondary); }
.wiz-mixtotal { font-size: 12px; color: var(--text-secondary); margin: 8px 0; }
.wiz-mixpreview { margin: 8px 0; }
.wiz-mixname { width: 100%; margin-top: 8px; }
.wiz-mixerr { font-size: 12px; color: var(--danger); margin: 8px 0 0; }
@media (prefers-reduced-motion: reduce) { .wiz-class, .wiz-class-check { transition: none; } }
/* While the mandatory first-run wizard is up, there is no escape hatch. */
html.shell-locked .shell-center, html.shell-locked .shell-back { display: none; }

/* ── Help & guides hub (help.html) ──────────────────────────────────────────
   A standalone/embedded reading page; reached from the shell menu. Built from
   the design-system surfaces + type scale. */
.help-page { background: var(--app-bg, var(--surface-0)); }
.help-root { max-width: 720px; margin: 0 auto; padding: 24px 16px 40px; }
.help-head { margin-bottom: 20px; }
.help-title { font-size: 26px; font-weight: 700; margin: 0 0 6px; }
.help-sub { font-size: 13px; color: var(--text-secondary); line-height: 1.5; margin: 0; }
.help-card { background: var(--surface-1); border: 0.5px solid var(--border); border-radius: var(--radius-md); padding: 18px 20px; margin-bottom: 16px; }
.help-h2 { font-size: 15px; font-weight: 600; margin: 0 0 12px; display: flex; align-items: center; gap: 8px; }
.help-h2 i { color: var(--border-accent); }
.help-steps { margin: 0; padding-left: 20px; }
.help-steps li { font-size: 13px; color: var(--text-secondary); line-height: 1.55; margin-bottom: 10px; }
.help-steps b, .help-p b, .help-q p b { color: var(--text-primary); }
.help-p { font-size: 13px; color: var(--text-secondary); line-height: 1.55; margin: 0; }
.help-faq { display: flex; flex-direction: column; gap: 8px; }
.help-q { border: 0.5px solid var(--border); border-radius: var(--radius); background: var(--surface-2); padding: 0; }
.help-q > summary { font-size: 13px; font-weight: 600; color: var(--text-primary); padding: 10px 14px; cursor: pointer; list-style: none; }
.help-q > summary::-webkit-details-marker { display: none; }
.help-q > summary::before { content: '+'; color: var(--text-muted); margin-right: 8px; font-weight: 400; }
.help-q[open] > summary::before { content: '\2013'; }
.help-q > p { font-size: 12px; color: var(--text-secondary); line-height: 1.55; margin: 0; padding: 0 14px 12px; }

/* Login: the "or" divider between the password form and Clerk sign-in. */
.auth-or { display: flex; align-items: center; gap: 10px; margin: 16px 0; color: var(--text-muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; }
.auth-or::before, .auth-or::after { content: ''; flex: 1 1 auto; height: 1px; background: var(--border); }
.clerk-open { display: flex; align-items: center; justify-content: center; gap: 6px; width: 100%; }
.linked-methods { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
