/* ==========================================================================
   printAnywhere.css — Print Anywhere module (.pnt-*)
   ==========================================================================

   STRUCTURE IS COPIED, NOT REINVENTED. Two existing screens are the template:
     - evriClaimGen.css (.ecg-*) — page header, buttons, cards, wide table,
       chips, banners, empty state, skeleton, modal, and the 1180px mobile gate.
     - messageTemplates.css (.mt-*) — the auto-fill card grid used by the
       Printers screen.

   DARK MODE IS DONE BY REDEFINING THE TOKENS, not by writing a second set of
   rules. That is evriClaimGen's approach (evriClaimGen.css:262-270) and it is
   the better one: messageTemplates.css needs 25 explicit dark overrides because
   its rules hardcode colour, and every new rule there is a new thing to
   remember. Here, one block at the bottom re-points the tokens and every
   surface above follows.

   TWO GLOBAL CSS TRAPS THIS FILE DELIBERATELY DEFENDS AGAINST — both have
   already cost real debugging time elsewhere in this app:
     1. style.css line 1 has an unscoped `button { color: white }`. Any button
        here that does not state its own colour renders white-on-white. So
        EVERY .pnt-* button states `color`.
     2. polish.css:429 has an unscoped `button.danger { background:#b42318 }`,
        which at (0,1,1) outranks a bare `.pnt-btn` (0,1,0) whatever the load
        order — and #b42318 is also our danger TEXT colour, so the label
        vanishes into the background. `.pnt-btn.danger` is (0,2,0) and states
        its own background, exactly as .ecg-btn.danger does.

   MOBILE: one gate at max-width:1180px (the documented house breakpoint) at the
   bottom of the file. Everything above it is the desktop layout and is not
   touched by the media query.
   ========================================================================== */

