/* ============================================================
   ProjectTelemetry — DESIGN SYSTEM  (the single source of truth)
   assets/design-system.css

   ★★ THIS FILE IS THE UI STANDARD. It loads LAST, after every module
   stylesheet, so it is the final word on control sizing, spacing and
   containment. If a module needs a different look, it changes a TOKEN
   here — it does not re-declare .btn-xs with its own pixels.

   Why this exists: control metrics were hard-coded in 58 stylesheets and
   in <style> blocks injected from JS, so the same class rendered at a
   different size depending which page you were on (.btn-xs alone existed
   in five different sizes), and edits to one module silently changed the
   look of another. Everything below is driven by tokens so that can't
   recur.

   THE FIVE RULES THIS FILE ENFORCES
     1. Controls come in exactly three sizes (xs / sm / default). One set
        of metrics, used everywhere.
     2. Nothing touches the chrome or the viewport edge — the page shell
        always keeps a gutter.
     3. Outlined containers never collide — panels/cards carry their own
        bottom spacing and inner padding.
     4. Modals never overlap themselves — the body scrolls, controls keep
        a minimum height, and nothing is clipped behind the header/footer.
     5. Tabs, inputs and buttons share one height so rows line up.

   CHECKLIST AFTER ANY PAGE/MODAL EDIT (do this every time):
     □ buttons use .btn-* (no inline font-size/padding on a control)
     □ page content sits inside the standard gutter (no edge-to-edge)
     □ panels/cards separated by --stack, not touching
     □ modal body scrolls; no input clipped by the header/footer
     □ inputs and buttons on the same row share a height
   ============================================================ */

/* ── How precedence is guaranteed (no build step) ─────────────
   1. This file is UNLAYERED and loaded LAST. Unlayered declarations
      out-rank layered ones, and at equal specificity the later sheet
      wins — so these primitives are the final word.
   2. Module stylesheets may not re-declare a shared primitive at all.
      That rule is machine-checked: tools/check-ui.sh fails the build
      if any module file defines .btn*, .field, .cp-modal, etc.
   Together those two mean an edit to one page cannot change another. */

:root {
  /* ── space scale ─────────────────────────────────────────── */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 20px; --sp-6: 24px; --sp-7: 32px; --sp-8: 40px;

  /* ── page shell: the gutter nothing may cross ────────────── */
  --page-x: 22px;          /* left/right breathing room from chrome + edge */
  --page-y: 18px;          /* top/bottom */
  --stack: 16px;           /* vertical gap between sibling panels/cards */

  /* ── the floors: what nothing is allowed to go below ─────── */
  /* These are MINIMUMS, not a density preference. Two boxes never touch;
     content never touches the edge of the box it lives in; and anything
     positioned in a corner gets a reserved lane so text can never slide
     underneath it. Tighten a layout by other means — never by going under
     these, because that is where things start colliding. */
  --gap-min:  12px;        /* smallest gap between any two adjacent boxes */
  --pad-min:  14px;        /* smallest gap between content and its box edge */
  --lane-tr:  42px;        /* reserved lane for a top-right corner icon/badge:
                              18px glyph + 12px offset + 12px clear of text */

  /* ── control metrics: THE three sizes ────────────────────── */
  --ctl-h:      34px;      /* default control height (buttons, inputs, selects) */
  --ctl-h-sm:   30px;
  --ctl-h-xs:   26px;
  --ctl-fs:     13px;
  --ctl-fs-sm:  12px;
  --ctl-fs-xs:  11.5px;
  --ctl-px:     16px;      /* horizontal padding */
  --ctl-px-sm:  12px;
  --ctl-px-xs:  9px;
  --ctl-radius: 7px;
  --ctl-gap:    8px;       /* gap between controls in a row */

  /* ── containers ──────────────────────────────────────────── */
  --panel-pad: 14px 16px;
  --panel-radius: 12px;
}

