/* Wyrd's two themes.
   Day   — Moonstone & Rose Quartz: pale blush, mauve, ivory.
   Night — Moonlit Bloom: the same app after sunset, not a developer dark mode.

   Every colour in this file goes through a token below, so the two themes are
   defined here and nowhere else. `--accent-strong` is always the *primary*
   interactive colour and `--accent` always the brighter hover, regardless of
   which is lighter in absolute terms — the roles stay put when the lightness
   inverts.

   The theme follows the operating system. There is no in-app toggle because
   there is no settings surface to put one on. */
:root {
  color-scheme: light dark;

  --bg: #faf5f7;
  --panel: #ffffff;
  --panel-2: #f4e8ee;
  --panel-border: #e6d2dc;
  --input-bg: var(--panel-2);
  --input-border: var(--panel-border);
  --text: #3a2630;
  --text-dim: #8a6d78;
  --accent: #c07a92;
  --accent-strong: #9c5570;
  --accent-wash: rgba(192, 122, 146, 0.14);
  /* Text laid on an accent fill. Flips with the theme: the dark theme's rose
     is light enough that white on it measures 2.1:1, i.e. unreadable. */
  --on-accent: #ffffff;
  --success: #2f7a4d;
  --success-wash: rgba(47, 122, 77, 0.12);
  --success-border: rgba(47, 122, 77, 0.35);
  --success-toast: #eaf5ee;
  --error: #b23b3b;
  --error-wash: rgba(178, 59, 59, 0.12);
  --error-border: rgba(178, 59, 59, 0.35);
  --error-toast: #fbecec;
  --info: #3f6fa0;
  --info-wash: rgba(63, 111, 160, 0.12);
  --info-border: rgba(63, 111, 160, 0.35);
  --info-toast: #eaf1f8;
  /* Sealed Boons — chance and divination. In daylight this is simply the
     accent; after dark it earns a colour of its own, so a wager reads
     differently from an ordinary action. */
  --sealed: var(--accent-strong);
  --sealed-wash: var(--accent-wash);
  --shadow: rgba(58, 38, 48, 0.16);
  /* Behind a modal. Dark in both themes — a scrim's job is to push the page
     back — but the brand's plum by day rather than the night ground. */
  --scrim: rgba(58, 38, 48, 0.55);

  --font-display: "Italiana", Georgia, "Times New Roman", serif;
  --font-body: "Mulish", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

/* --- Night: Moonlit Bloom ---------------------------------------------------
   Deliberately not pure black. #0F0F14 is the page, cards sit slightly blue
   above it, and elevated things sit higher again — so depth reads as altitude
   rather than as outlines. Borders stay whisper-quiet on purpose; the brief
   asks for atmosphere, and a control is identified by its fill, its label and
   a bright focus ring rather than by a hard grey box. */
@media (prefers-color-scheme: dark) {
  /* `:not([data-theme="light"])` is what lets an explicit choice win: pick
     Light on a dark phone and this steps aside. */
  :root:not([data-theme="light"]) {
    --bg: #0F0F14;
    --panel: #16182D;
    --panel-2: #21232C;
    --panel-border: #2E2F3B;
    /* Inputs sit a step above the card so they read as wells to type into;
       the divider colour alone is too quiet to bound a control. */
    --input-bg: #21232C;
    --input-border: #3A3C4C;
    --text: #F2F0F6;
    --text-dim: #A9A3B3;
    --accent: #E7C6DC;
    --accent-strong: #CFA6C8;
    --accent-wash: rgba(207, 166, 200, 0.14);
    --on-accent: #0F0F14;
    --success: #8FBF9F;
    --success-wash: rgba(143, 191, 159, 0.14);
    --success-border: rgba(143, 191, 159, 0.38);
    --success-toast: #2e363a;
    /* The daylight error red measures 2.98:1 on the night card — unreadable.
       This coral holds 7.6:1 and sits 54deg off the primary rose, so a
       destructive action can't be mistaken for another shade of the brand. */
    --error: #F0928A;
    --error-wash: rgba(240, 146, 138, 0.14);
    --error-border: rgba(240, 146, 138, 0.38);
    --error-toast: #3a3037;
    --info: #90A9F7;
    --info-wash: rgba(144, 169, 247, 0.14);
    --info-border: rgba(144, 169, 247, 0.38);
    --info-toast: #2e3344;
    --sealed: #A99AF7;
    --sealed-wash: rgba(169, 154, 247, 0.16);
    --shadow: rgba(0, 0, 0, 0.55);
    --scrim: rgba(15, 15, 20, 0.82);
  }
}

/* The same tokens again, for someone who chose Dark on a light system.
   Duplicated rather than shared because CSS cannot put a media-conditional
   selector and a plain one in the same list, and this project has no build
   step to generate one from the other. The risk is that the two drift, so
   tests/test_theme.py asserts they declare exactly the same values — which
   is the guard that makes the duplication safe rather than a liability. */
:root[data-theme="dark"] {
    --bg: #0F0F14;
    --panel: #16182D;
    --panel-2: #21232C;
    --panel-border: #2E2F3B;
    /* Inputs sit a step above the card so they read as wells to type into;
       the divider colour alone is too quiet to bound a control. */
    --input-bg: #21232C;
    --input-border: #3A3C4C;
    --text: #F2F0F6;
    --text-dim: #A9A3B3;
    --accent: #E7C6DC;
    --accent-strong: #CFA6C8;
    --accent-wash: rgba(207, 166, 200, 0.14);
    --on-accent: #0F0F14;
    --success: #8FBF9F;
    --success-wash: rgba(143, 191, 159, 0.14);
    --success-border: rgba(143, 191, 159, 0.38);
    --success-toast: #2e363a;
    /* The daylight error red measures 2.98:1 on the night card — unreadable.
       This coral holds 7.6:1 and sits 54deg off the primary rose, so a
       destructive action can't be mistaken for another shade of the brand. */
    --error: #F0928A;
    --error-wash: rgba(240, 146, 138, 0.14);
    --error-border: rgba(240, 146, 138, 0.38);
    --error-toast: #3a3037;
    --info: #90A9F7;
    --info-wash: rgba(144, 169, 247, 0.14);
    --info-border: rgba(144, 169, 247, 0.38);
    --info-toast: #2e3344;
    --sealed: #A99AF7;
    --sealed-wash: rgba(169, 154, 247, 0.16);
    --shadow: rgba(0, 0, 0, 0.55);
    --scrim: rgba(15, 15, 20, 0.82);
}


* { box-sizing: border-box; }

/* Native controls (checkboxes) follow the theme too. */
input[type=checkbox] { accent-color: var(--accent-strong); }

body {
  margin: 0;
  font-family: var(--font-body);
  background: var(--bg);
  color: var(--text);
  line-height: 1.5;
}

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: 0.01em;
}

