/* chromafish blog — page + index layout.
   Layout follows the design system's Pages rule: print sheet on a dot-grid
   desk, one masthead index line with a 2px rule beneath, one column, a 1080px
   measure, sections introduced by a numbered rule, and a colophon at the
   bottom edge. All values resolve to design-system tokens (build/design/).
   Colour comes from the tone's roles only, never a hue ramp, so the blog
   follows whichever tone styles.css imports. */

/* The iris tone is Jev's: the app card on the index, the judgment figure and
   the Jev column of the race all wear it (data-tone="iris"). */
@import url("tokens/tones/iris.css");

/* The inverted bars (api call, race header) need an accent that reads on
   --surface-inverse. No role covers it, so it is derived from the tone's
   live accent: mostly the tint, pulled a step toward the line. Declared on
   [data-tone] too, so a toned figure mixes its own tone's accent. */
:root,
[data-tone] {
  --accent-on-inverse: color-mix(in oklab, var(--accent-live-tint) 70%, var(--accent-live-line));
}

.desk {
  min-height: 100vh;
  background-color: var(--surface-sunken);
  background-image: var(--grid-dot);
  background-size: var(--grid-dot-size);
  padding: 0 16px;
}

.sheet {
  max-width: 1180px;
  margin: 0 auto;
  min-height: 100vh;
  background: var(--surface-page);
  border-left: 1px solid var(--border-subtle);
  border-right: 1px solid var(--border-subtle);
  padding: 0 32px;
}

@media (min-width: 1440px) {
  .sheet {
    max-width: 1280px;
    padding: 0 40px;
  }
}

@media (min-width: 1920px) {
  .sheet {
    max-width: 1360px;
  }
  :root {
    --measure: 72ch;
  }
}

.mast {
  display: flex;
  align-items: baseline;
  gap: 20px;
  padding: 24px 0 12px;
  border-bottom: 2px solid var(--ink-0);
}

.wordmark {
  font: var(--fw-medium) 24px/1 var(--font-mono);
  letter-spacing: -0.02em;
  color: var(--ink-0);
  border-bottom: none;
}

.mast h1 {
  margin: 0;
  font: inherit;
}

.mast-org {
  font: var(--type-label);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--text-faint);
}

.mast-nav {
  margin-left: auto;
  display: flex;
  gap: 20px;
}

.mast-nav a {
  font: var(--type-body);
  color: var(--text-muted);
  border-bottom: none;
}

.mast-nav a:hover {
  color: var(--text-strong);
}

/* --- about --- */

.about {
  padding: 28px 0 22px;
  border-bottom: var(--rule);
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.about-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px 24px;
  flex-wrap: wrap;
}

.about-links {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 8px;
  min-width: 0;
  font: var(--type-meta);
  color: var(--text-faint);
}

.about-links a {
  color: var(--text-muted);
  border-bottom: none;
  font: var(--type-meta);
  min-width: 0;
  overflow-wrap: anywhere;
}

.about-links a:hover {
  color: var(--text-link);
  border-bottom: 1px solid var(--text-link);
}

.about-sep {
  color: var(--text-faint);
}

.about-text {
  margin: 0;
  max-width: 78ch;
  font: var(--type-body);
  color: var(--text-body);
  line-height: var(--lh-normal);
}

.about-text a {
  color: var(--text-body);
  border-bottom: 1px solid color-mix(in oklab, var(--text-muted) 40%, transparent);
}

.about-text a:hover {
  color: var(--text-link);
  border-bottom-color: var(--text-link);
}

.about-em {
  font-weight: var(--fw-medium);
  color: var(--text-strong);
}

/* --- post page --- */

.post-head {
  padding-top: 36px;
  padding-bottom: 20px;
}

.post-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  margin: 0 0 10px;
  font: var(--type-meta);
  color: var(--text-muted);
}

.post-head h1 {
  font: var(--type-h1);
  letter-spacing: var(--ls-heading);
  max-width: 24ch;
}

