/* brisage-tracker — styles.
 *
 * Split out of index.html at ~2,300 lines, where a diff could no longer tell
 * you whether a change was visual or behavioural. Served from this box, never
 * a CDN: the page makes zero external requests, which is a privacy property as
 * much as an offline one.
 *
 * Everything composes from the tokens in :root — no magic numbers in component
 * rules, so spacing and colour stay consistent as things get added.
 */
/* ==========================================================================
   Design tokens. Everything below composes from these — no magic numbers in
   component rules, so spacing and colour stay consistent as things get added.
   ========================================================================== */
:root {
  /* Surfaces: four layers of depth rather than one flat grey. */
  --bg-0:#0e1116;         /* page */
  --bg-1:#151a22;         /* cards */
  --bg-2:#1c222c;         /* raised: header cells, inputs */
  --bg-3:#242c38;         /* hover */
  --line:#2a323f;
  --line-soft:#20272f;

  --fg:#e8ecf2;
  --fg-2:#9aa5b6;         /* secondary text */
  --fg-3:#6b7688;         /* tertiary / disabled */

  /* Amber primary: this is a Dofus tool, and kamas are gold. */
  --accent:#f0b429;
  --accent-dim:#8a6516;
  --accent-glow:rgba(240,180,41,.14);

  --good:#4ade80;  --good-bg:rgba(74,222,128,.12);
  --bad:#f87171;   --bad-bg:rgba(248,113,113,.12);
  --warn:#fbbf24;  --warn-bg:rgba(251,191,36,.12);
  --info:#60a5fa;  --info-bg:rgba(96,165,250,.12);
  --ours:#60a5fa;
  --theirs:#c084fc;

  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:24px; --sp-6:32px;
  --r-sm:6px; --r-md:10px; --r-lg:14px; --r-full:999px;

  --shadow: 0 1px 2px rgba(0,0,0,.4), 0 4px 16px rgba(0,0,0,.24);
  --shadow-lg: 0 8px 32px rgba(0,0,0,.45);
  --ease: cubic-bezier(.4,0,.2,1);
}

* { box-sizing:border-box; }
html { -webkit-text-size-adjust:100%; }

body {
  margin:0;
  background:
    radial-gradient(1200px 600px at 15% -10%, rgba(240,180,41,.05), transparent 60%),
    radial-gradient(900px 500px at 95% 0%, rgba(96,165,250,.05), transparent 55%),
    var(--bg-0);
  background-attachment:fixed;
  color:var(--fg);
  font:15px/1.55 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  -webkit-font-smoothing:antialiased;
}

:focus-visible { outline:2px solid var(--accent); outline-offset:2px; border-radius:var(--r-sm); }
::selection { background:var(--accent-glow); }

/* The hidden attribute must always win. Any rule that sets its own display
   (grid, flex) on an element the JS toggles with .hidden would otherwise
   override the UA's [hidden]{display:none} — the market grid on the Charts
   tab was the first to be bitten, showing both sub-views at once. */
[hidden] { display:none !important; }

/* Scrollbars, so the dark theme does not break at the edges. */
* { scrollbar-width:thin; scrollbar-color:var(--line) transparent; }
*::-webkit-scrollbar { width:10px; height:10px; }
*::-webkit-scrollbar-thumb { background:var(--line); border-radius:var(--r-full);
                             border:2px solid transparent; background-clip:content-box; }
*::-webkit-scrollbar-thumb:hover { background:var(--bg-3); background-clip:content-box; }

/* ------------------------------------------------------------------ header */
header {
  position:sticky; top:0; z-index:50;
  display:flex; align-items:center; gap:var(--sp-5); flex-wrap:wrap;
  padding:var(--sp-3) var(--sp-5);
  background:rgba(14,17,22,.82);
  backdrop-filter:blur(14px) saturate(160%);
  border-bottom:1px solid var(--line-soft);
}
.brand { display:flex; align-items:center; gap:var(--sp-2); font-weight:650;
         font-size:15px; letter-spacing:-.01em; }
.brand svg { width:24px; height:24px; flex:none; }
.brand em { font-style:normal; color:var(--fg-3); font-weight:400; font-size:12px;
            padding-left:var(--sp-2); border-left:1px solid var(--line);
            margin-left:var(--sp-1); }

nav { display:flex; gap:var(--sp-1); }
nav button {
  position:relative; background:none; border:none; color:var(--fg-2);
  cursor:pointer; font:inherit; font-size:13.5px; font-weight:500;
  padding:var(--sp-2) var(--sp-3); border-radius:var(--r-sm);
  transition:color .15s var(--ease), background .15s var(--ease);
}
nav button:hover { color:var(--fg); background:var(--bg-2); }
nav button.on { color:var(--accent); background:var(--accent-glow); }
nav button { display:flex; align-items:center; gap:6px; }
.ico { width:15px; height:15px; flex:none; fill:none; stroke:currentColor;
       stroke-width:1.9; stroke-linecap:round; stroke-linejoin:round; }
@media (max-width:820px) { nav button span { display:none; } }

/* Language picker. Flags rather than a <select>: recognisable without reading,
   which matters most in the situation where you need it — the interface is
   currently in a language you did not mean to pick. */
.langpick { display:flex; gap:var(--sp-1); }
.langpick button {
  padding:0; border:1px solid var(--line); background:none; cursor:pointer;
  border-radius:3px; overflow:hidden; line-height:0; opacity:.45;
  transition:opacity .15s var(--ease), border-color .15s var(--ease),
             transform .15s var(--ease);
}
.langpick button svg { width:24px; height:16px; display:block; }
.langpick button:hover { opacity:.85; transform:translateY(-1px); }
.langpick button.on { opacity:1; border-color:var(--accent);
                      box-shadow:0 0 0 2px var(--accent-glow); }

/* Runes render smaller than items: a rune is a detail of a break, an item is
   the subject of a row, and equal sizing made a five-rune list shout louder
   than the item it came from. */
.rune-chip { display:inline-flex; align-items:center; gap:4px;
             background:var(--bg-2); border:1px solid var(--line-soft);
             border-radius:var(--r-full); padding:2px 8px 2px 3px;
             margin:2px 3px 2px 0; font-size:12px; white-space:nowrap; }
.rune-chip img { width:18px; height:18px; object-fit:contain; flex:none; }
.rune-chip .q { color:var(--accent); font-weight:650;
                font-variant-numeric:tabular-nums; }
.rune-chip.unmatched { border-style:dashed; opacity:.75; }
.runeicon { width:20px; height:20px; object-fit:contain; flex:none; }

.right { margin-left:auto; display:flex; gap:var(--sp-3); align-items:center; }
#status { color:var(--fg-3); font-size:12px; font-variant-numeric:tabular-nums; }

/* ------------------------------------------------------------- form fields */
select, input, textarea {
  background:var(--bg-2); color:var(--fg);
  border:1px solid var(--line); border-radius:var(--r-sm);
  padding:7px 10px; font:inherit; font-size:13.5px;
  transition:border-color .15s var(--ease), box-shadow .15s var(--ease);
}
select:hover, input:hover, textarea:hover { border-color:var(--bg-3); }
select:focus, input:focus, textarea:focus {
  outline:none; border-color:var(--accent);
  box-shadow:0 0 0 3px var(--accent-glow);
}
select { cursor:pointer; padding-right:var(--sp-5);
  appearance:none;
  background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath fill='%239aa5b6' d='M3 4.5L6 8l3-3.5z'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 8px center;
}

main { padding:var(--sp-5); max-width:1600px; margin:0 auto; }
section { display:none; animation:fade .22s var(--ease); }
section.on { display:block; }
@keyframes fade { from { opacity:0; transform:translateY(4px);} to { opacity:1; transform:none; } }
@media (prefers-reduced-motion:reduce) { *,*::before,*::after { animation:none !important; transition:none !important; } }

/* --------------------------------------------------------------- surfaces */
.card, .explain, .filters, form {
  background:linear-gradient(180deg, var(--bg-1), rgba(21,26,34,.7));
  border:1px solid var(--line-soft);
  border-radius:var(--r-lg);
  box-shadow:var(--shadow);
}
.explain { padding:var(--sp-4) var(--sp-5); margin-bottom:var(--sp-4); }
.explain h2 { margin:0 0 var(--sp-2); font-size:14px; font-weight:650;
              letter-spacing:-.01em; display:flex; align-items:center; gap:var(--sp-2); }
