/* ============================================================================
   CrystalDocs - Mineral design system

   Ported from CrystalShards. Art direction is taken from the subject: shards
   are broken crystal, so the surfaces are cut rather than rounded, edges are
   hairlines, and the accent is sampled from the specimen photography rather
   than an arbitrary brand colour. Type is the IBM Plex superfamily: Condensed
   carries the vertical, cut feel in headings, Sans does the reading, Mono
   carries versions and counts because they are data, not prose.

   The accent here is quartz: the clear, cool specimen. Light is the default
   because this is a long-form reading surface. Dark is the same system with
   the polarity inverted, honoured automatically via prefers-color-scheme.

   Accessibility decisions that are load bearing, do not undo them casually:
   - --focus is 2px at >= 3:1 against both grounds. Never set outline:none
     without an equivalent replacement.
   - --edge-strong is the border for anything whose boundary is the only
     indicator of a control. Plain --edge is decorative and deliberately faint.
   - Interactive targets get --tap (24px) minimum height.
   - Links inside prose are underlined; the accent alone does not carry meaning.
   ========================================================================= */

:root {
  /* ---- ground and ink -------------------------------------------------- */
  --paper:        #f4f6f6;
  --surface:      #ffffff;
  --surface-sunk: #eef1f1;
  --ink:          #0e1416;   /* 17.13:1 on --paper */
  --ink-muted:    #55666b;   /* 6.05:1 on --surface */
  --ink-faint:    #5f7075;   /* 5.17:1 on --surface, 4.77:1 on --paper */

  /* ---- edges ----------------------------------------------------------- */
  --edge:         #d8dfe0;   /* decorative hairline */
  /* Was #98a7aa, which the comment claimed was 3.02:1 but actually measures
     2.49:1 on --surface and 2.29:1 on --paper. Darkened until the token is
     true to its stated job: 3.43:1 on --surface, 3.16:1 on --paper,
     3.02:1 on --surface-sunk. */
  --edge-strong:  #7b8e92;

  /* ---- accent, sampled from the specimen ------------------------------- */
  --accent:       #1d5c78;   /* 7.35:1 on --surface */
  --accent-deep:  #123c4e;
  --accent-wash:  #e2eef3;
  --on-accent:    #ffffff;   /* 7.35:1 on --accent */

  /* ---- status ---------------------------------------------------------- */
  --ok:           #1e7e34;   /* 5.14:1 with white */
  --warn:         #a15c00;   /* 5.20:1 with white */
  --danger:       #b3261e;   /* 5.94:1 with white */
  --ore:          #8a5a2b;   /* metadata icon tint, 5.28:1 on --surface */

  /* ---- focus ----------------------------------------------------------- */
  --focus:        #1d5c78;
  --focus-ring:   0 0 0 2px var(--surface), 0 0 0 4px var(--focus);

  /* ---- type ------------------------------------------------------------ */
  --sans: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --cond: "IBM Plex Sans Condensed", var(--sans);
  --mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;

  --step--1: 0.8125rem;
  --step-0:  1rem;
  --step-1:  1.125rem;
  --step-2:  clamp(1.25rem, 1.1rem + 0.6vw, 1.5rem);
  --step-3:  clamp(1.5rem, 1.3rem + 1vw, 1.9rem);
  --step-4:  clamp(2.1rem, 1.6rem + 2.4vw, 3.1rem);
  --step-5:  clamp(2.6rem, 1.8rem + 3.6vw, 3.9rem);

  /* ---- space and geometry ---------------------------------------------- */
  --gap:   1.125rem;
  --pad:   1.375rem;
  --bay:   4rem;
  --shell: 74rem;
  --facet: 14px;   /* the cut corner */
  --tap:   24px;   /* WCAG 2.2 target minimum */
}

@media (prefers-color-scheme: dark) {
  :root {
    --paper:        #070a0b;
    --surface:      #101718;
    --surface-sunk: #0b1112;
    --ink:          #e9f1f0;   /* 17.31:1 on --paper */
    --ink-muted:    #9fb2b3;   /* 8.24:1 on --surface */
    --ink-faint:    #83989a;   /* 5.94:1 on --surface */

    --edge:         #222e30;
    /* Was #4a5c5e, measured 2.58:1 not the 3.06:1 claimed. Now 3.38:1 on
       --surface, 3.70:1 on --paper, 3.54:1 on --surface-sunk. */
    --edge-strong:  #5b6e70;

    --accent:       #63c8e8;   /* 9.45:1 on --surface */
    --accent-deep:  #2e7a94;
    --accent-wash:  #122c36;
    --on-accent:    #04222b;   /* 8.62:1 on --accent */

    --ok:           #4ade80;
    --warn:         #f0b357;
    --danger:       #ff7b72;
    --ore:          #d9a066;

    --focus:        #63c8e8;
    --focus-ring:   0 0 0 2px var(--paper), 0 0 0 4px var(--focus);
  }
}

/* ============================================================================
   Base
   ========================================================================= */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 var(--step-0)/1.6 var(--sans);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 {
  font-family: var(--cond);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -0.015em;
  margin: 0 0 0.5em;
}
h1 { font-size: var(--step-4); }
h2 { font-size: var(--step-3); }
h3 { font-size: var(--step-2); }
h4 { font-size: var(--step-1); }

p { margin: 0 0 1em; }

a { color: var(--accent); text-underline-offset: 0.18em; }

/* Prose links carry an underline: the accent alone is not a sufficient cue. */
p a, li a, .doc-description a, .docs-content a,
.docs-doc a, .docs-member-doc a, .docs-readme a, .docs-summary a { text-decoration: underline; }

/* Navigational and component links opt out, they have their own affordances. */
.nav-link, .logo, .doc-name a, .doc-title a, .view-all-link,
.pagination-link, .footer-links a, .sidebar-links a, .breadcrumb-list a,
.docs-link, .back-link, .version-switch-link, .version-number,
.button, .search-button { text-decoration: none; }

img { max-width: 100%; height: auto; display: block; }

code, kbd, samp { font-family: var(--mono); font-size: 0.92em; }

/* One focus treatment, everywhere. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: 1px;
}

::selection { background: var(--accent); color: var(--on-accent); }

/* ============================================================================
   Primitives
   ========================================================================= */

.container { max-width: var(--shell); margin: 0 auto; padding: 0 1.5rem; }

/* The cut corner. One clipped bevel, never a uniform radius. */
.facet {
  clip-path: polygon(0 0, calc(100% - var(--facet)) 0, 100% var(--facet), 100% 100%, 0 100%);
}

