/* Structure: layout, rhythm, and component anatomy. Color, type, texture,
   and ornament belong to garden.css, which layers over these neutral
   defaults and can be swapped wholesale without touching markup. */
@layer reset, tokens, base, layout, prose, components, garden;

@layer reset {
  *,
  *::before,
  *::after {
    box-sizing: border-box;
  }
  html {
    text-size-adjust: none;
    -webkit-text-size-adjust: none;
  }
  body,
  h1,
  h2,
  h3,
  h4,
  p,
  ul,
  ol,
  li,
  figure,
  blockquote,
  pre,
  hr {
    margin: 0;
  }
  img,
  picture,
  svg,
  video {
    display: block;
    max-inline-size: 100%;
    block-size: auto;
  }
  input,
  button,
  textarea,
  select {
    font: inherit;
  }
}

@layer tokens {
  :root {
    color-scheme: light dark;
    --paper: light-dark(#fafafa, #161616);
    --surface: light-dark(#f0f0f0, #222);
    --ink: light-dark(#222, #eee);
    --ink-soft: light-dark(#666, #aaa);
    --accent: light-dark(#0b57d0, #8ab4f8);
    --link: var(--accent);
    --rule: light-dark(#ddd, #333);
    --rule-strong: light-dark(#bbb, #555);
    --highlight: light-dark(#fff3a3, #4a4320);

    --font-prose: Georgia, serif;
    --font-mono: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;

    --measure: 40rem;
    --wide-extra: 8rem;
    --sidenote-gap: 2.5rem;
    --sidenote-width: 16rem;
    --pad-inline: clamp(1.25rem, 6vw, 7rem);
    --pad-block: clamp(1.25rem, 4vh, 2.75rem);
    --rail-width: clamp(15rem, 22vw, 21rem);
    --rail-slim: 4.5rem;
    --flow: 1.25em;
    --radius: 0.4rem;

  }
}

@layer base {
  html {
    background-color: var(--paper);
    color: var(--ink);
    font-family: var(--font-prose);
    font-size: clamp(1.0625rem, 0.98rem + 0.3vw, 1.1875rem);
    line-height: 1.65;
    font-synthesis: none;
    hanging-punctuation: first last;
  }
  body {
    min-block-size: 100dvh;
  }
  h1,
  h2,
  h3,
  h4 {
    line-height: 1.15;
    text-wrap: balance;
  }
  p,
  li,
  dd,
  figcaption {
    text-wrap: pretty;
  }
  a {
    color: var(--link);
  }
  :focus-visible {
    outline: 0.15rem solid var(--accent);
    outline-offset: 0.2rem;
    border-radius: 0.15rem;
  }
  code,
  kbd,
  samp,
  pre {
    font-family: var(--font-mono);
    font-size: 0.86em;
  }
  ::selection {
    background: var(--highlight);
    color: var(--ink);
  }
}

@layer layout {
  /* The shell: content on the left, a navigation rail on the right. On
     narrow screens the rail drops below the content. */
  .shell {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    min-block-size: 100dvh;
  }
  .column {
    display: flex;
    flex-direction: column;
    min-inline-size: 0;
    padding-block: var(--pad-block);
    padding-inline: var(--pad-inline);
  }
  .rail nav {
    display: flex;
    flex-direction: column;
    justify-content: end;
    padding-block: var(--pad-block);
    padding-inline: var(--pad-inline);
  }
  @media (min-width: 60rem) {
    .shell {
      grid-template-columns: minmax(0, 1fr) var(--rail-width);
    }
    .rail nav {
      position: sticky;
      inset-block-start: 0;
      block-size: 100dvh;
      padding-inline: clamp(1.5rem, 2.5vw, 2.5rem);
    }
  }
  /* Until 84rem the rail folds to its glyphs, so main reaches the 52rem
     that sidenotes need from about 1090px (see the sidenote container
     query). At 84rem the full rail still leaves main just over 52rem, so
     notes stay put as it unfolds: keep the two in step if --rail-width or
     the threshold changes. */
  @media (min-width: 60rem) and (max-width: 83.99rem) {
    .shell {
      grid-template-columns: minmax(0, 1fr) var(--rail-slim);
    }
    .rail nav {
      padding-inline: 0.75rem;
    }
  }

  /* Main is a named-line grid anchored left: prose sits on the text track,
     and wide content breaks out to the right through subgrid. */
  main {
    flex: 1;
    display: grid;
    grid-template-columns:
      [full-start text-start wide-start] min(var(--measure), 100%)
      [text-end] minmax(0, var(--wide-extra))
      [wide-end] minmax(0, 1fr)
      [full-end];
    align-content: start;
    padding-block-start: clamp(3rem, 10vh, 7rem);
    container: main / inline-size;
  }
  main > *,
  .article > *,
  .article-body > * {
    grid-column: text;
  }
  main > .article,
  .article > .article-body {
    grid-column: full;
    display: grid;
    grid-template-columns: subgrid;
  }
  .article-body > :is(pre, table, .plates, .plate[data-size="wide"]) {
    grid-column: wide;
  }
  .article-body > .plate[data-size="full"] {
    grid-column: full;
  }

  /* Landing: exactly one screen. The intro sits high in the open space;
     the terminal owns everything below it, its log growing upward from the
     prompt at the bottom of the column. */
  [data-page="home"] main {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
    padding-block-start: 0;
  }
  main > .landing {
    grid-column: 1 / -1;
    display: grid;
    grid-template-rows: auto minmax(0, 1fr);
    gap: clamp(2rem, 6vh, 4rem);
  }
  /* The landing is exactly one screen tall at every width, so the
     terminal's row has a ceiling and its log scrolls within it. When the
     rail stacks below on small screens, it keeps its own row and the column
     takes what is left, so the links never leave the viewport. */
  [data-page="home"] .shell {
    block-size: 100dvh;
    grid-template-rows: minmax(0, 1fr) auto;
  }
  [data-page="home"] :is(.column, main) {
    min-block-size: 0;
  }
  @media (min-width: 60rem) {
    [data-page="home"] .shell {
      grid-template-rows: minmax(0, 1fr);
    }
  }
  @media (max-width: 59.99rem) {
    [data-page="home"] .landing-intro {
      padding-block-start: clamp(1rem, 7vh, 4rem);
    }
    [data-page="home"] .rail nav {
      padding-block: 0.75rem clamp(0.75rem, 3vh, 1.5rem);
    }
    [data-page="home"] .rail-links a {
      padding-block: 0.6rem;
    }
  }
  .landing-intro {
    display: grid;
    align-content: start;
    gap: clamp(1.75rem, 5vh, 3rem);
    padding-block-start: clamp(1.5rem, 18vh, 13rem);
  }
}

@layer prose {
  .article-body > * + * {
    margin-block-start: var(--flow);
  }
  .article-body > :is(h2, h3, h4) {
    margin-block-start: 2.2em;
  }
  .article-body > :is(h2, h3, h4) + * {
    margin-block-start: 0.75em;
  }
  /* Grid items never collapse margins, so set-pieces space themselves
     from what precedes them and push away what follows. */
  .article-body > :where(hr, pre, table, .callout, blockquote, .plate, .plates),
  .article-body > :where(hr, pre, table, .callout, blockquote, .plate, .plates) + * {
    margin-block-start: 2em;
  }
  .article-body > :is(h2, h3, h4, *):first-child {
    margin-block-start: 0;
  }
  .article-body > blockquote + blockquote {
    margin-block-start: 0.75em;
  }
  .article-body :is(ul, ol) {
    padding-inline-start: 1.35em;
  }
  .article-body li + li,
  .article-body li > :is(ul, ol) {
    margin-block-start: 0.4em;
  }
  .article-body li > p + p {
    margin-block-start: 0.6em;
  }
  .article-body blockquote > * + * {
    margin-block-start: 0.75em;
  }
  /* Section permalinks hang in the left margin of their heading. */
  .article-body :is(h2, h3, h4) {
    position: relative;
    scroll-margin-block-start: 2rem;
  }
  .heading-anchor {
    position: absolute;
    inset-inline-end: 100%;
    inset-block-start: 0;
    padding-inline-end: 0.45em;
  }

  /* Plates: figures built from image-only paragraphs. */
  .article-body {
    counter-reset: plate;
  }
  .plate {
    counter-increment: plate;
    display: grid;
    gap: 0.6rem;
    justify-items: center;
  }
  .plate img {
    border-radius: var(--radius);
  }
  .plate[data-size="full"] img {
    inline-size: 100%;
    border-radius: 0;
  }
  .plate[data-pixel] img {
    image-rendering: pixelated;
  }
  .plate figcaption {
    justify-self: stretch;
  }
  .plates {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
    gap: 1rem;
    align-items: start;
  }
  .plates img {
    inline-size: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
  }

  pre {
    overflow-x: auto;
    padding: 1rem 1.25rem;
    line-height: 1.5;
    tab-size: 2;
  }
  pre code {
    font-size: inherit;
  }
  :not(pre) > code {
    padding: 0.1em 0.3em;
    border-radius: 0.25rem;
    overflow-wrap: anywhere;
  }

  table {
    display: block;
    overflow-x: auto;
    border-collapse: collapse;
    font-variant-numeric: tabular-nums;
  }
  th,
  td {
    padding: 0.5em 0.9em;
    text-align: start;
    vertical-align: top;
  }

  hr {
    border: 0;
    block-size: 1px;
    background: var(--rule);
  }
  /* The footnote section draws its own divider. */
  .article-body > hr:has(+ .footnotes) {
    display: none;
  }
  .footnotes {
    font-size: 0.88em;
  }
  .footnotes ol {
    padding-inline-start: 1.5em;
  }
  .footnotes li + li {
    margin-block-start: 0.6em;
  }

  /* Sidenotes: each footnote is also copied beside its first reference
     (src/content/footnotes.odin), and one of the two copies shows. Where
     main fits the measure, a gap, and a note, the copy hangs in the margin
     and the notes section is hidden; elsewhere the copy is hidden. */
  .sidenote {
    display: none;
  }
  /* A note may hang halfway into the column's padding, so 52rem leaves it
     about 12rem: --measure + --sidenote-gap + 12rem - --pad-inline / 2. */
  @container main (min-width: 52rem) {
    .article-body {
      position: relative;
    }
    /* No block offset, so the note keeps its static position: the line its
       reference sits on. That needs an inline display (position: absolute
       blockifies the box anyway); a block's static position is the next
       line. Absolute rather than floated, because a grid item contains its
       floats and a long note would push the next paragraph down. (A
       reference inside a heading, which is positioned for its permalink,
       would place its note against the heading instead.) */
    .sidenote {
      display: inline;
      position: absolute;
      inset-inline-start: calc(var(--measure) + var(--sidenote-gap));
      inline-size: min(
        var(--sidenote-width),
        100cqi - var(--measure) - var(--sidenote-gap) + var(--pad-inline) / 2
      );
    }
    /* The first paragraph runs on from the note's number. */
    .sidenote-para + .sidenote-para {
      display: block;
      margin-block-start: 0.5em;
    }
    .footnotes {
      display: none;
    }
  }
  sup {
    line-height: 0;
  }
}

@layer components {
  .skip-link {
    position: fixed;
    inset-block-start: 0.5rem;
    inset-inline-start: 0.5rem;
    z-index: 10;
    translate: 0 -200%;
    padding: 0.5rem 0.75rem;
    background: var(--ink);
    color: var(--paper);
  }
  .skip-link:focus {
    translate: 0;
  }

  .eyebrow,
  .callout-label {
    font-family: var(--font-mono);
    font-size: 0.72rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    line-height: 1.4;
  }

  .page-header,
  .article-header {
    margin-block-end: clamp(2rem, 6vw, 3.5rem);
  }
  .page-header > * + *,
  .article-header > * + * {
    margin-block-start: 0.9rem;
  }

  .breadcrumb ol {
    display: flex;
    flex-wrap: wrap;
    padding: 0;
    list-style: none;
  }
  .breadcrumb li + li::before {
    content: "/";
    padding-inline: 0.75ch;
  }

  .rail-links {
    padding: 0;
    list-style: none;
  }
  .rail-links a {
    display: grid;
    grid-template-columns: 2.75rem 1fr;
    align-items: center;
    padding: 0.85rem 0.5rem;
  }
  /* The folded rail shows glyphs alone. Each label leaves the layout but
     still names its link, and slides out to the left of the rail on hover
     or focus (clipped, not hidden, so it stays in the accessibility tree). */
  @media (min-width: 60rem) and (max-width: 83.99rem) {
    .rail-links a {
      position: relative;
      grid-template-columns: 1fr;
      justify-items: center;
      padding-inline: 0;
    }
    .rail-label {
      position: absolute;
      inset-block: 0;
      inset-inline-end: 100%;
      display: flex;
      align-items: center;
      padding-inline: 0.85rem;
      white-space: nowrap;
      clip-path: inset(0 0 0 100%);
    }
    .rail-links a:is(:hover, :focus-visible) .rail-label {
      clip-path: inset(0);
    }
  }

  .latest {
    display: grid;
    gap: 0.35rem;
    justify-items: start;
    max-inline-size: 36rem;
  }

  .terminal {
    display: flex;
    flex-direction: column;
    justify-content: end;
    min-block-size: 0;
  }
  .terminal-output {
    flex: 0 1 auto;
    min-block-size: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
  }
  .terminal[data-collapsed] .terminal-output {
    max-block-size: 3lh;
    scrollbar-width: none;
  }
  .terminal-bar {
    display: flex;
    /* Last, not first: a notice can wrap above the prompt's own line. */
    align-items: last baseline;
    gap: 1.5rem;
  }
  .terminal-toggle {
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
  }
  .terminal:not([data-engaged]) .terminal-toggle {
    visibility: hidden;
  }
  .terminal-output > * + *,
  .terminal-chat > * + * {
    margin-block-start: 0.2rem;
  }
  /* Chat mode: a prompt with data-chat swaps the log for #lobby. */
  .terminal-chat {
    display: none;
    flex: 0 1 auto;
    min-block-size: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
  }
  .terminal:has(#terminal-prompt[data-chat]) .terminal-chat {
    display: block;
  }
  .terminal:has(#terminal-prompt[data-chat]) :is(.terminal-output, .terminal-toggle) {
    display: none;
  }
  .chat-line {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 1.5ch;
  }
  .chat-text {
    overflow-wrap: anywhere;
  }
  /* On a phone a name column squeezes the text; flow lines like IRC. */
  @media (max-width: 40rem) {
    .chat-line {
      display: block;
    }
    .chat-name {
      margin-inline-end: 1ch;
    }
  }
  .terminal-prompt:has(.terminal-notice) {
    flex-wrap: wrap;
  }
  .terminal-notice {
    flex-basis: 100%;
  }
  .terminal-leave {
    flex: none;
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
  }
  /* A prompt is aria-busy while its request is in flight (data-indicator).
     The hint fades in only if that lasts 3 s, so quick replies never flash
     it; the delay is the transition's, so no timer is needed. */
  .terminal-prompt {
    position: relative;
    min-inline-size: 0;
  }
  /* It floats just above the prompt's end, so it never takes room. */
  .terminal-sending {
    position: absolute;
    inset-block-end: 100%;
    inset-inline-end: 0;
    opacity: 0;
    pointer-events: none;
  }
  .terminal-prompt[aria-busy="true"] .terminal-sending {
    opacity: 1;
    transition: opacity 200ms 3s;
  }
  /* Where the rail drops below the content, #lobby takes the whole screen:
     the sheet grows to full height, and the landing's inline terminal
     becomes an overlay while the page behind holds still. */
  @media (max-width: 59.99rem) {
    .terminal-sheet:has(#terminal-prompt[data-chat]) {
      block-size: 100dvh;
    }
    [data-page="home"] .terminal:has(#terminal-prompt[data-chat]) {
      position: fixed;
      inset: 0;
      z-index: 20;
      padding-block: max(1.25rem, env(safe-area-inset-top)) max(1.25rem, env(safe-area-inset-bottom));
      padding-inline: var(--pad-inline);
    }
    html:has([data-page="home"] #terminal-prompt[data-chat]) {
      overflow: hidden;
    }
  }
  .terminal-prompt {
    flex: 1;
    display: flex;
    align-items: baseline;
    gap: 1ch;
    padding-block-start: 0.35rem;
  }
  .terminal-prompt input {
    flex: 1;
    min-inline-size: 0;
    padding: 0;
    border: 0;
    outline: 0;
    background: transparent;
    color: inherit;
  }
  .terminal-echo .terminal-ps1 {
    margin-inline-end: 1ch;
  }
  .terminal-list {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.1rem 2ch;
    margin: 0.2rem 0 0.4rem;
    padding: 0;
    list-style: none;
  }
  .terminal-list dd {
    margin: 0;
  }
  .terminal-posts {
    grid-template-columns: 1fr;
  }

  /* Guestbook: the sign form, then entries newest first. */
  .guestbook-sign {
    display: grid;
    gap: 0.35rem;
    max-inline-size: 32rem;
    margin-block-end: clamp(2.5rem, 7vw, 4rem);
  }
  .guestbook-sign label:not(:first-child) {
    margin-block-start: 0.75rem;
  }
  .guestbook-sign :is(input, textarea) {
    inline-size: 100%;
    padding: 0.55rem 0.7rem;
  }
  .guestbook-sign textarea {
    resize: vertical;
  }
  .guestbook-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 1rem;
    margin-block-start: 1rem;
  }
  .guestbook-actions button {
    padding: 0.5rem 1.1rem;
  }
  .guestbook-entries {
    display: grid;
    gap: 2rem;
    padding: 0;
    list-style: none;
  }
  .guestbook-message {
    white-space: pre-line;
    overflow-wrap: anywhere;
  }
  .guestbook-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0 1ch;
    margin-block-start: 0.4rem;
  }

  /* Off the landing page the terminal waits behind a trigger at the end of
     the breadcrumb row, and opens in a sheet pinned to the bottom. */
  /* A long breadcrumb wraps beside the trigger, which keeps to the first
     line. */
  .column-head {
    display: flex;
    align-items: baseline;
    gap: 1.5rem;
  }
  .column-head .breadcrumb {
    flex: 1;
    min-inline-size: 0;
  }
  .terminal-trigger {
    display: flex;
    align-items: baseline;
    flex: none;
    gap: 1.25ch;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    cursor: pointer;
  }
  /* The dialog has no padding, so a click on the dialog element itself
     can only be on its backdrop; the terminal inside carries the spacing. */
  .terminal-sheet {
    position: fixed;
    inset: auto 0 0;
    inline-size: 100%;
    max-inline-size: none;
    block-size: min(60dvh, 28rem);
    max-block-size: none;
    margin: 0;
    padding: 0;
    border: 0;
  }
  .terminal-sheet[open] {
    display: flex;
  }
  .terminal-sheet .terminal {
    flex: 1;
    padding-block: 1.25rem max(1.25rem, env(safe-area-inset-bottom));
    padding-inline: var(--pad-inline);
  }
  .terminal-hint {
    flex: none;
  }
  @media (hover: none) and (pointer: coarse) {
    .terminal-hint {
      display: none;
    }
  }
  @media (prefers-reduced-motion: no-preference) {
    .terminal-sheet,
    .terminal-sheet::backdrop {
      transition:
        translate 320ms var(--ease-out),
        opacity 320ms var(--ease-out),
        overlay 320ms allow-discrete,
        display 320ms allow-discrete;
    }
    .terminal-sheet:not([open]) {
      translate: 0 100%;
    }
    .terminal-sheet:not([open])::backdrop {
      opacity: 0;
    }
    @starting-style {
      .terminal-sheet[open] {
        translate: 0 100%;
      }
      .terminal-sheet[open]::backdrop {
        opacity: 0;
      }
    }
  }

  /* Entries are separated by space alone; the garden decides how a
     hovered entry announces itself. */
  .post-list {
    display: grid;
    gap: 0.25rem;
    padding: 0;
    list-style: none;
  }
  .post-entry {
    position: relative;
    display: grid;
    gap: 0.2rem;
    padding-block: 0.8rem;
  }
  .post-entry h2 {
    font-size: 1.1rem;
  }
  /* The title link covers the whole entry. */
  .post-entry h2 a::after {
    content: "";
    position: absolute;
    inset: 0;
  }

  .callout {
    padding: 1rem 1.25rem;
  }
  .callout > * + * {
    margin-block-start: 0.6em;
  }

  /* Footnote previews render in the top layer; script sets left/top. */
  .footnote-preview {
    position: fixed;
    inset: auto;
    margin: 0;
    max-inline-size: min(24rem, 100vw - 1rem);
    padding: 0.8rem 1rem;
  }
  .footnote-preview > * + * {
    margin-block-start: 0.5em;
  }

  .message-page {
    min-block-size: 45vh;
    display: grid;
    align-content: center;
    justify-items: start;
    gap: 1rem;
  }
}