.explain h2::before { content:''; width:3px; height:14px; border-radius:2px;
                      background:var(--accent); flex:none; }
.explain p { margin:0 0 var(--sp-3); color:var(--fg-2); font-size:13.5px; max-width:100ch; }
.explain b { color:var(--fg); font-weight:600; }

/* Collapsible intro cards. The heading is a real button (keyboard contract for
   free); the body animates through the 0fr/1fr grid trick so it can be any
   height. State is remembered per card — see the .extoggle wiring in app.js. */
.extoggle { display:flex; align-items:center; gap:var(--sp-2); flex:1;
            background:none; border:0; padding:0; margin:0; color:inherit;
            font:inherit; cursor:pointer; text-align:left;
            border-radius:var(--r-sm); }
.extoggle .chev { margin-left:auto; color:var(--fg-3); }
.extoggle:hover .chev { color:var(--fg); }
.extoggle[aria-expanded="false"] .chev { transform:rotate(-90deg); }
.exbody { display:grid; grid-template-rows:1fr; opacity:1; }
.exbody > .exinner { overflow:hidden; min-height:0; }
.exbody.closed { grid-template-rows:0fr; opacity:0; }
@media (prefers-reduced-motion:no-preference) {
  .exbody { transition:grid-template-rows .15s var(--ease), opacity .15s var(--ease); }
  .extoggle .chev { transition:transform .15s var(--ease), color .15s var(--ease); }
}

/* The explainer. Collapsed by default — it is for the two people who did not
   build this, and for anyone coming back after a month. */
.howto { margin:var(--sp-3) 0; border:1px solid var(--line-soft);
         border-radius:var(--r-md); background:rgba(0,0,0,.16); }
.howto summary { cursor:pointer; padding:10px var(--sp-4); font-size:13px;
                 font-weight:600; color:var(--accent); list-style:none;
                 display:flex; align-items:center; gap:var(--sp-2); }
.howto summary::-webkit-details-marker { display:none; }
.howto summary::before { content:'?'; display:inline-flex; align-items:center;
  justify-content:center; width:16px; height:16px; flex:none; border-radius:50%;
  background:var(--accent); color:var(--bg-0); font-size:11px; font-weight:800; }
.howto[open] summary { border-bottom:1px solid var(--line-soft); }
.howto-body { padding:var(--sp-3) var(--sp-4) var(--sp-4); }
.howto-body ol { margin:0; padding-left:var(--sp-5); }
.howto-body li { margin-bottom:var(--sp-3); color:var(--fg-2); font-size:13px;
                 line-height:1.55; }
.howto-body b { color:var(--fg); }
.howto-body code { background:rgba(0,0,0,.35); padding:1px 5px; border-radius:var(--r-sm);
                   font-size:11.5px; }
.howto-body p { margin:var(--sp-3) 0 0; padding-top:var(--sp-3); font-size:13px;
                color:var(--fg-2); border-top:1px solid var(--line-soft); }

.thnote { display:block; font-size:9px; font-weight:500; letter-spacing:.02em;
           text-transform:none; color:var(--fg-3); opacity:.8; margin-top:1px; }
.never { color:var(--bad); font-weight:700; text-transform:uppercase;
         font-size:12px; letter-spacing:.04em; }
.capped { color:var(--warn); font-size:10px; }
/* Error severity is judged against the size of the coefficient it belongs to. */
.err.small { opacity:.65; font-weight:500; }
.err.med   { font-weight:650; }
.err.big   { font-weight:800; text-decoration:underline; text-underline-offset:2px; }

.keys { display:flex; gap:var(--sp-5); flex-wrap:wrap; font-size:12px;
        padding-top:var(--sp-3); border-top:1px solid var(--line-soft); }
.key { display:flex; gap:var(--sp-2); align-items:center; color:var(--fg-2); }
.swatch { width:10px; height:10px; border-radius:3px; flex:none; }

/* ----------------------------------------------------------------- filters */
.filters { padding:var(--sp-4) var(--sp-5); margin-bottom:var(--sp-4); }

/* One row per question being asked, and a vertical rule between groups so two
   adjacent strips of chips do not read as one strip of ten. */
/* Every cell is label-on-top, control-underneath, and the row aligns on the
   TOP of those labels. Bottom-alignment was the problem in the screenshot:
   cells of different heights bottom-aligned, so their labels floated at
   different heights and the whole bar read as unaligned. */
.filterrow { display:flex; gap:var(--sp-4); align-items:flex-start; flex-wrap:wrap; }
.filterrow > * { min-height:0; }
.filterrow .f,
.filterrow .fgroup > .f { display:flex; flex-direction:column; gap:var(--sp-1); }
/* Label boxes share one height so the controls beneath them line up, even
   where a group has no label of its own. */
.filterrow .f > span:first-child,
.filterrow .fgroup > .presets::before { min-height:14px; }
.filterrow .fgroup { align-items:flex-end; }
.filterrow .fgroup > .presets { padding-bottom:1px; }
.filterrow.adv { margin-top:var(--sp-4); padding-top:var(--sp-4);
                 border-top:1px dashed var(--line); }
.filterrow .grow { flex:1 1 240px; }
.filterrow .grow input { width:100%; }

/* A labelled control plus its own presets, kept together. */
.fgroup { display:flex; gap:var(--sp-2);
          padding-left:var(--sp-4); border-left:1px solid var(--line-soft); }
.presets.tight { gap:3px; }
.presets.tight .chip { padding:5px 9px; font-size:11.5px; }

.tierbox.standalone { align-self:flex-end; padding:7px 0 7px var(--sp-4);
                      border-left:1px solid var(--line-soft); }

/* Collapse toggle for the set-once filters. */
.morefilters {
  display:inline-flex; align-items:center; gap:6px; align-self:flex-end;
  background:var(--bg-2); color:var(--fg-2); border:1px solid var(--line);
  border-radius:var(--r-sm); padding:7px 12px; font:inherit; font-size:13px;
  cursor:pointer; transition:all .15s var(--ease);
}
.morefilters:hover { color:var(--fg); border-color:var(--bg-3); }
.morefilters[aria-expanded="true"] { color:var(--accent); border-color:var(--accent-dim);
                                     background:var(--accent-glow); }
.morefilters .chev { transition:transform .18s var(--ease); }
.morefilters.open .chev { transform:rotate(180deg); }
.fcount { background:var(--accent); color:var(--bg-0); border-radius:var(--r-full);
          font-size:10px; font-weight:800; padding:1px 6px; }

/* Collapsed does not mean invisible: whatever is still filtering says so, and
   can be switched off from here. A filter you cannot see is how a table ends up
   empty for reasons nobody can explain. */
.activepills { display:flex; gap:var(--sp-2); flex-wrap:wrap; margin-top:var(--sp-3); }
.activepills button {
  display:inline-flex; align-items:center; gap:6px;
  background:var(--accent-glow); color:var(--accent);
  border:1px solid var(--accent-dim); border-radius:var(--r-full);
  padding:3px 8px 3px 11px; font:inherit; font-size:11.5px; font-weight:600;
  cursor:pointer;
}
.activepills button:hover { background:var(--bad-bg); color:var(--bad);
                            border-color:var(--bad); }
.activepills button::after { content:'\00d7'; font-size:14px; line-height:1; opacity:.8; }
.filters .f { display:flex; flex-direction:column; gap:var(--sp-1); margin:0;
              color:var(--fg-3); font-size:11px; font-weight:500;
              text-transform:uppercase; letter-spacing:.04em; }
.range { display:flex; align-items:center; gap:var(--sp-2); color:var(--fg-3); }
.range input { width:66px; text-align:center; }
.presets { display:flex; gap:var(--sp-1); flex-wrap:wrap; }
.chip {
  display:inline-flex; align-items:center; justify-content:center;
  min-height:32px;
  background:var(--bg-2); color:var(--fg-2); border:1px solid var(--line);
  border-radius:var(--r-full); padding:5px 12px; font:inherit; font-size:12px;
  font-weight:500; cursor:pointer;
  transition:all .15s var(--ease);
}
.chip:hover { color:var(--fg); border-color:var(--bg-3); transform:translateY(-1px); }
.chip.on { color:var(--bg-0); background:var(--accent); border-color:var(--accent);
           font-weight:600; }
#filterCount { color:var(--fg-3); font-size:12px; font-variant-numeric:tabular-nums;
               margin-left:auto; align-self:flex-end; white-space:nowrap;
               padding-bottom:9px; }