.post-body {
  max-width: var(--measure);
  font: var(--type-editorial);
  counter-reset: sec;
  padding-bottom: 8px;
}

.post-body p {
  margin: 0 0 var(--sp-5);
  hyphens: auto;
  /* Safari before 17.4 only knows the prefixed pair. */
  -webkit-hyphenate-limit-before: 2;
  -webkit-hyphenate-limit-after: 2;
  hyphenate-limit-chars: 5 2 2;
  text-wrap: pretty;
}

/* Justify only once a line holds enough words to absorb the slack. Below the
   sheet's 720px breakpoint a line is ~30 characters and every justified line
   opens rivers, so prose stays ragged-right there. */
@media (min-width: 721px) {
  .post-body p {
    text-align: justify;
  }
}

.post-body strong {
  font-weight: var(--fw-semibold);
  color: var(--text-strong);
}

.post-body em {
  font-style: italic;
}

.post-body code {
  font: var(--type-code);
  font-size: 0.92em;
  background: var(--surface-sunken);
  padding: 1px 5px;
}

.post-body a {
  color: var(--text-link);
}

.post-body a:hover {
  color: var(--text-link-hover);
}

.post-body ol,
.post-body ul {
  margin: 0 0 var(--sp-6);
  padding-left: 24px;
}

.post-body li {
  margin-bottom: var(--sp-2);
}

.post-body .section {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: var(--sp-9) 0 var(--sp-6);
  padding-bottom: 8px;
  border-bottom: var(--rule);
  font: var(--type-label);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--accent-live-line);
}

.post-body .section::before {
  counter-increment: sec;
  content: counter(sec, decimal-leading-zero);
  color: var(--text-faint);
}

.post-body blockquote {
  margin: 0 0 var(--sp-6);
  padding: 12px 16px;
  background: var(--surface-selected);
  border-left: 2px solid var(--border-accent);
}

.post-body blockquote p {
  margin: 0;
  color: var(--text-body);
}

.post-body .literal-block {
  margin: 0 0 var(--sp-6);
  padding: 14px 16px;
  background: var(--surface-sunken);
  border: 1px solid var(--border-subtle);
  font: var(--type-code);
  white-space: pre-wrap;
  overflow-x: auto;
}

.post-body hr.rule {
  height: 0;
  margin: var(--sp-8) auto var(--sp-8);
  border: 0;
  border-top: var(--rule);
  width: 40%;
}

.post-body .note {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin: 0 0 var(--sp-6);
  padding: 10px 16px;
  background: var(--status-warn-bg);
  border-left: 2px solid var(--status-warn-line);
}

.post-body .note .cf-label {
  color: var(--status-warn-fg);
}

.post-body .note p {
  margin: 0;
  font: var(--type-body);
}

/* --- index --- */

.list-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 28px 0 8px;
  border-bottom: var(--rule);
}

.list-count {
  font: var(--type-meta);
  color: var(--text-faint);
}

.rows {
  display: flex;
  flex-direction: column;
}

.row {
  display: grid;
  grid-template-columns: 120px 1fr 56px;
  gap: 16px;
  align-items: baseline;
  padding: 14px 12px;
  border-bottom: 1px solid var(--border-subtle);
  border-bottom-color: var(--border-subtle);
  color: inherit;
  transition: background var(--dur-instant) var(--ease-mech);
}

.row:hover {
  background: var(--surface-hover);
}

.row-side {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}

.row-date {
  font: var(--type-meta);
  color: var(--text-muted);
}

.row-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.row-title {
  font: var(--type-h4);
  letter-spacing: 0;
  color: var(--text-strong);
}

.row-desc {
  font: var(--type-body);
  color: var(--text-muted);
}

.row-meta {
  font: var(--type-meta);
  color: var(--text-faint);
  text-align: right;
}

/* --- apps ---
   Small side projects, each in a card dressed in the app's own tone
   (data-tone on the card), so the index hints at what opens. */

.app-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 340px));
  gap: var(--gutter);
  padding-top: var(--sp-7);
}

