/* Open Program public view.
 *
 * This is the Architecture grammar from site/static/css/base.css — light
 * mineral ground, ink and muted text, purple for non-semantic emphasis, flat
 * one-pixel borders, monospace for identifiers and state labels, and green,
 * amber and red reserved for success, in-progress and error. The token VALUES
 * are copied rather than imported because this file is served by programd on a
 * different origin from the static site; a shared stylesheet across origins
 * would need CORS the API deliberately does not send.
 *
 * The one deliberate departure from DESIGN.md is the dark scheme below.
 * DESIGN.md pins the marketing concepts to light because they are narrative
 * surfaces read once. This is an instrument, kept open while someone watches a
 * Program, so it answers the reader's own system setting. It keeps the same
 * hues, structure, flat borders, and reserved semantics — it is a legibility
 * accommodation, not a second brand.
 */

:root {
  color-scheme: light;
  --bg: #f0f2ef;
  --surface: #f8faf7;
  --sunken: #eaeee9;
  --ink: #18201c;
  --muted: #56615b;
  --accent: #6c40c8;
  --line: rgba(24, 32, 28, 0.2);
  --hairline: rgba(24, 32, 28, 0.1);
  --soft: rgba(108, 64, 200, 0.08);
  --rail: #e5e8e4;
  --success: #1d6b48;
  --warning: #7a5100;
  --danger: #a33d32;
  --focus: #155eef;
  --radius: 0.35rem;
  --font-display: "Trebuchet MS", "Segoe UI", sans-serif;
  --font-body: "Segoe UI", Tahoma, sans-serif;
  --font-mono: ui-monospace, "SFMono-Regular", Consolas, monospace;
  --page-gutter: clamp(1rem, 3vw, 3rem);
  --content-max: 82rem;
  --motion-fast: 160ms;
  --motion-base: 320ms;
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
}

@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
    --bg: #131714;
    --surface: #1a1f1b;
    --sunken: #101412;
    --ink: #e7ece7;
    --muted: #a2aca5;
    --accent: #b79dff;
    --line: rgba(231, 236, 231, 0.22);
    --hairline: rgba(231, 236, 231, 0.12);
    --soft: rgba(183, 157, 255, 0.13);
    --rail: #191e1a;
    --success: #63c290;
    --warning: #d9a84a;
    --danger: #ef9184;
    --focus: #7fb2ff;
  }
}

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

body {
  margin: 0;
  min-width: 18rem;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  line-height: 1.55;
  /* The page body never scrolls sideways. Anything genuinely wider than the
   * viewport — a digest, a chain row — scrolls inside .scroll-x instead. */
  overflow-x: hidden;
}

h1,
h2,
h3,
p {
  margin-top: 0;
}

h1,
h2,
h3 {
  font-family: var(--font-display);
  line-height: 1.02;
  text-wrap: balance;
}

h1 {
  margin-bottom: 1.1rem;
  font-size: clamp(2.3rem, 5vw, 3.9rem);
  letter-spacing: -0.045em;
}

/* Section headings stay well below the page title. This is an instrument: the
 * data is the largest thing on the page, and a heading that competes with a
 * digest or a count makes the page harder to scan, not easier. */
h2 {
  margin-bottom: 0.85rem;
  font-size: clamp(1.4rem, 2.4vw, 1.9rem);
  letter-spacing: -0.035em;
}

h3 {
  margin-bottom: 0.6rem;
  font-size: clamp(1rem, 1.5vw, 1.15rem);
  letter-spacing: -0.02em;
}

.panel h2 {
  font-size: 1.2rem;
  letter-spacing: -0.02em;
}


a {
  color: inherit;
  text-underline-offset: 0.2em;
}

:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
}

code,
.mono {
  font-family: var(--font-mono);
  font-size: 0.85em;
}

.skip-link {
  position: fixed;
  z-index: 100;
  top: 0.75rem;
  left: 0.75rem;
  padding: 0.7rem 1rem;
  transform: translateY(-180%);
  border-radius: 0.25rem;
  background: var(--surface);
  color: var(--ink);
  font-weight: 750;
}

.skip-link:focus {
  transform: translateY(0);
}

