/* ==========================================================================
   crypto-tools-v1 -- application stylesheet.

   Tokens live in tokens.css and are loaded first; NOT ONE COLOUR, FONT STACK,
   SPACING STEP OR RADIUS IS WRITTEN AS A LITERAL BELOW THIS LINE. That is the
   same discipline platform-core's ops panel states for itself ("NO hex value,
   no rgb() literal, no font stack of its own"), and it is what makes both
   themes come free: every rule here resolves through a var() that tokens.css
   already defines twice. crypto/tests/test_design_system.py fails the build on
   a raw hex value in this file.

   The component vocabulary -- .masthead / .topbar / .subnav, .card, .btn,
   .field-row, table.ledger, .summary-tile, .alert-box, .anno, .kicker -- is
   replicated from charting-v1's static/charting/css/app.css, class names
   included, so that the two products read as one system and a rule ported
   between them lands unchanged. See docs/design-system.md for what was
   replicated, what was adapted for this product, and what was deliberately
   left behind.

   MERGE NOTE (phase2/crypto-world). That branch created a file at this exact
   path for the Crypto World market pages. This file is a strict SUPERSET of
   it: every rule that branch defines appears here, and the "Crypto World"
   section near the foot carries them verbatim. On merge, resolve the add/add
   conflict by keeping THIS file and then deleting the now-duplicated token
   block from the other one -- do not hand-merge two palettes. Recorded in
   docs/design-system.md so whoever does the merge is not re-deriving it.
   ========================================================================== */

/* ---- base --------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: var(--md-link); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--md-text); }

h1, h2, h3 { font-family: var(--font-display); font-weight: 700; letter-spacing: -0.01em; color: var(--ink); margin: 0; }
h1 { font-size: var(--fs-display); line-height: 1.08; }
h2 { font-size: var(--fs-h2); line-height: 1.25; }
h3 { font-size: var(--fs-h3); line-height: 1.3; }
h4 { font-family: var(--font-mono); font-size: var(--fs-xs); font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--slate); margin: 0; }

p { margin: 0 0 var(--s4); }
p:last-child { margin-bottom: 0; }
ul, ol { margin: 0 0 var(--s4); padding-left: var(--s5); }
blockquote { margin: 0 0 var(--s4); padding: var(--s3) var(--s5); border-left: 3px solid var(--verified);
  background: var(--verified-tint); border-radius: 0 var(--r) var(--r) 0; }
blockquote footer { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--slate); }
small { font-size: var(--fs-xs); color: var(--slate); line-height: 1.45; }
strong { font-weight: 700; }

.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.muted { color: var(--slate); }
.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
code { font-family: var(--font-mono); font-size: 0.92em; }

:focus-visible { outline: 2px solid var(--verified); outline-offset: 2px; border-radius: 1px; }

input, select, textarea {
  background-color: var(--surface);
  color: var(--ink);
  border-color: var(--rule-strong);
}
select option, option, optgroup { background-color: var(--surface); color: var(--ink); }
input::placeholder, textarea::placeholder { color: var(--slate); opacity: 1; }
input:disabled, select:disabled, textarea:disabled, option:disabled { color: var(--slate); opacity: 1; cursor: not-allowed; }

/* ---- utility ------------------------------------------------------------ */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }

/* Keyboard users reach the content without tabbing the whole nav first. */
.skip-link { position: absolute; left: -9999px; top: 0; z-index: 20;
  background: var(--verified); color: var(--on-accent); padding: var(--s2) var(--s4);
  border-radius: 0 0 var(--r) 0; font-size: var(--fs-sm); font-weight: 600; text-decoration: none; }
.skip-link:focus { left: 0; color: var(--on-accent); }

.stack > * + * { margin-top: var(--s5); }
.prose { max-width: var(--maxw-prose); }

/* Progressive enhancement contract, expressed in one rule.

   Anything that only means something once JavaScript has run carries the
   `hidden` attribute in the server-rendered HTML, and the script that owns it
   removes the attribute on start-up. So a browser with JS off never sees a
   control that would do nothing -- it does not merely look unstyled, it is not
   painted at all. `[hidden]` is honoured by the browser already; the !important
   is here because several display rules below would otherwise win against it.
   Asserted by crypto/tests/test_progressive_enhancement.py. */
[hidden] { display: none !important; }

/* ---- masthead: the shared nav shell -------------------------------------
   Two rows, the same anatomy the sibling products use: a persistent .topbar
   (mark slot / section switcher / right zone) and a section-local .subnav
   carrying this product's own pages.

   ONE DELIBERATE DEPARTURE from charting-v1's markup, and it is a correctness
   fix rather than a style choice: over there the narrow-viewport subnav is a
   <button class="nav-toggle"> that toggles a class from JavaScript, so with JS
   off a phone gets no navigation at all. Here the disclosure is a native
   <details>/<summary>, which opens and closes with no script. Above the
   breakpoint the summary is hidden and the list is shown unconditionally, so
   the [open] state is irrelevant there. See docs/design-system.md. */
.masthead { background: var(--surface); border-top: 4px solid var(--verified); border-bottom: 3px double var(--rule-strong); }
.topbar { width: 100%; margin: 0 auto; padding: var(--s4) var(--gutter); box-sizing: border-box;
  display: flex; align-items: center; gap: var(--s5); flex-wrap: wrap; }

.mark-slot { display: inline-flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem; flex-shrink: 0; }
.mark-glyph { width: 12px; height: 12px; background: var(--verified); display: inline-block; flex-shrink: 0; }