.app-card {
  display: flex;
  flex-direction: column;
  background: var(--surface-panel);
  color: var(--text-body);
  border: var(--border-strong);
  transition: box-shadow var(--dur-fast) var(--ease-mech);
}

.app-card:hover {
  color: var(--text-body);
  border-bottom-color: var(--border-strongc);
  box-shadow: var(--shadow-float);
}

.app-card-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-5);
  border-bottom: var(--bw-thick) solid var(--border-strongc);
}

.app-card-name {
  font: var(--type-body);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-heading);
  color: var(--text-strong);
}

.app-card-tag {
  padding: 0 var(--sp-3);
  border: var(--border-hair);
  font: var(--type-label);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

.app-card-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-5);
  background-image: var(--grid-dot);
  background-size: var(--grid-dot-size);
}

.app-card-verdict {
  font: var(--type-h3);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-heading);
  color: var(--accent-live);
}

.app-card-desc {
  font: var(--fw-regular) var(--fs-meta)/var(--lh-normal) var(--font-text);
  color: var(--text-muted);
}

.app-card-go {
  margin-top: var(--sp-2);
  font: var(--type-label);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--text-link);
}

.app-card:hover .app-card-go {
  color: var(--text-link-hover);
}

/* --- colophon --- */

.colophon {
  margin-top: 72px;
  border-top: 2px solid var(--ink-0);
  padding: 14px 0 40px;
}

.colophon-line {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 4px 24px;
  margin: 0;
  font: var(--type-meta);
  color: var(--text-muted);
}

.colophon-links {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  min-width: 0;
}

.colophon-links a {
  color: var(--text-muted);
  border-bottom: none;
  min-width: 0;
  overflow-wrap: anywhere;
}

.colophon-links a:hover {
  color: var(--text-link);
  border-bottom: 1px solid var(--text-link);
}

/* Below ~700px the three profile links no longer fit on one line and stack,
   which would leave a separator dangling at the end of each wrapped line. */
@media (max-width: 700px) {
  .about-sep {
    display: none;
  }
}

@media (max-width: 720px) {
  .sheet {
    padding: 0 16px;
  }

  .about-head {
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
  }

  .colophon-line {
    flex-direction: column;
    align-items: flex-start;
  }

  .row {
    grid-template-columns: 1fr;
    gap: 4px;
  }

  .row-meta {
    text-align: left;
  }
}
/* --- api exchange ---
   A Jev call shown as what it is: an HTTP request and the response that
   comes back. The JSON stays literal and in front; the chrome only adds
   what a client would show anyway — method, URL, status, timing, cost.
   The second call repeats only the field that changed. */

.post-body .api {
  /* Aligned to the text column. An earlier version bled wider than the
     measure; against justified prose the offset just read as broken. */
  margin: 0 0 var(--sp-8);
  padding: 0;
  max-width: 100%;
  overflow: hidden;
  background: var(--surface-panel);
  border: 1px solid var(--border-default);
}

.api-bar {
  display: flex;
  align-items: baseline;
  gap: var(--sp-5);
  flex-wrap: wrap;
  padding: var(--sp-5) var(--pad-card);
  background: var(--surface-inverse);
}

.api-method {
  font: var(--type-label);
  letter-spacing: var(--ls-caps);
  color: var(--accent-on-inverse);
}

.api-url {
  font: var(--type-data);
  color: var(--text-inverse);
  min-width: 0;
  overflow-wrap: anywhere;
}

.api-model {
  margin-left: auto;
  font: var(--type-meta);
  color: var(--paper-4);
}

.api-call + .api-call {
  border-top: 1px solid var(--border-default);
}

.api-part {
  display: flex;
  align-items: baseline;
  gap: var(--sp-5);
  flex-wrap: wrap;
  padding: var(--sp-5) var(--pad-card) var(--sp-3);
}

.api .cf-label {
  font: var(--type-label);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--text-faint);
}

.api-note,
.api-caption {
  font: var(--fw-regular) var(--fs-meta)/var(--lh-normal) var(--font-text);
  color: var(--text-muted);
}

.api-caption {
  margin-left: auto;
}

