/* The face blanks are written in on /ai. Deliberately the SAME .ttf that
 * blanqBakeBytes embeds: the exported baseline is derived from this font's
 * metrics as measured in the browser, so screen and PDF must be one file.
 * Liberation Sans: metric-compatible with Arial, so filled entries blend
 * into the majority of real forms. Covers Latin, Latin Extended, Cyrillic,
 * Greek, Turkish and Vietnamese. It does NOT cover CJK, and RTL scripts
 * are not usable here because pdf-lib does no bidi reordering.
 */
@font-face{
  font-family:'BlanqSans';
  src:url('/assets/vendor/fonts/liberation-sans-400-normal.ttf') format('truetype');
  font-weight:400;
  font-style:normal;
  font-display:swap;
}

/* Blanq PDF viewer — shared by the editor (/) and the AI filler (/ai).
 *
 * Extracted from index.html's inline <style> rather than duplicated, because
 * these rules are GEOMETRY-CRITICAL, not merely visual. applyBlankGeometry()
 * sizes text against the box these rules produce, and blanqBakeBytes() maps
 * that CSS line-box model into PDF coordinates when stamping text. If the two
 * pages ever disagreed about .manual-wrap or .overlay-input, exported text
 * would land in the wrong place on one of them, and the editor would look
 * correct while /ai quietly did not.
 *
 * Depends on the design tokens (--card, --rule, --accent, ...) being defined
 * by the including page.
 */

/* Inline PDF viewer wrapper (working state) — pipeline JS injects pages here.
   No internal padding: clientWidth must equal the available render area so
   .pdf-page divs (sized to that width by the pipeline) sit flush. The frame
   hugs the page: analyze() sets width:max-content (after measuring the
   full-width clientWidth into dataset.containerW), so the bordered box
   tracks the zoomed page — centered (margin-inline:auto) when smaller than
   the column, overflowing it (the window scrolls) when zoomed past it. */
/* touch-action: pan-x pan-y lets one-finger panning scroll the page but
   disables the browser's native pinch-zoom, so our two-finger handler owns
   zoom (and touchmove stays cancelable instead of the browser hijacking it). */
