/* ══════════════════════════════════════════════════════════════════════════════════════════════════
   THE ALERT COMPOSER'S STYLESHEET. ONE COPY, TWO PAGES.
   --------------------------------------------------------------------------------------------------
   Lifted out of my-alerts.html on 2026-08-23, when the composer stopped being something you had to
   LEAVE an item page to reach. Every rule below was already here and is unchanged; what changed is
   that it is no longer inside one page's <style>, because two pages now render the same dialog
   (assets/alert-modal.js owns its markup and its behaviour, this file owns its look).

   Loaded AFTER styles.css and BEFORE the page's own <style>, so a page can still override it — that
   ordering is load-bearing on this site.
   ════════════════════════════════════════════════════════════════════════════════════════════════ */

.modal-overlay { position: fixed; inset: 0; background: var(--scrim-3);z-index: 1000; display: flex; align-items: center; justify-content: center; padding: var(--space-4); }
.modal-overlay.hidden { display: none; }
/* ENTRY ONLY. The scrim fades and the surface arrives at 97% -> 100%, so the dialog reads as landing
   ABOVE the page rather than the page being swapped; closing stays the instant .hidden flip — the
   member caused it and wants it gone, and an exit animation would need display-none choreography for
   zero benefit. Keyframes, not transitions, on purpose: the global animation kills (prefers-reduced-
   motion, html[data-perf="1"] *, html[data-motion="reduced|off"] *) reach them by element and degrade
   this to instant for free. Never raise the duration or drop the start scale below .95 — scale on a
   dialog full of text blurs it mid-animation if it runs long enough to watch. */
.modal-overlay:not(.hidden) { animation: tl-ov-in var(--t-fast) var(--ease); }
.modal-overlay:not(.hidden) .modal { animation: tl-modal-in var(--t-fast) var(--ease-out); }
@keyframes tl-ov-in { from { opacity: 0; } }
@keyframes tl-modal-in { from { opacity: 0; transform: scale(.97); } }
/* 520 -> 620 for the alert composer ONLY (#alertModal), not for every .modal on these pages —
   /deals and /ledger host their own dialogs on this class and neither asked to be widened. The
   extra width is for the result rows: name, price and the "not listed" note share one line, and at
   520px a long item name pushed the price against it. */
#alertModal .modal { max-width: 620px; }
.modal { background: var(--bg-elevated); border: 1px solid var(--border-2); border-radius: var(--r-xl); padding: var(--space-5); width: 100%; max-width: 520px; max-height: 90vh; max-height: 90dvh; display: flex; flex-direction: column; box-shadow: var(--shadow-xl); }
.modal-title { font-family: var(--font-display); font-size: 18px; font-weight: 700; letter-spacing: -0.01em; margin: 0 0 var(--space-1); }
.modal-head { margin-bottom: 20px; flex-shrink: 0; }
.modal-sub { font-size: 12px; color: var(--text-3); margin: 0; }
.modal-body { display: flex; flex-direction: column; gap: 18px; min-height: 0; flex: 1; overflow-y: auto; overflow-x: hidden; }
.modal-left { display: flex; flex-direction: column; flex-shrink: 0; }
.modal-right { display: flex; flex-direction: column; flex-shrink: 0; }
.modal-field { margin-bottom: 14px; }
.modal-label { font-size: 11px; font-weight: 600; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 6px; display: block; }