.skip-link {
  position: absolute;
  left: 0.5rem;
  top: -3rem;
  z-index: 100;
  padding: 0.7rem 1.1rem;
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
  transition: top 0.12s ease-out;
}
.skip-link:focus { top: 0.5rem; }

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}

.text-muted { color: var(--ink-muted); }

/* ============================================================================
   Header
   ========================================================================= */

.site-header { background: var(--surface); border-bottom: 1px solid var(--edge); }

/* Three columns rather than a flex row, so the search field is centred on the
   viewport rather than wherever the brand and menu widths happen to leave it.
   The bar is full bleed because documentation pages are: clamping it to
   --shell left the logo floating inside the page while the sidebar below
   started at the edge. */
.navbar {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 2rem;
  padding: 0 1.5rem;
  min-height: 4.125rem;
}

.navbar-brand { display: flex; align-items: center; justify-self: start; }

.logo {
  display: inline-flex; align-items: center; gap: 0.65rem;
  font: 700 1.3rem/1 var(--cond); letter-spacing: 0.005em;
  color: var(--ink);
}
/* The mark is a drawn hexagonal section, not an emoji. */
.logo::before {
  content: "";
  width: 1.32rem; height: 1.55rem;
  background: var(--accent);
  clip-path: polygon(50% 0, 100% 27%, 100% 73%, 50% 100%, 0 73%, 0 27%);
}

.navbar-menu { display: flex; flex-wrap: wrap; gap: 1.6rem; margin-left: auto; }

.nav-link {
  display: inline-flex; align-items: center;
  min-height: var(--tap);
  color: var(--ink-muted);
  font-size: 0.925rem; font-weight: 500;
  border-bottom: 2px solid transparent;
}
.nav-link:hover { color: var(--ink); border-bottom-color: var(--accent); }

/* ============================================================================
   Intro band

   Not a hero. A documentation host earns its keep by getting you to the docs
   for the version you run, so this stays one screen-inch tall and the lists
   start near the top. Search is in the masthead, reachable from every page.
   ========================================================================= */

.intro {
  display: flex; flex-wrap: wrap; align-items: end; gap: 1.5rem 3rem;
  padding: clamp(1.75rem, 3.5vw, 2.75rem) 0 clamp(1.25rem, 2.5vw, 1.75rem);
  border-bottom: 1px solid var(--edge);
}
.intro-copy { flex: 1 1 28rem; min-width: 0; }

.intro-title {
  font-size: var(--step-4); margin: 0 0 0.35em;
}
.intro-title .accent { color: var(--accent); }

.intro-lede {
  font-size: var(--step-1); color: var(--ink-muted);
  max-width: 54ch; margin: 0;
}

.intro-stats { display: flex; gap: 2.5rem; margin: 0; }
.intro-stats div { display: flex; flex-direction: column-reverse; }
.intro-stats dt {
  font: 400 0.7rem/1.4 var(--mono);
  letter-spacing: 0.13em; text-transform: uppercase; color: var(--ink-muted);
}
.intro-stats dd {
  margin: 0; font: 700 var(--step-3)/1 var(--cond); color: var(--ink);
}

/* ---- search ------------------------------------------------------------ */

.search-form { display: flex; max-width: 34rem; border: 1px solid var(--ink); background: var(--surface); }
.search-form:focus-within { outline: 2px solid var(--focus); outline-offset: 2px; }
.search-input {
  flex: 1; min-width: 0;
  border: 0; background: none; color: var(--ink);
  padding: 0.85rem 1rem; font: 400 1rem var(--sans);
}
.search-input::placeholder { color: var(--ink-faint); }
.search-input:focus { outline: none; }
.search-button {
  border: 0; cursor: pointer;
  background: var(--ink); color: var(--paper);
  font: 600 0.9rem var(--sans); padding: 0 1.5rem;
  min-height: var(--tap);
}
.search-button:hover { background: var(--accent); color: var(--on-accent); }

/* In the masthead the field flexes with the bar and the button is icon-only. */
.search-form-compact { flex: 1 1 20rem; max-width: 32rem; margin-right: 0.5rem; }
.search-form-compact .search-button { padding: 0 0.85rem; }

/* ---- reading recipes ---------------------------------------------------- */

.recipe-grid {
  display: grid; gap: var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
}
.recipe {
  --facet: 14px;
  background: var(--surface); border: 1px solid var(--edge);
  padding: 1.25rem 1.35rem 1.35rem;
  clip-path: polygon(0 0, calc(100% - var(--facet)) 0, 100% var(--facet), 100% 100%, 0 100%);
}
.recipe h3 { font-size: var(--step-1); margin: 0 0 0.3em; }
.recipe p { color: var(--ink-muted); font-size: 0.9rem; margin: 0 0 0.85rem; }
.recipe .code-block { margin: 0; }
.recipe .code-block pre { white-space: pre-wrap; overflow-wrap: anywhere; }

.code-block {
  background: var(--surface-sunk); border: 1px solid var(--edge);
  padding: 0.9rem 1rem; margin: 0.85rem 0; overflow-x: auto;
}
.code-block pre { margin: 0; font: 400 0.85rem/1.5 var(--mono); }

/* ---- stat rail --------------------------------------------------------- */

.stat-item {
  flex: 1 1 9rem; padding: 1.15rem 1.5rem;
  border-right: 1px solid var(--edge);
}
.stat-item:last-child { border-right: 0; }
.stat-item strong,
.stat-item .stat-value {
  display: block; font: 700 var(--step-3)/1 var(--cond); color: var(--ink);
}
.stat-item span {
  font: 400 0.7rem/1.4 var(--mono);
  letter-spacing: 0.13em; text-transform: uppercase; color: var(--ink-muted);
}

/* ============================================================================
   Sections
   ========================================================================= */

.section { padding: var(--bay) 0; }

.page-header { padding: clamp(2rem, 4vw, 3rem) 0 0; }
.page-header h1 { margin-bottom: 0.25em; }
.page-header p { color: var(--ink-muted); font-size: var(--step-1); }

.section-head {
  display: flex; align-items: baseline; gap: 1rem;
  margin-bottom: 1.5rem;
}
.section-head h2 { margin: 0; }
.section-head .rule { flex: 1; height: 1px; background: var(--edge); }

.view-all-link {
  display: inline-flex; align-items: center; gap: 0.4rem;
  min-height: var(--tap);
  color: var(--accent); font-weight: 500; font-size: 0.95rem;
}
.view-all-link:hover { text-decoration: underline; }

.section-footer { margin-top: 2rem; }

