/* inascii.com — design system
   Dark-terminal aesthetic, system monospace stack (zero external font requests). */

:root {
  --bg: #0b0d0f;
  --bg-raised: #121519;
  --bg-inset: #08090a;
  --border: #23282e;
  --border-soft: #1a1e23;
  --text: #e6e8eb;
  --text-dim: #9aa4af;
  --text-faint: #5b6570;
  --accent: #39ff88;
  --accent-dim: #1f9c53;
  --accent-contrast: #04170b;
  --danger: #ff5f56;
  --focus: #7cc6ff;
  --radius: 6px;
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", "Menlo", "Consolas",
    "Liberation Mono", monospace;
  --maxw: 1180px;
}

:root[data-theme="light"] {
  --bg: #f5f6f4;
  --bg-raised: #ffffff;
  --bg-inset: #edeeec;
  --border: #d8dbd6;
  --border-soft: #e6e8e4;
  --text: #14181a;
  --text-dim: #4b5560;
  --text-faint: #8a939c;
  --accent: #0f8a45;
  --accent-dim: #0c6e37;
  --accent-contrast: #f2fff6;
  --focus: #0a6cbf;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --bg: #f5f6f4;
    --bg-raised: #ffffff;
    --bg-inset: #edeeec;
    --border: #d8dbd6;
    --border-soft: #e6e8e4;
    --text: #14181a;
    --text-dim: #4b5560;
    --text-faint: #8a939c;
    --accent: #0f8a45;
    --accent-dim: #0c6e37;
    --accent-contrast: #f2fff6;
    --focus: #0a6cbf;
  }
}

* { box-sizing: border-box; }
html { color-scheme: dark light; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--mono);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--accent);
  color: var(--accent-contrast);
  padding: 8px 12px;
  z-index: 100;
}
.skip-link:focus { left: 8px; top: 8px; }

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

button {
  cursor: pointer;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: var(--radius);
  padding: 8px 12px;
  transition: border-color .12s ease, background .12s ease, color .12s ease;
}
button:hover { border-color: var(--accent-dim); color: var(--accent); }
button:focus-visible, a:focus-visible, select:focus-visible, input:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}
button.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
  box-shadow: 0 0 16px -5px color-mix(in srgb, var(--accent) 80%, transparent);
}
button.primary:hover { color: var(--accent-contrast); filter: brightness(1.08); box-shadow: 0 0 22px -4px color-mix(in srgb, var(--accent) 90%, transparent); }
button:disabled { opacity: .45; cursor: not-allowed; }
button.icon-btn { padding: 6px 10px; font-size: 13px; }

select, input[type="text"], input[type="number"], textarea {
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 7px 10px;
  width: 100%;
}
textarea { resize: vertical; }

input[type="range"] { width: 100%; accent-color: var(--accent); }
input[type="color"] {
  width: 40px; height: 30px; padding: 2px;
  background: var(--bg-inset); border: 1px solid var(--border); border-radius: var(--radius);
}

label { display: block; font-size: 12.5px; color: var(--text-dim); margin-bottom: 4px; }
.field { margin-bottom: 14px; }
.field-row { display: flex; gap: 10px; align-items: center; }
.field-row label { margin-bottom: 0; }

/* ---------- layout shell ---------- */
/* Deliberately not sticky, and neither is the toolbar below it: nothing in the
   chrome may overlay an AdSense anchor unit or an Auto-ads in-content slot.
   Fixed height, so header + toolbar is the same slice of chrome on every page
   at rest and after interaction, and nothing in it can shift layout. */