.thr-slider-wrap { margin-top: 12px; }
.thr-slider-wrap.hidden { display: none; }
.thr-slider {
  -webkit-appearance: none; appearance: none;
  display: block; width: 100%; height: 44px;
  background: transparent; margin: 0; padding: 0; cursor: pointer;
}
.thr-slider:disabled { cursor: not-allowed; opacity: .5; }
.thr-slider::-webkit-slider-runnable-track {
  height: 4px; border-radius: var(--r-pill);
  background: var(--bg-2); border: 1px solid var(--border);
}
.thr-slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--text); border: 1px solid var(--border-2);
  box-shadow: 0 1px 3px var(--scrim-2);
  
  margin-top: -6px;
  transition: background-color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.thr-slider::-moz-range-track {
  height: 4px; border-radius: var(--r-pill);
  background: var(--bg-2); border: 1px solid var(--border);
}
.thr-slider::-moz-range-thumb {
  width: 18px; height: 18px; border-radius: 50%; box-sizing: border-box;
  background: var(--text); border: 1px solid var(--border-2);
  box-shadow: 0 1px 3px var(--scrim-2);
  transition: background-color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.thr-slider:hover:not(:disabled)::-webkit-slider-thumb  { background: var(--blue-bright); }
.thr-slider:hover:not(:disabled)::-moz-range-thumb      { background: var(--blue-bright); }
.thr-slider:active:not(:disabled)::-webkit-slider-thumb { transform: scale(1.15); background: var(--blue-bright); }
.thr-slider:active:not(:disabled)::-moz-range-thumb     { transform: scale(1.15); background: var(--blue-bright); }

.thr-slider:focus { outline: none; }
.thr-slider:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 1px 3px var(--scrim-2), 0 0 0 2px var(--bg-elevated), 0 0 0 4px var(--blue);
}
.thr-slider:focus-visible::-moz-range-thumb {
  box-shadow: 0 1px 3px var(--scrim-2), 0 0 0 2px var(--bg-elevated), 0 0 0 4px var(--blue);
}
@media (prefers-reduced-motion: reduce) {
  .thr-slider::-webkit-slider-thumb { transition: none; }
  .thr-slider::-moz-range-thumb     { transition: none; }
  .thr-slider:active:not(:disabled)::-webkit-slider-thumb { transform: none; }
  .thr-slider:active:not(:disabled)::-moz-range-thumb     { transform: none; }
}
.thr-scale {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: var(--space-4); margin-top: -6px; font-size: 11px; line-height: 1.4; color: var(--text-3);
}
.thr-scale-hi { text-align: right; }
.thr-scale-end b { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: 500; color: var(--text-2); }

.thr-est { margin-top: 13px; padding-top: 12px; border-top: 1px solid var(--hairline); }
.thr-est:empty { display: none; }
.thr-est-lead  { margin: 0; font-size: 13px; line-height: 1.5; color: var(--text); }
.thr-est-basis { margin: 5px 0 0; font-size: 11.5px; line-height: 1.55; color: var(--text-3); }

.thr-est b { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: 600; }
.thr-est b.w { font-family: inherit; font-variant-numeric: normal; font-weight: 700; }
.thr-est-basis b { font-weight: 500; color: var(--text-2); }
.thr-est-basis b.w { font-weight: 600; }
.thr-est.is-now   .thr-est-lead { color: var(--green); }
.thr-est.is-never .thr-est-lead { color: var(--gold); }

.thr-est.is-thin  .thr-est-lead { color: var(--text-2); }
.thr-est.is-idle  .thr-est-lead { color: var(--text-3); font-size: 12px; }
.thr-est.is-error .thr-est-lead { color: var(--red-bright); }
.thr-est-load { display: flex; align-items: center; gap: var(--space-2); font-size: 12px; color: var(--text-3); }
.thr-est-load .spinner { width: 14px; height: 14px; border-width: 2px; }
.thr-est-retry {
  margin-top: 9px; min-height: 44px; padding: 8px 14px;
  background: var(--bg-2); border: 1px solid var(--border-2); border-radius: var(--r-sm);
  color: var(--text-2); font-size: 12px; font-family: inherit; cursor: pointer;
  transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease);
}
.thr-est-retry:hover { border-color: var(--blue); color: var(--blue-bright); background: var(--blue-dim); }
.thr-est-retry:disabled { opacity: .5; cursor: not-allowed; }

/* THE SUGGESTION, AS A TAP. The box above it is driven by a slider and a slider cannot be asked for a
   specific number, so naming a reachable price in prose was asking somebody to read four digits and
   then hunt for them by hand. Deliberately NOT a .thr-chip: the chips are a ladder of four graded
   options and this is one price with one count behind it, so it reads as a row, not as a rung. */
.thr-sug {
  display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
  width: 100%; margin-top: 9px; min-height: 44px; padding: 8px 11px; box-sizing: border-box;
  border: 1px solid rgba(232,163,61,0.30); border-radius: var(--r-sm);
  background: var(--bg-2); color: var(--text-2);
  font: inherit; font-size: 12px; text-align: left; cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease);
}
.thr-sug:hover { border-color: var(--gold); background: var(--gold-dim); }
.thr-sug:focus-visible { outline: 2px solid var(--blue-bright); outline-offset: 1px; }
.thr-sug-do { font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--gold); }
.thr-sug-price { font-family: var(--font-mono); font-size: 14px; font-weight: 600;
  font-variant-numeric: tabular-nums; color: var(--text); }
