/* PDF Editor — styles (Build 1)
   Stage rule: .stage-wrap is a fixed flex slot between the toolbar and the
   status bar. Panels (thumbnails, later the inspector and sheets) are
   absolutely positioned overlays INSIDE .stage-wrap, so opening or closing
   them never changes the stage box or the page canvas rect. */
:root {
  --ink: #1d2321;
  --ink-soft: #4c554f;
  --paper: #f7f3ec;
  --card: #fffdf8;
  --line: #ddd3bf;
  --brand: #17554f;
  --brand-deep: #123332;
  --gold: #e8a33d;
  --danger: #c7432e;
  --desk: #e9e2d2;
  --radius: 14px;
  --shadow: 0 18px 50px rgba(18, 51, 50, .18);
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans Devanagari", sans-serif;
  color: var(--ink);
  background: var(--paper);
}
#app { display: flex; flex-direction: column; height: 100dvh; overflow: hidden; }

/* ——— Top bar ——— */
.topbar {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 16px;
  background: var(--brand-deep); color: #f3eee1;
}
.brand { display: flex; align-items: center; gap: 10px; color: inherit; text-decoration: none; }
.brand img { border-radius: 7px; }
.brand-name { font-weight: 700; font-size: 18px; letter-spacing: .2px; }
.top-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }

/* ——— Buttons ——— */
.btn {
  font: inherit; font-size: 14px; cursor: pointer;
  border: 1px solid var(--line); background: var(--card); color: var(--ink);
  padding: 7px 14px; border-radius: 999px; line-height: 1.2;
}
.btn:hover:not(:disabled) { border-color: var(--brand); }
.btn:disabled { opacity: .45; cursor: default; }
.btn.primary { background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 600; }
.btn.primary:hover:not(:disabled) { background: #1d655d; }
.btn.ghost { background: transparent; }
.topbar .btn { background: rgba(255,255,255,.08); border-color: rgba(255,255,255,.25); color: #f3eee1; }
.topbar .btn.primary { background: var(--gold); border-color: var(--gold); color: #23180a; }
.btn.big { font-size: 16px; padding: 12px 26px; }
.btn.icon { padding: 5px 12px; font-size: 18px; }
/*B32*/
/* Mobile QA wave (Build 32): kill the double-tap-zoom delay on every
   button, and guarantee 44px touch targets on coarse pointers. */
button { touch-action: manipulation; }
@media (pointer: coarse) {
  .btn { min-height: 44px; }
}
/* /B32 */
/* ——— Toolbar ——— */
.toolbar {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 8px 16px; background: var(--card); border-bottom: 1px solid var(--line);
}
.pagenav { display: flex; align-items: center; gap: 8px; }
.page-label { display: flex; align-items: center; gap: 6px; font-size: 14px; color: var(--ink-soft); }
#pageInput { width: 58px; font: inherit; font-size: 14px; padding: 5px 8px; border: 1px solid var(--line); border-radius: 8px; text-align: center; }
.zoom-wrap select { font: inherit; font-size: 14px; padding: 7px 34px 7px 12px; border: 1px solid var(--line); border-radius: 999px; background: var(--card); appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%234c554f' fill='none' stroke-width='1.6'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 14px center; }
.doc-meta { font-size: 13px; color: var(--ink-soft); margin-left: auto; }

/* ——— Stage ——— */
.stage-wrap { position: relative; flex: 1 1 auto; min-height: 0; background: var(--desk); overflow: hidden; }
#stageScroller {
  position: absolute; inset: 0; overflow: auto;
  display: grid; justify-items: center; align-content: start;
  padding: 26px 18px 40px;
}
#pageHolder {
  position: relative; background: #fff;
  box-shadow: var(--shadow);
  line-height: 0;
}
#pageCanvas { display: block; }
#overlay { position: absolute; inset: 0; line-height: normal; }

/* ——— Thumbnails panel (overlay — never resizes the stage) ——— */
#thumbsPanel {
  position: absolute; top: 0; bottom: 0; left: 0; width: 176px;
  background: rgba(255, 253, 248, .97); border-right: 1px solid var(--line);
  transform: translateX(-105%); transition: transform .22s ease;
  overflow-y: auto; padding: 14px 12px; z-index: 5;
}
#thumbsPanel.open { transform: translateX(0); box-shadow: var(--shadow); }
#thumbsList { display: flex; flex-direction: column; gap: 12px; align-items: center; }
.thumb { cursor: pointer; border: 2px solid transparent; border-radius: 8px; padding: 3px; background: none; font: inherit; }
.thumb canvas { display: block; border: 1px solid var(--line); border-radius: 4px; background: #fff; max-width: 140px; height: auto; }
.thumb .thumb-num { display: block; text-align: center; font-size: 12px; color: var(--ink-soft); margin-top: 4px; }
.thumb.active { border-color: var(--brand); }
.thumb.active .thumb-num { color: var(--brand); font-weight: 700; }

/* ——— Empty state ——— */
#emptyState {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  padding: 20px; overflow-y: auto;
}
#emptyState[hidden] { display: none; }
.empty-card {
  background: var(--card); border: 1px solid var(--line); border-radius: 20px;
  box-shadow: var(--shadow); max-width: 520px; width: 100%;
  padding: 34px 30px; text-align: center;
}
.empty-card h1 { font-size: 24px; margin: 14px 0 8px; }
.empty-card p { color: var(--ink-soft); margin: 0 0 20px; line-height: 1.55; }
.promises { list-style: none; margin: 22px 0 0; padding: 16px 0 0; border-top: 1px solid var(--line); text-align: left; }
.promises li { padding: 5px 0 5px 26px; position: relative; font-size: 14.5px; color: var(--ink-soft); }
.promises li::before { content: "✓"; position: absolute; left: 4px; color: var(--brand); font-weight: 700; }

#dropVeil {
  position: absolute; inset: 10px; z-index: 9;
  border: 3px dashed var(--brand); border-radius: 18px;
  background: rgba(23, 85, 79, .12);
  display: flex; align-items: center; justify-content: center;
  font-size: 22px; font-weight: 700; color: var(--brand-deep);
  pointer-events: none;
}
#dropVeil[hidden] { display: none; }