.pdf-wrap{background:var(--card);border:1px solid var(--rule);border-radius:6px;box-shadow:0 1px 0 rgba(0,0,0,.02);min-height:200px;overflow:hidden;margin-inline:auto;touch-action:pan-x pan-y}
.pdf-wrap canvas{display:block}
.pdf-viewer{display:flex;flex-direction:column;gap:20px;align-items:center}
.pdf-page{position:relative;box-shadow:0 4px 18px rgba(0,0,0,.06),0 0 0 1px var(--rule);border-radius:6px;overflow:hidden;background:#fff}
.pdf-page canvas{display:block}
.add-mode .pdf-page{cursor:crosshair}
/* Per-page "still scanning" cue: a faint accent line sweeping across the
   top of a rendered-but-not-yet-detected page. Transform-only so it's
   cheap; suppressed under reduced-motion (the page stays usable either way). */
.pdf-page-scanning{position:absolute;top:0;left:0;right:0;height:2px;overflow:hidden;pointer-events:none;z-index:3}
.pdf-page-scanning::before{content:"";position:absolute;top:0;left:0;height:100%;width:40%;background:linear-gradient(90deg,transparent,var(--accent),transparent);animation:page-scan 1.1s linear infinite}
@keyframes page-scan{from{transform:translateX(-100%)}to{transform:translateX(350%)}}
@media(prefers-reduced-motion:reduce){.pdf-page-scanning::before{animation:none;width:100%;opacity:.25}}
/* Selectable text layer rendered by pdf.js on top of the canvas.
   The container has `pointer-events:none` so clicks fall through to
   .manual-wrap blanks (z-index 2) and to the wrap itself in add-blank
   mode; spans have `pointer-events:auto` so the browser's native
   text selection still works on them. Text is rendered transparent
   (the visual lives on the canvas underneath) — selection highlight
   is the only visible signal. z-index 1 puts the layer above the
   canvas but below the blanks.
   Properties mirror pdf.js's own `text_layer_builder.css` for
   compatibility with `pdfjsLib.renderTextLayer`:
   - `--scale-factor` is set inline (= viewport.scale) and pdf.js's
     internal CSS uses it to size span fonts proportionally so the
     spans' widths match the rendered canvas glyphs, keeping the
     selection highlight aligned with the visible text.
   - `text-align:initial`, `text-size-adjust:none`,
     `forced-color-adjust:none` defuse browser auto-rescaling that
     would otherwise stretch the spans and drift the alignment. */
.pdf-text-layer{position:absolute;inset:0;overflow:hidden;line-height:1;pointer-events:none;opacity:1;z-index:1;text-align:initial;-webkit-text-size-adjust:none;text-size-adjust:none;forced-color-adjust:none;transform-origin:0 0}
.pdf-text-layer>span,.pdf-text-layer>br{color:transparent;position:absolute;white-space:pre;cursor:text;transform-origin:0% 0%;pointer-events:auto}
.pdf-text-layer .endOfContent{display:block;position:absolute;inset:100% 0 0;z-index:-1;cursor:default;user-select:none}
.pdf-text-layer ::selection{background:rgba(31,29,26,.28);color:transparent}
.pdf-text-layer ::-moz-selection{background:rgba(31,29,26,.28);color:transparent}
/* In add-blank mode, spans must NOT intercept clicks — the user is
   drawing a new blank and the crosshair has to land on the page,
   not on a text span. */
.add-mode .pdf-text-layer>span{pointer-events:none}

/* Blank fields are <div contenteditable> rather than <textarea> so the
   line-box can be positioned with ordinary CSS instead of inferring
   Chrome's opaque textarea baseline. The editable fills its wrap
   (`height:100%`), carries lineHeight = ascent + descent (from
   canvas.measureText, see _applyBlankFont), and gets an explicit
   padding-top from _applyBlankFont: single-line layouts use
   `padding-top = boxH − lineHeight` to push the line-box bottom (=
   glyph descender end) to the wrap bottom (= the printed underline);
   multi-line layouts use `padding-top = 0`, with each row's line-box
   bottom landing on its own underline by construction. No flex, no
   transforms, no curve-fits — closed-form geometry. */
.overlay-input{background:transparent;color:#1a1a2a;padding:0 3px;margin:0;border:0;box-sizing:border-box;word-break:break-word;font-family:inherit;cursor:text;outline:none;overflow:visible;white-space:pre-wrap;width:100%;-webkit-text-size-adjust:none;text-size-adjust:none;transition:transform .12s ease-out}
/* Pen-below-line shift when not actively being edited. The text sits a
   touch (0.18 × fontSize) below the printed underline, which mimics a
   pen sitting just below the line — the same handwriting-style shift
   the old code applied via _baselineShift. Removed on :focus so the
   caret aligns cleanly with the underline while typing. Single source
   of truth: the PDF exporter reads getComputedStyle(ta).transform and
   propagates this same shift into the exported baseline so editor +
   export agree. `overflow: visible` (above) is what lets the descender
   extend below the wrap without being clipped. */
.overlay-input:not(:focus){transform:translateY(0.18em)}
/* A tick box holds a centred mark. The translateY nudge exists to sit text
   above an underline and would push a mark off-centre in a small square. */
/* Typed text is placed by padding-top (see applyBlankGeometry), so the
   handwriting nudge would double-shift it below the rule. */
.overlay-input[data-typed]:not(:focus){transform:none}
.overlay-input[data-choice]{text-align:center;padding:0;overflow:visible}
.overlay-input[data-choice]:not(:focus){transform:none}
/* Invisible placeholder — preserves the data-placeholder semantic the
   surrounding code uses for numbering without ever rendering visible
   placeholder text inside the blank. */
.overlay-input:empty:not(:focus)::before{content:attr(data-placeholder);color:transparent;pointer-events:none}

/* Manual blank wrappers — iconified square handles in two corners.
   Top-left = move (4-way arrow), bottom-right = resize (diagonal
   arrow). Delete lives in the editor bar. Handles show only on focus
   (no hover trigger — works on touch). The outline lives on the
   wrap (not the inner editable) so the visible field box is the wrap
   itself. The inner editable fills the wrap (height:100%) and
   _applyBlankFont sets padding-top so the line-box bottom lands on
   the wrap bottom — see _applyBlankFont for the geometry. */
/* Single source of truth for handle size — shared with the corner-
   notch geometry in `.manual-wrap:focus-within::before`. The handle
   sits centered on its corner (translate ±50%/±50%), so exactly half
   the handle extends INTO the wrap — that's the notch size that
   makes the line ends meet the handle's inside edge. */
.manual-wrap{position:absolute;z-index:2;touch-action:none;border-radius:2px;--blank-handle-size:16px}
/* Wrap edge is drawn by a clipped pseudo-element, not the browser's
   `outline`, so we can punch corner notches where the move + resize
   handles sit. Unfocused: full unclipped rectangle (no handles to
   make room for). Focused: clip-path notches each handle's
   inside-half so the line cleanly ends at the handle edge. */
.manual-wrap::before{
  content:"";
  position:absolute;
  inset:0;
  border:1px solid #c7c1b8;
  border-radius:2px;
  pointer-events:none;
  transition:border-color .15s ease,border-width .15s ease;
}
.manual-wrap:focus-within::before{
  border:1.5px solid #1f1d1a;
  /* Notch = half the handle size. `min(_, 50%)` keeps the polygon
     non-degenerate at the 8 px resize floor (well below 2 × notch). */
  --notch:min(calc(var(--blank-handle-size) / 2),50%);
  clip-path:polygon(
    var(--notch) 0, 100% 0,
    100% calc(100% - var(--notch)),
    calc(100% - var(--notch)) calc(100% - var(--notch)),
    calc(100% - var(--notch)) 100%,
    0 100%,
    0 var(--notch),
    var(--notch) var(--notch)
  );
}
.manual-wrap .overlay-input{position:relative;width:100%!important;height:100%!important;left:0!important;top:0!important}
.manual-wrap .move-handle,
.manual-wrap .resize-handle{
  position:absolute;
  width:var(--blank-handle-size);height:var(--blank-handle-size);
  background:#fff;
  border:1px solid #1f1d1a;
  border-radius:50%;
  box-sizing:border-box;
  box-shadow:0 1px 3px rgba(0,0,0,.22);
  z-index:5;
  display:none;
  align-items:center;
  justify-content:center;
  touch-action:none;
}
.manual-wrap .move-handle>svg,
.manual-wrap .resize-handle>svg{display:block;pointer-events:none}
.manual-wrap .move-handle{top:0;left:0;transform:translate(-50%,-50%);cursor:grab}
.manual-wrap .move-handle:active{cursor:grabbing}
.manual-wrap .resize-handle{bottom:0;right:0;transform:translate(50%,50%);cursor:nwse-resize}
.manual-wrap:focus-within .move-handle,
.manual-wrap:focus-within .resize-handle{display:flex}
/* ~44 px invisible hit area around each handle so fingers can grab.
   Inset is calculated against `--blank-handle-size` so the hit area
   stays ~44 px regardless of the visible handle dimension. */
.manual-wrap .move-handle::before,
.manual-wrap .resize-handle::before{content:"";position:absolute;inset:calc((44px - var(--blank-handle-size)) / -2)}

/* A blank whose first line sits behind a printed label carries a hole in that
   line instead of covering the word. The edge becomes a single stroked path
   (see _drawNotchedOutline) so the hole is outlined like every other edge and
   the field still reads as one connected box. The pseudo-element edge steps
   aside; the focused corner notches for the drag handles go with it, which is
   acceptable because the handles draw on top of the corners anyway. */
.manual-wrap.has-notch::before{display:none}
.blank-notch{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible}
.blank-notch path{
  fill:none;stroke:#c7c1b8;stroke-width:1;
  vector-effect:non-scaling-stroke;
  transition:stroke .15s ease,stroke-width .15s ease;
}
.manual-wrap:focus-within .blank-notch path{stroke:#1f1d1a;stroke-width:1.5}