header.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1.25rem;
  border-bottom: 1px solid var(--panel-border);
  background: var(--panel);
}

header.topbar .brand {
  font-family: var(--font-display);
  font-size: 1.4rem;
  color: var(--accent-strong);
  text-decoration: none;
  letter-spacing: 0.02em;
}

nav.tabs { display: flex; gap: 1rem; flex-wrap: wrap; }
nav.tabs a {
  color: var(--text-dim);
  text-decoration: none;
  font-size: 0.95rem;
  padding: 0.3rem 0;
  border-bottom: 2px solid transparent;
}
nav.tabs a:hover, nav.tabs a.active { color: var(--text); border-bottom-color: var(--accent); }
nav.tabs.filters { margin-bottom: 1rem; }

.topbar-end { display: flex; align-items: center; gap: 0.6rem; }
.topbar-end form { margin: 0; }
.icon-link {
  display: flex;
  align-items: center;
  justify-content: center;
  /* A 40px box round a 20px glyph: the icon reads small, but the thing you
     have to hit with a thumb does not. */
  width: 40px;
  height: 40px;
  border-radius: 8px;
  color: var(--text-dim);
}
.icon-link svg { width: 20px; height: 20px; }
.icon-link:hover, .icon-link.active { color: var(--accent-strong); background: var(--panel-2); }

/* A checkbox with a sentence of explanation beside it, not a bare word. */
.check {
  display: flex;
  gap: 0.6rem;
  align-items: flex-start;
  margin-bottom: 0.8rem;
  font-size: 0.9rem;
  color: var(--text);
}
.check input { margin-top: 0.15rem; flex: none; }