.section-switcher { display: flex; align-items: center; gap: var(--s2); font-family: var(--font-display); flex-wrap: wrap; }
.section-link { font-weight: 700; font-size: var(--fs-sm); color: var(--slate); text-decoration: none;
  padding: 0.35rem var(--s4); border-radius: 999px; }
.section-link:hover { color: var(--ink); background: var(--paper); }
.section-link.section-active { background: var(--verified); color: var(--on-accent); font-weight: 800; }
.section-link.section-active:hover { background: var(--verified); color: var(--on-accent); }
.section-link.disabled { cursor: default; opacity: 0.6; }

.topbar-right { margin-left: auto; display: flex; align-items: center; gap: var(--s4);
  font-size: var(--fs-xs); color: var(--slate); font-family: var(--font-mono); flex-wrap: wrap; }
.fy-chip { display: inline-flex; align-items: center; gap: var(--s1);
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--ink);
  background: var(--paper); border: 1px solid var(--rule-strong); border-radius: var(--r);
  padding: 0.2rem 0.55rem; }

/* The manual theme toggle. Same chip vocabulary as .fy-chip beside it
   (paper background, rule-strong border, --r radius) so the two read as one
   family in .topbar-right; sized and shaped as a button rather than a chip
   since it is one. Ships `hidden` in the HTML -- theme.js is the only thing
   that ever reveals it (the interactivity contract, docs/design-system.md). */
.theme-toggle { display: inline-flex; align-items: center; justify-content: center;
  width: 1.7rem; height: 1.7rem; padding: 0; font-size: var(--fs-sm); line-height: 1;
  color: var(--ink); background: var(--paper); border: 1px solid var(--rule-strong);
  border-radius: var(--r); cursor: pointer; }
.theme-toggle:hover { border-color: var(--slate); color: var(--verified-ink); }

/* The glyph is CSS content keyed off the live theme, never text theme.js
   writes -- the same discipline that keeps every word on this product inside
   the copy guard's rendered-page scan. Sun when the effective theme is
   light (offering to switch to dark), crescent moon when it is dark. Mirrors
   tokens.css's own three-block shape for the same reason: the media-query
   form covers the OS-default case, the [data-theme] form lets an explicit
   choice win in both directions. */
.theme-toggle-icon::before { content: "\2600"; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle-icon::before { content: "\263D"; }
}
:root[data-theme="dark"] .theme-toggle-icon::before { content: "\263D"; }
:root[data-theme="light"] .theme-toggle-icon::before { content: "\2600"; }

.nav-disclosure { border-top: 1px solid var(--rule); }
.nav-disclosure > summary { display: none; list-style: none; cursor: pointer;
  padding: var(--s3) var(--gutter); font-family: var(--font-mono); font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--slate);
  align-items: center; gap: var(--s2); }
.nav-disclosure > summary::-webkit-details-marker { display: none; }
.nav-disclosure > summary:hover { color: var(--verified-ink); }
.nav-toggle-bars, .nav-toggle-bars::before, .nav-toggle-bars::after {
  display: block; width: 1.1rem; height: 2px; background: var(--ink); }
.nav-toggle-bars { position: relative; }
.nav-toggle-bars::before, .nav-toggle-bars::after { content: ""; position: absolute; left: 0; }
.nav-toggle-bars::before { top: -6px; }
.nav-toggle-bars::after { top: 6px; }

.subnav { padding: var(--s3) var(--gutter);
  display: flex; align-items: center; gap: var(--s4); font-size: var(--fs-sm); flex-wrap: wrap; }
.subnav a { font-weight: 500; color: var(--ink); text-decoration: none; display: inline-flex; align-items: center; gap: var(--s1);
  padding-bottom: 0.2rem; box-shadow: 0 2px 0 0 transparent; transition: color 120ms ease, box-shadow 120ms ease; }
.subnav a:hover { color: var(--ink); box-shadow: 0 2px 0 0 var(--rule-strong); }
.subnav a.active { color: var(--verified-ink); font-weight: 700; box-shadow: 0 2px 0 0 var(--verified); }
.subnav .subnav-group { font-family: var(--font-mono); font-size: var(--fs-xs); text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--slate); }
.subnav .subnav-group.subnav-group-current { color: var(--verified-ink); font-weight: 700; }

/* ---- layout ------------------------------------------------------------- */
main { max-width: var(--maxw); margin: 0 auto; padding: var(--s6) var(--gutter) var(--s7); }

.page-head { margin-bottom: var(--s5); }
.kicker { display: block; font-family: var(--font-mono); font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: 0.08em; color: var(--verified-ink); margin-bottom: var(--s2); }
.anno { font-family: var(--font-mono); font-size: var(--fs-xs); text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--slate); }
.sub { color: var(--slate); font-size: var(--fs-sm); max-width: var(--maxw-prose); }
.page-head .sub { margin-top: var(--s2); }
.lede { max-width: var(--maxw-prose); }

.backlink { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--slate); text-decoration: none;
  text-transform: uppercase; letter-spacing: 0.04em; }
.backlink:hover { color: var(--ink); }

/* The row of sibling-page links every calculator carries at its head and
   foot. Compact and quiet -- these are lateral moves, not the page's action. */
.page-links { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s3); align-items: center;
  font-size: var(--fs-sm); margin: 0 0 var(--s5); padding: 0; list-style: none; }
.page-links li { display: inline-flex; }
.page-links a { text-decoration: none; color: var(--ink); border-bottom: 1px solid var(--rule-strong); }
.page-links a:hover { color: var(--ink); border-bottom-color: var(--rule-strong); }

/* An explanatory section under the tool: long-form prose that carries the
   page's SEO weight and must stay comfortably readable. */