/* Tier checkboxes. These had NO rules at all, so each label inherited
   `display:block` from the form rule and the four of them stacked into a
   column four rows tall inside a filter bar one row tall. Flex, and override
   the uppercase 11px inherited from `.f`. */
.tiers { display:flex; align-items:center; gap:var(--sp-4); flex-wrap:wrap;
         padding-top:2px; min-height:32px; }
.tierbox {
  display:inline-flex; align-items:center; gap:6px; margin:0;
  font-size:12.5px; font-weight:500; text-transform:none; letter-spacing:0;
  color:var(--fg-2); white-space:nowrap; cursor:pointer;
  transition:color .15s var(--ease);
}
.tierbox:hover { color:var(--fg); }
.tierbox input { width:auto; margin:0; flex:none; cursor:pointer; }
/* Each box carries its tier's colour, so the filter and the row badge it
   controls are recognisably the same thing. */
.tierbox.good input { accent-color:var(--good); }
.tierbox.warn input { accent-color:var(--warn); }
.tierbox.bad  input { accent-color:var(--bad); }
.tierbox.dim  input { accent-color:var(--fg-3); }

/* ------------------------------------------------- segmented toggle (seg) */
/* Used for the chart's x-axis mode toggle. It once also sat inside a sortable
   <th> as the benefit column's ROI/kamas switch, guarded by a stopPropagation;
   both the switch and the guard are gone — Benefit and ROI are two columns now. */
.seg { display:inline-flex; gap:0; border:1px solid var(--line); border-radius:var(--r-full);
       overflow:hidden; background:var(--bg-1); vertical-align:middle; }
