@layer reset, base, layout, components, prose, print;

/* Cross-document view transitions: same-origin navigations crossfade in
   supporting browsers and degrade to instant loads elsewhere. */
@view-transition {
  navigation: auto;
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

@layer reset {
  *, *::before, *::after {
    box-sizing: border-box;
  }
  * {
    margin: 0;
  }
  html {
    -webkit-text-size-adjust: 100%;
    scrollbar-gutter: stable;
  }
  body {
    min-height: 100dvh;
    line-height: 1.55;
  }
  img, svg {
    display: block;
    max-width: 100%;
    height: auto;
  }
  [hidden] {
    display: none !important;
  }
  /* Chrome links carry no color and no underline; the reading column adds
     both back in the prose layer. */
  a {
    color: inherit;
    text-decoration: none;
  }
  input, button {
    font: inherit;
  }
}

@layer base {
  /* Every color pairs light and dark via light-dark(). The effective theme
     is whatever color-scheme resolves to: the OS preference by default, or
     the explicit data-theme override set by theme.js.

     Ramps drawn from Catppuccin Frappe (dark) and Latte (light), used as a
     grey scale rather than as a palette: one faintly blue hue across text,
     muted, quiet and dim, and a single accent that marks exactly one thing,
     a story whose status is developing. --hover and --border are midpoints
     between two neighbouring Catppuccin steps; every other value is a named
     one. */
  :root {
    color-scheme: light dark;
    --font-mono:
      ui-monospace,
      SFMono-Regular,
      "SF Mono",
      Menlo,
      Consolas,
      monospace;
    /*                    Latte     Frappe                                 */
    --bg: light-dark(#eff1f5, #303446); /* base                          */
    --fg: light-dark(#4c4f69, #c6d0f5); /* text                          */
    --muted: light-dark(#5c5f77, #b5bfe2); /* subtext1                   */
    /* The de-emphasis steps are darkened past Latte's overlay shades and
       lightened past Frappe's: stock overlay2/overlay0 land at 3.49 and 2.30
       against the light base, and the site asserts a perfect Lighthouse
       accessibility score, which wants 4.5:1 on text this small. Everything
       from --fg (7.06) down to 4.5 is the whole usable band, so the steps are
       close together by necessity. Measured against --bg: 5.06 and 4.63
       light, 4.87 and 4.53 dark. Restoring the palette values reopens the
       color-contrast audit. */
    --quiet: light-dark(#62657b, #9aa2c0); /* subtext0 / overlay2-ish    */
    --dim: light-dark(#686b81, #949cbb); /* subtext0 / overlay2          */
    --surface: light-dark(#e6e9ef, #414559); /* mantle / surface0        */
    --hover: light-dark(#eaedf2, #383c4f); /* base to mantle / surface0  */
    --border: light-dark(#ccd0da, #494e63); /* surface0 / surface0 to 1  */
    /* Latte yellow is 2.31 on the light base, and this is text on the
       developing badge as well as the focus ring. Darkened to 4.79; Frappe's
       yellow already clears it at 7.62. */
    --accent: light-dark(#8f6100, #e5c890); /* yellow, darkened light   */

    --wide: 51.25rem;
    --gutter: clamp(1rem, 4vw, 1.5rem);
    /* Rows pad past the reading column so the hover band is wider than the
       text it covers. Every bleeding row pairs this with a negative margin. */
    --bleed: 0.625rem;

    /* Spacing scale. Every margin and padding on the site resolves to one of
       these; no rule carries a bare length. */
    --space-3xs: 0.25rem;
    --space-2xs: 0.375rem;
    --space-xs: 0.5rem;
    --space-sm: 0.75rem;
    --space-md: 1rem;
    --space-lg: 1.5rem;
    --space-xl: 2rem;

    /* Horizontal gaps. Two registers: one for rows of small labels and links,
       one for the nav, whose items sit at reading size. */
    --gap-inline: 0.875rem;
    --gap-nav: 1.25rem;

    /* The label used for every section head, field name, and eyebrow. */
    --label-size: 0.6875rem;
    --label-track: 0.09em;
    /* Wide enough for the longest field name, "Not established:". */
    --label-col: 8.5rem;
  }

  :root[data-theme="light"] {
    color-scheme: light;
  }
  :root[data-theme="dark"] {
    color-scheme: dark;
  }

  body {
    display: grid;
    grid-template-rows: auto 1fr auto;
    background-color: var(--bg);
    color: var(--fg);
    font-family: var(--font-mono);
    font-size: 0.875rem;
    -webkit-font-smoothing: antialiased;
  }

  a:hover {
    color: var(--fg);
  }
  a:focus-visible,
  button:focus-visible,
  input:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
  }

  code, pre {
    font-family: inherit;
  }
  code {
    padding: 0 var(--space-3xs);
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--fg);
  }
  pre {
    overflow-x: auto;
    padding: var(--space-md);
    border: 1px solid var(--border);
    background: var(--surface);
  }
}

@layer layout {
  .layout, .nav, .site-footer > p {
    width: min(calc(100% - var(--gutter) * 2), var(--wide));
    margin-inline: auto;
  }

  main {
    padding-bottom: 0;
  }

  .nav {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-lg);
    padding-top: var(--space-xl);
  }

  .brand {
    color: var(--fg);
    font-weight: 600;
    letter-spacing: -0.01em;
  }

  .nav-links {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--gap-nav);
    color: var(--quiet);
    font-size: 0.8125rem;
  }

  /* A word, not a glyph: the button names the theme a click switches to. */
  /* Hidden until the theme script announces itself, because the button does
     nothing without JS. The announcement is an attribute set in a blocking
     head script, so the button is there from the first paint; revealing it on
     DOMContentLoaded instead grew the nav afterwards and pushed main down by
     0.073, which was the whole layout-shift budget on a page that otherwise
     never moves. */
  .theme-toggle {
    display: none;
    padding: var(--space-3xs) var(--space-xs);
    border: 1px solid var(--border);
    background: transparent;
    color: var(--quiet);
    font-size: 0.75rem;
    cursor: pointer;
  }

  :root[data-js] .theme-toggle {
    display: inline-block;
  }

  .theme-toggle:hover {
    border-color: var(--quiet);
    color: var(--fg);
  }

  /* Which word shows tracks the effective theme with no JS, the same way the
     colors do: the OS preference by default, overridden by data-theme. */
  .theme-to-light {
    display: none;
  }
  @media (prefers-color-scheme: dark) {
    .theme-to-dark {
      display: none;
    }
    .theme-to-light {
      display: inline;
    }
  }
  :root[data-theme="light"] .theme-to-dark {
    display: inline;
  }
  :root[data-theme="light"] .theme-to-light {
    display: none;
  }
  :root[data-theme="dark"] .theme-to-dark {
    display: none;
  }
  :root[data-theme="dark"] .theme-to-light {
    display: inline;
  }

  /* body is a three-row grid with main at 1fr, so this sits on the bottom
     edge when the page is shorter than the viewport. */
  .site-footer {
    margin-top: var(--space-lg);
    color: var(--dim);
  }

  .site-footer > p {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--gap-inline);
    padding-bottom: var(--space-lg);
    font-size: 0.75rem;
  }

  .footer-build {
    display: flex;
    margin-left: auto;
    gap: var(--space-sm);
  }
}

@layer components {
  .skip {
    position: fixed;
    top: var(--space-sm);
    left: var(--space-sm);
    z-index: 10;
    transform: translateY(-140%);
    padding: var(--space-xs) var(--space-sm);
    border: 1px solid var(--border);
    background: var(--surface);
  }

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

  .visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }

  /* One label treatment, used by every section head, field name, and eyebrow. */
  .eyebrow,
  .day-section h2,
  .digest-month h2,
  .prose h2,
  .story-fields > ul > li > strong:first-child {
    color: var(--muted);
    font-size: var(--label-size);
    font-weight: 400;
    letter-spacing: var(--label-track);
    text-transform: uppercase;
  }

  .page-header {
    display: grid;
    gap: var(--space-2xs);
    margin-block: var(--space-xl) 0;
    padding-bottom: var(--space-sm);
    border-bottom: 1px solid var(--border);
  }

  .page-header h1 {
    font-size: 1.3125rem;
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: -0.02em;
  }
  .page-header p:not(.eyebrow) {
    color: var(--muted);
  }

  /* The digest date is a label, not a headline: date, publish time and story
     count share one line, with the suggest link pushed to the far end. */
  .digest-header {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-3xs) var(--gap-inline);
  }
  .digest-header .meta {
    flex: 1;
  }
  .digest-header .meta > span:last-child {
    margin-left: auto;
  }

  .meta {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--gap-inline);
    color: var(--dim);
    font-size: 0.75rem;
  }
  .meta a {
    color: var(--quiet);
  }

  .digest-list {
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .digest-month {
    margin-top: var(--space-xl);
  }

  /* A section head is the label alone. The story rows below carry their own
     hairlines, so a rule here only competes with them. */
  .day-section h2,
  .digest-month h2 {
    margin-bottom: var(--space-2xs);
  }
  .digest-row {
    display: grid;
    grid-template-columns: 8.125rem 1fr auto;
    gap: var(--space-md);
    align-items: baseline;
    padding: var(--space-xs) var(--bleed);
    margin-inline: calc(var(--bleed) * -1);
    border-bottom: 1px solid var(--border);
  }
  .digest-row:hover {
    background: var(--hover);
  }
  .digest-row time {
    color: var(--fg);
  }
  /* Two lines, because the row now carries a written sentence rather than a
     truncated title: one line cuts a lede in half at this column width. */
  .digest-lead {
    display: -webkit-box;
    min-width: 0;
    overflow: hidden;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    color: var(--muted);
    font-size: 0.8125rem;
  }
  .digest-count {
    color: var(--dim);
    font-size: 0.75rem;
  }

  /* Newer and older sit at the two edges. An empty span stands in for a
     missing neighbour so the surviving link keeps its own side. */
  .pagination {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--gap-inline);
    margin-top: var(--space-xl);
    color: var(--dim);
    font-size: 0.75rem;
  }

  /* Home index */
  .filter-bar {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    margin-top: var(--space-lg);
    padding: var(--space-xs) var(--space-sm);
    border: 1px solid var(--border);
    background: var(--surface);
  }

  .filter-bar::before {
    content: "/";
    color: var(--quiet);
  }

  .filter-search {
    display: flex;
    flex: 1;
    min-width: 0;
  }

  .filter-search input {
    width: 100%;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--fg);
    font-size: 0.8125rem;
  }

  .filter-search input::placeholder {
    color: var(--dim);
  }
  .filter-search input:focus {
    outline: none;
  }

  /* iOS Safari zooms the page whenever a text field under 16px takes focus,
     and leaves it zoomed. Only touch devices do it, so only they pay the
     larger text. The alternative fix, user-scalable=no on the viewport, would
     also take pinch zoom away from everyone. */
  @media (pointer: coarse) {
    .filter-search input {
      font-size: 1rem;
    }
  }

  /* Stays rendered when empty so the aria-live region keeps announcing; it
     shares the row with the input, so an empty count costs no vertical space. */
  .filter-count {
    color: var(--dim);
    font-size: 0.75rem;
    white-space: nowrap;
  }

  .story-list {
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .story-row {
    margin: 0;
  }

  .story-link {
    display: grid;
    gap: var(--space-xs);
    padding: var(--space-sm) var(--bleed);
    margin-inline: calc(var(--bleed) * -1);
    border-bottom: 1px solid var(--border);
    color: var(--fg);
  }

  .story-link:hover {
    background: var(--hover);
  }

  .story-title {
    font-size: 0.9375rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    text-wrap: pretty;
  }

  .story-summary {
    display: -webkit-box;
    overflow: hidden;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    color: var(--muted);
    font-size: 0.78125rem;
    line-height: 1.6;
    text-wrap: pretty;
  }

  /* Category and status read after the summary, not before the title: the
     headline is what the eye should land on first. */
  .story-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--gap-inline);
    color: var(--dim);
    font-size: var(--label-size);
    letter-spacing: 0.04em;
  }

  .badge {
    color: var(--dim);
  }

  .status-developing {
    color: var(--accent);
  }

  .story-empty {
    padding: var(--space-md) 0;
    color: var(--muted);
    font-size: 0.8125rem;
  }

  .search-more {
    width: 100%;
    margin-top: var(--space-md);
    padding: var(--space-xs) var(--space-md);
    border: 1px solid var(--border);
    background: transparent;
    color: var(--quiet);
    font-size: 0.75rem;
    cursor: pointer;
  }

  .search-more::-moz-focus-inner {
    border: 0;
  }
  .search-more:hover,
  .search-more:focus-visible {
    border-color: var(--quiet);
    color: var(--fg);
  }
}