.thr-sug-ev { font-family: var(--font-mono); font-size: 9.5px; color: var(--text-3); margin-left: auto; }

/* THE SAVE-TIME QUESTION. It sits directly above Create Alert because that is the button it is about,
   and it asks rather than refuses: both of its answers save the alert. */
.unr {
  margin-top: 14px; padding: 12px 13px;
  border: 1px solid rgba(232,163,61,0.34); border-radius: var(--r-sm);
  background: var(--gold-dim);
}
.unr:focus { outline: none; }
.unr:focus-visible { outline: 2px solid var(--blue-bright); outline-offset: 2px; }
.unr-lead  { margin: 0; font-size: 13px; font-weight: 600; line-height: 1.5; color: var(--gold); }
.unr-basis { margin: 5px 0 0; font-size: 11.5px; line-height: 1.55; color: var(--text-2); }
.unr-basis b { font-family: var(--font-mono); font-weight: 500; color: var(--text); }
.unr-btns { display: flex; gap: var(--space-2); flex-wrap: wrap; margin-top: 11px; }
.unr-btn {
  flex: 1 1 auto; min-height: 44px; padding: 8px 13px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  border-radius: var(--r-sm); border: 1px solid var(--border-2); background: var(--bg-2);
  color: var(--text-2); font: inherit; font-size: 12.5px; font-weight: 500; cursor: pointer;
  transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease);
}
.unr-btn:hover { border-color: var(--border-2); color: var(--text); background: var(--bg-3); }
.unr-btn:focus-visible { outline: 2px solid var(--blue-bright); outline-offset: 1px; }
.unr-use { border-color: var(--gold); color: var(--gold); font-weight: 600; }
.unr-use:hover { border-color: var(--gold); color: var(--gold); background: rgba(232,163,61,0.16); }
/* The quieter of the two answers, and it must LOOK like the quieter one without looking discouraged:
   it does not take the leftover width, and it carries no accent. It is still a real, first-class way
   out -- when there is no reachable price to suggest it is the only button in the row. */
.unr-keep { flex: 0 1 auto; color: var(--text-3); }
.unr-keep:hover { color: var(--text); }
.unr-btn-sub { font-family: var(--font-mono); font-size: 9.5px; font-weight: 400; color: var(--text-3); }
.modal-select, .modal-input { width: 100%; background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--r-sm); color: var(--text); font-size: 13px; padding: 10px 11px; box-sizing: border-box; font-family: inherit; -webkit-appearance: none; appearance: none; transition: border-color var(--t-fast) var(--ease); }
.modal-select:focus, .modal-input:focus { outline: none; border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-dim); }
.custom-select-val:focus { outline: none; border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-dim); }
.modal-dir-row { display: flex; gap: var(--space-2); }
.modal-dir-btn { flex: 1; padding: 10px; border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--bg-2); color: var(--text-2); font-size: 13px; font-weight: 500; cursor: pointer; transition: color var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease); font-family: inherit; }
.modal-dir-btn.sel-below { border-color: var(--green-dim); background: var(--green-dim); color: var(--green); }
.modal-dir-btn.sel-above { border-color: var(--gold-dim); background: var(--gold-dim); color: var(--gold); }
/* PINNED, because it lives INSIDE the scrolling .modal-body. With a taller result list the primary
   action was pushed past the fold and the reader had to scroll a dialog to find its own submit
   button. Sticky keeps it against the bottom of the scroll box at every height; the background is
   opaque (not the scrim) so rows passing underneath do not show through, and the hairline is what
   makes it read as a footer rather than as a row that stopped moving.
   .modal-body is overflow-y:auto, which is a scroll container — sticky is honoured inside it. The
   estate has been bitten by overflow:hidden killing sticky before; this is the other case. */
.modal-actions { display: flex; gap: var(--space-2); margin-top: auto; padding-top: 16px;
                 position: sticky; bottom: 0; z-index: 2;
                 background: var(--bg-elevated); border-top: 1px solid var(--hairline); }
