/* ============================================================================
   LORE LAB Steel Test Database - chart layer
   Hand-built dot plots (Index, Compare) and dot scatter (Detail).
   No library. JS sets a --pos (0..100 %, left to right) from the angle; the
   reversal (low degree = right) is baked into the JS mapping, while tick
   labels still read honest descending degrees. Position encodes the value;
   size encodes confidence; fill-state encodes pass/fail. Never blurred.
   ========================================================================= */

/* ---- The dot-plot scaffold (Index + Compare share this) ---------------- */
.plot { width: 100%; }

/* Axis header: the reversed degree ticks. Sticky on mobile so the scale
   stays visible while rows scroll under it (1.6 / 3.5). */
.axis {
  position: relative;
  height: 44px;          /* room for the tick row plus the unit caption below it */
  margin-bottom: var(--s-2);
}
/* Desktop: .prow's label column (--gutter) pushes .track in by 230px, so the
   axis must be inset by the same amount or its ticks (positioned against the
   full-width axis) drift out of alignment with dots (positioned against the
   narrower track). Same var+fallback as .prow keeps the two locked together.
   Mobile collapses .prow to a single column (layout.css), so this is scoped
   to desktop only. */
@media (min-width: 640px) {
  .axis { margin-left: var(--gutter, 230px); }
}
.axis__line {
  position: absolute;
  left: 0; right: 0; top: 17px;
  height: 1px;
  background: var(--mist-gray);
}
.axis__tick {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--ink-faint);
  font-feature-settings: "tnum" 1;
  white-space: nowrap;
}
.axis__tick::after {
  content: "";
  position: absolute;
  left: 50%; top: 16px;
  width: 1px; height: 6px;
  background: var(--mist-gray);
  transform: translateX(-50%);
}
/* Unit caption pinned to the bottom-left, clear of the centered tick labels. */
.axis__unit {
  position: absolute;
  left: 0; bottom: 0;
  font-size: var(--t-xs);
  color: var(--ink-faint);
  font-weight: 500;
  font-family: var(--font-body);
  background: var(--parchment-card);
  padding-right: var(--s-2);
}

/* A single plotted row */
.prow {
  display: grid;
  grid-template-columns: var(--gutter, 230px) 1fr;
  align-items: center;
  min-height: var(--tap);
  border-radius: var(--radius);
  border-left: 3px solid transparent;
  transition: background 120ms ease;
}
.prow + .prow { border-top: 1px solid var(--navy-05); }
.prow__label {
  padding: var(--s-2) var(--s-3) var(--s-2) var(--s-2);
  min-width: 0;
}
.prow__name {
  font-family: var(--font-display);
  font-size: 1.05rem;
  color: var(--lore-navy);
  letter-spacing: 0.01em;
  line-height: 1.1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.prow__meta {
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
  margin-top: 2px;
}
.prow__angle {
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--lore-navy);
  font-feature-settings: "tnum" 1;
}

/* The dot track: the plotting area where a dot is positioned by --pos */
.track {
  position: relative;
  height: var(--tap);
  min-width: 0;
}
/* Drop-lines align with axis ticks (desktop reading aid, 1.7) */
.track__grid {
  position: absolute;
  top: 0; bottom: 0;
  width: 1px;
  background: var(--navy-05);
}

/* A plotted dot. left:var(--pos) places it; size from --d; fill from state. */
.dot {
  position: absolute;
  top: 50%;
  left: var(--pos, 50%);
  width: var(--d, 12px);
  height: var(--d, 12px);
  transform: translate(-50%, -50%);
  border-radius: 50%;
  z-index: 2;
}
/* Invisible >=44px tap/hover halo so small dots stay reachable (acc. checklist) */
.dot::before {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  width: var(--tap); height: var(--tap);
  transform: translate(-50%, -50%);
  border-radius: 50%;
}
.dot--head  { background: var(--lore-navy); }        /* headline feasible dot */
.dot--pass  { background: var(--signal-blue); }      /* raw passing test */
.dot--fail  { background: transparent; border: 1.5px solid var(--lore-navy); }
.dot--heat  { background: transparent; border: 1.5px dashed var(--mist-gray); }
/* n=1 provisional ring on the headline dot (0.6) */
.dot--solo  { box-shadow: 0 0 0 3px var(--parchment-card), 0 0 0 4.5px var(--mist-gray); }
/* Compare series colors (0 to 4) - drawn in the fixed palette order (3.4) */
.dot--s0 { background: var(--lore-navy); }
.dot--s1 { background: var(--signal-blue); }
.dot--s2 { background: var(--lore-olive); }
.dot--s3 { background: var(--field-teal); }
.dot--s4 { background: var(--lore-navy); box-shadow: 0 0 0 2.5px var(--parchment-card), 0 0 0 4px var(--lore-navy); }

