/* recon: ink on ledger paper.
   ---------------------------------------------------------------------------
   The subject is bookkeeping. Two ledgers that should agree and do not, which
   is a problem people were solving with ruled paper and two colours of ink
   long before anyone wrote it down as a diff. So the page borrows from that
   rather than from a terminal: warm paper, printed ink, ruled lines.

   Everything here is deliberately low chroma. Printed ink is not saturated,
   and the moment one of these colours glows against the background the page
   stops reading as a ledger and starts reading as a dashboard.

     accent  a stamped green. Agreement, and the brand.
     bad     oxide red. The colour a correction was written in.
     warn    ochre. The third case people forget: no rule settles it, a
             human has to read it.

   Dark mode is the same ink under a lamp, not an inverted dashboard: warm
   near-black rather than blue-black, and the three inks pulled further down
   in chroma so none of them light up.

   base.css owns the chrome. This file owns the colours and the scenes. */

:root {
  --ground:    #1A1A17;
  --surface:   #232320;
  --surface-2: #2B2B27;
  --line:      #3B3A34;
  --text:      #E2E0D6;
  --dim:       #8C8A7F;

  --accent: #7E9A76;   /* agreement, brand   */
  --warn:   #B39A64;   /* needs a human      */
  --bad:    #BF7068;   /* drift, missing     */

  --glow: rgba(226, 224, 214, 0.035);
}

:root[data-theme="light"] {
  --ground:    #E8E7E0;
  --surface:   #F3F2ED;
  --surface-2: #DEDDD5;
  --line:      #C7C5BA;
  --text:      #24241F;
  --dim:       #6A695E;

  --accent: #46603F;
  --warn:   #7A5F22;
  --bad:    #9B3A32;

  --glow: rgba(36, 36, 31, 0.035);
}

/* ------------------------------------------------------------------ field */

/* What the glass panels have to blur.

   A blur over a flat colour gives back the flat colour, so glass on a page
   with a plain background is an expensive way to draw nothing. The panels need
   something with structure behind them, and on this page the honest something
   is the paper itself: feint ruled lines, plus two soft pools of warmth where
   a lamp would fall.

   The lines are 32px apart, which is roughly the row height of the ledger
   above them. That is deliberate: blurred at 20px they stop reading as lines
   and become a slow vertical rhythm, and the ruling only resolves in the
   margins where nothing covers it.

   `background-attachment: fixed` keeps the field still while the page scrolls
   over it. That is what sells it as paper the panels are lying on rather than
   as a texture stuck to them.

   Painted on `body` rather than a `::before` on purpose. A fixed pseudo-element
   at `z-index: -1` needs the whole ancestor chain to stay free of stacking
   contexts, and the first `opacity` or `transform` added anywhere above it
   swallows the layer silently. A background has no such dependency. */
body {
  background-image:
    radial-gradient(ellipse 60% 45% at 12% -5%, color-mix(in srgb, var(--warn) 13%, transparent), transparent 72%),
    radial-gradient(ellipse 55% 40% at 92% 18%, color-mix(in srgb, var(--accent) 11%, transparent), transparent 72%),
    repeating-linear-gradient(
      to bottom,
      transparent 0 31px,
      color-mix(in srgb, var(--line) 26%, transparent) 31px 32px
    );
  background-attachment: fixed;
}

:root[data-theme="light"] body {
  /* Ink on paper reverses which layer does the work. The ruling is what you
     see first on real ledger paper in daylight, so it comes up, and the pools
     come down to a tint rather than a glow. */
  background-image:
    radial-gradient(ellipse 60% 45% at 12% -5%, color-mix(in srgb, var(--warn) 10%, transparent), transparent 72%),
    radial-gradient(ellipse 55% 40% at 92% 18%, color-mix(in srgb, var(--accent) 9%, transparent), transparent 72%),
    repeating-linear-gradient(
      to bottom,
      transparent 0 31px,
      color-mix(in srgb, var(--line) 34%, transparent) 31px 32px
    );
}

/* ------------------------------------------------------------------- hero */