/* A small set of mutually exclusive choices, drawn as one control rather than
   three loose radios — with the real radios still there underneath, so the
   thing keeps its keyboard behaviour and its name in a screen reader. */
.segmented {
  display: flex;
  border: 1px solid var(--input-border);
  border-radius: 8px;
  overflow: hidden;
  margin-bottom: 0.6rem;
}
.segmented .segment { flex: 1; margin: 0; }
/* Hidden, not `display: none` — a display-none radio is unfocusable, which
   would leave this control reachable by mouse only. */
.segmented input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  pointer-events: none;
}
.segmented span {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0.5rem;
  cursor: pointer;
  font-size: 0.9rem;
  background: var(--input-bg);
  color: var(--text-dim);
}
.segmented .segment + .segment span { border-left: 1px solid var(--input-border); }
.segmented input:checked + span { background: var(--accent-strong); color: var(--on-accent); }
/* Inset, because the control clips its own overflow — an outside ring on the
   first or last segment would be cut in half by the rounded corner. */
.segmented input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: -3px; }

/* API tokens. The revealed token wraps rather than scrolls — it is shown
   once, and a value you have to scroll to select is a value you will
   half-copy. */
.token-list { margin-bottom: 0.9rem; }
.token-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--panel-border);
}
.token-row:last-child { border-bottom: none; }
.token-row form { margin: 0; }
.token-reveal {
  margin-top: 0.4rem;
  word-break: break-all;
  font-size: 0.85rem;
}

/* Marks a row an agent wrote. Quiet on purpose — it is provenance, not a
   warning; the row is an ordinary draft either way. */
.source-badge {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  vertical-align: middle;
  color: var(--text-dim);
  background: var(--panel-2);
  border: 1px solid var(--panel-border);
}

main { max-width: 780px; margin: 0 auto; padding: 1.25rem; }

.card {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 10px;
  padding: 1rem 1.25rem;
  margin-bottom: 1rem;
}

.flash { padding: 0.6rem 0.9rem; border-radius: 8px; margin-bottom: 0.75rem; font-size: 0.92rem; }
.flash-success { background: var(--success-wash); color: var(--success); border: 1px solid var(--success-border); }
.flash-error { background: var(--error-wash); color: var(--error); border: 1px solid var(--error-border); }
.flash-info { background: var(--info-wash); color: var(--info); border: 1px solid var(--info-border); }

.status-badge {
  display: inline-block;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  background: var(--panel-2);
  color: var(--text-dim);
  /* Two words is the common case — "in progress", "cycle active" — and a
     badge is a label, not a paragraph. Left to wrap inside a flex row that
     is squeezing it, it breaks across two lines and the pill grows into a
     lozenge taller than the title beside it. `flex: none` stops the row
     from shrinking it below its text in the first place; nowrap is the
     belt to that braces. */
  white-space: nowrap;
  flex: none;
  align-self: center;
  text-align: center;
}
.status-draft { color: var(--info); }
.status-in_progress { color: var(--accent-strong); }
.status-complete { color: var(--success); }
.status-abandoned, .status-missed { color: var(--error); }

a { color: var(--accent-strong); }
button, input[type=submit] {
  background: var(--accent-strong);
  color: var(--on-accent);
  border: none;
  border-radius: 6px;
  padding: 0.5rem 0.9rem;
  cursor: pointer;
  font-size: 0.92rem;
  font-family: inherit;
}
button:hover, input[type=submit]:hover { background: var(--accent); }
button.secondary { background: transparent; border: 1px solid var(--panel-border); color: var(--text-dim); }
button.secondary:hover { background: var(--panel-2); color: var(--text); }
button.danger { background: transparent; border: 1px solid var(--error); color: var(--error); }
button.danger:hover { background: var(--error-wash); }

input[type=text], input[type=password], input[type=number], textarea, select {
  width: 100%;
  padding: 0.5rem 0.6rem;
  border-radius: 6px;
  border: 1px solid var(--input-border);
  background: var(--input-bg);
  color: var(--text);
  font-family: inherit;
  font-size: 0.95rem;
  margin-bottom: 0.6rem;
}
input:focus, textarea:focus, select:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

label { font-size: 0.85rem; color: var(--text-dim); display: block; margin-bottom: 0.2rem; }

ul.plain { list-style: none; margin: 0; padding: 0; }