/* Heat-damage triangle glyph sits on the dashed ring */
.dot--heat::after {
  content: "";
  position: absolute;
  top: -7px; left: 50%;
  transform: translateX(-50%);
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-bottom: 6px solid var(--ink-faint);
}

/* No-dot row: the right-aligned no-pass tag sits in the track */
.track__nopass {
  position: absolute;
  top: 50%; right: var(--s-2);
  transform: translateY(-50%);
}

/* ---- Index-specific ---------------------------------------------------- */
.prow--link { cursor: pointer; }
.prow--link:hover { background: var(--olive-06); text-decoration: none; }
.prow--link:hover .prow__name { text-decoration: underline; }
.prow.is-selected { border-left-color: var(--lore-olive); background: var(--olive-14); }
.prow--untested { opacity: 0.85; }
.prow--untested .prow__name { color: var(--ink-faint); font-style: italic; }

/* Desktop hover popover (winning test) - adds detail, never gates it (1.7) */
.popover {
  position: absolute;
  z-index: 60;
  min-width: 220px;
  max-width: 280px;
  padding: var(--s-3) var(--s-4);
  background: var(--lore-navy);
  color: var(--parchment);
  border-radius: var(--radius);
  box-shadow: var(--shadow-pop);
  font-size: var(--t-xs);
  line-height: 1.5;
  pointer-events: none;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 120ms ease, transform 120ms ease;
}
.popover.is-on { opacity: 1; transform: translateY(0); }
.popover__name { font-family: var(--font-display); font-size: var(--t-sm); margin-bottom: 4px; }
.popover dt { color: var(--mist-gray); font-weight: 600; }
.popover dl { display: grid; grid-template-columns: auto 1fr; gap: 2px var(--s-3); margin: 0; }

/* ---- Detail scatter ---------------------------------------------------- */
.scatter-wrap { position: relative; padding-top: 26px; }  /* headroom for the feasible tag */
.scatter {
  position: relative;
  width: 100%;
  height: 340px;
  background:
    linear-gradient(var(--navy-05) 1px, transparent 1px) 0 0 / 100% 25%;
  border-left: 1px solid var(--mist-gray);
  border-bottom: 1px solid var(--mist-gray);
}
.scatter__plot { position: absolute; inset: 0; }
/* Y axis: cuts 0..600, true zero (2.4) */
.scatter__ylabel {
  position: absolute;
  left: 0; right: 0;
  transform: translateY(-50%);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  color: var(--ink-faint);
  font-feature-settings: "tnum" 1;
}
.scatter__yval { position: absolute; left: -2px; transform: translate(-100%, -50%); }
/* The PASS LINE at y=600 (2.4) */
.passline {
  position: absolute;
  left: 0; right: 0;
  height: 0;
  border-top: 1.5px solid var(--mist-gray);
}
/* PASS LINE label: sits just under the line at the left, clear of the
   feasible marker (which lives at top, often near the right). */