.site-header {
  border-bottom: 1px solid var(--border-soft);
  background: var(--bg);
}
.header-inner {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 6px 20px;
  min-height: 50px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
.brand { display: flex; align-items: baseline; gap: 10px; }
.brand-mark {
  font-weight: 700;
  font-size: 18px;
  letter-spacing: -0.02em;
}
.brand-mark .bracket { color: var(--accent); text-shadow: 0 0 12px color-mix(in srgb, var(--accent) 60%, transparent); }
.brand-tag { color: var(--text-faint); font-size: 12px; display: none; }
@media (min-width: 640px) { .brand-tag { display: inline; } }

.header-actions { display: flex; align-items: center; gap: 8px; }

/* ==========================================================================
   toolbar v1 — the portfolio navigation pattern.
   Spec: github.com/ngineer420/ngineer420.github.io/issues/13
   Reference implementation: photoshrink#7.

   Copied verbatim from the pilot. Everything site-specific is the eight custom
   properties declared on `.toolbar` below; remap those to the host site's
   tokens and nothing else needs touching. The silhouette is shared, the
   palette and type are not.

   Structure: one <nav class="toolbar">, a direct child of <body> immediately
   after </header>. A <details> trigger pinned left that never scrolls, and a
   single non-wrapping row of tool chips that does. Never sticky.
   ========================================================================== */

.toolbar {
  /* ---- the only site-owned values in this block ---- */
  --tb-bg: var(--bg-inset);
  --tb-bg-elev: var(--bg-raised);
  --tb-border: var(--border);
  --tb-border-soft: var(--border-soft);
  --tb-text: var(--text);
  --tb-text-dim: var(--text-dim);
  --tb-accent: var(--accent);
  --tb-focus: var(--focus);
  --tb-sheet-radius: 0;
  --tb-sheet-shadow: none;

  /* A stacking context, so the sheet lands above the tool's own positioned
     bits (the fullscreen gallery, tile menus) without a z-index arms race. */
  position: relative; z-index: 40;
  display: flex; align-items: stretch;
  /* 44px of content plus the 1px rule. box-sizing is border-box, so a flat
     44px here leaves the rail a 43px content box, the 44px chips overflow it
     by a pixel and the 2px active underline gets clipped to 1px. */
  height: 45px;
  background: var(--tb-bg);
  border-bottom: 1px solid var(--tb-border-soft);
}
/* The bar is full-bleed so its rule spans the window, but its contents belong
   in the same column as the header and <main>. Full-bleed is kept below 720px,
   where the 40px would come straight out of the rail's scrollable width and
   the fades already handle the edges. */
@media (min-width: 720px) {
  .toolbar { padding-inline: max(20px, calc((100% - var(--maxw)) / 2 + 20px)); }
}

/* ---- the trigger: labelled, counted, and never collapses to an icon ---- */
.tb-menu { position: relative; flex: 0 0 auto; }
.tb-trigger {
  display: flex; align-items: center; gap: 8px;
  min-height: 44px; padding: 0 14px;
  white-space: nowrap; cursor: pointer; list-style: none;
  font-size: 14.5px; font-weight: 700; color: var(--tb-text);
  border-right: 1px solid var(--tb-border-soft);
}
.tb-trigger::-webkit-details-marker { display: none; } /* Safari */
.tb-trigger::marker { content: ""; }
.tb-trigger:hover { color: var(--tb-accent); }
.tb-glyph { color: var(--tb-accent); font-size: 15px; line-height: 1; }

/* ---- the rail: one row, always. Wrapping is what produced every ragged
   multi-row header in the audit; nowrap makes those faults impossible. ---- */
.tb-rail {
  flex: 1 1 auto; min-width: 0;
  /* The offsetParent of a chip. Without it that is `.toolbar`, so the
     centre-on-current JS adds the trigger's width to its scroll target and the
     active chip lands with its first letters under the left fade — exactly the
     clipped label this pattern exists to prevent. */
  position: relative;
  display: flex; flex-wrap: nowrap; gap: 0;
  margin: 0; padding: 0; list-style: none;
  overflow-x: auto; overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;  /* a swipe past the end is not a back-nav */
  scroll-padding-inline: 24px;     /* Tab must not park a chip under the fade */
  scrollbar-width: none;
  /* The right fade is ON by default and JS turns it off once it can prove the
     rail is scrolled to its end. Defaulting it off would mean a JS-disabled
     visitor gets a label hard-clipped mid-word, which is the one thing this
     pattern exists to stop. */
  --f0: 0px; --f1: 24px;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--f0),
    #000 calc(100% - var(--f1)), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 var(--f0),
    #000 calc(100% - var(--f1)), transparent 100%);
}
.tb-rail::-webkit-scrollbar { display: none; } /* the fade is the affordance */
.tb-rail.can-l { --f0: 24px; }
.tb-rail.js-on:not(.can-r) { --f1: 0px; }
.tb-rail > li { flex: 0 0 auto; }
.tb-rail a {
  display: flex; align-items: center; min-height: 44px; padding: 0 12px;
  white-space: nowrap; text-decoration: none;
  font-size: 14.5px; font-weight: 600; color: var(--tb-text-dim);
  box-shadow: inset 0 -2px 0 transparent;
}
/* Hover changes colour only, so the scroller never reflows under the cursor. */
.tb-rail a:hover { color: var(--tb-text); text-decoration: none; }
.tb-rail a[aria-current] {
  color: var(--tb-text); font-weight: 800;
  box-shadow: inset 0 -2px 0 var(--tb-accent);
}