.step-row { display: flex; align-items: center; gap: 0.6rem; padding: 0.35rem 0; border-bottom: 1px solid var(--panel-border); }
.step-row:last-child { border-bottom: none; }
/* The step's own form is just a checkbox; the delete button is pushed to the
   far end of the row. Both were inline styles on the elements themselves. */
.step-row form { margin: 0; }
.step-row form.step-remove, .push-end { margin-left: auto; }
/* The "add a step" form, separated from the list above it. */
.step-add { margin-top: 0.9rem; }

/* A note that belongs to the control right above it, not to the page. The
   negative top margin cancels the field's own bottom margin so the two read
   as one unit; it was written inline in five places with three different
   values, which is how a layout drifts. */
.field-note { margin-top: -0.35rem; }
/* A note that follows a block and needs a little air, not a paragraph gap. */
.block-note { margin-top: 0.5rem; }
.step-row.complete .step-name { text-decoration: line-through; color: var(--text-dim); }

.muted { color: var(--text-dim); font-size: 0.88rem; }
.reward-note { font-size: 0.82rem; color: var(--error); }

.milestone-card {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem;
  text-decoration: none;
  color: var(--text);
}
/* The title block is what gives when the row is tight — it has somewhere to
   wrap to. Without this it refuses to shrink below its content and the
   badge is squeezed instead. */
.milestone-card .entity-head { min-width: 0; flex: 1 1 auto; }
.milestone-card:hover { border-color: var(--accent); }
.milestone-card strong { font-family: var(--font-display); font-size: 1.15rem; font-weight: 400; }

/* Buttons and links that sit together at the foot of a card.
   Also the fix for stacked <form> siblings: each form is block-level, so
   two of them (Bind, then Delete draft) rendered flush against each other
   with no gap at all and the second needed a display:inline hack to sit
   anywhere sensible. Wrapped in this, they are flex items with a real gap
   and the hacks come out. */
.actions { display: flex; gap: 0.6rem; flex-wrap: wrap; align-items: center; margin-top: 0.9rem; }
.actions form { margin: 0; }
.actions:first-child { margin-top: 0; }

/* Two <form> elements in a row are two block boxes with nothing between
   them, so a "Claim" form and the "Abandon" form under it rendered flush.
   Separating them once here beats an inline margin on each — which is what
   was there, applied to some and forgotten on others. Inside .actions the
   rule above wins, since those forms are meant to sit shoulder to shoulder. */
.card > form + form,
.card > form + .actions,
.card > .actions + form { margin-top: 1.1rem; }

/* Title (and often a thumbnail) on the left, a badge or a control on the
   right. Was an inline style repeated on five pages, each free to drift. */
.card-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 0.75rem;
}
.card-head > :first-child { min-width: 0; }
/* The card's own padding already spaces the row; .actions' top margin would
   push the buttons below the title they sit beside. */
.card-head > .actions { margin-top: 0; }

/* A card that *is* the row — a history entry, say, with its subject on the
   left and a timestamp or badge on the right. Same shape as .card-head, one
   level up. Was an inline display:flex on three different pages. */
.card.row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 0.75rem;
}
.card.row > :first-child { min-width: 0; }

/* A card's own padding is the spacing; a leading heading's top margin and a
   trailing paragraph's bottom margin double it up and knock the contents
   off-centre. Both were being cancelled by hand, inline, case by case. */
.card > :first-child, .card-head h1, .card-head h2 { margin-top: 0; }
.card > :last-child { margin-bottom: 0; }

.stat-row { display: flex; justify-content: space-between; padding: 0.4rem 0; border-bottom: 1px solid var(--panel-border); }
.stat-row:last-child { border-bottom: none; }

/* Round, like the Lots editor's thumbnails and for the same reason: at 40px
   a rounded square reads as a UI chip, while a circle reads as a portrait of
   the thing. The illustrations are drawn centred with a wide margin, so the
   mask takes nothing but empty ground. Squares are only ever the *source*
   crop — the cropper still enforces 1:1, which is what a circular mask
   wants. */
.boon-thumb {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--panel-border);
  flex-shrink: 0;
  background: var(--panel-2);
}
.boon-thumb.small { width: 40px; height: 40px; }
.reward-header { display: flex; gap: 0.75rem; align-items: flex-start; }
.boon-with-image { display: flex; gap: 0.75rem; align-items: center; }