/* ════════════════════════════════════════════════════════════
   1. CONTROLS — one set of metrics, everywhere
   Colour/variant styling still comes from app.css; this governs SIZE,
   shape and alignment only, so module variants keep their identity.
   ════════════════════════════════════════════════════════════ */
.btn, .btn-accent, .btn-dark, .btn-ghost, .btn-light, .btn-outline,
.btn-danger, .btn-primary, .btn-secondary {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 6px;
  min-height: var(--ctl-h);
  padding: 0 var(--ctl-px);
  font-size: var(--ctl-fs);
  font-family: inherit;
  line-height: 1;
  border-radius: var(--ctl-radius);
  white-space: nowrap;
  cursor: pointer;
  box-sizing: border-box;
}

/* Size modifiers — these are the ONLY sanctioned sizes. */
.btn-sm, .btn-accent.btn-sm, .btn-dark.btn-sm, .btn-ghost.btn-sm, .btn-light.btn-sm {
  min-height: var(--ctl-h-sm);
  padding: 0 var(--ctl-px-sm);
  font-size: var(--ctl-fs-sm);
  border-radius: var(--ctl-radius);   /* app.css set 5px here, 7px elsewhere */
}
.btn-xs, .btn-accent.btn-xs, .btn-dark.btn-xs, .btn-ghost.btn-xs, .btn-light.btn-xs {
  min-height: var(--ctl-h-xs);
  padding: 0 var(--ctl-px-xs);
  font-size: var(--ctl-fs-xs);
  border-radius: 6px;
}
.btn-lg, .btn-dark-lg, .btn-outline-lg {
  min-height: 42px; padding: 0 24px; font-size: 14px;
}

/* Square icon buttons — always square, always centred, never squashed. */
.btn-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--ctl-h); height: var(--ctl-h);
  padding: 0; flex: 0 0 auto;
  border-radius: var(--ctl-radius);
  font-size: 15px; line-height: 1; cursor: pointer;
  box-sizing: border-box;
}
/* The -sm/-xs variants must repeat the radius: used standalone (without
   .btn-icon) they'd otherwise render square. Found by the browser harness. */
.btn-icon-sm, .btn-icon.sm {
  width: var(--ctl-h-sm); height: var(--ctl-h-sm); font-size: 13.5px;
  border-radius: var(--ctl-radius);
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; flex: 0 0 auto; box-sizing: border-box; cursor: pointer;
}
.btn-icon-xs, .btn-icon.xs {
  width: var(--ctl-h-xs); height: var(--ctl-h-xs); font-size: 12.5px;
  border-radius: var(--ctl-radius);
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; flex: 0 0 auto; box-sizing: border-box; cursor: pointer;
}

/* Module control classes that predate the system, normalised to it so a
   toolbar doesn't mix a 19px-tall button with a 26px one. Colour/border
   intent stays in the module. */
.vmb-btn-sm, .sv-tgl {
  min-height: var(--ctl-h-xs);
  padding: 0 var(--ctl-px-xs);
  font-size: var(--ctl-fs-xs);
  display: inline-flex; align-items: center; gap: 4px;
  line-height: 1; box-sizing: border-box;
}

/* ── Missing variants ─────────────────────────────────────────
   .btn, .btn-light and .btn-primary were used 66 times across the app but
   never had a background or border defined anywhere — they rendered as
   bare text (found by rendering the control set in a real browser; no
   static check catches an unstyled class). Defined here once. */
.btn {
  background: var(--bg2); border: 1px solid var(--bd); color: var(--ink);
  font-weight: 500;
}
.btn:hover:not(:disabled) { background: var(--bg); border-color: var(--ink-faint); }

.btn-light {
  background: var(--white); border: 1px solid var(--bd); color: var(--ink);
  font-weight: 500;
}
.btn-light:hover:not(:disabled) { background: var(--bg2); border-color: var(--ink-faint); }

.btn-primary {
  background: var(--accent); border: 1px solid var(--accent); color: var(--accent-ink);
  font-weight: 600;
}
.btn-primary:hover:not(:disabled) { opacity: .9; }