/* outline-offset is negative so the ring is not clipped by the scroller. */
.tb-trigger:focus-visible,
.tb-rail a:focus-visible,
.tb-sheet a:focus-visible {
  outline: 2px solid var(--tb-focus); outline-offset: -2px;
}

/* ---- the sheet: a disclosure, not a modal. Focus is never trapped. ---- */
.tb-sheet {
  position: absolute; top: 100%; left: 0; z-index: 2;
  width: min(680px, 92vw); max-height: min(70vh, 520px);
  overflow-y: auto; overscroll-behavior: contain;
  background: var(--tb-bg-elev);
  border: 1px solid var(--tb-border);
  /* Radius and shadow are site-owned: a hard-edged idiom reads wrong with a
     soft drop shadow and a 12px corner. Structure stays identical portfolio-
     wide; only these two tokens move. */
  border-radius: var(--tb-sheet-radius, 0 0 12px 12px);
  box-shadow: var(--tb-sheet-shadow, 0 8px 24px rgba(0, 0, 0, .12));
  padding: 14px 4px;
  columns: 2; column-gap: 24px;
}
/* At <= 8 destinations the sheet renders one flat list and the columns move
   onto it; group headings are noise at that size. */
.tb-sheet.is-flat { columns: 1; }
.tb-sheet.is-flat ul { columns: 2; column-gap: 24px; }
.tb-sheet ul { margin: 0 0 12px; padding: 0; list-style: none; break-inside: avoid; }
.tb-sheet li { break-inside: avoid; }
.tb-sheet a {
  display: flex; align-items: center; min-height: 36px; padding: 0 14px;
  font-size: 14.5px; text-decoration: none; color: var(--tb-text-dim);
  border-left: 3px solid transparent; /* reserved, so the current item never shifts */
}
.tb-sheet a:hover { color: var(--tb-text); text-decoration: none; }
.tb-sheet a[aria-current] {
  color: var(--tb-text); font-weight: 800; border-left-color: var(--tb-accent);
}
/* <p>, not <h2>: chrome headings would pollute the outline of an SEO page. */
.tb-grouplabel {
  margin: 0 0 4px; padding: 0 14px;
  font-size: 12px; font-weight: 800; letter-spacing: .08em;
  text-transform: uppercase; color: var(--tb-text-dim);
  /* A group label and its list are two boxes, so `break-inside: avoid` on the
     list does nothing to keep them together: the column balancer will happily
     end a column on the heading and start the next one with the list it
     introduces. */
  break-after: avoid;
}
/* The one route from the chrome to a tier-2 family. */
.tb-hub {
  column-span: all; break-inside: avoid;
  margin: 2px 0 0; padding: 10px 14px 0;
  border-top: 1px solid var(--tb-border-soft);
}
.tb-hub a {
  padding: 0; border-left: 0; min-height: 36px;
  color: var(--tb-accent); font-weight: 700;
}

/* ---- mobile: full-bleed sheet, scrim, short trigger label ---- */
.tb-scrim { display: none; }
@media (max-width: 719px) {
  .tb-menu { position: static; }
  .tb-sheet {
    left: 0; right: 0; width: auto; columns: 1;
    border-width: 1px 0; border-radius: 0;
  }
  .tb-sheet.is-flat ul { columns: 1; }
  .tb-sheet a { min-height: 44px; padding: 0 16px; }
  /* Anchored to the bar rather than the viewport, so it cannot cover the
     trigger — with JS off, tapping the trigger again is the way out and there
     must be nothing on top of it. Pure CSS, so it works with JS off too. */
  .tb-menu[open] ~ .tb-scrim {
    display: block; position: absolute; top: 100%; left: 0; right: 0;
    height: 100vh; z-index: 1; background: rgba(0, 0, 0, .42);
  }
}
@media (max-width: 399px) {
  .tb-label-long { display: none; } /* "All 3 tools" -> "All 3"; the accessible
                                       name on the summary keeps the full string */
}

