/*
 *  app.css
 *  Loreglass web app
 *
 *  Every colour, typeface and corner radius comes from theme.js, which is a
 *  port of ChroniclerTheme. Nothing here hardcodes a palette colour — that's
 *  what lets Settings → Theme repaint the app live, exactly as it does natively.
 *
 *  Radii go through calc(N * var(--corner)) to mirror ChroniclerTheme.corner(_:),
 *  so the sharp / standard / soft toggle keeps the same relative proportions.
 */

:root {
  /* Fallbacks only — theme.js overwrites all of these on boot. */
  --gold:#C69E54; --glow:#F2D08A; --base:#1F1712; --accent:#7F2123;
  --obsidian:#0D0A07; --parchment:#ECE3CE; --ink:#241A12; --gradient-mid:#2A0B0C;
  --gold-12:rgba(198,158,84,.12); --gold-20:rgba(198,158,84,.2);
  --gold-45:rgba(198,158,84,.45); --gold-70:rgba(198,158,84,.7);
  --muted:rgba(236,227,206,.6); --faint:rgba(236,227,206,.4);
  --surface:rgba(236,227,206,.05); --surface-2:rgba(236,227,206,.08);
  --line:rgba(236,227,206,.14);
  --backdrop:linear-gradient(180deg,#1F1712 0%,#2A0B0C 55%,#000 100%);
  --font: ui-serif, "Iowan Old Style", Palatino, Georgia, serif;
  --corner: 1;
  --sidebar: 232px;
  --danger:#E8836B; --ok:#8FC79A;
}

* { box-sizing:border-box }
html { color-scheme:dark }
html, body { height:100% }
body {
  margin:0;
  background:var(--obsidian);
  color:var(--parchment);
  font:16px/1.55 var(--font);
  -webkit-font-smoothing:antialiased;
}
a { color:var(--gold) }
button, input, textarea, select { font-family:var(--font) }

/* The app's nightGradient backdrop, fixed so it doesn't scroll away. */
.backdrop {
  position:fixed; inset:0; background:var(--backdrop); z-index:-1; pointer-events:none;
}

.spin {
  display:inline-block; width:14px; height:14px; border:2px solid var(--line);
  border-top-color:var(--gold); border-radius:50%; animation:sp .8s linear infinite;
  vertical-align:-2px;
}
@keyframes sp { to { transform:rotate(360deg) } }
.boot { padding:60px; text-align:center; color:var(--muted) }

/* ── Sign in ───────────────────────────────────────────────────────────────── */

.gate { max-width:430px; margin:11vh auto; padding:0 20px; text-align:center }
.gate img {
  width:84px; height:84px; border-radius:calc(20px * var(--corner));
  margin-bottom:20px; box-shadow:0 12px 44px rgba(0,0,0,.55);
}
.gate h1 { font-size:2rem; margin:0 0 8px; color:var(--parchment); letter-spacing:.01em }
/* The logo and wordmark are one link back to the site. It has to be
   inline-block, not the default inline, or the block <h1> inside it escapes the
   link's own box and only the icon stays clickable. */
.gate .home { display:inline-block; text-decoration:none; color:inherit }
.gate .home:hover img { box-shadow:0 12px 44px rgba(0,0,0,.7) }
.gate .home:hover h1 { color:var(--gold) }
.gate p { color:var(--muted); margin:0 0 24px }
.gate .gbtn { display:flex; justify-content:center; min-height:44px }
/* The age gate reuses .gate, but its form has real fields, so the labels go
   back to left-aligned — centred field labels read as decoration. */
.gate .agegate { text-align:left; margin-top:8px }
.gate .agegate .bar { text-align:center }

/* ── Shell ─────────────────────────────────────────────────────────────────── */

.shell { display:flex; height:100vh; overflow:hidden }

.side {
  width:var(--sidebar); flex:0 0 var(--sidebar);
  border-right:1px solid var(--line); display:flex; flex-direction:column;
  background:rgba(0,0,0,.25); backdrop-filter:blur(8px);
}
.side header {
  padding:15px 16px; border-bottom:1px solid var(--line);
  display:flex; align-items:center; gap:10px;
}
.side header img { width:28px; height:28px; border-radius:calc(8px * var(--corner)) }
/* margin-right:auto lives on the LINK, not the wordmark inside it — otherwise
   there's nothing left to push the sync spinner to the far edge. */
.side header .home {
  display:flex; align-items:center; gap:10px; margin-right:auto;
  text-decoration:none; color:inherit; min-width:0;
}
.side header .brand {
  font-weight:600; color:var(--gold); letter-spacing:.02em;
}
.side header .home:hover .brand { color:var(--glow) }
.side .scroll { overflow-y:auto; flex:1; padding:10px 8px }
.side footer { border-top:1px solid var(--line); padding:11px 14px; font-size:13px; color:var(--muted) }
.side footer .who { overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.side footer a { color:var(--muted); text-decoration:none }
.side footer a:hover { color:var(--gold) }

.main { flex:1; overflow-y:auto; min-width:0; display:flex; flex-direction:column }
.main .pad { padding:22px 34px 90px; max-width:980px }

/* ChroniclerHeader: gold glyph + serif title on the left, actions on the right,
   sitting directly on the backdrop with no bar of its own (ContentView.swift:946).
   Sticky rather than inset, since the web scrolls the whole pane. */
.screenhead {
  display:flex; align-items:center; gap:10px;
  padding:10px 34px; position:sticky; top:0; z-index:10;
  background:linear-gradient(180deg, rgba(0,0,0,.55), rgba(0,0,0,.15) 70%, transparent);
  backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
  /* Same column as .pad. Without this, `margin-left:auto` on the actions threw
     them to the far right of the whole pane while the content sat in a 980px
     column — hundreds of pixels apart on a wide window, which made the New hero
     and Add PDF buttons easy to miss completely. */
  max-width:980px; box-sizing:content-box;
}
.screenhead .glyph { width:20px; height:20px; flex:0 0 20px; color:var(--gold) }
.screenhead h1 {
  font-size:1.35rem; font-weight:600; margin:0; color:var(--parchment);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.screenhead .actions { margin-left:auto; display:flex; gap:8px; align-items:center }
@media (max-width:860px) { .screenhead { position:static } }

@media (max-width:860px) {
  .shell { flex-direction:column; height:auto; overflow:visible }
  .side { width:auto; flex:none; border-right:0; border-bottom:1px solid var(--line) }
  .side .scroll { display:flex; overflow-x:auto; gap:4px; padding:8px }
  .side .scroll .sidelabel, .side .scroll .campaign-list { display:none }
  .main .pad { padding:20px }
}

/* ── Nav items ─────────────────────────────────────────────────────────────── */

.nav {
  display:flex; align-items:center; gap:10px; width:100%; text-align:left;
  background:none; border:0; color:var(--parchment); padding:9px 11px;
  border-radius:calc(10px * var(--corner)); cursor:pointer; font-size:15px;
  white-space:nowrap;
}
.nav:hover { background:var(--surface) }
.nav.on { background:var(--gold-12); color:var(--gold) }
.nav .ico { width:17px; height:17px; flex:0 0 17px; opacity:.9 }
.nav.on .ico { opacity:1 }

.item {
  display:block; width:100%; text-align:left; background:none; border:0;
  color:var(--parchment); padding:8px 11px; border-radius:calc(9px * var(--corner));
  cursor:pointer; font-size:14px;
}
.item:hover { background:var(--surface) }
.item.on { background:var(--gold-12); color:var(--gold) }
.item .sub { display:block; color:var(--muted); font-size:12px; margin-top:1px }
.item.on .sub { color:var(--gold-70) }
.sidelabel {
  font-size:10px; text-transform:uppercase; letter-spacing:.14em; color:var(--faint);
  padding:15px 11px 6px; font-weight:600;
}

/* ── Type ──────────────────────────────────────────────────────────────────── */

h1 { font-size:1.85rem; margin:0 0 5px; color:var(--parchment); letter-spacing:.01em }
h2 {
  font-size:1.1rem; margin:28px 0 12px; padding-top:15px;
  border-top:1px solid var(--line); color:var(--gold);
}
h3 { color:var(--parchment) }
.lede { color:var(--muted); margin:0 0 22px }
.empty { color:var(--muted); padding:30px 0; text-align:center }

/* ── Tabs ──────────────────────────────────────────────────────────────────── */

.tabs { display:flex; gap:3px; flex-wrap:wrap; border-bottom:1px solid var(--line); margin:20px 0 22px }
.tab {
  background:none; border:0; border-bottom:2px solid transparent; color:var(--muted);
  padding:9px 14px; cursor:pointer; font-size:15px;
}
.tab:hover { color:var(--parchment) }
.tab.on { color:var(--gold); border-bottom-color:var(--gold) }
.tab .count { color:var(--faint); font-size:12px; margin-left:6px }

/* ── Cards + rows ──────────────────────────────────────────────────────────── */

/* chroniclerCard: Liquid Glass tinted with base.opacity(0.28), a
   gold.opacity(0.2) hairline edge, 16px padding, corner 18. backdrop-filter is
   the closest the browser gets to glassEffect — the point is that the backdrop
   (including its gold bloom) shows through rather than being covered. */
.grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(255px,1fr)); gap:13px }
.card {
  background:var(--card-tint); border:1px solid var(--card-edge);
  backdrop-filter:blur(14px) saturate(1.2);
  -webkit-backdrop-filter:blur(14px) saturate(1.2);
  border-radius:calc(18px * var(--corner)); padding:16px; text-align:left;
  width:100%; cursor:pointer; color:var(--parchment);
}
.card:hover { border-color:var(--gold-45) }
.card h3 { margin:0 0 4px; font-size:1.05rem }
.card p { margin:0; color:var(--muted); font-size:13.5px }
.card .tagrow { margin-top:10px; display:flex; gap:6px; flex-wrap:wrap }

.row {
  display:flex; align-items:flex-start; gap:12px; padding:14px 16px;
  background:var(--card-tint); border:1px solid var(--card-edge);
  backdrop-filter:blur(14px) saturate(1.2);
  -webkit-backdrop-filter:blur(14px) saturate(1.2);
  border-radius:calc(18px * var(--corner)); margin-bottom:9px;
}
.row .grow { flex:1; min-width:0 }
.row h3 { margin:0 0 3px; font-size:1rem }
.row p { margin:0; color:var(--muted); font-size:13.5px }
.row .clip { display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden }

.tag {
  display:inline-block; font-size:11px; padding:2px 9px;
  border-radius:999px; border:1px solid var(--line); color:var(--muted);
}
.tag.gold { border-color:var(--gold-45); color:var(--gold) }
.tag.dead { border-color:rgba(232,131,107,.4); color:var(--danger) }

/* ── Buttons ───────────────────────────────────────────────────────────────── */

/* Primary = the app's gold pill: LinearGradient([glow, gold], top→bottom) in a
   Capsule, ink label, .padding(.horizontal,24).padding(.vertical,10).
   See ChroniclerSheet's Done button (ContentView.swift:1040-1045).

   Capsules are NOT scaled by --corner: a capsule stays a capsule in the app
   whatever the corner style, because it's Capsule() rather than corner(_:). */
.btn {
  display:inline-flex; align-items:center; justify-content:center; gap:7px;
  border:0; border-radius:999px; padding:10px 24px;
  font-weight:600; cursor:pointer; font-size:15px; text-decoration:none;
  background:linear-gradient(to bottom, var(--glow), var(--gold));
  color:var(--ink);
}
.btn:hover { filter:brightness(1.06) }
.btn:active { filter:brightness(.96) }
.btn:disabled { opacity:.42; cursor:not-allowed; filter:none }

/* Secondary = transparent with a gold.opacity(0.25) stroke and a
   parchment.opacity(0.8) label, matching ChroniclerIconButton's treatment. */
.btn.ghost {
  background:transparent; color:var(--parchment-80); border:1px solid var(--gold-25);
}
.btn.ghost:hover { color:var(--parchment); border-color:var(--gold-45) }

.btn.sm { padding:6px 16px; font-size:13.5px }

/* Full-width CTA, which the app draws as a RoundedRectangle(corner(12))
   rather than a capsule (ContentView.swift:373-375). */
.btn.block {
  width:100%; border-radius:calc(12px * var(--corner)); padding:11px 18px;
}

.btn.danger { background:transparent; color:var(--danger); border:1px solid rgba(232,131,107,.4) }
.bar { display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-bottom:16px }
.bar .push { margin-left:auto }

/* ── Forms ─────────────────────────────────────────────────────────────────── */

.field { margin-bottom:14px }
.field label { display:block; font-size:12px; color:var(--muted); margin-bottom:5px }
input[type=text], input[type=number], input[type=color], textarea, select {
  width:100%; background-color:rgba(0,0,0,.32); color:var(--parchment);
  border:1px solid var(--line); border-radius:calc(10px * var(--corner));
  padding:10px 12px; font-size:15px;
}
input:focus, textarea:focus, select:focus { outline:none; border-color:var(--gold-70) }
textarea { min-height:100px; resize:vertical; line-height:1.55 }
input[type=color] { padding:3px; height:40px; cursor:pointer }

/* Number fields: strip the OS stepper, same reasoning as the select below, and
   because its arrows didn't actually change the value here. The .stepper
   buttons replace it. */
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button { -webkit-appearance:none; appearance:none; margin:0 }
input[type=number] { -moz-appearance:textfield; appearance:textfield }

/* One control, three parts: the buttons carry the outer corners and the input
   drops its own side borders, so the seams read as dividers rather than as
   three separate boxes. */
.stepper { display:flex; align-items:stretch }
.stepper input[type=number] {
  border-radius:0; border-left-color:transparent; border-right-color:transparent;
  text-align:center; padding-left:4px; padding-right:4px; min-width:0;
}
.stepper .step {
  flex:0 0 36px; background-color:rgba(0,0,0,.32); color:var(--gold-70);
  border:1px solid var(--line); font-family:inherit; font-size:18px; line-height:1;
  cursor:pointer; display:flex; align-items:center; justify-content:center;
  padding:0; -webkit-user-select:none; user-select:none;
}
.stepper .step:first-child {
  border-radius:calc(10px * var(--corner)) 0 0 calc(10px * var(--corner));
}
.stepper .step:last-child {
  border-radius:0 calc(10px * var(--corner)) calc(10px * var(--corner)) 0;
}
.stepper .step:hover:not(:disabled) { border-color:var(--gold-45); color:var(--glow) }
.stepper .step:active:not(:disabled) { background-color:rgba(0,0,0,.5) }
.stepper .step:disabled { opacity:.3; cursor:default }
/* Focus lights the whole control, not just the middle third. */
.stepper:focus-within .step,
.stepper:focus-within input[type=number] { border-color:var(--gold-70) }

/* Selects: strip the native control entirely.
   Left alone, macOS draws its own up/down stepper inside the field, which
   ignores the border radius and the palette and reads as a system widget
   dropped into the middle of a themed screen. appearance:none removes it; the
   chevron below replaces it.

   The arrow is a data-URI SVG, so it can't use currentColor — white at 55%
   suits every palette, since `parchment` is always a light tone whether warm
   (crimson) or cool (steel).

   color-scheme:dark is what makes the OPEN popup dark too. Without it macOS
   renders a light menu, which is the most jarring part. */
select {
  appearance:none; -webkit-appearance:none;
  padding-right:34px; cursor:pointer; color-scheme:dark;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none' stroke='%23ffffff' stroke-opacity='0.55' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5'/%3E%3C/svg%3E");
  background-repeat:no-repeat;
  background-position:right 12px center;
  background-size:12px 8px;
}
select:hover { border-color:var(--gold-45) }
/* Options are drawn by the OS; these are the only two properties it honours. */
select option { background:var(--base); color:var(--parchment) }

/* A select used as a header action is inline, not full width. */
.screenhead select { width:auto; min-width:150px; padding-top:6px; padding-bottom:6px }
.bar select { width:auto }
.two { display:grid; grid-template-columns:1fr 1fr; gap:12px }
@media (max-width:620px) { .two { grid-template-columns:1fr } }
.check { display:flex; align-items:center; gap:9px; font-size:15px; margin-bottom:12px }
.check input { width:auto }

.err { color:var(--danger); font-size:14.5px; margin:10px 0; min-height:1.2em }
.saved { color:var(--ok); font-size:13.5px }

/* ── Modal ─────────────────────────────────────────────────────────────────── */

.scrim {
  position:fixed; inset:0; background:rgba(0,0,0,.66); display:flex;
  align-items:flex-start; justify-content:center; padding:6vh 18px; z-index:50;
  overflow-y:auto; backdrop-filter:blur(3px);
}
.sheet {
  background:var(--base); border:1px solid var(--gold-20);
  border-radius:calc(20px * var(--corner)); width:100%; max-width:640px; padding:26px;
  box-shadow:0 20px 70px rgba(0,0,0,.6);
}
.sheet h2 { margin:0 0 18px; border:0; padding:0; font-size:1.3rem; color:var(--gold) }

/* ── Chat ──────────────────────────────────────────────────────────────────── */

/* Fills whatever height .main gives it rather than 100vh: the screen header now
   sits inside this column, and 100vh would push the composer off-screen by
   exactly the header's height. min-height:0 is what lets .log actually scroll
   inside a flex parent. */
.chat { display:flex; flex-direction:column; flex:1; min-height:0; height:100% }
.chat .log { flex:1; min-height:0; overflow-y:auto; padding:22px 34px }
.chat .log .inner { max-width:780px; margin:0 auto }
.msg { margin-bottom:20px; display:flex; gap:12px }
.msg .avatar {
  width:29px; height:29px; border-radius:calc(9px * var(--corner)); flex:0 0 29px;
  display:flex; align-items:center; justify-content:center; font-size:12px;
  font-weight:700; background:var(--surface-2); border:1px solid var(--line);
  color:var(--muted);
}
.msg.me .avatar { background:var(--gold-20); border-color:var(--gold-45); color:var(--gold) }
.msg .body { flex:1; min-width:0; white-space:pre-wrap; word-wrap:break-word }
.chat .composer { border-top:1px solid var(--line); padding:14px 34px; background:rgba(0,0,0,.3) }
.chat .composer .inner { max-width:780px; margin:0 auto; display:flex; gap:10px; align-items:flex-end }
.chat .composer textarea { min-height:46px; max-height:190px }
@media (max-width:860px) {
  .chat { height:auto; min-height:70vh }
  .chat .log { padding:18px }
  .chat .composer { padding:12px 18px }
}

/* ── Theme editor ──────────────────────────────────────────────────────────── */

.swatches { display:grid; grid-template-columns:repeat(auto-fill,minmax(160px,1fr)); gap:12px }
.swatch {
  border:1px solid var(--line); border-radius:calc(14px * var(--corner));
  padding:12px; cursor:pointer; background:var(--surface); text-align:left; color:var(--parchment);
}
.swatch.on { border-color:var(--gold) }
.swatch .name { font-size:13.5px; margin-bottom:9px }
.swatch .strip { display:flex; height:22px; border-radius:calc(6px * var(--corner)); overflow:hidden }
.swatch .strip span { flex:1 }
/* The app's segmented toggle (ContentView.swift:1074-1082): clear when off with
   a parchment.opacity(0.8) label, gold gradient + ink label when on, in a
   RoundedRectangle(corner(9)) — not a capsule. */
.chips { display:flex; gap:8px; flex-wrap:wrap }
.chip {
  border:1px solid var(--gold-25); background:transparent; color:var(--parchment-80);
  border-radius:calc(9px * var(--corner)); padding:9px 16px; cursor:pointer; font-size:14px;
}
.chip:hover { color:var(--parchment) }
.chip.on {
  border-color:transparent; color:var(--ink);
  background:linear-gradient(to bottom, var(--glow), var(--gold));
}

/* ── Heroes ────────────────────────────────────────────────────────────────── */

/* The six ability scores as a block, with the derived SRD modifier under each —
   the layout the printed sheet uses, so it reads the way players expect. */
.abilities { display:grid; grid-template-columns:repeat(6, 1fr); gap:9px; margin-bottom:6px }
@media (max-width:620px) { .abilities { grid-template-columns:repeat(3, 1fr) } }
.ability {
  border:1px solid var(--card-edge); background:var(--card-tint);
  border-radius:calc(12px * var(--corner)); padding:9px 7px; text-align:center;
}
.ability label {
  display:block; font-size:11px; letter-spacing:.1em; color:var(--muted);
  margin-bottom:5px; font-weight:600;
}
.ability input { text-align:center; padding:6px 4px; font-size:17px }
.ability .mod { margin-top:5px; font-size:13px; color:var(--gold) }
/* Six cells across a 900px sheet leaves ~120px each, so the steppers get
   narrower buttons here rather than squeezing the score itself. */
.ability .field { margin-bottom:0 }
.ability .stepper .step { flex:0 0 26px; font-size:16px }
.ability .stepper input[type=number] { padding:6px 0 }

/* ── Sharing ───────────────────────────────────────────────────────────────── */

/* The join code is the one thing on that dialog someone has to read aloud down
   a phone line, so it gets monospace, wide tracking, and enough size that O and
   0 can't be confused. The alphabet the Worker generates from already excludes
   I, O, 0 and 1 for the same reason. */
.joincode {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size:2.2rem; letter-spacing:.22em; text-align:center;
  color:var(--gold); padding:18px 10px 14px;
  background:rgba(0,0,0,.32); border:1px solid var(--gold-25);
  border-radius:calc(12px * var(--corner)); margin:6px 0 14px;
}
.code {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  letter-spacing:.12em; color:var(--gold);
}

/* ── Maps ──────────────────────────────────────────────────────────────────── */

/* Thumbnails are a fixed 16:9 box with object-fit:cover, so a library of maps
   at wildly different aspect ratios still reads as a tidy grid instead of a
   ragged staircase. */
.mapcard { padding:0; overflow:hidden }
.mapcard h3 { margin:11px 14px 2px }
.mapcard p { margin:0 14px }
.mapcard .tagrow { margin:9px 14px 14px }
.mapcard .cardactions { margin:0 14px 14px }
.mapthumb {
  display:block; width:100%; aspect-ratio:16/9; object-fit:cover;
  background:rgba(0,0,0,.35);
}
.mapthumb.empty {
  display:flex; align-items:center; justify-content:center;
  color:var(--faint); font-size:13px;
}

/* The stage the grid is drawn over. inline-block so it shrink-wraps the image:
   the canvas is positioned against this box, and if it stretched to the full
   dialog width the grid would start before the map does. */
.mapstage {
  position:relative; display:inline-block; max-width:100%;
  border-radius:calc(12px * var(--corner)); overflow:hidden;
  border:1px solid var(--card-edge); line-height:0;
}
.mapfull { display:block; max-width:100%; height:auto }
.mappreview {
  display:block; width:100%; max-height:220px; object-fit:cover;
  border-radius:calc(12px * var(--corner)); margin-bottom:14px;
}
/* pointer-events:none so the overlay never eats a click meant for the map. */
.gridlayer { position:absolute; inset:0; pointer-events:none }

/* ── Live board ────────────────────────────────────────────────────────────── */

/* touch-action:none is load-bearing: without it a drag on a tablet scrolls the
   page instead of moving the token, which is the whole feature. */
.boardstage {
  position:relative; display:inline-block; max-width:100%; line-height:0;
  border-radius:calc(12px * var(--corner)); overflow:hidden;
  border:1px solid var(--card-edge); touch-action:none; user-select:none;
}
.boardimage { display:block; max-width:100%; height:auto; pointer-events:none }
.weatherlayer { position:absolute; inset:0; pointer-events:none }

/* Positioned by its CENTRE, so the coordinate the server holds is the middle of
   the token rather than its top-left — which is what makes a 4-square dragon
   and a 1-square goblin both land on the square you dropped them on. */
.token {
  position:absolute; transform:translate(-50%, -50%);
  border-radius:50%; overflow:hidden; cursor:default;
  border:2px solid rgba(0,0,0,.55);
  box-shadow:0 2px 10px rgba(0,0,0,.5);
  display:flex; align-items:center; justify-content:center;
  min-width:22px; min-height:22px;
}
.token img { width:100%; height:100%; object-fit:cover; pointer-events:none }
.token .initial {
  color:#fff; font-weight:700; font-size:13px; line-height:1;
  text-shadow:0 1px 2px rgba(0,0,0,.6);
}
/* Only a token you're allowed to move says so. A grab cursor on the dragon
   would promise something the server will refuse. */
.token.movable { cursor:grab }
.token.movable:hover { border-color:var(--gold-70) }
.token.dragging { cursor:grabbing; box-shadow:0 6px 22px rgba(0,0,0,.6); z-index:3 }
.token.selected { border-color:var(--gold) }

.swatchdot {
  width:26px; height:26px; border-radius:50%; border:2px solid transparent;
  cursor:pointer; padding:0;
}
.swatchdot.on { border-color:var(--parchment) }

/* ── Forge ─────────────────────────────────────────────────────────────────── */

/* The subject picker: all four ImageKinds shown up front, since the choice
   drives the aspect ratio, the prompt hint and the styled prompt sent to the
   model. Same fill treatment as the segmented control — clear when off, gold
   gradient with an ink label when on. */
.kindrow {
  display:grid; grid-template-columns:repeat(4, 1fr); gap:9px; margin:0 0 18px;
}
@media (max-width:640px) { .kindrow { grid-template-columns:repeat(2, 1fr) } }
.kind {
  display:flex; flex-direction:column; align-items:center; gap:7px;
  padding:14px 8px; cursor:pointer; font-size:14px;
  border:1px solid var(--gold-25); background:transparent; color:var(--parchment-80);
  border-radius:calc(14px * var(--corner));
}
.kind:hover { color:var(--parchment); border-color:var(--gold-45) }
.kind svg { width:26px; height:26px }
.kind.on {
  border-color:transparent; color:var(--ink);
  background:linear-gradient(to bottom, var(--glow), var(--gold));
}

.forge-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(230px,1fr)); gap:13px }
.forge-item {
  border:1px solid var(--line); border-radius:calc(14px * var(--corner));
  overflow:hidden; background:var(--surface);
}
.forge-item img { width:100%; display:block; aspect-ratio:1; object-fit:cover }
.forge-item .meta { padding:10px 12px; font-size:12.5px; color:var(--muted) }
.preview {
  border:1px solid var(--line); border-radius:calc(16px * var(--corner));
  overflow:hidden; margin-bottom:16px; background:rgba(0,0,0,.3);
  min-height:240px; display:flex; align-items:center; justify-content:center;
}
.preview img { width:100%; display:block }
.preview .placeholder { color:var(--faint); padding:40px; text-align:center }