/* --- Brand art ---------------------------------------------------------------
   Generated by scripts/build_brand_assets.py. Both marks are sized by height
   or width alone so the source aspect ratio drives the other axis; the width
   and height attributes in the markup reserve the box and stop the topbar
   jumping as the logo loads. */
.brand { display: flex; align-items: center; }
.brand-logo { display: block; height: 30px; width: auto; }

.wordmark { display: block; width: 220px; max-width: 100%; height: auto; margin: 0 auto 0.5rem; }
.login-card { max-width: 360px; margin: 1.5rem auto; }
/* On the stats page the mark is a title plate rather than a hero, and the
   topbar already carries the lettering, so it sits smaller. */
.wordmark-plate { width: 150px; margin-bottom: 1rem; }

/* --- Boon images -----------------------------------------------------------
   Upload control, the square crop modal, and the tap-to-enlarge lightbox.
   All colours come from tokens, so the modal is moonlit after dark like
   everything else. */
/* A thumbnail sitting beside a name — on a card in a list, and beside the
   heading on a detail page. */
.entity-head { display: flex; align-items: center; gap: 0.75rem; min-width: 0; }
.entity-head h1 { margin: 0; }
.entity-head > div { min-width: 0; }

.image-control { margin-top: 0.75rem; }
.image-current { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.6rem; }
.boon-thumb[data-full] { cursor: zoom-in; }

/* The file input itself is hidden: browsers render it inconsistently and it
   can't be styled to match the app's buttons. The label is the control. */
.file-label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 44px;
  padding: 0.4rem 0.9rem;
  border: 1px dashed var(--input-border);
  border-radius: 6px;
  color: var(--accent-strong);
  cursor: pointer;
  font-size: 0.9rem;
}
.file-label:hover { border-color: var(--accent); color: var(--accent); }
.file-label input[type=file] {
  position: absolute;
  width: 1px; height: 1px;
  opacity: 0;
  pointer-events: none;
}
.file-label::before { content: "\1F5BC\FE0F"; font-size: 1rem; }

/* Scroll lock while a modal is up. */
body.crop-open { overflow: hidden; }

/* `display: flex` below beats the user-agent rule that the `hidden` attribute
   relies on, so setting el.hidden would leave both of these on screen. This
   is what actually closes them. */
.crop-modal[hidden], .lightbox[hidden] { display: none; }

.crop-modal, .lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  padding-bottom: calc(1rem + env(safe-area-inset-bottom));
  background: var(--scrim);
}
.crop-panel {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 12px;
  padding: 1rem;
  width: min(30rem, 100%);
  box-shadow: 0 12px 40px var(--shadow);
}
/* Bounded so a tall photo can't push the buttons off a phone screen. */
.crop-stage { max-height: min(60vh, 24rem); }
.crop-stage img { display: block; max-width: 100%; }
.crop-actions { display: flex; gap: 0.5rem; justify-content: flex-end; margin-top: 0.9rem; }

.lightbox img {
  max-width: 100%;
  max-height: 100%;
  border-radius: 10px;
  box-shadow: 0 12px 40px var(--shadow);
  cursor: zoom-out;
}

/* Cropper.js draws its own chrome; tint it to the brand rather than its
   default blue. */
.cropper-view-box, .cropper-face { outline-color: var(--accent); }
.cropper-line, .cropper-point { background-color: var(--accent); }

/* --- Sealed boons ----------------------------------------------------------
   A boon with lots pays out a draw rather than a known thing. The badge and
   the odds list are the app being honest about its own weighting: only which
   lot you get is concealed, never the pool or the maths. */
.sealed-badge {
  display: inline-block;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
  background: var(--sealed-wash);
  color: var(--sealed);
  vertical-align: 0.1em;
}

details.disclosure { margin-top: 0.75rem; border-top: 1px solid var(--panel-border); padding-top: 0.6rem; }
details.disclosure > summary {
  cursor: pointer;
  font-family: var(--font-display);
  font-size: 1.05rem;
  color: var(--accent-strong);
  min-height: 32px;
}
.disclosure-body { padding-top: 0.6rem; }
/* Only the lots panel wears the divination colour. Edit is an ordinary
   action, and the sealed accent is meant to be spent sparingly — otherwise
   it stops signalling "this one is a wager". */