/* ============================================================================
   Doc cards
   ========================================================================= */

.doc-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
  gap: var(--gap);
}
.doc-list { display: grid; gap: var(--gap); }

.doc-card {
  --facet: 16px;
  position: relative;
  background: var(--surface);
  border: 1px solid var(--edge);
  padding: var(--pad) var(--pad) 1.1rem;
  clip-path: polygon(0 0, calc(100% - var(--facet)) 0, 100% var(--facet), 100% 100%, 0 100%);
  transition: border-color 0.14s ease, background 0.14s ease;
}
/* The whole card is the target, so the whole card reacts, and it reacts the
   same way to a pointer as it does to keyboard focus landing inside it. */
.doc-card:hover, .doc-card:focus-within { border-color: var(--accent); }
/* The cut corner catches light, the way a real bevel would. Decorative only:
   without pointer-events:none it would swallow clicks in the top-right
   corner, which is now part of the card's click target. */
.doc-card::after {
  content: "";
  position: absolute; top: 0; right: 0;
  width: calc(var(--facet) + 8px); height: calc(var(--facet) + 8px);
  background: linear-gradient(225deg, var(--accent) 0 50%, transparent 50%);
  opacity: 0.28; transition: opacity 0.14s ease;
  pointer-events: none;
}
.doc-card:hover::after, .doc-card:focus-within::after { opacity: 0.9; }

.doc-card-header { display: flex; align-items: baseline; flex-wrap: wrap; gap: 0.55rem; }

.doc-name {
  font: 600 var(--step-1)/1.25 var(--sans);
  letter-spacing: 0; margin: 0;
}
.doc-name a { color: var(--ink); }
.doc-name a:hover { color: var(--accent); }

/* ---- whole-card click target ------------------------------------------- */

/* The title link stretches over the card with a pseudo-element instead of the
   card being wrapped in an anchor. That keeps one tab stop per card and keeps
   the link's accessible name the package name rather than the whole card
   body. Known tradeoff: the overlay sits over the description and meta text,
   so drag-selecting that text is harder than it was. */
.doc-name a::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
}

/* Every other link in the card rides above the overlay so it keeps its own
   hit area, hover and focus. The title link is the exception: it has to stay
   unpositioned or its ::after would resolve against the title text instead of
   the card, covering only the title. */
.doc-card a { position: relative; z-index: 1; }
.doc-card .doc-name a { position: static; }

/* Not outline:none without a replacement. The outline moves from the anchor,
   which is only as wide as the title, onto the overlay, which is the card, so
   the ring is drawn where the click target actually is. Offset inward because
   the card is clip-path'd and an outward ring would be cut away, the same
   reason .docs-nav-link uses a negative offset. Still 2px var(--focus). */
.doc-name a:focus-visible { outline: none; }
.doc-name a:focus-visible::after {
  outline: 2px solid var(--focus);
  outline-offset: -2px;
}

.doc-description {
  color: var(--ink-muted); font-size: 0.9rem; line-height: 1.55;
  margin: 0.55rem 0 1.1rem;
}

.doc-meta {
  display: flex; align-items: center; flex-wrap: wrap; gap: 1rem;
  border-top: 1px solid var(--edge); padding-top: 0.75rem;
  font: 400 0.78rem/1 var(--mono); color: var(--ink-muted);
}
.doc-meta .icon { color: var(--ore); margin-right: 0.35rem; }

/* ---- version pill ------------------------------------------------------ */

.doc-version, .version-number {
  display: inline-block;
  font: 500 0.72rem/1.55 var(--mono);
  padding: 0.05rem 0.45rem;
  color: var(--accent); background: var(--accent-wash);
  border: 1px solid transparent;
}
a.version-number:hover { text-decoration: underline; }

/* ============================================================================
   Doc detail (package page)
   ========================================================================= */

.doc-header { padding: clamp(1.75rem, 3vw, 2.5rem) 0 0; }
.doc-title-block { display: flex; align-items: baseline; flex-wrap: wrap; gap: 0.75rem; }
.doc-title { font-size: var(--step-4); margin: 0; }
.doc-description-large { font-size: var(--step-1); color: var(--ink-muted); max-width: 62ch; }

.doc-stats-block {
  display: flex; flex-wrap: wrap; gap: 1.5rem;
  font: 400 0.85rem/1 var(--mono); color: var(--ink-muted);
  padding: 0.9rem 0 0;
}
.doc-stats-block .stat-item { display: inline-flex; align-items: baseline; gap: 0.35rem; }
.doc-stats-block .icon { color: var(--ore); margin-right: 0.35rem; }

.doc-section {
  --facet: 16px;
  background: var(--surface); border: 1px solid var(--edge);
  padding: clamp(1.25rem, 2.5vw, 1.9rem);
  margin: var(--gap) 0;
  clip-path: polygon(0 0, calc(100% - var(--facet)) 0, 100% var(--facet), 100% 100%, 0 100%);
}
.doc-section h2 { font-size: var(--step-2); margin-bottom: 0.75rem; }

.version-list { list-style: none; margin: 0; padding: 0; }
.version-list li {
  display: flex; align-items: center; gap: 0.6rem;
  min-height: var(--tap);
  padding: 0.4rem 0; border-bottom: 1px solid var(--edge);
  font-size: 0.9rem;
}
.version-list li:last-child { border-bottom: 0; }
.version-date { font: 400 0.75rem var(--mono); color: var(--ink-faint); margin-left: auto; }

/* ============================================================================
   Documentation viewer
   ========================================================================= */

.docs-header {
  --facet: 16px;
  background: var(--surface); border: 1px solid var(--edge);
  padding: clamp(1.25rem, 2.5vw, 1.9rem);
  margin-bottom: var(--gap);
  clip-path: polygon(0 0, calc(100% - var(--facet)) 0, 100% var(--facet), 100% 100%, 0 100%);
}

.docs-header .docs-nav {
  display: flex; justify-content: space-between; align-items: center;
  flex-wrap: wrap; gap: 0.75rem;
  margin-bottom: 1.25rem; padding-bottom: 1rem;
  border-bottom: 1px solid var(--edge);
}

.back-link {
  display: inline-flex; align-items: center; gap: 0.45rem;
  min-height: var(--tap);
  color: var(--ink); font-weight: 500; font-size: 0.95rem;
}
.back-link:hover { color: var(--accent); }

.version-switcher { display: flex; align-items: center; gap: 0.5rem; }
.version-switcher label {
  font: 500 0.7rem/1 var(--mono);
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-muted);
}
.version-switcher select {
  min-height: 2.5rem;
  border: 1px solid var(--edge-strong);
  background: var(--surface); color: var(--ink);
  padding: 0.45rem 0.6rem; font: 400 0.92rem var(--sans);
  cursor: pointer;
}