/* Two rules that do not quite meet, which is the whole project in one mark. */
.gap {
  position: relative;
  height: 3px;
  margin-top: 3.5rem;
  display: grid;
  grid-template-columns: 1fr 2.5rem 1fr;
  align-items: center;
}
.gap span { height: 2px; background: var(--line); }
.gap span:nth-child(2) { background: none; }
.gap::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  transform: translate(-50%, -50%);
}

/* ------------------------------------------------------------------ scene */

/* The scene is the pane. Everything inside it is drawn on the glass rather
   than blurring separately, so the ruling behind bends once and stays legible
   through the rows. */
.scene {
  margin-top: 1.6rem;
  background: var(--glass);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
  box-shadow: inset 0 1px 0 var(--glass-edge), 0 22px 50px -34px var(--shadow);
}

.scene-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.7rem;
  padding: 0.7rem 0.9rem;
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--surface-2) 55%, transparent);
}
.scene-bar .grow { flex: 1 1 auto; }

.tally {
  font-family: var(--mono);
  font-size: 0.76rem;
  letter-spacing: 0.04em;
  color: var(--dim);
}
.tally b {
  color: var(--text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.tally.is-clean b { color: var(--accent); }

/* The toggle is the argument, so it looks like a switch rather than a link. */
.switch {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dim);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}
.switch .track {
  width: 2.3rem;
  height: 1.1rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--ground);
  position: relative;
  transition: border-color 0.2s, background-color 0.2s;
}
.switch .track::after {
  content: "";
  position: absolute;
  top: 1px;
  left: 1px;
  width: 0.85rem;
  height: 0.85rem;
  border-radius: 50%;
  background: var(--dim);
  transition: transform 0.2s, background-color 0.2s;
}
.switch[aria-pressed="true"] { color: var(--accent); }
.switch[aria-pressed="true"] .track { border-color: var(--accent); }
.switch[aria-pressed="true"] .track::after {
  background: var(--accent);
  transform: translateX(1.15rem);
}

/* ------------------------------------------------------------------- rows */

.ledger-head, .row {
  display: grid;
  grid-template-columns: 6.5rem 1fr 2.2rem 1fr 9.5rem;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.9rem;
  font-family: var(--mono);
  font-size: 0.78rem;
}

.ledger-head {
  color: var(--dim);
  font-size: 0.66rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  border-bottom: 1px solid var(--line);
}

.row {
  border-bottom: 1px solid color-mix(in srgb, var(--line) 55%, transparent);
  color: var(--dim);
  /* Rows start invisible and are dealt out one at a time. Opacity rather than
     display so the table height never jumps mid-run. */
  opacity: 0.25;
  transition: opacity 0.35s, background-color 0.35s;
}
.row:last-child { border-bottom: 0; }
.row.is-dealt { opacity: 1; }
.row.is-lit { background: color-mix(in srgb, var(--accent) 7%, transparent); }

.row .key { color: var(--text); font-weight: 700; }
.row .val { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row .link { text-align: center; color: var(--line); transition: color 0.3s; }
.row.is-dealt .link { color: var(--dim); }

/* Field name sits above the value so a narrow column still says what it is. */
.row .field {
  display: block;
  font-size: 0.62rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--dim) 75%, transparent);
}

.verdict {
  justify-self: end;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  font-size: 0.64rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  opacity: 0;
  transform: translateY(3px);
  transition: opacity 0.3s, transform 0.3s, color 0.3s, border-color 0.3s;
}
.row.is-dealt .verdict { opacity: 1; transform: none; }
.verdict::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
}
.verdict[data-kind="match"] { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
.verdict[data-kind="drift"] { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 45%, transparent); }
.verdict[data-kind="missing"] { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 45%, transparent); }
.verdict[data-kind="duplicate"] { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 45%, transparent); }

/* A row whose only problem was formatting. With tolerances on it goes quiet,
   which is the point being made: those rows were never findings. The key has
   to be dimmed by name, because it sets its own colour and would otherwise
   stay as loud as the rows that still matter. */
