/* Size axis (docs/AXIS.md) - the zoom continuum. Editorial grammar: space and
   hairlines carry structure, type carries hierarchy, and the semantic reader
   violet marks the user's own line. Logical properties only (RTL: ar, ur).
   Everything animated per frame is transform, opacity or visibility, driven
   from JS; this file sets geometry once and never transitions what the loop
   already moves.

   Own tokens derive from tokens.css. The bar tone is NOT --border-strong on
   purpose: bars carry data, so they need >= 3:1 against the ground
   (rgba(22,24,29,.55) on white ~ 4.0:1; the dark twin ~ 4.1:1 on #0E1014). */
.scale, .scale-strip {
  --scale-spine: 14px;               /* must match SPINE_PX in render.js */
  --scale-gutter: 76px;              /* end-side track reserved for the ladder */
  --scale-bar: rgba(22, 24, 29, 0.55);
  --scale-grid-line: var(--border);
}
.scale {
  /* the continuum owns the screen: full remaining height under header, nav
     and view head. dvh, not vh, so the phone browser chrome is respected. */
  --scale-h: clamp(420px, calc(100dvh - 240px), 1200px);
}
:root[data-theme='dark'] .scale, :root[data-theme='dark'] .scale-strip { --scale-bar: rgba(233, 235, 239, 0.55); }
/* first paint for a dark-OS visitor before theme.js stamps data-theme
   (same two-block pattern as tokens.css; keep in step with the line above) */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .scale, :root:not([data-theme]) .scale-strip { --scale-bar: rgba(233, 235, 239, 0.55); }
}