.docs-title-block h1 { font-size: var(--step-3); margin-bottom: 0.25rem; }
.version-badge {
  font: 500 0.55em/1 var(--mono);
  color: var(--accent);
  letter-spacing: 0;
}

.docs-subtitle { font-size: var(--step-1); color: var(--ink-muted); margin-bottom: 0.5rem; max-width: 62ch; }

.docs-links { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 0.75rem; }
.docs-link {
  display: inline-flex; align-items: center;
  min-height: var(--tap);
  padding: 0.35rem 0.8rem;
  background: var(--surface-sunk); border: 1px solid var(--edge);
  color: var(--ink); font-size: 0.88rem; font-weight: 500;
}
.docs-link:hover { border-color: var(--accent); color: var(--accent); }

.docs-layout {
  display: grid; grid-template-columns: 17rem minmax(0, 1fr);
  gap: clamp(1.5rem, 3vw, 2.5rem);
  align-items: start;
}

.docs-content-wrapper {
  --facet: 16px;
  background: var(--surface); border: 1px solid var(--edge);
  padding: clamp(1.25rem, 2.5vw, 1.9rem);
  clip-path: polygon(0 0, calc(100% - var(--facet)) 0, 100% var(--facet), 100% 100%, 0 100%);
}

.docs-content { font-size: 0.95rem; line-height: 1.7; min-width: 0; }
.docs-content > :first-child { margin-top: 0; }

.docs-content h1, .docs-content h2, .docs-content h3,
.docs-content h4, .docs-content h5, .docs-content h6 {
  margin: 1.6em 0 0.6em;
}
/* The content h1 is demoted to h2 at render time so the page chrome owns the
   only h1; the rule stays so stray top-level headings still read correctly. */
.docs-content h1, .docs-content h2 {
  font-size: var(--step-2);
  padding-bottom: 0.3rem;
  border-bottom: 1px solid var(--edge);
}
.docs-content h3 { font-size: var(--step-1); }
.docs-content h4 { font-size: var(--step-0); }

.docs-content p { margin: 0 0 1em; }
.docs-content ul, .docs-content ol { margin: 0 0 1em; padding-left: 1.5rem; }
.docs-content li { margin-bottom: 0.35rem; }

.docs-content table {
  width: 100%; border-collapse: collapse; margin: 0 0 1rem;
  font-size: 0.9rem;
}
.docs-content th, .docs-content td {
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--edge);
  text-align: left;
}
.docs-content th { background: var(--surface-sunk); font-weight: 600; }

.docs-content blockquote {
  margin: 1rem 0; padding: 0.75rem 1rem;
  border-left: 3px solid var(--accent);
  background: var(--surface-sunk);
  color: var(--ink-muted);
}

.docs-content pre { margin: 0.85rem 0; overflow-x: auto; }
.docs-content :not(pre) > code {
  background: var(--surface-sunk);
  border: 1px solid var(--edge);
  padding: 0.1em 0.35em;
}

.docs-content-unavailable h2 { margin-top: 0; }
.docs-content-hint { color: var(--ink-muted); font-size: 0.9rem; }

/* ============================================================================
   Sidebar
   ========================================================================= */

.doc-sidebar { min-width: 0; display: grid; gap: var(--gap); align-content: start; }

.sidebar-section {
  --facet: 12px;
  background: var(--surface); border: 1px solid var(--edge);
  padding: 1.15rem 1.25rem;
  clip-path: polygon(0 0, calc(100% - var(--facet)) 0, 100% var(--facet), 100% 100%, 0 100%);
}

.sidebar-title {
  font: 700 var(--step-1)/1.2 var(--cond);
  letter-spacing: -0.01em;
  margin-bottom: 0.4rem;
}

.sidebar-description {
  font-size: 0.85rem; color: var(--ink-muted); line-height: 1.5;
  margin-bottom: 0;
}

.sidebar-heading {
  font: 500 0.7rem/1 var(--mono);
  letter-spacing: 0.13em; text-transform: uppercase;
  color: var(--ink-muted); margin-bottom: 0.85rem;
}

.sidebar-version { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; }

.version-tag {
  display: inline-block;
  font: 500 0.78rem/1.4 var(--mono);
  padding: 0.1rem 0.5rem;
  color: var(--on-accent); background: var(--accent);
}

.version-switch-link {
  display: inline-flex; align-items: center; min-height: var(--tap);
  color: var(--accent); font-size: 0.82rem;
}
.version-switch-link:hover { text-decoration: underline; }

.sidebar-meta { font: 400 0.78rem var(--mono); color: var(--ink-faint); margin-bottom: 0.6rem; }

/* White on --ok (#1e7e34) measures 5.14:1, over the 4.5:1 needed for the
   12px badge; do not lighten the green back to #28a745 (3.13:1). */