/* ---- the tier-2 sibling cluster, under the h1 of every font page ----
   A font page is the same renderer with the font fixed, so its siblings are not
   peers of the three tools and do not belong in the rail. They belong next to
   the control they change, above the fold — this was a link list at the very
   bottom of the page, which is not where a visitor decides the font is wrong. */
.font-switch {
  max-width: var(--maxw); margin: 0 0 18px;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px;
}
.font-switch-label {
  color: var(--text-faint); font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .07em;
}
.font-switch ul {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin: 0; padding: 0; list-style: none; min-width: 0;
}
.font-switch a.chip {
  display: inline-flex; align-items: center; min-height: 32px;
  padding: 0 11px; border-radius: var(--radius);
  border: 1px solid var(--border-soft); background: var(--bg-raised);
  color: var(--text-dim); font-size: 12.5px; font-weight: 600;
  text-decoration: none;
}
.font-switch a.chip:hover { color: var(--text); border-color: var(--border); text-decoration: none; }
.font-switch a.chip[aria-current="page"] {
  color: var(--accent-contrast); background: var(--accent); border-color: var(--accent);
}
.font-switch a.chip-all { border-style: dashed; color: var(--accent); }
@media (max-width: 560px) { .font-switch a.chip { min-height: 36px; } }

main { max-width: var(--maxw); margin: 0 auto; padding: 24px 20px 60px; }

.container-narrow { max-width: 760px; margin: 0 auto; padding: 40px 20px 60px; }
.container-narrow h1 { font-size: 22px; }
.container-narrow h2 { font-size: 16px; margin-top: 32px; }
.container-narrow p, .container-narrow li { color: var(--text-dim); }

/* ---------- hero (CRT phosphor screen) ---------- */
.hero {
  position: relative;
  text-align: center;
  padding: 18px 16px 20px;
  margin: 6px 0 18px;
  border-radius: 10px;
  background:
    radial-gradient(120% 130% at 50% 0%, color-mix(in srgb, var(--accent) 9%, transparent), transparent 62%),
    var(--bg-inset);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--border));
  overflow: hidden;
}
/* faint CRT scanlines across the hero screen */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(
    to bottom,
    transparent 0, transparent 2px,
    color-mix(in srgb, var(--accent) 7%, transparent) 3px, transparent 4px
  );
  opacity: .7;
}
.hero > * { position: relative; }
.hero h1 {
  font-size: clamp(22px, 4vw, 34px);
  margin: 0 0 8px;
  letter-spacing: -0.01em;
  color: var(--accent);
  text-shadow:
    0 0 2px color-mix(in srgb, var(--accent) 85%, transparent),
    0 0 16px color-mix(in srgb, var(--accent) 45%, transparent);
}
.hero p { color: var(--text-dim); margin: 0; font-size: 14.5px; }
.hero .trust-line {
  margin-top: 10px;
  font-size: 12.5px;
  color: var(--text-faint);
}
.hero .trust-line .dot { color: var(--accent); margin: 0 6px; }

/* ---------- homepage panels ---------- */
/* What is left of the old tab strip: the homepage mounts both tools and swaps
   between them in place. The strip itself is now the portfolio toolbar. */
.tab-panel { display: none; }
.tab-panel.active { display: block; }

/* ---------- tool layout ---------- */
.tool-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
}
@media (min-width: 900px) {
  .tool-grid { grid-template-columns: 300px 1fr; }
  /* Stretch (grid default) so controls and output end at the same baseline;
     the output column's preview grows to soak up any leftover height instead
     of leaving dead space beside a taller controls column. */
  .output-col { display: flex; flex-direction: column; }
  .output-col > .panel:first-child { display: flex; flex-direction: column; flex: 1; }
  .output-col > .panel:first-child .ascii-surface { flex: 1; }
}

/* Text tab: display + controls on the left, scrollable font list beside them.
   The side column is absolutely positioned (desktop only) so its height follows
   the main column instead of the 59-font list dictating the row height. */