/* ── head: hint and live window readout ─────────────────────────────────── */
.scale-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s-4); flex-wrap: wrap;
  margin-block-end: var(--s-3);
}
.scale-hint { margin: 0; font-size: var(--fs-sm); max-width: 52ch; }
.scale-window {
  font-size: var(--fs-sm); color: var(--text-muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* ── stage: the viewport onto the axis ──────────────────────────────────── */
.scale-stage {
  position: relative;
  height: var(--scale-h);
  overflow: hidden;
  /* the surface IS the gesture: one finger pans, two pinch; the page must
     not scroll from here, which is exactly what touch-action: none says */
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  border-block: 1px solid var(--border);
  cursor: grab;
  overscroll-behavior: contain;
}
.scale-stage:active { cursor: grabbing; }
.scale-stage:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ── the spine: the one continuous line the axis IS ─────────────────────── */
/* every bar grows out of it, so a 6px tick still reads as a mark ON the
   axis instead of a loose dash in space */
.scale-spine {
  position: absolute; inset-block: 0;
  inset-inline-start: calc(var(--scale-spine) - 1px);
  inline-size: 2px;
  background: var(--scale-bar);
  opacity: 0.5;
  pointer-events: none;
}

/* ── decade grid (aria-hidden decoration) ───────────────────────────────── */
.scale-grid { position: absolute; inset: 0; pointer-events: none; }
.scale-decade {
  position: absolute; inset-inline: 0; block-size: 0;
  border-block-start: 1px solid var(--scale-grid-line);
  will-change: transform;
}
/* a firmer tick where the decade crosses the spine: line, tick, caption is
   the whole anatomy of an axis */
.scale-decade::before {
  content: ''; position: absolute;
  inset-inline-start: calc(var(--scale-spine) - 5px);
  inset-block-start: -2px;
  inline-size: 10px; block-size: 3px;
  background: var(--scale-bar); opacity: 0.7;
}
/* the ladder rides its own end-side track (chart convention: y captions on
   the reading edge) so a lone "1" can never look like part of an object name */
.scale-decade-label {
  position: absolute; inset-inline-end: var(--s-2); inset-block-start: 3px;
  font-size: var(--fs-xs); color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.scale-decade-label .unit {
  font-size: 10px; color: var(--text-dim); opacity: 0.75;
  margin-inline-start: 3px;
}

/* ── entries ────────────────────────────────────────────────────────────── */
.scale-list { list-style: none; margin: 0; padding: 0; position: absolute; inset: 0; }
.scale-entry { position: absolute; inset-inline: 0; inset-block-start: 0; block-size: 0; }

/* the bar: linear size truth. 2px of ink, no fill blocks, no rounded ends
   (scaleX would distort them). Its length is the statement. */
.scale-bar {
  position: absolute;
  inset-inline-start: var(--scale-spine);
  inline-size: calc(100% - var(--scale-spine) - 16px); /* 16 = barW end pad in render.js */
  block-size: 2px;
  background: var(--scale-bar);
  transform-origin: 0 50%;
  will-change: transform, opacity;
  z-index: 1;
}
[dir='rtl'] .scale-bar { transform-origin: 100% 50%; }

/* The object: a real button whose text is its accessible name. It paints on
   the page ground so foreign bars never strike through its letters; that is
   paint protection, not a card. Its box is its tap target, so it never goes
   below 24px (28 for touch); block-size and line-height per prominence MUST
   stay in step with BOX in render.js.
   Hidden state = visibility, decided per frame in render.js: an unplaced
   label is not painted, not hit-testable, not tabbable, not in the
   accessibility tree. It appears with a short wipe (clip-path, opaque at
   every instant, so bars can never shine through the letters) and leaves
   INSTANTLY - a fading label could overlap its replacement, a vanished one
   cannot. */
.scale-obj {
  position: absolute;
  inset-inline-start: var(--scale-spine);
  display: inline-flex; align-items: baseline; gap: 6px;
  max-inline-size: calc(100% - var(--scale-spine) - var(--scale-gutter));
  border: 0; margin: 0; padding: 0; padding-inline-end: 6px;
  block-size: 24px; line-height: 24px;
  background: var(--bg);
  font: inherit; font-size: var(--fs-sm);
  color: var(--text); text-align: start; white-space: nowrap;
  cursor: pointer; will-change: transform;
  z-index: 2;
  clip-path: inset(-4px -8px -4px -4px);
  transition: clip-path 140ms ease-out;
}
.scale-obj.is-hid {
  visibility: hidden;
  clip-path: inset(-4px 101% -4px -4px);
  transition: none;
}
[dir='rtl'] .scale-obj.is-hid { clip-path: inset(-4px -4px -4px 101%); }
/* keyboard focus must never sit inside a clip or an invisible outline */
.scale-obj:focus-visible { clip-path: none; outline: 2px solid var(--accent); outline-offset: 1px; }
.scale-obj:hover .scale-name { text-decoration: underline; }
.scale-obj .num { font-variant-numeric: tabular-nums; }
.scale-obj .unit { font-size: var(--fs-xs); color: var(--text-dim); }
.scale-name { overflow: hidden; text-overflow: ellipsis; }

/* prominence: type and box grow with how far a thing is meant to carry */
.scale-entry.p3 .scale-obj { block-size: 26px; line-height: 26px; }
.scale-entry.p4 .scale-obj { block-size: 28px; line-height: 28px; font-weight: 600; }
.scale-entry.p5 .scale-obj { block-size: 30px; line-height: 30px; font-size: var(--fs-md); font-weight: 700; }
.scale-entry.p2 .scale-obj, .scale-entry.p1 .scale-obj {
  font-size: var(--fs-xs); color: var(--text-muted);
}
/* touch: every box one step taller (+4, mirrored in render.js BOX/COARSE) */
@media (pointer: coarse) {
  .scale-obj { block-size: 28px; line-height: 28px; }
  .scale-entry.p3 .scale-obj { block-size: 30px; line-height: 30px; }
  .scale-entry.p4 .scale-obj { block-size: 32px; line-height: 32px; }
  .scale-entry.p5 .scale-obj { block-size: 34px; line-height: 34px; }
}

/* The reader's own line: violet, a stronger bar and a leading dot. */
.scale-mineline .scale-bar { background: var(--you); block-size: 3px; }
.scale-mineline .scale-obj { color: var(--you); font-weight: 700; }
.scale-mineline .scale-obj::before {
  content: ''; align-self: center; flex: none;
  inline-size: 8px; block-size: 8px; border-radius: 50%;
  background: var(--you);
}

/* ── return affordance: quiet, edge-pinned, only when the mark is gone ──── */
.scale-return {
  position: absolute; inset-inline-end: var(--s-3); z-index: 5;
  display: inline-flex; align-items: center; gap: 6px;
}
.scale-return[data-dir='up'] { inset-block-start: var(--s-3); }
.scale-return[data-dir='down'] { inset-block-end: var(--s-3); }

/* ── zoom tools: keyboardless mice and touch both get explicit zoom ─────── */
.scale-tools {
  position: absolute; inset-inline-end: var(--s-3); inset-block-end: var(--s-3);
  display: flex; flex-direction: column; gap: var(--s-1); z-index: 5;
}
.scale-tool {
  min-inline-size: 40px; min-block-size: 40px;
  justify-content: center; font-size: var(--fs-md);
  background: var(--bg);
}

/* empty axis: an honest note, not a spinner. The [hidden] rule is load-bearing:
   app.css gives .empty a display of its own, which would otherwise override
   the UA's [hidden] { display: none } and show this inside a full axis. */
.scale-stage .empty {
  position: absolute; inset-inline: var(--s-6); inset-block-start: 40%;
  margin: 0; text-align: center; color: var(--text-muted);
}
.scale-stage .empty[hidden] { display: none; }

/* detail dialog bits (rendered inside the shared .modal) */
.scale-detail-val { margin-block: 0 var(--s-2); }
.scale-detail-val .num-xl { font-size: var(--fs-xl); font-weight: 700; }
.scale-detail-kind { font-size: var(--fs-sm); }
.scale-detail-src { font-size: var(--fs-sm); }
.scale-detail-src a { color: inherit; }

/* ── the strip: the same axis as one horizontal line (strip.js) ─────────── */
/* the host view places it; everything here is self-contained and one line
   high: marks above the line, the ends and the honest count below it */
.scale-strip-track { position: relative; block-size: 46px; }
.scale-strip-track::before {
  content: ''; position: absolute; inset-inline: 0; inset-block-end: 8px;
  border-block-start: 1px solid var(--scale-bar);
}
.scale-strip-mark {
  position: absolute; inset-block-end: 15px;
  display: inline-flex; align-items: center;
  min-block-size: 26px; padding-inline: 4px;
  border: 0; margin: 0; background: var(--bg);
  font: inherit; font-size: var(--fs-xs); color: var(--text);
  white-space: nowrap; cursor: pointer;
}
.scale-strip-mark.p5 { font-weight: 600; }
.scale-strip-mark.is-hid { visibility: hidden; }
/* a strip landmark is a signpost, not a caption: long names ellipsize so a
   phone still fits a handful of landmarks instead of one full sentence */
.scale-strip-name { max-inline-size: 16ch; overflow: hidden; text-overflow: ellipsis; }
.scale-strip-mark:hover .scale-strip-name { text-decoration: underline; }
.scale-strip-mark:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* the dot marks the TRUE spot on the line, even when the label box had to
   slide inward at a container edge (--dot-x from strip.js) */
.scale-strip-dot {
  position: absolute; inset-block-start: calc(100% + 1px);
  left: calc(var(--dot-x, 50%) - 3px);
  inline-size: 7px; block-size: 7px; border-radius: 50%;
  background: var(--scale-bar);
}
.scale-strip-mine { color: var(--accent-text); font-weight: 700; }
.scale-strip-mine .scale-strip-dot { background: var(--accent); inline-size: 9px; block-size: 9px; inset-block-start: 100%; left: calc(var(--dot-x, 50%) - 4px); }
.scale-strip-meta {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-3); margin-block-start: 2px;
  font-size: var(--fs-xs); color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.scale-strip-count { min-block-size: 24px; font-size: var(--fs-xs); }
.scale-strip-count:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* narrow screens: the hint would push the readout around; stack them */
@media (max-width: 620px) {
  .scale { --scale-h: clamp(380px, calc(100dvh - 310px), 900px); --scale-gutter: 64px; }
  .scale-hint { font-size: var(--fs-xs); }
}

/* Reduced motion: the JS side swaps every camera ride for a jump and every
   bar fade for an instant set on the same media query; the only CSS motion
   is the label wipe-in, and it stops here. The picture without any of it is
   complete: same labels, same places, same controls. */
@media (prefers-reduced-motion: reduce) {
  .scale-obj { transition: none; }
}