.build-status-badge {
  display: inline-block;
  font: 500 0.72rem/1.4 var(--mono);
  letter-spacing: 0.06em; text-transform: uppercase;
  padding: 0.2rem 0.55rem;
}
.status-success { background: var(--ok); color: #ffffff; }
.status-pending { background: var(--warn); color: #ffffff; }
.status-failed { background: var(--danger); color: #ffffff; }

/* The dark status tints are light pastels, so the badge text inverts. */
@media (prefers-color-scheme: dark) {
  .status-success, .status-pending, .status-failed { color: var(--paper); }
}

.sidebar-links { list-style: none; margin: 0; padding: 0; }
.sidebar-links a {
  display: flex; align-items: center; min-height: var(--tap);
  padding: 0.25rem 0.5rem;
  color: var(--accent); font-size: 0.92rem;
}
.sidebar-links a:hover { text-decoration: underline; }
.sidebar-links .sidebar-link.active {
  background: var(--accent-wash);
  color: var(--accent);
  font-weight: 600;
}

/* ============================================================================
   Breadcrumb
   ========================================================================= */

.breadcrumb { padding: 1rem 0 0; }

.breadcrumb-list {
  display: flex; flex-wrap: wrap; align-items: center;
  list-style: none; margin: 0; padding: 0;
  font: 400 0.8rem var(--mono);
}

.breadcrumb-item { display: flex; align-items: center; min-height: var(--tap); }
.breadcrumb-item a { color: var(--accent); }
.breadcrumb-item a:hover { text-decoration: underline; }
.breadcrumb-separator { color: var(--ink-faint); margin: 0 0.5rem; }
.breadcrumb-current { color: var(--ink-muted); }

/* ============================================================================
   Buttons, pagination, states
   ========================================================================= */

.button {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem;
  min-height: 2.5rem; padding: 0.6rem 1.35rem;
  border: 1px solid var(--ink); background: var(--surface); color: var(--ink);
  font: 600 0.92rem var(--sans); cursor: pointer;
}
.button:hover { background: var(--surface-sunk); }
.button-primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.button-primary:hover { background: var(--accent-deep); border-color: var(--accent-deep); }

.pagination { display: flex; align-items: center; gap: 1rem; padding: 2rem 0 0; }
.pagination-link {
  display: inline-flex; align-items: center; min-height: var(--tap);
  padding: 0.4rem 0.8rem;
  border: 1px solid var(--edge-strong); color: var(--ink);
  font-size: 0.9rem;
}
.pagination-link:hover { border-color: var(--accent); color: var(--accent); }
.pagination-info { font: 400 0.82rem var(--mono); color: var(--ink-muted); }

.empty-state, .error-page {
  text-align: center; padding: clamp(3rem, 8vw, 5rem) 1.5rem;
  color: var(--ink-muted);
}
.empty-state p { color: var(--ink-muted); }
.error-page h1 { color: var(--ink); }

.error-message { font-size: var(--step-1); color: var(--ink-muted); }
.error-hint { color: var(--ink-muted); }
.error-actions {
  display: flex; justify-content: center; flex-wrap: wrap;
  gap: 1rem; margin: 1.5rem 0 2.5rem;
}
.error-details {
  --facet: 16px;
  max-width: 36rem; margin: 0 auto;
  background: var(--surface); border: 1px solid var(--edge);
  padding: clamp(1.25rem, 2.5vw, 1.9rem);
  text-align: left;
  clip-path: polygon(0 0, calc(100% - var(--facet)) 0, 100% var(--facet), 100% 100%, 0 100%);
}

.search-results-count { font: 400 0.85rem var(--mono); color: var(--ink-muted); margin: 0 0 1rem; }

/* ============================================================================
   Footer
   ========================================================================= */

.site-footer {
  border-top: 1px solid var(--edge); background: var(--surface);
  padding: 2.25rem 0; margin-top: var(--bay);
  font-size: 0.9rem; color: var(--ink-muted);
}
.footer-links { display: flex; flex-wrap: wrap; gap: 1.5rem; }
.footer-links a {
  display: inline-flex; align-items: center; min-height: var(--tap);
  color: var(--ink-muted);
}
.footer-links a:hover { color: var(--accent); text-decoration: underline; }

/* ============================================================================
   Documentation surface

   The rendered API reference. We parse `crystal docs --format=json` and lay
   the document out ourselves, so nothing from the generated theme reaches
   this page: it is the mineral system at full width with a left rail. Full
   width rather than --shell because a reference is scanned, not read
   linearly, and a 74rem column wastes the screen it is scanned on.

   Measure is capped on prose only, at 74ch. Signatures, code and tables take
   the whole column: wrapping `def fetch(url : String, headers : Hash(String,
   String)) : HTTP::Client::Response` to a reading measure costs more than the
   eye travel it saves.
   ========================================================================= */

/* MainLayout wraps every page in <main class="container">, which clamps to
   --shell and adds a 1.5rem gutter. The docs surface is the one page that has
   to escape that, and it has to escape it without changing any other page, so
   the container is unclamped only when it actually holds a docs shell. The
   rail and the reading column carry their own padding, so the gutter goes too.
   Where :has() is unsupported the page simply stays clamped: narrower than
   intended, never broken. */
main.container:has(.docs-shell) {
  max-width: none;
  padding-inline: 0;
}

.docs-shell {
  display: grid;
  grid-template-columns: 18rem minmax(0, 1fr);
  align-items: start;          /* not stretch, or the rail cannot be sticky */
  width: 100%;
  max-width: none;             /* deliberately not clamped to --shell */
}
.docs-shell > * { min-width: 0; }

/* ---- left rail --------------------------------------------------------- */

/* The masthead is static, so top:0 puts the rail directly under it while the
   header is on screen and pins it to the viewport once the header scrolls off. */
.docs-sidebar {
  position: sticky;
  top: 0;
  min-height: 100vh;
  max-height: 100vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 1.35rem 1.15rem 2rem;
  background: var(--surface);
  border-right: 1px solid var(--edge);
}

.docs-nav { display: grid; gap: 1.6rem; }

.docs-nav-group { display: grid; gap: 0.15rem; }

.docs-nav-heading {
  font: 500 0.7rem/1 var(--mono);
  letter-spacing: 0.13em; text-transform: uppercase;
  color: var(--ink-muted);
  margin: 0 0 0.5rem;
}

.docs-nav-list { list-style: none; margin: 0; padding: 0; }
.docs-nav-item { margin: 0; }

.docs-nav-link {
  display: flex; align-items: center;
  min-height: var(--tap);
  padding: 0.32rem 0.55rem;
  border-left: 2px solid transparent;
  color: var(--ink-muted);
  font-size: 0.9rem; line-height: 1.45;
  overflow-wrap: anywhere;
  transition: color 0.12s ease, border-color 0.12s ease;
}
.docs-nav-link:hover { color: var(--ink); border-left-color: var(--edge-strong); }

/* Inset ring: the rail is its own scroll container, so an offset ring on a
   full-width row would be clipped at the sides. */
.docs-nav-link:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }

.docs-nav-link.is-current {
  color: var(--accent);
  background: var(--accent-wash);
  border-left-color: var(--accent);
  font-weight: 600;
}

/* A filter field is a control, and --edge-strong only just clears 3:1. An
   input's boundary is worth more than the minimum, so this takes --ink-muted
   (6.00:1 light, 8.20:1 dark), the same reasoning that puts --ink on
   .search-form. */
.docs-nav-filter {
  width: 100%;
  min-height: 2.25rem;
  margin: 0 0 1.35rem;
  padding: 0.45rem 0.6rem;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--ink-muted);
  font: 400 0.9rem var(--sans);
}
.docs-nav-filter::placeholder { color: var(--ink-faint); }
.docs-nav-filter:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ---- type tree ----------------------------------------------------------
   A namespace in Crystal is itself a documented type, so a branch row does two
   jobs at once: it names something you can unfold, and it names a page you can
   visit. <details>/<summary> does the first natively, which is the whole
   reason this is not a div with a click handler: keyboard operation, the
   expanded state and its announcement all come from the element rather than
   from markup we would have to remember to write. The <a> sits inside the
   <summary>, and a click resolves to the nearest activatable element, so the
   name navigates and the rest of the row folds. Which branches start open is
   decided on the server from the current type's ancestry, not here and not by
   script. */

.docs-nav-summary {
  display: flex;
  align-items: center;
  min-height: var(--tap);
  cursor: pointer;
  list-style: none;                            /* Chrome, Firefox */
}
.docs-nav-summary::-webkit-details-marker { display: none; }   /* Safari */

/* The disclosure mark is cut from a box, not set from the icon font. It is the
   one glyph here whose absence would leave a control with no affordance at
   all, so it does not wait on a webfont; cut rather than drawn is also the
   geometry the rest of this system uses. --ink-muted is 6.00:1 light and
   8.20:1 dark on --surface, well past the 3:1 a meaningful graphic owes. */
.docs-nav-summary::before {
  content: "";
  flex: 0 0 var(--tap);
  height: var(--tap);
  background: var(--ink-muted);
  clip-path: polygon(38% 30%, 68% 50%, 38% 70%);
  transition: transform 0.12s ease, background-color 0.12s ease;
}
.docs-nav-branch[open] > .docs-nav-summary::before { transform: rotate(90deg); }
.docs-nav-summary:hover::before { background: var(--ink); }

.docs-nav-summary > .docs-nav-link { flex: 1 1 auto; min-width: 0; }

/* A leaf carries no mark, so its row is offset by the width of one and every
   name at a given depth starts on the same vertical. */
.docs-nav-item > .docs-nav-link { margin-left: var(--tap); }

/* Depth is indentation plus a hairline dropped from the parent's mark. The
   line is decorative and stays on --edge deliberately: the nesting is already
   in the markup and the open state is already in the mark, so the rule is
   never the sole carrier of either. */
.docs-nav-sublist {
  margin-left: calc(var(--tap) / 2);
  padding-left: 0.65rem;
  border-left: 1px solid var(--edge);
}

/* The filter hides rows it did not match. Ancestors of a match are put back,
   which is how a hit inside a folded namespace is reachable. */
.docs-nav-item[hidden] { display: none; }

/* Carries the filter's match count for anyone who cannot see the list shrink.
   Empty until then, and it takes no space while it is. */
.docs-nav-status {
  margin: 0 0 0.4rem;
  color: var(--ink-muted);
  font: 400 var(--step--1)/1.4 var(--sans);
}
.docs-nav-status:empty { display: none; }

/* ---- reading column ---------------------------------------------------- */

.docs-main {
  min-width: 0;
  padding: clamp(1.5rem, 3vw, 2.75rem) clamp(1.25rem, 3vw, 3rem) var(--bay);
}

/* ---- type header ------------------------------------------------------- */

.docs-type-header {
  padding-bottom: 1.25rem;
  margin-bottom: 1.75rem;
  border-bottom: 1px solid var(--edge);
}

/* accent on --accent-wash is 6.22:1 light, 7.61:1 dark. The wash alone barely
   separates from the ground, so the accent hairline draws the chip. */
.docs-kind {
  display: inline-block;
  padding: 0.1rem 0.5rem;
  font: 500 0.68rem/1.5 var(--mono);
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--accent);
  background: var(--accent-wash);
  border: 1px solid var(--accent);
}