.text-tool-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
}
/* Grid items default to min-width:auto, so wide `white-space: pre` art would
   stretch the whole column past the viewport instead of scrolling inside it. */
.text-main-col, .text-side-col { min-width: 0; }
@media (min-width: 900px) {
  .text-tool-grid { grid-template-columns: minmax(0, 1fr) 320px; }
  .text-side-col { position: relative; }
  .text-side-col .font-list-panel { position: absolute; inset: 0; }
}
.preview-panel { position: relative; }
.font-list-panel { display: flex; flex-direction: column; }
.font-list-panel .gallery {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  grid-template-columns: 1fr;
  padding-right: 4px;
}
@media (max-width: 899px) {
  .font-list-panel .gallery { max-height: 420px; }
}

.panel {
  background: var(--bg-raised);
  border: 1px solid var(--border-soft);
  border-radius: 10px;
  padding: 16px;
}
.panel h2 {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--text-faint);
  margin: 0 0 14px;
}
.panel + .panel { margin-top: 16px; }

.controls-stack { display: flex; flex-direction: column; }
.controls-stack--row { display: flex; flex-direction: column; }
@media (min-width: 560px) {
  .controls-stack--row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0 20px; }
}
@media (min-width: 900px) {
  .controls-stack--row { grid-template-columns: repeat(4, 1fr); }
}

/* Style panel (text tab): one wrapping row where each control takes its
   natural width and the size slider soaks up the slack, instead of four
   forced-equal columns full of dead space. */
.style-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 16px 28px;
}
.style-controls .field { margin-bottom: 0; }
/* Size gets a full-width row of its own (a slider earns the span); the other
   three share the second row, with Layout growing to close out the line. */
.style-controls .field--size { flex: 1 1 100%; }
.style-controls .field--layout { flex: 1 1 150px; max-width: 240px; }

fieldset { border: none; padding: 0; margin: 0 0 14px; }
fieldset legend { padding: 0; }

.btn-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }

/* ---------- preview / output ---------- */
.output-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}
.output-toolbar .btn-row { margin-top: 0; }
.toolbar-left { display: flex; align-items: baseline; gap: 10px; min-width: 0; }
.edit-hint {
  font-size: 11.5px;
  color: var(--text-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 40vw;
}

.ascii-surface {
  background: var(--bg-inset);
  border: 1px solid var(--border-soft);
  border-radius: 10px;
  overflow: auto;
  min-height: 220px;
  position: relative;
}
.ascii-surface pre {
  margin: 0;
  padding: 20px;
  white-space: pre;
  line-height: 1.05;
  font-size: 13px;
  tab-size: 1;
}
.ascii-surface canvas { display: block; max-width: 100%; }
.ascii-surface.is-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-faint);
  font-size: 13px;
  min-height: 260px;
  text-align: center;
  padding: 20px;
}

.copy-flash {
  position: absolute;
  top: 10px; right: 10px;
  background: var(--accent);
  color: var(--accent-contrast);
  padding: 4px 10px;
  border-radius: 5px;
  font-size: 12px;
  font-weight: 600;
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity .15s ease, transform .15s ease;
  pointer-events: none;
}
.copy-flash.show { opacity: 1; transform: translateY(0); }

/* ---------- editable preview (text tab) ----------
   The big ASCII art *is* the text field: clicking the surface focuses a
   visually hidden textarea (#text-input) that drives the render, and a
   block caret blinks after the art while it has focus. */
.ascii-surface--editable { cursor: text; min-height: 280px; }
.ascii-surface--editable.is-editing {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}
.ghost-input {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  opacity: 0;
  border: none;
  padding: 0;
  resize: none;
  pointer-events: none;
}
/* The caret is positioned in art coordinates (JS sets left/top/height from
   the rendered width of the text before the cursor), so it tracks the real
   cursor through arrow keys, backspace, and click-to-place. */
#text-output { position: relative; }
.ascii-caret {
  display: none;
  position: absolute;
  width: 2px;
  background: var(--accent);
}
.is-editing .ascii-caret {
  display: block;
  animation: caret-blink 1.1s steps(1) infinite;
}
@keyframes caret-blink { 50% { opacity: 0; } }