.row.is-dealt.is-noise { opacity: 0.62; }
.row.is-noise .key { color: var(--dim); font-weight: 400; }

@media (max-width: 720px) {
  .ledger-head { display: none; }
  .row {
    grid-template-columns: 1fr auto;
    row-gap: 0.15rem;
    padding: 0.7rem 0.9rem;
  }
  .row .link { display: none; }
  .row .val { grid-column: 1; }
  .row .verdict { grid-row: 1; grid-column: 2; }
}

/* --------------------------------------------------------------- terminal */

/* Inside a scene the terminal is the panel body, so it drops its own frame
   and sits flush under the control bar. */
.scene .term { border: 0; border-radius: 0; }

/* The one panel that stays nearly solid. A terminal is a screen, not a pane,
   and mono text at 0.76rem is the first thing on the page to lose against a
   field showing through it. It reads as the dark slab set into the glass,
   which is also what a screen in a real desk looks like. */
.term {
  background: color-mix(in srgb, var(--ground) 88%, transparent);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 0.9rem 1rem;
  font-family: var(--mono);
  font-size: 0.76rem;
  line-height: 1.75;
  /* A fixed height, not a max-height. A panel that grows line by line shoves
     the rest of the page down while you are reading it, and the section never
     settles. Fixed, it behaves like a real terminal: the box stays put and
     the text scrolls inside it. */
  height: 14rem;
  overflow: auto;
  scrollbar-gutter: stable;
}

/* macOS hides overlay scrollbars until something moves, so a panel that has
   scrolled looks like a panel that has nothing more in it. Given a visible
   track, the position is readable at a glance. */
.term::-webkit-scrollbar { width: 10px; height: 10px; }
.term::-webkit-scrollbar-track { background: transparent; }
.term::-webkit-scrollbar-thumb {
  background: var(--line);
  border: 3px solid var(--ground);
  border-radius: 999px;
}
.term::-webkit-scrollbar-thumb:hover { background: var(--dim); }
.term { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
.term .prompt { color: var(--accent); }
.term .out { color: var(--dim); white-space: pre; }
.term .code-line { color: var(--text); }
.term p { margin: 0; white-space: pre; }
.term .caret {
  display: inline-block;
  width: 0.5rem;
  height: 1em;
  vertical-align: -0.15em;
  background: var(--accent);
  animation: blink 1.05s steps(1, end) infinite;
}
@keyframes blink { 50% { opacity: 0; } }

.exit { color: var(--text); font-weight: 700; }
.exit[data-code="0"] { color: var(--accent); }
.exit[data-code="1"] { color: var(--bad); }
.exit[data-code="2"] { color: var(--warn); }

/* ------------------------------------------------------------------ cards */

/* Three cards, and three is the point: these are the three things that break
   on the way in. The shared 20rem minimum wraps them 2 + 1, which reads as a
   list that ran out rather than as a set. */
.split { grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr)); }

.card h3 {
  margin: 0 0 0.5rem;
  font-family: var(--mono);
  font-size: 0.95rem;
  letter-spacing: -0.01em;
}
.card p { margin: 0; font-size: 0.88rem; color: var(--dim); }

/* The header cell as the file gives it against the header cell as a naive
   reader sees it. Hovering or focusing the card flips it, which is a small
   thing but it is the actual failure, shown rather than described. */
.flip {
  display: grid;
  gap: 0.35rem;
  margin: 0 0 0.9rem;
  font-family: var(--mono);
  font-size: 0.8rem;
}
.flip code {
  display: block;
  padding: 0.4rem 0.55rem;
  background: color-mix(in srgb, var(--ground) 62%, transparent);
  border: 1px solid var(--line);
  border-radius: var(--r);
  color: var(--dim);
}
.flip code.bad { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 40%, transparent); }
.flip code.good { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, transparent); }

@media (prefers-reduced-motion: reduce) {
  /* With motion off nothing is dealt out over time, so everything is already
     in its end state and the argument still reads. */
  .row { opacity: 1; }
  .verdict { opacity: 1; transform: none; }
  .term .caret { animation: none; }
}