details.lots > summary { color: var(--sealed); }
/* An advanced option folded into a form. Quieter than a section heading —
   it is a footnote on the button below it, not a part of the page. */
details.advanced { border-top: none; margin-top: 0.25rem; padding-top: 0; }
details.advanced > summary { font-family: inherit; font-size: 0.9rem; color: var(--text-dim); }
details.advanced > summary:hover { color: var(--accent-strong); }
/* The Lots editor.

   Was a single flex row per lot with the name, weight, odds, description,
   URL, upload control and remove button all competing for one line: on a
   phone the text inputs collapsed to about six characters and their
   placeholders truncated mid-word ("description (o", "image URL (h"). Now
   each lot is its own card — a fixed left column holding the thumbnail and
   the weight that governs it, and a full-width right column for the fields,
   which is the one thing that lets a text input actually be wide enough to
   read. */
.lot-card {
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  gap: 0.5rem 1rem;
  align-items: start;
  padding: 1rem;
  margin-bottom: 0.85rem;
  border: 1px solid var(--panel-border);
  border-radius: 10px;
  background: var(--panel-2);
}
.lot-card:last-of-type { margin-bottom: 0; }

/* Thumbnail, then the weight that decides how often it comes up — the two
   things about a lot that are worth seeing at a glance, kept together. */
.lot-aside { display: flex; flex-direction: column; align-items: center; gap: 0.3rem; }
.lot-aside label { margin: 0.25rem 0 0; font-size: 0.7rem; letter-spacing: 0.08em; text-transform: uppercase; }
.lot-aside .weight-input { width: 100%; margin-bottom: 0; text-align: center; }

.lot-thumb {
  width: 5.5rem;
  height: 5.5rem;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--panel-border);
  background: var(--panel);
  flex: none;
}
.lot-thumb[data-full] { cursor: zoom-in; }
/* Same box as a real thumbnail so adding an image doesn't reflow the card. */
.lot-thumb-empty, .lot-thumb-add {
  display: flex;
  align-items: center;
  justify-content: center;
  border-style: dashed;
  color: var(--text-dim);
}
.lot-thumb-empty svg { width: 1.75rem; height: 1.75rem; }
.lot-thumb-add { font-size: 1.6rem; color: var(--accent-strong); }

.lot-main { min-width: 0; }
.lot-title-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 0.35rem;
}
/* Enough room to read a lot name before Remove is allowed to sit beside it;
   below that the button wraps to its own line rather than crushing it. */
.lot-title-row .lot-name-input { flex: 1 1 11rem; }
.lot-title-row form { margin: 0; }

/* The name reads as the lot's title and edits in place. Transparent until
   you touch it, so the card looks like a heading rather than a form. */
.lot-name-input {
  flex: 1;
  min-width: 0;
  margin-bottom: 0;
  font-family: var(--font-display);
  font-size: 1.15rem;
  background: transparent;
  border-color: transparent;
  padding: 0.25rem 0.4rem;
}
.lot-name-input:hover { border-color: var(--panel-border); }
.lot-name-input:focus { background: var(--input-bg); border-color: var(--input-border); }

.lot-odds {
  font-variant-numeric: tabular-nums;
  color: var(--sealed);
  font-size: 0.8rem;
  text-align: center;
  /* "90.9% of cast" breaks after "of" in the stacked layout otherwise —
     the same fault as the status badge, in a different corner. */
  white-space: nowrap;
}
.lot-cast { flex: none; font-size: 0.78rem; }
.lot-add-title { font-family: var(--font-display); font-size: 1.15rem; font-weight: 400; }
.lot-count {
  font-variant-numeric: tabular-nums;
  color: var(--sealed);
  background: var(--sealed-wash);
  border-radius: 999px;
  padding: 0.05rem 0.5rem;
  font-size: 0.85rem;
}
.lot-hint {
  display: flex;
  gap: 0.5rem;
  margin: 0.85rem 0 0;
  padding: 0.6rem 0.75rem;
  border-radius: 8px;
  background: var(--panel-2);
  color: var(--text-dim);
  font-size: 0.85rem;
}

/* The odds a claimer sees before committing — read-only, on the pickers and
   detail pages. */