.post-body .api-json {
  margin: 0;
  padding: var(--sp-2) 0 var(--sp-6);
  font: var(--type-code);
  font-size: 15px;
  line-height: 1.5;
  /* These requests carry prose in their values, so lines wrap. Each line
     carries its own indent as padding, so a wrapped value continues at its
     own depth instead of at column zero. */
  color: var(--text-body);
}

.j-line {
  display: block;
  padding: 0 var(--pad-card) 0 calc(var(--pad-card) - 2px + var(--ind, 0ch));
  white-space: pre-wrap;
  border-left: 2px solid transparent;
}

.j-line.is-mark {
  background: var(--surface-sunken);
  border-left-color: var(--text-muted);
}

.j-key {
  color: var(--text-strong);
}

.j-str {
  color: var(--accent-live);
}

.j-num {
  color: var(--status-warn-fg);
}

.j-lit {
  color: var(--accent-select);
}

.j-punc {
  color: var(--text-faint);
}

.j-answer {
  font-size: 1.35em;
  font-weight: var(--fw-medium);
  padding: 0 var(--sp-2);
  background: var(--status-danger-bg);
  color: var(--status-danger-fg);
}

.is-pass .j-answer {
  background: var(--status-ok-bg);
  color: var(--status-ok-fg);
}

.api-status {
  display: flex;
  align-items: baseline;
  gap: var(--sp-5);
  flex-wrap: wrap;
  padding: var(--sp-4) var(--pad-card);
  background: var(--surface-sunken);
  border-top: var(--rule);
  border-bottom: var(--rule);
  font: var(--type-meta);
  color: var(--text-muted);
}

.api-status span + span::before {
  content: "·";
  margin-right: var(--sp-5);
  color: var(--border-default);
}

.api-code {
  font: var(--type-label);
  color: var(--status-ok-fg);
}

.post-body .api-foot {
  margin: 0;
  padding: var(--sp-5) var(--pad-card);
  border-top: 1px solid var(--border-default);
  background: var(--surface-sunken);
  font: var(--fw-regular) var(--fs-meta)/var(--lh-normal) var(--font-text);
  color: var(--text-muted);
}

.post-body .api-foot code {
  font: var(--type-meta);
  background: none;
  padding: 0;
  color: var(--text-strong);
}

@media (max-width: 720px) {
  .api-caption {
    margin-left: 0;
  }

  .api-model {
    margin-left: 0;
  }
}

/* --- race ---
   Two models answering the same question side by side. The decision model
   returns a number and freezes its clock; the conversational one is still
   emitting reasoning tokens ten seconds later. A script sets --p (how many
   tokens are out) and writes the clocks; everything else is here. */

@property --p {
  syntax: "<number>";
  initial-value: 0;
  inherits: true;
}

.post-body .race {
  margin: 0 0 var(--sp-8);
  padding: 0;
  max-width: 100%;
  background: var(--surface-panel);
  border: 1px solid var(--border-default);
}

/* The prompt as a chat model receives it: state hung on tags because the
   format has nowhere else to put it, then the question in prose. */
.race-prompt {
  padding: var(--sp-2) 0 var(--sp-6);
  font: var(--type-code);
  font-size: 15px;
  line-height: 1.5;
  color: var(--text-body);
}

.p-line {
  display: block;
  min-height: 1em;
  padding: 0 var(--pad-card);
  white-space: pre-wrap;
}

.p-tag {
  color: var(--text-faint);
}

.race-in {
  display: flex;
  align-items: baseline;
  gap: var(--sp-5);
  padding: var(--sp-5) var(--pad-card) var(--sp-2);
}

.race-col + .race-col {
  border-top: 1px solid var(--border-default);
}

/* A column in its own tone (the Jev side) repaints its ground; the figure's
   panel colour was resolved in the page's tone. */
.race-col[data-tone] {
  background: var(--surface-panel);
}

.race-head {
  display: flex;
  align-items: baseline;
  gap: var(--sp-5);
  padding: var(--sp-4) var(--pad-card);
  background: var(--surface-inverse);
}