.article { max-width: var(--maxw-prose); margin-top: var(--s7); }
.article > section + section { margin-top: var(--s6); }
.article h2 { margin-bottom: var(--s3); padding-bottom: var(--s2); border-bottom: 1px solid var(--rule); }
.article h3 { margin: var(--s5) 0 var(--s2); }

/* ---- buttons ------------------------------------------------------------ */
.btn, .btn-ghost { font-family: var(--font-body); font-weight: 600; font-size: var(--fs-sm);
  padding: 0.5rem 1.1rem; border-radius: var(--r); cursor: pointer; min-height: 2.25rem;
  box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center;
  text-decoration: none; border: none; }
.btn { background: var(--verified); color: var(--on-accent); border: 1px solid var(--verified-ink); font-weight: 600; }
.btn:hover { background: var(--md-primary-hover); color: var(--on-accent); }
.btn-ghost { background: transparent; color: var(--slate); border: 1px solid var(--rule-strong); font-weight: 500; }
.btn-ghost:hover { background: var(--paper); border-color: var(--slate); color: var(--ink); }
.btn-small { font-family: var(--font-mono); font-size: var(--fs-xs); background: transparent;
  border: 1px solid var(--rule-strong); color: var(--ink); border-radius: var(--r);
  padding: 0.22rem 0.55rem; cursor: pointer; white-space: nowrap; letter-spacing: 0.02em; }
.btn-small:hover { background: var(--paper); border-color: var(--slate); }

.btn, .btn-ghost, .btn-small { transition: background-color 120ms ease, border-color 120ms ease, transform 80ms ease; }
.btn:active { background: var(--md-primary-active); transform: scale(0.96); }
.btn-ghost:active { background: var(--paper); border-color: var(--slate); transform: scale(0.96); }
.btn-small:active { background: var(--paper); border-color: var(--slate); transform: scale(0.96); }

/* A press response, not only a hover swap: a touch tap produces no hover
   state at all, so hover-only feedback tells a phone user nothing. */
@media (pointer: coarse) {
  .btn-small { padding: 0.55rem 0.8rem; min-height: 2.75rem; }
}
@media (prefers-reduced-motion: reduce) {
  .btn, .btn-ghost, .btn-small, .subnav a { transition: none; }
}

.form-actions { display: flex; flex-wrap: wrap; gap: var(--s3); align-items: center; margin-top: var(--s5); }

/* The submit button while the browser is uploading and the server is
   computing. Set from JS on submit; with JS off the button simply never
   enters this state and the browser's own progress indicator does the job. */
.btn[data-busy="true"] { opacity: 0.75; cursor: progress; }
.spinner { display: inline-block; width: 0.8em; height: 0.8em; margin-right: var(--s2);
  border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%;
  animation: spin 700ms linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 2400ms; } }

/* ---- panels and cards --------------------------------------------------- */
.card {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  padding: var(--s5);
  box-shadow: var(--shadow-1);
}
.panel { background: var(--surface); border: 1px solid var(--rule-strong); border-radius: var(--r);
  padding: var(--s5); box-shadow: var(--shadow-1); }
.panel > h2:first-child, .panel > legend { margin-bottom: var(--s4); }
.panel-wide { max-width: none; }
.section-head { display: flex; align-items: baseline; gap: var(--s3); flex-wrap: wrap; margin-bottom: var(--s3); }

/* ---- forms -------------------------------------------------------------- */
.form-card { border: 1px solid var(--rule-strong); border-radius: var(--r); background: var(--surface);
  padding: var(--s5); box-shadow: var(--shadow-1); max-width: var(--maxw-prose); }
.form-card.wide { max-width: none; }

.field-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: 0 var(--s5); }

.field-row { margin-bottom: var(--s4); }
.field-row label { display: block; font-family: var(--font-mono); font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: 0.04em; color: var(--slate); margin-bottom: var(--s1); }
.field-row input, .field-row select, .field-row textarea { width: 100%; background: var(--paper);
  border: 1px solid var(--rule-strong); color: var(--ink); font-family: var(--font-body);
  font-size: var(--fs-sm); padding: 0.5rem 0.6rem; border-radius: var(--r); box-sizing: border-box;
  transition: border-color 120ms ease, box-shadow 120ms ease; }
.field-row input:hover, .field-row select:hover, .field-row textarea:hover { border-color: var(--slate); }
.field-row input:focus, .field-row select:focus, .field-row textarea:focus { border-color: var(--verified); }
.field-row input[type="number"], .field-row input[inputmode="decimal"] { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.field-row.has-error input, .field-row.has-error select { border-color: var(--risk); }
.field-row.is-required label::after { content: " *"; color: var(--risk); }

.help { font-size: var(--fs-xs); color: var(--slate); margin-top: var(--s1); display: block; line-height: 1.4; max-width: var(--maxw-prose); }
.errorlist { color: var(--risk); font-size: var(--fs-xs); list-style: none; padding: 0; margin: var(--s1) 0 0; }
.errorlist li { margin: 0; }

/* Radio group (the cost-basis choice). A fieldset, so the group has one
   accessible name and the legend is not read as a field label. */
.choice-group { border: 1px solid var(--rule); border-radius: var(--r); padding: var(--s3) var(--s4);
  margin: 0 0 var(--s4); background: var(--paper); }
.choice-group > legend { font-family: var(--font-mono); font-size: var(--fs-xs); text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--slate); padding: 0 var(--s2); }
.choice-group > div { display: flex; flex-direction: column; gap: var(--s2); }
.choice-group label { display: flex; align-items: flex-start; gap: var(--s2); font-size: var(--fs-sm);
  color: var(--ink); text-transform: none; letter-spacing: 0; font-family: var(--font-body); margin: 0;
  cursor: pointer; padding: var(--s1) 0; }