/* ---------- font gallery ---------- */
.gallery {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
}
@media (min-width: 560px) {
  .gallery { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
}
.gallery-category {
  grid-column: 1 / -1;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--text-faint);
  padding-top: 14px;
  margin-top: 6px;
  border-top: 1px solid var(--border-soft);
}
.gallery-category:first-child {
  padding-top: 0;
  margin-top: 0;
  border-top: none;
}
.gallery-item {
  text-align: left;
  background: var(--bg-inset);
  border: 1px solid var(--border-soft);
  padding: 10px 12px;
  border-radius: 8px;
  /* No `overflow: hidden` here: inside the fixed-height scrollable list it
     zeroes the grid item's automatic minimum size, collapsing every tile.
     The inner <pre> handles horizontal overflow itself; min-width keeps wide
     art from stretching the 1fr column past the container. */
  min-width: 0;
}
.gallery-item:hover { border-color: var(--accent-dim); }
.gallery-item[aria-pressed="true"] { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--bg-inset)); }
.gallery-item .font-name {
  display: block;
  font-size: 11px;
  color: var(--text-faint);
  margin-top: 8px;
  text-transform: uppercase;
  letter-spacing: .04em;
}
.gallery-item[aria-pressed="true"] .font-name { color: var(--accent); }
.gallery-item pre {
  margin: 0;
  font-size: 11px; /* per-tile JS override scales this so the art fits the width */
  line-height: 1.05;
  color: var(--text);
  white-space: pre;
  overflow: hidden;
}
/* A tile whose .flf has not been fetched yet. Reserving roughly one tile's
   worth of height keeps the list's scroll extent close to the loaded layout,
   so the scrollbar doesn't lurch and scrollGalleryToSelection's offsets land. */
.gallery-item pre.is-pending {
  min-height: 64px;
  color: var(--text-faint);
}

/* fullscreen font browsing */
.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 14px;
}
.panel-head h2 { margin: 0; }
.font-list-panel.is-fullscreen {
  position: fixed;
  inset: 0;
  z-index: 60;
  border-radius: 0;
  border: none;
  padding: 20px clamp(16px, 4vw, 40px) 24px;
}
.font-list-panel.is-fullscreen .gallery {
  max-height: none;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 10px;
  align-content: start;
}
body.gallery-fullscreen { overflow: hidden; }

/* ---------- dropzone (image mode) ---------- */
.dropzone {
  border: 1.5px dashed var(--border);
  border-radius: 10px;
  padding: 28px 16px;
  text-align: center;
  color: var(--text-dim);
  cursor: pointer;
  background: var(--bg-inset);
  transition: border-color .12s ease, background .12s ease;
}
.dropzone:hover, .dropzone.dragover {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 6%, var(--bg-inset));
}
.dropzone strong { color: var(--text); }
.dropzone .hint { font-size: 12px; color: var(--text-faint); margin-top: 4px; }
.source-thumb {
  margin-top: 12px;
  display: none;
  align-items: center;
  gap: 10px;
  background: var(--bg-inset);
  border: 1px solid var(--border-soft);
  border-radius: 8px;
  padding: 8px;
}
.source-thumb.show { display: flex; }
.source-thumb img { width: 44px; height: 44px; object-fit: cover; border-radius: 4px; }
.source-thumb .meta { font-size: 12px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; }

/* ---------- range value display ---------- */
.range-label { display: flex; justify-content: space-between; font-size: 12.5px; color: var(--text-dim); margin-bottom: 4px; }
.range-label span:last-child { color: var(--text); font-variant-numeric: tabular-nums; }

