/*
 * ds.css — typography, spacing, and layout
 * One file, no build step. Source: bridger.to (look) + craft-ds.com (structure).
 *
 * This file only sets type, space, and page layout. It has no buttons, forms,
 * cards, or other components. Build those on top using the same tokens.
 *
 * Every class has one behavior. There are no size variants to choose from:
 * spacing, line height, and columns adapt to the content and the space
 * available. Your job is to pick the right element and class for the
 * content; the file decides how it looks.
 *
 * Everything is prefixed ds- (classes) or --ds- (variables), and lives in
 * @layer ds, so any CSS of your own overrides it without specificity fights.
 * With Tailwind v4, put this line first so Tailwind's utilities still win:
 *   @layer ds, theme, base, components, utilities;
 *
 * ───────────────────────────────────────────────────────────────────────────
 * RULES FOR AGENTS — read before writing markup
 * ───────────────────────────────────────────────────────────────────────────
 *
 * 1. One size. All text is 16px except small meta (.ds-small, 14px) and
 *    group labels (.ds-label, 12px). Show hierarchy with color (.ds-muted),
 *    weight (.ds-medium), order, and space, never with bigger text.
 *    Headings (h1–h6) are for meaning; they look like body text.
 *
 * 2. Line spacing follows the element, so never set it yourself:
 *      single lines (names, links, labels, headings)   20px
 *      <p> paragraphs                                    24px
 *      everything inside .ds-prose                       28px
 *
 * 3. One width. Every page uses .ds-container: 512px of content (about
 *    70 characters per line), centered. Headers, grids, and text share its
 *    edges. There is no wider option.
 *
 * 4. Page skeleton. Every page is built the same way:
 *
 *      <body>
 *        <main class="ds-main">
 *          <header class="ds-section">
 *            <div class="ds-container">
 *              <div class="ds-masthead">
 *                <a class="ds-primary" href="/">Name</a>
 *                <span class="ds-muted">Role</span>
 *                <h1 class="ds-crumb"><span class="ds-muted">/</span><a href="/work">Work</a></h1>
 *              </div>
 *            </div>
 *          </header>
 *          <section class="ds-section">
 *            <div class="ds-container"> …blocks… </div>
 *          </section>
 *        </main>
 *      </body>
 *
 *    Leave out .ds-crumb on the home page.
 *
 * 5. Blocks. Spacing is automatic:
 *      .ds-container  page blocks, 32px apart
 *      .ds-stack      items, 12px apart. 24px apart when the items are
 *                     groups themselves (stacks, asides, grids). A
 *                     .ds-label as the first child sits 8px above the rest.
 *      .ds-row        one line, items pushed to both ends (title + date)
 *      .ds-cluster    a wrapping line of items, like links
 *      .ds-aside      a label or date beside its content; the label moves
 *                     on top when there isn't room. Exactly two children:
 *                     the label, then one element holding the content.
 *      .ds-grid       two equal columns, or one on phones. For several
 *                     items of one kind. Columns keep the same width
 *                     whatever the number of items.
 *      .ds-prose      markdown, CMS, or article HTML. Not for UI.
 *
 *      <div class="ds-stack">
 *        <h2 class="ds-label">Work</h2>
 *        <div class="ds-row"><a href="/x">Project</a><span class="ds-small ds-muted">2026</span></div>
 *        <div class="ds-row"><a href="/y">Project</a><span class="ds-small ds-muted">2025</span></div>
 *      </div>
 *
 *      <div class="ds-aside">
 *        <span class="ds-small ds-muted">2026</span>
 *        <div class="ds-stack"><a href="/x">Project</a><p>What it was.</p></div>
 *      </div>
 *
 * 6. Text classes: .ds-muted .ds-primary .ds-medium .ds-small .ds-label
 *    .ds-link .ds-truncate .ds-sr-only. Headings balance their lines and
 *    paragraphs avoid a lone last word automatically.
 *
 * 7. 4px grid. All vertical spacing, line heights, and fixed sizes are
 *    multiples of 4px, so every block starts on a 4px line. Widths that
 *    stretch with the screen (the column on phones, grid columns) are
 *    fluid and aren't. If you add CSS, use the --ds-space-* and
 *    --ds-leading-* tokens, and take --ds-hairline out of the padding next
 *    to any border you add. Images are the one vertical exception: they
 *    scale with the column, so their height can land between lines.
 *
 * 8. Color. Text colors only: --ds-foreground, --ds-muted-foreground, and
 *    one accent (--ds-primary) for links and the site name. Never write a
 *    raw color; use var(--ds-*). Dark mode follows the OS. Put
 *    data-theme="dark" or data-theme="light" on <html> to force one.
 * ───────────────────────────────────────────────────────────────────────────
 */