.passline__tag {
  position: absolute;
  left: 4px; top: 3px;
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--ink-soft);
  background: var(--parchment-card);
  padding: 0 4px;
}
/* The feasible-angle marker: vertical olive line tying headline to raw dots (2.4) */
.feasmark {
  position: absolute;
  top: -22px; bottom: 0;
  width: 0;
  border-left: 1.5px solid var(--lore-olive);
  z-index: 4;
}
.feasmark__tag {
  position: absolute;
  top: -22px; left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  font-size: 0.625rem;
  font-weight: 700;
  color: var(--parchment);
  background: var(--lore-olive);
  border-radius: 3px;
  padding: 1px 5px;
  z-index: 5;
}
/* A scatter dot positioned by --x (angle) and --y (cuts). It is a <button>
   (tappable, keyboard-reachable -- correct, keep it) but the UA button
   styling was never reset: Chrome's default `padding: 1px 6px` plus a 2px
   border cannot fit inside an 11px border-box, so the used width floors at
   16px while the height stays 11px and `border-radius: 50%` draws an
   ellipse, not a circle. Measured live 2026-09-09 on /steels/19: pass dots
   16x11 (ratio 1.46), fail dots 14x11. Reset to zero so the declared 11x11
   is the box Chrome actually paints; the ::before tap target below still
   gives a real touch area, so this does not shrink what is tappable. */
.sdot {
  position: absolute;
  left: var(--x);
  bottom: var(--y);
  width: 11px; height: 11px;
  padding: 0;
  border: 0;
  appearance: none;
  background: none;
  font: inherit;
  transform: translate(-50%, 50%);
  border-radius: 50%;
  z-index: 3;
  cursor: pointer;
}
.sdot::before {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  width: var(--tap); height: var(--tap);
  transform: translate(-50%, -50%);
  border-radius: 50%;
}
.sdot--pass { background: var(--signal-blue); }
.sdot--fail { background: transparent; border: 1.5px solid var(--lore-navy); }
.sdot--heat { background: transparent; border: 1.5px dashed var(--mist-gray); }
.sdot--heat::after {
  content: "";
  position: absolute;
  top: -7px; left: 50%;
  transform: translateX(-50%);
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-bottom: 6px solid var(--ink-faint);
}
.sdot.is-active { box-shadow: 0 0 0 3px var(--parchment-card), 0 0 0 4.5px var(--lore-olive); z-index: 5; }

/* X axis label row under the scatter */
.scatter__xaxis {
  position: relative;
  height: 22px;
  margin-top: var(--s-1);
}
.scatter__xval {
  position: absolute;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  color: var(--ink-faint);
  font-feature-settings: "tnum" 1;
}

/* Desktop scatter tooltip */
.stip {
  position: absolute;
  z-index: 70;
  min-width: 200px;
  max-width: 270px;
  padding: var(--s-3);
  background: var(--lore-navy);
  color: var(--parchment);
  border-radius: var(--radius);
  box-shadow: var(--shadow-pop);
  font-size: var(--t-xs);
  line-height: 1.5;
  pointer-events: none;
  opacity: 0;
  transition: opacity 120ms ease;
}
.stip.is-on { opacity: 1; }
.stip__head { font-family: var(--font-display); font-size: var(--t-sm); margin-bottom: 4px; display: flex; align-items: center; gap: 6px; }
.stip dl { display: grid; grid-template-columns: auto 1fr; gap: 1px var(--s-3); margin: 0; }
.stip dt { color: var(--mist-gray); font-weight: 600; }

/* Mobile tap-card (anchored below the chart on touch - 2.7) */
.tapcard {
  margin-top: var(--s-3);
  padding: var(--s-3) var(--s-4);
  background: var(--parchment-deep);
  border: var(--border);
  border-left: 3px solid var(--lore-olive);
  border-radius: var(--radius);
  font-size: var(--t-sm);
}
.tapcard[hidden] { display: none; }
.tapcard__head { font-family: var(--font-display); font-size: var(--t-md); display: flex; align-items: center; gap: var(--s-2); }
.tapcard dl { display: grid; grid-template-columns: auto 1fr; gap: 2px var(--s-4); margin: var(--s-2) 0 0; }
.tapcard dt { color: var(--ink-soft); font-weight: 600; }

/* Swipe hint for the horizontally-scrollable mobile scatter (2.7) */
.swipe-hint {
  font-size: var(--t-xs);
  color: var(--ink-faint);
  font-style: italic;
  margin-top: var(--s-2);
  display: none;
}