.choice-group label:hover { color: var(--verified-ink); }
.choice-group input { margin-top: 0.25rem; accent-color: var(--verified); }

/* ---- file upload -------------------------------------------------------- */
/* The drop zone IS the label for the file input, so clicking it opens the
   picker with no script at all. Drag-and-drop and the selected-file readout
   are the enhancement layered on top; with JS off this is a labelled file
   input in a bordered box, which is exactly what it was before. */
/* The wrapper is NOT .field-row: that class restyles any <label> inside it
   into a small mono caption, which would flatten the drop box. */
.upload-row { margin-bottom: var(--s5); }
.dropzone { display: block; border: 2px dashed var(--rule-strong); border-radius: var(--r-lg);
  background: var(--paper); padding: var(--s5); text-align: center; cursor: pointer;
  transition: border-color 120ms ease, background-color 120ms ease; }
.dropzone:hover { border-color: var(--slate); }
.dropzone.is-dragover { border-color: var(--verified); background: var(--verified-tint); }
.dropzone.has-file { border-style: solid; border-color: var(--verified); }
.dropzone.has-error { border-color: var(--risk); background: var(--risk-tint); }
.dropzone-title { display: block; font-family: var(--font-mono); font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--slate); margin-bottom: var(--s2); }
.dropzone-hint { display: block; font-size: var(--fs-xs); color: var(--slate); margin-top: var(--s2); }
.dropzone input[type="file"] { display: block; margin: 0 auto; max-width: 100%; font-size: var(--fs-sm); color: var(--ink); }
.dropzone-readout { display: block; margin-top: var(--s3); font-family: var(--font-mono);
  font-size: var(--fs-xs); color: var(--ink); word-break: break-all; }
.dropzone-readout.is-error { color: var(--risk); }
@media (prefers-reduced-motion: reduce) { .dropzone { transition: none; } }

/* ---- the live preview panel --------------------------------------------
   Visually and semantically distinct from a submitted result: dashed rule,
   attention tint, mono caption, and a heading that says what it is. The
   submitted result is a solid-bordered .result-panel in the accent colour.
   The two must never be confusable -- one is arithmetic this browser did,
   the other is the figure the server computed and the page stands behind. */
.preview-panel { border: 1px dashed var(--attention); border-left: 3px solid var(--attention);
  background: var(--attention-tint); border-radius: var(--r); padding: var(--s4);
  margin: var(--s5) 0 0; max-width: var(--maxw-prose); }
.preview-label { font-family: var(--font-mono); font-size: var(--fs-xs); text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--attention); display: block; margin-bottom: var(--s2); }
.preview-figure { font-family: var(--font-display); font-weight: 800; font-size: var(--fs-stat);
  line-height: 1.05; font-variant-numeric: tabular-nums; display: block; }
.preview-lines { list-style: none; margin: var(--s3) 0 0; padding: 0;
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--slate); }
.preview-lines li { display: flex; justify-content: space-between; gap: var(--s4); padding: 0.1rem 0; }
.preview-note { font-size: var(--fs-xs); color: var(--slate); margin: var(--s3) 0 0; }

.result-panel { border: 1px solid var(--rule-strong); border-left: 3px solid var(--verified);
  background: var(--surface); border-radius: var(--r); padding: var(--s5); margin-top: var(--s5);
  box-shadow: var(--shadow-1); }
.headline { font-family: var(--font-display); font-weight: 800; font-size: var(--fs-stat);
  line-height: 1.1; color: var(--ink); font-variant-numeric: tabular-nums; margin: 0 0 var(--s4); }
.headline strong { font-weight: 800; }

/* ---- repeatable rows ----------------------------------------------------
   The DCA schedule and the loss-offset position list. The server renders a
   fixed number of blank rows, which is a complete and usable form on its own;
   the script only adds the ability to append and withdraw rows beyond that. */
.row-tools { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center; margin-top: var(--s3); }
.row-count { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--slate); }
.row-drop { background: none; border: none; color: var(--slate); cursor: pointer; font: inherit;
  font-size: var(--fs-xs); padding: var(--s1) var(--s2); border-radius: var(--r); line-height: 1; }
.row-drop:hover { color: var(--risk); background: var(--risk-tint); }
table.ledger td.row-drop-cell { width: 2.5rem; text-align: center; }

/* The running total the loss-offset page keeps as rows are typed. Same
   preview vocabulary as above, deliberately -- it is the same kind of claim. */
.running-total { display: flex; flex-wrap: wrap; gap: var(--s4); align-items: baseline;
  border-top: 1px dashed var(--attention); margin-top: var(--s3); padding-top: var(--s3);
  font-family: var(--font-mono); font-size: var(--fs-sm); }
.running-total .running-label { font-size: var(--fs-xs); text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--attention); }
.running-total .running-value { font-variant-numeric: tabular-nums; color: var(--ink); }

/* ---- tables ------------------------------------------------------------- */
.table-scroll { overflow-x: auto; border: 1px solid var(--rule-strong); border-radius: var(--r); background: var(--surface); }
table.ledger { width: 100%; border-collapse: collapse; min-width: 480px; }
table.ledger caption { caption-side: top; text-align: left; font-family: var(--font-mono);
  font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.06em; color: var(--slate);
  padding: var(--s3) var(--s3) var(--s2); }
