/* ============================================================
   tool-utilities.css — the bespoke utility tools
   ============================================================

   The four utilities added 2026-08-22 (graph paper, coloring page,
   paint-by-number, mandala) reuse the `gt-*` shell that
   ImageSplitter / ProportionCalculator / AspectRatioCalculator
   already have — `.gt-tool`, `.gt-tool-grid`, `.gt-tool-side`,
   `.gt-tool-main`, `.gt-field`, `.gt-btn`, `.gt-tool-actions`,
   `.gt-tool-note`. Those live in app/globals.css.

   This file adds ONLY what those three never needed: a drop target,
   a preview surface, chips, a status line and a palette readout.

   It is a separate stylesheet rather than an addition to
   app/globals.css because that file carries another session's
   uncommitted work. Fold it in once the tree is committed.

   IT WAS WRITTEN AGAINST THE WRONG TOKEN NAMES (fixed 2026-09-06).
   `--c-text-1/2/3`, `--c-surface` and `--c-text-inv` are the FROZEN
   TOOL's names, declared on `.gt-root` in components/tool/tool.css.
   This sheet is loaded globally by app/layout.tsx and styles markup
   OUTSIDE that root, where the site's tokens.css is the authority and
   those five names do not exist. Seven references carried no fallback,
   so `color: var(--c-text-2)` was invalid at computed-value time and
   the property INHERITED: the graph-paper status line, the drop
   target's label, every chip, every checkbox row and the paint-by-
   number hex readout all painted the parent's near-black where a
   muted grey was intended. Nothing errored and nothing looked
   obviously broken, which is why it survived. Use `--c-ink-*` and
   `--c-surface-N`, and give every reference a fallback.
   ------------------------------------------------------------ */

/* --- the file drop target ------------------------------------ */