.seg button {
  background:none; border:none; color:var(--fg-3); cursor:pointer;
  font:inherit; font-size:10px; font-weight:600; letter-spacing:.03em;
  text-transform:uppercase; padding:3px 9px; line-height:1.6;
  transition:background .15s var(--ease), color .15s var(--ease);
}
.seg button + button { border-left:1px solid var(--line); }
.seg button:hover { color:var(--fg); background:var(--bg-3); }
.seg button.on { background:var(--accent); color:#1a1204; }
th .seg { margin-left:var(--sp-2); }
/* The chart toggle is bigger: it is a primary control, not a column option. */
.seg.lg button { font-size:11px; padding:5px 12px; text-transform:none;
                 letter-spacing:0; font-weight:500; }

/* ------------------------------------------ measured-vs-scraped value pair */
/* An in-game reading and the API's claim for the same item. The game value is
   authoritative and reads as primary; the API keeps its place underneath
   because the gap between them is the only calibration signal we have. */
/* Every predicted figure carries the number of observations behind it. This is
   a project rule, not decoration: n=4,301 and n=1 look identical without it,
   and the two prediction columns sit side by side. */
.nbadge { display:inline-block; font-size:9.5px; font-weight:700; letter-spacing:.02em;
          padding:1px 5px; border-radius:var(--r-full); margin-left:var(--sp-1);
          background:var(--bg-3); color:var(--fg-2); font-variant-numeric:tabular-nums; }
.nbadge.bad { background:var(--bad-bg); color:var(--bad); }
/* "no interval" is spelled out. A dash would read as a narrow one. */
.nointerval { font-style:italic; color:var(--bad); }

.gamev { color:#38bdf8; font-weight:650; }
.apiv  { color:var(--fg-3); }
.err   { font-weight:600; }
.err.hi { color:var(--bad); }     /* API reads high — overstates profitability */
.err.lo { color:var(--info); }

/* ------------------------------------------------------------------- table */
.scroll { border:1px solid var(--line-soft); border-radius:var(--r-lg);
          overflow:auto; background:var(--bg-1); box-shadow:var(--shadow);
          max-height:calc(100vh - 260px);
          /* Clip the corners properly. Without this the sticky header paints
             over the rounded border at the top edge and the right-hand column
             appears to spill past the frame. */
          isolation:isolate; }
/* No stray outer edge: the container already draws the border, so the last
   column must not draw one of its own next to it. */
tbody tr td:last-child, thead th:last-child { border-right:none; }
table { border-collapse:separate; border-spacing:0; width:100%;
        font-variant-numeric:tabular-nums; font-size:13.5px; }

/* Cell padding is sp-3 on the sides, not sp-4 (2026-08-10, the no-scroll
   pass): 8px per cell edge x 9 columns is 72px, which together with the
   header renames (Runes récupérées → Brut) and the badge-to-tooltip moves is
   what brings the width budget under a ~1400px content area. The budget,
   summed from worst-case French content: item 280 (flexes, ellipsis) +
   coefficient ~120 + break-even ~100 + estimated ~90 + cost ~170 + gross ~80
   + net ~110 + ROI ~50 + confidence ~90 = 1090, plus 9 x 24px padding = 216
   and borders ≈ 1320px total. */
thead tr.cols th {
  position:sticky; z-index:2;
  padding:var(--sp-3); text-align:right; white-space:nowrap;
  font-weight:600; font-size:11px; letter-spacing:.05em; text-transform:uppercase;
  color:var(--fg-3); cursor:pointer; user-select:none;
  /* Fully opaque. A translucent sticky header lets rows ghost through it. */
  background:#1c222c;
  border-bottom:1px solid var(--line);
  transition:color .15s var(--ease);
}
thead tr.cols th:first-child { text-align:left; }

/* Group header row. Nine numbers side by side need to say which question each
   of them answers, or they read as one undifferentiated wall. */
thead tr.groups th {
  position:sticky; top:0; z-index:3;
  padding:var(--sp-2) var(--sp-4) 4px; text-align:center; white-space:nowrap;
  font-size:10px; font-weight:700; letter-spacing:.07em; text-transform:uppercase;
  color:var(--fg-3); background:#1c222c;
  border-bottom:1px solid var(--line-soft);
}
thead tr.groups th:first-child { border-top-left-radius:var(--r-lg); }
thead tr.groups th:last-child  { border-top-right-radius:var(--r-lg); }
/* Only the GROUPED tables get the offset — reco always had the group row,
   and the obs table gained one with the est-vs-real work. Still not every
   thead: applying this to a table with no group row pushes its header down
   over its own first row, which is what once cut the top item off. */
/* The column row sits directly under the group row. 29px was a guess at the
   group row's height, and when the real height differed the gap let table rows
   show through as you scrolled. --grouph is measured from the DOM after render
   (see syncStickyOffset in app.js), so it is right at any font size or zoom. */
#recoTable thead tr.cols th,
#obsTable  thead tr.cols th { top:var(--grouph, 29px); }

/* Vertical rules at the group boundaries, carried down through the body so a
   row can be read across without losing which group a number belongs to. */
thead tr.groups th.gsep,
thead tr.cols   th.gsep,
tbody td.gsep { border-left:1px solid var(--line); }
/* Lighter separation between columns inside a group. */
tbody td + td { border-left:1px solid var(--line-soft); }
tbody td.gsep { border-left:1px solid var(--line); }
thead tr.cols th:hover { color:var(--fg); }
thead tr.cols th.sorted { color:var(--accent); }
th .arrow { font-size:9px; opacity:.9; }

tbody td { padding:var(--sp-3); text-align:right; white-space:nowrap;
           border-bottom:1px solid var(--line-soft); }
tbody td:first-child { text-align:left; }
tbody tr { cursor:pointer; transition:background .15s var(--ease); }
tbody tr:hover td { background:var(--bg-2); }
tbody tr:last-child td { border-bottom:none; }
/* Obs rows have no click action, so no pointer cursor there — the delete
   button is the only control and carries its own affordance. */
#obsTable tbody tr { cursor:default; }
/* Zebra on the two long tables, subtle enough to guide the eye across a row
   without competing with the good/bad colour coding. The hover rules repeat
   id-scoped BELOW the stripes so they win on even rows too (same specificity,
   later wins). */
#recoTable tbody tr:nth-child(even) td,
#obsTable  tbody tr:nth-child(even) td { background:rgba(255,255,255,.025); }
#recoTable tbody tr:hover td,
#obsTable  tbody tr:hover td { background:var(--bg-2); }
/* Focusable rows and sortable headers live inside scrollers that would clip
   an outset outline, so theirs is drawn inward. */
tbody tr:focus-visible,
thead th:focus-visible { outline:2px solid var(--accent); outline-offset:-2px; }

.good { color:var(--good); } .bad { color:var(--bad); }
.warn { color:var(--warn); } .dim  { color:var(--fg-3); }
.sub  { display:block; font-size:11px; color:var(--fg-3); font-weight:400;
        margin-top:1px; }

.pill { display:inline-block; font-size:10px; font-weight:600; letter-spacing:.03em;
        padding:2px 7px; border-radius:var(--r-full);
        background:var(--bg-3); color:var(--fg-2); margin-left:var(--sp-2); }
.pill.warn { background:var(--warn-bg); color:var(--warn); }
.pill.bad  { background:var(--bad-bg);  color:var(--bad); }

/* Item cell: flex must live on a DIV inside the td — putting it on the td
   removes table-cell behaviour and the row border stops being drawn. */
/* min 280 / max 400: the name column is the one that FLEXES so the numeric
   columns never scroll sideways. A name longer than the cap truncates with
   an ellipsis; the full name rides in the span's title (app.js sets it). */
.itemcell { display:flex; gap:var(--sp-3); align-items:center;
            min-width:280px; max-width:400px; }
.icon.sm { width:22px; height:22px; }
.icon { width:36px; height:36px; flex:none; border-radius:var(--r-sm);
        background:var(--bg-2); object-fit:contain; padding:2px;
        border:1px solid var(--line-soft); }
.names { display:flex; flex-direction:column; line-height:1.3; min-width:0; }
.nm { font-weight:600; color:var(--fg);
      overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.theirs-v { color:var(--theirs); opacity:.8; font-size:11px; }

.tier { margin-left:auto; flex:none; font-size:10px; font-weight:650;
        letter-spacing:.03em; padding:3px 9px; border-radius:var(--r-full);
        white-space:nowrap; }
.tier.good { background:var(--good-bg); color:var(--good); }
.tier.bad  { background:var(--bad-bg);  color:var(--bad); }
.tier.warn { background:var(--warn-bg); color:var(--warn); }
.tier.dim  { background:var(--bg-3);    color:var(--fg-3); }

.confbar { display:block; height:4px; width:56px; background:var(--bg-3);
           border-radius:var(--r-full); margin:var(--sp-1) 0 0 auto;
           overflow:hidden; }
.confbar i { display:block; height:100%; background:currentColor;
             border-radius:var(--r-full); transition:width .3s var(--ease); }
.bar { display:inline-block; height:4px; border-radius:var(--r-full);
       background:linear-gradient(90deg, var(--good), rgba(74,222,128,.35));
       vertical-align:middle; margin-left:var(--sp-2); }

/* -------------------------------------------------------------------- form */
form { padding:var(--sp-5); max-width:680px; }
label { display:block; margin:var(--sp-4) 0 var(--sp-1); color:var(--fg-3);
        font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:.04em; }
form input, form textarea { width:100%; }
.row { display:flex; gap:var(--sp-3); } .row > div { flex:1; min-width:0; }
.check { display:flex; align-items:center; gap:var(--sp-2); margin-top:var(--sp-3); }
.check input { width:auto; accent-color:var(--accent); }
.check label { margin:0; text-transform:none; letter-spacing:0; font-size:13px;
               font-weight:400; color:var(--fg-2); }

button.go {
  margin-top:var(--sp-5); background:var(--accent); color:#1a1204; border:none;
  min-height:40px;
  padding:10px 22px; border-radius:var(--r-sm); font:inherit; font-weight:650;
  cursor:pointer; box-shadow:0 2px 12px rgba(240,180,41,.22);
  transition:all .15s var(--ease);
}
button.go:hover { filter:brightness(1.08); transform:translateY(-1px);
                  box-shadow:0 4px 18px rgba(240,180,41,.3); }
button.go:active { transform:none; }

.msg { margin-top:var(--sp-3); padding:10px var(--sp-4); border-radius:var(--r-sm);
       font-size:13px; border:1px solid transparent; }
.msg.ok  { background:var(--good-bg); color:var(--good); border-color:rgba(74,222,128,.25); }
.msg.err { background:var(--bad-bg);  color:var(--bad);  border-color:rgba(248,113,113,.25); }
.msg code { background:rgba(0,0,0,.3); padding:1px 5px; border-radius:var(--r-sm); font-size:12px; }
/* Close button for error boxes whose failure does not block the rest of the
   tab — the message must be seeable AND clearable. */
.msg .dismiss { float:right; background:none; border:0; color:inherit;
                cursor:pointer; font:inherit; font-size:16px; line-height:1;
                padding:0 4px; opacity:.7; border-radius:var(--r-sm); }
.msg .dismiss:hover { opacity:1; }

#runes .rune { display:flex; gap:var(--sp-2); margin-top:var(--sp-2); }
#runes .rune input:last-child { max-width:92px; }
.linkish { background:none; border:none; color:var(--accent); cursor:pointer;
           font:inherit; font-size:13px; padding:var(--sp-2) 0; }
.linkish:hover { text-decoration:underline; }

ul.hits { list-style:none; margin:var(--sp-1) 0 0; padding:var(--sp-1);
          border:1px solid var(--line); border-radius:var(--r-sm);
          max-height:230px; overflow:auto; background:var(--bg-2);
          box-shadow:var(--shadow-lg); }
ul.hits li { display:flex; align-items:center; gap:var(--sp-2); padding:7px var(--sp-2);
             cursor:pointer; border-radius:var(--r-sm); font-size:13px; }
ul.hits li:hover { background:var(--bg-3); }
.hiticon { width:24px; height:24px; object-fit:contain; flex:none; }
.picked { margin-top:var(--sp-2); font-size:12.5px; color:var(--fg-2);
          background:var(--good-bg); border:1px solid rgba(74,222,128,.22);
          border-radius:var(--r-sm); padding:8px var(--sp-3); }
.picked .ok { color:var(--good); font-weight:700; }

/* ------------------------------------------------------------------- chart */
.card { padding:var(--sp-5); margin-bottom:var(--sp-4); }
.card h3 { margin:0 0 2px; font-size:15px; font-weight:650; letter-spacing:-.01em; }
.card .meta { color:var(--fg-3); font-size:12.5px; margin-bottom:var(--sp-4); }

.stats { display:grid; grid-template-columns:repeat(auto-fit,minmax(110px,1fr));
         gap:var(--sp-3); margin-top:var(--sp-4); padding-top:var(--sp-4);
         border-top:1px solid var(--line-soft); }
.stat { font-size:11px; color:var(--fg-3); text-transform:uppercase;
        letter-spacing:.04em; font-weight:600; }
.stat b { display:block; font-size:22px; color:var(--fg); font-weight:650;
          letter-spacing:-.02em; text-transform:none; margin-bottom:2px;
          font-variant-numeric:tabular-nums; }

.axisbar { display:flex; align-items:center; gap:var(--sp-3); flex-wrap:wrap;
           margin-bottom:var(--sp-3); }
.axisnote { font-size:11.5px; max-width:64ch; line-height:1.45; }

svg.chart { width:100%; height:auto; display:block; }
svg.chart .grid { stroke:var(--line-soft); stroke-width:1; }
svg.chart .axis { fill:var(--fg-3); font-size:10px; }
svg.chart .lbl  { font-size:11px; fill:var(--fg-3); font-weight:600; }
svg.chart .line { fill:none; stroke:var(--accent); stroke-width:2.5;
                  stroke-linejoin:round; stroke-linecap:round; }
svg.chart .dot  { fill:var(--accent); }
svg.chart .brk  { fill:var(--info); }
svg.chart .brkmark line { stroke:var(--info); stroke-width:1;
                          stroke-dasharray:3 3; opacity:.5; }

.legend { display:flex; gap:var(--sp-5); flex-wrap:wrap; margin-top:var(--sp-3);
          padding-top:var(--sp-3); border-top:1px solid var(--line-soft);
          font-size:12px; color:var(--fg-2); }
.legend span { display:flex; align-items:center; gap:var(--sp-2); }
.sw { width:14px; height:3px; border-radius:2px; display:inline-block; flex:none; }
.sw.line { background:var(--accent); height:3px; }
.sw.dot  { width:8px; height:8px; border-radius:50%; background:var(--accent); }
.sw.dash { background:none; border-top:2px dashed var(--accent); height:0; }
.sw.brk  { width:0; height:0; background:none; border-left:5px solid transparent;
           border-right:5px solid transparent; border-bottom:8px solid var(--info); }

.flatnote { margin-top:var(--sp-4); padding:var(--sp-3) var(--sp-4);
            border-radius:var(--r-sm); background:var(--warn-bg);
            border-left:3px solid var(--warn); color:var(--fg-2); font-size:12.5px; }
.nopred { border:1px dashed var(--line); border-radius:var(--r-md);
          padding:var(--sp-4); color:var(--fg-2); font-size:13px;
          background:rgba(0,0,0,.14); }
.prog { height:6px; background:var(--bg-0); border-radius:var(--r-full);
        overflow:hidden; margin-top:var(--sp-3); border:1px solid var(--line); }
.prog span { display:block; height:100%;
             background:linear-gradient(90deg, var(--accent-dim), var(--accent));
             border-radius:var(--r-full); transition:width .4s var(--ease); }


/* "If it reached X" panel. */
.reachbar { display:flex; gap:var(--sp-5); align-items:flex-start; flex-wrap:wrap;
            margin-bottom:var(--sp-4); padding-bottom:var(--sp-4);
            border-bottom:1px solid var(--line-soft); }
.reachbar .range input { width:82px; }
.reachout { flex:1; min-width:280px; }
.reach-head { font-size:13px; margin-bottom:var(--sp-2); }
.reach-head b { color:var(--accent); }
table.reach { width:100%; max-width:420px; font-size:12px; border-collapse:collapse; }
table.reach td { padding:2px 6px; border:none; text-align:right; white-space:nowrap; }
table.reach td:first-child { text-align:left; color:var(--fg-2); width:64px; }
table.reach td:nth-child(2) { width:100%; }
.bar2 { display:block; height:6px; background:var(--bg-3); border-radius:var(--r-full);
        overflow:hidden; min-width:60px; }
.bar2 i { display:block; height:100%; background:var(--accent); border-radius:var(--r-full); }
.reach-note { font-size:11px; margin-top:var(--sp-2); max-width:60ch; line-height:1.45; }
.reach-now { color:var(--good); font-weight:650; font-size:13px; }

/* -------------------------------------------------------------- responsive */
@media (max-width:820px) {
  header { padding:var(--sp-3) var(--sp-4); gap:var(--sp-3); }
  .brand em { display:none; }
  main { padding:var(--sp-4); }
  .filters { gap:var(--sp-3); padding:var(--sp-3) var(--sp-4); }
  #filterCount { margin-left:0; }
  .scroll { max-height:none; }
  .itemcell { min-width:220px; }
  form { padding:var(--sp-4); }
  .row { flex-direction:column; gap:var(--sp-2); }
}

/* Phones. Tables keep their horizontal scroll inside .scroll; everything that
   is a bar stacks instead of overflowing sideways, and everything tappable
   grows to a finger-sized target. */
@media (max-width:768px) {
  .filterrow { flex-direction:column; align-items:stretch; }
  .filterrow .fgroup { padding-left:0; border-left:none; flex-wrap:wrap; }
  .filterrow .grow { flex:1 1 auto; }
  .obstoolbar { flex-direction:column; align-items:stretch; }
  .obstoolbar input { max-width:none; }
  .chip, nav button, .etab, .morefilters { min-height:40px; }
  .langpick button { min-width:40px; min-height:36px; display:flex;
                     align-items:center; justify-content:center; }
  .rmrune, td.rowact button.del { min-width:40px; min-height:40px; }
  button.go { width:100%; }
}

/* Show more. Deliberately a button and not an infinite-scroll handler: this
   table is for scanning, and a list that grows while you read loses your place. */
.moreactions { display:flex; gap:var(--sp-2); justify-content:center;
              margin-top:var(--sp-4); }
.showmore { padding:9px 22px; font-size:13px; }

/* ------------------------------------------------- ingredient price rows */
.ing { display:flex; align-items:center; gap:var(--sp-2); padding:6px 0;
       border-bottom:1px solid var(--line-soft); }
.ing:last-child { border-bottom:none; }
.ing img { width:26px; height:26px; object-fit:contain; flex:none; }
.ing .qty { color:var(--fg-3); font-size:12px; min-width:26px; }
.ing .nm2 { flex:1; min-width:0; font-size:13px; overflow:hidden;
            text-overflow:ellipsis; white-space:nowrap; }
.ing input { width:104px; text-align:right; }
/* Where the price came from and how old it is. Recency decides which price we
   use, so the age is the load-bearing part, not decoration. */
.ing .src { font-size:10.5px; white-space:nowrap; min-width:96px; text-align:right; }
.ing .src.manual { color:var(--good); }
.ing .src.scrape { color:var(--fg-3); }
.ing .src.old    { color:var(--warn); }
.ing .src.none   { color:var(--bad); }
.recipe-total { display:flex; justify-content:space-between; align-items:baseline;
                margin-top:var(--sp-3); padding-top:var(--sp-3);
                border-top:1px solid var(--line); font-size:13px; }
.recipe-total b { font-size:16px; color:var(--fg); }

/* The derived before-value, shown as its own arithmetic rather than asserted. */
.derived-note { margin-top:var(--sp-2); padding:8px var(--sp-3);
                background:var(--accent-glow); border-left:3px solid var(--accent-dim);
                border-radius:var(--r-sm); font-size:12.5px; color:var(--fg-2); }
.derived-note b { color:var(--accent); }

/* Caveats under the headroom count. Small and dim, but never hidden behind a
   toggle: a "you can break 32 more" that conceals "3 of 8 ingredients have no
   price" is worse than no number, because it looks like it knows something. */
.derived-note .cav { margin-top:5px; font-size:11.5px; line-height:1.45; }
.derived-note .cav:first-of-type { margin-top:8px;
                                   padding-top:7px; border-top:1px solid var(--line); }

/* Same shape, different colour: this one is a question, not a derivation. */
.derived-note.warn { background:rgba(224,150,60,.10);
                     border-left-color:var(--warn, #e0963c); }
.derived-note.warn b { color:var(--warn, #e0963c); }

/* ---------------------------------------------- delete an observation ---- */
/* Dim until you are on the row, so a column of red crosses does not sit beside
   data whose whole point is that it is hard-won. Full opacity on hover and
   always visible to the keyboard. */
.rowact { width:34px; text-align:right; }
td.rowact button.del {
  background:none; border:0; cursor:pointer; padding:2px 6px;
  font-size:17px; line-height:1; border-radius:var(--r-sm);
  color:var(--fg-3); opacity:0; transition:opacity .12s, color .12s;
}
tr:hover td.rowact button.del { opacity:1; }
td.rowact button.del:hover  { color:var(--bad, #e05c5c); background:rgba(224,92,92,.12); }
td.rowact button.del:focus-visible { opacity:1; outline:2px solid var(--accent); }
@media (hover:none) { td.rowact button.del { opacity:.55; } }

/* Undo toast. Deletion archives the row (migration 040), so this is a real
   offer, not a courtesy — hence a button rather than a countdown. */
.toast {
  position:fixed; left:50%; bottom:var(--sp-5); transform:translateX(-50%);
  z-index:60; display:flex; gap:var(--sp-3); align-items:center;
  padding:10px var(--sp-4); border-radius:var(--r-md);
  background:var(--bg-3); border:1px solid var(--line);
  box-shadow:0 6px 24px rgba(0,0,0,.45); font-size:13px; color:var(--fg);
  max-width:min(90vw,520px);
}
.toast button { font-size:13px; }

/* ------------------------------------------------------------- method tab */
/* Written for one reader who does this professionally. Denser than the rest of
   the site on purpose: tables, not prose. */
.method { max-width:1180px; }
.method .lede { font-size:14px; color:var(--fg); padding:var(--sp-3) var(--sp-4);
                background:var(--accent-glow); border-left:3px solid var(--accent);
                border-radius:var(--r-sm); }
.method h3 { margin:var(--sp-5) 0 var(--sp-2); font-size:13px; font-weight:700;
             letter-spacing:.04em; text-transform:uppercase; color:var(--accent); }
.method p { font-size:13.5px; line-height:1.6; }
.method ul { font-size:13.5px; line-height:1.6; color:var(--fg-2);
             padding-left:var(--sp-5); }
.method li { margin-bottom:var(--sp-2); }
.method code { background:rgba(0,0,0,.35); padding:1px 5px; border-radius:var(--r-sm);
               font-size:12px; color:var(--fg); }
/* Caveats inline rather than in footnotes: a limitation you have to go looking
   for is a limitation nobody reads. */
.caveat { display:block; margin-top:var(--sp-2); padding-left:var(--sp-3);
          border-left:2px solid var(--warn); color:var(--warn);
          font-size:12.5px; line-height:1.5; }
table.mtable { width:100%; border-collapse:collapse; margin:var(--sp-3) 0;
               font-size:12.5px; font-variant-numeric:tabular-nums; }
table.mtable th { text-align:left; padding:6px 10px; color:var(--fg-3);
                  font-size:10.5px; text-transform:uppercase; letter-spacing:.05em;
                  border-bottom:1px solid var(--line); }
table.mtable td { padding:7px 10px; border-bottom:1px solid var(--line-soft);
                  vertical-align:top; color:var(--fg-2); }
table.mtable td b { color:var(--fg); }
table.mtable tr:last-child td { border-bottom:none; }

/* Source cards. The four-column table this replaces put a paragraph of prose in
   a cell 90px wide and overflowed everywhere; a definition list wraps instead of
   pushing sideways. */
.srcgrid { display:grid; grid-template-columns:repeat(auto-fit, minmax(330px, 1fr));
           gap:var(--sp-3); margin:var(--sp-3) 0; }
.src { border:1px solid var(--line-soft); border-left:3px solid var(--fg-3);
       border-radius:var(--r-md); padding:var(--sp-3) var(--sp-4);
       background:rgba(0,0,0,.14); min-width:0; }
.src.good { border-left-color:var(--good); }
.src.warn { border-left-color:var(--warn); }
.src-h { display:flex; align-items:center; gap:var(--sp-2); flex-wrap:wrap;
         font-size:14px; margin-bottom:2px; }
.src p { font-size:12.5px; margin:var(--sp-2) 0; color:var(--fg-2); }
.src .badge { font-size:9.5px; font-weight:700; text-transform:uppercase;
              letter-spacing:.04em; padding:2px 7px; border-radius:var(--r-full);
              background:var(--bg-3); color:var(--fg-2); white-space:nowrap; }
.src .badge.good { background:var(--good-bg); color:var(--good); }
.src .badge.warn { background:var(--warn-bg); color:var(--warn); }
.src dl { display:grid; grid-template-columns:auto 1fr; gap:3px var(--sp-3);
          margin:0; font-size:12px; }
.src dt { color:var(--fg-3); text-transform:uppercase; font-size:9.5px;
          letter-spacing:.04em; padding-top:3px; white-space:nowrap; }
.src dd { margin:0; color:var(--fg-2); min-width:0; }
.src dd b { color:var(--fg); }

/* Long tables scroll rather than overflow the card. */
.method table.mtable { display:block; overflow-x:auto; white-space:nowrap; }
.method table.mtable td, .method table.mtable th { white-space:nowrap; }
@media (max-width:700px) { .srcgrid { grid-template-columns:1fr; } }

/* h4 inside the method page: one estimator per heading, tighter than h3. */
.method h4 { margin:var(--sp-4) 0 var(--sp-1); font-size:13px; font-weight:650;
             color:var(--fg); letter-spacing:-.01em; }
.method h4::before { content:'▸ '; color:var(--accent); }
/* The ours-vs-theirs table has a label column, so it wraps rather than scrolls. */
.method table.mtable td.rowlbl { color:var(--fg-3); font-size:10.5px;
                                 text-transform:uppercase; letter-spacing:.04em;
                                 white-space:nowrap; }

/* Two estimators in one cell. The better one leads; the other sits beneath in
   a lighter weight, and turns amber when they disagree materially — that
   disagreement is the signal, not noise to be averaged out. */
.est-1 { color:var(--fg-2); }
.est-2 { opacity:.6; font-size:10.5px; }
.est-2.diverge { opacity:1; color:var(--warn); }

/* ------------------------------------------------ two ways to record ---- */
/* A form for a coefficient and nothing else; a paste box because typing runes
   one at a time is tedious enough that it does not get done. */
.entrytabs { display:flex; gap:2px; margin-bottom:var(--sp-3); }
.etab { background:none; border:0; border-bottom:2px solid transparent;
        padding:7px var(--sp-3); cursor:pointer; color:var(--fg-2);
        font-size:13px; border-radius:0; }
.etab.on { color:var(--fg); border-bottom-color:var(--accent); }
.etab:hover { color:var(--fg); }

#pasteBox textarea { width:100%; font-family:ui-monospace,SFMono-Regular,
        "SF Mono",Menlo,monospace; font-size:12px; line-height:1.5;
        white-space:pre; overflow-x:auto; }
.pasteactions { display:flex; gap:var(--sp-3); align-items:center;
                margin-top:var(--sp-3); }
.pasteactions .go { margin:0; }

/* What the server understood, echoed back before anything is written. Shown
   for the check AND the save: the two differing is the bug worth catching. */
table.pastepreview { width:100%; margin-top:var(--sp-3); font-size:12px; }
table.pastepreview th { text-align:left; font-weight:500; color:var(--fg-3);
                        padding:3px 8px 3px 0; }
table.pastepreview td { padding:3px 8px 3px 0; }

/* Runes as icon and count. The name is redundant when the picture identifies
   it, and eight named chips per row made the observations table unreadable.
   A rune we could NOT match has no icon, so it keeps its name — that is the
   case where you cannot tell what it is. */
.rune-chip.iconly { padding:2px 5px; gap:3px; }
.rune-chip.iconly img { width:18px; height:18px; }
.rune-chip.iconly .q { font-variant-numeric:tabular-nums; opacity:.85; }

/* ------------------------------------------- record form + decay curve --- */
/* Form left, curve right — a real 50/50 above 1100px. ONE definition,
   mobile-first: single column by default, two equal columns wide. (The curve
   used to get a fixed 480px column, which left half the tab empty; a later
   pass overrode it at the bottom of this sheet — both are consolidated here.)
   The columns stretch, so the curve card runs the height of the form beside
   it; the svg is viewBox-based and scales with the space it is given. */
.logsplit { display:grid; grid-template-columns:minmax(0,1fr);
            gap:var(--sp-4); align-items:stretch; }
/* The grid cell is the width limit now; the global form max-width would
   re-shrink the left column inside its own half. */
.logsplit form { max-width:none; }
@media (min-width:1101px) {
  .logsplit { grid-template-columns:minmax(0,1fr) minmax(0,1fr); }
}

.curvepanel {
  background:linear-gradient(180deg, var(--bg-1), rgba(21,26,34,.7));
  border:1px solid var(--line-soft); border-radius:var(--r-lg);
  box-shadow:var(--shadow); padding:var(--sp-4);
  display:flex; flex-direction:column;
}
.curvepanel h3 { margin:0 0 var(--sp-2); font-size:14px; font-weight:600; }
.curvepanel p { margin:0 0 var(--sp-3); font-size:12.5px; line-height:1.5; }
#curveWrap { flex:1; display:flex; flex-direction:column; }
#curveWrap svg { width:100%; height:auto; display:block; }
/* Before an item is picked the panel must say WHY it is empty rather than
   sit there as a grey rectangle that looks broken. */
.curve-empty { flex:1; display:flex; align-items:center; justify-content:center;
               min-height:160px; padding:var(--sp-4); color:var(--fg-3);
               font-size:13px; text-align:center; }
.curvenote { margin-top:var(--sp-2); font-size:12px; line-height:1.5; }
.curvenote b { color:var(--accent); }

/* The prompt to hand an AI. Collapsed by default — needed once, then never
   again — but present on the page you are standing on when you need it. */
#promptBox { margin:var(--sp-3) 0; border:1px solid var(--line-soft);
             border-radius:var(--r-sm); background:var(--bg-2); }
#promptBox > summary { cursor:pointer; padding:8px var(--sp-3); font-size:13px;
                       color:var(--fg-2); user-select:none; }
#promptBox > summary:hover { color:var(--fg); }
#promptBox[open] > summary { border-bottom:1px solid var(--line-soft); }
.promptrow { display:flex; justify-content:flex-end; padding:6px var(--sp-3) 0; }
#aiPrompt { margin:0; padding:var(--sp-3); font-size:11.5px; line-height:1.5;
            white-space:pre-wrap; word-break:break-word; color:var(--fg-2);
            font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,monospace;
            max-height:340px; overflow:auto; }

/* Charts tab: the Market/Item sub-view toggle and the market grid. Four
   cards in 2x2, one column below 1100px — the same breakpoint the log
   form's split uses, so the page narrows all at once instead of piecemeal. */
.chartviews { margin:0 0 var(--sp-4); }
.marketgrid { display:grid; grid-template-columns:repeat(2, minmax(0,1fr));
              gap:var(--sp-4); align-items:start; }
@media (max-width:1100px) { .marketgrid { grid-template-columns:1fr; } }
.mkcard { background:var(--bg-1); border:1px solid var(--line-soft);
          border-radius:var(--r-lg); box-shadow:var(--shadow);
          padding:var(--sp-4); }
.mkcard h3 { margin:0 0 var(--sp-1); font-size:14px; font-weight:600; }
.mkcard p { margin:0 0 var(--sp-3); font-size:12px; line-height:1.5; }
.mkwrap svg { width:100%; height:auto; display:block; }
/* Footnotes under a market chart: the <6h silence note, the batch count. */
.mknote { margin-top:var(--sp-2); font-size:11.5px; line-height:1.45; }

/* Charts tab: browse the ranking without typing (see renderChartBrowse). */
.chartbrowse { margin:var(--sp-3) 0 var(--sp-4); }
.chartbrowse #chartBrowseList {
  max-height:320px; overflow-y:auto; margin-top:var(--sp-2);
  border:1px solid var(--line); border-radius:var(--r-md);
  background:var(--bg-1);
}
.browserow {
  /* center, not baseline: the rows carry item icons now, and a 22px image
     aligned on the text baseline hangs below the line it belongs to */
  display:flex; gap:var(--sp-3); align-items:center; width:100%;
  padding:var(--sp-2) var(--sp-3); background:none; border:0; cursor:pointer;
  text-align:left; color:inherit; font:inherit;
  transition:background .15s var(--ease);
}
.browserow:hover { background:var(--bg-2); }
.browserow:focus-visible { outline:2px solid var(--accent); outline-offset:-2px; }
.browserow .nm { flex:1; }
.browserow .hiticon { width:22px; height:22px; }

/* Observations scoreboard: one card per player, net of cost and tax. */
.kpis { display:flex; gap:var(--sp-3); margin:0 0 var(--sp-4); flex-wrap:wrap; }
.kpi  { background:var(--bg-1); border:1px solid var(--line-soft);
        border-radius:var(--r-md); padding:var(--sp-3) var(--sp-4);
        box-shadow:var(--shadow);
        display:flex; flex-direction:column; gap:2px; min-width:170px; }
.kpi .who { font-weight:600; text-transform:capitalize; }
.kpi .num { font-size:20px; font-weight:700; }

/* Filter + sort toolbar for the log. Client-side over the fetched rows; the
   KPI cards above are all-time and sit outside its reach on purpose. */
.obstoolbar { display:flex; gap:var(--sp-3); align-items:center; flex-wrap:wrap;
              margin:0 0 var(--sp-3); }
.obstoolbar input { flex:1 1 220px; max-width:340px; min-height:32px; }
#obsPlayers { display:flex; gap:var(--sp-1); flex-wrap:wrap; }
/* The obs table has no group row, so its header can stick to the very top of
   the scroller. (The reco offset below is scoped to #recoTable for the same
   reason in reverse — see the --grouph note.) */
#obsTable thead tr.cols th { top:0; }
/* cursor:pointer comes from the generic sortable-header rule; the columns
   that do not sort must not advertise that they do. */
#obsTable thead th:not([data-osort]) { cursor:default; }
/* The runes cell WRAPS. A break can carry a dozen rune kinds, and with the
   global `tbody td { white-space:nowrap }` those chips forced the whole table
   into horizontal scroll (Itziar, 2026-08-02). The table may grow vertically,
   never horizontally: the cell opts back into wrapping, and the chips sit in
   a wrapping flex div whose max-width is the actual limit — max-width on the
   td itself is not honoured by auto table layout, the div is what constrains.
   Each chip keeps its own white-space:nowrap so a chip never breaks inside. */
#obsTable td.runescell { white-space:normal; }
#obsTable td.runescell .runeswrap { display:flex; flex-wrap:wrap;
                                    align-items:center; justify-content:flex-end;
                                    max-width:400px; }
@media (max-width:768px) {
  #obsTable td.runescell .runeswrap { max-width:220px; }
}

/* Rune rows: the way out for an accidental rune, and the predicted hint. */
.rmrune { background:none; border:0; color:var(--fg-3); cursor:pointer;
          font-size:15px; padding:0 var(--sp-1); border-radius:var(--r-sm);
          transition:color .15s var(--ease); }
.rmrune:hover { color:var(--bad); }
.rune.predicted .rn { opacity:.85; font-style:italic; }

/* Profession levels editor.
   Closed: a compact summary sitting at the right end of the mode row.
   Open: the panel claims the FULL row and lays the jobs out as two rows
   flowing into as many columns as fit — a dozen professions read as a
   short wide strip, not a tall thin ladder. The first version put the
   grid inside the flex row's leftover width: one column, enormous, and
   visibly out of place (Itziar, 2026-08-03). */
#modeRow { flex-wrap:wrap; align-items:end; }
#jobLevels { margin-left:auto; }
#jobLevels > summary { cursor:pointer; padding:6px 10px; border-radius:var(--r-sm);
                       min-height:32px; display:inline-flex; align-items:center; }
#jobLevels > summary:hover { background:var(--bg-2); color:var(--fg); }
#jobLevels[open] { flex-basis:100%; margin-left:0;
                   border-top:1px solid var(--line); padding-top:var(--sp-2); }
.joblevels { display:grid; grid-template-rows:repeat(2, auto);
             grid-auto-flow:column; grid-auto-columns:max-content;
             gap:var(--sp-2) var(--sp-5); padding:var(--sp-2) 0;
             overflow-x:auto; }
/* Columns size to their content (no full-width stretch — that was the
   original gap complaint), and WITHIN a column the inputs share a right
   edge via space-between: aligned boxes, small gaps. Two requests, one
   layout (Itziar, 2026-08-03). */
.joblvl { display:flex; justify-content:space-between; gap:var(--sp-2); align-items:center;
          font-size:12.5px; color:var(--fg-2); white-space:nowrap; }
.joblvl .jn { margin-right:var(--sp-2); }
.joblvl input { width:52px; }
/* DofusDB serves these as JPG: the transparent background arrives baked to
   solid black — the "little black rectangles". On a dark theme, screen
   blending erases black and keeps the glyph. */
.jobicon { width:20px; height:20px; object-fit:cover; flex:none;
           mix-blend-mode:screen; }

/* The craft-vs-buy glyph before the cost figure (066). Inline SVG, not game
   art — see costIcon() in app.js. */
.costsrc { display:inline-block; margin-right:4px; vertical-align:-1px;
           color:var(--fg-2); }

/* Number spinners overlapped the typed digits in these narrow inputs (and
   the break form's). Every number here is typed, not nudged — hide them. */
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button { -webkit-appearance:none; margin:0; }
input[type=number] { -moz-appearance:textfield; appearance:textfield; }
@media (max-width:768px) {
  /* Two forced rows overflow sideways on a phone; fall back to a wrapping
     grid there — vertical scroll is native where horizontal is not. */
  .joblevels { grid-auto-flow:row; grid-template-rows:none;
               grid-template-columns:repeat(auto-fill, minmax(140px, 1fr)); }
  #jobLevels { margin-left:0; flex-basis:100%; }
}

/* The cost cell: craft | buy, one column, a light divider between the two
   ways in. The side Benefit/ROI stand on (the cheaper, 066) is full weight;
   the other is dimmed context. */
.costcell { white-space:nowrap; }
.costside { display:inline-flex; align-items:center; gap:4px; opacity:.45; }
.costside.on { opacity:1; font-weight:600; }
.costsvg { width:11px; height:11px; flex:none; color:var(--fg-2); }
.costside.on .costsvg { color:var(--accent); }
.costdiv { display:inline-block; width:1px; height:14px; background:var(--line-soft);
           margin:0 var(--sp-2); vertical-align:middle; }

/* -------------------------------------- "this price is wrong" (068) ------ */
/* The flag follows the obs delete pattern: dim until you are on the row, so
   a column of flags does not shout beside the numbers; always reachable by
   keyboard, half-visible where there is no hover to reveal it. */
.flagprice {
  background:none; border:0; cursor:pointer; padding:2px 4px;
  margin-left:var(--sp-2); font-size:12px; line-height:1;
  border-radius:var(--r-sm);
  color:var(--fg-3); opacity:0; transition:opacity .12s, color .12s;
}
#recoTable tbody tr:hover .flagprice { opacity:1; }
.flagprice:hover { color:var(--warn, #e0963c); background:rgba(224,150,60,.12); }
.flagprice:focus-visible { opacity:1; outline:2px solid var(--accent); }
@media (hover:none) { .flagprice { opacity:.55; } }

/* The correction popover: one small positioned card, one at a time (the id
   enforces it). Absolute, not fixed — it belongs to the row it was opened
   from and should scroll away with it. */
.pricepop {
  position:absolute; z-index:70; min-width:220px; max-width:280px;
  padding:var(--sp-3) var(--sp-4); border-radius:var(--r-md);
  background:var(--bg-3); border:1px solid var(--line);
  box-shadow:0 6px 24px rgba(0,0,0,.45); font-size:13px;
  display:flex; flex-direction:column; gap:var(--sp-3); text-align:left;
}
.pricepop .pf-title { font-weight:600; }
.pricepop .pf-price { display:flex; flex-direction:column; gap:4px;
                      font-size:12px; color:var(--fg-2); }
.pricepop .pf-price input { width:100%; }
.pricepop .pf-note { font-size:11px; white-space:normal; }
.pricepop .pf-actions { display:flex; gap:var(--sp-2); justify-content:flex-end; }

/* The item hover card (2026-08-10): the game's own EFFETS tooltip, rebuilt
   from item_effects with the basic-rune icons. Absolute like .pricepop, but
   pointer-events:none — it is a preview, never a control, and a hoverable
   card would flicker-fight the row underneath it. Positive lines green,
   malus lines red and iconless: a malus yields no runes (Itziar's rule),
   and the card must be honest about which lines pay. Desktop-only by
   construction (opened from mouseover, which touch never fires). */
.itemcard {
  position:absolute; z-index:80; min-width:200px; max-width:300px;
  padding:var(--sp-3) var(--sp-4); border-radius:var(--r-md);
  background:var(--bg-3); border:1px solid var(--line);
  box-shadow:0 6px 24px rgba(0,0,0,.45); font-size:13px;
  pointer-events:none; text-align:left;
}
.itemcard .ic-name { font-weight:650; }
.itemcard .ic-meta { font-size:11px; }
.itemcard .ic-h {
  font-size:10px; letter-spacing:.12em; text-transform:uppercase;
  color:var(--fg-3); margin-top:var(--sp-2); padding-top:var(--sp-2);
  border-top:1px solid var(--line-soft);
}
.itemcard .ic-line { display:flex; align-items:center; gap:6px;
                     line-height:1.55; font-variant-numeric:tabular-nums; }
.itemcard .ic-line.good { color:var(--good); }
.itemcard .ic-line.bad  { color:var(--bad); }
.itemcard .ic-rune { width:16px; height:16px; object-fit:contain; flex:none; }

td.rowact { width:auto; white-space:nowrap; }

/* The item typeahead now lives inside a row column; the dropdown needs its
   anchor to be that column, not some distant positioned ancestor. */
#obsForm .row > div { position:relative; }

/* Form first-row polish (UI revision, 2026-08-03): the Craft/Achat chips
   seat on the same baseline as the item input beside them instead of
   floating at label height; and a quantity box does not need the width of
   a name box. */
#obsForm #acqChips { min-height:34px; align-items:end; display:inline-flex; }
#obsForm .rune .rq { width:72px; flex:none; }

/* Te coute: the header right-aligns like every numeric column, so the cell
   content does too — hammer|buy pair seated right, matching its own title
   (Itziar, 2026-08-03). */
#recoTable td.costcell { text-align:right; }

/* ==========================================================================
   Token-shop currency picker (2026-09-04)

   A deliberately CHUNKY control rather than a row of chips or a native
   <select>. Three reasons, in order:

     * Twenty-two currencies do not fit as tags across a filter bar, and most
       of them have no catalogue yet — as chips they read as twenty-two equal
       options when only one is currently usable.
     * The ICON is what makes a currency recognisable at a glance. A native
       <select> cannot carry images in its options, which is why this is a
       button plus a listbox instead.
     * It is the primary control of the whole tab: everything below it is
       scoped by this one choice, so it should look like a decision and not
       like a filter.

   Roles and keyboard behaviour live in app.js; these are only the looks.
   ========================================================================== */
.bigpick { position:relative; }

.bigpick-btn {
  display:flex; align-items:center; gap:var(--sp-3);
  min-width:280px; width:100%; padding:10px var(--sp-3);
  background:var(--bg-2); color:var(--fg);
  border:1px solid var(--line); border-radius:var(--r-md);
  font:inherit; text-align:left; cursor:pointer;
  transition:border-color .15s var(--ease), background .15s var(--ease);
}
.bigpick-btn:hover { border-color:var(--bg-3); background:var(--bg-3); }
.bigpick-btn:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
.bigpick-btn[aria-expanded="true"] { border-color:var(--accent); }
.bigpick-btn .chev { margin-left:auto; color:var(--fg-3); flex:none;
                     transition:transform .18s var(--ease); }
.bigpick-btn[aria-expanded="true"] .chev { transform:rotate(180deg); }

/* 32px in the button, 22px in the list. Big enough in the button to be the
   thing you recognise before you read anything. */
.bigpick-ico { width:32px; height:32px; object-fit:contain; flex:none; }
.bigpick-ico.sm { width:22px; height:22px; }
/* No resolved item id, so no icon exists — a plain coin rather than someone
   else's picture. Dim on purpose: an unresolved currency IS less certain. */
.bigpick-ico.noicon { display:inline-flex; align-items:center;
                      justify-content:center; color:var(--fg-3); }
.bigpick-ico.noicon svg { width:100%; height:100%; fill:none;
                          stroke:currentColor; stroke-width:1.6;
                          stroke-linejoin:round; }

.bigpick-name { font-weight:600; font-size:14px; }
.bigpick-sub  { color:var(--fg-3); font-size:11.5px;
                font-variant-numeric:tabular-nums; margin-left:auto; }
.bigpick-btn .bigpick-sub { margin-left:0; }

.bigpick-menu {
  position:absolute; z-index:40; top:calc(100% + 6px); left:0;
  min-width:100%; max-height:min(60vh, 420px); overflow-y:auto;
  background:var(--bg-1); border:1px solid var(--line);
  border-radius:var(--r-md); box-shadow:var(--shadow-lg); padding:var(--sp-1);
}
.bigpick-opt {
  display:flex; align-items:center; gap:var(--sp-3); width:100%;
  padding:8px var(--sp-2); background:none; border:0; border-radius:var(--r-sm);
  color:var(--fg); font:inherit; text-align:left; cursor:pointer;
}
.bigpick-opt:hover:not([disabled]) { background:var(--bg-2); }
.bigpick-opt:focus-visible { outline:2px solid var(--accent); outline-offset:-2px; }
.bigpick-opt[aria-selected="true"] { background:var(--accent-glow); }
.bigpick-opt[aria-selected="true"] .bigpick-name { color:var(--accent); }
/* Kept in the list, not filtered out: a currency nobody has catalogued yet is
   a to-do, and hiding it would imply Dofus has three currencies. */
.bigpick-opt[disabled] { cursor:default; opacity:.45; }

/* The rate column carries the number the whole tab ranks on, so it gets room
   to breathe and tabular figures — 2 191 above 2 131 must line up digit for
   digit or the comparison is done by reading rather than by looking. */
#jetTable td .rate { font-size:14px; font-variant-numeric:tabular-nums;
                     letter-spacing:-.01em; }

/* Kama coin and token icons, inline beside the numbers they qualify. A bare
   number in a game with four currencies is ambiguous; the glyph is faster to
   read than a repeated word and costs no column width. */
.kama { width:12px; height:12px; vertical-align:-1px; margin-left:3px;
        flex:none; }
.jetico { width:16px; height:16px; object-fit:contain; vertical-align:-3px;
          margin-left:4px; }
.jetico.sm { width:14px; height:14px; vertical-align:-2px; }
#jetCostHead .jetico { margin-left:0; }
/* The caveat marker: one glyph on the name, everything in its tooltip. It
   replaced a whole column of pills (Itziar, 2026-09-04: clutter). */
.cav { color:var(--warn); font-size:11px; margin-left:5px; cursor:help; }