/* Icon buttons are bare until hovered — give them a visible affordance. */
.btn-icon { background: none; border: none; color: var(--ink-soft); }
.btn-icon:hover:not(:disabled) { background: var(--bg2); color: var(--ink); }

/* A disabled control must LOOK disabled, everywhere. */
.btn:disabled, .btn-accent:disabled, .btn-dark:disabled, .btn-ghost:disabled,
.btn-light:disabled, .btn-icon:disabled, .btn-primary:disabled {
  opacity: .45; cursor: not-allowed;
}

/* Icons inside controls never dictate the control's height. */
.btn i, .btn-accent i, .btn-dark i, .btn-ghost i, .btn-light i,
.btn-sm i, .btn-xs i, .btn-icon i { line-height: 1; flex: 0 0 auto; }

/* ════════════════════════════════════════════════════════════
   2. FORM CONTROLS — same height as buttons so rows line up
   ════════════════════════════════════════════════════════════ */
.field input:not([type="checkbox"]):not([type="radio"]),
.field select, .field textarea {
  box-sizing: border-box;
  font-family: inherit;
  border-radius: var(--ctl-radius);
}
/* Inputs must be the SAME height as buttons or every filter row is ragged.
   Height comes from the token; the font-size stays whatever the type scale
   says (16px on Comfortable, which also stops iOS Safari zooming on focus),
   so we set the box and let the text sit inside it. */
.field input:not([type="checkbox"]):not([type="radio"]),
.field select {
  height: var(--ctl-h);
  min-height: var(--ctl-h);
  padding-top: 0; padding-bottom: 0;
  line-height: normal;
}
.field textarea { padding: 9px 12px; min-height: 80px; }

/* Module inputs adopt the same box so they line up with buttons beside them. */
.sv-in:not(textarea), .sv-select {
  height: var(--ctl-h-xs);
  min-height: var(--ctl-h-xs);
  padding-top: 0; padding-bottom: 0;
  box-sizing: border-box;
}
/* Two-up field rows collapse instead of overflowing their modal. */
.field-row { gap: var(--sp-3); }
@media (max-width: 560px) { .field-row { grid-template-columns: 1fr; } }

/* ── Field rows with something other than two fields ─────────
   .field-row is a FIXED two-column grid. Put a third field in one and it
   wraps onto a second row while the inline flex/width hints people reach for
   do nothing at all — grid items don't read them. That is the fifth
   "boxes are colliding" report, and the honest fix is to give the row an
   explicit column count rather than leave everyone guessing.

   Use .f3 / .f4 for three- and four-up rows. Both collapse the same way. */
.field-row.f3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.field-row.f4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.field-row.f5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.field-row.f6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
/* A field may take two tracks where one input needs the room — a project name
   beside a project number, say. This is what the inline flex:2 was reaching
   for; unlike flex:2, it works. */