/* ——— Status bar ——— */
.statusbar {
  display: flex; align-items: center; gap: 14px;
  padding: 7px 16px; font-size: 13px; color: var(--ink-soft);
  background: var(--card); border-top: 1px solid var(--line);
}
.statusbar .spacer { flex: 1; }
.privacy-note { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.build-chip { background: var(--brand-deep); color: #f3eee1; border-radius: 999px; padding: 2px 10px; font-size: 12px; font-weight: 600; flex: none; }

@media (max-width: 640px) {
  .brand-name { font-size: 16px; }
  .doc-meta { display: none; }
  .privacy-note { display: none; }
  .empty-card { padding: 26px 20px; }
  #stageScroller { padding: 16px 8px 30px; }
}

/* ——— Bundled fonts (Build 3): the same files pdf-lib embeds on export,
   so the on-screen preview matches the written PDF. ——— */
@font-face { font-family: 'PE Sans'; src: url('./fonts/LiberationSans-Regular.ttf?v=1') format('truetype'); font-weight: 400; }
@font-face { font-family: 'PE Sans'; src: url('./fonts/LiberationSans-Bold.ttf?v=1') format('truetype'); font-weight: 700; }
@font-face { font-family: 'PE Sans'; src: url('./fonts/LiberationSans-Italic.ttf?v=1') format('truetype'); font-style: italic; }
@font-face { font-family: 'PE Sans'; src: url('./fonts/LiberationSans-BoldItalic.ttf?v=1') format('truetype'); font-weight: 700; font-style: italic; }
@font-face { font-family: 'PE Serif'; src: url('./fonts/LiberationSerif-Regular.ttf?v=1') format('truetype'); }
@font-face { font-family: 'PE Serif'; src: url('./fonts/LiberationSerif-Bold.ttf?v=1') format('truetype'); font-weight: 700; }
@font-face { font-family: 'PE Serif'; src: url('./fonts/LiberationSerif-Italic.ttf?v=1') format('truetype'); font-style: italic; }
@font-face { font-family: 'PE Serif'; src: url('./fonts/LiberationSerif-BoldItalic.ttf?v=1') format('truetype'); font-weight: 700; font-style: italic; }
@font-face { font-family: 'PE Mono'; src: url('./fonts/LiberationMono-Regular.ttf?v=1') format('truetype'); }
@font-face { font-family: 'PE Mono'; src: url('./fonts/LiberationMono-Bold.ttf?v=1') format('truetype'); font-weight: 700; }
@font-face { font-family: 'PE Devanagari'; src: url('./fonts/NotoSansDevanagari-Regular.ttf?v=1') format('truetype'); }
@font-face { font-family: 'PE Devanagari'; src: url('./fonts/NotoSansDevanagari-Bold.ttf?v=1') format('truetype'); font-weight: 700; }

/* ——— Edit bar (Build 2+) ——— */
.editbar {
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  padding: 7px 16px; background: #fdf9ef; border-bottom: 1px solid var(--line);
}
.editbar[hidden] { display: none; }
.edit-group { display: flex; gap: 7px; align-items: center; }
.edit-group + .edit-group { border-left: 1px solid var(--line); padding-left: 18px; }

/* ——— Overlay objects (Build 2+) ——— */
#overlay { pointer-events: none; }
.obj {
  position: absolute; pointer-events: auto; cursor: move;
  border: 1.5px solid transparent; border-radius: 3px;
  overflow: visible; user-select: none; -webkit-user-select: none;
  touch-action: none; transform-origin: center center;
}
.obj:hover { border-color: rgba(23, 85, 79, .45); }
.obj.selected { border-color: var(--brand); }
.obj-text { padding: 0; overflow: hidden; overflow-wrap: break-word; }
.obj-image img { width: 100%; height: 100%; display: block; pointer-events: none; }
.obj-shape svg { display: block; pointer-events: none; }
.handle {
  position: absolute; width: 13px; height: 13px; border-radius: 50%;
  background: #fff; border: 2px solid var(--brand); z-index: 3;
}
.handle-nw { left: -8px; top: -8px; cursor: nwse-resize; }
.handle-ne { right: -8px; top: -8px; cursor: nesw-resize; }
.handle-sw { left: -8px; bottom: -8px; cursor: nesw-resize; }
.handle-se { right: -8px; bottom: -8px; cursor: nwse-resize; }
.handle-rot { left: 50%; top: -30px; transform: translateX(-50%); cursor: grab; background: var(--gold); }
.inline-editor {
  position: absolute; z-index: 8; resize: both;
  border: 2px solid var(--brand); border-radius: 4px;
  background: rgba(255, 255, 255, .92); padding: 0; margin: 0;
  transform-origin: center center; outline: none; overflow: hidden;
}

/* ——— Inspector (Build 2+): absolute sheet inside the stage wrap, so it
   never changes the stage or canvas geometry. ——— */
#inspector {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: 12px; z-index: 6; width: min(680px, calc(100% - 20px));
  max-height: 52%; overflow-y: auto;
  background: rgba(255, 253, 248, .98); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 12px 14px; display: flex; flex-wrap: wrap; gap: 10px 18px; align-items: end;
}
#inspector[hidden] { display: none; }
.insp-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--ink-soft); }
.insp-field input[type="number"] { width: 76px; font: inherit; font-size: 14px; padding: 5px 8px; border: 1px solid var(--line); border-radius: 8px; }
.insp-field input[type="color"] { width: 52px; height: 32px; border: 1px solid var(--line); border-radius: 8px; background: none; padding: 2px; cursor: pointer; }
.insp-field select { font: inherit; font-size: 14px; padding: 6px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--card); }
.insp-text { font: inherit; font-size: 14px; min-width: 220px; padding: 7px 9px; border: 1px solid var(--line); border-radius: 8px; }
.insp-row { display: flex; gap: 7px; align-items: center; flex-wrap: wrap; }
.insp-check { display: flex; gap: 6px; align-items: center; font-size: 13.5px; color: var(--ink-soft); }
.btn.chip { padding: 6px 12px; font-size: 13.5px; }
.btn.chip.on { background: var(--brand); border-color: var(--brand); color: #fff; }
.btn.chip.danger { color: var(--danger); border-color: var(--danger); }

/*B5*/
/* ——— Draw & annotate tools (Build 5) ——— */
/* With a tool active the overlay takes pointer events so clicks anywhere
   on the page start a stroke; the stage box itself is untouched. */
#pageHolder.tool-active #overlay { pointer-events: auto; cursor: crosshair; }
#pageHolder.tool-active .obj { cursor: crosshair; }
.btn.tool-on { background: var(--brand); border-color: var(--brand); color: #fff; }
.obj-draw svg { display: block; pointer-events: none; }
.obj-highlight { border-radius: 2px; }
.stroke-preview { z-index: 7; }
.mark-line { position: absolute; left: 0; right: 0; border-radius: 2px; pointer-events: none; }
.obj-mark.kind-underline .mark-line { bottom: 1px; }
.obj-mark.kind-strike .mark-line { top: 50%; transform: translateY(-50%); }
.obj-note {
  background: #ffe9a8; border: 1px solid #cfa63e; border-radius: 4px 4px 4px 0;
  box-shadow: 0 2px 6px rgba(18, 51, 50, .25);
}
.obj-note::after {
  content: ''; position: absolute; left: 3px; bottom: -8px;
  border: 5px solid transparent; border-top-color: #cfa63e;
}
.note-editor { min-width: 200px; min-height: 70px; background: #fffbe8; }
/* Notes panel: absolute overlay inside the stage wrap (like the thumbs
   panel, mirrored) — it never changes the stage or canvas geometry. */
#notesPanel {
  position: absolute; top: 0; bottom: 0; right: 0; width: 230px;
  background: rgba(255, 253, 248, .97); border-left: 1px solid var(--line);
  transform: translateX(105%); transition: transform .22s ease;
  overflow-y: auto; padding: 14px 12px; z-index: 5;
}
#notesPanel.open { transform: translateX(0); box-shadow: var(--shadow); }
.notes-head { font-size: 13px; font-weight: 700; color: var(--ink-soft); margin: 0 0 10px; }
.note-item {
  display: block; width: 100%; text-align: left; font: inherit; font-size: 13px; color: var(--ink);
  background: var(--card); border: 1px solid var(--line); border-radius: 10px;
  padding: 8px 10px; margin: 0 0 8px; cursor: pointer;
}
.note-item:hover { border-color: var(--brand); }
.note-item .note-page { display: block; font-size: 11.5px; color: var(--ink-soft); font-weight: 600; margin-bottom: 2px; }
.note-item .note-text {
  display: block; overflow: hidden; display: -webkit-box;
  -webkit-line-clamp: 3; -webkit-box-orient: vertical;
}
.note-empty { color: var(--ink-soft); font-size: 13.5px; text-align: center; padding: 24px 8px; line-height: 1.5; }
/* /B5 */
/*B6*/
/* ——— Whiteout & redaction covers (Build 6) ——— */
/* Whiteout: opaque white fill; a dashed edge keeps it visible against the
   white page on screen (export draws only the white rect). Redact is
   always an opaque black box. */
.obj-whiteout { border: 1.5px dashed #9aa3a1; border-radius: 2px; }
.obj-redact { background: #111; border-radius: 2px; }
/* Distinct swatches on the tool buttons: white vs black. */
.btn.swatch::before {
  content: ''; display: inline-block; width: 12px; height: 12px;
  border-radius: 3px; margin-right: 7px; vertical-align: -1px;
}
.btn.sw-white::before { background: #fff; border: 1.5px solid #8a9290; }
.btn.sw-black::before { background: #111; border: 1.5px solid #111; }
/* Honest-redaction warning banner: absolute overlay inside the stage area,
   shown while the Redact tool is armed. Never moves or resizes the canvas. */
.warn-banner {
  position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
  z-index: 12; max-width: min(560px, calc(100% - 32px));
  display: flex; gap: 12px; align-items: center;
  background: #fff7e6; border: 1.5px solid #d9a441; border-radius: 12px;
  box-shadow: var(--shadow); padding: 10px 14px;
  font-size: 13px; line-height: 1.45; color: #5c4310;
}
.warn-banner[hidden] { display: none; }
.warn-banner .warn-text { flex: 1 1 auto; }
.warn-banner .warn-dismiss { flex: 0 0 auto; }
/* Same warning, shown at the top of the inspector when a redact box is
   selected. */
.insp-warn {
  flex-basis: 100%; margin: 0; padding: 8px 10px; font-size: 12.5px;
  line-height: 1.45; color: #5c4310; background: #fff7e6;
  border: 1.5px solid #d9a441; border-radius: 8px;
}
/* /B6 */
/*B7*/
/* ——— Signatures (Build 7) ——— */
/* Signature panel: absolute overlay inside the stage wrap (right side),
   like the notes panel — it never moves or resizes the page canvas. */
.sign-panel {
  position: absolute; top: 12px; right: 12px; z-index: 11;
  width: min(360px, calc(100% - 24px));
  max-height: calc(100% - 24px); overflow-y: auto;
  background: rgba(255, 253, 248, .98); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 14px;
}
.sign-panel[hidden] { display: none; }
.sign-head { display: flex; align-items: center; justify-content: space-between; margin: 0 0 10px; }
.sign-head h2 { font-size: 15px; margin: 0; }
.sign-tabs { display: flex; gap: 6px; margin-bottom: 10px; }
.sign-pane[hidden] { display: none; }
#sigDrawCanvas {
  width: 100%; height: auto; display: block;
  background: #fff; border: 1.5px solid var(--line); border-radius: 8px;
  touch-action: none; cursor: crosshair;
}
.sign-hint { font-size: 12.5px; color: var(--ink-soft); margin: 8px 0; line-height: 1.45; }
.sign-row { display: flex; gap: 10px; align-items: center; margin-top: 8px; flex-wrap: wrap; }
.sign-row .spacer { flex: 1 1 auto; }
.sign-ink-label { font-size: 12.5px; color: var(--ink-soft); }
.ink-swatch { display: inline-flex; gap: 5px; align-items: center; font-size: 12.5px; cursor: pointer; color: var(--ink); }
.ink-dot { width: 14px; height: 14px; border-radius: 50%; border: 1.5px solid #8a9290; display: inline-block; }
.sign-type-input {
  width: 100%; font: inherit; font-size: 16px; padding: 9px 11px; margin-top: 6px;
  border: 1.5px solid var(--line); border-radius: 8px; background: #fff; color: var(--ink);
}
.sign-preview-row { margin: 12px 0 4px; }
.sign-preview-label { font-size: 12.5px; color: var(--ink-soft); display: block; margin-bottom: 6px; }
.sign-preview-box {
  min-height: 64px; display: flex; align-items: center; justify-content: center;
  background: #fff; border: 1.5px dashed #b9c2c0; border-radius: 8px; padding: 8px;
}
.sign-preview-box img { max-width: 100%; max-height: 84px; }
#sigPreviewEmpty { font-size: 12.5px; color: var(--ink-soft); text-align: center; }
.sign-actions { display: flex; gap: 8px; margin: 10px 0; }
.sign-note {
  font-size: 11.5px; color: var(--ink-soft); line-height: 1.5; margin: 8px 0 0;
  border-top: 1px solid var(--line); padding-top: 8px;
}
.sign-lib-head { font-size: 13px; margin: 12px 0 8px; color: var(--ink-soft); }
.sign-lib { display: flex; flex-direction: column; gap: 8px; }
.sign-lib-item {
  display: flex; gap: 8px; align-items: center;
  background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 6px 8px;
}
.sign-lib-thumb { max-height: 40px; max-width: 110px; background: #fff; border: 1px solid var(--line); border-radius: 6px; padding: 3px; }
.sign-lib-cap { font-size: 11.5px; color: var(--ink-soft); flex: 1 1 auto; }
.sign-lib-empty { font-size: 12.5px; color: var(--ink-soft); text-align: center; padding: 10px 4px; line-height: 1.5; }
/* /B7 */
/*B8*/
/* ——— Form filling (Build 8) ——— */
/* Native HTML widgets sit exactly over each detected field rect. The
   wrapper is inert for move/resize (fields stay anchored); the control
   inside is fully interactive. touch-action/user-select are restored for
   the controls so taps focus and text stays selectable on touch. */
.obj-formfill {
  border: 1.5px dashed rgba(23, 85, 79, .35); border-radius: 4px;
  background: rgba(255, 255, 255, .35); cursor: default;
  touch-action: manipulation;
}
.obj-formfill.selected { border-style: solid; border-color: var(--brand); }
.obj-formfill input, .obj-formfill select {
  user-select: auto; -webkit-user-select: auto;
}
.obj-formfill input[type="text"], .obj-formfill select {
  width: 100%; height: 100%; font: inherit;
  border: none; background: transparent; color: var(--ink);
  padding: 1px 4px; outline: none; cursor: text;
}
.obj-formfill select { cursor: default; }
.ff-check-wrap { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
.obj-formfill input[type="checkbox"] { width: 16px; height: 16px; margin: 0; accent-color: var(--brand); cursor: pointer; }
/* Flatten toggle in the edit bar: shown only when the document has
   fillable fields. */
.form-opt { display: flex; gap: 8px; align-items: center; font-size: 13.5px; cursor: pointer; }
.form-opt input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--brand); }
.form-note { font-size: 12px; color: var(--ink-soft); max-width: 380px; line-height: 1.4; }
.insp-fieldname { font-size: 13.5px; font-weight: 600; margin: 0; flex-basis: 100%; word-break: break-all; }
/* /B8 */
/*B9*/
/* ——— Stamps & watermark (Build 9) ——— */
/* Both panels are absolute overlays inside the stage wrap (like the sign
   panel) — they never move or resize the page canvas. */
.stamp-panel {
  position: absolute; top: 12px; right: 12px; z-index: 11;
  width: min(340px, calc(100% - 24px));
  max-height: calc(100% - 24px); overflow-y: auto;
  background: rgba(255, 253, 248, .98); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 14px;
}
.stamp-panel[hidden] { display: none; }
.stamp-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 12px; }
.stamp-preset {
  font: inherit; font-size: 13.5px; font-weight: 700; letter-spacing: 1px;
  text-transform: uppercase; text-align: center;
  background: rgba(255, 255, 255, .7); border: 3px solid var(--line);
  border-radius: 10px; padding: 12px 6px; cursor: pointer; color: var(--ink);
}
.stamp-preset:hover { filter: brightness(.96); }
.stamp-custom { display: flex; flex-direction: column; gap: 10px; }
.stamp-custom-row { align-items: center; }
.stamp-custom-row input[type="color"] {
  width: 52px; height: 32px; border: 1px solid var(--line);
  border-radius: 8px; background: none; padding: 2px; cursor: pointer;
}
.stamp-custom-row .spacer { flex: 1 1 auto; }
#watermarkPanel .insp-field { margin-bottom: 10px; }
#watermarkPanel input[type="range"] { width: 100%; accent-color: var(--brand); }
/* Placed stamps: bordered rounded box, uppercase centered text in the
   stamp colour. The border is set inline (3px solid <color>). */
.obj-stamp {
  border-radius: 10px; background: rgba(255, 255, 255, .55);
  display: flex; align-items: center; justify-content: center;
  padding: 4px 8px;
}
.stamp-text {
  font-weight: 700; text-transform: uppercase; text-align: center;
  line-height: 1.15; white-space: nowrap; overflow: hidden;
}
/* Watermarks render under the page's own objects (see renderWatermarks):
   large centered text, rotated, translucent. */
.obj-watermark {
  display: flex; align-items: center; justify-content: center;
  text-align: center; line-height: 1.1; white-space: nowrap; overflow: visible;
  user-select: none; -webkit-user-select: none;
}
/* /B9 */
/*B10*/
/* ——— Selection & touch polish (Build 10) ——— */
/* Multi-selection bar: absolute overlay at the stage bottom (like the
   inspector, stacked above it) — the canvas never moves or resizes. */
.sel-bar {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: 12px; z-index: 7;
  display: flex; gap: 8px; align-items: center;
  background: rgba(255, 253, 248, .98); border: 1px solid var(--line);
  border-radius: 999px; box-shadow: var(--shadow);
  padding: 8px 12px; max-width: calc(100% - 20px); overflow-x: auto;
}
.sel-bar[hidden] { display: none; }
#stageWrap.selbar-on #inspector { bottom: 76px; }
.sel-count { font-size: 12.5px; color: var(--ink-soft); font-weight: 600; white-space: nowrap; }
.sel-group { display: flex; gap: 4px; }
.sel-group + .sel-group { border-left: 1px solid var(--line); padding-left: 8px; }
/* Smart guides: thin gold lines drawn while dragging (screen-only). */
.snap-guide { position: absolute; z-index: 9; pointer-events: none; background: var(--gold); opacity: .9; }
.snap-guide.v { width: 1px; top: 0; bottom: 0; }
.snap-guide.h { height: 1px; left: 0; right: 0; }
/* Shortcuts dialog: absolute overlay inside the stage wrap — the canvas
   never moves or resizes. */
.keys-dialog {
  position: absolute; inset: 0; z-index: 14;
  display: flex; align-items: center; justify-content: center;
  background: rgba(18, 51, 50, .35);
}
.keys-dialog[hidden] { display: none; }
.keys-card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 16px 18px; width: min(420px, calc(100% - 32px));
  max-height: calc(100% - 40px); overflow-y: auto;
}
.keys-list {
  display: grid; grid-template-columns: 1fr auto; gap: 7px 14px;
  margin: 10px 0 0; font-size: 13.5px;
}
.keys-list dt { color: var(--ink-soft); }
.keys-list dd { margin: 0; font-weight: 600; text-align: right; white-space: nowrap; }
/* Touch: ≥44px targets for tool buttons; handles keep their 13px look
   but gain a 45px hit area. The page pans with one finger while objects
   keep their no-scroll drag gesture; two fingers pinch-zoom via JS. */
@media (pointer: coarse) {
  .editbar .btn { min-height: 44px; }
  .sel-bar .btn { min-height: 44px; }
  #pageHolder { touch-action: pan-x pan-y; }
  .handle::after { content: ''; position: absolute; inset: -16px; }
}
/* /B10 */
/*B11*/
/* ——— Visual page grid (Build 11) ——— */
/* The grid is an absolute overlay INSIDE .stage-wrap: the stage box stays
   pixel-identical; only its content mode changes (canvas vs grid). */
#stageScroller[hidden] { display: none; }
.pages-grid {
  position: absolute; inset: 0; z-index: 6;
  display: flex; flex-direction: column;
  background: var(--desk); overflow: hidden;
}
.pages-grid[hidden] { display: none; }
.grid-bar {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 16px; border-bottom: 1px solid var(--line);
  background: var(--card);
}
.grid-count { font-size: 13px; color: var(--ink-soft); font-weight: 600; }
.grid-ops {
  display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
  padding: 10px 16px; border-bottom: 1px solid var(--line);
  background: var(--card);
}
.grid-list {
  flex: 1 1 auto; overflow-y: auto; padding: 16px;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 14px; align-content: start;
}
.page-cell {
  cursor: pointer; border: 2px solid transparent; border-radius: 10px;
  padding: 6px; background: var(--card); text-align: center;
  transition: border-color .12s ease, box-shadow .12s ease;
}
.page-cell canvas {
  display: block; width: 100%; height: auto;
  border: 1px solid var(--line); border-radius: 4px; background: #fff;
}
.page-cell .page-num {
  display: block; margin-top: 6px; font-size: 12.5px; color: var(--ink-soft);
}
.page-cell:hover { border-color: var(--line); }
.page-cell.selected { border-color: var(--brand); box-shadow: var(--shadow); }
.page-cell.selected .page-num { color: var(--brand); font-weight: 700; }
.page-cell.drop-target { border-color: var(--gold); border-style: dashed; }
.page-cell:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.grid-hint {
  margin: 0; padding: 10px 16px 14px; font-size: 12.5px; color: var(--ink-soft);
  border-top: 1px solid var(--line); background: var(--card);
}
@media (pointer: coarse) {
  .grid-ops .btn { min-height: 44px; }
  .page-cell { padding: 10px; }
}
/* /B11 */
/*B12*/
/* ——— Merge & split panels (Build 12) ——— */
/* Absolute overlays inside .stage-wrap, like the Build 11 grid: the stage
   box stays pixel-identical; only its content mode changes. */
.tool-panel {
  position: absolute; inset: 0; z-index: 6;
  display: flex; flex-direction: column; align-items: center;
  background: var(--desk); overflow-y: auto; padding: 20px 12px;
}
.tool-panel[hidden] { display: none; }
.tool-sheet {
  width: min(620px, 100%);
  background: var(--card); border: 1px solid var(--line);
  border-radius: 12px; padding: 16px; box-shadow: var(--shadow);
}
.tool-sheet .insp-field { margin-bottom: 12px; }
.tool-note { font-size: 13px; color: var(--ink-soft); margin: 0 0 12px; line-height: 1.5; }
.merge-list { list-style: none; margin: 0 0 12px; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.merge-row {
  display: flex; align-items: center; gap: 10px;
  border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px;
  background: var(--desk);
}
.merge-row.pinned { border-style: dashed; }
.merge-name { flex: 1 1 auto; font-weight: 600; font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.merge-meta { flex: 0 0 auto; font-size: 12.5px; color: var(--ink-soft); }
.merge-ops { flex: 0 0 auto; display: flex; gap: 4px; }
.merge-ops .btn { min-width: 34px; }
.merge-row.drop-target { border-color: var(--gold); border-style: dashed; }
@media (pointer: coarse) {
  .merge-ops .btn { min-height: 44px; min-width: 44px; }
}
/* /B12 */
/*B16*/
/* ——— Form creator (Build 16) ——— */
/* Edit mode: dashed placeholder boxes with name+kind labels. They move
   and resize through the standard object machinery (the rotate handle is
   removed in app.js — fields export as unrotated rects). The label is
   inert so drags always hit the box. */
.obj-formfield.ff-edit {
  border: 1.5px dashed rgba(23, 85, 79, .55); border-radius: 4px;
  background: rgba(255, 255, 255, .5);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; cursor: move;
}
.obj-formfield.ff-edit .ff-label {
  font-size: 11px; color: var(--ink-soft); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; pointer-events: none;
  padding: 0 4px;
}
/* Preview mode: created-field widgets reuse the .obj-formfill sizing
   (input/select/checkbox styles ride along); date + signature need their
   own control styles. */
.obj-formfill.ff-created input[type="date"] {
  width: 100%; height: 100%; border: none; background: transparent;
  color: var(--ink); padding: 1px 4px; font: inherit; outline: none;
}
.obj-formfill.ff-created input[type="radio"] {
  width: 16px; height: 16px; margin: 0; accent-color: var(--brand); cursor: pointer;
}
.obj-formfill.ff-created .ff-sign-btn {
  width: 100%; height: 100%; border: none; background: transparent;
  color: var(--brand); font: inherit; font-size: 12.5px; cursor: pointer;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  padding: 0 4px; overflow: hidden;
}
.obj-formfill.ff-created .ff-sign-btn img {
  max-width: 60%; max-height: 90%; object-fit: contain; pointer-events: none;
}
/* Preview-form toggle active state (the .tool-on convention). */
#ffPreviewToggle.on { background: var(--brand); border-color: var(--brand); color: #fff; }
/* /B16 */
/*B17*/
/* ——— Field properties & tab order (Build 17) ——— */
/* Tab-number badge on each field box in edit mode. The box is the
   positioning context (.obj sets position:absolute). */
.obj-formfield.ff-edit .ff-tab {
  position: absolute; top: -10px; right: -10px; z-index: 2;
  min-width: 20px; height: 20px; padding: 0 5px;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; line-height: 1;
  color: #fff; background: var(--brand); border-radius: 10px;
  pointer-events: none;
}
/* Tab-order panel: an absolute overlay inside .stage-wrap (the Build
   11–16 pattern) — the stage box stays pixel-identical. */
.taborder-panel {
  position: absolute; top: 12px; right: 12px; z-index: 11;
  width: min(340px, calc(100% - 24px));
  max-height: calc(100% - 24px); overflow-y: auto;
  background: rgba(255, 253, 248, .98); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 14px;
}
.taborder-panel[hidden] { display: none; }
.taborder-list { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.taborder-row {
  display: flex; align-items: center; gap: 8px;
  border: 1px solid var(--line); border-radius: 8px; padding: 6px 8px;
  background: var(--desk);
}
.taborder-num {
  flex: 0 0 auto; min-width: 24px; height: 24px; padding: 0 4px;
  display: flex; align-items: center; justify-content: center;
  font-size: 12.5px; font-weight: 700; color: #fff;
  background: var(--brand); border-radius: 12px;
}
.taborder-name {
  flex: 1 1 auto; font-size: 13px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.taborder-ops { flex: 0 0 auto; display: flex; gap: 4px; }
.taborder-ops .btn { min-width: 34px; }
@media (pointer: coarse) {
  .taborder-ops .btn { min-height: 44px; min-width: 44px; }
}
/* Capture mode: the button takes the toggle active state; the arming
   hint reuses .tool-note. */
#tabOrderCaptureBtn.on { background: var(--brand); border-color: var(--brand); color: #fff; }
#ffTabOrderBtn.on { background: var(--brand); border-color: var(--brand); color: #fff; }
/* Read-only inspector value (radio group selection). */
.insp-ro {
  font-size: 13.5px; color: var(--ink); margin: 0 0 12px;
  border: 1px solid var(--line); border-radius: 8px; padding: 7px 10px;
  background: var(--desk);
}
/* /B17 */
/*B18*/
/* Form-data panel: an absolute overlay inside .stage-wrap (the Build
   11–17 pattern) — the stage box stays pixel-identical. Sections reuse
   .tool-note; the progress line and the result line get their own
   emphasis. */
.fd-panel {
  position: absolute; top: 12px; right: 12px; z-index: 11;
  width: min(360px, calc(100% - 24px));
  max-height: calc(100% - 24px); overflow-y: auto;
  background: rgba(255, 253, 248, .98); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 14px;
}
.fd-panel[hidden] { display: none; }
.fd-panel h3 { font-size: 13.5px; margin: 0 0 6px; }
.fd-sec { border-top: 1px solid var(--line); padding-top: 12px; margin-top: 14px; }
.fd-sec h3 { font-size: 13.5px; margin: 0 0 6px; }
.fd-progress { font-size: 13px; font-weight: 600; color: var(--ink); margin: 8px 0 0; }
.fd-result { font-size: 13px; color: var(--ink-soft); margin: 8px 0 0; line-height: 1.5; }
#fdBtn.on { background: var(--brand); border-color: var(--brand); color: #fff; }
/* /B18 */
/*B19*/
/* Text-extraction map (Build 19): the dashed selection box over the
   selected text line. pointer-events:none so clicks pass through to the
   hit-test; transform-origin matches .obj (center). The toggle active
   state follows the .tool-on convention. */
.te-line-sel {
  position: absolute; pointer-events: none;
  border: 2px dashed var(--brand); border-radius: 3px;
  transform-origin: center center;
  background: rgba(23, 85, 79, 0.06);
}
#teBtn.on { background: var(--brand); border-color: var(--brand); color: #fff; }
/* /B19 */
/*B20*/
/* Whiteout + matched-font overlay (Build 20): the replacement text span
   over the bg-coloured cover box (pointer-events:none so clicks reach the
   textedit's own handler); the unconfirmed draft preview gets a dashed
   outline so the provisional state is visible. */
.obj-textedit { border-radius: 2px; }
.te-text { display: block; width: 100%; height: 100%; pointer-events: none; }
.obj-textedit.te-draft { pointer-events: none; outline: 2px dashed var(--brand); outline-offset: -1px; }
/* /B20 */
/*B23*/
/* OCR panel (Build 23): absolute overlay inside the stage wrap (right
   side), like the signature panel — it never moves or resizes the page
   canvas. The progress track mirrors the form-data progress style. */
.ocr-panel {
  position: absolute; top: 12px; right: 12px; z-index: 11;
  width: min(360px, calc(100% - 24px));
  max-height: calc(100% - 24px); overflow-y: auto;
  background: rgba(255, 253, 248, .98); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 14px;
}
.ocr-panel[hidden] { display: none; }
.ocr-row { display: flex; gap: 12px; align-items: center; margin: 8px 0; font-size: 13.5px; flex-wrap: wrap; }
.ocr-row label { display: inline-flex; gap: 6px; align-items: center; }
.ocr-dim { color: var(--ink-soft); font-size: 12.5px; }
.ocr-lang { font: inherit; font-size: 13.5px; padding: 6px 8px; border: 1.5px solid var(--line); border-radius: 8px; background: #fff; color: var(--ink); }
.ocr-prog { margin: 10px 0; }
.ocr-prog[hidden] { display: none; }
.ocr-prog-track { height: 8px; border-radius: 4px; background: #e8e2d4; overflow: hidden; }
.ocr-prog-fill { height: 100%; width: 0%; border-radius: 4px; background: var(--brand); transition: width .2s; }
.ocr-prog-label { font-size: 12.5px; color: var(--ink-soft); margin: 6px 0 0; line-height: 1.45; }
.ocr-results { margin: 6px 0; }
.ocr-hint { font-size: 13px; color: var(--ink-soft); margin: 6px 0; line-height: 1.5; }
#ocrBtn.on { background: var(--brand); border-color: var(--brand); color: #fff; }
/* /B23 */
/*B24*/
/* Scan cleanup (Build 24) reuses the .ocr-panel shell. The preview canvas
   scales to the panel width; the toggle active state follows the
   .tool-on convention. */
.clean-preview { width: 100%; height: auto; display: block; border: 1px solid var(--line); border-radius: 8px; background: #fff; }
.clean-info { min-height: 1.2em; }
#cleanBtn.on { background: var(--brand); border-color: var(--brand); color: #fff; }
/* /B24 */
/*B26*/
/* Convert panel (Build 26) reuses the .ocr-panel shell. */
.conv-h { font-size: 0.95em; margin: 0.6em 0 0.3em; }
#convertBtn.on { background: var(--brand); border-color: var(--brand); color: #fff; }
/* /B26 */
/*B29*/
/* Batch queue (Build 29) reuses the .ocr-panel shell. */
.batch-row { display: flex; justify-content: space-between; gap: 8px; padding: 4px 0; border-bottom: 1px solid var(--line); font-size: 0.9em; }
.batch-row .st { color: var(--dim); white-space: nowrap; }
.batch-row .st.done { color: var(--ok, #1a7f4e); }
.batch-row .st.failed { color: var(--err, #b3261e); }
#batchBtn.on { background: var(--brand); border-color: var(--brand); color: #fff; }
/* /B29 */
/*B30*/
/* Document library (Build 30) reuses the .ocr-panel shell. */
.lib-row { display: flex; align-items: center; gap: 8px; padding: 5px 0; border-bottom: 1px solid var(--line); font-size: 0.9em; }
.lib-row .nm { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lib-row button.linklike { background: none; border: none; padding: 0; color: var(--brand); cursor: pointer; font-size: 1em; text-align: left; }
.lib-row .meta { color: var(--dim); font-size: 0.85em; white-space: nowrap; }
.lib-search, .lib-name, .lib-newfolder, .lib-pass, .lib-restore-pass { width: 100%; }
#libBtn.on { background: var(--brand); border-color: var(--brand); color: #fff; }
/* /B30 */
/*B31*/
/* Privacy proof view (Build 31): fixed overlay above the app. */
.privacy-view { position: fixed; inset: 0; z-index: 200; background: rgba(0,0,0,0.45); display: flex; align-items: center; justify-content: center; padding: 16px; }
.privacy-view[hidden] { display: none; }
.privacy-card { background: var(--bg); color: var(--ink); max-width: 560px; width: 100%; max-height: 88vh; overflow-y: auto; border-radius: 12px; padding: 20px 22px; box-shadow: 0 12px 40px rgba(0,0,0,0.25); }
.privacy-card h2 { margin-top: 0; }
.privacy-card h3 { margin-bottom: 0.3em; }
.privacy-card ul, .privacy-card ol { margin-top: 0.3em; padding-left: 1.3em; }
.privacy-card li { margin: 0.25em 0; }
.linklike { background: none; border: none; color: var(--brand); cursor: pointer; text-decoration: underline; font-size: 0.85em; padding: 0 4px; }
/* /B31 */
/*B36*/
/* ——— Browser-QA fixes (Build 36) ——— */
/* Root fix is above: every pre-B36 section marker was a JS-style //
   comment, which is invalid CSS — each one silently deleted the first
   rule of its section (.toolbar, .sign-panel, .tool-panel, .ocr-panel,
   .fd-panel, .privacy-view, .clean-preview, the tool-active overlay
   rule, …). The markers are CSS comments now. This section adds the
   mobile layout fixes from the same QA pass: the edit bar gets a height
   cap with its own scroll (it otherwise eats most of a phone screen),
   edit groups may wrap internally instead of overflowing the viewport,
   and the toolbar is compacted so it fits one row on a phone. */
.editbar { max-height: 42vh; overflow-y: auto; }
.edit-group { min-width: 0; flex-wrap: wrap; }
#formGroup .form-note { flex: 1 1 170px; }
/* The privacy card and the batch/library meta text referenced CSS
   variables from another app's palette (--bg, --dim) that this app
   never defines: the privacy modal rendered transparent over the page
   and the meta text fell back to full-strength ink. */
.privacy-card { background: var(--card); }
.batch-row .st, .lib-row .meta { color: var(--ink-soft); }
@media (max-width: 640px) {
  .toolbar { gap: 8px; padding: 6px 10px; }
  .pagenav { gap: 6px; }
  .page-label > span:first-child { display: none; }
  #pageInput { width: 46px; padding: 5px 4px; }
  .zoom-wrap select { padding: 7px 26px 7px 10px; background-position: right 9px center; }
  .editbar { gap: 10px; padding: 7px 10px; max-height: 46vh; }
  .edit-group + .edit-group { padding-left: 10px; }
}
/*B36b*/
/* Lead fixes (Build 36, second wave): text-edit mode never enabled overlay
   pointer events (only draw tools add .tool-active), so line clicks were
   dead; .te-active is toggled by setTextEditMode. The two form notes get
   room to breathe instead of squeezing into narrow columns. */
#pageHolder.te-active #overlay { pointer-events: auto; cursor: text; }
#formGroup .form-note { max-width: 560px; }
/* /B36b*/
/* /B36 */
/*B37*/
/* ——— Toolbar tabs (Build 37, mobile-only) ——— */
/* Desktop: the tab strip is hidden and the panels dissolve into the
   flat edit bar (display:contents), so the desktop layout is unchanged
   except that the separator between two groups that now sit in different
   tabs drops — the groups are no longer DOM siblings. Nothing here
   touches #stageScroller / #pageCanvas: the strip + always row + one
   tool row all live in the edit bar above #stageWrap, so the stage box
   keeps its geometry (stage rule). */
.tabstrip { display: none; }
.tab-panel, .tab-tools, .tab-foot { display: contents; }
@media (max-width: 900px) {
  .editbar { flex-direction: column; align-items: stretch; max-height: none; overflow: visible; }
  .always-group { display: flex; }
  /* QA fix: without a width the strip sized to its content (683px on a
     390px phone, clipped by #app's overflow-x:hidden) so Pages/More were
     off-screen and only reachable by scrolling the whole bar. Constrain
     it to the bar; buttons grow from their own label width (flex-basis
     auto, no shrink) so every label stays whole, and the strip scrolls
     instead of clipping on screens narrower than the labels. */
  .tabstrip { display: flex; gap: 6px; width: 100%; overflow-x: auto; }
  .tabstrip .tab-btn {
    flex: 1 0 auto; min-width: 0; min-height: 44px;
    font: inherit; font-size: 13px; font-weight: 600;
    padding: 6px 4px; border-radius: 999px; cursor: pointer;
    border: 1px solid var(--line); background: transparent; color: var(--ink);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .tabstrip .tab-btn.tab-on { background: var(--brand); border-color: var(--brand); color: #fff; }
  .tab-panel { display: none; }
  /* QA fix: like the strip, a block panel sized to its content (the wide
     Fill tool row, ~956px) because the automatic minimum size clamps the
     stretch — the whole bar then scrolled sideways. A definite width
     pins the panel to the bar so the tool row scrolls inside it and the
     Fill foot wraps instead of overflowing. */
  .tab-panel.active { display: block; width: 100%; }
  .tab-panel.active .tab-tools {
    display: flex; flex-wrap: nowrap; overflow-x: auto; gap: 7px; padding: 2px 0 6px;
  }
  .tab-panel.active .tab-tools .edit-group { display: contents; }
  .tab-panel.active .tab-tools .edit-group + .edit-group { border-left: none; padding-left: 0; }
  .tab-panel.active .tab-foot { display: block; }
  .tab-panel.active .tab-foot #formGroup { display: flex; flex-wrap: wrap; }
}
/* /B37 */
/*B38*/
/* ——— Desktop toolbar tabs + inspector fits content (Build 38) ——— */
/* Tabs at EVERY width (the B37 strip was mobile-only). These rules sit
   after the B37 block and override its desktop defaults (strip hidden,
   panels display:contents) by cascade order; the B37 block itself is
   untouched so staged older builds render exactly as before. Chrome
   becomes header + one nav row + always row + tab strip + one tool row.
   The tool row scrolls horizontally within itself (never wraps to a
   second row), so chrome height stays constant. The editbar's B36
   42vh cap + own scroll are dropped: with one tool row there is nothing
   left for the bar itself to scroll. Nothing here touches
   #stageScroller / #pageCanvas (stage rule). */
/* The ≤900px media block in B37 stays as the mobile-only tweaks (its
   column/strip/panel rules now agree with the ungated defaults); it
   hides nothing at any width and forces no display:contents. */
.editbar { flex-direction: column; align-items: stretch; gap: 8px 18px; max-height: none; overflow: visible; }
.tabstrip { display: flex; gap: 6px; width: 100%; overflow-x: auto; }
.tabstrip .tab-btn {
  flex: 1 0 auto; min-width: 0; min-height: 44px;
  font: inherit; font-size: 13px; font-weight: 600;
  padding: 6px 4px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--line); background: transparent; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tabstrip .tab-btn.tab-on { background: var(--brand); border-color: var(--brand); color: #fff; }
.tab-panel { display: none; }
.tab-panel.active { display: block; width: 100%; }
.tab-panel.active .tab-tools {
  display: flex; flex-wrap: nowrap; overflow-x: auto; gap: 7px; padding: 2px 0 6px;
}
.tab-panel.active .tab-tools .edit-group { display: contents; }
.tab-panel.active .tab-tools .edit-group + .edit-group { border-left: none; padding-left: 0; }
.tab-panel.active .tab-foot { display: block; }
.tab-panel.active .tab-foot #formGroup { display: flex; flex-wrap: wrap; }
/* B37's foot rule above overrides the `hidden` attribute (author CSS
   beats the UA [hidden] rule), so the flatten row leaked onto the Fill
   tab of docs with nothing to fill — +22px of chrome on Fill only, and
   a toggle whose own note says it affects original fields only. The
   JS gating (formGroup.hidden, set from AcroForm detection) is the
   intended switch; this restores it. Higher specificity + later order
   win over both the B37 rule and the UA rule. */
.tab-panel.active .tab-foot #formGroup[hidden] { display: none; }
/* Inspector (object-properties card) fits its content — no internal
   scrollbar, ever. Root cause of the reported scrollbar: the base rule
   capped the card at max-height:52% of the stage with overflow-y:auto,
   so whenever content exceeded half the stage (always on phones, where
   the text card wraps to ~300px against a ~263px cap, and on any
   shorter desktop window) the card scrolled and its bottom row
   (Rotation + Duplicate + Delete) sat below the fold. The card is
   bottom-anchored inside #stageWrap, which itself sits inside the
   100dvh #app, so sizing to content keeps it inside the viewport at
   every QA viewport without any repositioning logic (it is not
   object-anchored, so there is no edge to flip from). No control is
   dropped and no spacing is tightened: the text, shape, draw, formfield,
   stamp and watermark variants all size to their own content. Only the
   pathological case — a card taller than the whole stage — could still
   clip at the stage top, and no QA viewport comes close. */
#inspector { max-height: none; overflow: visible; }
/* /B38 */
/*B39*/
/* ——— Chrome merge + inspector dock/sheet + dark theme (Build 39) ——— */
/* Theme: dark is the default. :root carries the dark palette; light is
   restored only under [data-theme="light"]. The page itself stays
   paper-white in both themes: #pageHolder, the canvas and every
   document-data colour are explicit values, never variables, and are
   untouched here. The amber warning banners deliberately stay amber in
   both themes. Panels that were hard-coded rgba(255,253,248,.98) now use
   --card-rgb so they follow the theme without losing their translucency. */
:root {
  --ink: #e9efec;
  --ink-soft: #aebbb4;
  --paper: #121816;
  --card: #1f2926;
  --card-rgb: 31, 41, 38;
  --line: #3d4a44;
  --brand: #4db3a6;
  --brand-deep: #0d2b28;
  --gold: #e8a33d;
  --danger: #e0684f;
  --desk: #1a211f;
  --shadow: 0 18px 50px rgba(0, 0, 0, .45);
}
[data-theme="light"] {
  --ink: #1d2321;
  --ink-soft: #4c554f;
  --paper: #f7f3ec;
  --card: #fffdf8;
  --card-rgb: 255, 253, 248;
  --line: #ddd3bf;
  --brand: #17554f;
  --brand-deep: #123332;
  --gold: #e8a33d;
  --danger: #c7432e;
  --desk: #e9e2d2;
  --shadow: 0 18px 50px rgba(18, 51, 50, .18);
}
#thumbsPanel, #notesPanel, #inspector, .sign-panel, .stamp-panel,
.taborder-panel, .fd-panel, .ocr-panel, .sel-bar {
  background: rgba(var(--card-rgb), .98);
}
.editbar { background: var(--card); }
#pageInput, .insp-text, .insp-field input[type="number"],
.sign-type-input, .ocr-lang {
  background: var(--card); color: var(--ink);
}
#inspector input:not([type="color"]):not([type="checkbox"]):not([type="radio"]),
#inspector select, #inspector textarea,
.sign-panel input:not([type="color"]):not([type="checkbox"]):not([type="radio"]),
.sign-panel select, .tool-sheet input:not([type="color"]):not([type="checkbox"]):not([type="radio"]),
.tool-sheet select, .tool-sheet textarea {
  background: var(--card); color: var(--ink);
}
[data-theme="dark"] .zoom-wrap select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23aebbb4' fill='none' stroke-width='1.6'/%3E%3C/svg%3E");
}
[data-theme="dark"] .ocr-prog-track { background: #33413c; }
[data-theme="dark"] .stamp-preset { background: rgba(255, 255, 255, .08); }
/* Compact inspector pieces: the "Text style" disclosure, the small
   original-text line, and the collapse button that shrinks the panel
   to a slim bar. */
.te-style-disc { flex: 1 1 100%; }
.te-style-disc summary { cursor: pointer; font-weight: 600; font-size: 13px; color: var(--ink); }
.te-orig { font-size: 12.5px; color: var(--ink-soft); margin: 0; flex-basis: 100%; overflow-wrap: break-word; }
.insp-collapse { flex: 0 0 auto; }
#inspector.insp-collapsed { max-height: 56px; overflow: hidden; }
/* Chrome merge (desktop, >=1200px): the always row spans both columns
   on row 1; the tab strip (col 1) and the active tool row (col 2) share
   row 2, so the bar drops from three stacked rows to two. The strip
   keeps width:auto + min-width:0 + its own horizontal scroll — a
   width:100% strip was the B37 trap that hogged the row. The tab
   buttons drop to 34px only on fine pointers (44px touch targets stay
   on coarse pointers, and the 13px font is unchanged everywhere).
   Header/toolbar padding is tightened a few pixels; no font shrinks. */
@media (min-width: 1200px) {
  .topbar { padding: 8px 16px; }
  .toolbar { padding: 6px 16px; }
  #editBar { display: grid; grid-template-columns: minmax(280px, 38%) minmax(0, 1fr); gap: 8px 18px; padding: 6px 14px; }
  #editBar[hidden] { display: none; }
  #editBar .always-group { grid-column: 1 / -1; grid-row: 1; }
  #editBar .tabstrip { grid-column: 1; grid-row: 2; width: auto; min-width: 0; overflow-x: auto; }
  #editBar .tab-panel.active { grid-column: 2; grid-row: 2; width: auto; min-width: 0; }
  #editBar .tab-panel.active .tab-tools { min-width: 0; overflow-x: auto; }
  #editBar .tab-panel.active .tab-foot { display: block; }
}
@media (min-width: 1200px) and (pointer: fine) {
  #editBar .tabstrip .tab-btn { min-height: 34px; }
}
/* Mobile bottom sheet (<=900px): the inspector becomes a compact
   half-height sheet that scrolls internally, never full-screen, and
   sits above the selection bar when it is on (that rule is scoped to
   this sheet layout only). */
@media (max-width: 900px) {
  /* The header gained the Theme button in B39; at 390px its label
     wrapped to two lines (50px button, 70px topbar) and pushed mobile
     chrome 299 -> 305px. Compact the header buttons slightly and keep
     the theme label on one line: 44px touch height is untouched, no
     control is dropped, and the topbar returns to 64px. */
  .topbar .btn { padding: 7px 8px; font-size: 13px; }
  #themeBtn { white-space: nowrap; }
  #inspector {
    width: calc(100% - 20px);
    max-height: min(45dvh, calc(100% - 24px));
    overflow-y: auto;
  }
  #inspector.insp-collapsed { max-height: 56px; overflow: hidden; }
  #stageWrap.selbar-on #inspector { bottom: 76px; }
}
/* Desktop right dock (>=1200px): a 300px panel at the stage's top
   right. The stage itself shrinks (see the JS: .insp-dock adds
   324px of scroller right padding and subtracts the dock width in
   scale()), so the page is never covered by the panel. This block is
   last so its bottom:auto beats the sheet/selbar bottom rules. */
@media (min-width: 1200px) {
  #stageWrap.insp-dock #inspector {
    left: auto; transform: none; right: 12px; top: 12px; bottom: auto;
    width: 300px; max-height: calc(100% - 24px); overflow-y: auto;
  }
  #stageWrap.insp-dock #inspector.insp-collapsed { max-height: 56px; overflow: hidden; }
  #stageWrap.insp-dock #inspector .insp-field { flex: 1 1 100%; }
  #stageWrap.insp-dock .insp-text { min-width: 0; width: 100%; }
  #stageWrap.insp-dock #stageScroller { padding-right: 342px; }
}
/* /B39 */
/*B40*/
/* ——— Mobile chrome compaction + sheet/footer + theme leaks (Build 40) ——— */
/* Mobile-only (<=900px) except the two theme-correctness rules at the
   top, which fix a dark-theme leak that also affects desktop geometry
   not at all (colour only): the zoom select set a background but no
   text colour, so Chromium's default light colour-scheme painted
   FieldText black onto the dark card; color-scheme makes the control
   (and its dropdown options, and number-input spinners) follow the
   active theme, and the explicit colour is belt-and-braces.
   Chrome math (measured B39: 64 + 57 + 178 = 299): five stacked rows
   at 44px can never fit a <=230px budget (5 x 44 = 220 before a pixel
   of padding, gap, or border), so chrome rows use 40px pills — the
   brief's stated floor, still comfortably tappable — and the rest of
   the saving comes from padding/gaps: header 40 + 3 + 3 = 46, nav
   40 + 3 + 3 + 1 border = 47, edit bar 3 x 40 + 2 x 4 gap + 3 + 3
   padding + 1 border = 135, total 228. Sheet buttons and panel
   controls keep the 44px coarse-pointer target: only the three chrome
   rows and the tab strip drop to 40px. Desktop (>=1200px) rules are
   untouched: every rule below is inside the <=900px query except the
   two theme rules, which change no geometry at any width.
   Root causes fixed here, all measured on stage-b39 at 390x844:
   - Brand wrapped because three wide pills squeezed it (brand box
     61x44, two lines) and the Theme pill alone was 96px wide.
   - Tool pills were 50px tall, not 44: .tab-tools flex items shrink
     (default flex-shrink 1), so "Edit text" squeezed to 55px wide,
     wrapped to two lines, and stretched the whole row to 50px.
     flex:none + white-space:nowrap on tool buttons fixes the height
     and the wrap at once; the row scrolls instead of squeezing.
   - The left-edge pill sliver / mid-label clip came from a scrolled
     row with no snap: mandatory scroll-snap aligns a whole pill to
     the left edge after any scroll (proximity snap was tried first
     and still left a sliver after a click's scroll-into-view), so no
     label is ever cut on the left. Note the trap found while
     verifying this: scroll-padding-inline: 10px (tried first as
     breathing room) insets the snapport, so the snapped position
     deliberately leaves the previous pill peeking in from the left
     — the padding was REMOVED so snapped pills sit flush at the
     edge. The right-edge partial pill remains as the scroll
     affordance: the next label is revealed whole by scrolling,
     never hidden.
   - The sheet's "dead space" measured only 13px (bottom padding):
     the perceived blankness was the wide, loosely-gapped last flex
     row (Why? bottom-aligned under a 44px button) plus 12px padding
     and 10px gaps. Tightened padding/gaps, centred row alignment,
     and a grabber make the sheet read as fitted to its content;
     height stays auto with internal scroll only above the cap.
   - Footer wrapped because status + two links shared one wrapping
     flex row (51px, two visual lines with a long status). Now one
     nowrap line: status ellipsizes, links stay whole at 12px. */
:root { color-scheme: dark; }
[data-theme="light"] { color-scheme: light; }
.zoom-wrap select { color: var(--ink); }
@media (max-width: 900px) {
  /* Header: one row, never wraps. The brand ellipsizes before it
     wraps (at 390px "PDF Editor" fits whole); the theme toggle is
     icon-only (see below), which frees ~56px for the brand. */
  .topbar { padding: 3px 10px; gap: 8px; }
  .brand { min-width: 0; flex: 1 1 auto; gap: 8px; }
  .brand img { flex: none; width: 28px; height: 28px; }
  .brand-name { font-size: 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .top-actions { flex: none; gap: 6px; }
  .topbar .btn { min-height: 40px; padding: 6px 10px; font-size: 13px; border-color: rgba(255, 255, 255, .18); }
  /* Icon-only theme toggle: the JS label ("Theme: Dark"/"Theme:
     Light") keeps updating textContent, and the desktop label is
     untouched — on mobile the text is hidden (font-size:0) and the
     glyph carries the state, so a textContent rewrite by applyTheme
     cannot wipe it (a naive inner span would be). Glyph follows the
     CURRENT theme, like the desktop label does. */
  #themeBtn { font-size: 0; min-width: 40px; padding: 6px 10px; }
  #themeBtn::before { font-size: 17px; line-height: 1; }
  [data-theme="dark"] #themeBtn::before { content: "☾"; }
  [data-theme="light"] #themeBtn::before { content: "☀"; }
  /* Nav row: one nowrap scroll row, never wraps; the zoom control is
     shorter and its text is theme-aware (see the global rule above). */
  .toolbar { flex-wrap: nowrap; overflow-x: auto; gap: 8px; padding: 3px 10px; }
  .toolbar .btn { min-height: 40px; padding: 6px 10px; font-size: 13px; flex: none; }
  .toolbar .btn.icon { padding: 4px 10px; }
  .pagenav { flex: none; gap: 6px; }
  .page-label { flex: none; }
  #pageInput { width: 44px; padding: 5px 4px; }
  .zoom-wrap { flex: none; }
  .zoom-wrap select { padding: 6px 26px 6px 10px; font-size: 13px; max-width: 132px; background-position: right 9px center; }
  .doc-meta { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* Edit bar: three 40px rows, tight padding/gaps. Inactive pills get
     a softer border (color-mix degrades gracefully to the plain
     --line border where unsupported); the single filled accent for
     the active tab and the active tool is unchanged (brand fill). */
  #editBar { gap: 4px; padding: 3px 10px; }
  #editBar .always-group { flex-wrap: nowrap; gap: 6px; }
  #editBar .always-group .btn { min-height: 40px; padding: 6px 11px; font-size: 13px; white-space: nowrap; flex: none; }
  #editBar .tabstrip { gap: 6px; scroll-snap-type: x mandatory; }
  #editBar .tabstrip .tab-btn { min-height: 40px; padding: 6px 8px; font-size: 13px; scroll-snap-align: start; border-color: color-mix(in srgb, var(--line) 60%, transparent); }
  #editBar .tabstrip .tab-btn.tab-on { border-color: var(--brand); }
  #editBar .tab-panel.active .tab-tools { gap: 6px; padding: 0; min-width: 0; scroll-snap-type: x mandatory; }
  #editBar .tab-tools .btn { min-height: 40px; padding: 6px 11px; font-size: 13px; white-space: nowrap; flex: none; scroll-snap-align: start; border-color: color-mix(in srgb, var(--line) 60%, transparent); }
  #editBar .tab-tools .btn.tool-on { background: var(--brand); border-color: var(--brand); color: #fff; }
  /* Bottom sheet: height fits content (auto, no forced scroll), cap
     raised 45 -> 55dvh, tighter padding/gaps, centred row alignment
     so the Why? summary no longer sits bottom-aligned under the
     Replace button, and a subtle grabber bar (none existed before).
     Collapsed: only the expand button stays visible, so the bar
     shrinks to a centred pill instead of a full-width row. */
  #inspector { height: auto; max-height: min(55dvh, calc(100% - 24px)); overflow-y: auto; padding: 14px 12px 10px; gap: 8px 14px; align-items: center; }
  #inspector::before { content: ""; position: absolute; top: 5px; left: 50%; transform: translateX(-50%); width: 36px; height: 4px; border-radius: 999px; background: var(--line); }
  #inspector.insp-collapsed { max-height: 56px; overflow: hidden; padding: 6px 10px; width: max-content; max-width: calc(100% - 20px); }
  #inspector.insp-collapsed > :not(.insp-collapse) { display: none; }
  #inspector.insp-collapsed::before { display: none; }
  /* Footer: exactly one line. The status ellipsizes; both links stay
     whole at 12px (never dropped, desktop untouched); the long
     privacy sentence, already hidden <=640px, stays hidden <=900px
     so the row cannot wrap on tablets either. */
  .statusbar { flex-wrap: nowrap; gap: 8px; padding: 6px 10px; overflow: hidden; }
  .statusbar .spacer { display: none; }
  .privacy-note { display: none; }
  #statusText { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .statusbar .linklike { flex: none; white-space: nowrap; font-size: 12px; padding: 0 2px; }
}
/* /B40 */
/*B41*/
/* ——— Merge the Undo/Redo/Close row into the nav row + header (Build 41) ——— */
/* The dedicated always row is deleted from the HTML (minimal-layout
   principle: no chrome row for just three controls). Undo/Redo move
   into .pagenav, immediately after Next, so at 390px they sit inside
   the first ~330px of the nav row: visible without scrolling, before
   the zoom control, which was already the row's scroll-off tail.
   Close moves into .top-actions between Download and Theme, so file
   actions (Open / Download / Close) sit together. All three keep
   their ids, handlers and disabled logic; they are `hidden` in the
   static HTML and unhidden by the B41 JS block in openBytes only.
   Sizing: compact icon buttons, 40px minimum in both dimensions at
   every width (the Build 40 floor, now applied to these three on
   desktop too), flex:none so they can never be squeezed or wrapped.
   The nav row never wraps at any width: it scrolls within itself
   (mobile already did; desktop >=1200px and the 901-1199px band gain
   the same rule here). At >=1200px the editBar grid drops its empty
   always row: tabstrip + tool row move from grid row 2 to row 1, so
   desktop chrome falls by one full row and mobile chrome by one
   40px row (B40 228px -> ~184px at 390x844, to be measured by QA).
   Old B37/B39/B40 always-group rules are left untouched (dead rules
   for a deleted element, kept per the never-edit-an-old-block rule);
   only this block styles the new homes. No other row is merged here:
   the mobile tabstrip + tool row stay separate (merging them would
   scroll the tabs away with the tools) — recorded, not shipped. */
/* Compact icon homes, all widths. */
#undoBtn, #redoBtn { min-width: 40px; min-height: 40px; padding: 6px 10px; font-size: 17px; line-height: 1; flex: none; white-space: nowrap; }
#closeDocBtn { min-width: 40px; min-height: 40px; padding: 6px 10px; font-size: 16px; line-height: 1; flex: none; white-space: nowrap; }
/* Nav row: one nowrap scroll row at every width, never a second row. */
.toolbar { flex-wrap: nowrap; overflow-x: auto; }
.pagenav { flex: none; }
@media (min-width: 1200px) {
  #editBar .tabstrip { grid-row: 1; }
  #editBar .tab-panel.active { grid-row: 1; }
}
/* /B41 */
/*B42*/
/* ——— Nav-row uniformity + chrome rhythm (Build 42) ——— */
/* The B41 nav row mixed heights (buttons 40, input 31, zoom 32 on
   mobile; 33/34/31/35/40 on desktop), mixed fonts (input and "of N"
   at 14 against 13 neighbours; chevrons 18 and undo/redo 17 on
   desktop) and mixed radii (input 8 against 999 pills), and at
   390px its content ran 419px wide so the zoom pill sliced off at
   the viewport edge with no affordance. This block gives the whole
   nav row one rhythm at every width: every element 40px tall, one
   text size (13px) for every text-bearing element, one glyph size
   (16px) for every icon-only button (a glyph at 13px reads smaller
   than 13px text, so icons share their own single size rather than
   each drifting), one radius (999px pills, input included), and one
   gap per width (10px desktop, 5px on phones) shared by the toolbar,
   the pagenav group and the page label through --nav-gap. Nothing
   is dropped and no target shrinks below 40px tall; the fit at
   390px comes from the single gap, tighter paddings and a shorter
   zoom chevron inset, not from smaller controls. The row still
   scrolls within itself when content genuinely overflows (a
   three-digit page count): the wrap carries a fade mask that only
   appears while there is more row to the right, driven by the B42
   JS block, and the row's own right padding is the end padding so
   the last pill lands whole when scrolled to the end. The same
   pass fixes the other chrome rows: header buttons are all 40px
   (desktop Open/Download/Theme were 33 against Close at 40), tab
   and tool buttons are all 40px at 13px with no squeeze and no
   wrap (desktop Fill "Text field" wrapped to two lines and pushed
   that tab to 180px), and the footer reads at one size (12px) for
   status, links and chip alike. */
/* Nav row: one height, one text size, one glyph size, one radius. */
.toolbar { --nav-gap: 10px; gap: var(--nav-gap); scroll-snap-type: x proximity; }
.toolbar > * { scroll-snap-align: start; }
.pagenav { gap: var(--nav-gap); }
.toolbar .btn { height: 40px; min-height: 40px; font-size: 13px; padding: 6px 12px; flex: none; white-space: nowrap; }
.toolbar .btn.icon { font-size: 16px; padding: 6px 10px; }
#undoBtn, #redoBtn { min-width: 40px; font-size: 16px; }
.page-label { height: 40px; font-size: 13px; gap: var(--nav-gap); }
#pageInput { height: 40px; font-size: 13px; border-radius: 999px; }
.zoom-wrap { flex: none; }
.zoom-wrap select { height: 40px; font-size: 13px; padding: 6px 24px 6px 12px; background-position: right 10px center; }
.doc-meta { font-size: 13px; }
/* Fade affordance for the genuinely-overflowing nav row. The wrap
   is position-relative so the mask pins to the viewport edge of the
   row instead of scrolling with it; pointer-events none so the
   zoom control stays tappable through the fade. */
.toolbar-wrap { position: relative; }
.toolbar-wrap::after { content: ""; position: absolute; top: 0; bottom: 1px; right: 0; width: 28px; background: linear-gradient(to left, var(--card), transparent); pointer-events: none; opacity: 0; transition: opacity .15s ease; z-index: 2; }
.toolbar-wrap.can-scroll:not(.at-end)::after { opacity: 1; }
/* Header: every button 40px tall (desktop text buttons were 33). */
.top-actions .btn { height: 40px; min-height: 40px; }
/* Tab strip and tool rows: one height and one size at every width,
   never squeezed, never wrapped. */
#editBar .tabstrip .tab-btn { height: 40px; }
#editBar .tab-panel.active .tab-tools { padding: 0; }
#editBar .tab-tools .btn { height: 40px; font-size: 13px; flex: none; white-space: nowrap; }
/* Footer: one size for status, links and chip. */
.statusbar { font-size: 12px; }
.statusbar .linklike { font-size: 12px; }
.build-chip { font-size: 12px; }
@media (min-width: 901px) {
  #editBar .tab-panel.active .tab-tools { scroll-snap-type: x proximity; }
  #editBar .tab-tools .btn { scroll-snap-align: start; }
  /* Desktop chrome trim: with every row now a uniform 40px, the
     desktop row paddings drop a few pixels (header 8 to 6,
     toolbar 6 to 4, edit bar 6 to 4), which closes the B41
     13px miss against the about-150px desktop chrome target
     without touching any control size. Mobile paddings are
     untouched by this query. */
  .topbar { padding: 6px 16px; }
  .toolbar { padding: 4px 16px; }
  #editBar { padding: 4px 14px; }
}
@media (max-width: 900px) {
  .toolbar { --nav-gap: 5px; padding: 3px 8px; }
  .toolbar .btn { padding: 6px 8px; }
  .toolbar .btn.icon { padding: 6px 8px; }
  .zoom-wrap select { padding: 6px 20px 6px 10px; background-position: right 8px center; }
}
/* /B42 */