/* ── Documents ─────────────────────────────────────────────────────────────── */

/* A file input styled as a button: the native control can't be restyled, so the
   real <input type=file> is hidden inside a <label> that carries .btn. */
.btn.disabled { opacity:.42; cursor:not-allowed; filter:none }
label.btn { cursor:pointer }
label.btn input { display:none }

/* The reader is wider than a normal sheet — a PDF page needs the room. */
.sheet.reader { max-width:1100px }
.pdfview {
  width:100%; height:74vh; min-height:420px;
  border:1px solid var(--line); border-radius:calc(12px * var(--corner));
  overflow:hidden; background:#0a0a14;
}

/* ── Dice tray ─────────────────────────────────────────────────────────────── */

/* The tray is far wider than it is tall, which is what the camera fit in
   dicetray.js assumes — the field of view is pinned to the vertical, so depth
   runs out before width. */
.tray {
  position:relative; width:100%; aspect-ratio:16/9; min-height:240px;
  border:1px solid var(--card-edge); border-radius:calc(18px * var(--corner));
  overflow:hidden; background:
    radial-gradient(ellipse at 50% 0%, var(--gold-12), transparent 60%),
    linear-gradient(180deg, rgba(0,0,0,.35), rgba(0,0,0,.6));
}
.tray canvas { width:100%; height:100%; display:block }
.trayhint {
  position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  color:var(--faint); pointer-events:none;
}