.lot-odds-list { list-style: none; margin: 0.35rem 0 0; padding: 0; }
.lot-odds-list li {
  display: flex;
  justify-content: space-between;
  gap: 0.75rem;
  font-size: 0.88rem;
  color: var(--text-dim);
  padding: 0.15rem 0;
}
.lot-odds-list li span:last-child { font-variant-numeric: tabular-nums; color: var(--sealed); }
.lot-drawn {
  font-family: var(--font-display);
  font-size: 1.3rem;
  color: var(--success);
}

/* Cairns — the waypoints on a thread.
   Reuses the lot card's shape: a bordered, slightly-raised block per item so
   several stack legibly without becoming a wall of inputs. */
.cairn-card {
  padding: 0.85rem 1rem;
  margin-bottom: 0.75rem;
  border: 1px solid var(--panel-border);
  border-radius: 10px;
  background: var(--panel-2);
}
.cairn-card:last-of-type { margin-bottom: 0; }
/* Reached and unclaimed: the one that wants acting on. */
.cairn-card.ready { border-color: var(--accent); }
.cairn-card.claimed { opacity: 0.72; }
.cairn-card form { margin: 0; }
.cairn-card select, .cairn-card textarea { margin-bottom: 0.6rem; }

.cairn-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.5rem;
}
.cairn-days {
  font-family: var(--font-display);
  font-size: 1.2rem;
  white-space: nowrap;
}
.cairn-add { margin-top: 0.9rem; }
.cairn-add-row { display: flex; gap: 0.6rem; align-items: flex-start; }
.cairn-add-row input { flex: 1; min-width: 0; margin-bottom: 0; }
.cairn-add-row button { flex: none; }

.m0 { margin: 0; }

/* Thread streak counter */
.streak-card { text-align: center; }
.streak-count {
  font-family: var(--font-display);
  font-size: clamp(3.5rem, 18vw, 5.5rem);
  line-height: 1;
  color: var(--accent-strong);
  font-variant-numeric: tabular-nums;
}
.streak-count.reached { color: var(--success); }
.streak-label { color: var(--text-dim); font-size: 0.95rem; margin-top: 0.25rem; }
.streak-label strong { color: var(--success); }
.streak-bar {
  height: 6px;
  border-radius: 999px;
  background: var(--panel-2);
  overflow: hidden;
  margin-top: 0.9rem;
}
.streak-bar-fill {
  height: 100%;
  background: var(--accent);
  border-radius: 999px;
  transition: width 0.3s ease;
}

/* ==========================================================================
   Mobile
   The app is a phone-installed PWA first, so below MOBILE_BP the top nav is
   replaced by a thumb-reachable fixed bottom bar and the topbar keeps only
   the brand + sign out.
   ========================================================================== */

/* iOS zooms the whole page when a focused input's font-size is under 16px,
   which reads as the layout lurching on every tap into a field. 1rem is the
   floor, not a preference. */
input[type=text], input[type=password], input[type=number], textarea, select {
  font-size: 1rem;
}

/* Comfortable touch targets. 44px is Apple's documented minimum. */
button, input[type=submit] { min-height: 44px; }
button.tiny { min-height: 32px; padding: 0.2rem 0.6rem; font-size: 0.8rem; }

/* Filter tabs (Draft / Running / ...) scroll rather than wrap on narrow screens. */
nav.tabs.filters {
  overflow-x: auto;
  flex-wrap: nowrap;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding-bottom: 0.2rem;
}
nav.tabs.filters::-webkit-scrollbar { display: none; }
nav.tabs.filters a { white-space: nowrap; }

/* --- Create-form disclosure ------------------------------------------------
   The create form used to sit above the list, pushing the actual content
   below the fold on a phone. Collapsed to a single row, it stays discoverable
   in a predictable spot without costing a screenful. */
.card.new-form { padding: 0; overflow: hidden; }
.new-form > summary {
  list-style: none;
  cursor: pointer;
  padding: 0.85rem 1.25rem;
  font-family: var(--font-display);
  font-size: 1.1rem;
  color: var(--accent-strong);
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
}
.new-form > summary::-webkit-details-marker { display: none; }
.new-form > summary::before {
  content: "+";
  font-family: var(--font-body);
  font-size: 1.3rem;
  line-height: 1;
  transition: transform 0.2s ease;
}
.new-form[open] > summary::before { transform: rotate(45deg); }
.new-form[open] > summary { border-bottom: 1px solid var(--panel-border); }
.new-form .new-form-body { padding: 1rem 1.25rem 1.25rem; }