@layer ds.base, ds.layout, ds.prose, ds.text;

/* ── 1. Tokens ─────────────────────────────────────────────────────────── */

@layer ds.base {
  :root {
    /*
     * System fonts only: nothing downloads, so text never shifts on load.
     * Mac/iOS: Helvetica Neue. Windows: Segoe UI. Linux: Nimbus Sans (a
     * Helvetica clone), then Liberation Sans. Android: Roboto via sans-serif.
     */
    --ds-font-sans: "Helvetica Neue", Helvetica, "Segoe UI", "Nimbus Sans",
      "Liberation Sans", Arial, sans-serif, "Apple Color Emoji",
      "Segoe UI Emoji";
    --ds-font-mono: ui-monospace, "SF Mono", Menlo, Consolas,
      "DejaVu Sans Mono", "Liberation Mono", monospace;

    /*
     * 4px grid. Every size that takes up space is a multiple of 4px. Line
     * heights are fixed lengths, not ratios, so lines are 16–28px tall.
     */
    --ds-text-base: 1rem; /* 16px — nearly everything */
    --ds-text-sm: 0.875rem; /* 14px — dates, meta */
    --ds-text-xs: 0.75rem; /* 12px — group labels */

    --ds-leading-xs: 1rem; /* 16px — 12px labels */
    --ds-leading-tight: 1.25rem; /* 20px — single lines, 14px text */
    --ds-leading-normal: 1.5rem; /* 24px — paragraphs */
    --ds-leading-prose: 1.75rem; /* 28px — long-form reading */
    --ds-tracking-tight: -0.025em; /* headings in prose */

    --ds-weight-medium: 500;
    --ds-weight-semibold: 600;

    /* Space: multiples of 4px */
    --ds-space-1: 0.25rem; /* 4 */
    --ds-space-2: 0.5rem; /* 8 */
    --ds-space-3: 0.75rem; /* 12 */
    --ds-space-4: 1rem; /* 16 */
    --ds-space-6: 1.5rem; /* 24 */
    --ds-space-8: 2rem; /* 32 */
    --ds-space-12: 3rem; /* 48 */
    --ds-space-18: 4.5rem; /* 72 */
    --ds-hairline: 1px; /* borders; subtract from padding to stay on grid */

    /* Layout. The column is the text width plus its padding, so text,
       headers, and grids share the same edges on every page. */
    --ds-prose: 32rem; /* 512px — about 70 characters per line */
    --ds-container: calc(var(--ds-prose) + 2 * var(--ds-container-pad));
    --ds-gutter: var(--ds-space-6); /* 24px between columns */
    --ds-grid-min: 12rem; /* 192px — narrowest grid column */
    --ds-aside-label: 8rem; /* 128px — label column beside content */
    --ds-radius: 0.25rem; /* 4px — code blocks, images in prose */

    /* Page padding (phones; larger screens below) */
    --ds-main-pad: var(--ds-space-12);
    --ds-section-pad: var(--ds-space-2);
    --ds-container-pad: var(--ds-space-4);

    /*
     * Color: light-dark(<light>, <dark>). Tints (link underlines, code
     * fills, bullets) are mixed from these where they're used, so
     * overriding a color on any element updates its tints too.
     */
    --ds-background: light-dark(#ffffff, #0c0a09);
    --ds-foreground: light-dark(#0c0a09, #fafaf9);
    --ds-muted: light-dark(#f5f5f4, #292524); /* code fills */
    --ds-muted-foreground: light-dark(#79716b, #a6a09b); /* secondary text */
    --ds-border: light-dark(#e7e5e4, rgb(255 255 255 / 0.1)); /* hairlines */
    --ds-primary: light-dark(#ff5c00, #ff5c00); /* links, site name */

    color-scheme: light dark;
  }

  /* The only screen-size breakpoint: more page padding from 640px up.
     Grids and asides respond to the space they're in instead (below). */
  @media (min-width: 640px) {
    :root {
      --ds-main-pad: var(--ds-space-18);
      --ds-section-pad: var(--ds-space-4);
      --ds-container-pad: var(--ds-space-6);
    }
  }

  /* Browsers without light-dark() (before mid-2024): light colors only */
  @supports not (color: light-dark(#000, #fff)) {
    :root {
      --ds-background: #ffffff;
      --ds-foreground: #0c0a09;
      --ds-muted: #f5f5f4;
      --ds-muted-foreground: #79716b;
      --ds-border: #e7e5e4;
      --ds-primary: #ff5c00;
    }
  }

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

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

  /* ── 2. Base ─────────────────────────────────────────────────────────── */

  *,
  *::before,
  *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    border: 0 solid var(--ds-border);
  }

  html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  }

  body {
    min-height: 100dvh;
    background: var(--ds-background);
    color: var(--ds-foreground);
    font-family: var(--ds-font-sans);
    font-size: var(--ds-text-base);
    line-height: var(--ds-leading-tight);
  }

  /* Headings are for meaning, not size. They look like body text and
     split their lines evenly. */
  h1,
  h2,
  h3,
  h4,
  h5,
  h6 {
    font-size: inherit;
    font-weight: inherit;
    text-wrap: balance;
  }

  /* Paragraphs wrap, so they get reading line spacing and avoid a lone
     word on the last line. */
  p {
    line-height: var(--ds-leading-normal);
    text-wrap: pretty;
  }

  a {
    color: inherit;
    text-decoration: inherit;
  }

  /* Images and video fill their box. Inline SVG icons stay inline. */
  img,
  video {
    display: block;
    max-width: 100%;
    height: auto;
  }

  ol,
  ul {
    list-style: none;
  }

  button,
  input,
  select,
  textarea {
    font: inherit;
    color: inherit;
  }

  :focus-visible {
    outline: 2px solid var(--ds-foreground);
    outline-offset: 2px;
  }
}

/* ── 3. Layout ─────────────────────────────────────────────────────────── */

@layer ds.layout {
  .ds-main {
    padding: var(--ds-main-pad) var(--ds-space-2);
  }

  .ds-section {
    padding-block: var(--ds-section-pad);
  }

  /* The page column. Blocks inside are 32px apart. */
  .ds-container {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-8);
    width: 100%;
    max-width: var(--ds-container);
    margin-inline: auto;
    padding: var(--ds-container-pad);
  }

  /* Items 12px apart. Groups of groups get 24px. */
  .ds-stack {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-3);
  }
  .ds-stack:has(> :is(.ds-stack, .ds-aside, .ds-grid)) {
    gap: var(--ds-space-6);
  }
  /* A label leads its group: 8px below it instead of the stack's gap */
  .ds-stack > .ds-label:first-child + * {
    margin-block-start: calc(var(--ds-space-2) - var(--ds-space-3));
  }
  .ds-stack:has(> :is(.ds-stack, .ds-aside, .ds-grid))
    > .ds-label:first-child
    + * {
    margin-block-start: calc(var(--ds-space-2) - var(--ds-space-6));
  }

  /* One line, pushed to both ends. Centered, not baseline-aligned, so
     mixed text sizes keep the row on the 4px grid. The first item gives
     way when space runs out. */
  .ds-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--ds-space-2);
  }
  .ds-row > :first-child {
    min-width: 0;
  }
  .ds-row > :not(:first-child) {
    flex-shrink: 0;
  }

  /* A wrapping line of items */
  .ds-cluster {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--ds-space-2) var(--ds-space-3);
  }

  /* Equal columns, as many as fit at --ds-grid-min or wider: two in the
     page column, one on phones. auto-fill keeps empty columns, so one item
     is as wide as two items are. */
  .ds-grid {
    display: grid;
    grid-template-columns: repeat(
      auto-fill,
      minmax(min(var(--ds-grid-min), 100%), 1fr)
    );
    gap: var(--ds-gutter);
  }

  /* Label beside content, measured against the aside's own width, so it
     works anywhere (in a grid cell, outside the page column). The label is
     a fixed 128px; the content takes the rest. When the content would get
     less than 60% of the width, it wraps under the label. */
  .ds-aside {
    display: flex;
    flex-wrap: wrap;
    gap: var(--ds-space-2) var(--ds-gutter);
  }
  .ds-aside > :first-child {
    flex: 0 0 var(--ds-aside-label);
  }
  .ds-aside > :last-child {
    flex: 1 1 0;
    min-inline-size: 60%;
  }

  /* Page header: name at the start, role at the end, crumb below */
  .ds-masthead {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: start;
    gap: var(--ds-space-2);
  }

  .ds-masthead > .ds-crumb {
    grid-column: 1 / -1;
  }

  .ds-crumb {
    display: flex;
    align-items: center;
    gap: var(--ds-space-2);
  }
}