.docs-type-name {
  margin: 0.55rem 0 0;
  font: 700 var(--step-4)/1.08 var(--cond);
  letter-spacing: -0.015em;
  color: var(--ink);
  overflow-wrap: anywhere;
}

.docs-ancestry {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 0.15rem 0.5rem;
  margin: 0.8rem 0 0;
  font: 400 0.8rem/1.7 var(--mono);
  color: var(--ink-faint);
}
.docs-ancestry-link { color: var(--accent); }

.docs-summary {
  max-width: 74ch;
  margin: 1rem 0 0;
  font-size: var(--step-1);
  color: var(--ink-muted);
}

/* ---- sections and members ---------------------------------------------- */

.docs-section { margin: 2.75rem 0 0; }

.docs-section-heading {
  display: flex; align-items: center; gap: 1rem;
  margin: 0 0 1.1rem;
  font: 700 var(--step-2)/1.15 var(--cond);
  letter-spacing: -0.01em;
  color: var(--ink);
}
.docs-section-heading::after { content: ""; flex: 1; height: 1px; background: var(--edge); }

.docs-member {
  display: grid;
  gap: 0.6rem;
  padding: 1.3rem 0 1.5rem;
  border-top: 1px solid var(--edge);
}
.docs-member:first-of-type { border-top: 0; padding-top: 0.35rem; }
.docs-member > * { max-width: 100%; }

.docs-member-name {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 0.55rem;
  margin: 0;
  font: 600 var(--step-1)/1.3 var(--mono);
  color: var(--ink);
  overflow-wrap: anywhere;
}

/* Long signatures are the rule, not the exception: wrap, never overflow. */
.docs-member-signature {
  display: block;
  margin: 0;
  padding: 0.7rem 0.85rem;
  background: var(--surface-sunk);
  border: 1px solid var(--edge);
  font: 400 0.85rem/1.65 var(--mono);
  color: var(--ink);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  tab-size: 2;
}

.docs-member-doc { font-size: 0.95rem; line-height: 1.7; }
.docs-member-doc > :last-child { margin-bottom: 0; }

.docs-member-source {
  justify-self: end;
  display: inline-flex; align-items: center; gap: 0.35rem;
  min-height: var(--tap);
  font: 400 0.78rem/1.6 var(--mono);
  color: var(--ink-muted);
}
.docs-member-source:hover { color: var(--accent); }

/* ---- prose ------------------------------------------------------------- */

.docs-doc { margin-top: 1.4rem; }
.docs-readme { line-height: 1.75; }

.docs-doc > :first-child,
.docs-member-doc > :first-child,
.docs-readme > :first-child { margin-top: 0; }

/* The measure is capped on the prose elements, never on their container. Cap
   the container and a fenced example inside a doc comment gets squeezed to
   74ch too, which is the one thing code must not do here. */
.docs-doc p, .docs-doc ul, .docs-doc ol, .docs-doc blockquote,
.docs-member-doc p, .docs-member-doc ul, .docs-member-doc ol, .docs-member-doc blockquote,
.docs-readme p, .docs-readme ul, .docs-readme ol, .docs-readme blockquote { max-width: 74ch; }

.docs-doc ul, .docs-doc ol,
.docs-member-doc ul, .docs-member-doc ol,
.docs-readme ul, .docs-readme ol { padding-left: 1.5rem; }