.modal-cancel { flex: 1; background: var(--bg-2); border: 1px solid var(--border); color: var(--text-2); border-radius: var(--r-sm); padding: 11px; font-size: 13px; font-weight: 500; cursor: pointer; font-family: inherit; }
.modal-cancel:hover { border-color: var(--border-2); color: var(--text); }
.modal-submit { flex: 2; border: none; border-radius: var(--r-sm); padding: 11px; font-size: 13px; font-weight: 600; cursor: pointer; font-family: inherit; }
.modal-submit.dir-below { background: var(--green-dim); color: var(--green); border: 1px solid var(--green-dim); }
.modal-submit.dir-above { background: var(--gold-dim); color: var(--gold); border: 1px solid var(--gold-dim); }
.modal-submit:disabled { opacity: 0.4; cursor: not-allowed; }
.modal-err { font-size: 12px; color: var(--red-bright); margin-top: 8px; min-height: 16px; }
.modal-err a { color: var(--gold); }
.item-search-wrap { display: flex; flex-direction: column; }
/* THE RESULT LIST WAS THE WHOLE DIALOG'S BOTTLENECK. A flat 240px against 51px rows showed 4.7 of
   them, so a search returning 20 items hid 16 behind a scrollbar — measured, at every viewport from
   390px to 1920px, because a fixed pixel height cannot know it is on a 1080px-tall screen. It scales
   with the viewport now and keeps the old 240px as the floor, so nothing gets SHORTER on a small
   screen; the ceiling stops it from swallowing a tall one. ~8 rows at 900px and up. */
.item-search-results { flex: none; max-height: clamp(240px, 44vh, 420px); border: 1px solid var(--border); border-radius: var(--r-sm); overflow-y: auto; background: var(--bg-2); margin-top: 6px; min-height: 0; }
/* No standing empty box. Before anybody types, the results container is EMPTY (the JS writes nothing
   into it), and an empty bordered void under the search field was one of the four look-alike boxes
   the composer used to open as — the input's own placeholder already promises what typing does. It
   appears with the first thing worth showing: results, "Searching…", or the too-short hint. */
.item-search-results:empty { display: none; }
.item-result { display: flex; align-items: center; gap: 9px; padding: 9px 10px; cursor: pointer; transition: background var(--t-fast) var(--ease); font-size: 13px; border-bottom: 1px solid var(--hairline); }
button.item-result { width: 100%; text-align: left; background: none; border: 0; border-bottom: 1px solid var(--hairline); font: inherit; color: inherit; cursor: pointer; }
.item-result:last-child { border-bottom: none; }

.item-result.selected-item { background: var(--blue-dim); border: 1px solid var(--blue); }
.item-result:hover { background: var(--bg-3); }
.item-result img { width: 32px; height: 32px; border-radius: var(--r-xs); object-fit: contain; background: var(--bg-3); flex-shrink: 0; }
.item-result-none { padding: var(--space-4); font-size: 12px; color: var(--text-3); text-align: center; }
@media (max-width: 640px) { .modal { padding: 18px; } }