/* ============================================================================
   Compare: brand rows (leaf-2.3, C5). Server-rendered, one .brand-row per
   (steel, company) pair -- NOT the pooled single-dot-per-steel chart above,
   which the Index and Detail pages still use. Scoped entirely under .cmp-*
   and .brand-row so it cannot touch .prow / .dot / .axis rendering elsewhere
   in this shared file (leaf-2.3 brief trap).
   ========================================================================= */
.cmp-picker {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  margin: var(--s-3) 0 var(--s-4);
}
.cmp-picker__label {
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.cmp-card { padding: var(--s-2) var(--s-4) var(--s-4); }

/* Desktop 9-25deg axis, every 2deg, inset to line up with the track below
   (same --gutter var + fallback the shared .axis rule uses, 2026-07-03 fix). */
.cmp-axis {
  position: relative;
  height: 22px;
  margin: var(--s-3) 0 var(--s-1);
}
@media (min-width: 680px) {
  /* Inset by the WHOLE label side of .brand-row, not just column 1. The row is
     `gutter | value | track` with a var(--s-4) column gap, so the track starts
     at gutter + gap + value + gap. Insetting by --gutter alone left every tick
     about 85px left of the dot it labelled (Sivali, 2026-09-08). --cmp-value is
     a FIXED width on purpose: each .brand-row is its own grid, so an `auto`
     value column sized to its own text and gave every row a different track
     origin, which broke row-to-row comparison as well as the axis. */
  .cmp-axis {
    margin-left: calc(var(--gutter, 230px) + var(--cmp-value, 132px) + (2 * var(--s-4)));
  }
}
.cmp-axis__tick {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--ink-faint);
  font-feature-settings: "tnum" 1;
  white-space: nowrap;
}
/* Phone: the full tick row is too tight under 680px; a 3-point legend
   (9 / 17 / 25) replaces it, per the approved phone canvas (ph_cmp). */
.cmp-axis-mobile {
  display: none;
  justify-content: space-between;
  font-size: var(--t-xs);
  color: var(--ink-faint);
  margin: var(--s-3) 0 var(--s-1);
}
@media (max-width: 679px) {
  .cmp-axis { display: none; }
  .cmp-axis-mobile { display: flex; }
}

.cmp-block { padding: var(--s-3) 0; }
.cmp-block + .cmp-block { border-top: 1px solid var(--navy-05); }
.cmp-block__head {
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
  padding: 0 0 var(--s-1);
}
.cmp-block__steel {
  font-family: var(--font-display);
  font-size: 1.05rem;
  color: var(--lore-navy);
}
.cmp-block__count { font-size: var(--t-xs); color: var(--ink-muted); }
.cmp-block__empty { font-size: var(--t-sm); color: var(--ink-faint); font-style: italic; padding: var(--s-1) 0; }

/* One brand line: name | value | dot-on-a-line track. Desktop: 3 columns
   side by side (matches gen_final.py cmp_block). Phone: name+value share a
   line, the track (with its dot) is the "short bar" underneath it
   (matches gen_phone.py ph_cmp). */
.brand-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 2px var(--s-3);
  padding: var(--s-1) 0;
  min-height: var(--tap);
}
.brand-row__name {
  font-size: var(--t-sm);
  color: var(--ink-body);
  grid-column: 1;
}
.brand-row__value {
  grid-column: 2;
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
  white-space: nowrap;
}
.brand-row__angle { font-size: var(--t-sm); font-weight: 700; color: var(--ink-strong, var(--lore-navy)); }
.brand-row__fail { font-size: var(--t-sm); font-weight: 700; color: var(--danger); }
.brand-row__n { font-size: var(--t-xs); color: var(--ink-muted); }
.brand-row__track {
  grid-column: 1 / -1;
  position: relative;
  height: 20px;
}
.brand-row__rule {
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 1px;
  background: var(--mist-gray);
}
.brand-row__bar {
  position: absolute;
  left: 0; top: 50%;
  height: 2px;
  transform: translateY(-50%);
}
.brand-row__bar--pass { background: var(--success); }
.brand-row__bar--fail { background: var(--mist-gray); }
.brand-row__dot {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
}
.brand-row__dot--sm { width: var(--dot-sm); height: var(--dot-sm); }
.brand-row__dot--md { width: var(--dot-md); height: var(--dot-md); }
.brand-row__dot--lg { width: var(--dot-lg); height: var(--dot-lg); }
.brand-row__dot--pass { background: var(--success); }
.brand-row__dot--fail { background: var(--mist-gray); }