.race-head .cf-label {
  font: var(--type-label);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--paper-4);
}

.race-clock {
  margin-left: auto;
  font: var(--type-meta);
  font-variant-numeric: tabular-nums;
  color: var(--accent-on-inverse);
}

/* Once the fast column lands, its clock is the number the panel exists to
   show. The live accent itself sits at 1.9:1 on the inverted bar and
   disappears; its tint is the readable end in both themes. */
.is-fast.is-done .race-clock {
  color: var(--accent-live-tint);
  font-weight: var(--fw-medium);
}

.post-body .s-out {
  margin: 0;
  padding: var(--sp-6) var(--pad-card);
  font: var(--type-code);
  font-size: 15px;
  line-height: 1.6;
  color: var(--text-muted);
  text-align: left;
  min-height: 6.5em;
}

.s-tok {
  position: relative;
  /* clamps itself: negative until --p reaches this token, then saturates */
  opacity: calc((var(--p) - var(--i)) * 8);
}

/* The caret rides the newest token. Hidden tokens keep their space so the
   paragraph never reflows, which would leave a single trailing caret sitting
   in blank space instead of at the write head. */
.s-tok::after {
  content: "";
  position: absolute;
  top: 0.1em;
  right: -0.5em;
  width: 0.5em;
  height: 1em;
  background: var(--accent-live-line);
  opacity: min(
    calc((var(--p) - var(--i)) * 8),
    calc((1 - var(--p) + var(--i)) * 8)
  );
}

.race-res {
  display: flex;
  align-items: baseline;
  gap: var(--sp-5);
  padding: var(--sp-5) var(--pad-card) var(--sp-2);
}

/* Scores. --fill is written by the script: 0 until the reply lands, then
   0 -> 1 over ~600ms so the result can actually be read. Rows are staggered
   slightly so the eye follows them down. */
.opts {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--pad-card) var(--sp-7);
}

.opt {
  display: grid;
  grid-template-columns: 7rem 1fr 3rem;
  align-items: center;
  gap: var(--sp-5);
  --f: clamp(0, calc((var(--fill, 0) - var(--o) * 0.12) * 1.4), 1);
  opacity: min(calc(var(--fill, 0) * 6 - var(--o) * 0.6), 1);
}

.opt-label {
  font: var(--type-code);
  color: var(--text-body);
}

.opt-track {
  display: block;
  height: 14px;
  background: var(--surface-sunken);
  border: 1px solid var(--border-subtle);
}

.opt-bar {
  display: block;
  height: 100%;
  width: calc(var(--w) * var(--f));
  background: var(--accent-live-line);
}

.opt:not(:first-child) .opt-bar {
  background: var(--paper-4);
}

.opt-p {
  font: var(--type-code);
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--text-muted);
}

.opt:first-child .opt-p {
  color: var(--accent-live);
  font-weight: var(--fw-medium);
}

.race-foot {
  display: flex;
  align-items: center;
  gap: var(--sp-6);
  padding: var(--sp-5) var(--pad-card);
  background: var(--surface-sunken);
  border-top: 1px solid var(--border-default);
}

.race-replay {
  height: var(--control-h);
  padding: 0 var(--sp-7);
  background: var(--action-bg);
  color: var(--action-fg);
  border: 0;
  font: var(--type-label);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  cursor: pointer;
}

.race-replay:hover {
  background: var(--action-bg-hover);
}

.race-replay:active {
  background: var(--action-bg-active);
}

.race-replay:focus-visible {
  outline: var(--border-focus);
  outline-offset: 2px;
}


@media (max-width: 720px) {
  .post-body .s-out {
    min-height: 0;
  }

  .opt {
    grid-template-columns: 5.5rem 1fr 2.6rem;
    gap: var(--sp-4);
  }
}

/* Motion is the argument here, so reduced motion keeps the finished state
   instead of removing the panel's meaning. */
@media (prefers-reduced-motion: reduce) {
  .s-tok::after {
    opacity: 0;
  }
}