/* --- Finished-work peek ----------------------------------------------------
   The list pages are working surfaces. Finished items are collapsed here and
   rendered dimmer, smaller and without their descriptions, so they read as
   background rather than as competing with what's live. */
.card.peek { padding: 0; overflow: hidden; }
.peek > summary {
  list-style: none;
  cursor: pointer;
  padding: 0.85rem 1.25rem;
  font-family: var(--font-display);
  font-size: 1.05rem;
  color: var(--text-dim);
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
}
.peek > summary::-webkit-details-marker { display: none; }
.peek > summary::before {
  content: "";
  width: 0; height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform 0.2s ease;
}
.peek[open] > summary::before { transform: rotate(90deg); }
.peek[open] > summary { border-bottom: 1px solid var(--panel-border); }
.peek-body { padding: 0.4rem 1.25rem 1rem; }
.peek-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--panel-border);
  font-size: 0.92rem;
  color: var(--text-dim);
  text-decoration: none;
  min-height: 32px;
}
.peek-row:hover { color: var(--text); }
.peek-row + p { margin-top: 0.75rem; }
.peek-row:last-of-type { border-bottom: none; }

@media (prefers-reduced-motion: reduce) {
  .peek > summary::before { transition: none; }
}

/* --- Flash toasts ----------------------------------------------------------
   Saving no longer jumps to the top of the page, so a flash rendered inline
   at the top would go unseen. Fixed positioning keeps it visible wherever
   the user is on the page. */
.toasts {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  top: calc(0.75rem + env(safe-area-inset-top));
  z-index: 50;
  width: min(32rem, calc(100vw - 1.5rem));
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  pointer-events: none;
}
.toasts .flash {
  margin: 0;
  pointer-events: auto;
  box-shadow: 0 6px 20px var(--shadow);
  background: var(--panel);
  animation: toast-in 0.18s ease-out;
}
.toasts .flash-success { background: var(--success-toast); }
.toasts .flash-error { background: var(--error-toast); }
.toasts .flash-info { background: var(--info-toast); }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(-0.4rem); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .toasts .flash { animation: none; }
  .streak-bar-fill { transition: none; }
  .new-form > summary::before { transition: none; }
}

/* --- Bottom nav ------------------------------------------------------------ */
.bottom-nav { display: none; }

@media (max-width: 640px) {
  header.topbar { padding: 0.6rem 1rem; }
  header.topbar .tabs { display: none; }          /* replaced by .bottom-nav */

  main {
    padding: 1rem;
    /* Clear the fixed bar plus the iPhone home indicator. */
    padding-bottom: calc(4.75rem + env(safe-area-inset-bottom));
  }

  .bottom-nav {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 40;
    background: var(--panel);
    border-top: 1px solid var(--panel-border);
    padding-bottom: env(safe-area-inset-bottom);
  }
  .bottom-nav a {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.15rem;
    padding: 0.45rem 0.2rem 0.5rem;
    min-height: 52px;
    text-decoration: none;
    color: var(--text-dim);
    font-size: 0.68rem;
    letter-spacing: 0.02em;
  }
  .bottom-nav a svg { width: 21px; height: 21px; stroke-width: 1.6; }
  .bottom-nav a.active { color: var(--accent-strong); }
  .bottom-nav a.active svg { stroke-width: 2.1; }

  .toasts { top: auto; bottom: calc(4.75rem + env(safe-area-inset-bottom)); }

  h1 { font-size: 1.9rem; }
  .card { padding: 0.9rem 1rem; }

  /* The lot card is two columns on a wide screen. On a 390px phone that
     leaves about 200px for the fields, and an image URL truncates inside it
     — measured, not assumed. So the thumbnail and its weight move above the
     fields into a row, and the fields get the full 318px. */
  .lot-card { grid-template-columns: 1fr; }
  .lot-aside { flex-direction: row; align-items: center; gap: 0.6rem; }
  .lot-aside label { margin: 0; }
  .lot-aside .weight-input { width: 4.5rem; }
  .lot-thumb { width: 4rem; height: 4rem; }

  .brand-logo { height: 26px; }
  .wordmark { width: 190px; }
  .wordmark-plate { width: 130px; }

}