.rollresult {
  display:flex; align-items:center; gap:16px; margin-top:14px; padding:16px 18px;
  border:1px solid var(--card-edge); background:var(--card-tint);
  border-radius:calc(18px * var(--corner));
}
.rollresult .total { font-size:2.6rem; font-weight:700; color:var(--gold); line-height:1 }
.rollresult .summary { color:var(--parchment) }
.rollresult .breakdown { color:var(--muted); font-size:13.5px; margin-top:3px }
.rollresult .critflag { font-size:12px; font-weight:700; letter-spacing:.1em }
.rollresult.crit-hit { border-color:rgba(143,199,154,.5) }
.rollresult.crit-hit .total, .rollresult.crit-hit .critflag { color:var(--ok) }
.rollresult.crit-miss { border-color:rgba(232,131,107,.5) }
.rollresult.crit-miss .total, .rollresult.crit-miss .critflag { color:var(--danger) }

.dicerow { display:grid; grid-template-columns:repeat(7,1fr); gap:9px }
@media (max-width:720px) { .dicerow { grid-template-columns:repeat(4,1fr) } }
.diebtn { position:relative; display:flex; flex-direction:column }
.diebtn .face {
  border:1px solid var(--gold-25); background:transparent; color:var(--parchment-80);
  border-radius:calc(14px * var(--corner)); padding:16px 6px; cursor:pointer;
  font-size:15px; position:relative;
}
.diebtn .face:hover { color:var(--parchment); border-color:var(--gold-45) }
.diebtn.on .face {
  border-color:transparent; color:var(--ink);
  background:linear-gradient(to bottom, var(--glow), var(--gold));
}
.diebtn .dcount {
  position:absolute; top:5px; right:8px; font-size:12px; font-weight:700; opacity:.85;
}
.diebtn .minus {
  margin-top:5px; border:1px solid var(--line); background:transparent;
  color:var(--muted); border-radius:calc(8px * var(--corner)); padding:3px 0;
  cursor:pointer; font-size:15px; line-height:1.2;
}
.diebtn .minus:disabled { opacity:.3; cursor:not-allowed }
.diebtn .minus:hover:not(:disabled) { color:var(--parchment); border-color:var(--gold-45) }