.trait-field.hidden { display: none; }
.custom-select { position: relative; user-select: none; }
.custom-select-val {
  width: 100%; background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--r-sm); color: var(--text); font-size: 13px; padding: 10px 28px 10px 11px;
  box-sizing: border-box; cursor: pointer; font-family: inherit; transition: border-color var(--t-fast) var(--ease);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center;
}
.custom-select-val:hover, .custom-select.open .custom-select-val { border-color: var(--border-2); }
.custom-select-opts {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 50;
  background: var(--bg-elevated); border: 1px solid var(--border-2); border-radius: var(--r-sm);
  max-height: 220px; overflow-y: auto; display: none; box-shadow: var(--shadow-lg);
}
.custom-select.open .custom-select-opts { display: block; }
.custom-select-opt { padding: 9px 11px; font-size: 13px; cursor: pointer; color: var(--text-2); transition: background var(--t-fast) var(--ease); }
.custom-select-opt:hover { background: var(--bg-3); color: var(--text); }
.custom-select-opt.sel { color: var(--blue-bright); }
.custom-select-opt.disabled-opt { color: var(--text-3); pointer-events: none; font-style: italic; }
  /* A STATUS LINE, NOT A FIELD. This used to wear the .modal-input recipe — border, bg-2 fill, the
     same radius — which made "Pick an item to start." indistinguishable from a disabled text input
     sitting directly above the one REAL input on the dialog. It is a sentence the form writes, so it
     is set as prose: no box, placeholder-toned (.s-dim, --text-3) until it carries a real alert,
     then --text-2 with the load-bearing words in --text. */
  .modal-summary { margin: 10px 0 0; font-size: 12.5px; line-height: 1.5;
    color: var(--text-2); }
  .modal-summary b { color: var(--text); font-weight: 600; }
  .modal-summary .s-dim { color: var(--text-3); }
  .modal-summary .s-below { color: var(--green); font-weight: 600; }
  .modal-summary .s-above { color: var(--gold); font-weight: 600; }

  /* THE DM REQUIREMENT, in the composer header. Three states and three weights of voice:
       is-warn   we KNOW they are not in the server — a stronger border and a heavier heading,
                 because there is something to do. Not gold: a full gold wash on a warning made
                 the box louder than the price it sat under, and gold here means money and
                 Premium. The Join button beside it is the actionable element.
       (base)    we could not check — neutral grey, because a failed lookup is not an accusation
       is-ok     we know the DM lands — one quiet line, no panel; a green box for "nothing is wrong"
                 is noise on a form somebody opens twenty times.
     There is deliberately no red: nothing here blocks the save any more. */
  .mdm { margin: 10px 0 0; padding: 10px 11px; border-radius: var(--r-sm);
    border: 1px solid var(--border); background: var(--bg-2);
    display: flex; flex-direction: column; gap: 7px; }
  .mdm.hidden { display: none; }
  .mdm.is-warn { border-color: var(--border-2); }
  .mdm-h { margin: 0; font-family: var(--font-display); font-size: 12.5px; font-weight: 700;
    color: var(--text); }
  .mdm p { margin: 0; font-size: 11.5px; line-height: 1.55; color: var(--text-2); }
  .mdm-btns { display: flex; gap: 8px; flex-wrap: wrap; }
  .mdm-btns .btn { min-height: 32px; }
  /* is-ok collapses the panel back to a single line of help text. */
  .mdm.is-ok { border: 0; background: none; padding: 6px 0 0; gap: 0; }
  .mdm.is-ok .mdm-h { display: none; }
  .mdm.is-ok p { font-size: 11px; color: var(--text-3); }
  /* is-compact: the HOST PAGE is already saying this. /my-alerts keeps a standing delivery banner
     with the same fact and the same button pair, and the full card here repeated all nine lines of
     it two inches away (~230px of a phone viewport before the first form field). When that banner is
     on screen the composer keeps one line and the recheck action; the join button lives on the
     banner. Pages without the banner (/item, /alerts) keep the full card — there this is the only
     telling. */
  .mdm.is-compact { border: 0; background: none; padding: 6px 0 0; gap: 0; }
  .mdm.is-compact p { font-size: 11px; color: var(--text-2); }
  /* The inline recheck: same dashed-underline text-action grammar as .ma-why / .rev-sim-btn, with
     the page's own 24px floor under it. */
  .mdm-act { display: inline-flex; align-items: center; min-height: 24px; padding: 0 2px;
    margin-left: 4px; font: inherit; font-size: 11px; font-weight: 600; color: var(--text-2);
    background: none; border: 0; border-bottom: 1px dashed var(--border-2); cursor: pointer; }
  .mdm-act:hover { color: var(--text); }
  .mdm-act:focus-visible { outline: 2px solid var(--blue-bright); outline-offset: 1px; }
  .mdm-act:disabled { opacity: .55; cursor: default; }

  /* THE PRICE LADDER. Four rungs, each carrying the share of hours behind it. The evidence line is
     as much a part of the control as the price is — a rung without it is a slogan — so it is set in
     the same block, not in a tooltip. The rungs get progressively cooler and quieter towards
     Exceptional: the rare one should read as rare, not as the recommended default. */
  .thr-tiers { margin-top: 10px; }
  .thr-tiers.hidden { display: none; }
  .tt-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
    font-size: 11.5px; font-weight: 600; color: var(--text-2); margin-bottom: 7px; }
  .tt-win { font-family: var(--font-mono); font-size: 10px; font-weight: 400; color: var(--text-3); }
  .tt-win b { color: var(--text-2); font-weight: 600; }
  .tt-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: 6px; }
  /* Four rungs do not fit a phone. auto-fit lands on 3+1, which reads as a broken row; 2x2 keeps the
     ladder legible as a ladder. */
  @media (max-width: 520px) { .tt-row { grid-template-columns: repeat(2, 1fr); } }
  .thr-chip { display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
    min-height: 32px; padding: 7px 10px; border-radius: var(--r-sm);
    border: 1px solid var(--border); background: var(--bg-2); color: var(--text-2);
    font: inherit; font-size: 11.5px; cursor: pointer; text-align: left;
    transition: border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease),
                background var(--t-fast) var(--ease); }
  .thr-chip:hover { border-color: var(--border-2); color: var(--text); background: var(--bg-3); }
  .thr-chip b { font-family: var(--font-mono); color: var(--text); font-weight: 600; }
  .tt-label { font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--text-3); }
  .tt-price { font-size: 14px; font-variant-numeric: tabular-nums; line-height: 1.15; }
  .tt-ev { font-family: var(--font-mono); font-size: 9.5px; color: var(--text-3); }
  .tt-typical .tt-label     { color: var(--text-2); }
  .tt-good .tt-label        { color: var(--blue-bright); }
  .tt-great .tt-label       { color: var(--green); }
  .tt-exceptional .tt-label { color: var(--gold); }
  .tt-exceptional           { border-color: rgba(232,163,61,0.30); }
  .tt-note { margin: 0; font-size: 11.5px; line-height: 1.55; color: var(--text-3); }
  .tt-note b { color: var(--text-2); font-family: var(--font-mono); font-weight: 600; }
  .thr-tiers.is-thin, .thr-tiers.is-loading { border: 1px dashed var(--border);
    border-radius: var(--r-sm); padding: 9px 11px; background: var(--bg-1); }

  /* THE CENSUS. Shown INSTEAD of the ladder when the gate fails, and it has to be legible at a glance
     as a weaker kind of statement, because it is one: a list of what happened, not four graded
     recommendations. So it borrows the thin box's dashed border rather than the ladder's chips, runs
     as a stacked list rather than a 2x2 grid, and carries no colour at all except the OBSERVED tag and
     a single hairline bar for the hour share. Nothing in here is green, gold or blue — those three
     mean "we rate this" on the ladder two lines up, and the whole point is that we do not rate this. */
  .thr-tiers.is-observed { border: 1px dashed var(--border); border-radius: var(--r-sm);
    padding: 10px 11px 9px; background: var(--bg-1); }
  .ob-head { display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; margin-bottom: 8px; }
  .ob-tag { font-family: var(--font-mono); font-size: 8.5px; font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.1em; color: var(--text-3); border: 1px solid var(--border-2); border-radius: 3px;
    padding: 2px 5px; line-height: 1; }
  .ob-h { font-size: 11.5px; font-weight: 600; color: var(--text-2); }
  .ob-win { font-family: var(--font-mono); font-size: 10px; color: var(--text-3); flex-basis: 100%; }
  .ob-win b { color: var(--text-2); font-weight: 600; }
  .ob-lead, .ob-one { margin: 0 0 7px; font-size: 11.5px; line-height: 1.55; color: var(--text-2); }
  .ob-lead b, .ob-one b { color: var(--text); font-family: var(--font-mono); font-weight: 600; }
  .ob-list { list-style: none; margin: 0 0 8px; padding: 0; display: flex; flex-direction: column; gap: 3px; }
  .ob-row { display: grid; grid-template-columns: minmax(58px, auto) 34px 1fr 34px; align-items: center;
    gap: 8px; width: 100%; min-height: 30px; padding: 5px 8px; border-radius: var(--r-sm);
    border: 1px solid transparent; background: var(--bg-2); color: var(--text-2);
    font: inherit; font-size: 11px; text-align: left; cursor: pointer;
    transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease); }
  .ob-row:hover { border-color: var(--border-2); background: var(--bg-3); }
  /* The focus ring, and NOT a quieter one. This said `outline: 2px solid var(--border-2)`, which is
     #2c3444 — the same hairline these rows are separated by — so overriding the site ring
     (--blue-bright, styles.css) with it replaced a visible ring with an invisible one, on the only
     rows on this panel a keyboard user can reach. The offset stays at 1px because the rows sit 3px
     apart and the site default of 3px would run the ring into its neighbour; the colour is the
     site's. */
  .ob-row:focus-visible { outline: 2px solid var(--blue-bright); outline-offset: 1px; }
  .ob-price { font-family: var(--font-mono); font-size: 12.5px; font-weight: 600; color: var(--text);
    font-variant-numeric: tabular-nums; }
  .ob-hours, .ob-share { font-family: var(--font-mono); font-size: 9.5px; color: var(--text-3);
    font-variant-numeric: tabular-nums; }
  .ob-share { text-align: right; }
  /* The bar is decoration for the number beside it and nothing else — aria-hidden, and every row
     still states its hour count in words. */
  .ob-bar { display: block; height: 3px; border-radius: 2px; background: var(--bg-3); overflow: hidden; }
  .ob-bar i { display: block; height: 100%; background: var(--text-3); }
  /* An inline price inside the one-price sentence: clickable, but typographically part of the prose. */
  .ob-inline { font: inherit; font-family: var(--font-mono); font-weight: 600; color: var(--text);
    background: none; border: 0; border-bottom: 1px dashed var(--border-2); padding: 0 1px;
    cursor: pointer; }
  .ob-inline:hover { border-bottom-color: var(--text-3); }
  .ob-foot { margin: 0; font-size: 10.5px; line-height: 1.5; color: var(--text-3); }
  .ob-foot b { color: var(--text-2); font-family: var(--font-mono); font-weight: 600; }
  .ob-caveat { margin-top: 5px; padding-top: 6px; border-top: 1px solid var(--hairline); }
  @media (max-width: 420px) {
    .ob-row { grid-template-columns: minmax(52px, auto) 30px 1fr 30px; gap: 6px; }
  }

  .modal-more { margin-bottom: 14px; border-top: 1px solid var(--hairline); }
  .modal-more > summary { display: flex; align-items: center; gap: 8px; padding: 13px 0; cursor: pointer;
    list-style: none; min-height: 44px; font-size: 12.5px; font-weight: 600; color: var(--text-2); }
  .modal-more > summary::-webkit-details-marker { display: none; }
  .modal-more > summary::before { content: ''; flex: none; width: 7px; height: 7px;
    border-right: 1.7px solid var(--text-3); border-bottom: 1.7px solid var(--text-3);
    transform: rotate(-45deg); transition: transform var(--t-fast) var(--ease); }
  .modal-more[open] > summary::before { transform: rotate(45deg); }
  .modal-more > summary:hover { color: var(--text); }
  .modal-more-sum { margin-left: auto; font-family: var(--font-mono); font-size: 11px; color: var(--text-3); font-weight: 400; }
  .modal-more[open] .modal-more-sum { display: none; }
  /* ── THE TRADE-OFF LADDER ─────────────────────────────────────────────────────────────────────
     Three prices with their trigger counts under them, and the member's own price marked. It reads
     left to right as a range rather than as a ranking: there is no "best" cell and none of them is
     coloured as one, because which trade-off somebody wants is not something we know. */
  .trg-wrap { padding: 9px 10px; border: 1px solid var(--border); border-radius: var(--r-sm);
    background: var(--bg-1); }
  .trg-head { margin: 0 0 7px; font-size: 10.5px; color: var(--text-3); }
  .trg-head b { font-family: var(--font-mono); color: var(--text-2); }
  .trg-ladder { display: flex; align-items: flex-start; flex-wrap: wrap; gap: 10px; }
  .trg-cell { display: flex; flex-direction: column; gap: 1px; }
  .trg-p { font-family: var(--font-mono); font-size: 12.5px; font-weight: 700; color: var(--text-2); }
  .trg-n { font-family: var(--font-mono); font-size: 10px; color: var(--text-3); }
  .trg-cell.is-mine .trg-p { color: var(--gold); }
  .trg-cell.is-mine .trg-n { color: var(--gold); }
  .trg-sep { color: var(--border-2); }
  .trg-note { margin: 7px 0 0; font-size: 10px; line-height: 1.55; color: var(--text-3); }