/* ── 4. Prose ──────────────────────────────────────────────────────────── */
/* For markdown, CMS, or article HTML. Every selector is wrapped in        */
/* :where(), so a plain element or class selector of yours overrides it.   */
/*                                                                         */
/* Spacing is "flow": each block gets space above it based on what it      */
/* follows, so the gap between any two elements is the same everywhere.    */

@layer ds.prose {
  .ds-prose {
    min-width: 0;
    max-width: var(--ds-prose);
    line-height: var(--ds-leading-prose);
  }

  /* Flow: 16px between blocks */
  :where(.ds-prose, .ds-prose blockquote, .ds-prose details) > * + * {
    margin-block-start: var(--ds-space-4);
  }
  :where(.ds-prose :is(li, figure, dd)) > * + * {
    margin-block-start: var(--ds-space-2);
  }

  /* Headings: more room above than below, so each groups with what follows */
  :where(.ds-prose) :where(h1, h2, h3, h4, h5, h6) {
    letter-spacing: var(--ds-tracking-tight);
    text-wrap: balance;
    scroll-margin-block-start: var(--ds-space-8);
  }
  :where(.ds-prose) :where(h1, h2, h3) {
    font-weight: var(--ds-weight-medium);
  }
  :where(.ds-prose) > * + :where(h1, h2) {
    margin-block-start: var(--ds-space-8);
  }
  :where(.ds-prose) > * + :where(h3, h4, h5) {
    margin-block-start: var(--ds-space-6);
  }
  :where(.ds-prose) > :where(h1, h2) + * {
    margin-block-start: var(--ds-space-2);
  }
  :where(.ds-prose) > :where(h3, h4, h5, h6) + * {
    margin-block-start: var(--ds-space-1);
  }

  /* Inline text */
  :where(.ds-prose) :where(p) {
    line-height: var(--ds-leading-prose);
    overflow-wrap: break-word;
  }
  :where(.ds-prose) :where(strong, b) {
    font-weight: var(--ds-weight-semibold);
  }
  /* Smaller inline text never makes its line taller */
  :where(.ds-prose) :where(small, sub, sup, code, kbd, samp) {
    line-height: 0;
  }
  :where(.ds-prose) :where(small, sub, sup) {
    font-size: var(--ds-text-sm);
  }
  /* Raise and lower with position, which never changes line height */
  :where(.ds-prose) :where(sub, sup) {
    position: relative;
    vertical-align: baseline;
  }
  :where(.ds-prose) :where(sup) {
    inset-block-start: -0.5em;
  }
  :where(.ds-prose) :where(sub) {
    inset-block-start: 0.25em;
  }
  :where(.ds-prose) :where(a) {
    color: var(--ds-primary);
    text-decoration: underline;
    text-decoration-color: color-mix(in oklab, currentColor 50%, transparent);
    text-underline-offset: 0.1em;
  }
  :where(.ds-prose) :where(a):hover {
    text-decoration-color: currentColor;
  }
  :where(.ds-prose) :where(h1, h2, h3, h4, h5, h6) :where(a) {
    color: inherit;
    text-decoration: none;
  }

  /* Lists */
  :where(.ds-prose) :where(li + li) {
    margin-block-start: var(--ds-space-1);
  }
  :where(.ds-prose) :where(li) > :where(ul, ol) {
    margin-block-start: var(--ds-space-2);
  }
  :where(.ds-prose) :where(ul > li) {
    position: relative;
    padding-inline-start: var(--ds-space-6);
  }
  /* 4px dot, centered on the first 28px line */
  :where(.ds-prose) :where(ul > li)::before {
    content: "";
    position: absolute;
    inset-inline-start: var(--ds-space-2);
    inset-block-start: var(--ds-space-3);
    width: var(--ds-space-1);
    height: var(--ds-space-1);
    border-radius: 50%;
    background: var(--ds-bullet, color-mix(in oklab, currentColor 80%, transparent));
  }
  :where(.ds-prose) :where(ul ul > li)::before {
    opacity: 0.75;
  }
  :where(.ds-prose) :where(ul ul ul > li)::before {
    opacity: 0.5;
  }
  :where(.ds-prose) :where(ol) {
    list-style: decimal;
    padding-inline-start: var(--ds-space-6);
  }
  :where(.ds-prose) :where(ol > li) {
    padding-inline-start: var(--ds-space-2);
  }
  :where(.ds-prose) :where(ol ol) {
    list-style: lower-alpha;
  }
  :where(.ds-prose) :where(ol ol ol) {
    list-style: lower-roman;
  }
  :where(.ds-prose) :where(li)::marker {
    color: var(--ds-bullet, color-mix(in oklab, currentColor 80%, transparent));
  }
  :where(.ds-prose) :where(dt) {
    font-weight: var(--ds-weight-medium);
  }
  :where(.ds-prose) :where(dd + dt) {
    margin-block-start: var(--ds-space-3);
  }
  :where(.ds-prose) :where(dd) {
    color: var(--ds-muted-foreground);
  }

  /* Code: plain inline, filled block */
  :where(.ds-prose) :where(code, kbd, samp) {
    font-family: var(--ds-font-mono);
    font-size: 0.9375em;
  }
  /* Long lines wrap: a scrollbar would add height off the grid */
  :where(.ds-prose) :where(pre) {
    padding: var(--ds-space-4);
    border-radius: var(--ds-radius);
    background: var(--ds-surface, color-mix(in oklab, var(--ds-muted) 50%, transparent));
    font-family: var(--ds-font-mono);
    font-size: var(--ds-text-sm);
    line-height: var(--ds-leading-tight);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
  }
  :where(.ds-prose) :where(pre code) {
    font-size: inherit;
    line-height: inherit;
  }

  /* Tables: each row is 8 + 28 + 7 + 1 (hairline) = 44px. Separate borders,
     because collapsed borders split into half pixels. */
  :where(.ds-prose) :where(table) {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
  }
  :where(.ds-prose) :where(th, td) {
    padding-block: var(--ds-space-2) calc(var(--ds-space-2) - var(--ds-hairline));
    padding-inline: 0 var(--ds-space-3);
    border-block-end: var(--ds-hairline) solid var(--ds-border);
    text-align: start;
    vertical-align: top;
  }
  :where(.ds-prose) :where(th) {
    font-weight: var(--ds-weight-medium);
  }

  /* Media */
  :where(.ds-prose) :where(img, video) {
    border-radius: var(--ds-radius);
  }
  :where(.ds-prose) :where(figcaption) {
    color: var(--ds-muted-foreground);
    font-size: var(--ds-text-sm);
    line-height: var(--ds-leading-tight);
  }

  /* Quotes: 2px rule + 14px padding puts the text 16px in */
  :where(.ds-prose) :where(blockquote) {
    padding-inline-start: calc(var(--ds-space-4) - 2 * var(--ds-hairline));
    border-inline-start: calc(2 * var(--ds-hairline)) solid var(--ds-border);
    color: var(--ds-muted-foreground);
  }

  /* Divider: hairline with 32px above; the block after it gets 32px minus
     the hairline, so it lands back on the grid */
  :where(.ds-prose) > * + :where(hr) {
    margin-block-start: var(--ds-space-8);
  }
  :where(.ds-prose) > :where(hr) + * {
    margin-block-start: calc(var(--ds-space-8) - var(--ds-hairline));
  }
  :where(.ds-prose) :where(hr) {
    border-block-start: var(--ds-hairline) solid
      var(--ds-rule, color-mix(in oklab, var(--ds-border) 50%, transparent));
  }
}

/* ── 5. Text ───────────────────────────────────────────────────────────── */
/* Last layer, so a text class always wins over prose and layout styles.   */

@layer ds.text {
  /* Small label that leads a group, e.g. "Work" over a list */
  .ds-label {
    color: var(--ds-muted-foreground);
    font-size: var(--ds-text-xs);
    line-height: var(--ds-leading-xs);
  }

  /* Dates and meta */
  .ds-small {
    font-size: var(--ds-text-sm);
    line-height: var(--ds-leading-tight);
  }

  .ds-muted {
    color: var(--ds-muted-foreground);
  }
  .ds-primary {
    color: var(--ds-primary);
  }
  .ds-medium {
    font-weight: var(--ds-weight-medium);
  }

  .ds-link {
    color: var(--ds-primary);
    text-decoration: underline;
    text-decoration-color: color-mix(in oklab, currentColor 50%, transparent);
    text-underline-offset: 0.1em;
  }
  .ds-link:hover {
    text-decoration-color: currentColor;
  }

  .ds-truncate {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .ds-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}