.swatchrow { display:flex; gap:9px; align-items:center }
.swatchrow input[type=color] { flex:0 0 90px }
input[type=range] { width:100%; accent-color:var(--gold) }

/* ── Settings rows ─────────────────────────────────────────────────────────── */

/* A labelled row with its control on the right, so a settings page reads as a
   list of decisions rather than a stack of headings. Wraps on narrow screens
   so the control drops under its label instead of being squeezed. */
.settingrow {
  display:flex; align-items:center; gap:16px; flex-wrap:wrap;
  padding:15px 0; border-top:1px solid var(--line);
}
.settingrow .grow { flex:1; min-width:220px }
.settingrow h3 { margin:0 0 2px; font-size:1rem }
.settingrow p { margin:0; color:var(--muted); font-size:13.5px }

/* A disclosed block, set in from the row that opened it. */
.subpanel {
  border:1px solid var(--card-edge); background:var(--card-tint);
  border-radius:calc(16px * var(--corner)); padding:18px; margin:4px 0 18px;
}

/* Definition list used for read-only account facts. */
.kv {
  display:grid; grid-template-columns:auto 1fr; gap:8px 20px;
  margin:6px 0 20px; font-size:15px;
}
.kv dt { color:var(--muted) }
.kv dd { margin:0; color:var(--parchment) }
@media (max-width:520px) { .kv { grid-template-columns:1fr; gap:2px 0 } .kv dd { margin-bottom:10px } }