table.ledger thead th { font-family: var(--font-mono); font-weight: 500; font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--slate);
  text-align: left; padding: var(--s3) var(--s3); white-space: nowrap;
  border-bottom: 3px double var(--rule-strong); background: var(--surface); }
table.ledger thead th.num { text-align: right; }
table.ledger tbody td, table.ledger tbody th { padding: var(--s3) var(--s3); border-bottom: 1px solid var(--rule);
  font-size: var(--fs-sm); vertical-align: baseline; text-align: left; font-weight: 400; }
table.ledger tbody th[scope="row"] { color: var(--slate); font-weight: 500; }
table.ledger tbody td.num { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
table.ledger tbody tr:last-child td, table.ledger tbody tr:last-child th { border-bottom: none; }
table.ledger tbody tr:hover td, table.ledger tbody tr:hover th { background: var(--md-surface-3); }
table.ledger tfoot td, table.ledger tfoot th { padding: var(--s3); border-top: 2px solid var(--rule-strong);
  font-size: var(--fs-sm); font-weight: 700; text-align: left; }
table.ledger tfoot td.num { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
table.ledger input, table.ledger select { width: 100%; background: var(--paper); border: 1px solid var(--rule-strong);
  color: var(--ink); font-family: var(--font-mono); font-size: var(--fs-sm); padding: 0.4rem 0.5rem;
  border-radius: var(--r); box-sizing: border-box; }

/* A two-column "row label / figure" table -- the shape most result panels on
   this product use. It is a ledger with the first column doing the labelling. */
table.ledger.figures th[scope="row"] { width: auto; color: var(--ink); font-weight: 500; }
table.ledger.figures td { width: 12rem; }

/* The sort/filter strip above a long result table. Rendered `hidden` and
   revealed by tables.js, so with JS off there is no dead control on screen --
   only the table, fully populated and in the server's order. */
.table-toolbar { display: flex; flex-wrap: wrap; gap: var(--s3); align-items: center;
  margin-bottom: var(--s3); font-size: var(--fs-sm); }
.table-toolbar label { font-family: var(--font-mono); font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: 0.04em; color: var(--slate); }
.table-toolbar input { background: var(--paper); border: 1px solid var(--rule-strong); color: var(--ink);
  font-family: var(--font-body); font-size: var(--fs-sm); padding: 0.35rem 0.55rem; border-radius: var(--r); }
.table-filter-count { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--slate); }
/* A button built by tables.js out of the heading's own text. It inherits
   everything so the column head still reads as a column head. */
.linkish { background: none; border: none; color: inherit; cursor: pointer;
  font: inherit; letter-spacing: inherit; text-transform: inherit; padding: 0; }
th.sortable { cursor: pointer; user-select: none; }
th.sortable:hover { color: var(--verified-ink); }
th.sortable::after { content: " \2195"; opacity: 0.35; }
th.sortable[aria-sort="ascending"]::after { content: " \2191"; opacity: 1; }
th.sortable[aria-sort="descending"]::after { content: " \2193"; opacity: 1; }

/* ---- stat tiles --------------------------------------------------------- */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: var(--s4); margin: var(--s4) 0; }
.stat { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r); padding: var(--s3) var(--s4); }
.stat .label { display: block; font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.06em; color: var(--slate); }
.stat .value { display: block; font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 1.15rem; margin-top: var(--s1); }
.stat .value.headline { font-size: var(--fs-stat); font-weight: 600; }

.summary-tile { background: var(--surface); border: 1px solid var(--rule-strong);
  border-left: 3px solid var(--verified); border-radius: var(--r); padding: var(--s4);
  display: flex; flex-direction: column; gap: var(--s2); }
.summary-tile:hover { box-shadow: var(--shadow-2); }
.summary-tile .tile-label { font-family: var(--font-mono); font-size: var(--fs-xs);
  letter-spacing: 0.03em; color: var(--slate); text-transform: uppercase; }
.summary-tile .tile-value { font-family: var(--font-display); font-weight: 800; font-size: var(--fs-stat);
  line-height: 1.05; color: var(--ink); font-variant-numeric: tabular-nums; }
.summary-tile .tile-note { font-size: var(--fs-xs); color: var(--slate); }
.summary-tile .tile-value.gain { color: var(--verified-ink); }
.summary-tile .tile-value.loss { color: var(--risk); }
.gain { color: var(--verified-ink); }
.loss { color: var(--risk); }

/* ---- notes, alerts, disclaimers ----------------------------------------- */
.note, .disclaimer {
  border-left: 3px solid var(--rule-strong);
  background: var(--surface);
  padding: var(--s3) var(--s4);
  font-size: var(--fs-sm);
  border-radius: 0 var(--r) var(--r) 0;
}
.note p, .disclaimer p { margin: 0; }
.note.attention { border-left-color: var(--attention); background: var(--attention-tint); }

/* The mandatory disclaimer (CLAUDE.md hard constraint 4). Never a footer
   link, never collapsed, never quieter than body text -- it sits at the foot
   of the content column in full, at the same size as everything else. */
.disclaimer { margin-top: var(--s7); border: 1px solid var(--rule-strong);
  border-left: 4px solid var(--attention); background: var(--attention-tint);
  border-radius: var(--r); padding: var(--s4) var(--s5); max-width: var(--maxw-prose); color: var(--ink); }

.alert-box { border: 1px solid rgba(var(--risk-rgb), 0.35); border-left: 4px solid var(--attention);
  background: var(--attention-tint); border-radius: var(--r); padding: var(--s3) var(--s4);
  margin: var(--s4) 0; font-size: var(--fs-sm); color: var(--ink); max-width: var(--maxw-prose); }