/* Header and rail ------------------------------------------------------- */

.topbar {
  display: flex;
  align-items: center;
  /* The removed status tag carried `margin-right: auto`, which is what held the
     button against the right edge. Stated here now that nothing sits between. */
  justify-content: space-between;
  flex-wrap: wrap;
  min-height: 4.5rem;
  padding: 0.7rem var(--page-gutter);
  border-bottom: 1px solid var(--line);
  gap: 0.75rem 1rem;
}

.wordmark {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  text-decoration: none;
  font-family: "Arial Narrow", "Helvetica Neue", Arial, sans-serif;
  font-size: 1rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  gap: 0.7rem;
}

.wordmark__mark {
  display: block;
  width: 2.35rem;
  height: 2.35rem;
  flex: 0 0 auto;
}

.brand-mark {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.brand-mark__aperture,
.brand-mark__axis {
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.brand-mark__aperture {
  stroke-width: 2.8;
}

.brand-mark__axis {
  stroke-width: 1.85;
}

.brand-mark__node {
  fill: currentColor;
}

/* Bands and panels ------------------------------------------------------ */

main:focus {
  outline: 0;
}

.band {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: clamp(1.75rem, 3.5vw, 2.75rem) var(--page-gutter);
}

.band--head {
  padding-bottom: clamp(1.25rem, 2vw, 1.75rem);
}

.band + .band {
  padding-top: 0;
}

.band--rule {
  border-top: 1px solid var(--hairline);
  margin-top: clamp(1.25rem, 3vw, 2.25rem);
  padding-top: clamp(1.75rem, 3vw, 2.5rem);
}

/* The head band and the summary band are separate sections so that the static
 * document renders its own title before any read completes. Collapsing the gap
 * between them keeps them reading as one masthead. */
.band--head + #view .band--head {
  padding-top: 0;
}

.eyebrow {
  margin-bottom: 0.75rem;
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 0.69rem;
  font-weight: 750;
  letter-spacing: 0.105em;
  line-height: 1.45;
  text-transform: uppercase;
}

.lede {
  max-width: 46rem;
  color: var(--muted);
  font-size: clamp(1.02rem, 1.6vw, 1.2rem);
}

.status {
  margin: 0;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.78rem;
}

.panel {
  padding: clamp(1rem, 2.5vw, 1.6rem);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}

.panel + .panel {
  margin-top: 1rem;
}

.panel--note {
  background: var(--soft);
}

.panel h2,
.panel h3 {
  margin-top: 0;
}

.panel__title {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  margin-bottom: 1rem;
  gap: 0.5rem 1rem;
}

.panel__title h2,
.panel__title h3 {
  margin-bottom: 0;
}

/* The headline sentence. At zero activity this IS the dashboard: one true
 * sentence beats six tiles reading 0. */
.headline {
  max-width: 52rem;
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 2.8vw, 2.1rem);
  letter-spacing: -0.025em;
  line-height: 1.2;
}

.headline em {
  color: var(--muted);
  font-style: normal;
}

.note {
  max-width: 46rem;
  margin: 1rem 0 0;
  color: var(--muted);
  font-size: 0.95rem;
}

.note:last-child {
  margin-bottom: 0;
}

/* Figures --------------------------------------------------------------- */

/* Flex rather than grid, because the hairlines between cells are drawn by the
 * container's background showing through a 1px gap. Six figures in a grid of
 * four leave two empty tracks, and those tracks render as a slab of divider
 * colour. Flex items grow to fill the last line instead, so there is never an
 * empty cell to show through. */
.figures {
  display: flex;
  flex-wrap: wrap;
  margin-top: 1.5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  gap: 1px;
  background: var(--line);
}

.figure {
  flex: 1 1 9.5rem;
  min-width: 0;
  padding: 0.9rem 1rem;
  background: var(--surface);
}

.figure__label {
  display: block;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.figure__value {
  display: block;
  margin-top: 0.35rem;
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 3vw, 2.1rem);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.03em;
  line-height: 1.1;
}

.figure__unit {
  display: block;
  margin-top: 0.2rem;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.68rem;
}

.figure--success .figure__value {
  color: var(--success);
}