.docs-readme h1, .docs-readme h2, .docs-readme h3, .docs-readme h4 { margin: 1.6em 0 0.5em; }
.docs-readme h1, .docs-readme h2 {
  font-size: var(--step-2);
  padding-bottom: 0.3rem;
  border-bottom: 1px solid var(--edge);
}
.docs-readme h3 { font-size: var(--step-1); }
.docs-readme h4 { font-size: var(--step-0); }

/* Headings are anchor targets now, so a hash link scrolls one flush against
   the top of the viewport with its border-bottom touching the edge. Hold it
   off by the gap it would have had if you had scrolled to it yourself. */
.docs-readme :is(h1, h2, h3, h4, h5, h6),
.docs-doc :is(h1, h2, h3, h4, h5, h6),
.docs-member-doc :is(h1, h2, h3, h4, h5, h6) { scroll-margin-top: 1.5rem; }

.docs-doc pre, .docs-member-doc pre, .docs-readme pre {
  max-width: 100%;
  margin: 0.9rem 0;
  padding: 0.9rem 1rem;
  background: var(--surface-sunk);
  border: 1px solid var(--edge);
  overflow-x: auto;
}
.docs-doc :not(pre) > code,
.docs-member-doc :not(pre) > code,
.docs-readme :not(pre) > code {
  padding: 0.1em 0.35em;
  background: var(--surface-sunk);
  border: 1px solid var(--edge);
}

/* No display:block wrapper hack: cells wrap, so a wide table cannot push the
   page sideways at 390px. */
.docs-readme table {
  width: 100%; max-width: 100%;
  border-collapse: collapse;
  margin: 0 0 1rem;
  font-size: 0.9rem;
}
.docs-readme th, .docs-readme td {
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--edge);
  text-align: left;
  overflow-wrap: anywhere;
}
.docs-readme th { background: var(--surface-sunk); font-weight: 600; }

.docs-readme blockquote {
  margin: 1rem 0; padding: 0.75rem 1rem;
  border-left: 3px solid var(--accent);
  background: var(--surface-sunk);
  color: var(--ink-muted);
}

/* ---- contents ---------------------------------------------------------- */

.docs-toc {
  margin: 1.5rem 0 0;
  padding: 1rem 1.15rem;
  background: var(--surface);
  border: 1px solid var(--edge);
  columns: 16rem;
  column-gap: 1.75rem;
  list-style: none;
}
.docs-toc ul, .docs-toc ol { list-style: none; margin: 0; padding: 0; }
.docs-toc li { margin: 0; break-inside: avoid; }

.docs-toc-link {
  display: block;
  min-height: var(--tap);
  padding: 0.15rem 0;
  color: var(--accent);
  font: 400 0.84rem/1.75 var(--mono);
  overflow-wrap: anywhere;
}

/* ---- link decoration inside the docs surface ---------------------------
   Prose keeps the site underline (see the base rule). Navigation opts out and
   earns its underline on hover. A cross-package link is dashed, so the cue
   survives even if the icon font never loads; the trailing glyph is Font
   Awesome, the icon convention this app already uses, not an emoji.
   These carry two classes because `li a` and `p a` in the base rules outrank
   a single class. */
.docs-nav-list a, .docs-toc a, .docs-ancestry a, .docs-member .docs-member-source {
  text-decoration: none;
}
.docs-toc a:hover, .docs-ancestry a:hover, .docs-member .docs-member-source:hover {
  text-decoration: underline;
}

.docs-nav-list a.docs-external,
.docs-toc a.docs-external,
.docs-ancestry a.docs-external,
.docs-doc a.docs-external,
.docs-member-doc a.docs-external,
.docs-readme a.docs-external,
.docs-summary a.docs-external,
a.docs-external {
  text-decoration-line: underline;
  text-decoration-style: dashed;
  text-underline-offset: 0.18em;
}
.docs-external::after {
  content: "\f08e";                        /* fa-arrow-up-right-from-square */
  display: inline-block;                   /* atomic inline: the underline stops short of it */
  margin-left: 0.35em;
  font: 900 0.72em/1 "Font Awesome 6 Free";
}

/* ============================================================================
   Responsive
   ========================================================================= */

@media (max-width: 60rem) {
  .docs-layout { grid-template-columns: minmax(0, 1fr); }
  .navbar { gap: 1rem; }
  .search-form-compact { flex-basis: 12rem; }
}

@media (max-width: 48rem) {
  /* Brand and menu share the first row, search takes its own full-width row
     so it never squeezes below a usable size. */
  .navbar { flex-wrap: wrap; gap: 0.75rem 1rem; padding-top: 0.75rem; padding-bottom: 0.75rem; }
  .search-form-compact { order: 3; flex-basis: 100%; max-width: none; margin-right: 0; }
  .navbar-menu { margin-left: auto; }
}

@media (max-width: 40rem) {
  :root { --bay: 2.75rem; }
  .navbar-menu { margin-left: 0; width: 100%; gap: 1.1rem; }
  .intro { gap: 1.25rem; }
  .intro-stats { gap: 1.75rem; }
  .stat-item { flex-basis: 50%; border-bottom: 1px solid var(--edge); }
  .search-form { max-width: none; }
  .docs-header .docs-nav { align-items: flex-start; }
  .version-switcher { width: 100%; }
  .version-switcher select { flex: 1; }
  .error-actions { flex-direction: column; align-items: stretch; }
}

/* The rail is 18rem plus gutters. Below 64rem that no longer leaves a usable
   reading column, so it collapses to a band above the content and the main
   column takes the full width. Nothing below is allowed to introduce a
   horizontal scrollbar: the grid drops to one track, every long token wraps,
   and code scrolls inside its own box. */
@media (max-width: 64rem) {
  .docs-shell { grid-template-columns: minmax(0, 1fr); }
  .docs-sidebar {
    position: static;
    min-height: 0;
    max-height: none;
    padding: 1.1rem 1.25rem 1.25rem;
    border-right: 0;
    border-bottom: 1px solid var(--edge);
  }
  .docs-nav { gap: 1.15rem; }
  /* The root list only. A nested list must not become its own scroll box. */
  .docs-nav-tree { max-height: 17rem; overflow-y: auto; overscroll-behavior: contain; }
}