.alert-box.risk { border-left-color: var(--risk); background: var(--risk-tint); }
.alert-box strong { color: var(--ink); }
.error-note { background: var(--risk-tint); border: 1px solid rgba(var(--risk-rgb),0.45);
  border-left: 3px solid var(--risk); padding: var(--s3) var(--s4); border-radius: var(--r);
  color: var(--ink); margin: var(--s4) 0; max-width: var(--maxw-prose); }
.empty-note { color: var(--slate); font-size: var(--fs-sm); background: var(--surface);
  border: 1px dashed var(--rule-strong); border-radius: var(--r); padding: var(--s5); max-width: var(--maxw-prose); }
.definition { color: var(--slate); font-size: var(--fs-xs); margin: var(--s2) 0 var(--s4); }
.caveat { color: var(--slate); font-size: var(--fs-xs); margin-top: var(--s2); display: block; max-width: var(--maxw-prose); }

.attribution { font-size: var(--fs-xs); color: var(--slate); margin-top: var(--s5); }
.attribution a { color: var(--slate); }
.links-list { list-style: none; padding: 0; margin: 0; font-size: var(--fs-sm); }
.links-list li { padding: var(--s1) 0; border-bottom: 1px solid var(--rule); word-break: break-all; }
.links-list li:last-child { border-bottom: 0; }
.empty-state { text-align: center; color: var(--slate); padding: var(--s6) var(--s4); }

/* The home page's one dominant element: the fundamentals-screener preview
   (docs/crypto-world-hub-layout.md). Sized and bordered like `.card` but
   wider and with its own entrance fade -- the only page-load animation on
   this page, gated on prefers-reduced-motion like every other transition in
   this file, and CSS-only: no script writes any of this page's text (the
   interactivity contract, docs/design-system.md). */
.home-feature { background: var(--surface); border: 1px solid var(--rule-strong);
  border-radius: var(--r-lg); padding: var(--s5); box-shadow: var(--shadow-1);
  margin-bottom: var(--s6); animation: home-feature-in 320ms ease-out; }
.home-feature-head { display: flex; justify-content: space-between; align-items: flex-end;
  gap: var(--s4) var(--s5); flex-wrap: wrap; margin-bottom: var(--s4); }
.home-feature-head > div { flex: 1 1 26rem; }
.home-feature-title { text-transform: none; font-size: 1.4rem; margin: var(--s1) 0 var(--s2); }
.home-feature-updated { margin-top: var(--s3); }
@keyframes home-feature-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .home-feature { animation: none; } }

/* Everything else on the index: the same sixteen links as before, restyled
   quieter -- smaller type, tighter padding, a plain rule instead of the
   decorative accent border every card used to carry regardless of what it
   linked to (colour is for meaning only; a uniform teal top-border on all
   sixteen cards signalled nothing).

   `auto-fill`, not `auto-fit`, in the track list -- deliberately, not a
   typo. Both compute the same number of tracks from the container's width
   alone, which is what makes every .tool-grid on this page (5 cards, then
   4, then 12) an instance of one shared column scheme rather than three
   independent ones. Where they differ is what happens to a track no row
   item reaches: auto-fit COLLAPSES it to zero width, so the existing 1fr
   cards stretch to fill the gap -- exactly what made the Filing tools row
   (4 cards) render wider cards than the Calculators row (5 cards) at the
   same viewport width, even though both grids share this one rule.
   auto-fill leaves that track at its sized width, empty, so a short row
   ends in blank space instead of wider cards, and every row's card width
   agrees with every other row's at a given breakpoint. */
.home-rail .section-head h2 { font-size: var(--fs-sm); }
.tool-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); gap: var(--s3); list-style: none; padding: 0; margin: 0; }
.tool-card { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r);
  padding: var(--s3); transition: border-color 120ms ease; }
.tool-card:hover { border-color: var(--rule-strong); }
.tool-card h3 { margin-bottom: var(--s1); font-size: var(--fs-sm); }
.tool-card h3 a { text-decoration: none; }
.tool-card p { font-size: var(--fs-xs); color: var(--slate); margin: 0; }
@media (prefers-reduced-motion: reduce) { .tool-card { transition: none; } }

/* ---- site footer -------------------------------------------------------- */
.site-foot { border-top: 1px solid var(--rule); background: var(--surface); margin-top: var(--s7); }
.site-foot-inner { max-width: var(--maxw); margin: 0 auto; padding: var(--s5) var(--gutter);
  font-size: var(--fs-xs); color: var(--slate); display: flex; flex-wrap: wrap; gap: var(--s3) var(--s5); }
.site-foot a { color: var(--slate); }
.site-foot a:hover { color: var(--md-text); }

/* ---- responsive --------------------------------------------------------- */
@media (max-width: 640px) {
  .topbar { padding: var(--s3) var(--s4); gap: var(--s3); }
  .nav-disclosure > summary { display: flex; }
  .nav-disclosure:not([open]) .subnav { display: none; }
  .subnav { flex-direction: column; align-items: flex-start; gap: var(--s3); padding: 0 var(--s4) var(--s4); }
  main { padding: var(--s5) var(--s4) var(--s6); }
  .stat-grid { grid-template-columns: 1fr; }
  .field-grid { grid-template-columns: 1fr; }
  .site-foot-inner { padding: var(--s4); }
}

/* ==========================================================================
   Crypto World (phase2/crypto-world), carried verbatim.

   These rules belong to the market pages that branch builds. They are here so
   that this file is a strict superset of that branch's stylesheet and the
   merge is "keep this file", not a two-way reconciliation of overlapping
   selectors. Nothing in Phase 1 uses them today.
   ========================================================================== */