.gt-drop {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  min-height: 108px;
  padding: var(--s-4);
  margin: 0 0 var(--s-4);
  /* --c-dashed-line, not --c-border-strong — see the token's note in
     tokens.css (2026-09-02). Hover already goes to --c-text-1, which is still
     a clear step darker than this. */
  border: 2px dashed var(--c-dashed-line, #6b7178);
  border-radius: var(--r-lg, 12px);
  background: var(--c-surface-2, #fafafa);
  color: var(--c-ink-2, #404040);
  font-size: 0.92rem;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.gt-drop:hover,
.gt-drop:focus-within {
  border-color: var(--c-ink-1, #0a0a0a);
  background: var(--c-surface-1, #fff);
}

/* The input stays a REAL <input type="file"> in the document — the
   visual-semantics requirement in 10-UIUX Part 1 — and is merely
   made invisible. Never replace it with a div and a click handler. */
.gt-drop input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* --- preview surfaces ---------------------------------------- */

/* THE EMPTY PLATE IS RULED (2026-09-06), the same answer and the same reason as
   the hero tool's own empty canvas (CLAUDE.md §6, 2026-09-02): before a picture
   is loaded, four of these pages — /tools/grid-overlay/,
   /tools/coloring-page-generator/, /tools/paint-by-number-generator/ and
   /tools/photo-grid-maker/ — showed a ~950 x 480 BLANK WHITE RECTANGLE as the
   largest element on the page. Squared paper says "this is a drawing surface";
   an empty white box says the page failed to load.

   IT IS DRIVEN BY `data-empty`, NOT BY THE BITMAP, and the first pass got that
   wrong. The reasoning was that a canvas paints its own bitmap OVER its CSS
   background and every one of these components writes alpha 255 across the
   whole bitmap the moment it has something to show — which is true, and is not
   enough. `object-fit: contain` plus the `max-height: 70vh` below means the
   ELEMENT BOX and the BITMAP have different aspect ratios whenever the cap
   binds, and the letterbox bars are background: on /tools/mandala-generator/,
   whose plate is never empty, that painted a column of ruled ticks down both
   inside edges of a finished drawing. The components say when they are empty
   (`hasImage`, `dims`, `items.length`); mandala never is, so it never asks. */
.gt-canvas {
  display: block;
  width: 100%;
  height: auto;
  max-height: 70vh;
  object-fit: contain;
  background: #fff;
  border: 1px solid var(--c-border, #e5e7eb);
  border-radius: var(--r-md, 8px);
}
.gt-canvas[data-empty="true"] {
  background:
    repeating-linear-gradient(to right, rgba(10, 10, 12, .085) 0 1px, transparent 1px 96px),
    repeating-linear-gradient(to bottom, rgba(10, 10, 12, .085) 0 1px, transparent 1px 96px),
    repeating-linear-gradient(to right, rgba(10, 10, 12, .045) 0 1px, transparent 1px 24px),
    repeating-linear-gradient(to bottom, rgba(10, 10, 12, .045) 0 1px, transparent 1px 24px),
    #fff;
}

.gt-canvas--square {
  max-width: 720px;
  margin-inline: auto;
  aspect-ratio: 1 / 1;
}

/* The graph-paper preview is an inline <svg> sized in millimetres, so it
   must be scaled down to the column without changing its own units. */
/* NO BORDER OF ITS OWN (2026-09-06). `.gt-tool-main` became a bordered card on
   the same day, and a bordered tinted box inside a bordered white card is the
   box-in-a-box the /tools/ cohort has had to unpick twice already. What is left
   is a MAT — the tinted ground the sheet's own drop shadow sits on, which is
   the arrangement the tool's canvas has under `--canvas-mat`. */
.gt-paper-preview {
  display: flex;
  justify-content: center;
  /* Shrink-to-fit, so the mat hugs the sheet. Full width it was a 960px band of
     grey around a 450px A4 portrait — a mat is the border a plate sits on, not
     the table it sits on. */
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
  padding: var(--s-4);
  background: var(--c-surface-2, #fafafa);
  border-radius: var(--r-md, 8px);
}

.gt-paper-preview svg {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 64vh;
  box-shadow: var(--shadow-md, 0 2px 12px rgba(0, 0, 0, 0.08));
  background: #fff;
}

/* --- status line --------------------------------------------- */

.gt-status {
  margin: 0 0 var(--s-3);
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--c-ink-2, #404040);
}

/* --- chips ---------------------------------------------------- */

.gt-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-top: var(--s-4);
}

.gt-chip {
  display: inline-flex;
  align-items: center;
  padding: 5px 12px;
  border: 1px solid var(--c-border, #e5e7eb);
  border-radius: var(--r-pill, 999px);
  background: var(--c-surface-1, #fff);
  color: var(--c-ink-2, #404040);
  font-size: 0.8rem;
  font-weight: 500;
  line-height: 1.4;
  cursor: pointer;
}

.gt-chip:hover {
  border-color: var(--c-ink-1, #0a0a0a);
  color: var(--c-ink-1, #0a0a0a);
}

.gt-chip--on {
  background: var(--c-ink-1, #0a0a0a);
  border-color: var(--c-ink-1, #0a0a0a);
  color: var(--c-surface-1, #fff);
}

/* A SELECTED CHIP WENT INVISIBLE UNDER THE POINTER (2026-09-06). `.gt-chip:hover`
   is (0,2,0) — a class plus a pseudo-class — and `.gt-chip--on` is (0,1,0), so
   the hover rule's `color: var(--c-ink-1)` beat the selected rule's
   `color: var(--c-surface-1)` on a ground the selected rule had already painted
   `--c-ink-1`. Measured on /tools/instagram-grid-maker/: rgb(10,10,10) on
   rgb(10,10,10), a contrast ratio of 1.00:1, i.e. the label was simply gone for
   as long as the pointer sat on the chip you had just clicked.

   NOTHING COULD SEE IT. axe and every contrast audit read the resting state;
   `audit-btn-text-color` reads `.gm-btn` / `.gmp-btn`; and a screenshot only
   catches it if the harness happens to leave the pointer on the chip, which is
   exactly how it was found. It affected every bespoke tool that renders a
   selected chip, not one page. `node scripts/_diag-chip.mjs` measures it and
   `_probe-instagram-grid.mjs` now gates it.

   A selected chip needs no further hover signal — it is already the black one —
   so the fix keeps the ground and restores the label. */
.gt-chip--on:hover,
.gt-chip--on:focus-visible {
  color: var(--c-surface-1, #fff);
  border-color: var(--c-ink-1, #0a0a0a);
}

/* Chips that name augmented query variants are labels, not controls. */
.gt-chip--static {
  cursor: default;
  background: var(--c-surface-2, #fafafa);
}

.gt-chip--static:hover {
  border-color: var(--c-border, #e5e7eb);
  color: var(--c-ink-2, #404040);
}

/* --- paint-by-number palette --------------------------------- */

/* 210, not 148, and `min(…, 100%)` rather than a bare px floor — CLAUDE.md
   rule 11c. Two reasons for the widening: /tools/canvas-size-calculator/ and
   /tools/mural-scaling-calculator/ reuse this list for stock-size rows, where
   148px broke "48 × 36 in  exact fit" across four lines; and a fixed floor
   overflows any container narrower than itself, which is every phone. */
.gt-palette {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(210px, 100%), 1fr));
  gap: var(--s-2);
  list-style: none;
  margin: var(--s-4) 0 0;
  padding: 0;
}

.gt-swatch {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: 6px 10px;
  border: 1px solid var(--c-border, #e5e7eb);
  border-radius: var(--r-md, 8px);
  background: var(--c-surface-1, #fff);
  font-size: 0.82rem;
}

.gt-swatch-chip {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  border-radius: 4px;
  border: 1px solid rgba(0, 0, 0, 0.15);
}

.gt-swatch-num {
  font-weight: 700;
  min-width: 1.4em;
  /* On the two size calculators this is a measurement ("48 × 36 in"), and a
     measurement broken across two lines reads as two numbers. The note beside
     it may still wrap; that one is a sentence. */
  white-space: nowrap;
  color: var(--c-ink-1, #0a0a0a);
}

.gt-swatch-hex {
  font-family: var(--font-mono);
  font-size: 0.76rem;
  color: var(--c-ink-3, #737373);
}

/* --- ghost button variant ------------------------------------ */

.gt-btn--ghost {
  background: transparent;
  color: var(--c-ink-1, #0a0a0a);
  border-color: var(--c-border-strong, var(--c-border, #e5e7eb));
}

.gt-btn--ghost:hover:not(:disabled) {
  background: var(--c-surface-2, #fafafa);
  border-color: var(--c-ink-1, #0a0a0a);
}

.gt-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* --- checkbox rows ------------------------------------------- */

.gt-check {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin: 0 0 var(--s-3);
  font-size: 0.88rem;
  color: var(--c-ink-2, #404040);
  cursor: pointer;
}

@media (max-width: 640px) {
  .gt-canvas {
    max-height: 52vh;
  }
  .gt-paper-preview svg {
    max-height: 48vh;
  }
}

@media print {
  /* Printing a colouring page or a worksheet should give the artwork and
     nothing else — not the controls, not the site chrome. */
  .gt-tool-side,
  .gt-chips,
  .gt-status,
  .gt-tool-note,
  .gt-palette {
    display: none !important;
  }
  .gt-canvas,
  .gt-paper-preview {
    border: 0;
    max-height: none;
  }
}

/* --- the art projector --------------------------------------- */

/* The live camera view and the reference are separate LAYERS, never
   composited to a canvas: compositing every frame costs battery and
   gains nothing, because the user is looking at the screen rather
   than exporting a file. It also keeps the promise honest — the
   stream is displayed and never read.

   THE FRAME FILLS THE CARD (2026-09-07). It used to be an
   `aspect-ratio` box capped at 820px, inside a card that stretches
   to the control column's height — so at 1440 it was a ~300px black
   rectangle marooned in ~700px of white. An aspect-ratio box cannot
   flex-grow, so the ratio is gone and `object-fit: cover` on the
   video fills whatever shape the card produces instead.

   THE SLOT EXISTS FOR STAGE MODE. When `.gt-projector` goes
   `position: fixed` it leaves the flow, and without a box holding
   its place the card would collapse to its padding — which
   `_probe-bespoke-tool` rule 4b would then fail on the way back
   out. The slot keeps the flow box whatever the frame is doing. */
.gt-projector-slot {
  position: relative;
  flex: 1 1 auto;
  width: 100%;
  min-height: min(74vh, 460px);
  max-width: 1100px;
  margin-inline: auto;
}

.gt-projector {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: #111;
  border: 1px solid var(--c-border, #e5e7eb);
  border-radius: var(--r-md, 8px);
  touch-action: none;
}

/* THE FRAME IS RULED PAPER UNTIL THE CAMERA IS RUNNING, and it never
   prints a word over the reference. The old placeholder was a
   centred "The camera view appears here" that rendered on TOP of a
   loaded picture, because the reference draws whenever one exists
   and the placeholder drew whenever the camera was off. Both
   elements were individually correct; together they read as a
   broken page. The prose belongs in `.gt-status` above the frame,
   which is where it is now. */
.gt-projector[data-live="false"] {
  background-color: var(--c-surface-2, #fafafa);
  background-image:
    linear-gradient(to right, rgba(10, 10, 10, 0.07) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(10, 10, 10, 0.07) 1px, transparent 1px),
    linear-gradient(to right, rgba(10, 10, 10, 0.12) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(10, 10, 10, 0.12) 1px, transparent 1px);
  background-size: 24px 24px, 24px 24px, 96px 96px, 96px 96px;
}

.gt-projector:focus-visible {
  outline: 2px solid var(--c-ink-1, #0a0a0a);
  outline-offset: 2px;
}

.gt-projector-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* The wrapper carries the transform so it is written once, whichever
   of the two children is showing. The CHILD carries the size cap. */
.gt-projector-ref {
  position: absolute;
  top: 50%;
  left: 50%;
  transform-origin: center;
  user-select: none;
  -webkit-user-drag: none;
  pointer-events: none;
  line-height: 0;
}

.gt-projector-ref > img,
.gt-projector-ref > canvas {
  display: block;
  max-width: 90vmin;
  max-height: 90vmin;
  width: auto;
  height: auto;
}

/* The guide grid is fixed to the FRAME, above the reference: it is a
   registration aid on the screen, not part of the artwork. Two
   passes so it reads on white paper and on a dark surface alike. */
.gt-projector-grid {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  filter: drop-shadow(0 0 1.2px rgba(0, 0, 0, 0.9));
}

/* A white line with a dark drop shadow, rather than two sets of
   <line> elements: one shadow on the whole overlay reads on white
   paper AND on a dark surface, and leaves the element count honest
   so the probe can assert it. */
.gt-projector-grid line {
  stroke: rgba(255, 255, 255, 0.8);
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
}

/* --- the phone hand-off -------------------------------------- */

/* POINTER-EVENTS IS LOAD-BEARING, not a nicety. This block sits inside the
   drop plate, which is a <label> wrapping the real file input, so a click
   anywhere in it opens a file dialog. A visitor who taps the QR to look at it
   would get a file picker instead. */
.gt-dz-handoff {
  display: flex;
  align-items: center;
  gap: 14px;
  max-width: 420px;
  margin-top: var(--s-5, 20px);
  padding-top: var(--s-4, 16px);
  border-top: 1px solid var(--c-border, #e5e7eb);
  pointer-events: none;
  text-align: left;
}

.gt-dz-handoff-qr {
  flex: 0 0 auto;
  width: 96px;
  height: 96px;
  border-radius: var(--r-sm, 6px);
  /* A code needs its quiet zone to read; the viewBox carries four modules of
     it, and this keeps that white rather than letting the ruled plate show. */
  background: #ffffff;
}

.gt-dz-handoff-text {
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--c-ink-3, #737373);
}

@media (max-width: 560px) {
  .gt-dz-handoff {
    display: none;
  }
}

/* --- stage mode ---------------------------------------------- */

/* ONE NODE, ONE CLASS. Rendering the stage as a different element,
   or moving the frame in the tree, unmounts the <video> and drops
   its srcObject — the camera visibly flashes off and back. */
.gt-projector.is-stage {
  position: fixed;
  inset: 0;
  z-index: 900;
  border: 0;
  border-radius: 0;
}

.gt-stage-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 12px 14px calc(12px + env(safe-area-inset-bottom, 0px));
  background: linear-gradient(to top, rgba(0, 0, 0, 0.72), rgba(0, 0, 0, 0));
  transition: opacity 0.25s ease;
}

/* It retires while you draw and comes back on any input. Visibility,
   not display: a control that leaves the layout would reflow the
   frame under the user's hand. */
.gt-stage-bar.is-idle {
  opacity: 0;
}

.gt-stage-bar:focus-within {
  opacity: 1;
}

/* THE BAR'S SLIDER IS NOT IN A `.gt-field`, so the shell's range
   rules do not reach it and it would paint the browser's default
   blue on a black gradient. */
.gt-stage-bar input[type="range"] {
  flex: 1 1 120px;
  min-width: 100px;
  accent-color: #ffffff;
}

.gt-stage-btn {
  min-width: 44px;
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid rgba(255, 255, 255, 0.55);
  border-radius: var(--r-sm, 6px);
  background: rgba(0, 0, 0, 0.45);
  color: #ffffff;
  font: inherit;
  font-size: 0.88rem;
  cursor: pointer;
}

/* The pressed state paints a light ground, so its label has to go
   dark WITH it — the failure already recorded against the tool's own
   B&W toggle, where a pressed pill kept its light ink and rendered
   1.00:1. */
.gt-stage-btn.is-on {
  background: #ffffff;
  border-color: #ffffff;
  color: #0a0a0c;
}

.gt-stage-btn--exit {
  margin-left: auto;
}

/* ============================================================
   the proportion calculator — the drafting sheet (2026-09-06)
   ============================================================

   /tools/proportion-calculator/ answered its three questions in a
   five-row definition list at the top of the working card and left
   roughly 700px of that card empty underneath. What replaces it is
   not more furniture: it is the drawing the numbers describe — the
   two rectangles at true relative size, ruled at the chosen count,
   with the corresponding cell picked out on each. The markup and
   the geometry are in components/tools/ProportionCalculator.tsx;
   everything here is presentation.

   EVERY RULE THAT OVERRIDES THE SHELL REPEATS A CLASS THE ELEMENT
   REALLY HAS. `.gt-field` in app/globals.css is (0,1,0) and this
   sheet is a separate link element, so a plain `.pc-units-field`
   would be settled by source order between two files rather than by
   intent — the failure already recorded against the two
   `tier-s-bento-emoji-hide` patch sheets. `.gt-field.pc-units-field`
   is (0,2,0) and cannot be reordered into losing.

   Tokens are the SITE's — `--c-ink-*`, `--c-surface-N` — with a
   fallback on every one, per the note at the head of this file.
   ------------------------------------------------------------ */

/* --- the masthead: the scale factor, then the sentence -------- */

.pc-main .pc-head {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  align-items: end;
  gap: 8px 30px;
  margin: 0 0 26px;
  padding: 0 0 20px;
  border-bottom: 1px solid var(--c-border, #e5e5ea);
}

@media (max-width: 720px) {
  .pc-main .pc-head {
    grid-template-columns: minmax(0, 1fr);
    gap: 12px;
  }
}

/* 0.76rem, not 0.72. Every tracked-caps label in this block was written at
   0.72rem, which is 11.5px at a 16px root — under `audit-responsive`'s 12px
   floor at 320-414, on five selectors this pass introduced. It reports rather
   than gates, and adding to a report nobody can clear is how a report stops
   being read. */
.pc-eyebrow {
  display: block;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--c-ink-3, #737373);
  margin-bottom: 10px;
}

.pc-scale {
  display: block;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: clamp(2.8rem, 5.4vw, 4.4rem);
  line-height: 0.84;
  letter-spacing: -0.035em;
  font-variant-numeric: tabular-nums;
  color: var(--c-ink-1, #0a0a0a);
}

.pc-lede {
  margin: 0;
  padding-bottom: 0.35em;
  font-size: 0.97rem;
  line-height: 1.62;
  color: var(--c-ink-2, #404040);
  max-width: 56ch;
}

.pc-warn {
  margin-bottom: 24px;
}

/* --- the plate ----------------------------------------------- */

.pc-plate {
  margin: 0 0 28px;
  padding: 16px 16px 0;
  border: 1px solid var(--c-border, #e5e5ea);
  border-radius: 8px;
  background: var(--c-surface-1, #fff);
}

/* The drawing has no width or height attribute — it takes the column
   and its own viewBox ratio for the height, which is derived from
   what is being drawn rather than fixed, so a 3:1 reference and a 1:3
   reference are not both letterboxed into a square. The cap is the
   one case where `preserveAspectRatio` earns its keep: a very tall
   drawing letterboxes inside a shorter box instead of running most of
   a screen deep. */
.pc-svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: 62vh;
  font-family: var(--font-head);
}

/* THE ANNOTATION LAYER IS SWITCHED OFF WHERE THE PLATE CANNOT CARRY IT.

   Type inside an svg is in USER UNITS, so its painted size is the plate's
   width over 1000 and no unit or property opts out of that. On a 390px
   phone the plate is ~358px, where the 20-unit labels paint at 7px — a
   drawing annotated in type nobody can read, which is worse than one that
   is not annotated at all. Below 820px the dimension lines, the leader's
   label, the callout and the stretch label all go, and the figure's caption
   states both sizes instead: it is real HTML at 13px, so it is legible at
   every width, and it is written from what was actually drawn.

   The stacked layout is what makes 820 the right number: at 1024 and below
   the tool is one column, so the plate is nearly the whole page — ~858px at
   1024, ~620px at 768. Above 1024 the control column takes 320 back and the
   narrowest plate in the two-column range is ~577px, which is the floor the
   20-unit size was chosen against. */
@media (max-width: 820px) {
  .pc-svg .pc-anno {
    display: none;
  }
}

.pc-plate figcaption {
  margin: 0;
  padding: 14px 2px 16px;
  font-size: 0.82rem;
  line-height: 1.55;
  color: var(--c-ink-3, #737373);
  max-width: 72ch;
}

/* --- the ledger ---------------------------------------------- */

.pc-ledger-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--c-border, #e5e5ea);
}

.pc-ledger-head h3 {
  margin: 0;
  font-family: var(--font-head);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--c-ink-3, #737373);
}

.pc-copy {
  font: inherit;
  font-size: 0.8rem;
  font-weight: 500;
  line-height: 1.2;
  padding: 0.5em 0.85em;
  border: 1px solid var(--c-border, #e5e5ea);
  border-radius: var(--r-sm, 6px);
  background: var(--c-surface-1, #fff);
  color: var(--c-ink-1, #0a0a0a);
  cursor: pointer;
}

.pc-copy:hover {
  border-color: var(--c-ink-3, #737373);
}

.pc-copy:focus-visible {
  outline: 2px solid var(--c-ink-1, #0a0a0a);
  outline-offset: 1px;
}

/* `min(212px, 100%)`, never a bare px floor — CLAUDE.md rule 11c. */
.pc-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(212px, 100%), 1fr));
  gap: 0 28px;
  margin: 0;
}

.pc-list > div {
  padding: 13px 0;
  border-bottom: 1px solid var(--c-border, #e5e5ea);
}

.pc-list dt {
  font-size: 0.76rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--c-ink-3, #737373);
  margin-bottom: 4px;
}

.pc-list dd {
  margin: 0;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: 1.1rem;
  font-variant-numeric: tabular-nums;
  color: var(--c-ink-1, #0a0a0a);
}

/* --- the control column: a size is one row, not two ---------- */

.pc-size-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: end;
  gap: 10px;
  margin-bottom: 12px;
}

.pc-size-row .gt-field {
  flex-direction: column;
  align-items: stretch;
  gap: 5px;
  margin: 0;
}

.pc-size-row .gt-field label {
  flex: none;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--c-ink-3, #737373);
}

.pc-size-row .gt-field input {
  width: 100%;
  font-variant-numeric: tabular-nums;
}

/* A disabled field here is a DERIVED one, not an unavailable one, so
   it keeps its ink and takes a tinted ground instead. The
   -webkit-text-fill-color line is load-bearing: WebKit paints a
   disabled control's text at its own washed-out colour and ignores
   `color`, which would leave the derived height — a figure the reader
   is meant to read off — at about 2.5:1. */
.pc-size-row .gt-field input:disabled {
  background: var(--c-surface-2, #fafafa);
  color: var(--c-ink-2, #404040);
  -webkit-text-fill-color: var(--c-ink-2, #404040);
  opacity: 1;
  cursor: default;
}

.pc-times {
  padding-bottom: 0.5em;
  font-size: 0.95rem;
  color: var(--c-ink-3, #737373);
}

.pc-hint {
  margin: -6px 0 14px;
}

/* Doubled for the reason at the head of this block: `.gt-check` is declared
   once in app/globals.css and again in this file, both at (0,1,0), so a plain
   `.pc-lock` would be settled by which stylesheet the head happens to put
   last. */
.gt-check.pc-lock {
  margin: 2px 0 4px;
}

/* --- units: a segmented control, not a dropdown -------------- */

/* Four options of two characters each is the case a select element is
   worst at — it hides three of the four behind a click, on the one
   field this page is about. The row is hairline-divided rather than
   gapped so it reads as one control with a state, which is also what
   keeps it inside the 24px tap-target minimum on a phone without
   growing. */
.gt-field.pc-units-field {
  display: block;
  margin-bottom: 16px;
}

.gt-field.pc-units-field > label {
  display: block;
  flex: none;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--c-ink-3, #737373);
  margin-bottom: 6px;
}

.pc-units {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  border: 1px solid var(--c-border, #e5e5ea);
  border-radius: var(--r-sm, 6px);
  overflow: hidden;
  background: var(--c-surface-1, #fff);
}

.pc-unit {
  font: inherit;
  font-size: 0.86rem;
  font-weight: 600;
  line-height: 1.2;
  padding: 0.62em 0.2em;
  border: 0;
  border-left: 1px solid var(--c-border, #e5e5ea);
  background: transparent;
  color: var(--c-ink-2, #404040);
  cursor: pointer;
}

.pc-unit:first-child {
  border-left: 0;
}

.pc-unit:hover {
  background: var(--c-surface-2, #fafafa);
  color: var(--c-ink-1, #0a0a0a);
}

.pc-unit.is-on {
  background: var(--c-ink-1, #0a0a0a);
  color: var(--c-surface-1, #fff);
}

.pc-unit:focus-visible {
  outline: 2px solid var(--c-ink-1, #0a0a0a);
  outline-offset: -2px;
}

/* The unit's full name is the button's accessible name and nothing
   else — "mm" alone is not a word. Clipped rather than hidden, so it
   is still read out; `audit-responsive` knows this idiom and skips a
   1px box on purpose. */
.pc-unit-full {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.pc-cells label strong {
  font-variant-numeric: tabular-nums;
  color: var(--c-ink-1, #0a0a0a);
}

@media print {
  .pc-copy {
    display: none;
  }
  .pc-svg {
    max-height: none;
  }
}

/* ===========================================================================
   THE INSTAGRAM GRID MAKER — the imposition plate (2026-09-06)
   ---------------------------------------------------------------------------
   `components/tools/InstagramGridMaker.tsx`, on /tools/instagram-grid-maker/
   and /tools/instagram-grid-splitter/. Two figures side by side: the PLATE (the
   photograph with the cuts, the gutters and the crop loss drawn on it) and the
   PROOF (the same tiles as a profile lays them out). Then the posting register
   underneath.

   It takes the /tools/ cohort's own idiom rather than inventing one — print
   crop marks at the corners of a ruled plate, tracked-caps keys, tabular
   figures, a dot-ruled register — because this widget sits directly above 100
   pages of exactly that.

   THE COLUMN COUNT IS CONTAINER-DRIVEN, and on this page a viewport rule would
   be wrong in the direction already recorded against the bento, the audience
   register and the Related index. `.gt-tool-main` is ~960px at a 1440 viewport,
   ~662px at 1100 (the sidebar is still beside it), and ~928px again at 1024
   (the grid has stacked and the main column has the page). So the same viewport
   gives the proof three different widths, and the narrowest of them is in the
   MIDDLE of the range.

   `.ig-work` exists to be that container: an element cannot query its own
   `container-type`, and putting one on `.gt-tool-main` would apply containment
   to the other fifteen widgets.

   Tokens: `--c-ink-*` / `--c-surface-*` / `--c-border`, always with a fallback.
   `--c-text-*` is the FROZEN TOOL's vocabulary and does not exist out here.
   ======================================================================== */

.ig-work {
  container: ig-work / inline-size;
}

.ig-proof {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 26px;
  align-items: start;
}

/* 880px of container: a 302px proof beside a plate that still has 546px is the
   point at which two columns say more than one. Below it the proof would be
   reading a three-column profile at 90px a thumbnail. */
@container ig-work (min-width: 880px) {
  .ig-proof {
    grid-template-columns: minmax(0, 1fr) 302px;
    gap: 32px;
  }
}

.ig-plate,
.ig-profile {
  margin: 0;
  min-width: 0;
}

/* ---- the plate ----------------------------------------------------------- */

/* The margin is the crop marks' room. They hang 9px outside the plate, which is
   what makes them read as register marks on a sheet rather than as a border
   with gaps in it. */
/* `fit-content`, so the box is exactly the canvas and the four crop marks land
   on the PLATE's corners rather than on the column's. The canvas is height-
   capped in JS and can be narrower than the column it sits in, and marks pinned
   to the column would then float in the margin beside it. The ResizeObserver is
   on `.ig-plate` (the figure) for the same reason — observing a fit-content box
   that is sized from its own child is a feedback loop. */
.ig-plate-box {
  position: relative;
  width: fit-content;
  margin: 9px auto 11px;
}

.ig-stage {
  display: block;
  max-width: 100%;
  height: auto;
  background: var(--c-surface-2, #fafafa);
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}

.ig-stage:active {
  cursor: grabbing;
}

.ig-stage:focus-visible {
  outline: 2px solid var(--c-ink-1, #0a0a0a);
  outline-offset: 3px;
}

.ig-mark {
  position: absolute;
  width: 13px;
  height: 13px;
  pointer-events: none;
  border: 0 solid var(--c-ink-3, #737373);
}

.ig-mark--tl { top: -9px; left: -9px; border-top-width: 1px; border-left-width: 1px; }
.ig-mark--tr { top: -9px; right: -9px; border-top-width: 1px; border-right-width: 1px; }
.ig-mark--bl { bottom: -9px; left: -9px; border-bottom-width: 1px; border-left-width: 1px; }
.ig-mark--br { bottom: -9px; right: -9px; border-bottom-width: 1px; border-right-width: 1px; }

/* ---- the proof ----------------------------------------------------------- */

/* A 3 x 9 grid is 27 thumbnails, which at a 302px column is ~1,170px of
   profile. It scrolls, because that is what a profile does — capping the row
   count instead would hide the half of a long plan that is hardest to picture. */
/* Capped and centred even when the proof is stacked under the plate at full
   column width: a 936px-wide model of a phone profile is not a model of
   anything. In the 302px column the cap is not binding. */
.ig-profile-box {
  max-width: 340px;
  margin-inline: auto;
  border: 1px solid var(--c-border, #e5e7eb);
  border-radius: 14px;
  padding: 10px;
  background: var(--c-surface-1, #fff);
  max-height: 620px;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.ig-profile-canvas {
  display: block;
  width: 100%;
  height: auto;
}

/* ---- both captions ------------------------------------------------------- */

.ig-cap {
  display: flex;
  gap: 12px;
  align-items: baseline;
  margin-top: 14px;
  padding-top: 11px;
  border-top: 1px solid var(--c-border, #e5e7eb);
  font-size: 0.8rem;
  line-height: 1.55;
  color: var(--c-ink-2, #404040);
}

.ig-cap-k {
  flex: 0 0 auto;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--c-ink-1, #0a0a0a);
}

/* ---- the sidebar's own two lines ----------------------------------------- */

.ig-spec {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 2px 0 4px;
  font-size: 0.82rem;
}

.ig-spec > span:first-child {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--c-ink-1, #0a0a0a);
}

.ig-spec > span + span {
  color: var(--c-ink-3, #737373);
}

/* Monochrome on purpose — the site has had no chromatic accent since the
   2026-06-19 orange-to-grey pass, so a note earns its weight from a rule and
   its position, not from a colour nothing else on the page uses. */
.ig-warn {
  margin: 4px 0 12px;
  padding: 9px 0 9px 12px;
  border-left: 2px solid var(--c-ink-1, #0a0a0a);
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--c-ink-2, #404040);
}

.ig-check {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  margin: 12px 0;
  font-size: 0.88rem;
  line-height: 1.45;
  color: var(--c-ink-2, #404040);
  cursor: pointer;
}

.ig-check input {
  flex: 0 0 auto;
  margin-top: 1px;
}

/* `.gt-btn-primary` is globally `width: 100%`, which is right where it is a
   lone CTA at the foot of a control column (PhotoGridMaker's export rail) and
   wrong in a wrap-flex row in the working area: it painted a 1,230px black bar
   across the plate with "Use a different picture" stranded under it. Scoped
   here rather than fixed globally, because the global default is load-bearing
   on the other page that uses it. */
.ig-work .gt-tool-actions .gt-btn-primary {
  width: auto;
}

/* ---- the posting register ------------------------------------------------ */

.ig-register {
  margin-top: 30px;
  padding-top: 20px;
  border-top: 1px solid var(--c-border, #e5e7eb);
}

.ig-register-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 10px;
  margin: 0 0 14px;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--c-ink-1, #0a0a0a);
}

.ig-register-head > span + span {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.08em;
  color: var(--c-ink-3, #737373);
}

/* `min(310px, 100%)` and not a bare 310px: a fixed floor in an auto-fill track
   holds the column open on a 288px phone and scrolls the page sideways
   (CLAUDE.md rule 11c). */
.ig-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(310px, 100%), 1fr));
  gap: 0 34px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* FOUR columns and four children. The first pass had five — the filename and
   its place in the grid were separate cells — so grid auto-placement pushed the
   Save button onto a row of its own under every entry and the register ran to
   twice its height with a hole in each row. */
.ig-row {
  display: grid;
  grid-template-columns: auto 2.2ch minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  padding: 9px 0;
  border-bottom: 1px dotted var(--c-border-strong, #d4d4d8);
}

.ig-meta {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.ig-thumb {
  display: block;
  border-radius: 2px;
  background: var(--c-surface-2, #fafafa);
}

.ig-n {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 0.92rem;
  text-align: right;
  color: var(--c-ink-1, #0a0a0a);
}

.ig-name {
  min-width: 0;
  font-size: 0.84rem;
  overflow-wrap: break-word;
  color: var(--c-ink-2, #404040);
}

.ig-where {
  display: block;
  font-size: 0.76rem;
  color: var(--c-ink-3, #737373);
}

.ig-save {
  min-height: 30px;
  padding: 0.3em 0.75em;
  font-size: 0.78rem;
}

/* ---- motion -------------------------------------------------------------- */

@media (prefers-reduced-motion: no-preference) {
  .ig-proof {
    animation: ig-settle 0.34s cubic-bezier(0.22, 0.61, 0.36, 1) both;
  }
  @keyframes ig-settle {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: none; }
  }
}

/* A disabled position slider means the crop has no slack on that axis — it is
   telling the truth about the framing, so it has to look inert rather than
   broken. */
.gt-field input[type="range"]:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.gt-field:has(> input[type="range"]:disabled) > label {
  color: var(--c-ink-3, #737373);
}

/* ==========================================================================
   THE IMAGE SPLITTER'S WORKING SURFACES (2026-09-06)
   /tools/image-splitter/, /tools/instagram-grid-maker/,
   /tools/instagram-grid-splitter/

   The widget used to show a drop zone and then a bare grid of finished tiles,
   which meant the one question a person actually has — WHERE DOES IT CUT — was
   only answerable after committing to a cut. There are two surfaces now and
   they answer two different questions:

     .gt-plate   the picture with the cuts ruled over it
     .gt-sheet   what comes out of the exporter, at the shape it will be

   NEITHER OF THEM RASTERISES ANYTHING. The plate is hairline divs positioned by
   percentage; the sheet is one background-image per cell cropped by
   background-size + background-position, which reproduces drawImage's eight
   arguments exactly. That is what lets the overlap and inset sliders preview
   live on a 24-megapixel photograph — the canvas only runs on export.

   The visual language is the one this site already speaks everywhere else: a
   trim guide on a print plate. Hairline dashed rules, tracked-caps register
   labels, crop-mark grips, and no colour anywhere.
   ========================================================================== */

/* ---- the header row over the working surface ----------------------------- */

.gt-workbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-3, 12px);
  margin: 0 0 18px;
  padding: 0 0 14px;
  border-bottom: 1px solid var(--c-border, #e5e7eb);
}

/* A ledger, not a sentence: four facts a person checks at a glance and then
   stops reading. A min() floor so it never holds a track wider than the card
   (rule 11c). */
.gt-ledger {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(112px, 100%), max-content));
  gap: 4px 26px;
  margin: 0;
  min-width: 0;
}

.gt-ledger > div { min-width: 0; }

.gt-ledger dt {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--c-ink-3, #737373);
  margin: 0 0 2px;
}

.gt-ledger dd {
  margin: 0;
  font-size: 0.94rem;
  font-weight: 600;
  color: var(--c-ink-1, #0a0a0a);
  font-variant-numeric: tabular-nums;
}

/* ---- segmented control --------------------------------------------------- */

/* One hairline box divided by hairlines, rather than three separate pills: the
   options are exclusive, and a divided box is what says so. Same reasoning as
   the /tools/ Capabilities matrix, where the rules ARE the 1px gaps. */
.gt-seg {
  display: inline-flex;
  flex: 0 0 auto;
  padding: 2px;
  gap: 2px;
  border: 1px solid var(--c-border, #e5e7eb);
  border-radius: var(--r-sm, 6px);
  background: var(--c-surface-2, #fafafa);
}

.gt-seg-btn {
  font: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  line-height: 1;
  /* 0.62em top and bottom over a 0.82rem face is a ~31px control, clear of the
     24px floor in WCAG 2.2 SC 2.5.8 with no spacing exception needed. */
  padding: 0.62em 0.85em;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--c-ink-2, #404040);
  cursor: pointer;
  white-space: nowrap;
}

.gt-seg-btn:hover { color: var(--c-ink-1, #0a0a0a); }

.gt-seg-btn.is-on {
  background: var(--c-ink-1, #0a0a0a);
  color: #fff;
}

.gt-seg-btn:focus-visible {
  outline: 2px solid var(--c-ink-1, #0a0a0a);
  outline-offset: 2px;
}

/* ---- the plate: where it cuts -------------------------------------------- */

.gt-plate { margin: 0; }

/* Shrink-to-fit so the marks land on the picture and not on the card. The box
   is the positioning context for every rule, guide and label on it. */
.gt-plate-box {
  position: relative;
  display: block;
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
  overflow: hidden;
  border-radius: var(--r-sm, 6px);
  background: var(--c-surface-2, #fafafa);
  box-shadow: var(--shadow-md, 0 2px 12px rgba(0, 0, 0, 0.08));
}

.gt-plate-img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 62vh;
}

/* THE WORKING RECT. It is the whole picture unless the tile shape is being paid
   for by cropping the source, and then the part being cropped away has to be
   visible: dimming it is the only way to say "these pixels will not be in any
   tile" without moving anything. The wash is a huge spread box-shadow rather
   than four divs, and .gt-plate-box clips it. */
.gt-frame {
  position: absolute;
  outline: 1px solid rgba(10, 10, 12, 0.55);
  outline-offset: -1px;
}

.gt-frame.is-cropped {
  box-shadow: 0 0 0 9999px rgba(250, 250, 250, 0.74);
}

/* A cut is a dashed hairline with a white hairline either side of it, because
   the picture underneath is arbitrary: a plain dark rule vanishes on a dark
   photograph and a plain light one vanishes on a light one. Two-tone is
   legible on both, which is why a cutting mat is drawn this way. */
.gt-cut {
  position: absolute;
  pointer-events: none;
}

.gt-cut--v {
  top: 0;
  bottom: 0;
  width: 0;
  border-left: 1px dashed rgba(10, 10, 12, 0.92);
  box-shadow: 1px 0 0 rgba(255, 255, 255, 0.6), -1px 0 0 rgba(255, 255, 255, 0.6);
}

.gt-cut--h {
  left: 0;
  right: 0;
  height: 0;
  border-top: 1px dashed rgba(10, 10, 12, 0.92);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.6), 0 -1px 0 rgba(255, 255, 255, 0.6);
}

/* The overlap band: the strip of picture that ends up in BOTH neighbours. It is
   the only way to see what a 40px bleed costs before printing twelve sheets. */
.gt-bleed {
  position: absolute;
  pointer-events: none;
  background: rgba(10, 10, 12, 0.16);
}

.gt-bleed--v { top: 0; bottom: 0; }
.gt-bleed--h { left: 0; right: 0; }

/* A dragged cut. The hit area is 24px (SC 2.5.8) and transparent; the visible
   line is the ::before, so a person aims at a 24px target and sees a hairline.
   Having to grab a 1px line is why guide-dragging is rare and horrible in the
   few tools that do offer it. */
.gt-guide {
  position: absolute;
  padding: 0;
  border: 0;
  background: none;
  cursor: grab;
  touch-action: none;
  z-index: 2;
}

.gt-guide:active { cursor: grabbing; }

.gt-guide--v {
  top: 0;
  bottom: 0;
  width: 24px;
  transform: translateX(-12px);
}

.gt-guide--h {
  left: 0;
  right: 0;
  height: 24px;
  transform: translateY(-12px);
}

.gt-guide::before {
  content: "";
  position: absolute;
  background: rgba(10, 10, 12, 0.92);
}

.gt-guide--v::before {
  top: 0;
  bottom: 0;
  left: 50%;
  width: 1px;
  margin-left: -0.5px;
  box-shadow: 1px 0 0 rgba(255, 255, 255, 0.6), -1px 0 0 rgba(255, 255, 255, 0.6);
}

.gt-guide--h::before {
  left: 0;
  right: 0;
  top: 50%;
  height: 1px;
  margin-top: -0.5px;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.6), 0 -1px 0 rgba(255, 255, 255, 0.6);
}

/* The grip is a drafting-square mark at the head of the guide, so the line
   reads as something you can take hold of rather than as decoration. */
.gt-guide-grip {
  position: absolute;
  width: 11px;
  height: 11px;
  background: var(--c-surface-1, #fff);
  border: 1px solid rgba(10, 10, 12, 0.92);
}

.gt-guide--v .gt-guide-grip { top: 6px; left: 50%; transform: translateX(-50%) rotate(45deg); }
.gt-guide--h .gt-guide-grip { left: 6px; top: 50%; transform: translateY(-50%) rotate(45deg); }

.gt-guide:hover .gt-guide-grip,
.gt-guide:focus-visible .gt-guide-grip {
  background: var(--c-ink-1, #0a0a0a);
}

.gt-guide:focus-visible {
  outline: 2px solid var(--c-ink-1, #0a0a0a);
  outline-offset: -1px;
}

/* The register label. Centred on its cell, on an opaque ground so it survives a
   busy photograph. */
.gt-cell-tag {
  position: absolute;
  transform: translate(-50%, -50%);
  padding: 3px 7px;
  font-family: var(--font-head);
  font-size: 0.68rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.06em;
  color: var(--c-ink-1, #0a0a0a);
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid rgba(10, 10, 12, 0.35);
  border-radius: 3px;
  pointer-events: none;
  font-variant-numeric: tabular-nums;
}

.gt-plate figcaption {
  margin: 14px 0 0;
  font-size: 0.84rem;
  line-height: 1.5;
  color: var(--c-ink-3, #737373);
  text-align: center;
}

/* ---- the sheet: what comes out ------------------------------------------- */

/* A contact sheet. The gap is the ground showing through, so the cut is legible
   as a cut; the cells carry the real output aspect, which is the only place
   tile shape, fit and inset are visible before export. */
.gt-sheet {
  display: grid;
  gap: 6px;
  padding: 10px;
  background: var(--c-surface-2, #fafafa);
  border-radius: var(--r-md, 8px);
}

.gt-sheet-cell {
  position: relative;
  display: block;
  width: 100%;
  min-width: 0;
  padding: 0;
  border: 0;
  overflow: hidden;
  cursor: pointer;
  outline: 1px solid rgba(10, 10, 12, 0.12);
  outline-offset: -1px;
}

/* The picture, cropped by the same eight numbers the exporter will use. */
.gt-sheet-crop {
  position: absolute;
  background-repeat: no-repeat;
}

.gt-sheet-tag {
  position: absolute;
  left: 4px;
  top: 4px;
  padding: 2px 5px;
  font-family: var(--font-head);
  font-size: 0.64rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.05em;
  color: #fff;
  background: rgba(10, 10, 12, 0.76);
  border-radius: 3px;
  font-variant-numeric: tabular-nums;
}

/* "Save" is stated on hover and on keyboard focus rather than printed on every
   cell: nine permanent captions over nine pieces of a picture is the picture
   you can no longer see. */
.gt-sheet-save {
  position: absolute;
  inset: auto 0 0 0;
  padding: 5px 0;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  text-align: center;
  color: #fff;
  background: rgba(10, 10, 12, 0.82);
  opacity: 0;
  transform: translateY(100%);
  transition: opacity 0.16s ease, transform 0.16s ease;
}

.gt-sheet-cell:hover .gt-sheet-save,
.gt-sheet-cell:focus-visible .gt-sheet-save {
  opacity: 1;
  transform: none;
}

.gt-sheet-cell:hover,
.gt-sheet-cell:focus-visible {
  outline: 2px solid var(--c-ink-1, #0a0a0a);
  outline-offset: -2px;
}

@media (prefers-reduced-motion: reduce) {
  .gt-sheet-save { transition: none; }
}

/* Under ~430px a nine-up contact sheet is nine 90px thumbnails, and the gap and
   the labels are most of what is left, so both come down. */
@media (max-width: 430px) {
  .gt-sheet { gap: 4px; padding: 6px; }
  .gt-sheet-tag { font-size: 0.58rem; padding: 1px 4px; }
  .gt-cell-tag { font-size: 0.6rem; padding: 2px 5px; }
  .gt-workbar { gap: 10px; }
  .gt-ledger { gap: 4px 18px; }
}

/* The running commentary a screen reader needs and nobody else does: the tile
   count and size change on every control, and an export runs for seconds. */
.gt-sr-live {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ===========================================================================
   /tools/mandala-generator/ — the drawing plate                  (2026-09-08)
   ===========================================================================

   THE PLATE IS SIZED IN JAVASCRIPT AND THE CANVAS IS ABSOLUTELY POSITIONED
   INSIDE IT, and both halves of that are load-bearing.

   Sized in JS because the backing store has to be `size * devicePixelRatio` —
   the drawing is thin repeated lines and a 1.25x upscale is the whole of what
   it looks like — and a CSS-sized canvas gives the component no honest number
   to size the bitmap from. `.gt-canvas`'s own `width: 100%; height: auto;
   max-height: 70vh; object-fit: contain` is exactly the arrangement that put
   letterbox bars down both sides of this figure once before (the ruled-ground
   trap recorded against `data-empty` in the block above), so `max-height` is
   released here and the element carries an explicit square box instead.

   Absolutely positioned because the plate's height comes from the CARD — it is
   `flex: 1 1 auto` in a column that `align-self: stretch`es to the control
   column beside it — and a canvas that contributed its own height to the box
   whose height decides its size is the feedback loop already paid for on
   InstagramGridMaker. Out of flow, it cannot.
   ------------------------------------------------------------------------ */

.mg-plate {
  position: relative;
  /* Square by default; grown by the card at >=1024, floored on a phone. The
     max-height is what stops a 1400px card producing a 1400px plate and pushing
     every other band off the screen — the leftover width either side then reads
     as the mat the hero tool's own canvas has. */
  aspect-ratio: 1 / 1;
  min-height: 260px;
  max-height: 74vh;
  margin: 0 0 16px;
}

.gt-tool-main > .mg-plate { flex: 1 1 auto; }

.gt-canvas.mg-canvas {
  position: absolute;
  inset: 0;
  margin: auto;
  /* Released: the box and the bitmap are the same square, set together in JS. */
  max-height: none;
  object-fit: fill;
  cursor: crosshair;
  touch-action: none;
}

/* The eraser removes a whole stroke and all of its repeats, so the pointer says
   so rather than pretending to rub something out. */
.gt-canvas.mg-canvas[data-mode="erase"] { cursor: pointer; }

/* ---- the saved-drawing offer --------------------------------------------- */

/* OFFERED, NEVER APPLIED. Auto-loading last week's mandala over the fresh plate
   somebody has just arrived at is worse than asking; this is the only tool in
   the cohort whose previous session is a whole artefact rather than a set of
   preferences. */
.mg-offer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 16px;
  margin: 0 0 16px;
  padding: 12px 14px;
  border: 1px solid var(--c-border, #e5e7eb);
  border-left: 3px solid var(--c-ink-1, #0a0a0a);
  border-radius: var(--r-sm, 6px);
  background: var(--c-surface-2, #fafafa);
  font-size: 0.9rem;
  color: var(--c-ink-2, #404040);
}

.mg-offer-acts { display: flex; flex-wrap: wrap; gap: 8px; }
.mg-offer-acts .gt-btn { padding: 0.5em 0.9em; font-size: 0.88rem; }

/* ---- the control column --------------------------------------------------- */

/* Six marks will not sit on one line in a 280px column, and a segmented control
   that scrolls sideways hides half its options. It wraps, and the box keeps its
   hairline frame around all of them. */
/* A GRID OF THREE, NOT A WRAPPING ROW. Six marks cannot sit on one line in a
   272px control column, and left to wrap they rag 5 + 1 with "Erase" stranded
   and stretched across a second line — which reads as a control that ran out of
   room rather than one laid out. Two rows of three is the same box, deliberate,
   and gives every option the same target. `flex: none` on the buttons is
   load-bearing: `.gt-seg-btn` is written for a row, and left alone the flex
   shorthand becomes a HEIGHT rule once the container's main axis is no longer
   horizontal — the trap already recorded against `.gt-field select`. */
.gt-seg.mg-modes {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.gt-seg.mg-modes .gt-seg-btn { flex: none; padding-inline: 0.6em; }

/* EIGHT COLUMNS, STATED. The same rag one control down: eight 30px swatches
   with 6px gutters need 282px and the column offers 272, so the last ink
   dropped to a line of its own. Stated as a grid they always sit in one row and
   scale with the column instead. */
.gt-chips.mg-swatches {
  display: grid;
  grid-template-columns: repeat(8, minmax(0, 1fr));
  gap: 4px;
  /* The sidebar is full width below 1024, where a free-scaling row would set
     56px swatches. This is a palette, not a set of buttons. */
  max-width: 320px;
}

/* Square, and never under 24px. WCAG 2.2 SC 2.5.8 wants 24 and these sit in a
   4px gutter, so the spacing exception would be doing the work; it does not
   need to. The narrowest column this ever renders in is the 280px sidebar at
   1400px, whose 232px of content gives (232 - 7x4) / 8 = 25.5px. */
.mg-swatch {
  width: auto;
  aspect-ratio: 1 / 1;
  min-width: 24px;
  padding: 0;
  border: 1px solid var(--c-border, #e5e7eb);
  border-radius: var(--r-sm, 6px);
  cursor: pointer;
}

/* The selected ink is marked with a RING outside the swatch, never by tinting
   the swatch itself — the swatch's whole job is to show one exact colour, and a
   selected state that changes it is a swatch that lies. */
.mg-swatch.is-on {
  border-color: var(--c-ink-1, #0a0a0a);
  box-shadow: 0 0 0 2px var(--c-surface-1, #fff), 0 0 0 3px var(--c-ink-1, #0a0a0a);
}

.mg-swatch:focus-visible {
  outline: 2px solid var(--c-ink-1, #0a0a0a);
  outline-offset: 3px;
}

.gt-presets.mg-place { margin-bottom: 6px; }

/* The download row is the last thing in the column and stands clear of the
   three stroke actions above it: they act on the drawing, this one leaves with
   it. */
.gt-tool-actions.mg-export { margin-top: 12px; }

.mg-ledger { margin: 14px 0 0; }

@media (max-width: 640px) {
  .mg-plate { max-height: none; }
  .mg-offer { font-size: 0.86rem; }
}

/* A TRANSPARENT GROUND NEEDS A GROUND TO BE TRANSPARENT AGAINST. With "None"
   the canvas is cleared rather than filled, so on a white plate a pale ink is
   invisible on screen while being perfectly correct in the file. The checker is
   the universal indicator, it says what the export will be, and it gives light
   ink something to read against. It is CSS behind the bitmap, so it can never
   reach the PNG. */
.gt-canvas.mg-canvas[data-ground="none"] {
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #e9e9ee 25%, transparent 25%, transparent 75%, #e9e9ee 75%),
    linear-gradient(45deg, #e9e9ee 25%, transparent 25%, transparent 75%, #e9e9ee 75%);
  background-size: 22px 22px;
  background-position: 0 0, 11px 11px;
}

/* ==========================================================================
   Paint by number generator — /tools/paint-by-number-generator/
   ==========================================================================
   Rebuilt 2026-09-09 alongside the widget. Five classes, all of them things
   the shared vocabulary genuinely does not have: a hint under a segmented
   control, an inline qualifier inside a label, the plate's own toolbar, an
   error box, and the coverage figure in the palette legend.

   EVERY TOKEN CARRIES A FALLBACK. This sheet is loaded globally by layout.tsx
   and styles markup OUTSIDE `.gt-root`, where the frozen tool's `--c-text-*`
   names do not exist — seven references without fallbacks once made a muted
   grey inherit near-black across six widgets, silently, because an invalid
   `var()` is invalid at computed-value time and the property inherits. Use
   `--c-ink-*` / `--c-surface-N`, always with a fallback. */

/* The preset's one line of consequence. It sits under the segmented control
   and explains what "Simple" costs, which is the question the control raises. */
.pbn-hint {
  margin: 8px 0 0;
  font-size: 0.8rem;
  line-height: 1.45;
  color: var(--c-ink-3, #737373);
}

/* "(the picture only holds 7)" — the quantiser returns the colours a picture
   HAS rather than the number the slider asked for, so the label has to be able
   to say so without becoming a second line of chrome. */
.pbn-note {
  font-weight: 400;
  color: var(--c-ink-3, #737373);
}

/* The plate's toolbar: the view control at the left, the status at the right.
   `min-width: 0` on the status is what lets it ellipsise rather than push the
   segmented control off the card — a flex item's automatic minimum size is its
   min-content width, and `max-width` alone does not release it. */
.pbn-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 16px;
  margin: 0 0 12px;
}

.pbn-bar .gt-status {
  margin: 0;
  min-width: 0;
}

.pbn-error {
  margin: 0 0 12px;
  padding: 10px 12px;
  border: 1px solid var(--c-border-strong, #d4d4d8);
  border-left: 3px solid var(--c-ink-1, #0a0a0a);
  border-radius: var(--r-sm, 6px);
  background: var(--c-surface-2, #fafafa);
  font-size: 0.86rem;
  line-height: 1.5;
  color: var(--c-ink-1, #0a0a0a);
}

/* How much of the picture this paint covers. It is pushed to the end of the
   swatch row, which is what makes the column read as a column of figures; and
   it is tabular, because these are compared down the list rather than read. */
.pbn-share {
  margin-left: auto;
  padding-left: 8px;
  /* 0.78rem is 12.5px at a 16px root. NOT 0.74 — that is 11.8px, under
     `audit-responsive` rule 2's floor, and that audit cannot see it: the legend
     only exists after a photograph is loaded, so the sweep never reaches it. A
     control that a gate cannot reach has to be got right by hand. */
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  color: var(--c-ink-3, #737373);
}

@media (max-width: 640px) {
  .pbn-bar { align-items: flex-start; }
}

/* ==============================================================
   /tools/coloring-page-generator/ — ColoringPageGenerator.tsx
   ============================================================== */

/* THE PREVIEW IS A SHEET OF PAPER, so it is presented as one: a mat that
   hugs the sheet, the sheet itself with a shadow under it, and print crop
   marks at the four corners — the same drafting language as the /tools/
   plate gallery and the Related index. `width: fit-content` is what makes
   the marks land on the SHEET rather than on the card; full width they
   would sit in the corners of a 960px band with a 450px A4 page in the
   middle of it, which is the defect `.gt-paper-preview` already records. */
.cp-sheet {
  position: relative;
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
  padding: var(--s-4, 20px);
}

.cp-sheet-canvas {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  /* The sheet has to stay whole on screen, and a portrait A4 at the card's
     full width is taller than most laptop viewports. */
  max-height: 64vh;
  background: #fff;
  box-shadow: var(--shadow-md, 0 2px 12px rgba(0, 0, 0, 0.08));
}

/* Two hairlines each, drawn as borders on an empty box, inset just outside
   the paper's own corner. */
.cp-crop {
  position: absolute;
  width: 13px;
  height: 13px;
  border: 0 solid var(--c-ink-3, #737373);
  pointer-events: none;
}
.cp-crop--tl { top: 7px; left: 7px; border-top-width: 1px; border-left-width: 1px; }
.cp-crop--tr { top: 7px; right: 7px; border-top-width: 1px; border-right-width: 1px; }
.cp-crop--bl { bottom: 7px; left: 7px; border-bottom-width: 1px; border-left-width: 1px; }
.cp-crop--br { bottom: 7px; right: 7px; border-bottom-width: 1px; border-right-width: 1px; }

/* The filename, which OVERFLOWED ITS OWN BOX in v1 and put a horizontal
   scrollbar on the whole control column — a 64-character CDN filename in a
   centred flex box that had nothing to break on. It ellipsises now, and
   keeps the full name in a title attribute. */
.cp-file {
  margin: 0 0 var(--s-3, 12px);
  overflow: hidden;
  font-size: 0.86rem;
  line-height: 1.4;
  color: var(--c-ink-2, #404040);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A <label> wrapping a real file input, styled as a button. The input stays
   in the document — the visual-semantics requirement — and is merely made
   invisible, exactly as `.gt-drop` does it. */
.cp-replace {
  position: relative;
  cursor: pointer;
}
.cp-replace input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* Four ages, two by two. `minmax(0, 1fr)` and never a bare `1fr`: the bare
   form floors each track at its item's min-content width, so the longest
   label would hold the grid open and overflow the column (rule 11b). */
.cp-presets {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
}
.cp-presets .gt-preset { width: 100%; text-align: center; }

.cp-more {
  align-self: flex-start;
  margin: 0 0 var(--s-3, 12px);
}

.cp-export {
  flex-wrap: wrap;
  gap: 8px;
}

@media (max-width: 520px) {
  .cp-sheet { padding: 12px; }
  .cp-sheet-canvas { max-height: 52vh; }
}

/* =============================================================
   THE CALCULATOR PLATES  (2026-09-09)

   /tools/aspect-ratio-calculator/, /tools/canvas-size-calculator/ and
   /tools/mural-scaling-calculator/ each answered a GEOMETRIC question in PROSE
   inside a card that was mostly empty — the aspect card ran ~800px holding a
   four-row list and a dashed box. That is the finding ProportionCalculator was
   rebuilt on in 2026-09-06 ("the fix is not more furniture, it is the drawing
   the numbers describe"); these three never got the pass.

   THE PREFIX IS `gt-draw-`, NOT `gt-plate-`. `.gt-plate` is already taken by
   ImageSplitter (:1368) for the picture with the cuts ruled over it, and its
   `.gt-plate { margin: 0 }` and `.gt-plate figcaption` would have applied to
   these drawings while these rules applied to its plate — a collision in both
   directions, silent, with correct markup on both sides.

   The fact rows, the buttons and the button row are NOT redeclared here: they
   are `.gt-ledger`, `.gt-btn gt-btn--ghost` and `.gt-tool-actions`, which
   ImageSplitter, ColoringPage, Mandala and PaintByNumber already share.
   ============================================================= */

.gt-draw {
  margin: 0 0 24px;
  padding: 16px 16px 0;
  border: 1px solid var(--c-border, #e5e5ea);
  border-radius: 8px;
  background: var(--c-surface-1, #fff);
}

/* No width or height attribute: the drawing takes the column and its own
   viewBox ratio for the height, which is DERIVED from what is being drawn
   rather than fixed, so a 3:1 subject and a 1:3 subject are not both
   letterboxed into a square. The cap is the one case `preserveAspectRatio`
   earns its keep — a very tall drawing letterboxes rather than running most of
   a screen deep. */
.gt-draw-svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: 62vh;
  font-family: var(--font-head);
}

/* THE ANNOTATION LAYER IS SWITCHED OFF WHERE THE PLATE CANNOT CARRY IT.

   Type inside an svg is in USER UNITS, so its painted size is the plate's width
   over 1000 and no unit or property opts out of that. On a 390px phone the
   plate is ~358px, where a 20-unit label paints at 7px — a drawing annotated in
   type nobody can read, which is worse than one that is not annotated. Below
   820px the dimension lines, the callouts and the haloed labels all go, and the
   caption states the figures instead: real HTML at 13px, legible at every
   width, and written from what was actually drawn.

   820 rather than 1024 because the stacked layout is what decides it: at 1024
   and below the tool is one column, so the plate is nearly the whole page
   (~858px at 1024, ~620px at 768). Above 1024 the control column takes 320
   back and the narrowest plate in the two-column range is ~577px, which is the
   floor the 20-unit size was chosen against. */
@media (max-width: 820px) {
  .gt-draw-svg .gt-draw-anno {
    display: none;
  }
}

.gt-draw figcaption {
  margin: 0;
  padding: 14px 2px 16px;
  font-size: 0.82rem;
  line-height: 1.55;
  color: var(--c-ink-3, #737373);
  max-width: 72ch;
}

/* --- the candidate specimens (canvas size) -------------------

   THE COLUMN COUNT IS CONTAINER-DRIVEN, NOT VIEWPORT-DRIVEN — the fourth band
   in this codebase to need that, after the audience register, the Related index
   and the bento. `.gt-tool-main` is ~960px at a 1440 viewport but ~662px at
   1100, where the 320px control column is still beside it, so the narrowest
   container sits in the MIDDLE of the viewport range and a media query would
   flip the wrong ones.

   The container is a WRAPPER and never `.gt-tool-main` itself: `container-type`
   brings layout containment, which would make the working card a containing
   block for every absolutely positioned descendant in it — and an element can
   never respond to its own `container-type` in any case. */
.gt-specimens-wrap {
  container-type: inline-size;
  margin: 0 0 22px;
}

/* `gap: 1px` over a border-coloured ground IS the hairline grid — exact at
   three, two and one column with no nth-child arithmetic. */
.gt-specimens {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1px;
  background: var(--c-border, #e5e5ea);
  border: 1px solid var(--c-border, #e5e5ea);
  border-radius: 8px;
  overflow: hidden;
  list-style: none;
  margin: 0;
  padding: 0;
}

@container (max-width: 640px) {
  .gt-specimens { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@container (max-width: 330px) {
  .gt-specimens { grid-template-columns: minmax(0, 1fr); }
}

.gt-specimen {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 7px;
  width: 100%;
  padding: 12px 12px 11px;
  border: 0;
  background: var(--c-surface-1, #fff);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.gt-specimen:hover { background: var(--c-surface-2, #fafafa); }

.gt-specimen:focus-visible {
  outline: 2px solid var(--c-ink-1, #0a0a0a);
  outline-offset: -2px;
}

/* No transform and no shadow on the selected cell: a cell that moves drags the
   hairlines it shares with its neighbours out of true. */
.gt-specimen[aria-pressed="true"] { background: var(--c-surface-2, #f4f4f6); }

.gt-specimen-fig { display: block; width: 100%; height: auto; }

.gt-specimen-name {
  font-family: var(--font-head);
  font-weight: 600;
  font-size: 0.9rem;
  font-variant-numeric: tabular-nums;
  color: var(--c-ink-1, #0a0a0a);
}

.gt-specimen-note {
  font-size: 0.74rem;
  line-height: 1.35;
  color: var(--c-ink-3, #737373);
}

/* --- shape presets (aspect ratio) ----------------------------
   Each preset draws its own rectangle at its own ratio, so "2.39:1 cinema" is
   something you pick by eye rather than by decoding the label. */
.gt-shape-presets {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(84px, 100%), 1fr));
  gap: 6px;
  margin: 0 0 14px;
}

.gt-shape-preset {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 8px 4px 7px;
  border: 1px solid var(--c-border, #e5e5ea);
  border-radius: var(--r-sm, 6px);
  background: var(--c-surface-1, #fff);
  font: inherit;
  cursor: pointer;
}

.gt-shape-preset:hover { border-color: var(--c-ink-3, #737373); }

.gt-shape-preset:focus-visible {
  outline: 2px solid var(--c-ink-1, #0a0a0a);
  outline-offset: 1px;
}

.gt-shape-preset[aria-pressed="true"] {
  border-color: var(--c-ink-1, #0a0a0a);
  background: var(--c-surface-2, #f4f4f6);
}

/* A FIXED BOX, so every shape is read against the same frame. The comparison is
   the whole point of drawing them, and a row of tiles each sized to its own
   drawing compares nothing. */
.gt-shape-preset-box {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 34px;
}

.gt-shape-preset-fig { display: block; }

.gt-shape-preset-label {
  /* 0.72, not 0.68: audit-responsive rule 2 read the smaller value at 10.9px,
     the smallest type on the page. This matches `.gt-preset-group-label`
     immediately above it, which is the label these sit under. */
  font-size: 0.72rem;
  line-height: 1.25;
  text-align: center;
  color: var(--c-ink-2, #404040);
}

.gt-shape-preset[aria-pressed="true"] .gt-shape-preset-label { color: var(--c-ink-1, #0a0a0a); }

/* --- the ledger head ----------------------------------------
   The heading and the two output buttons on one rule. `.gt-ledger` itself is
   the cohort's shared fact row (ImageSplitter, ColoringPage, Mandala,
   PaintByNumber) and is deliberately not redeclared; only its header is new
   here, because those four sit theirs inside a `.gt-workbar` instead.

   These two were missing for one build. The markup was correct, the class
   names were correct, and `display` fell back to `block` — so the heading took
   the full width and the buttons dropped to their own line on all three pages.
   `audit-widget-classes.mjs` is what named it: CSS has no error for a selector
   nobody wrote. */
.gt-ledger-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 4px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--c-border, #e5e5ea);
}

.gt-ledger-head h3 {
  margin: 0;
  font-family: var(--font-head);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--c-ink-3, #737373);
}

.gt-ledger-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

/* The buttons are `.gt-btn gt-btn--ghost`, which is the cohort's own button.
   Here they sit on a ledger rule rather than in a `.gt-tool-actions` row, so
   they come down to the scale of the heading beside them. */
.gt-ledger-actions .gt-btn {
  font-size: 0.82rem;
  padding: 0.5em 0.85em;
}

.gt-ledger-head + .gt-ledger {
  margin-top: 14px;
}

/* A CAP, because the cell width is the grid's to decide and the drawing would
   otherwise take whatever it is given: at a 1440 card the six cells are ~300px
   and a 132x84 figure painted 178px tall, so the row ran 260px a cell for a
   drawing that is legible at 100. preserveAspectRatio centres it in what is
   left. */
.gt-specimen-fig {
  max-height: 104px;
}