/* swatch segmented control */
.segmented { display: flex; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.segmented button {
  flex: 1; border: none; border-radius: 0; background: var(--bg-inset);
  border-right: 1px solid var(--border);
}
.segmented button:last-child { border-right: none; }
.segmented button[aria-pressed="true"] { background: var(--accent); color: var(--accent-contrast); font-weight: 600; }

/* ---------- footer ---------- */
.site-footer {
  border-top: 1px solid var(--border-soft);
  margin-top: 40px;
}
.footer-inner {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 24px 20px 40px;
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  justify-content: space-between;
  color: var(--text-faint);
  font-size: 12.5px;
}
.footer-links { display: flex; gap: 16px; flex-wrap: wrap; }
.footer-links a { color: var(--text-dim); }

.erabbit-mark { display: block; width: 10px; margin: 0 0 0 auto; line-height: 0; cursor: default; }
.erabbit-mark img { display: block; width: 10px; height: 10px; }

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

.spinner {
  display: inline-block; width: 12px; height: 12px;
  border: 2px solid var(--border); border-top-color: var(--accent);
  border-radius: 50%; animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ---------- animated source controls (image tool) ---------- */
.anim-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 10px;
  padding: 8px 10px;
  background: var(--bg-inset);
  border: 1px solid var(--border-soft);
  border-radius: 8px;
}
.anim-bar input[type="range"] { flex: 1 1 120px; min-width: 0; }
.anim-count {
  font-size: 12px;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ---------- font landing pages (/fonts/) ---------- */
.ascii-sample {
  background: var(--bg-inset);
  border: 1px solid var(--border-soft);
  border-radius: 10px;
  overflow-x: auto;   /* wide art scrolls here, never on the page body */
  overflow-y: hidden;
}
.ascii-sample pre {
  margin: 0;
  padding: 18px 20px;
  white-space: pre;
  line-height: 1.05;
  font-size: 13px;
  tab-size: 1;
  color: var(--text);
}
.ascii-sample--hero pre { color: var(--accent); }

.font-page-label {
  display: block;
  margin: 16px 0 6px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--text-faint);
}
.font-page-row { display: flex; gap: 8px; flex-wrap: wrap; }
.font-page-row input {
  flex: 1 1 200px;
  min-width: 0;             /* let the field shrink instead of forcing overflow */
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--radius, 8px);
  color: var(--text);
  padding: 8px 12px;
  font: inherit;
}
.font-page-note { margin: 10px 0 0; font-size: 12.5px; color: var(--text-faint); }
.font-page-cta { font-weight: 700; }

.font-facts { margin: 0; display: grid; gap: 8px; grid-template-columns: 1fr; }
@media (min-width: 560px) { .font-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.font-facts > div {
  display: flex;
  gap: 10px;
  justify-content: space-between;
  background: var(--bg-inset);
  border: 1px solid var(--border-soft);
  border-radius: 8px;
  padding: 8px 12px;
  font-size: 13px;
}
.font-facts dt { color: var(--text-faint); margin: 0; }
.font-facts dd { margin: 0; color: var(--text); text-align: right; }
.font-credit { font-size: 12.5px; color: var(--text-dim); margin: 14px 0 0; }
.font-credit span { color: var(--text-faint); }

.related-fonts h2 { font-size: 15px; }
.font-links { list-style: none; padding: 0; margin: 10px 0 0; display: flex; flex-wrap: wrap; gap: 8px; }
.font-links a {
  display: inline-block;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 5px 12px;
  font-size: 12.5px;
  color: var(--text-dim);
}
.font-links a:hover { color: var(--accent); border-color: var(--accent); text-decoration: none; }

.font-index-group { margin-top: 30px; }
.font-index-group h2 { font-size: 15px; margin: 0 0 12px; }
.font-index-grid { display: grid; grid-template-columns: 1fr; gap: 12px; }
@media (min-width: 620px) { .font-index-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.font-index-card {
  display: block;
  background: var(--bg-raised);
  border: 1px solid var(--border-soft);
  border-radius: 10px;
  padding: 14px 16px;
  color: inherit;
  min-width: 0;
}
.font-index-card:hover { border-color: var(--accent); text-decoration: none; }
.font-index-name {
  display: block;
  font-size: 13px;
  font-weight: 700;
  color: var(--accent);
  margin-bottom: 8px;
}
.font-index-card pre {
  margin: 0;
  font-size: 10px;
  line-height: 1.05;
  white-space: pre;
  overflow-x: auto;         /* per-card scroll; the page itself never scrolls sideways */
  color: var(--text);
}

/* ---------- reduced motion ----------
   Both animations here loop forever: the editing caret blinks on every page
   where the preview has focus, and the spinner spins while a font loads. Anyone
   who has asked their OS to reduce motion should get neither. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .is-editing .ascii-caret { animation: none; opacity: 1; }
  .spinner { animation: none; border-top-color: var(--accent); }
}

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

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--bg-inset); }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 6px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-faint); }