.field-row .field.span2 { grid-column: span 2; }
@media (max-width: 1100px) { .field-row.f5, .field-row.f6 { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 900px)  { .field-row.f4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 760px)  { .field-row.f3, .field-row.f5, .field-row.f6 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px)  {
  .field-row.f3, .field-row.f4, .field-row.f5, .field-row.f6 { grid-template-columns: 1fr; }
  .field-row .field.span2 { grid-column: auto; }
}

/* A date input carries a wide intrinsic size on WebKit — wide enough to push
   past its own grid track and sit over the field beside it. The track already
   says how wide it may be. */
.field input[type="date"], .field input[type="time"], .field input[type="number"] {
  width: 100%; min-width: 0;
}

/* ════════════════════════════════════════════════════════════
   2b. SCHEDULE / GANTT DAY SHADING — one language, every view
   ════════════════════════════════════════════════════════════
   Every Gantt-style view (master schedule, scheduler, man loading, scope
   tasking) says the same thing the same way:
     · grey column   = a non-working day on the PROJECT calendar (weekend).
                       Always drawn, purely as a visual aid.
     · amber column  = a project HOLIDAY. Distinct from a weekend because it
                       is a decision someone made, and it has a name.
     · cross-hatch   = a day a BAR passes through without producing — the
                       task doesn't work that day, so it's dead time inside
                       the bar and the bar had to stretch to cover it.
   Tokens so a theme change can't split the meaning across modules. */
:root {
  --day-off:     color-mix(in srgb, var(--bd) 55%, transparent);
  --day-holiday: color-mix(in srgb, #B45309 22%, transparent);
  --day-holiday-line: #B45309;
}
/* The hatch itself — 45° stripes over whatever the bar's colour is. */
.pt-hatch, .sv-hatch {
  position: absolute; top: 0; bottom: 0; pointer-events: none;
  background-image: repeating-linear-gradient(45deg,
    rgba(255,255,255,.55) 0 3px, rgba(255,255,255,0) 3px 7px);
  background-color: rgba(15,23,42,.28);
}
.pt-hatch.hol, .sv-hatch.hol { background-color: color-mix(in srgb, var(--day-holiday-line) 55%, transparent); }

/* ════════════════════════════════════════════════════════════
   3. PAGE SHELL — nothing touches the chrome or the viewport edge
   ════════════════════════════════════════════════════════════ */
.page-hdr { margin-bottom: var(--sp-5); }

/* The shell gutter. #pt-main already pads itself in app.css; this guarantees
   a minimum so a module can never leave content flush against the sidebar or
   the window edge. */
#pt-main { padding: 28px; }
@media (max-width: 1024px) { #pt-main { padding: 18px; } }
@media (max-width: 600px)  { #pt-main { padding: 14px; } }

/* Full-bleed surfaces (curve editor, plan viewer, big canvases) opt out with
   a CLASS — never by writing inline style onto the shell. Inline styles on
   #pt-main leak: if the restore is missed on an error or a fast route change,
   every later page renders with no gutter. A class always comes off cleanly. */
#pt-main.is-fullbleed {
  padding: 0; overflow: hidden;
  display: flex; flex-direction: column;
}

/* A page's first element never collides with the sticky header. */
#pt-main > :first-child { margin-top: 0; }

/* ════════════════════════════════════════════════════════════
   4. CONTAINERS — outlined boxes never butt against each other
   Any bordered/elevated container gets inner padding and a bottom gap.
   :where() keeps specificity at 0 so modules can still override freely.
   ════════════════════════════════════════════════════════════ */
:where(.pt-card, .card, .panel, .pt-panel, .cd-panel, .sv-panel,
       .mod-panel, .box, .tile) {
  border-radius: var(--panel-radius);
}
:where(.pt-card, .card, .panel, .pt-panel) { padding: var(--panel-pad); }

/* Consecutive panels always keep air between them. */
:where(.sv-panel, .cd-panel, .pt-card, .card, .panel) +
:where(.sv-panel, .cd-panel, .pt-card, .card, .panel) { margin-top: var(--stack); }

/* Toolbars: consistent gap so buttons never touch each other or the edge. */
.mod-tb, .pt-toolbar, .sv-panel-tools, .td-filters, .oc-toolbar {
  display: flex; align-items: center; gap: var(--ctl-gap); flex-wrap: wrap;
}

/* Grids of cards keep a real gutter rather than hairline-to-hairline. */
:where(.pt-grid, .card-grid, .tile-grid) { gap: var(--stack); }

/* ════════════════════════════════════════════════════════════
   5. MODALS — never overlap, never clip, never exceed the viewport
   ════════════════════════════════════════════════════════════ */
.modal-overlay { padding: var(--sp-5); overflow-y: auto; }
.cp-modal, .pt-modal {
  display: flex; flex-direction: column;
  max-height: calc(100vh - 2 * var(--sp-5));
  max-width: min(96vw, 640px);
  border-radius: 12px;
  overflow: hidden;                 /* header/footer stay put… */
}
.cp-modal .cp-body, .pt-modal .pt-modal-body {
  overflow-y: auto;                 /* …only the body scrolls */
  flex: 1 1 auto;
  min-height: 0;                    /* the fix that stops clipped inputs */
}
.cp-header, .pt-modal-header { flex: 0 0 auto; gap: var(--sp-3); }
/* A modal's action row never sits on top of the last field. */
.cp-body > :last-child { margin-bottom: 0; }
.tc-actions, .pt-modal-actions {
  display: flex; align-items: center; gap: var(--ctl-gap);
  flex-wrap: wrap; margin-top: var(--sp-4);
}
/* Long content inside a modal wraps instead of forcing horizontal scroll. */
.cp-body { word-break: break-word; }
.cp-body table { width: 100%; }

/* ── 5b. UNIVERSAL MODAL SAFETY ───────────────────────────────
   The app grew twelve independent modal implementations (bl-, cb-, co-,
   cp-, es-, forms-, hr-, msg-, pdc-, tc-, plus drawers). Most never got
   max-height or a scrolling body, which is why tall forms clipped their
   last input or ran a button under the edge. Rather than patch twelve
   stylesheets — the very habit that caused the drift — every family is
   brought under one rule here.
   ★ New modal? Prefer .cp-modal/.cp-header/.cp-body. If you must invent
     one, add its classes to the lists below so it inherits the safety. */

/* Backdrops: cover the viewport, centre the card, scroll if it overflows. */
.modal-overlay, .cb-modal-bg, .msg-modal-back, .pdc-modal,
.bl-modal, .co-modal,
.cn-drawer-overlay, .dir-drawer-ov, .pl-overlay {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

/* Cards: never taller than the viewport, always a flex column so the body
   is the only part that scrolls. */
.cp-modal, .bl-modal-box, .cb-modal, .cb-modal-wide, .co-modal-box,
.es-modal, .forms-modal, .hr-modal, .msg-modal, .pdc-modal-card, .tc-modal {
  display: flex; flex-direction: column;
  max-height: calc(100vh - 2 * var(--sp-5));
  max-width: min(96vw, 720px);
  box-sizing: border-box;
  overflow: hidden;
}

/* Bodies scroll; min-height:0 is what actually stops the clipping inside
   a flex column (without it the body refuses to shrink and overflows). */
.cp-body, .bl-modal-bd, .cb-modal-p, .co-modal-bd, .forms-modal-bd,
.hr-modal-bd, .msg-modal-note, .pdc-modal-bd {
  overflow-y: auto; flex: 1 1 auto; min-height: 0;
}

/* Headers and footers hold their size so they can't be pushed off-screen
   or overlap the body. */
.cp-header, .bl-modal-hd, .cb-modal-hd, .co-modal-hd, .forms-modal-hd,
.hr-modal-hd, .msg-modal-hd, .pdc-modal-hd,
.bl-modal-ft, .co-modal-ft, .forms-modal-ft, .hr-modal-ft, .msg-modal-ft,
.pdc-modal-ft, .cb-modal-act {
  flex: 0 0 auto;
}
/* Footers keep their buttons off the last field. */
.bl-modal-ft, .co-modal-ft, .forms-modal-ft, .hr-modal-ft, .msg-modal-ft,
.pdc-modal-ft, .cb-modal-act, .flt-modal-actions {
  display: flex; align-items: center; gap: var(--ctl-gap); flex-wrap: wrap;
}

/* Side drawers: full height, own scroll, never wider than the viewport. */
.cn-drawer, .dir-drawer, .pl-drawer, .mtg-idrawer, .wip-sheet {
  max-width: 96vw; max-height: 100vh;
  display: flex; flex-direction: column;
  box-sizing: border-box;
}
.dir-drawer-body, .pl-drawer-list { overflow-y: auto; flex: 1 1 auto; min-height: 0; }

/* ════════════════════════════════════════════════════════════
   6. TABS — one height, one rhythm, no collisions
   ════════════════════════════════════════════════════════════ */
.ld-tabs, .tc-tabs, .pt-tabs, .oc-viewtoggle, .pth-toggle {
  display: flex; align-items: center; gap: 4px; flex-wrap: wrap;
}
.ld-tab, .pt-tab, .tc-tab {
  min-height: var(--ctl-h-sm);
  padding: 0 var(--ctl-px-sm);
  font-size: var(--ctl-fs-sm);
  display: inline-flex; align-items: center; gap: 5px;
  border-radius: var(--ctl-radius);
  cursor: pointer; white-space: nowrap;
  box-sizing: border-box;
}

/* ════════════════════════════════════════════════════════════
   6b. MANPOWER CURVE EDITOR — its toolbar geometry lives HERE
   This module injects its own <style> at runtime, which lands after every
   stylesheet and therefore always used to win. Its control METRICS were
   moved here so the toolbar can no longer drift between releases (it had
   changed size repeatedly); the module keeps only colour/border intent.
   ★ Do not put width/height/font-size/padding for .ce-* controls back
     into curve-editor.js — tools/check-ui.sh fails the build if you do.
   ════════════════════════════════════════════════════════════ */
/* 22px with a 12px glyph made these near-impossible to hit with a finger —
   the collapse caret worst of all, since it was a bare text triangle rather
   than an icon. 30px matches --ctl-h-sm, the smallest control size the system
   already sanctions, and the row they sit in has a real gap so a miss lands on
   nothing rather than on Delete. */
.ce-icon-btn {
  width: var(--ctl-h-sm); height: var(--ctl-h-sm); font-size: 16px;
  border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto; box-sizing: border-box;
}
.ce-icon-btn > i { font-size: 17px; line-height: 1; }
/* The action cluster on a curve card. Was an inline gap:2px, which put a
   destructive button two pixels from a routine one. */
.ce-card-acts { display: flex; align-items: center; gap: var(--sp-2); flex: 0 0 auto; }
/* Inline text-links inside the curve editor (override / reset). These used to
   carry ~9 inline style properties each on every render. */
.ce-linkbtn, .ce-hlinkbtn {
  font-weight: 600; text-transform: none; letter-spacing: 0;
  background: none; border: none; cursor: pointer;
  text-decoration: underline; padding: 0;
  color: var(--ink-soft); font-family: inherit;
}
.ce-linkbtn  { font-size: 10px; }
.ce-hlinkbtn { font-size: 9.5px; }
.ce-linkbtn.is-reset, .ce-hlinkbtn.is-reset { color: var(--accent); }

.ce-seg    { padding: 4px 10px; font-size: 11px; line-height: 1; box-sizing: border-box; }
.ce-seg-sm { padding: 2px 7px;  font-size: 10px; line-height: 1; box-sizing: border-box; }
.ce-tab-btn{ padding: 7px 14px; font-size: 12px; line-height: 1; box-sizing: border-box; }

/* ════════════════════════════════════════════════════════════
   7. TABLES — headers and cells never crush their content
   ════════════════════════════════════════════════════════════ */
:where(table) { border-collapse: collapse; }
/* Any wide table scrolls inside its own container instead of pushing the
   page sideways (a recurring cause of content running under the chrome). */
:where(.pt-tblwrap, .sv-tblwrap, .table-wrap) { overflow-x: auto; max-width: 100%; }
/* Controls sitting inside table cells use the compact size automatically. */
:where(td) > .btn { min-height: var(--ctl-h-sm); font-size: var(--ctl-fs-sm); padding: 0 var(--ctl-px-sm); }

/* ════════════════════════════════════════════════════════════
   8. GLOBAL SAFETY — the "never again" rules
   ════════════════════════════════════════════════════════════ */
/* Nothing may force the page to scroll sideways. */
html, body { max-width: 100%; }
/* Media and embeds stay inside their box. */
:where(img, svg, video, canvas) { max-width: 100%; }
/* Absolutely-positioned overlays never escape their container. */
:where(.pt-page, .sv-panel, .cd-panel) { position: relative; }

/* Modal close (X). Used by .cp-modal headers across modules and, until now,
   never actually styled anywhere — it rendered as a raw browser button. */
.cp-x {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--ctl-h); height: var(--ctl-h);
  background: none; border: none; border-radius: 8px;
  color: var(--ink-soft); font-size: 22px; line-height: 1; cursor: pointer;
}
.cp-x:hover { background: var(--bg2); color: var(--ink); }

/* Touch: keep controls tappable WITHOUT inflating dense toolbars — the
   mistake that repeatedly broke the manpower curve editor. Only reach the
   minimum by growing the tap area, never the visual box.
   ★ That was the stated intent for a long time but only `touch-action` was
   ever implemented — no tap area was actually grown, so a 26–34px control
   stayed a 26–34px target on glass. The ::after overlay below is the missing
   half: it enlarges the HIT REGION and changes no layout and no pixel, which
   is exactly why it is safe on the curve editor's dense chrome too.
   Insets are chosen to land on ~44px and to consume the standard 7px toolbar
   gap without overlapping a neighbour. */
@media (hover: none) and (pointer: coarse) {
  .btn, .btn-accent, .btn-dark, .btn-ghost, .btn-light, .btn-icon,
  .btn-sm, .btn-xs, .btn-icon-sm, .btn-icon-xs, .ld-tab, .pt-tab, .cp-x {
    touch-action: manipulation;
    position: relative;
  }
  .btn::after, .btn-accent::after, .btn-dark::after, .btn-ghost::after,
  .btn-light::after, .btn-icon::after, .btn-sm::after, .btn-icon-sm::after,
  .cp-x::after {
    content: ''; position: absolute; inset: -5px; z-index: 0;
  }
  /* xs controls start smaller, so they need a little more reach. */
  .btn-xs::after, .btn-icon-xs::after { content: ''; position: absolute; inset: -9px; z-index: 0; }
  /* The overlay must never sit above the control's own content. */
  .btn > *, .btn-accent > *, .btn-dark > *, .btn-ghost > *, .btn-light > *,
  .btn-icon > *, .btn-sm > *, .btn-xs > *, .cp-x > * { position: relative; z-index: 1; }
}

/* Print: don't carry interactive chrome onto paper. */
@media print {
  .btn, .btn-accent, .btn-dark, .btn-ghost, .btn-icon, .mod-tb, .pt-toolbar { display: none !important; }
}

/* ── A field never overflows the track it sits in ────────────
   .field is the shared form wrapper, and it is routinely dropped into grid and
   flex rows. Both give a child min-width:auto by default, so a long label,
   value or input refuses to shrink and runs into the field beside it. This has
   now been the cause of four separate "boxes are colliding" reports; fixing it
   at the primitive rather than per page is the only version that stays fixed. */
.field { min-width: 0; }
.field > label { overflow-wrap: anywhere; }
.field input, .field select, .field textarea { max-width: 100%; box-sizing: border-box; }

/* ── Form controls never force a grid or flex track wider ────
   THE APP-WIDE FIX for overlapping inputs.

   A `1fr` track is minmax(auto, 1fr), and `auto` resolves to the item's
   min-content width. A form control is a replaced element with an intrinsic
   width, so it reports a large min-content — which forces its track past the
   share it was given, eats the gap, and pushes the neighbouring control on top
   of it or over the container edge.

   Setting min-width:0 on the control itself collapses that contribution, so
   every `1fr` track in the app behaves like minmax(0,1fr) without having to
   rewrite the grid declarations one page at a time. box-sizing keeps padding
   and border INSIDE a width:100%, which is the other half of the same overflow.

   Applied at the primitive because this defect has now been reported on four
   separate screens and inline grid styles exist in dozens of modals. */
input, select, textarea, .btn, .btn-accent, .btn-dark, .btn-ghost, .btn-light {
  min-width: 0;
}
input, select, textarea { box-sizing: border-box; max-width: 100%; }