/* Sub-heading inside a settings section. */
.subhead {
  font-size:.95rem; margin:22px 0 10px; color:var(--gold);
  text-transform:uppercase; letter-spacing:.1em; font-weight:600;
}
.subhead:first-child { margin-top:0 }

/* A card that isn't itself clickable, because it holds its own buttons — a
   <button> can't legally contain another one. */
.card.static { cursor:default }
.card.static:hover { border-color:var(--card-edge) }
.cardactions {
  display:flex; gap:8px; margin-top:12px; padding-top:12px;
  border-top:1px solid var(--line);
}
.btn.danger.sm { padding:5px 13px; font-size:13px }

/* ── Compact dialogs ──────────────────────────────────────────────────────── */

.sheet.wide { max-width:900px }

/* A framed dialog scrolls its BODY and pins its actions, so a long form can't
   push Save below the fold — which is what made the hero editor feel endless. */
.sheet.framed {
  display:flex; flex-direction:column; max-height:86vh; padding-bottom:0;
}
.sheet.framed h2 { flex:0 0 auto }
.sheetbody { overflow-y:auto; flex:1; min-height:0; padding-right:4px }
.sheetfoot {
  flex:0 0 auto; display:flex; gap:10px; align-items:flex-start; flex-wrap:wrap;
  border-top:1px solid var(--line); padding:14px 0; margin-top:6px;
  background:var(--base);
}
.sheetfoot .push { margin-left:auto }

/* Denser field grids for dialogs that are genuinely multi-column. */
.three { display:grid; grid-template-columns:repeat(3,1fr); gap:10px 12px }
.four  { display:grid; grid-template-columns:repeat(4,1fr); gap:10px 12px }
@media (max-width:820px) {
  .three { grid-template-columns:repeat(2,1fr) }
  .four  { grid-template-columns:repeat(2,1fr) }
}
@media (max-width:560px) {
  .three, .four { grid-template-columns:1fr }
}

/* Inside a dialog, fields sit tighter than on a full page. */
.sheet .field { margin-bottom:10px }
.sheet .field label { margin-bottom:3px }
.sheet input[type=text], .sheet input[type=number], .sheet textarea, .sheet select {
  padding:7px 10px; font-size:14.5px;
}
.sheet textarea { min-height:auto; line-height:1.45 }

/* Tabs inside a dialog need less air than page-level ones. */
.tabs.tight { margin:0 0 16px }
.tabs.tight .tab { padding:7px 12px; font-size:14px }

/* An allowance running out is worth noticing before it does. --danger, not
   --gold: gold is decorative everywhere else here and wouldn't read as a
   warning. */
.note.warn { color:var(--danger) }