@layer prose {
  .prose > * + * {
    margin-top: var(--space-sm);
  }

  /* One block rhythm, shared by the about page's headed blocks and the story
     page's fields: a rule with --space-md on each side. The page header's own
     border is the first block's rule, so the first block draws none and the
     gap below the header matches every other gap below a rule. */
  .prose > .page-header + * {
    margin-top: var(--space-sm);
  }
  .prose h2 {
    margin-top: var(--space-sm);
    padding-top: var(--space-sm);
    border-top: 1px solid var(--border);
  }
  .prose > h2:first-child,
  .prose > .page-header + h2 {
    padding-top: 0;
    border-top: 0;
  }
  /* Label to its content, the same 6px used by every other label on the site. */
  .prose h2 + * {
    margin-top: var(--space-2xs);
  }
  .prose h3 {
    margin-top: var(--space-md);
    font-size: 0.9375rem;
    font-weight: 600;
    letter-spacing: -0.01em;
  }
  .prose p, .prose li {
    color: var(--fg);
    line-height: 1.7;
    text-wrap: pretty;
  }
  .prose ul, .prose ol {
    padding-left: var(--space-lg);
  }
  .prose li + li {
    margin-top: var(--space-3xs);
  }
  .prose blockquote {
    padding-left: var(--space-md);
    border-left: 1px solid var(--border);
    color: var(--muted);
  }
  .prose table {
    width: 100%;
    border-collapse: collapse;
  }
  .prose th, .prose td {
    padding: var(--space-xs);
    border: 1px solid var(--border);
    text-align: left;
  }

  /* The reading column is the one place a link is marked as one. */
  .prose a {
    color: var(--fg);
    text-decoration: underline;
    text-decoration-color: var(--border);
    text-underline-offset: 0.18em;
  }
  .prose a:hover {
    text-decoration-color: var(--quiet);
  }
  .prose .meta a {
    text-decoration: none;
  }

  .story-page h1 {
    font-size: 1.5rem;
    line-height: 1.32;
  }

  /* The generated story body is one list of "**Label:** value" bullets. Each
     becomes a labelled block: no card, no bullet, a rule between fields. */
  .story-fields > ul {
    padding: 0;
    list-style: none;
  }

  /* The field name hangs in its own column so a one-word value costs one
     line, not two. A hanging indent rather than a grid: the values carry
     inline <code> and <a>, and every one of those would become its own cell
     in a grid container. */
  .story-fields > ul > li {
    margin-top: 0;
    padding: var(--space-sm) 0;
    padding-left: var(--label-col);
    border-top: 1px solid var(--border);
    line-height: 1.7;
    text-indent: calc(var(--label-col) * -1);
  }

  .story-fields > ul > li:first-child {
    padding-top: 0;
    border-top: 0;
  }

  .story-fields > ul > li > strong:first-child {
    display: inline-block;
    width: var(--label-col);
    /* Markdown always emits a space after the label. Pull the value back by
       exactly one character so its first line starts on the column, level
       with the lines that wrap under it. */
    margin-right: -1ch;
    text-indent: 0;
  }

  .day-section {
    margin-top: var(--space-xl);
  }

  .run-meta {
    margin-top: var(--space-md);
    color: var(--dim);
  }

  .story-feedback {
    margin-top: var(--space-xl);
  }
}

@media (max-width: 640px) {
  .nav {
    gap: var(--space-sm);
  }
  .nav-links {
    flex-wrap: nowrap;
    gap: var(--space-md);
  }

  /* Both label columns stop earning their width: stack instead. */
  .story-fields > ul > li {
    padding-left: 0;
    text-indent: 0;
  }
  .story-fields > ul > li > strong:first-child {
    display: block;
    width: auto;
    margin-right: 0;
    margin-bottom: var(--space-2xs);
  }

  .digest-row {
    grid-template-columns: 1fr auto;
    gap: var(--space-3xs) var(--space-sm);
  }
  .digest-lead {
    grid-column: 1 / -1;
  }

  .site-footer > p {
    flex-wrap: nowrap;
    gap: var(--space-sm);
  }
  .footer-build {
    flex-shrink: 0;
    white-space: nowrap;
  }
}

@media print {
  .site-header, .site-footer, .skip, .filter-bar {
    display: none;
  }
  body {
    background: white;
    color: black;
  }
  a {
    color: black;
  }
}