.figure--warning .figure__value {
  color: var(--warning);
}

.figure--danger .figure__value {
  color: var(--danger);
}

/* Key/value grids ------------------------------------------------------- */

.kv {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
  margin: 0;
  gap: 0.9rem 1.5rem;
}

.kv + .kv {
  margin-top: 0.9rem;
}

/* Rows whose value is a list of pills need the full column width; at four
 * across they wrap into ragged stacks. */
.kv--wide {
  grid-template-columns: repeat(auto-fit, minmax(min(24rem, 100%), 1fr));
}

.kv > div {
  min-width: 0;
  padding-top: 0.7rem;
  border-top: 1px solid var(--hairline);
}

.kv dt {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.kv dd {
  margin: 0.3rem 0 0;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.kv dd small {
  display: block;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.7rem;
}

/* Pills and state labels ------------------------------------------------ */

.pill-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.pill {
  display: inline-flex;
  align-items: center;
  /* Effect kinds and stop conditions are owner-chosen identifiers, and
   * three_consecutive_failures is wider than a phone. Wrapping inside the pill
   * keeps it in the panel; a nowrap pill would push the layout sideways, and
   * body has overflow-x: hidden, so it would simply be lost off the edge. */
  max-width: 100%;
  padding: 0.25rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  overflow-wrap: anywhere;
  text-transform: uppercase;
}

/* State colour follows DESIGN.md: amber is warning or in progress, so only the
 * three states where a Program can currently be doing work carry it. proposed,
 * funding, winding_down and closed are neutral — "nothing is happening" is not
 * a warning. Nothing here is red: there is no failed Program state. */
.pill--live {
  border-color: var(--warning);
  color: var(--warning);
}

.pill--accent {
  border-color: var(--accent);
  color: var(--accent);
}

.pill--good {
  border-color: var(--success);
  color: var(--success);
}

/* Red is only ever a task outcome. A Program never gets one: there is no
 * failed Program state, and "closed" is a completed lifecycle, not an error. */
.pill--bad {
  border-color: var(--danger);
  color: var(--danger);
}

.pill--quiet {
  color: var(--muted);
}

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

.program-list {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
  gap: 1rem;
}

.program-card {
  display: block;
  padding: clamp(1rem, 2.5vw, 1.6rem);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  text-decoration: none;
  transition:
    border-color var(--motion-fast) ease,
    transform var(--motion-fast) var(--ease-out);
}

.program-card:hover {
  border-color: var(--accent);
  transform: translateY(-2px);
}

.program-card:active {
  transform: translateY(0);
}

.program-card__head {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 0.5rem 0.85rem;
}

/* The glyph anchors the row. Ids here differ by a suffix, which is exactly the
   case where text alone is hard to scan and harder to return to. */
.glyph {
  width: 2rem;
  height: 2rem;
  border-radius: 0.5rem;
  flex: none;
}

@media (max-width: 34rem) {
  .program-card__head {
    grid-template-columns: auto 1fr;
  }

  .program-card__head .pill-row {
    grid-column: 2;
  }
}

.program-card__id {
  font-family: var(--font-mono);
  font-size: clamp(1rem, 2vw, 1.25rem);
  font-weight: 700;
  overflow-wrap: anywhere;
}

.program-card__objective {
  max-width: 52rem;
  margin: 0.9rem 0 0;
  font-size: 1.02rem;
}

.program-card__meta {
  display: flex;
  flex-wrap: wrap;
  margin-top: 1rem;
  padding-top: 0.8rem;
  border-top: 1px solid var(--hairline);
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  gap: 0.35rem 1.25rem;
}

.program-card__open {
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* Digests and chain ----------------------------------------------------- */

.chain {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
  gap: 0;
}

.chain__link {
  position: relative;
  padding: 1rem 0 1rem 2rem;
  border-top: 1px solid var(--hairline);
}

.chain__link:first-child {
  border-top: 0;
}

/* The vertical rule and node are the chain made visible: one continuous line
 * through every link, which is exactly what previous_digest asserts. */
.chain__link::before {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0.34rem;
  width: 1px;
  background: var(--line);
  content: "";
}

.chain__link:first-child::before {
  top: 1.35rem;
}

.chain__link:last-child::before {
  bottom: auto;
  height: 1.35rem;
}

.chain__link::after {
  position: absolute;
  top: 1.15rem;
  left: 0;
  width: 0.75rem;
  height: 0.75rem;
  border: 2px solid var(--surface);
  border-radius: 50%;
  background: var(--accent);
  content: "";
}

.chain__head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
}

.chain__seq {
  font-family: var(--font-mono);
  font-size: 0.95rem;
  font-weight: 750;
}

.chain__time {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.72rem;
}

.linkage {
  display: flex;
  align-items: flex-start;
  margin-bottom: 1.25rem;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-size: 0.92rem;
  gap: 0.6rem;
}

.linkage--ok {
  border-color: var(--success);
  color: var(--success);
}

.linkage--broken {
  border-color: var(--danger);
  color: var(--danger);
}

.linkage strong {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* Tables ---------------------------------------------------------------- */

.scroll-x {
  max-width: 100%;
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.88rem;
}

th,
td {
  padding: 0.6rem 0.9rem 0.6rem 0;
  border-bottom: 1px solid var(--hairline);
  text-align: left;
  vertical-align: top;
  white-space: nowrap;
}

th {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

td.num {
  font-variant-numeric: tabular-nums;
}

td .mono {
  font-size: 0.8rem;
}

/* Buttons and links ----------------------------------------------------- */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.6rem;
  padding: 0.6rem 1rem;
  border: 1px solid currentColor;
  border-radius: 0.25rem;
  background: transparent;
  color: inherit;
  cursor: pointer;
  font: inherit;
  font-size: 0.88rem;
  font-weight: 700;
  line-height: 1.2;
  text-decoration: none;
  transition:
    background-color var(--motion-fast) ease,
    transform var(--motion-fast) var(--ease-out);
}

.button:hover {
  background: var(--soft);
  transform: translateY(-1px);
}

.button:active {
  transform: translateY(0);
}

.button--quiet {
  border-color: var(--line);
  color: var(--muted);
}

.button[disabled] {
  cursor: progress;
  opacity: 0.55;
  transform: none;
}

.back-link {
  display: inline-flex;
  align-items: center;
  min-height: 2.5rem;
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  gap: 0.4rem;
}

.more-row {
  margin-top: 1rem;
}

/* Failure and emptiness ------------------------------------------------- */

.failure {
  border-color: var(--danger);
}

.failure .eyebrow {
  color: var(--danger);
}

.failure__detail {
  margin: 1rem 0 0;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--hairline);
  border-radius: 0.25rem;
  background: var(--sunken);
  font-family: var(--font-mono);
  font-size: 0.76rem;
  line-height: 1.6;
  overflow-wrap: anywhere;
}

.failure__detail span {
  display: block;
}

.empty {
  max-width: 46rem;
  margin: 0;
  color: var(--muted);
}

.empty strong {
  color: var(--ink);
}

.endpoint-list {
  margin: 0.75rem 0;
  padding: 0;
  list-style: none;
}

.endpoint-list li {
  padding: 0.35rem 0;
  border-top: 1px solid var(--hairline);
}

/* Proof footer ---------------------------------------------------------- */

.proof {
  display: grid;
  grid-template-columns: minmax(15rem, 0.75fr) minmax(0, 1.6fr);
  max-width: var(--content-max);
  margin: clamp(2.5rem, 6vw, 5rem) auto 0;
  padding: clamp(2rem, 5vw, 4rem) var(--page-gutter) 2.5rem;
  border-top: 1px solid var(--line);
  gap: 1.5rem clamp(2rem, 6vw, 5rem);
}

.proof__intro h2 {
  font-size: clamp(1.6rem, 3vw, 2.3rem);
}

.proof__intro p:last-child {
  margin-bottom: 0;
  color: var(--muted);
  font-size: 0.92rem;
}

.proof__body article + article {
  margin-top: 1.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--hairline);
}

.proof__body p {
  max-width: 48rem;
  margin-bottom: 0;
  color: var(--muted);
}

.proof .legal {
  grid-column: 1 / -1;
  margin: 1rem 0 0;
  padding-top: 1rem;
  border-top: 1px solid var(--hairline);
  color: var(--muted);
  font-size: 0.76rem;
}

/* Motion: one-shot, and only to soften a view swap. Nothing loops, nothing
 * pulses, and the terminal state is the default so a reader who prefers
 * reduced motion loses nothing. */
@media (prefers-reduced-motion: no-preference) {
  .view-enter {
    animation: rise var(--motion-base) var(--ease-out) both;
  }
}

@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(0.5rem);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

@media (max-width: 52rem) {
  .proof {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 30rem) {
  .topbar {
    align-items: flex-start;
    flex-direction: column;
  }

  .button {
    width: 100%;
  }

  .figure {
    flex-basis: 100%;
  }
}

/* The three structural caveats, folded. They qualify the data, so they belong
   on the page; they are not what a reader came for, so they do not belong in
   front of it. */
.proof__disclosure > summary {
  cursor: pointer;
  padding: 0.6rem 0;
  font-size: 0.9rem;
}

.proof__disclosure > summary:focus-visible {
  outline: 2px solid var(--focus, #155eef);
  outline-offset: 3px;
}

.proof__disclosure[open] > summary {
  margin-bottom: 1rem;
}


/* Figures line up column-wise wherever a number is compared to another number.
   Proportional digits make a list of counts a ragged edge to read. */
.program-card__meta,
.metric__value,
table td,
table th {
  font-variant-numeric: tabular-nums;
}

/* The state light.
   "operating" was the least interesting word on a row; whether a Program is
   live is what the eye actually looks for, so it is now colour and motion
   rather than text. The state name survives as the accessible name and the
   title, so nothing is lost to a screen reader or a hover. */
.light {
  display: inline-block;
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  background: var(--muted, #5d625c);
  flex: none;
}

.light--live {
  background: #3fbf6a;
  box-shadow: 0 0 0 0 rgba(63, 191, 106, 0.55);
  animation: light-pulse 2.4s ease-out infinite;
}

.light--warm {
  background: #d9a441;
}

.light--idle {
  background: color-mix(in srgb, currentColor 35%, transparent);
}

@keyframes light-pulse {
  /* Ends on a fully transparent ring rather than restarting hard, so the row
     reads as alive without ever pulling the eye off the data beside it. */
  70% { box-shadow: 0 0 0 0.42rem rgba(63, 191, 106, 0); }
  100% { box-shadow: 0 0 0 0 rgba(63, 191, 106, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .light--live { animation: none; }
}

.topbar__links {
  display: flex;
  gap: 1.1rem;
  margin-left: auto;
  font-size: 0.86rem;
}

.topbar__links a {
  color: inherit;
  opacity: 0.72;
  text-decoration: none;
}

.topbar__links a:hover,
.topbar__links a:focus-visible {
  opacity: 1;
  text-decoration: underline;
}

/* Task outcomes share the state light's vocabulary, and this is where red
   belongs: failure is a task outcome, never a Program state. */
.light--good { background: #3fbf6a; }
.light--bad  { background: #d1495b; }

/* Daily task outcomes. Same colours as the outcome lights, so a red column and
   a red dot are the same fact at two zoom levels. */
.activity-chart {
  display: block;
  width: 100%;
  height: 4rem;
  overflow: visible;
}

.activity-chart__bar--completed { fill: #3fbf6a; }
.activity-chart__bar--failed    { fill: #d1495b; }
.activity-chart__bar--other     { fill: #d9a441; }

/* A day with nothing in it still gets a baseline tick. An absent bar and a
   zero bar look identical otherwise, and "nothing ran" is a real answer. */
.activity-chart__bar--empty { fill: currentColor; opacity: 0.16; }

/* The whole column is the hover target, not just the coloured pixels. A day
   with one short bar was almost unhoverable otherwise. */
.activity-chart__target { fill: transparent; }

.activity-chart__day { cursor: default; }

.activity-chart__day:hover .activity-chart__bar { filter: brightness(1.15); }

.activity-chart__day:hover .activity-chart__target { fill: currentColor; fill-opacity: 0.06; }

/* A compacted figure keeps its exact value one hover away. */
.figure-exact[title] { cursor: help; }