table.data { width: 100%; border-collapse: collapse; background: var(--surface); font-size: var(--fs-sm); }
table.data caption { text-align: left; color: var(--slate); font-size: var(--fs-sm); padding-bottom: var(--s2); }
table.data th, table.data td { padding: var(--s2) var(--s3); border-bottom: 1px solid var(--rule); text-align: left; }
table.data thead th {
  font-family: var(--font-display); font-size: var(--fs-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--slate);
  border-bottom: 2px solid var(--rule-strong); white-space: nowrap;
}
table.data td.num, table.data th.num { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
table.data tbody tr:hover { background: var(--md-surface-3); }
table.data td.rank { color: var(--slate); font-family: var(--font-mono); width: 3rem; }

/* Task 18's BTC hourly-volatility grid. Cell background is set inline (a
   per-cell computed value, not a fixed rule -- see market_views.py's
   `_intraday_cell_display`); this only lays the text out inside it. */
.intraday-heatmap td { text-align: center; font-family: var(--font-mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
.intraday-heatmap td .muted { font-size: var(--fs-xs); }

/* A small inline caveat marker beside a figure -- the options page's "thin"
   mark on an expiry whose open interest sits across very few strikes. No new
   colour: it borrows the same --slate/--paper-deep pair every other muted
   marker on this product already uses, so both themes come free. Deliberately
   NOT a --bear/--bull tint: a thin reading is a statement about sample size,
   never about direction. */
.flag { display: inline-block; font-family: var(--font-mono); font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--slate);
  background: var(--paper-deep); border: 1px solid var(--rule); border-radius: var(--r);
  padding: 0 0.35em; margin-left: 0.4em; vertical-align: baseline; }

.coin-cell { display: flex; align-items: center; gap: var(--s2); }
.coin-cell img { width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0; background: var(--paper-deep); }
.coin-cell .sym { color: var(--slate); font-family: var(--font-mono); font-size: var(--fs-xs); text-transform: uppercase; }

/* Directional colour on a percentage. A number went up or it went down --
   that is a fact about the past, and this is the only thing on the page
   that says it. Nothing here characterises the move. */
.up { color: var(--bull); }
.down { color: var(--bear); }
.flat { color: var(--slate); }

/* Markets-table sparkline (Task 15). `.up`/`.down`/`.flat` above are reused
   directly for the stroke colour -- `currentColor` in the SVG picks up
   whichever of the three is set on this element, so no new colour token is
   needed here. `display:block` keeps the SVG from taking the small extra
   baseline gap an inline default would add inside the table cell. */
.sparkline-cell { width: 6.5rem; }
.sparkline { display: block; width: 100%; height: 1.75rem; }

/* The coin page's multi-timeframe change strip (Task 15) -- secondary to
   the four primary .stat boxes above it, so a quiet pill row rather than a
   second .stat-grid, matching .filter-bar's own visual weight exactly. */
.change-strip { display: flex; flex-wrap: wrap; gap: var(--s2); margin: 0 0 var(--s4); }
.change-chip {
  display: flex; align-items: baseline; gap: var(--s1);
  background: var(--surface); border: 1px solid var(--rule); border-radius: 999px;
  padding: 0.25rem 0.8rem; font-size: var(--fs-sm);
}
.change-chip .label { color: var(--slate); font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.04em; }
.change-chip .value { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

.chart-frame { position: relative; background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-lg); padding: var(--s3); }
.chart-canvas { width: 100%; height: 420px; }
.chart-meta { display: flex; gap: var(--s3); align-items: baseline; flex-wrap: wrap;
  font-size: var(--fs-xs); color: var(--slate); margin-top: var(--s2); }

/* Which line is which venue. Built by premium-chart.js, because the line
   colours are assigned there and a legend whose swatches were coloured in a
   second place would drift from them. Names only, in the order the server
   sent -- alphabetical, never by figure. */
.chart-legend { display: flex; gap: var(--s4); flex-wrap: wrap; list-style: none;
  margin: var(--s3) 0 0; padding: 0; font-size: var(--fs-xs); color: var(--slate);
  text-transform: uppercase; letter-spacing: 0.06em; }
.chart-legend li { display: inline-flex; align-items: center; gap: var(--s2); }
.chart-legend .swatch { display: inline-block; width: 12px; height: 2px; border-radius: 1px; }
.live-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--slate); margin-right: 0.35rem; vertical-align: baseline; }
.live-on .live-dot { background: var(--bull); }
.live-off .live-dot { background: var(--attention); }

/* A cell whose figure was just replaced by a live tick. Fades from a tint
   back to nothing, so a change is noticeable without anything moving. */
.ticked { animation: tick-fade 1.2s ease-out; }
@keyframes tick-fade { from { background: var(--verified-tint); } to { background: transparent; } }
@media (prefers-reduced-motion: reduce) { .ticked { animation: none; } }

.vendor-text { font-size: var(--fs-sm); }
.vendor-text p { margin: 0 0 var(--s3); }

/* ---- news citations (phase2/crypto-world, Task 3) -----------------------
   Added on the merge, after the "carried verbatim" block above was written --
   Task 3 postdates it. Same rule as everything else here: every colour and
   every face comes from tokens.css, nothing is written as a literal.

   A citation is a headline, a snippet and an attribution line. There is no
   styling for a sentiment badge, a score, or a promoted-story treatment,
   because there is nothing of the kind to style -- see
   crypto/templates/crypto/_news_citation.html. */