@media (max-width: 40rem) {
  .docs-sidebar { padding-left: 1.1rem; padding-right: 1.1rem; }
  .docs-main { padding-left: 1.1rem; padding-right: 1.1rem; }
  .docs-nav-tree { max-height: 13rem; }
  .docs-toc { columns: auto; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}

/* ============================================================================
   About page
   ========================================================================= */

/* Running prose needs a measure. 62ch matches .docs-subtitle and
   .doc-description-large, the two existing long-text blocks in this app. */
.about-copy { max-width: 62ch; }

/* The build pipeline is a sequence, so it is an ordered list rather than a
   grid of identical cards. The marker is a counter, not an image. */
.about-steps {
  counter-reset: about-step;
  list-style: none;
  margin: 0; padding: 0;
  max-width: 62ch;
}
.about-steps > li {
  counter-increment: about-step;
  position: relative;
  padding-left: 3rem;
  padding-bottom: 1.4rem;
}
.about-steps > li:last-child { padding-bottom: 0; }
.about-steps > li::before {
  content: counter(about-step);
  position: absolute; left: 0; top: 0;
  display: inline-flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem;
  /* 6.22:1 light, 7.61:1 dark, both computed against --accent-wash. */
  background: var(--accent-wash); color: var(--accent);
  font: 600 0.85rem/1 var(--mono);
}
.about-steps h3 { font-size: var(--step-1); margin: 0.2rem 0 0.35em; }
.about-steps p { margin: 0; color: var(--ink-muted); }


/* ============================================================================
   Build state page (docs requested but not yet built)

   Reuses .build-status-badge and its .status-* tints rather than introducing
   a second badge vocabulary; only the page's own layout lives here.
   ========================================================================= */

.build-state { max-width: 62ch; padding: 2.5rem 0 1rem; }

.build-state-status {
  display: flex; flex-direction: column; align-items: flex-start; gap: 0.85rem;
}

/* The badge is inline-block from .build-status-badge; the icon needs the
   flex box to sit on the text baseline without a magic margin. */
.build-state-badge {
  display: inline-flex; align-items: center; gap: 0.45rem;
}

.build-state-title {
  margin: 0;
  font: 700 var(--step-3)/1.15 var(--cond);
  overflow-wrap: anywhere;
}

.build-state-copy {
  margin: 1.1rem 0 0;
  font-size: var(--step-1);
  color: var(--ink-muted);
}

.build-state-meta { margin: 0.85rem 0 0; color: var(--ink-faint); }

.build-state-error { margin: 1.4rem 0 0; }
.build-state-error summary {
  display: inline-flex; align-items: center; min-height: var(--tap);
  color: var(--accent); cursor: pointer;
}
.build-state-error summary:hover { text-decoration: underline; }
.build-state-error pre {
  margin: 0.6rem 0 0; padding: var(--pad);
  /* --edge-strong, not --edge: this box is the only thing separating the
     builder's output from our own prose, so its boundary carries meaning. */
  border: 1px solid var(--edge-strong);
  background: var(--surface-sunk);
  overflow-x: auto;
}
.build-state-error code { font: 400 var(--step--1)/1.6 var(--mono); color: var(--ink); }

/* The gear reads as "work happening" only while it turns. Users who have
   asked for less motion get the same icon, still. */
@media (prefers-reduced-motion: no-preference) {
  .build-state-badge .fa-gear { animation: build-state-spin 3s linear infinite; }
}

@keyframes build-state-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* ============================================================================
   Job ad (CrystalGigs)

   Paid placement, so it is drawn as an inset rather than as one more section
   of this site: sunk ground, a rule along the top, and the disclosure line
   before anything else. It deliberately does not reuse the card styles this
   site uses for its own content, because an ad dressed as a listing is a dark
   pattern.

   The component renders nothing when there is nothing to advertise, so none
   of these rules need an empty state.
   ========================================================================= */

.job-ad {
  /* Same gutters and shell as .container, so the strip lines up with the
     content above it without having to be wrapped in one. */
  width: min(calc(var(--shell) - 3rem), calc(100% - 3rem));
  margin: var(--bay) auto 0;
  padding: 1.1rem var(--pad) 1.25rem;
  background: var(--surface-sunk);
  border: 1px solid var(--edge);
  border-top: 2px solid var(--edge-strong);
  clip-path: polygon(0 0, calc(100% - var(--facet)) 0, 100% var(--facet), 100% 100%, 0 100%);
}

.job-ad-head {
  display: flex; align-items: baseline; flex-wrap: wrap;
  gap: 0.35rem 0.75rem;
  margin-bottom: 0.9rem;
}

/* The disclosure comes first in the markup and reads first, because a reader
   should know this is an ad before they read the offer. */
.job-ad-disclosure {
  order: -1; flex-basis: 100%;
  margin: 0 0 0.15rem;
  font: 600 var(--step--1)/1.2 var(--mono);
  letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--ink-muted);   /* 5.28:1 on --surface-sunk light, 8.61:1 dark */
}
.job-ad-icon { margin-right: 0.4rem; color: var(--ink-faint); }

.job-ad-heading {
  margin: 0;
  font: 700 var(--step-1)/1.2 var(--cond);
  letter-spacing: -0.01em;
  color: var(--ink);
}

.job-ad-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--gap);
  list-style: none; margin: 0; padding: 0;
}

.job-ad-item {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 0.3rem;
  padding: 0.85rem 1rem;
  background: var(--surface);
  border: 1px solid var(--edge);
}
.job-ad-item-featured { border-left: 3px solid var(--accent); }

.job-ad-title {
  display: inline-flex; align-items: center; min-height: var(--tap);
  font: 600 var(--step-0)/1.3 var(--sans);
  color: var(--ink);
  /* Underlined, because the accent alone does not carry meaning. */
  text-decoration: underline; text-underline-offset: 2px;
}
.job-ad-title:hover { color: var(--accent); }

.job-ad-featured {
  padding: 0.05rem 0.45rem;
  font: 600 var(--step--1)/1.5 var(--mono);
  color: var(--accent);            /* 6.22:1 light, 7.61:1 dark on --accent-wash */
  background: var(--accent-wash);
  /* The wash alone barely separates from the card, so the hairline draws it. */
  border: 1px solid var(--accent);
}

.job-ad-meta {
  display: flex; flex-wrap: wrap; gap: 0 0.5rem;
  margin: 0;
  font-size: var(--step--1);
  color: var(--ink-muted);
}
/* Separators are drawn, never typed, so a screen reader reads the fields as a
   list of facts rather than announcing punctuation between them. */
.job-ad-meta span + span::before { content: "\00b7"; margin-right: 0.5rem; }

@media (max-width: 40rem) {
  .job-ad { padding: 1rem 1.1rem 1.15rem; }
  .job-ad-list { grid-template-columns: minmax(0, 1fr); }
}