/* ---- Confidence Halo (Concept 1, leaf B2, 2026-09-08) --------------------
   The dot keeps its series/pass-fail fill; a ring drawn on a ::before pseudo
   (brand-row__dot has no ::before of its own today, so this never competes
   with the dot's fill or border) carries the same n=1 / n=2-4 / n>=5 tier the
   template already computes for dot SIZE (`dsize`, mirroring `dotSize()` in
   app.js). n>=5 (the `lg` dot) gets no ring: 5+ tests need no visual caveat.
   `--lore-navy` chosen over `--mist-gray` for the ring: measured against
   --parchment-card, mist-gray is only 1.4:1 (the existing gridline/axis use
   of it is decorative context, not an instructional mark, and is out of this
   leaf's scope regardless) while navy clears the 3:1 non-text floor at both
   the solo ring (13.4:1, opaque) and the thin ring (4.07:1 at 55% opacity,
   hand-verified >=3:1 with margin; 40% as sketched in the concept doc
   measured 2.37:1 and was rejected). */
.brand-row__dot--solo::before,
.brand-row__dot--thin::before {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  border-radius: 50%;
  transform: translate(-50%, -50%);
}
.brand-row__dot--solo::before {
  width: calc(100% + 14px);
  height: calc(100% + 14px);
  border: 2px dashed var(--lore-navy);
}
.brand-row__dot--thin::before {
  width: calc(100% + 8px);
  height: calc(100% + 8px);
  border: 1.5px solid var(--lore-navy);
  opacity: 0.55;
}

/* n=1 chip riding the track beside the dot, not only in the value column
   (Concept 1's stated requirement). Reuses the dormant `.nchip--solo` pill
   (mist-gray fill, navy text, ~9.5:1) already sitting unused in app.css --
   this is the "switch it on" part of the brief. Positioned by the same
   dot_pct the dot itself uses, then flipped to whichever side of the dot has
   more track: a dot near the axis's right edge (dot_pct high, near 98%
   clamp) gets its chip pulled left, back onto the track, never off the
   card's edge, and the mirror holds at the left/2% clamp. aria-hidden: the
   same "n=1" is already read accessibly from .brand-row__n in the value
   column (web-interaction.md 1.8, a restated pill is noise for anyone not
   already looking at the track). */
.brand-row__nchip--track {
  position: absolute;
  top: 50%;
  z-index: 3;
}
.brand-row__nchip--track.brand-row__nchip--left  { transform: translate(calc(-100% - 10px), -50%); }
.brand-row__nchip--track.brand-row__nchip--right { transform: translate(10px, -50%); }

@media (min-width: 680px) {
  .brand-row {
    grid-template-columns: var(--gutter, 230px) var(--cmp-value, 132px) 1fr;
    gap: 2px var(--s-4);
  }
  .brand-row__name { grid-column: 1; }
  .brand-row__value { grid-column: 2; justify-self: end; }
  .brand-row__track { grid-column: 3; }
}

.cmp-method { margin-top: var(--s-3); }

/* ---- Pass with no recorded angle (2026-09-08) --------------------------
   Two real rows (Pro-Tech/MAGNACUT, IYK/SK5) passed 600 cuts with no
   micro-bevel recorded. They used to render as failures. They read as
   passes now, and they take NO position on the angle axis, because there
   is no measurement to place. Styled as a quieter pass, never as a fail:
   the point is "we know it held, we did not record at what". */
.brand-row__angle--noangle {
  color: var(--success);
}
.brand-row__noangle-note {
  font-size: var(--t-xs);
  color: var(--ink-muted);
  white-space: nowrap;
}
.brand-row__noangle-track {
  position: absolute;
  left: 0; top: 50%;
  transform: translateY(-50%);
  font-size: var(--t-xs);
  font-style: italic;
  color: var(--ink-muted);
  white-space: nowrap;
}