:root {
  --pnt-primary: #0588b6;
  --pnt-primary-d: #03658e;
  --pnt-grad: linear-gradient(135deg, #03658e, #0588b6);
  --pnt-ink: #0f2740;
  --pnt-sub: #5b7183;
  --pnt-line: #e6edf5;
  --pnt-line-s: #cfdcea;
  --pnt-shadow: 0 10px 30px rgba(15, 23, 42, .06);
  --pnt-ok: #0f7b52;
  --pnt-warn: #9a6700;
  --pnt-bad: #b42318;
  --pnt-card: #fff;
  --pnt-bg-soft: #f6f9fc;
}

.pnt-wrap { max-width: 1180px; margin: 0 auto; padding: 6px 4px 44px; }

/* ---------- Page header (shape from .ecg-head) ---------- */
.pnt-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; flex-wrap: wrap; margin: 4px 0 14px; }
.pnt-eyebrow { font-size: 12px; font-weight: 800; letter-spacing: .09em; text-transform: uppercase; color: var(--pnt-primary); }
.pnt-title { font-size: 25px; font-weight: 800; color: var(--pnt-ink); margin: 4px 0; line-height: 1.15; }
.pnt-sub { font-size: 13.5px; color: var(--pnt-sub); margin: 0; max-width: 680px; line-height: 1.5; }
.pnt-head-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* ---------- Screen tabs (Printers / Queue / Agents) ---------- */
.pnt-tabs { display: flex; gap: 8px; flex-wrap: wrap; margin: 0 0 14px; border-bottom: 1.5px solid var(--pnt-line); padding-bottom: 10px; }
.pnt-tab { appearance: none; border: 1.5px solid var(--pnt-line-s); background: #fff; color: #4a6885; font-size: 13px; font-weight: 700; padding: 8px 15px; border-radius: 10px; cursor: pointer; transition: .15s; margin: 0; box-shadow: none; }
.pnt-tab:hover { border-color: var(--pnt-primary); color: var(--pnt-primary); }
.pnt-tab.on { border-color: var(--pnt-primary); background: var(--pnt-grad); color: #fff; box-shadow: 0 6px 14px rgba(5, 136, 182, .24); }
.pnt-tab:focus-visible { outline: 2px solid var(--pnt-primary); outline-offset: 2px; }

/* ---------- Buttons (colour + background always stated — see traps above) ----
   `margin` and `box-shadow` are stated too, and that is not tidiness. The same
   unscoped style.css rule also sets `margin:3px` and a teal `box-shadow`, and
   .ecg-btn omits both — so every secondary button in the Claims screen silently
   carries 3px of margin and a teal glow it never asked for. Declaring them here
   is the difference between a button that looks designed and one that inherits. */
.pnt-btn { appearance: none; border: 1.5px solid var(--pnt-line-s); background: #fff; color: #21506f; font-size: 13px; font-weight: 700; padding: 9px 15px; border-radius: 11px; cursor: pointer; display: inline-flex; align-items: center; gap: 7px; transition: .15s; text-decoration: none; margin: 0; box-shadow: none; }
.pnt-btn:hover:not(:disabled) { border-color: var(--pnt-primary); color: var(--pnt-primary); }
.pnt-btn.primary { border: none; color: #fff; background: var(--pnt-grad); box-shadow: 0 8px 18px rgba(5, 136, 182, .28); }
.pnt-btn.primary:hover:not(:disabled) { color: #fff; filter: brightness(1.06); }
.pnt-btn.danger { background: #fff; border-color: #f1c9c5; color: var(--pnt-bad); }
.pnt-btn.danger:hover:not(:disabled) { background: #fdf1f0; border-color: var(--pnt-bad); color: var(--pnt-bad); }
.pnt-btn:disabled { opacity: .5; cursor: not-allowed; }
.pnt-btn:focus-visible { outline: 2px solid var(--pnt-primary); outline-offset: 2px; }
.pnt-btn-sm { padding: 6px 11px; font-size: 12.5px; border-radius: 9px; }
.pnt-linkbtn { appearance: none; background: none; border: none; padding: 0; font: inherit; font-weight: 700; color: var(--pnt-primary); text-decoration: underline; cursor: pointer; }
.pnt-linkbtn:hover { color: var(--pnt-primary-d); }
.pnt-linkbtn:focus-visible { outline: 2px solid var(--pnt-primary); outline-offset: 2px; border-radius: 3px; }

/* ---------- Cards ---------- */
.pnt-card { background: var(--pnt-card); border: 1.5px solid var(--pnt-line); border-radius: 18px; box-shadow: var(--pnt-shadow); padding: 18px; margin-bottom: 16px; }
.pnt-card-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }
.pnt-card-head h3 { font-size: 15.5px; font-weight: 800; color: var(--pnt-ink); margin: 0; }
.pnt-spacer { flex: 1; }

/* ---------- Status banner (renders GET /status `reason` verbatim) ---------- */
.pnt-banner { border: 1.5px solid var(--pnt-line-s); background: #eef7fb; color: #21506f; border-radius: 13px; padding: 11px 14px; font-size: 13px; margin-bottom: 14px; display: flex; align-items: flex-start; gap: 11px; flex-wrap: wrap; line-height: 1.45; }
.pnt-banner.warn { border-color: #f2dda0; background: #fff8e6; color: #7a5200; }
.pnt-banner.bad { border-color: #f1c9c5; background: #fdf1f0; color: var(--pnt-bad); }
.pnt-banner.ok { border-color: #bde3d1; background: #eaf7f1; color: var(--pnt-ok); }
.pnt-banner-ico { font-size: 16px; line-height: 1.3; flex: 0 0 auto; }
.pnt-banner-body { flex: 1; min-width: 220px; }
.pnt-banner-t { font-weight: 800; margin-bottom: 2px; }
/* `problems` from the status endpoint — surfaced, never swallowed. */
.pnt-problems { list-style: none; margin: 7px 0 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.pnt-problems li { font-size: 12px; line-height: 1.45; opacity: .92; }
.pnt-problems li::before { content: "! "; font-weight: 800; }

/* ---------- Printer card grid (shape from .mt-grid) ---------- */
.pnt-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(310px, 1fr)); gap: 14px; }
.pnt-pcard { background: var(--pnt-card); border: 1.5px solid var(--pnt-line); border-radius: 16px; box-shadow: var(--pnt-shadow); padding: 15px; display: flex; flex-direction: column; gap: 10px; }
.pnt-pcard.off { opacity: .72; }
.pnt-pcard-top { display: flex; align-items: flex-start; gap: 9px; }
.pnt-pcard-name { font-size: 14.5px; font-weight: 800; color: var(--pnt-ink); flex: 1; line-height: 1.25; word-break: break-word; }
.pnt-pcard-badges { display: flex; gap: 6px; flex-wrap: wrap; }
.pnt-badge { font-size: 11px; font-weight: 800; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--pnt-line-s); background: var(--pnt-bg-soft); color: #41607d; white-space: nowrap; }
.pnt-badge.sample { background: #f3f0fb; border-color: #ddd4f0; color: #6b52a6; }
/* Two SEPARATE facts, never merged into one badge: whether the agent process is
   alive, and whether the printer answered. See the module's own note. */
.pnt-facts { display: flex; flex-direction: column; gap: 5px; }
.pnt-fact { display: flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--pnt-ink); }
.pnt-fact-k { color: var(--pnt-sub); min-width: 104px; flex: 0 0 auto; }
.pnt-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; background: #b9c6d4; }
.pnt-dot.ok { background: #17a06a; }
.pnt-dot.bad { background: #d2504a; }
.pnt-dot.unknown { background: #c9a227; }
.pnt-checked { font-size: 11.5px; color: var(--pnt-sub); }
.pnt-pcard-foot { display: flex; gap: 7px; flex-wrap: wrap; margin-top: 2px; }

/* NO .pnt-chip HERE. The .ecg-* family it is copied from has a four-variant chip,
   but this module expresses state with .pnt-badge (what a printer IS) and the step
   indicator (where a job HAS GOT TO), so a chip would be a rule nothing renders.
   Shipping it "for family completeness" is just dead CSS; add it back the day a
   screen actually needs one. */

/* ---------- Toolbar / filters ---------- */
.pnt-toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; background: var(--pnt-card); border: 1.5px solid var(--pnt-line); border-radius: 14px; box-shadow: var(--pnt-shadow); padding: 11px 13px; margin-bottom: 13px; }
/* min-width:0 is load-bearing: style.css sets an unscoped
   `input,select,textarea { min-width:170px }`, which .ecg-inp never resets — so a
   narrow field there cannot actually go narrow, and the numeric inputs fight a
   170px floor they never declared. */
.pnt-select, .pnt-inp { border: 1.5px solid var(--pnt-line-s); border-radius: 11px; padding: 8px 11px; font-size: 13px; font-family: inherit; color: var(--pnt-ink); background: #fff; transition: .15s; box-sizing: border-box; min-width: 0; margin: 0; }
.pnt-select:focus, .pnt-inp:focus { outline: none; border-color: var(--pnt-primary); box-shadow: 0 0 0 3px rgba(5, 136, 182, .13); }
.pnt-field { margin-bottom: 13px; }
.pnt-label { display: block; font-size: 11.5px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; color: var(--pnt-sub); margin-bottom: 5px; }
.pnt-hint { font-size: 12px; color: var(--pnt-sub); margin-top: 5px; line-height: 1.45; }

/* ---------- Status counts header ---------- */
.pnt-counts { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.pnt-count { border: 1.5px solid var(--pnt-line-s); border-radius: 11px; background: var(--pnt-card); padding: 7px 12px; display: flex; align-items: baseline; gap: 7px; cursor: pointer; transition: .15s; }
.pnt-count:hover { border-color: var(--pnt-primary); }
.pnt-count.on { border-color: var(--pnt-primary); box-shadow: 0 0 0 3px rgba(5, 136, 182, .13); }
.pnt-count-n { font-size: 16px; font-weight: 800; color: var(--pnt-ink); }
.pnt-count-k { font-size: 11.5px; font-weight: 700; color: var(--pnt-sub); text-transform: uppercase; letter-spacing: .04em; }

/* ---------- Wide table (scrolls INSIDE its wrapper, never the page body) ---------- */
.pnt-tablewrap { overflow-x: auto; border: 1.5px solid var(--pnt-line); border-radius: 13px; -webkit-overflow-scrolling: touch; background: var(--pnt-card); }
/* margin-top:0 resets style.css's unscoped `table { margin-top:16px }`, which
   .ecg-table never resets and which pushes the table off its wrapper's top edge. */
.pnt-table { border-collapse: collapse; width: 100%; min-width: 1080px; font-size: 12.5px; margin-top: 0; }
.pnt-table th { text-align: left; padding: 9px 11px; background: var(--pnt-bg-soft); color: var(--pnt-ink); font-weight: 800; font-size: 11px; letter-spacing: .04em; text-transform: uppercase; border-bottom: 1.5px solid var(--pnt-line); white-space: nowrap; position: sticky; top: 0; }
.pnt-table td { padding: 8px 11px; border-bottom: 1px solid var(--pnt-line); color: var(--pnt-ink); vertical-align: middle; }
.pnt-table td.nowrap { white-space: nowrap; }
.pnt-table tbody tr:hover td { background: #f3f9fc; }
.pnt-table td.empty { color: #9db0c2; }
.pnt-mono { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px; }

/* ---------- Job step indicator (queued -> claimed -> printing -> done) ------
   A progression, not a single badge: an operator watching a job wants to see
   where it has got to, and a bare "claimed" says nothing about what is left. */
.pnt-steps { display: inline-flex; align-items: center; gap: 0; }
.pnt-step { display: inline-flex; align-items: center; gap: 5px; }
.pnt-step-dot { width: 10px; height: 10px; border-radius: 50%; background: #dbe3ec; border: 1.5px solid #cbd6e2; box-sizing: border-box; }
.pnt-step.done .pnt-step-dot { background: var(--pnt-ok); border-color: var(--pnt-ok); }
.pnt-step.now .pnt-step-dot { background: var(--pnt-primary); border-color: var(--pnt-primary); box-shadow: 0 0 0 3px rgba(5, 136, 182, .18); }
.pnt-step.fail .pnt-step-dot { background: var(--pnt-bad); border-color: var(--pnt-bad); }
.pnt-step-bar { width: 16px; height: 2px; background: #dbe3ec; }
.pnt-step-bar.done { background: var(--pnt-ok); }
.pnt-step-lbl { font-size: 11.5px; font-weight: 700; color: var(--pnt-sub); margin-left: 6px; white-space: nowrap; }
.pnt-step-lbl.now { color: var(--pnt-primary); }
.pnt-step-lbl.done { color: var(--pnt-ok); }
.pnt-step-lbl.fail { color: var(--pnt-bad); }

/* ---------- Empty / skeleton ---------- */
.pnt-empty { text-align: center; padding: 34px 18px; color: var(--pnt-sub); }
.pnt-empty-ico { font-size: 30px; margin-bottom: 9px; }
.pnt-empty h4 { margin: 0 0 6px; font-size: 15px; color: var(--pnt-ink); font-weight: 800; }
.pnt-empty p { margin: 0 auto 12px; font-size: 13px; line-height: 1.55; max-width: 460px; }
.pnt-skel { display: grid; grid-template-columns: repeat(auto-fill, minmax(310px, 1fr)); gap: 14px; }
.pnt-sk-card { height: 150px; border-radius: 16px; background: var(--rms-skel-fill); background-size: 400% 100%; animation: rms-shimmer var(--t-shimmer) var(--e-in-out) infinite; }
.pnt-sk-row { height: 38px; border-radius: 9px; margin-bottom: 8px; background: var(--rms-skel-fill); background-size: 400% 100%; animation: rms-shimmer var(--t-shimmer) var(--e-in-out) infinite; }
@media (prefers-reduced-motion: reduce) { .pnt-sk-card, .pnt-sk-row { animation: none; } }

/* ---------- Pagination ---------- */
.pnt-pager { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-top: 11px; font-size: 12.5px; color: var(--pnt-sub); }
.pnt-pager-btns { display: flex; gap: 7px; }

/* ---------- Modal (structure copied from .ecg-modal) ---------- */
.pnt-modal { position: fixed; inset: 0; z-index: 6000; display: flex; align-items: center; justify-content: center; padding: 18px; }
.pnt-modal-bg { position: absolute; inset: 0; background: rgba(9, 24, 40, .5); backdrop-filter: blur(2px); }
.pnt-modal-shell { position: relative; background: var(--pnt-card); border-radius: 19px; width: min(680px, 100%); max-height: 90vh; display: flex; flex-direction: column; box-shadow: 0 26px 70px rgba(9, 24, 40, .3); overflow: hidden; }
.pnt-modal-head { display: flex; align-items: center; gap: 12px; padding: 15px 19px; border-bottom: 1.5px solid var(--pnt-line); }
.pnt-modal-head h3 { margin: 0; font-size: 16px; font-weight: 800; color: var(--pnt-ink); flex: 1; }
.pnt-modal-body { padding: 17px 19px; overflow-y: auto; }
.pnt-modal-foot { display: flex; gap: 9px; justify-content: flex-end; padding: 13px 19px; border-top: 1.5px solid var(--pnt-line); flex-wrap: wrap; }
.pnt-x { appearance: none; border: none; background: none; font-size: 22px; line-height: 1; color: var(--pnt-sub); cursor: pointer; padding: 0 4px; }
.pnt-x:hover { color: var(--pnt-ink); }

/* ---------- Help modal steps ---------- */
.pnt-steps-list { list-style: none; margin: 0; padding: 0; counter-reset: pnt-step-counter; }
.pnt-steps-list li { position: relative; padding: 0 0 15px 38px; counter-increment: pnt-step-counter; }
.pnt-steps-list li::before { content: counter(pnt-step-counter); position: absolute; left: 0; top: 0; width: 25px; height: 25px; border-radius: 50%; background: var(--pnt-grad); color: #fff; font-size: 12.5px; font-weight: 800; display: flex; align-items: center; justify-content: center; }
.pnt-steps-list h5 { margin: 3px 0 4px; font-size: 13.5px; font-weight: 800; color: var(--pnt-ink); }
.pnt-steps-list p { margin: 0; font-size: 12.8px; line-height: 1.55; color: var(--pnt-sub); }

/* ---------- The one-time agent secret ---------- */
.pnt-secret { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 13px; width: 100%; box-sizing: border-box; border: 1.5px solid var(--pnt-primary); border-radius: 11px; padding: 11px 12px; background: var(--pnt-bg-soft); color: var(--pnt-ink); word-break: break-all; }
.pnt-secret-row { display: flex; gap: 8px; align-items: stretch; margin-top: 6px; }
.pnt-secret-row .pnt-btn { flex: 0 0 auto; }

/* ---------- Batch progress ---------- */
.pnt-progress { font-size: 13px; color: var(--pnt-ink); display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.pnt-bar { height: 7px; border-radius: 999px; background: var(--pnt-line); overflow: hidden; flex: 1; min-width: 130px; }
.pnt-bar-fill { height: 100%; background: var(--pnt-grad); transition: width .2s var(--e-out, ease); }

/* NO TOAST STYLES HERE, DELIBERATELY. Toasts come from the shared kit
   (window.uiToast, public/js/ui-kit.js). evriClaims.js and evriTracking.js each
   cloned their own, and evriClaimGen.js:23-25 records that as a mistake not to
   repeat — so this module uses the shared one and ships no toast CSS at all. */

/* ================= DARK MODE — token redefinition only ==================
   Everything above reads from these tokens, so this block is the whole of dark
   mode for the module. Anything that needs a literal here is a surface the
   tokens genuinely cannot express (a white input background, a hover tint). */
html[data-theme="dark"] {
  --pnt-ink: #e6edf6;
  --pnt-sub: #9fb0c4;
  --pnt-line: #243049;
  --pnt-line-s: #2f3d5a;
  --pnt-card: #161f33;
  --pnt-bg-soft: #1a2438;
  --pnt-shadow: 0 10px 30px rgba(0, 0, 0, .3);
}
html[data-theme="dark"] .pnt-btn,
html[data-theme="dark"] .pnt-tab,
html[data-theme="dark"] .pnt-select,
html[data-theme="dark"] .pnt-inp { background: #1a2438; color: #cfe0ef; }
html[data-theme="dark"] .pnt-btn.primary,
html[data-theme="dark"] .pnt-tab.on { color: #fff; background: var(--pnt-grad); }
html[data-theme="dark"] .pnt-btn.danger { background: #2a1a1c; border-color: #5d2f2c; color: #f0a5a0; }
html[data-theme="dark"] .pnt-table tbody tr:hover td { background: #1d2941; }
html[data-theme="dark"] .pnt-banner { background: #17293a; color: #cfe0ef; border-color: #2f3d5a; }
html[data-theme="dark"] .pnt-banner.warn { background: #2a2313; color: #e8cf92; border-color: #5c4a1e; }
html[data-theme="dark"] .pnt-banner.bad { background: #2a1a1c; color: #f0a5a0; border-color: #5d2f2c; }
html[data-theme="dark"] .pnt-banner.ok { background: #142b22; color: #8fd9b6; border-color: #2c5a45; }
/* .pnt-badge carries its own TEXT colour in light mode, so dark needs the colour
   too — a background-only override leaves #41607d unreadable on a dark surface. */
html[data-theme="dark"] .pnt-badge { color: #b8c6d8; }
html[data-theme="dark"] .pnt-badge.sample { background: #241d38; border-color: #3c3157; color: #c3b0ec; }
html[data-theme="dark"] .pnt-step-dot { background: #2a3650; border-color: #3a4867; }
html[data-theme="dark"] .pnt-step-bar { background: #2a3650; }
html[data-theme="dark"] .pnt-modal-foot { background: #161f33; }

/* ================= MOBILE (additive only, gated at 1180px) =================
   The desktop rules above are untouched by this block. The wide job table keeps
   its min-width and scrolls inside .pnt-tablewrap, exactly as .ecg-table does —
   it does NOT become cards, because a print queue is read by column (which
   printer, what status, when) and a card stack loses that comparison. */
@media (max-width: 1180px) {
  .pnt-wrap { padding: 4px 2px 40px; }
  .pnt-title { font-size: 21px; }
  .pnt-card { padding: 14px; border-radius: 15px; }
  .pnt-head { flex-direction: column; gap: 11px; }
  .pnt-head-actions { width: 100%; }
  .pnt-head-actions .pnt-btn { flex: 1; justify-content: center; }
  .pnt-tabs { overflow-x: auto; flex-wrap: nowrap; -webkit-overflow-scrolling: touch; }
  .pnt-tab { flex: 0 0 auto; }
  .pnt-toolbar { flex-direction: column; align-items: stretch; }
  .pnt-toolbar .pnt-select, .pnt-toolbar .pnt-inp, .pnt-toolbar .pnt-btn { width: 100%; }
  .pnt-pcard-foot .pnt-btn { flex: 1; justify-content: center; }
  .pnt-tablewrap { border-radius: 11px; }
  .pnt-table { min-width: 980px; }
  .pnt-modal { padding: 0; align-items: flex-end; }
  .pnt-modal-shell { width: 100%; max-height: 94vh; border-radius: 19px 19px 0 0; }
  .pnt-modal-foot { position: sticky; bottom: 0; background: var(--pnt-card); }
  .pnt-modal-foot .pnt-btn { flex: 1; justify-content: center; }
  .pnt-secret-row { flex-direction: column; }
  /* Comfortable touch targets. */
  .pnt-btn, .pnt-tab { min-height: 40px; }
  .pnt-inp, .pnt-select { min-height: 40px; }
}

/* ---------------------------------------------------------------------------
   P6 — printer grouping (display only).
   ADDITIVE: every selector here is new (.pnt-group*), so no existing desktop
   rule is altered and an ungrouped list renders byte-for-byte as it did before.
   The mobile block below sits inside the same 1180px breakpoint the rest of the
   file uses, and nothing here can scroll horizontally: the children are a
   normal block flow with a left inset, not a fixed-width row.
   ------------------------------------------------------------------------ */
.pnt-group { display: flex; flex-direction: column; gap: 10px; }
.pnt-group-kids {
  display: flex; flex-direction: column; gap: 10px;
  margin-left: 18px; padding-left: 14px;
  border-left: 2px solid var(--pnt-line-s);
}
/* P7: the label became a real control, so it has to LOOK like one — it was previously
   static text and people would not think to click it. Reset the button chrome rather than
   restyle it, so it keeps the old typography and only gains the affordances. */
.pnt-group-lbl {
  font-size: 12px; font-weight: 700; letter-spacing: .02em;
  color: var(--pnt-sub); text-transform: uppercase;
  display: flex; align-items: center; gap: 7px;
  appearance: none; background: none; border: 0; box-shadow: none;
  padding: 4px 2px; margin: 0; width: fit-content;
  cursor: pointer; text-align: left; font-family: inherit;
}
.pnt-group-lbl:hover { color: var(--pnt-primary); }
.pnt-group-lbl:focus-visible { outline: 2px solid var(--pnt-primary); outline-offset: 2px; border-radius: 6px; }
.pnt-chev { display: inline-block; transition: transform .15s ease; font-size: 11px; }
.pnt-chev.open { transform: rotate(90deg); }
/* Collapsed groups keep the label but drop the left rule, which would otherwise hang under
   the parent card pointing at nothing. */
.pnt-group-kids.collapsed { border-left-color: transparent; gap: 0; }
@media (max-width: 1180px) {
  /* Indenting costs width that a phone does not have, so children sit flush and
     are distinguished by the rule alone. */
  .pnt-group-kids { margin-left: 0; padding-left: 10px; }
}

/* ---------------------------------------------------------------------------
   P8 — the print preview.
   ADDITIVE: every selector is new (.pnt-prev*), so nothing above changes and the
   dialog without a file chosen renders exactly as it did.

   THE ONE RULE THAT MATTERS: .pnt-prev-strip.full must NOT scale the image. The
   preview exists to tell the truth about legibility at 203 dpi, and a stylesheet
   that quietly resizes it turns it back into the reassuring lie it replaced. In
   "Fit" the image is scaled down to be glanceable; in "Actual size" it is left at
   its natural pixel size and the container scrolls.
   ------------------------------------------------------------------------ */
.pnt-prev { margin-top: 12px; display: flex; flex-direction: column; gap: 10px; }
.pnt-prev-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
}
.pnt-prev-count { font-size: 15px; font-weight: 700; color: var(--pnt-text); }
.pnt-prev-zoom { display: flex; gap: 6px; }
.pnt-prev-zoom .pnt-btn.on { background: var(--pnt-primary); color: #fff; border-color: var(--pnt-primary); }
.pnt-prev-note {
  font-size: 12.5px; padding: 8px 11px; border-radius: 9px;
  background: var(--pnt-soft); color: var(--pnt-sub); border: 1px solid var(--pnt-line-s);
}
/* A VERTICAL LIST, NOT A SIDEWAYS STRIP.
   The strip was a row of thumbnails, which meant the only way to see a whole page
   was a 132px card — too small to check anything — or "Actual size", which needs
   scrolling in two directions at once. The count is already stated in words above
   ("4 pages — 4 labels will print"), so a row bought nothing that sentence did not.
   One page per row, each as big as the panel allows, is what an operator can
   actually check before pressing Print. */
.pnt-prev-strip {
  display: flex; flex-direction: column; gap: 12px; padding: 4px 2px 10px;
  -webkit-overflow-scrolling: touch;
}
/* Picture on the left, what-it-will-do on the right — the facts stay beside the
   page they describe instead of being pushed off the bottom of the dialog. */
.pnt-prev-page {
  margin: 0; display: flex; flex-direction: row; align-items: flex-start; gap: 12px;
  max-width: 100%;
  padding: 9px; border-radius: 11px;
  background: var(--pnt-soft); border: 1px solid var(--pnt-line-s);
}
.pnt-prev-page > figcaption { flex: 1 1 auto; min-width: 0; }
.pnt-prev-stage {
  background: #fff; border: 1px solid var(--pnt-line); border-radius: 9px;
  padding: 6px; display: flex; align-items: flex-start; justify-content: center;
  overflow: auto; flex: 0 0 auto; max-width: 100%;
}
/* FIT: the WHOLE page, as large as the panel allows. max-width keeps a 4x6 to a
   sensible card (300px wide is 450px tall) rather than filling the dialog with one
   page, and width:100% lets it shrink on a narrow screen instead of overflowing. */
.pnt-prev-img { display: block; width: 100%; max-width: 300px; height: auto; image-rendering: auto; }
/* ACTUAL SIZE: natural pixels, one screen pixel per printer dot. The card stacks so
   the picture gets the dialog's full width before it has to be scrolled at all. */
/* ACTUAL SIZE: the card stacks, and EVERY BIT OF CHROME GETS OUT OF THE WAY.
   An 812px page needed 888px of dialog once card padding, stage padding and body
   padding were added, and an 873px-wide window could not give it that — so it still
   scrolled sideways by about 15px, which is the most annoying possible amount.
   Stripping the padding in this mode drops the requirement to roughly 812 + 20px,
   which fits. The padding is decoration; not having to drag the page sideways to
   read the right-hand column is not. */
.pnt-prev-strip.full .pnt-prev-page {
  flex-direction: column; padding: 0; background: none; border: 0; gap: 8px;
}
/* flex:0 0 auto is LOAD-BEARING and was found by measuring, not by reading. The stage
   is a flex container, so the image is a flex item and shrinks to fit by default —
   an 812px picture in a 520px dialog was laid out at 492px, which is a 61% preview
   wearing an "Actual size" label. Refusing to shrink is what makes the stage's
   overflow:auto scroll instead.
   THE WIDTH ITSELF is set inline by the module from the label's real size in inches.
   `width:auto` was tried and measured wrong: it lays out from the image's intrinsic
   size, which is 0 until a data URI has decoded, so the panel opened empty and
   jumped. max-width:none defeats any inherited img{max-width:100%}.

   NO image-rendering:pixelated HERE, AND THAT IS A CORRECTION.
   It was set back when "Actual size" meant 1:1 — 812 image pixels drawn at 812 CSS
   pixels — where there is no resampling and nearest-neighbour is honest. Actual size
   now means the LABEL's real size: an 812px page drawn at 4in x 96 = 384px, a
   downscale to 0.47. At that ratio `pixelated` DROPS every other pixel column
   instead of averaging it, so a barcode bar or a glyph stem one pixel wide has a
   better-than-even chance of disappearing. It made the labels look broken in Actual
   size while the Fit view, which smooth-scales, looked clean. Smooth scaling is what
   an eye does to a real label at arm's length, so it is also the truthful one. */
.pnt-prev-strip.full .pnt-prev-img { flex: 0 0 auto; max-width: none; image-rendering: auto; }
/* justify-content:flex-start IS THE FIX FOR A CLIPPED PREVIEW, and it is not cosmetic.
   The stage centres its child, which is right when the picture is smaller than the
   stage. When it is BIGGER, centring puts the item at a NEGATIVE offset — and a flex
   container generates no scrollable overflow on its start edge, so scrollLeft bottoms
   out at 0 with the left of the image still off-screen and no way to reach it.
   Measured at a 630px dialog: the image sat at -98px, scrollWidth reported 719 for an
   812px picture, and 98px of the left edge could not be scrolled to at all — which
   read on screen as "Slip" where the page says "Packing Slip". Anchoring to the start
   sends the whole overflow to the right, where it is scrollable. */
/* NO SIDEWAYS SCROLLING, AND NO NESTED SCROLLER.
   A 4x6 label at 203 dpi is 812 x 1218 real dots — TALLER than it is wide. There is
   therefore no good reason for "Actual size" to scroll horizontally: the dialog just
   has to be as wide as the page, and then the only direction left is down. So the
   shell widens to the page's natural width (see .pnt-modal-shell.pnt-prev-wide) and
   the stage stops being a scroller at all — max-height:none hands the scrolling to
   the modal body, so there is ONE scrollbar going one way instead of a small box you
   have to drag around in two.
   justify-content:flex-start stays: it is what stops a centred overflowing flex item
   putting its left edge out of reach, which is still possible on a phone too narrow
   to fit 812px. Measured at a 630px dialog before the fix: -98px, permanently
   unreachable. */
.pnt-prev-strip.full .pnt-prev-stage {
  width: 100%; max-height: none; justify-content: flex-start; padding: 0;
  /* overflow:visible, NOT auto, and this was measured. With overflow:auto the stage
     grew its own VERTICAL scrollbar (the page is 1218px tall), that scrollbar ate
     10px of width, and 812px of picture then no longer fitted in 816px of stage — so
     it grew a HORIZONTAL scrollbar too and the right-hand column went off the edge
     again. Six pixels short is enough to bring the whole problem back. Letting the
     modal body do all the scrolling leaves one scrollbar, going one way. */
  overflow: visible;
}
/* Widened only while "Actual size" is showing, and only as wide as the page really
   is — --pnt-prev-w is set inline by the module from the server's reported pixel
   width, so a 5x5 (1015px) or a 6x4 (1218px) each get exactly what they need. The
   76px is the chrome between the shell edge and the picture: modal body padding 38,
   strip 4, card 20, stage 14. min(..., 100%) keeps it inside the viewport, so a
   phone still clamps and scrolls rather than pushing the dialog off-screen. */
.pnt-modal-shell.pnt-prev-wide { width: min(calc(var(--pnt-prev-w, 812px) + 40px), 100%); }
/* The dialog gives back its own outer breathing room too while a full-size page is
   on screen — 36px of it — because that is 36px the page could be using instead. */
.pnt-modal:has(.pnt-modal-shell.pnt-prev-wide) { padding: 6px; }
.pnt-prev-wide .pnt-modal-body { padding: 12px 10px; }
.pnt-prev-strip.full .pnt-prev-page { flex: 0 0 auto; width: 100%; }
.pnt-prev-nopic {
  width: 200px; min-height: 120px; display: flex; align-items: center; text-align: center;
  font-size: 12px; color: var(--pnt-sub); padding: 10px;
}
/* The picture and the edge guide share one box, so the dashed line stays exactly on
   the label's safety inset whatever size the picture is drawn at. */
.pnt-prev-frame { position: relative; display: block; line-height: 0; flex: 0 0 auto; max-width: 100%; }
.pnt-prev-strip.full .pnt-prev-frame { max-width: none; }
/* THE EDGE THE PRINTER CANNOT REACH, drawn on the label at true size. This is the
   strip that ate the left of every line in the operator's photo — content placed
   hard against x=0 on a printer that cannot print to its physical edge. Showing it
   is the difference between "it came out cut off" discovered at the bench and seen
   on screen before pressing Print. */
.pnt-prev-safe {
  position: absolute; pointer-events: none;
  border: 1px dashed rgba(190, 60, 60, .55); border-radius: 2px;
}
.pnt-prev-edge { font-size: 11.5px; line-height: 1.45; margin-top: 5px; color: #a12d2d; }
.pnt-prev-edge.ok { color: var(--pnt-sub); }
html[data-theme="dark"] .pnt-prev-edge { color: #f0a3a3; }
html[data-theme="dark"] .pnt-prev-edge.ok { color: var(--pnt-sub); }
.pnt-prev-n { font-size: 13px; font-weight: 700; color: var(--pnt-text); margin-bottom: 3px; }
.pnt-prev-facts { font-size: 12px; color: var(--pnt-sub); line-height: 1.5; }
.pnt-prev-warn {
  font-size: 11.5px; line-height: 1.45; margin-top: 6px; padding: 6px 9px;
  border-radius: 7px;
  background: rgba(180, 130, 20, .1); color: #8a6100; border: 1px solid rgba(180, 130, 20, .25);
}
.pnt-prev-warn.bad { background: rgba(190, 60, 60, .1); color: #a12d2d; border-color: rgba(190, 60, 60, .28); }
html[data-theme="dark"] .pnt-prev-warn { color: #e8c07a; }
html[data-theme="dark"] .pnt-prev-warn.bad { color: #f0a3a3; }
html[data-theme="dark"] .pnt-prev-stage { background: #fff; }

@media (max-width: 1180px) {
  /* A phone has no room for picture-beside-facts, so the card stacks. "Actual size"
     still refuses to scale the image — that view IS the measurement, and shrinking
     it to fit would be exactly the reassuring lie the preview exists to replace. */
  .pnt-prev-head { flex-direction: column; align-items: stretch; }
  .pnt-prev-zoom .pnt-btn { flex: 1; justify-content: center; }
  .pnt-prev-page { flex-direction: column; }
  .pnt-prev-stage { width: 100%; }
  .pnt-prev-img { max-width: 240px; margin: 0 auto; }
  .pnt-prev-nopic { width: 100%; }
  .pnt-prev-strip.full .pnt-prev-stage { max-height: 52vh; }
}