.filter-bar { display: flex; flex-wrap: wrap; gap: var(--s2); font-size: var(--fs-sm); }
.filter-bar a {
  text-decoration: none; color: var(--slate); background: var(--surface);
  border: 1px solid var(--rule); border-radius: 999px; padding: 0.25rem 0.8rem;
}
.filter-bar a:hover { color: var(--ink); border-color: var(--rule-strong); }
.filter-bar a.active { background: var(--verified); border-color: var(--verified); color: var(--on-accent); font-weight: 600; }

.citation-list { list-style: none; padding: 0; margin: 0; }
.citation {
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-lg);
  padding: var(--s3) var(--s4); margin-bottom: var(--s3);
}
.citation-headline { font-family: var(--font-display); font-weight: 700; font-size: 1.02rem; line-height: 1.3; color: var(--ink); text-decoration: none; }
.citation-headline:hover { color: var(--verified-ink); text-decoration: underline; }
.citation-snippet { margin: var(--s2) 0 0; font-size: var(--fs-sm); color: var(--ink); }
.citation-meta { margin: var(--s2) 0 0; font-size: var(--fs-xs); color: var(--slate); }
.citation-meta a { color: var(--verified-ink); }

/* --- Indicator controls and readings (docs/charting-port-phase-a.md) ------
   The toggle list is script-only and ships `hidden`; chart-indicators.js is
   the one file that reveals it. The readings table beside it is ordinary
   server-rendered page content and is styled to be readable on its own,
   because it IS the page when JavaScript is off. No literal colour or font
   stack here, per crypto/tests/test_design_system.py. */
.indicator-controls { margin-top: var(--s3); }
.indicator-picker {
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r);
  padding: var(--s2) var(--s3);
}
.indicator-picker > summary {
  cursor: pointer; font-family: var(--font-display); font-weight: 700;
  color: var(--ink); font-size: var(--fs-sm);
}
.indicator-picker > p { margin: var(--s2) 0 0; font-size: var(--fs-xs); }
.indicator-toggle-list {
  list-style: none; padding: 0; margin: var(--s3) 0 0;
  display: grid; gap: var(--s3);
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
}
.indicator-toggle-list > li {
  border-left: 2px solid var(--rule-strong); padding-left: var(--s2);
}
.indicator-toggle {
  display: flex; align-items: flex-start; gap: var(--s2);
  font-size: var(--fs-sm); font-weight: 600; color: var(--ink); cursor: pointer;
}
.indicator-toggle input { margin-top: 0.15rem; flex: none; }
.indicator-note { margin: var(--s1) 0 0; font-size: var(--fs-xs); line-height: 1.45; }
.indicator-readings { margin-top: var(--s4); }
.indicator-readings th[scope="row"] {
  text-align: left; font-weight: 600; vertical-align: top; white-space: nowrap;
}

/* Market-cap treemap (Task 16). Genuinely needs JavaScript to lay out
   (see crypto/static/crypto/js/treemap.js's own docstring for why, unlike
   the sparkline) -- .treemap-wrap ships `hidden` in the markup and the
   script reveals it only once a real layout has rendered, so a reader
   without JavaScript sees the table below and nothing half-drawn here. */
.treemap-wrap { margin: var(--s4) 0; }
.treemap-figure-holder { position: relative; }
/* Reserved by CSS alone, at the same aspect ratio treemap.js computes its
   layout at (width * 0.42 height => ~2.38:1) -- so the box exists, and can
   be MEASURED, before any script runs, and a reader without JavaScript
   sees a quiet placeholder plus the <noscript> explanation rather than a
   layout that jumps or a box reporting zero width to a script that would
   otherwise try to lay out a treemap inside nothing. */
#market-treemap {
  width: 100%; aspect-ratio: 2.38 / 1; background: var(--surface);
  border: 1px solid var(--rule); border-radius: var(--r); overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
#market-treemap noscript { color: var(--slate); font-size: var(--fs-sm); padding: var(--s4); text-align: center; }
.treemap-figure { display: block; width: 100%; height: 100%; }
.treemap-cell { cursor: default; }
.treemap-cell:focus-visible rect { outline: 2px solid var(--ink); outline-offset: -2px; }
.treemap-label-symbol, .treemap-label-change {
  fill: var(--on-accent); pointer-events: none; font-family: var(--font-mono);
}
.treemap-label-symbol { font-size: 0.72rem; font-weight: 700; letter-spacing: 0.02em; }
.treemap-label-change { font-size: 0.68rem; }

.treemap-tooltip {
  position: absolute; z-index: 20; pointer-events: none;
  background: var(--surface); border: 1px solid var(--rule-strong); border-radius: var(--r);
  box-shadow: var(--shadow-2);
  padding: var(--s2) var(--s3); font-size: var(--fs-sm); max-width: 14rem;
}
.treemap-tooltip p { margin: 0; }
.treemap-tooltip p + p { margin-top: 0.15rem; }
.treemap-tooltip-name { font-weight: 700; color: var(--ink); }
.treemap-tooltip-price, .treemap-tooltip-market_cap { color: var(--slate); font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.treemap-tooltip-change_24h, .treemap-tooltip-change_7d { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

.treemap-legend { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s3); margin-top: var(--s3); font-size: var(--fs-xs); color: var(--slate); }
.treemap-legend-scale {
  display: block; width: 9rem; height: 0.6rem; border-radius: 999px;
  background: linear-gradient(to right, var(--bear), var(--neutral), var(--bull));
}
.treemap-legend-labels { display: flex; justify-content: space-between; width: 9rem; font-family: var(--font-mono); }

@media (max-width: 640px) {
  .treemap-wrap { display: none; }
}
