/* DocPivot — workspace styles.
 *
 * !! THE THEME SETS html { font-size: 10px } !!
 * So 1rem = 10px here, NOT the usual 16px. `2.5rem` is 25px, not 40px.
 * Every font-size in this file is therefore written in px, and any spacing that
 * has to hit an exact figure (clearing an absolutely positioned control, for
 * instance) is in px too. Assume rem == 16px and you will be 37.5% out.
 *
 * Kept in its own file. Never appended to custom.css: that file is shared with
 * every other tool on the site and carries other work-in-progress on prod, so
 * mixing DocPivot into it would violate the deploy isolation rule.
 *
 * Design tokens follow the site's standard tool skin — brand gradient panel,
 * white inner cards, orange #fa4c10.
 */

:root {
  --dp-orange: #fa4c10;
  --dp-grad-a: #fff7f3;
  --dp-grad-b: #ffeee6;
  --dp-border: #e8ded8;
  --dp-text: #23201f;
  --dp-muted: #7b716c;
  --dp-radius: 12px;
  /* One width for every drop zone — the hero's and every tool's. Declared once
     so the two cannot drift apart again; they were 360 and 720 at various
     points, which is how the page ended up looking like two different products.
     px, not rem: this theme sets html { font-size: 10px }. */
  --dp-drop-max: 500px;
}

/* The tool panel above the fold. Replaces the theme's .main-index on DocPivot
   tool pages: .main-index paints its white background and shadow on the
   Bootstrap column's PADDING box, so it ran 15px wider on each side than every
   card below it, which paint on the content box. Same look, one level in, and
   the panel, the article and the sidebar cards all share their edges. */
.dp-toolhead {
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 14px;
  padding: 2rem 2.5rem;
  margin-bottom: 20px;
}

/* Centred title block. Values copied from .plg-hero in
   output/plagiarism-checker-pro.php so a DocPivot tool page and a ToolsPivot
   tool page have the same header, rather than two hero treatments that are
   almost but not quite alike. */
.dp-toolhead h1 {
  font-size: 30px;
  font-weight: 800;
  color: #1a202c;
  line-height: 1.2;
  margin: 6px 0 0;
}

.dp-toolhead .dp-tool-desc {
  max-width: 760px;
  margin: 12px auto 0;
  color: #64748b;
  font-size: 16px;
  line-height: 1.6;
}

/* The badge is an inline pill, so text-align on the <p> is what centres it. */
.dp-toolhead .dp-tier-badge { margin: 10px 0 0; }

.dp-toolhead .dp-workspace,
.dp-toolhead #dp-workspace { margin-top: 26px; }

/* Reserve the zero state's height before boot.js runs.
   The workspace ships empty and the ES module injects the drop zone, so
   without this the panel grows after load and pushes everything below it —
   the same class of shift as an unreserved ad slot, just self-inflicted.

   THE RESERVATION MUST MATCH THE LOADED ZERO STATE, AND IT DRIFTED.
   The old values (231 / 198) were measured once and never rechecked. By
   2026-08-22 the real zero state was 292px on desktop, 316px on the image
   converter, and 315px at 500px wide — so every DocPivot tool shifted 61px on
   desktop and 117px on mobile, on every single load. A number that is only
   correct on the day it is measured is not a fix.

   So the same floor is now applied to BOTH states: before boot, and after boot
   while the workspace is still empty. Whatever the zero state actually renders
   at, the box is the same height on either side of the module loading, which
   is what removes the shift. It is released by .has-pages, so a workspace with
   files in it is never held to a minimum it has outgrown.

   One number for both breakpoints, sized to the tallest zero state measured
   (the image converter's, whose longer drop hint wraps). Being a little taller
   than a given tool needs costs some whitespace; being SHORTER costs CLS. */
#dp-workspace:not(.dp-workspace),
.dp-workspace:not(.has-pages) {
  min-height: 320px;
}
#dp-workspace:not(.dp-workspace) {
  /* .dp-workspace's own bottom margin only lands once the class is added, so
     without matching it here the panel still grew by 2rem after boot. */
  margin-bottom: 2rem;
}

/* .xd_top_box only pads its bottom, which is right for a slot above content.
   The closing slot sits under the tool, so the padding has to flip. */
.dp-toolhead .dp-ad-bottom { padding-top: 18px; padding-bottom: 0; }

@media (max-width: 560px) {
  .dp-toolhead h1 { font-size: 24px; }
  .dp-toolhead .dp-tool-desc { font-size: 15px; }
}

@media (max-width: 767px) {
  .dp-toolhead { padding: 1.5rem 1.35rem; }
}

/* ---- sidebar: Popular DocPivot Tools --------------------------------- */
/* Reuses .popular_tool_box wholesale; only the row accent and the icon differ.
   Icons are inline SVG stroked with currentColor, so setting `color` on the
   row is all that is needed to invert them on hover. */

.dp-poptools .dp-hovr:hover { background: var(--dp-orange); }
.dp-poptools .dp-hovr:hover a { color: #fff; text-decoration: none; }

.dp-poptools .icon_image {
  color: var(--dp-orange);
  display: flex;
  align-items: center;
  justify-content: center;
}

.dp-poptools .dp-hovr:hover .icon_image { color: #fff; }

/* The sheet bodies read as solid paper against either background. */
.dp-poptools .icon_image svg rect,
.dp-poptools .icon_image svg circle { vector-effect: non-scaling-stroke; }

/* audit 2026-10-01: nothing on this site emits a `dir` attribute - RTL is set
   with the CSS `direction` property (rtl.css) - so `[dir="rtl"]` alone never
   matched and every RTL rule in this file was dead. The three RTL languages
   are matched by `lang` as well, which <html> does carry. */
[dir="rtl"] .dp-poptools .icon_image,
html:lang(ar) .dp-poptools .icon_image,
html:lang(ur) .dp-poptools .icon_image,
html:lang(fa) .dp-poptools .icon_image { float: right; margin-right: 0; margin-left: 10px; }

.dp-workspace { margin: 0 0 2rem; }

/* ---- drop zone ------------------------------------------------------- */

.dp-dropzone {
  /* px, not rem. `36rem` reads as 576px and rendered as 360px, because this
     theme sets html { font-size: 10px } — which is why the zone looked cramped
     on every tool. Same trap as the section padding and the card spacing. */
  /* width AND max-width: `margin: 0 auto` alone centres it, but inside a flex
     column (the utility tools) a flex item with auto margins shrinks to its
     content — which is why favicon and base64 rendered at 466px while every
     other tool sat at 720px. */
  width: 100%;
  max-width: var(--dp-drop-max);
  margin: 0 auto;
  box-sizing: border-box;
  background: #fff;
  border: 2px dashed #f0cdbc;
  border-radius: 18px;
  /* The whole zone opens the picker, so it has to look like it does. */
  cursor: pointer;
  box-shadow: 0 10px 30px rgba(250, 76, 16, .08);
  padding: 40px 30px 34px;
  text-align: center;
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}

/* Caps the TEXT width independently of the box, so the caption stays readable
   in a zone that is now 720px wide. */
/* No cap of its own any more: at 500px the box already holds the text to a
   comfortable measure, and a second limit here would only be a number to keep
   in sync with the first. */
.dp-dropzone-inner { margin: 0 auto; }

.dp-dropzone-icon { display: block; margin: 0 auto 18px; color: var(--dp-orange); }

.dp-dropzone.dp-dragover {
  border-color: var(--dp-orange);
  border-style: solid;
  background: #fffaf7;
  box-shadow: 0 14px 36px rgba(250, 76, 16, .20);
  transform: translateY(-2px);
}

.dp-dropzone-title { font-size: 19px; font-weight: 600; color: var(--dp-text); margin: 18px 0 6px; }
.dp-dropzone-hint  { font-size: 15.5px; line-height: 1.55; color: var(--dp-muted); margin: 0; }
.dp-dropzone-hint:empty { display: none; }

.dp-btn {
  display: inline-block;
  border: 0;
  border-radius: 8px;
  /* px, not rem — see the warning at the top of this file. html is 10px here,
     so the `1rem` this rule used to carry rendered Apply and Download PDF at
     10px with 7px padding. .dp-btn-ghost already hard-coded 16px, which is why
     only the primary buttons looked wrong. */
  padding: 11px 22px;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
}

.dp-btn-primary { background: var(--dp-orange); color: #fff; }

/* Touch targets. Apple HIG asks 44pt, Material 48dp; every control here
   measured 28-37px, which is a mis-tap on a phone rather than a style nit.
   Scoped to coarse pointers so the desktop toolbar stays compact. */
@media (pointer: coarse) {
  .dp-btn,
  .dp-field select,
  .dp-field input,
  .dp-toolbar .dp-btn-ghost {
    min-height: 44px;
  }
  /* Vertical padding is already inside the 44px, so only the sides matter. */
  .dp-toolbar .dp-btn-ghost { padding-top: 0; padding-bottom: 0; }
}
.dp-btn-primary:hover { filter: brightness(1.06); }

/* The SECOND of two stacked actions. Apply and Download are both real steps
   and both were solid orange, so the panel offered two equal primaries and the
   eye had no idea which came first. Whichever is the next step keeps the solid
   fill; the other steps back.

   Stepping back means the SITE'S secondary, .dp-btn-ghost — white, a neutral
   border, ordinary text. An orange outline was tried first and read as an
   unfinished or disabled control rather than a real second action, because
   nothing else on the site is drawn that way. */
.dp-btn-primary.is-secondary {
  background: #fff;
  color: var(--dp-text);
  box-shadow: inset 0 0 0 1px var(--dp-border);
}
.dp-btn-primary.is-secondary:hover {
  filter: none;
  box-shadow: inset 0 0 0 1px var(--dp-orange);
  color: var(--dp-orange);
}
.dp-btn:focus-visible { outline: 3px solid var(--dp-orange); outline-offset: 2px; }

/* Once pages are loaded the drop zone stops dominating the page. */
.dp-workspace.has-pages .dp-dropzone {
  max-width: none;
  padding: 12px 18px;
  box-shadow: none;
  border-radius: var(--dp-radius);
}
.dp-workspace.has-pages .dp-dropzone-inner { max-width: none; }
/* Once a file is loaded the zone is a small "add another" control, so the
   illustration and the sub-caption stop earning their space. */
.dp-workspace.has-pages .dp-dropzone-icon,
.dp-workspace.has-pages .dp-dropzone-hint,
.dp-workspace.has-pages .dp-zero-icon,
.dp-workspace.has-pages .dp-zero-privacy { display: none; }
.dp-workspace.has-pages .dp-dropzone-title { margin: 10px 0 0; font-size: 15px; }
.dp-workspace.has-pages .dp-btn-lg { font-size: 16px; padding: 8px 18px; }
.dp-workspace.has-pages .dp-zero-drag { margin-top: 8px; font-size: 14px; }

/* An empty grid frame under an empty workspace is just a stray box. */
.dp-workspace:not(.has-pages) .dp-grid-viewport,
.dp-workspace:not(.has-pages) .dp-grid-host { display: none; }

/* The theme sets a global `display` on inputs, which beats the UA stylesheet's
   [hidden] { display: none }. Without this the native "Choose files / No file
   chosen" control shows next to our own button on every tool page. */
.dp-workspace input[type="file"][hidden],
.dp-zero input[type="file"][hidden] { display: none !important; }

/* ---- notices --------------------------------------------------------- */

.dp-notice { margin: .75rem 0 0; font-size: 16px; }
.dp-notice:empty { display: none; }
.dp-notice.is-error { color: #b3261e; }
.dp-notice.is-warn  { color: #8a5a00; }
.dp-notice.is-ok    { color: var(--dp-muted); }

/* ---- page grid ------------------------------------------------------- */

.dp-grid-host { margin-top: 1rem; }
.dp-grid-host:empty { display: none; }

.dp-grid-viewport {
  max-height: 70vh;
  overflow-y: auto;
  overflow-x: hidden;
  border: 1px solid var(--dp-border);
  border-radius: var(--dp-radius);
  background: #fff;
  padding: 12px;
}

.dp-grid-viewport:focus-visible { outline: 3px solid var(--dp-orange); outline-offset: -3px; }
.dp-grid-spacer { position: relative; }
.dp-grid-track  { display: grid; will-change: transform; }

.dp-page {
  position: relative;
  border: 2px solid transparent;
  border-radius: 8px;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .10);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  overflow: hidden;
  /* Fixed height keeps the virtualisation maths honest — see computeVisibleRange.
     The grid sets --dp-tile-h to the height its maths uses, so the zoom
     slider changes the real tile and the virtualiser's idea of it together. */
  height: var(--dp-tile-h, 200px);
}

/* SELECTED AND FOCUSED MUST NOT LOOK THE SAME.
 *
 * Both were orange — selection a border, focus an outline — so a page that was
 * merely the keyboard's current tile read as chosen. Worse, the roving focus
 * marker sits on page 1 from the moment a document loads, so the first page
 * always looked selected before anything had been picked.
 *
 * Selection is now orange AND tinted, which nothing else in the grid is.
 * Focus is a neutral ring, and only while the grid actually has keyboard
 * focus — it is an accessibility affordance for people driving by keyboard,
 * not a permanent mark on one tile. */
.dp-page.is-selected {
  border-color: var(--dp-orange);
  background: var(--dp-grad-a);
  box-shadow: 0 0 0 1px var(--dp-orange) inset;
}
.dp-grid-viewport:focus-within .dp-page.is-focused {
  outline: 2px solid var(--dp-text);
  outline-offset: 2px;
}

.dp-page-thumb { max-width: 100%; max-height: 100%; display: block; }

.dp-page-num {
  position: absolute;
  bottom: 4px; right: 6px;
  background: rgba(0, 0, 0, .62);
  color: #fff;
  font-size: 13px;
  line-height: 1;
  padding: 3px 6px;
  border-radius: 4px;
}

.dp-grid-status { margin-top: .5rem; font-size: 16px; color: var(--dp-muted); }
.dp-grid-status:empty { display: none; }

/* ---- RTL ------------------------------------------------------------- */
/* Built in from the first commit. Retrofitting RTL across 86 tools later is
   far more expensive than carrying it from the start (architecture §7.1). */

[dir="rtl"] .dp-page-num,
html:lang(ar) .dp-page-num,
html:lang(ur) .dp-page-num,
html:lang(fa) .dp-page-num { right: auto; left: 6px; }

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

@media (prefers-reduced-motion: reduce) {
  .dp-dropzone, .dp-zero { transition: none; }
  .dp-zero.dp-dragover { transform: none; }
  /* Decorative drift has no meaning to convey, so it is the first thing to go. */
  .dp-deco { animation: none; }
}

/* ---- export bar ------------------------------------------------------ */

.dp-exportbar {
  display: none;
  align-items: center;
  gap: .9rem;
  margin-top: 1rem;
}

.dp-exportbar.is-ready { display: flex; }
.dp-export-status { font-size: 16px; color: var(--dp-muted); }
.dp-btn[disabled] { opacity: .5; cursor: not-allowed; }

/* A page whose thumbnail could not be rendered still occupies its slot, so the
   grid stays aligned and the failure is visible rather than silent. */
.dp-page.is-unrendered { background: repeating-linear-gradient(45deg,#fafafa,#fafafa 6px,#f2f2f2 6px,#f2f2f2 12px); }

/* ---- toolbar --------------------------------------------------------- */

.dp-toolbar {
  display: none;
  flex-wrap: wrap;
  align-items: center;
  /* NEVER SHRUNK BELOW ITS OWN CONTENT.
   *
   * The canvas is a fixed-height column, and a flex item defaults to
   * shrinkable — so when the buttons wrapped to a second row the toolbar was
   * squeezed back to one row's height and the second row spilled over the page
   * grid. Measured at 560px with only the original six buttons: 23px of
   * buttons sitting on top of the thumbnails. Latent for any tool whose
   * toolbar wraps; adding two more buttons only moved the width it happens at.
   * The grid gives up the space instead, which is what it is for. */
  flex-shrink: 0;
  gap: .5rem;
  margin-top: 1rem;
  padding: .6rem .75rem;
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: var(--dp-radius);
}

.dp-toolbar.is-ready { display: flex; }

.dp-btn-ghost {
  background: #fff;
  color: var(--dp-text);
  border: 1px solid var(--dp-border);
  padding: .5rem .9rem;
  font-size: 16px;
  font-weight: 500;
}

.dp-btn-ghost:hover:not([disabled]) { border-color: var(--dp-orange); color: var(--dp-orange); }

.dp-toolbar-sep { width: 1px; height: 22px; background: var(--dp-border); margin: 0 .25rem; }
.dp-toolbar-hint { font-size: 16px; color: var(--dp-muted); margin-left: auto; }
.dp-toolbar-hint.is-error { color: #b3261e; }

[dir="rtl"] .dp-toolbar-hint,
html:lang(ar) .dp-toolbar-hint,
html:lang(ur) .dp-toolbar-hint,
html:lang(fa) .dp-toolbar-hint { margin-left: 0; margin-right: auto; }
/* In the editor the toolbar is a column of rows, so the status line takes a
   full row under the buttons instead of wedging itself between them. Empty it
   collapses, so a silent toolbar reserves no blank strip. */
.dp-view-editor .dp-toolbar-hint { margin-left: 0; width: 100%; padding: 2px 4px 0; }
[dir="rtl"] .dp-view-editor .dp-toolbar-hint,
html:lang(ar) .dp-view-editor .dp-toolbar-hint,
html:lang(ur) .dp-view-editor .dp-toolbar-hint,
html:lang(fa) .dp-view-editor .dp-toolbar-hint { margin-right: 0; }
.dp-toolbar-hint:empty { display: none; }

/* ---- workspace body / op panel --------------------------------------- */

.dp-body { display: grid; grid-template-columns: 1fr; gap: 1rem; margin-top: 1rem; }
@media (min-width: 900px) {
  .dp-body:has(.dp-panel-host.is-ready) { grid-template-columns: 1fr 280px; }
}

.dp-panel-host { display: none; }
.dp-panel-host.is-ready { display: block; }

.dp-oppanel {
  background: linear-gradient(180deg, var(--dp-grad-a), var(--dp-grad-b));
  border: 1px solid var(--dp-border);
  border-radius: var(--dp-radius);
  padding: 1rem;
}

.dp-oppanel-title { font-size: 20px; margin: 0 0 .75rem; color: var(--dp-text); }
.dp-oppanel-rows { display: grid; gap: .7rem; }
.dp-field { display: grid; gap: .25rem; }

/* An author `display` rule beats the UA stylesheet's [hidden] { display: none },
   so a conditionally hidden field stays visible without this. */
.dp-field[hidden] { display: none; }
.dp-field label { font-size: 16px; color: var(--dp-muted); }

.dp-field input,
.dp-field select {
  width: 100%;
  padding: .55rem .65rem;
  border: 1px solid var(--dp-border);
  border-radius: 8px;
  background: #fff;
  /* 16px is a hard floor on inputs: iOS Safari zooms the whole page when a
     focused field is smaller, which throws the layout off mid-task. */
  font-size: 16px;
}

.dp-field input:focus-visible,
.dp-field select:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: 1px; }
.dp-field input[type="checkbox"] { width: auto; }

.dp-oppanel-actions { display: flex; align-items: center; gap: .6rem; margin-top: .9rem; }
.dp-oppanel-hint { font-size: 16px; color: var(--dp-muted); }
.dp-oppanel-hint.is-error { color: #b3261e; }

/* ======================================================================
   Homepage
   ====================================================================== */

.dp-visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ---- hero ------------------------------------------------------------ */

/* The theme renders an empty `.tool_area_banner` strip between the nav and the
   page content. On a normal tool page it is invisible; under a full-bleed
   tinted hero it reads as a stray grey band separating the hero from the
   header.
   
   Scoped with :has() so only DocPivot pages are affected — the strip keeps
   working everywhere else. Browsers without :has() simply keep the small gap,
   which is today's behaviour, so this degrades to no worse than before.

   `~` not `+`: the view emits its stylesheet <link> between the banner and
   #dp-home, so the adjacent-sibling combinator never matches.
   
   NOTE: if that strip is ever used for an ad slot, this rule must be revisited. */
.tool_area_banner:has(~ #dp-home) { display: none; }

.dp-hero {
  position: relative;
  overflow: hidden;
  padding: 5rem 0 4.5rem;
  text-align: center;

  /* Warm tint plus a dot field. The dots are a repeating radial-gradient rather
     than an image: no extra request, no CLS, and it scales to any width. */
  background-color: var(--dp-grad-a);
  background-image:
    radial-gradient(circle, rgba(250, 76, 16, .16) 1.6px, transparent 1.7px),
    linear-gradient(180deg, var(--dp-grad-a), var(--dp-grad-b));
  background-size: 26px 26px, 100% 100%;
  background-position: 0 0, 0 0;
}

/* ---- decorative icons ------------------------------------------------ */

/* Ornamental only. The layer is aria-hidden in the markup and non-interactive
   here, so it can never take focus or swallow a click on the drop zone. */
.dp-hero-deco {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.dp-deco {
  position: absolute;
  display: block;
  opacity: .92;
  filter: drop-shadow(0 4px 10px rgba(0, 0, 0, .07));
  animation: dp-float 7s ease-in-out infinite;
}

/* Logical insets so the arrangement mirrors in ar / ur / fa instead of
   colliding with the headline. */
.dp-deco-1 { inset-block-start: 16%; inset-inline-start: 7%;  animation-delay: 0s;   }
.dp-deco-2 { inset-block-start: 58%; inset-inline-start: 4%;  animation-delay: -1.2s;}
.dp-deco-3 { inset-block-start: 74%; inset-inline-start: 15%; animation-delay: -2.4s;}
.dp-deco-4 { inset-block-start: 30%; inset-inline-start: 17%; animation-delay: -3.6s;}
.dp-deco-5 { inset-block-start: 14%; inset-inline-end: 8%;    animation-delay: -0.6s;}
.dp-deco-6 { inset-block-start: 55%; inset-inline-end: 5%;    animation-delay: -1.8s;}
.dp-deco-7 { inset-block-start: 72%; inset-inline-end: 16%;   animation-delay: -3s;  }
.dp-deco-8 { inset-block-start: 32%; inset-inline-end: 18%;   animation-delay: -4.2s;}

@keyframes dp-float {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-9px) rotate(-2deg); }
}

/* Below 1100px the icons start crowding the copy, and the headline has to win.
   Under 900px they are gone entirely — on a phone they are pure noise. */
@media (max-width: 1100px) {
  .dp-deco-3, .dp-deco-4, .dp-deco-7, .dp-deco-8 { display: none; }
}

@media (max-width: 900px) {
  .dp-hero-deco { display: none; }
}

/* Once a file is loaded the hero has done its job. Collapse it to a slim title
   strip and drop the decoration — a half-height dot field behind a working
   grid just looks like a rendering fault. */
.dp-hero.is-collapsed { padding: 1.5rem 0; background-image: none; background: var(--dp-grad-a); }
.dp-hero.is-collapsed .dp-zero,
.dp-hero.is-collapsed .dp-hero-sub,
.dp-hero.is-collapsed .dp-hero-deco { display: none; }
.dp-hero.is-collapsed .dp-hero-title { font-size: 23px; margin: 0; }

.dp-hero-title { font-size: 32px; line-height: 1.2; margin: 0 0 .6rem; color: var(--dp-text); }
/* 46rem was 460px here (html is 10px in this theme), which broke the new,
   longer subtitle onto four ragged lines under a title running nearly twice
   that width. 72rem = 720px lands it on two lines and keeps the measure
   comfortably inside the 75-character limit for readable prose. */
.dp-hero-sub   { font-size: 18px; color: var(--dp-muted); margin: 0 auto 1.6rem; max-width: 72rem; }



/* ---- zero-state ------------------------------------------------------ */

/* The primary action of the whole page, so it is built to look like one:
   icon for affordance, a large button as the path everyone can take, and
   dragging offered second because it needs a desktop and a file already in
   hand. Accepted formats are stated up front — discovering a file type is
   unsupported only after choosing it is exactly the mid-task wall we exist
   to avoid. */
.dp-zero {
  /* px: `36rem` was 360px here, not 576px. See the note on .dp-dropzone.
     Narrower than a TOOL's drop zone on purpose. The hero sits inside a
     decorated band with scattered glyphs either side, and a 720px box crowded
     them; a tool page has nothing competing for that width. */
  max-width: var(--dp-drop-max);
  margin: 0 auto;
  padding: 40px 30px 34px;
  background: #fff;
  border: 2px dashed #f0cdbc;
  border-radius: 18px;
  box-shadow: 0 10px 30px rgba(250, 76, 16, .08);
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}

.dp-zero-icon { display: block; margin: 0 auto .9rem; }

.dp-btn-lg {
  font-size: 18px;
  padding: .95rem 2.25rem;
  border-radius: 10px;
  box-shadow: 0 4px 14px rgba(250, 76, 16, .28);
}

.dp-btn-lg:hover { box-shadow: 0 6px 18px rgba(250, 76, 16, .36); }

.dp-zero-drag { margin: .9rem 0 0; font-size: 16px; color: var(--dp-muted); }

/* Two separate facts, so they get two lines rather than one sentence that
   wraps in the middle of "nothing is | uploaded". */
.dp-zero-formats { margin: .55rem 0 0; font-size: 13px; color: #6f655f; letter-spacing: .01em; }
.dp-zero-privacy { margin: .2rem 0 0; font-size: 13px; color: #6f655f; }

/* Dragging lifts the target so it is obvious where to let go. */
.dp-zero.dp-dragover {
  border-color: var(--dp-orange);
  border-style: solid;
  background: #fffaf7;
  box-shadow: 0 14px 36px rgba(250, 76, 16, .20);
  transform: translateY(-2px);
}

.dp-zero.is-loading { opacity: .65; pointer-events: none; }
.dp-zero.is-error   { border-color: #b3261e; border-style: solid; }
/* Why a drop on the hub was refused (an encrypted PDF, a file that will not
   open). Same red as every other DocPivot error line. */
.dp-zero-status { margin: .6rem 0 0; font-size: 14px; font-weight: 600; color: #b3261e; }

@media (max-width: 767px) {
  .dp-zero { padding: 1.75rem 1.1rem 1.4rem; border-radius: 14px; }
  .dp-btn-lg { width: 100%; padding: .9rem 1rem; }
  /* Dragging is a desktop gesture; on touch the line is just noise. */
  .dp-zero-drag { display: none; }
}

/* ---- sections -------------------------------------------------------- */

.dp-home section,
.dp-home nav { margin: 0; }

/* Section bands.
   Hairline rules were not enough separation — the blocks still read as one
   long column. Each section is now a full-bleed band with its own background,
   alternating plain and tinted, so every section is visibly its own thing.
   Bands run edge to edge; the inner .container keeps the copy aligned with the
   rest of the site. */
/* ONE section rhythm for the whole page. 3.25rem read as 52px when written and
   renders as 32px, because this theme sets html { font-size: 10px } — which is
   why the sections were sitting on top of each other. Stated in px so the value
   is the value. */
.dp-band { padding: 72px 0; }
.dp-band + .dp-band { border-top: 1px solid var(--dp-border); }
.dp-band-plain { background: #fff; }
.dp-band-tint  { background: linear-gradient(180deg, #fffaf7, var(--dp-grad-a)); }

@media (max-width: 767px) {
  /* 2.25rem was 22px here, not 36px. Same rem trap as the desktop value. */
  .dp-band { padding: 48px 0; }
  .dp-section-title { font-size: 23px; }
}

/* Every section heading, one size — the site's canonical h2 (custom.css sets
   h2 28px desktop / 23px mobile, and these match rather than invent a value).
   .dp-feature-copy h2 is held to the same size for the same reason. */
.dp-section-title { font-size: 28px; line-height: 1.25; margin: 0 0 12px; color: var(--dp-text); }
.dp-section-title.text-center + .dp-lede { max-width: 60ch; }

/* ONE gap between a section's heading block and its body.
   Previously the spacing came off the heading's own margin, so a section WITH
   a lede got 16 + 32 and a section WITHOUT one ("Who ends up using it") got
   only 16 — the heading sat on top of its cards. Putting the space on the body
   instead makes it identical either way. */
.dp-band .dp-tool-grid,
.dp-band .dp-steps-list,
.dp-band .dp-compare-grid,
.dp-band .dp-who-grid,
.dp-band .dp-proof-grid,
.dp-band .dp-faq-list,
.dp-band .dp-flow,
.dp-band .dp-pipe { margin-top: 32px; }

/* The lede is part of the heading block, so it keeps its own tight spacing and
   does not add to the 32px below. */
.dp-lede + .dp-tool-grid,
.dp-lede + .dp-steps-list,
.dp-lede + .dp-compare-grid,
.dp-lede + .dp-who-grid,
.dp-lede + .dp-proof-grid,
.dp-lede + .dp-faq-list,
.dp-lede + .dp-pipe { margin-top: 0; }

/* NOTE: this theme sets html { font-size: 10px }, so 1rem is TEN pixels, not
   sixteen. The values below are in px on purpose — rem figures written with a
   16px base in mind land ~40% too tight here, which is exactly what happened
   to this block on its first pass. */
.dp-tools-head {
  display: flex; flex-wrap: wrap; gap: 20px;
  align-items: flex-end; justify-content: space-between;
  margin-bottom: 26px;
}
.dp-tools-titles { flex: 1 1 auto; min-width: 0; }
.dp-tools-head .dp-section-title { margin-bottom: 6px; }
.dp-tools-sub { margin: 0; font-size: 16px; color: var(--dp-muted); }

/* The search field, given the room it earns. Typing three letters is the
   fastest route to any of 56 tools, and squeezed beside the heading it read as
   a filter for the heading rather than for the grid. */
/* 60% of the row, beside the heading. Full width read as a page-level search
   rather than as this section's, and the original corner-sized field was too
   small to look like the fastest way in. */
.dp-search { position: relative; display: block; flex: 0 0 60%; max-width: 60%; }
.dp-search-icon {
  /* Trailing edge, not leading. Logical property so it lands on the left in
     Arabic, Urdu and Farsi rather than colliding with the text there.

     Drawn as the same filled orange pill the ToolsPivot header search uses, so
     the two halves of the site read as one product rather than two. Geometry
     copied from .search_field / .searc_icon_btn in custom.css.

     It stays pointer-events:none ON PURPOSE. The control is a <label> wrapping
     the input, so a click anywhere inside it - the pill included - focuses the
     field natively. That gives the pill a real behaviour without it being a
     button that does nothing: the grid filters as you type, so there is no
     submit step to fake. */
  position: absolute; inset-inline-end: 4px; top: 50%; transform: translateY(-50%);
  box-sizing: border-box;
  width: 72px; height: 47px;   /* over the 44px target floor */
  padding: 13px 26px;          /* centres the 20x20 glyph inside the pill */
  border-radius: 999px;
  background: var(--dp-orange);
  color: #fff;
  pointer-events: none;
  transition: background-color .15s ease;
}
.dp-search input {
  width: 100%;
  box-sizing: border-box;
  height: 55px;                /* matches the ToolsPivot field height exactly */
  /* Logical padding: the trailing value clears the pill on whichever side it
     lands, so RTL needs no separate override. */
  padding-block: 0;
  padding-inline: 24px 88px;
  border: 1px solid var(--dp-border);
  border-radius: 999px;
  background: #fff;
  font-size: 16px;      /* the floor: below this iOS zooms the page on focus */
  color: var(--dp-text);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .06);
  transition: border-color .15s ease, box-shadow .15s ease;
}
.dp-search input::placeholder { color: #6f6058; }
.dp-search input:focus,
.dp-search input:focus-visible {
  outline: none;
  border-color: var(--dp-orange);
  box-shadow: 0 0 0 4px rgba(250, 76, 16, .13);
}
/* The glyph is white on orange now, so focus deepens the pill instead of
   recolouring the icon - recolouring would have made it vanish. */
.dp-search:focus-within .dp-search-icon { background: #d8410d; }

/* ---- category switcher -------------------------------------------------
   Replaced six stacked category sections. The page was a long scroll of
   headings, and finding "compress" meant knowing which heading owned it. */

.dp-filters {
  display: flex; flex-wrap: wrap; gap: 8px;
  /* A clear gap to the grid. At 1.5rem the chips crowded the first row of
     cards and read as part of it rather than as the control above it. */
  margin-bottom: 34px;
}

.dp-filter {
  display: inline-flex; align-items: center; gap: .5rem;
  /* 44px min height: these are the primary controls on the page and the first
     thing a thumb reaches for. */
  min-height: 44px;
  padding: 10px 18px;
  border: 1px solid var(--dp-border);
  border-radius: 999px;
  background: #fff;
  color: var(--dp-text);
  font-size: 15px; font-weight: 600;
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}
.dp-filter:hover { border-color: var(--dp-orange); color: var(--dp-orange); }
.dp-filter:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: 2px; }

.dp-filter.is-active {
  background: var(--dp-orange);
  border-color: var(--dp-orange);
  color: #fff;
}
.dp-filter.is-active:hover { color: #fff; }

/* The count is the useful part: it tells you a filter is worth pressing before
   you press it, and that "Secure" holds four tools rather than forty. */
.dp-filter-n {
  font-size: 12.5px; font-weight: 700;
  padding: 1px 7px; border-radius: 999px;
  background: #f4efec; color: var(--dp-muted);
  font-variant-numeric: tabular-nums;
}
.dp-filter.is-active .dp-filter-n { background: rgba(255,255,255,.25); color: #fff; }

/* ---- tool grid ---------------------------------------------------------- */

.dp-tool-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(268px, 1fr));
  gap: 14px;
  /* Stretch, so a card whose description wraps to two lines does not leave its
     neighbours short. Ragged card heights are the main thing that makes the
     reference grids look unfinished. */
  align-items: stretch;
  /* Every ROW the same height too, not just the cards within a row. */
  grid-auto-rows: 1fr;
}

/* ---- tool card ----------------------------------------------------------
   Vertical stack: mark, name, then what it does. Reading order matches
   scanning order, and it stops a long tool name from squeezing the description
   into a column three words wide — which is what the icon-beside-text layout
   did at four columns. */

.dp-tool {
  position: relative;               /* anchor for the ghost glyph */
  overflow: hidden;                 /* clips it against the corner */
  /* No uniform gap: the mark wants more air beneath it than the name wants
     above its description, so the two spacings are set separately. A single
     gap value makes the three parts read as a list rather than as a heading
     with a caption. */
  display: flex; flex-direction: column;
  padding: 22px 21px;
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 10px;
  text-decoration: none;
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.dp-tool[hidden] { display: none; }
.dp-tool:hover {
  border-color: #d9cec7;
  box-shadow: 0 6px 20px rgba(35, 32, 31, .08);
  transform: translateY(-2px);
  text-decoration: none;
}
.dp-tool:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: 2px; }

/* A tinted mark per CATEGORY, not per tool.
   Every reference design either randomises this colour per tile or repeats one
   identical icon on all of them — either way the colour carries no
   information. Six hues mapped to the six categories means someone scanning
   for an image tool can skip the PDF ones without reading a word. */
.dp-tool-ico {
  display: grid; place-items: center;
  width: 44px; height: 44px;
  margin-bottom: 14px;
  border-radius: 11px;
  background: #f4efec; color: var(--dp-muted);
}
/* The shared set draws at 30px for the sidebar; a card wants it a shade
   smaller so the tint has a visible margin around the glyph. */
.dp-tool-ico svg { width: 25px; height: 25px; }
.dp-ico-organise { background: #fff1e8; color: #c0430f; }
.dp-ico-convert  { background: #e9f2ff; color: #1f5fb0; }
.dp-ico-edit     { background: #f1ecff; color: #5a44b8; }
.dp-ico-optimise { background: #e9f7ee; color: #1c6b3a; }
.dp-ico-secure   { background: #fdeef0; color: #b3263c; }
.dp-ico-images   { background: #fff6e0; color: #96660a; }

/* The oversized glyph in the corner. Same icon as the chip, so the card has
   one idea rather than two, and set in the category's own colour so the tint
   still carries the grouping at a glance.
   Kept very faint on purpose: at full strength it competes with the tool name,
   which is the one thing on the card that has to be read. */
.dp-tool-ghost {
  position: absolute;
  top: -18px; right: -18px;
  color: var(--dp-muted);
  /* Strong enough to read as a shadow behind the card rather than as a smudge.
     This value is not free: it tints the background the description sits on,
     so .dp-tool-text below is darkened to keep that text above the 4.5:1 AA
     floor on every one of the six category colours. Raise one and re-check
     the other.
     .05 = 95% transparent: barely there, a shadow you notice only once you
     look for it. */
  opacity: .05;
  pointer-events: none;             /* never intercepts the card's own click */
  transition: opacity .2s ease, transform .2s ease;
}
.dp-tool-ghost svg { width: 116px; height: 116px; stroke-width: 1.5; }
/* Already at the ceiling, so hover moves it rather than brightening it. */
.dp-tool:hover .dp-tool-ghost { transform: scale(1.04) rotate(-3deg); }

.dp-ghost-organise { color: #c0430f; }
.dp-ghost-convert  { color: #1f5fb0; }
.dp-ghost-edit     { color: #5a44b8; }
.dp-ghost-optimise { color: #1c6b3a; }
.dp-ghost-secure   { color: #b3263c; }
.dp-ghost-images   { color: #96660a; }

/* The real content sits above the texture. */
.dp-tool-ico, .dp-tool-name, .dp-tool-text { position: relative; z-index: 1; }

.dp-tool-name {
  display: block;
  font-size: 17px; font-weight: 700; line-height: 1.3;
  color: var(--dp-text); margin-bottom: 6px;
}

/* What the tool actually does. This line replaced the tier badge, which said
   "In your browser · unlimited" on 41 of 56 cards — technically true, and
   useless as the only thing a card says about itself.
   Not line-clamped: a clamp cut 12 of them mid-word, which reads as a bug
   rather than as brevity. Rows stretch to the tallest card, so the longest
   description costs nothing. */
.dp-tool-text {
  font-size: 14px; line-height: 1.5;
  /* Darker than --dp-muted (#7b716c). That grey measures 4.75:1 on plain white
     and 4.38:1 over even the faintest ghost, which is under the 4.5:1 AA floor —
     the ghost tints the background this text sits on, so the grey that just
     passed on white no longer does. #6f655f holds 5.23:1 against the worst of
     the six category tints. */
  color: #6f655f;
}

/* .dp-tier / .dp-tier-browser|server|ai lived here until 2026-08-02. Their
   last markup was the homepage tool card; the tool PAGE states the tier via
   .dp-tier-badge, which is a separate rule and still live. */

@media (max-width: 767px) {
  /* One column below 768px: a 60% field beside a heading leaves neither room. */
  .dp-search { flex-basis: 100%; max-width: 100%; }
  /* Shorter field and a narrower pill, but the pill stays at the 44px target
     floor and the trailing padding still clears it. */
  .dp-search input { height: 52px; padding-block: 0; padding-inline: 18px 74px; font-size: 16px; }
  .dp-search-icon { inset-inline-end: 3px; width: 60px; height: 44px; padding: 12px 20px; }
  .dp-filters { margin-bottom: 24px; }
  /* The switcher scrolls sideways rather than stacking into six rows that
     push the tools themselves off the screen. */
  .dp-filters {
    flex-wrap: nowrap; overflow-x: auto;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
    margin-inline: -15px; padding-inline: 15px;
  }
  .dp-filters::-webkit-scrollbar { display: none; }
  .dp-filter { flex: none; }
  .dp-tool-grid { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  .dp-tool, .dp-filter, .dp-tool-ghost { transition: none; }
  .dp-tool:hover .dp-tool-ghost { transform: none; }
  .dp-tool:hover { transform: none; }
}

.dp-empty { font-size: 16px; color: var(--dp-muted); }

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

/* THE GENERIC PILL. Used by "Recently used" on the home page, where each chip
 * is a plain link to a tool.
 *
 * Two other blocks in this file also style `.dp-chip` — the file card's format
 * list and the op panel's radiogroup — and both once did it unscoped. Being
 * later in the file, they won: the op panel's `flex: 0 0 100%` turned this row
 * into six full-width stacked bars with centred grey text. Both are scoped to
 * their own containers now. Keep it that way; this rule is the one that has to
 * survive, because it is the only one styling a navigation link. */
.dp-chips { display: flex; flex-wrap: wrap; gap: 8px; }

.dp-chip {
  /* inline-flex plus min-height rather than more padding: these are tap
     targets on a phone, and 44px is the floor. Padding alone got to 34. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: .45rem 1.1rem;
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 999px;
  font-size: 16px;
  text-decoration: none;
  color: var(--dp-text);
  transition: border-color .12s ease, color .12s ease;
}

.dp-chip:hover { border-color: var(--dp-orange); color: var(--dp-orange); text-decoration: none; }
.dp-chip:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: 2px; }

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

.dp-proof-grid {
  display: grid; gap: 16px;
  grid-template-columns: repeat(4, 1fr);
}
.dp-proof-item {
  padding: 24px 22px 22px;
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 14px;
}
.dp-proof-ico {
  display: grid; place-items: center;
  width: 42px; height: 42px; margin-bottom: 14px;
  border-radius: 11px;
  background: var(--dp-grad-b); color: var(--dp-orange);
}
.dp-proof-item strong {
  display: block; font-size: 17px; line-height: 1.3;
  margin-bottom: 8px; color: var(--dp-text);
}
.dp-proof-item p { margin: 0; font-size: 15px; line-height: 1.6; color: #57504a; }

/* ---- the two tool families ------------------------------------------------
   Two columns of prose above the tool grid, saying what the hub holds. Plain
   text on the page background rather than cards: the grid directly below is
   already 104 bordered tiles, and boxing these two would read as the first row
   of it. Sizes in px because this theme sets html { font-size: 10px }. */

.dp-families-grid {
  display: grid; gap: 28px 48px;
  grid-template-columns: repeat(2, 1fr);
}
/* Cards rather than bare columns. As plain text the two halves floated in the
   tint with nothing holding them, and the section read as a footnote to the
   grid above instead of the answer to "what is in this catalogue". Card
   geometry is copied from .dp-proof-item so the page keeps one card language. */
.dp-family {
  padding: 26px 26px 24px;
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 14px;
}

/* Icon, title and count on one baseline. The count is the useful part: it is
   read from the catalogue, so it also tells the reader how the 100+ splits. */
.dp-family-head {
  display: flex; align-items: center; gap: 12px;
  margin-bottom: 14px;
}
.dp-family-ico {
  display: grid; place-items: center; flex: none;
  width: 42px; height: 42px;
  border-radius: 11px;
  background: var(--dp-grad-b); color: var(--dp-orange);
}
.dp-family h3 {
  margin: 0; flex: 1 1 auto;
  font-size: 20px; font-weight: 600; line-height: 1.3;
  color: var(--dp-text);
}
.dp-family-count {
  flex: none;
  padding: 3px 11px;
  border-radius: 999px;
  background: var(--dp-grad-b);
  color: var(--dp-orange);
  font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums;
}
.dp-family p {
  margin: 0 0 .9rem;
  font-size: 16px; line-height: 1.7; color: #57504a;
}
.dp-family p:last-child { margin-bottom: 0; }

/* ---- under the hood -------------------------------------------------- */

/* .dp-eyebrow is display:inline-block because feature rows use it left-aligned
   beside their copy. That makes `text-center` on the element itself a no-op —
   it centres the text inside a box already shrunk to the text. Centred here by
   turning it back into a block, scoped so the feature rows keep their layout. */
.dp-tech .dp-eyebrow { display: block; text-align: center; }

/* Capped and centred to match .dp-faq-list, so the two prose blocks on this
   page share one measure instead of each finding their own. */
.dp-tech-body { max-width: 880px; margin-inline: auto; }
.dp-tech-body p {
  margin: 0 0 .9rem;
  font-size: 16px; line-height: 1.7; color: #57504a;
}
.dp-tech-body p:last-child { margin-bottom: 0; }

@media (max-width: 767px) {
  .dp-families-grid { grid-template-columns: 1fr; gap: 24px; }
}

/* ---- FAQ ------------------------------------------------------------------
   A flat list with hairline dividers and a chevron, not a stack of boxes.
   Boxed rows put a border around every question and turned a quiet reference
   section into eight competing cards. */

.dp-faq-list { max-width: 880px; margin-inline: auto; }

.dp-faq-item { border-bottom: 1px solid var(--dp-border); }
.dp-faq-item:first-of-type { border-top: 1px solid var(--dp-border); }

.dp-faq-item summary {
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  /* Comfortably past the 44px touch minimum — this is the only control here. */
  padding: 22px 4px;
  cursor: pointer;
  font-size: 17px; font-weight: 600; line-height: 1.45;
  color: var(--dp-text);
  list-style: none;                 /* Firefox */
  transition: color .15s ease;
}
.dp-faq-item summary::-webkit-details-marker { display: none; }
.dp-faq-item summary:hover { color: var(--dp-orange); }
.dp-faq-item summary:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: -3px; }
.dp-faq-item[open] summary { color: var(--dp-orange); }

/* A chevron that rotates to point up when the answer is open. Drawn with a
   rotated corner so there is no icon font and no second asset, and CSS drives
   both the mark and the state so they cannot disagree. */
.dp-faq-mark {
  position: relative; flex: none;
  width: 14px; height: 14px;
  transition: transform .2s ease;
}
.dp-faq-mark::before {
  content: ''; position: absolute; left: 1px; top: 2px;
  width: 9px; height: 9px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
}
.dp-faq-item[open] .dp-faq-mark { transform: rotate(180deg); }

.dp-faq-item p {
  margin: 0; padding: 0 4px 24px;
  max-width: 72ch;
  font-size: 16px; line-height: 1.7; color: #57504a;
}

@media (max-width: 991px) {
  .dp-proof-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 599px) {
  .dp-proof-grid { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) {
  /* The transition is on the mark itself; the pseudo-elements never had one,
     so the chevron went on rotating. The language pills scale on hover. */
  .dp-faq-mark, .dp-faq-mark::before, .dp-faq-mark::after { transition: none; }
  .dp-lang-pill { transition: none; }
}

/* ---- languages -------------------------------------------------------- */

.dp-langs { text-align: center; }
.dp-langs-title { margin-bottom: .35rem; }
.dp-langs-sub { font-size: 16px; color: var(--dp-muted); margin: 0 0 1.5rem; }

/* Dot field stands in for the world map: no asset to ship, no layout shift,
   and it scales to any width. The flags carry the "global" meaning. */
/* An actual world map behind the chips.
   The dotted texture that used to be here implied a map without being one, so
   the chips read as floating rather than placed. The aspect ratio is the
   jVectorMap viewport's (900 x 440.7) and the SVG fills the box exactly, which
   is what lets a chip's left/top percentage mean the same thing as the map's —
   change one and the other has to change with it.
   Kept as a background-image, not inline: 94 KB of path data belongs in a
   cacheable file, not in the HTML of the most-visited page on the site. */
.dp-langs-map {
  position: relative;
  margin: 0 auto;
  max-width: 1000px;
  aspect-ratio: 900 / 440.70631074413296;
  /* Floor for very narrow viewports, where the ratio alone would collapse it. */
  min-height: 300px;
  background-image: url("../img/world-map.svg");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 100% 100%;
  border-radius: var(--dp-radius);
}


@media (max-width: 767px) {
  /* Below this the scatter collapses to a wrapped list (see .is-unplaced
     handling), so the map would be decoration behind a list — drop it and the
     94 KB request with it. */
  .dp-langs-map {
    aspect-ratio: auto;
    min-height: 0;
    background-image: none;
  }
}

.dp-lang-pill {
  position: absolute;
  transform: translate(-50%, -50%);
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  padding: .42rem .75rem;
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 999px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .07);
  font-size: 16px;
  line-height: 1.2;
  color: var(--dp-text);
  text-decoration: none;
  white-space: nowrap;
  transition: border-color .15s ease, transform .15s ease;
}

.dp-lang-pill:hover,
.dp-lang-pill:focus-visible {
  border-color: var(--dp-orange);
  color: var(--dp-orange);
  text-decoration: none;
  transform: translate(-50%, -50%) scale(1.05);
  z-index: 2;
}

.dp-lang-pill:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: 2px; }
.dp-lang-pill img { width: 20px; height: 20px; border-radius: 3px; flex: none; }

/* A language with no assigned coordinates still has to appear somewhere. */
.dp-lang-pill.is-unplaced { position: static; transform: none; margin: .25rem; }

/* Below 900px the scatter becomes an unreadable pile, so it reflows into a
   plain wrapped list. Same links, same content — only the arrangement changes. */
@media (max-width: 900px) {
  .dp-langs-map {
    min-height: 0;
    background-image: none;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: .5rem;
  }
  .dp-lang-pill,
  .dp-lang-pill.is-unplaced {
    position: static;
    transform: none;
    left: auto !important;
    top: auto !important;
  }
  .dp-lang-pill:hover,
  .dp-lang-pill:focus-visible { transform: scale(1.03); }
}

/* ======================================================================
   Typography — mobile
   ======================================================================
   Mirrors the canonical sitewide steps in custom.css (page title 32→26,
   h2 28→23, h3 23→20) so DocPivot pages step down with the rest of the site
   instead of staying desktop-sized on a phone.

   Body copy stays 16px throughout: it is the site's floor, and inputs below
   16px make iOS Safari zoom on focus.
   ====================================================================== */

@media (max-width: 767px) {
  .dp-hero          { padding: 2.5rem 0 2.25rem; }
  .dp-hero-title    { font-size: 26px; }
  .dp-hero-sub      { font-size: 16px; }
  .dp-section-title { font-size: 23px; }
  .dp-cat-title     { font-size: 20px; }
  .dp-oppanel-title { font-size: 18px; }
  .dp-tool-name     { font-size: 17px; }
}

/* The sub-brand bar that used to live here is gone (2026-08-02). It rendered
   UNDER the full ToolsPivot navbar, which put two brand headings on every
   tool page. The shared navbar is now DocPivot-branded on /doc/* instead —
   see theme/smallseo/docpivot/brand.php — so the bar and its styles were
   redundant, not merely unused. */

/* ======================================================================
   Action chooser
   ======================================================================
   Shown once, right after a file lands on the homepage. Loading the file
   answered "what have I got"; this answers "what do I want" before dropping
   the user into a workspace full of controls with no obvious next step.
   ====================================================================== */

.dp-modal-open { overflow: hidden; }

.dp-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 10050;   /* above the site's floating widgets (the chat panel is 10002) */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background: rgba(35, 32, 31, .55);
  animation: dp-fade-in .15s ease-out;
}

/* 30rem was right for a five-item list. The chooser now offers up to fourteen,
   and at that width they became a single scrolling column with the choices
   below the fold — the dialog asked a question and hid most of the answers.
   Wider plus a grid puts the whole set on screen without scrolling. */
.dp-modal {
  width: 100%;
  /* px, not rem: this theme sets html{font-size:10px}, so "46rem" here read as
     460px and the dialog barely grew. Stated in px so the intent survives the
     root size. */
  max-width: 720px;
  max-height: 88vh;
  max-height: 88dvh;   /* the visible viewport on a phone; the line above is the fallback */
  display: flex;
  flex-direction: column;
  background: #fff;
  border-radius: 18px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .25);
  animation: dp-modal-in .18s ease-out;
}

/* The head stays put and only the choices scroll, so the question and the
   close button remain reachable however many actions there are. */
.dp-modal-body {
  overflow-y: auto;
  padding-bottom: .35rem;
}

@media (prefers-reduced-motion: reduce) {
  .dp-modal, .dp-modal-overlay { animation: none; }
}

@keyframes dp-fade-in  { from { opacity: 0; } }
@keyframes dp-modal-in { from { opacity: 0; transform: translateY(10px) scale(.98); } }

.dp-modal-head {
  position: relative;
  /* px, not rem: the close button is 40px wide at an 8px inset, so the title
     needs a hard 60px of clearance. Keeps a long translated title off the
     button at any of the 18 languages. */
  padding: 15px 60px 8px;
  text-align: center;
}

.dp-modal-title { font-size: 23px; margin: 0 0 .3rem; color: var(--dp-text); }

/* One line, ellipsised, full name in the title attribute.
   Wrapping a long filename ("Monthly SEO Analytics Report - ... July 2026
   (1).pdf") took four lines and made the file the loudest thing in a dialog
   that exists to ask about actions. It is context; it should read as context. */
.dp-modal-sub {
  margin: 0 auto;
  max-width: 480px;   /* px: root is 10px here */
  font-size: 15px;
  color: var(--dp-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dp-modal-close {
  position: absolute;
  inset-block-start: .6rem;
  inset-inline-end: .8rem;
  width: 40px;
  height: 40px;           /* comfortably past the 44px-with-padding target */
  border: 0;
  background: transparent;
  font-size: 26px;
  line-height: 1;
  color: var(--dp-muted);
  cursor: pointer;
  border-radius: 8px;
}

.dp-modal-close:hover { background: var(--dp-grad-a); color: var(--dp-orange); }
.dp-modal-close:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: 2px; }

/* One grid for every action. auto-fit keeps the columns even at any width, and
   because there is a single grid rather than one per category the only row
   that can be short is the last. */
.dp-modal-actions {
  padding: .35rem 1rem 1.1rem;
  display: grid;
  gap: .5rem;
  grid-template-columns: 1fr;
}

/* ---- signature dialog --------------------------------------------------
   Draw / Type / Upload, all three ending as an image item. px throughout for
   the same reason .dp-modal is: html{font-size:10px} makes rem read small. */
.dp-sigmodal { max-width: 700px; }
/* The shared .dp-modal-title carries no padding of its own — it is written to
   sit inside .dp-modal-head, which supplies it. Appended straight into the
   dialog it hugged the top-left corner and collided with the tab row. */
.dp-sigmodal .dp-modal-title { padding: 16px 16px 0; }

.dp-sig-tabs {
  display: flex;
  gap: .4rem;
  padding: 0 16px 12px;
  border-bottom: 1px solid var(--dp-border);
}
.dp-sig-tab.is-active {
  background: var(--dp-grad-a);
  border-color: var(--dp-orange);
  color: var(--dp-orange);
}

.dp-sig-body {
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: flex-start;
}

/* The signing surface. A dashed edge says "draw inside here" without needing a
   label, and crosshair rather than the arrow because this is a drawing area. */
.dp-sig-canvas {
  width: 100%;
  height: 200px;
  border: 1.5px dashed var(--dp-border);
  border-radius: 10px;
  background: #fff;
  cursor: crosshair;
  touch-action: none;          /* or the browser pans the page mid-signature */
}

.dp-sig-input { width: 100%; }

/* Ink and hand pickers. */
.dp-sig-opts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.dp-sig-swatch {
  width: 34px;
  height: 34px;
  padding: 0;
  border: 2px solid var(--dp-border);
  border-radius: 50%;
  cursor: pointer;
}
.dp-sig-swatch.is-active { border-color: var(--dp-orange); box-shadow: 0 0 0 2px var(--dp-grad-a); }
/* Each face previews in its own font — the only honest sample. */
.dp-sig-face {
  min-width: 46px;
  height: 38px;
  padding: 2px 10px;
  font-size: 20px;
  line-height: 1;
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 8px;
  cursor: pointer;
}
.dp-sig-face.is-active { border-color: var(--dp-orange); background: var(--dp-grad-a); color: var(--dp-orange); }
.dp-sig-preview { max-width: 100%; max-height: 200px; }

.dp-sig-foot {
  display: flex;
  justify-content: flex-end;
  gap: .5rem;
  padding: .35rem 16px 16px;
}

/* A link bakes to an annotation and paints nothing, so on screen it needs its
   own affordance: the dashed region plus an input for the target. */
.dp-item-linkinput {
  font-size: 12px;
  color: var(--dp-orange);
}

/* ---- inspector ---------------------------------------------------------
   The editor's properties panel, rendered into the action panel that an
   editor tool otherwise left empty. */
.dp-insp { display: flex; flex-direction: column; gap: 12px; }

/* The text surface's guidance and Apply button, relocated out of the canvas
   into the action panel where this layout keeps every action. */
.dp-te-panel { display: flex; flex-direction: column; gap: 12px; }
.dp-te-panel .dp-te-hint {
  font-size: 14px;
  line-height: 1.6;
  margin: 0;
  color: var(--dp-muted);
}
.dp-te-panel .dp-te-status { font-size: 13px; margin: 0; color: var(--dp-muted); }
/* Lives in the panel foot, above the export bar — see the note in
   workspace.js setSurface. The margin separates the two stacked buttons. */
.dp-te-apply { width: 100%; }
.dp-actionpanel-foot > .dp-te-apply { margin-bottom: 8px; }

/* Bounded like the overlay stage — but on the WRAPPER, never on .dp-te-stage.
   That element is the positioning context for .dp-te-layer (`position:absolute;
   inset:0`), which holds every clickable text run. Putting padding on it
   offset all of them by the padding; putting height+overflow on it made them
   drift away from their words as soon as the user scrolled. Both showed up as
   "the edit box is in the wrong place and the text moves". The scroll box has
   to be an ancestor, so the stage keeps sitting exactly on its canvas. */
/* Three links in one chain, all needed, or `height: 100%` on the card below
   means nothing.

   The body is a grid whose row is sized to CONTENT, so a tall page inside the
   text host stretched the row to ~1010px inside a 533px body; 100% then
   resolved against that row and overflowed rather than fitting. Capping the
   row makes 100% mean what it reads as. `.dp-editor-host` sits between the two
   as the actual grid item and has to pass the height through.

   Scoped to a text surface that is actually on screen: the overlay editor
   shares this host and sizes itself from its content, and constraining it
   there would clip a drawing canvas nobody asked to scroll. */
.dp-body:has(> .dp-editor-host) {
  grid-template-rows: minmax(0, 1fr);
  /* The editor host carries a 14px top margin, which is zeroed below. It is
     NOT re-added here as padding: against a right-hand panel that is one
     continuous card, that 14px read as a dead band between the toolbar and the
     document — the two columns started at the same line and then disagreed
     about where their content began. The document card now sits directly under
     the toolbar card, and the row is the full height, so both columns finish
     level as well as starting level. */
  padding-top: 0;
}
.dp-editor-host {
  height: 100%;
  min-height: 0;
  margin-top: 0;
}
/* The overlay surface nests: .dp-editor > .dp-editor-viewport > stage, with
   the page-nav pill floating inside the viewport. Every link has to pass the
   height down or the stage falls back to sizing itself from the WINDOW
   (min(70dvh, 760px)), which is unrelated to the work block and left the
   document card ending well short of the panel beside it. */
.dp-editor { height: 100%; min-height: 0; }
.dp-editor > .dp-editor-viewport { flex: 1 1 auto; min-height: 0; }
.dp-editor-viewport > .dp-editor-stage { height: 100%; }

.dp-textedit-host {
  display: block;
  /* FILL the work block, do not measure the viewport.
     This was `min(70dvh, 760px)`, which is a height picked from the window and
     therefore unrelated to the 600px block the layout is built on — so the
     document card and the action panel beside it only ever lined up by
     coincidence. On a 900px-tall window it came out 61px short and the two
     columns visibly finished at different heights.
     100% of the body row, which is what the panel opposite also fills. */
  height: 100%;
  /* A floor for a short window, where 100% of the row would be unusable. */
  min-height: 320px;
  overflow: auto;
  overscroll-behavior: contain;
  background: #f4f1ef;
  border: 1px solid var(--dp-border);
  border-radius: var(--dp-radius);
  padding: 16px;
  text-align: center;   /* centres the inline-block stage */
}

/* The active group's tools, in the panel above their options. A grid rather
   than a row: four or seven tools wrap predictably at panel width, and every
   tile keeps the 52px touch height the toolbar uses. */
.dp-insp-tools {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
  gap: 4px;
}
.dp-insp-tools .dp-tbtn { min-width: 0; width: 100%; }
/* Separator between "which tool" and "how it looks" — two different questions. */
.dp-insp-rule {
  height: 1px;
  background: var(--dp-border);
  margin: 2px 0 4px;
}

.dp-insp-title {
  font-size: 16px;
  font-weight: 600;
  margin: 0;
  color: var(--dp-text);
}
.dp-insp-sub {
  font-size: 13px;
  margin: -6px 0 0;
  color: var(--dp-muted);
}
/* The no-selection state. It reads as guidance rather than as a blank panel,
   which is what this space used to be. */
.dp-insp-hint {
  font-size: 14px;
  line-height: 1.6;
  margin: 0;
  color: var(--dp-muted);
}

.dp-insp-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin: 0;
  font-weight: 400;
}
.dp-insp-label { font-size: 14px; color: var(--dp-text); }

/* 44px keeps the swatch and the slider thumb inside the touch-target minimum;
   a 20px colour chip is a miss on a phone. */
.dp-insp-color {
  width: 52px;
  height: 34px;
  min-height: 34px;
  padding: 2px;
  border: 1px solid var(--dp-border);
  border-radius: 8px;
  background: #fff;
  cursor: pointer;
}
.dp-insp-range { width: 150px; height: 24px; cursor: pointer; }
.dp-insp-del { align-self: flex-start; }
.dp-insp-select { width: 150px; }

/* Bold/italic and the alignment set: grouped buttons, because each is one
   decision with a few options rather than several independent switches. */
.dp-insp-seg { display: inline-flex; gap: 4px; }
.dp-insp-segbtn {
  min-width: 38px;
  min-height: 34px;
  padding: 4px 8px;
  font-size: 13px;
}
.dp-insp-segbtn.is-active {
  background: var(--dp-orange);
  border-color: var(--dp-orange);
  color: #fff;
}

@media (min-width: 620px) {
  .dp-modal-actions { grid-template-columns: repeat(auto-fit, minmax(215px, 1fr)); }
}

.dp-action {
  display: flex;
  align-items: center;
  gap: .9rem;
  width: 100%;
  /* 44px floor: without it a one-line text card came out at 42px, just under
     the minimum touch target. Stated as a floor rather than by inflating the
     padding so a two-line label still grows naturally. */
  min-height: 44px;
  padding: .85rem 1rem;
  text-align: start;
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 12px;
  cursor: pointer;
  transition: border-color .12s ease, background .12s ease, transform .12s ease;
}

.dp-action:hover,
.dp-action:focus-visible {
  border-color: var(--dp-orange);
  background: var(--dp-grad-a);
  transform: translateY(-1px);
}

.dp-action:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: 2px; }

.dp-action-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 10px;
  background: var(--dp-grad-b);
  color: var(--dp-orange);
}

.dp-action-text  { display: block; }
.dp-action-label { display: block; font-size: 17px; font-weight: 600; color: var(--dp-text); }
.dp-action-desc  { display: block; font-size: 14px; color: var(--dp-muted); margin-top: .1rem; }

@media (prefers-reduced-motion: reduce) {
  .dp-modal-overlay, .dp-modal { animation: none; }
  .dp-action:hover, .dp-action:focus-visible { transform: none; }
}

/* ======================================================================
   Tool page content
   ======================================================================
   The below-the-fold half of a tool page: what it does, how it works, the
   privacy statement, FAQ, and links into the rest of the silo.
   ====================================================================== */

.dp-content {
  background: linear-gradient(180deg, #fffaf7, var(--dp-grad-a));
  border-top: 1px solid var(--dp-border);
  padding: 3.25rem 0;
}

/* Layout is Bootstrap's col-md-8 + the theme's own sidebar.php (col-md-4), so
   there is no DocPivot grid here on purpose — the sidebar is shared with the
   rest of the site and must stay on the site's own column system.

   custom.css gives #rightCol margin-top:-130px so that on a normal tool page
   the sidebar rises alongside the centred tool heading. DocPivot's sidebar
   starts a fresh section below the workspace, so that pull drags it 130px up
   into the section above and misaligns it with the article. The breakpoint
   rules it has to beat are themselves !important, hence this one. */
.dp-content #rightCol { margin-top: 0 !important; }

@media screen and (max-width: 990px) {
  /* Columns have stacked by here; restore the theme's own stacked spacing. */
  .dp-content #rightCol { margin-top: 20px !important; }
}

/* Matches the sidebar boxes beside it: the same soft grey shadow instead of a
   border, and square corners. The rounded, hairline-bordered card read as a
   different component sitting next to .box_shadow_border rather than the other
   half of the same row. Padding stays generous — the sidebar's 15px is right
   for a widget and too tight for an article. */
.dp-article {
  background: #fff;
  border: 0;
  border-radius: 0;
  box-shadow: 0 0 6px 3px #ededed;
  padding: 2.25rem 2.5rem;
}

.dp-article h2 {
  font-size: 28px;
  margin: 2rem 0 .75rem;
  color: var(--dp-text);
}

.dp-article h2:first-child { margin-top: 0; }

/* h3 was never needed while about_tool only ran on tool pages, whose copy is
   two levels deep. The hub article uses a third level. 23px is the sitewide
   h3 in the canonical scale (32 / 28 / 23 / 16), not a value picked here. */
.dp-article h3 {
  font-size: 23px;
  margin: 1.6rem 0 .6rem;
  color: var(--dp-text);
}

.dp-article p  { font-size: 16px; line-height: 1.7; color: #4a423e; margin: 0 0 .9rem; }
.dp-article ul { margin: 0 0 1.1rem; padding-inline-start: 1.35rem; }
.dp-article li { font-size: 16px; line-height: 1.7; color: #4a423e; margin-bottom: .35rem; }
.dp-article strong { color: var(--dp-text); }

/* Step screenshots in "How ... Works". Full width of the list item so the image
   lines up with its step text; the hairline border keeps the white tool UI in
   the shot from melting into the white article. */
.dp-article .dp-step-img {
  display: block;
  width: 100%;
  height: auto;
  margin: .75rem 0 1.4rem;
  border: 1px solid #e8ded8;
  border-radius: 12px;
}

@media (max-width: 767px) {
  .dp-content { padding: 2.25rem 0; }
  .dp-article { padding: 1.5rem 1.35rem; }
  .dp-article h2 { font-size: 23px; }
  .dp-article h3 { font-size: 19px; }
}

/* A boolean field reads as "[x] label", not as a caption above a lone box.
 *
 * EXPLICIT PLACEMENT, NOT SOURCE ORDER. This was `flex-direction: row-reverse`,
 * which works only while the field holds exactly two children. The moment one
 * got help text it held three, and reversing put the help paragraph FIRST — so
 * the row read "[long explanation] [x] Run OCR first (for scans)" with the
 * label crushed into a narrow column and broken over four lines. A grid with
 * named cells cannot be knocked over by adding a child. */
.dp-oppanel .dp-field-check {
  display: grid;
  grid-template-columns: 16px minmax(0, 1fr);
  align-items: start;
  gap: 6px 10px;
}
.dp-oppanel .dp-field-check input[type="checkbox"] {
  grid-column: 1; grid-row: 1;
  width: 16px; height: 16px; margin: 2px 0 0;
}
.dp-oppanel .dp-field-check label {
  grid-column: 2; grid-row: 1;
  margin: 0;
  font-weight: 600;
}
/* Full width under both, so the explanation is not squeezed into the label's
   column and does not push the checkbox out of line. */
.dp-oppanel .dp-field-check .dp-field-help {
  grid-column: 1 / -1; grid-row: 2;
  margin: 0;
}

/* ---- overlay editor -------------------------------------------------- */

.dp-editor-host { min-width: 0; }
.dp-editor { display: flex; flex-direction: column; gap: 12px; }

/* ---- unified editor toolbar -------------------------------------------
   One container holding three rows: page actions, tool groups, then the
   chosen group's tools. It used to be two separate bordered strips stacked on
   each other, which read as two unrelated toolbars. */
.dp-editor-bar {
  display: contents;   /* the rows join the toolbar's own column flow */
}
/* `.is-ready` is REQUIRED in this selector, not decoration.
   `.dp-toolbar` is display:none until `.dp-toolbar.is-ready` turns it on when
   a file exists. Written as `.dp-toolbar:has(...)` this rule carries the same
   specificity as that one and sits later in the file, so it won its
   `display:flex` unconditionally and put the whole toolbar on screen before
   anything had been loaded — a markup toolbar above an empty drop zone. */
.dp-toolbar.is-ready:has(.dp-editor-bar) {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  padding: 0;
  /* NOT `overflow: hidden`. The rows sit flush against the rounded border at
     padding:0, so hiding the overflow was how their square corners were
     clipped — but it also clipped the Pages dropdown, which opens downward
     out of the toolbar. Only 4px of a 180px menu was visible. The corners are
     rounded on the rows themselves instead, so the menu can escape. */
  overflow: visible;
}
.dp-toolbar.is-ready:has(.dp-editor-bar) > :first-child {
  border-top-left-radius: inherit;
  border-top-right-radius: inherit;
}
.dp-toolbar.is-ready:has(.dp-editor-bar) > :last-child {
  border-bottom-left-radius: inherit;
  border-bottom-right-radius: inherit;
}
.dp-editor-pageactions,
.dp-editor-groups,
.dp-editor-tools {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  align-items: stretch;
  padding: 6px 8px;
}
.dp-editor-pageactions { border-bottom: 1px solid var(--dp-border); }
.dp-editor-groups      { border-bottom: 1px solid var(--dp-border); }
/* That divider separates the page actions from the tool row under them. When
   the tool row is empty there is nothing to separate, and the 1px line landed
   1px above the toolbar card's own bottom border — reading as one doubled,
   slightly dirty edge rather than a divider. */
.dp-toolbar:has(> .dp-editor-bar:empty) .dp-editor-pageactions { border-bottom: 0; }
.dp-editor-tools       { background: linear-gradient(180deg, #fffaf7, var(--dp-grad-a)); }

/* The toolbar button: icon over label, the shape every established PDF editor
   uses. Sized so the whole control clears the 44px touch target even though
   the icon itself is 22px. */
.dp-tbtn {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  /* 54, not 60. Measured: the merged first row came to 749px inside a 726px
     container — over by 23px, so it wrapped and the toolbar was three lines
     again after merging it to two. Six buttons sit at exactly the minimum, so
     trimming it recovers ~36px and the row fits with headroom. Still 52px tall,
     which is what carries the touch target. */
  min-width: 54px;
  min-height: 52px;
  padding: 6px 6px;
  border: 1px solid transparent;
  border-radius: 10px;
  background: transparent;
  color: var(--dp-text);
  font-size: 12px;
  line-height: 1.2;
  cursor: pointer;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
/* `:not(.is-active)` is load-bearing.
   An armed tool is orange with white text. Without this exclusion, hovering it
   repainted the background pale and left the white text behind — white on
   near-white, so the label vanished under the cursor exactly when the user was
   pointing at it. An armed button gets a DARKER orange on hover instead, which
   is the feedback it should have had anyway. */
.dp-tbtn:hover:not(:disabled):not(.is-active) {
  background: var(--dp-grad-a);
  border-color: var(--dp-border);
  color: var(--dp-text);
}
.dp-tbtn.is-active:hover:not(:disabled) { filter: brightness(.92); }

/* Restore prompt. Sits above the page rather than as a modal: it is an offer,
   not a question that must be answered before the tool can be used. */
.dp-restore {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 10px;
  padding: 10px 14px;
  background: var(--dp-grad-a);
  border: 1px solid var(--dp-orange);
  border-radius: var(--dp-radius);
}
.dp-restore-msg { flex: 1 1 260px; font-size: 14px; color: var(--dp-text); }

/* ---- pointer modes -----------------------------------------------------
   The active mode must be obvious without reading the toolbar, because the
   entire complaint class here is "I did not know which mode I was in". The
   cursor says it too. */
.dp-editor-mode.is-active {
  background: var(--dp-grad-a);
  border-color: var(--dp-orange);
  color: var(--dp-orange);
  font-weight: 600;
}
.dp-editor-stage.is-panning { cursor: grab; }
.dp-editor-stage.is-grabbing { cursor: grabbing; }
/* While panning, marks must not swallow the drag or look draggable. */
.dp-editor-stage.is-panning .dp-item { pointer-events: none; cursor: grab; }

/* Eraser: the pointer says "this click removes something". */
.dp-editor-page.is-erasing,
.dp-item-erasable { cursor: crosshair; }
.dp-item-erasable:hover { outline: 2px solid #c0392b; outline-offset: 1px; }

/* ---- viewer: page, floating nav pill, document actions ------------------
   The nav used to be a plain row under the stage: detached from the page it
   controls, and wide enough that "Page 1 of 9" wrapped onto two lines. Now it
   floats inside the viewer, sized by its own content so it cannot wrap. */
.dp-editor-viewport { position: relative; }

/* ---- column heights ----------------------------------------------------
   The grid is `align-items:start`, so each column takes its own content
   height: the viewer is a fixed-height box and the panel is as tall as its
   contents, which left the two cards ending at visibly different points. On
   an editor tool they stretch to a shared height instead, so the panel bottom
   lines up with the bottom of the page view. */
.dp-view-editor .dp-layout { align-items: stretch; }
.dp-view-editor .dp-rail {
  position: static;        /* stretching and sticky cannot both apply */
  max-height: none;
  overflow: visible;
}
.dp-view-editor .dp-actionpanel { flex: 1 1 auto; }
/* The properties list scrolls inside the panel rather than growing it. */
.dp-view-editor .dp-panel-host { overflow-y: auto; }

.dp-editor-nav {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  z-index: 6;
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: nowrap;      /* never wrap — it is a pill, not a row */
  white-space: nowrap;
  padding: 8px 14px;      /* roomier: the old 5/8 made it read as a cramped strip */
  background: rgba(255, 255, 255, .98);
  border: 1px solid var(--dp-border);
  border-radius: 999px;
  box-shadow: 0 8px 26px rgba(0, 0, 0, .18);
  /* No backdrop-filter: behind a 98%-opaque fill the blur was recomputed on
     every scroll frame of the stage and never visible. */
}
/* Pill controls are round and evenly weighted, so the bar reads as one object
   rather than a row of rectangles that happen to sit together. */
.dp-editor-nav .dp-btn {
  min-height: 34px;
  min-width: 34px;
  padding: 4px 10px;
  border-radius: 999px;
  border-color: transparent;
  background: transparent;
}
.dp-editor-nav .dp-btn:hover:not(:disabled) { background: var(--dp-grad-a); }
.dp-editor-nav .dp-btn:disabled { opacity: .35; }
.dp-editor-pageno { white-space: nowrap; font-size: 14px; padding: 0 2px; }
.dp-editor-zoom { min-width: 46px; text-align: center; font-size: 14px; }
/* Hairline dividers rather than gaps, so the groups inside the pill are
   legible without spacing them so far apart the pill grows. */
.dp-editor-navsep {
  width: 1px;
  align-self: stretch;
  margin: 2px 4px;
  background: var(--dp-border);
}

/* ---- nav bar additions ------------------------------------------------- */
.dp-nav-ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 36px;
  padding: 4px 6px;
}
/* Type a page number instead of clicking "next" two hundred times. */
.dp-editor-pagebox {
  width: 52px;
  height: 34px;
  padding: 2px 6px;
  text-align: center;
  font-size: 14px;
  border: 1px solid var(--dp-border);
  border-radius: 8px;
  background: #fff;
  color: var(--dp-text);
}
.dp-editor-pagebox:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: 1px; }

/* ---- find in document --------------------------------------------------- */
.dp-find { display: inline-flex; align-items: center; gap: 6px; }
.dp-find-input { width: 160px; height: 34px; font-size: 14px; }
.dp-find-count { font-size: 13px; color: var(--dp-muted); max-width: 260px; }
/* Match marks sit in the overlay layer, above the page and below the items. */
.dp-find-hit {
  position: absolute;
  background: rgba(255, 214, 0, .45);
  outline: 1px solid rgba(200, 150, 0, .6);
  border-radius: 2px;
  pointer-events: none;
}

/* ---- Pages menu --------------------------------------------------------
   The four page operations behind one labelled control, so the whole toolbar
   can keep its labels and still fit two rows. */
.dp-pageswrap { position: relative; display: inline-flex; }

.dp-pagesmenu {
  position: absolute;
  top: calc(100% + 4px);
  inset-inline-start: 0;
  z-index: 40;
  min-width: 190px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 10px;
  box-shadow: 0 12px 28px rgba(0, 0, 0, .14);
}
/* Inside the menu the items read as a list, not as a row of tiles. */
.dp-pagesmenu-item {
  flex-direction: row;
  justify-content: flex-start;
  gap: 8px;
  min-height: 40px;
  width: 100%;
  text-align: start;
}
.dp-tbtn:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: 2px; }
.dp-tbtn:disabled { opacity: .4; cursor: default; }
.dp-tbtn .dp-ico { flex: none; }
.dp-tbtn-label { white-space: nowrap; }
/* ...except in the inspector's tool grid, where the cell is ~73px and the
   label is a single word that cannot be shortened. "Strikethrough" overflowed
   its button in English; the translated labels are LONGER (German
   "Durchstreichen", Telugu "కొట్టివేత"), so nowrap truncates far more widely
   than it appears to in English. Wrapping to a second line grows every button
   in the row together, which keeps the grid even, and `balance` splits the
   word across lines instead of leaving one character stranded. */
.dp-insp-tools .dp-tbtn-label {
  white-space: normal;
  text-wrap: balance;
  overflow-wrap: anywhere;
}

/* Group = chosen section, tinted. Tool = armed, solid. Two different states
   that must not look alike, since both can be active at the same time. */
/* This rule was byte-identical to `.dp-editor-mode.is-active`, so the bar
   showed two orange "current" markers at once — Select lit as the pointer
   mode and Insert lit as the open section — each answering a different
   question and neither saying which won. A group is a TAB: it says which
   sub-tools the panel lists, not what a click on the page does. It keeps the
   brand tint and gains an underline; the orange border and the solid orange
   fill stay reserved for "this is what a click does". */
.dp-editor-group.is-active {
  position: relative;
  background: var(--dp-grad-a);
  border-color: transparent;
  color: var(--dp-text);
  font-weight: 600;
}
.dp-editor-group.is-active::after {
  content: '';
  position: absolute;
  left: 10px;
  right: 10px;
  bottom: -1px;
  height: 3px;
  border-radius: 3px 3px 0 0;
  background: var(--dp-orange);
}
.dp-editor-tool.is-active {
  background: var(--dp-orange);
  border-color: var(--dp-orange);
  color: #fff;
}
.dp-editor-del { margin-inline-start: auto; }

/* The viewport.
   A 9-page A4 document rendered at 820px is ~10,600px tall, and the stage had
   no bound — so the preview ran down the page, pushed the About section far
   below the fold and made the whole document one long scroll with the toolbar
   left behind at the top. Every established editor bounds this area and
   scrolls INSIDE it; the toolbar, the page nav and the zoom stay put.
   dvh, not vh, so mobile browser chrome does not crop it. */
.dp-editor-stage {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  height: min(70dvh, 760px);
  overflow: auto;
  padding: 16px;
  background: #f4f1ef;
  border: 1px solid var(--dp-border);
  border-top: 0;
  border-radius: 0 0 var(--dp-radius) var(--dp-radius);
  overscroll-behavior: contain;   /* stop the page scrolling on when this ends */
}

/* The page and its item layer must share one coordinate space, so the layer is
   absolutely positioned over a wrapper sized to the rendered canvas. */
.dp-editor-page {
  position: relative;
  background: #fff;
  box-shadow: 0 2px 14px rgba(0,0,0,.14);
  border-radius: 4px;
  overflow: hidden;
  max-width: 100%;
}
.dp-editor-page.is-placing { cursor: crosshair; }
.dp-editor-canvas { display: block; width: 100%; height: auto; }
.dp-editor-layer { position: absolute; inset: 0; }

.dp-item { position: absolute; box-sizing: border-box; cursor: move; }
.dp-item.is-selected { outline: 2px solid var(--dp-orange); outline-offset: 1px; }
.dp-item-svg { position: absolute; inset: 0; pointer-events: none; }

/* Freehand, cross, line and arrow are drawn inside a full-bleed SVG, so their
   container box is the whole page rather than the mark. Left clickable, the
   topmost such mark swallowed every click on blank paper: selecting itself
   instead of deselecting, and with the eraser armed deleting a mark the user
   never pointed at. Only the drawn stroke is a target — `.dp-item-hit` is a
   transparent fat copy of it, wide enough to actually hit. */
.dp-item-path, .dp-item-line { pointer-events: none; }
.dp-item-hit { pointer-events: stroke; cursor: move; }
/* An outline on a full-bleed container frames the entire page instead of the
   mark, so these two say "selected" by glowing along the stroke. */
.dp-item-path.is-selected, .dp-item-line.is-selected,
.dp-item-path.dp-item-erasable:hover, .dp-item-line.dp-item-erasable:hover { outline: none; }
.dp-item-path.is-selected .dp-item-svg,
.dp-item-line.is-selected .dp-item-svg { filter: drop-shadow(0 0 2px var(--dp-orange)); }
.dp-item-path.dp-item-erasable:hover .dp-item-svg,
.dp-item-line.dp-item-erasable:hover .dp-item-svg { filter: drop-shadow(0 0 2px #c0392b); }
.dp-item-img { width: 100%; height: 100%; object-fit: contain; display: block; }

.dp-item-text { width: 100%; height: 100%; border: 0; background: transparent;
                color: inherit; font: inherit; padding: 0; }
.dp-item-text:focus { outline: 1px dashed var(--dp-orange); }

/* Bottom-right grip. 14px is under the 44px touch minimum, so on a coarse
   pointer it grows — resizing a highlight on a phone is otherwise a lottery. */
.dp-item-handle {
  position: absolute; right: -6px; bottom: -6px;
  width: 14px; height: 14px;
  background: #fff; border: 2px solid var(--dp-orange); border-radius: 3px;
  cursor: nwse-resize;
}
@media (pointer: coarse) {
  .dp-item-handle { width: 24px; height: 24px; right: -12px; bottom: -12px; }
}

.dp-editor-nav { display: flex; align-items: center; justify-content: center; gap: .75rem; }
/* On a phone the pill (page box, zoom, read aloud, print) is ~520px and could
   not wrap, so it spilled past both sides of a ~330px viewer and over the
   page. There it may wrap, stays inside the stage, and tightens its gaps. */
@media (max-width: 600px) {
  .dp-editor-nav {
    flex-wrap: wrap;
    white-space: normal;
    max-width: calc(100% - 16px);
    gap: .35rem;
    padding: 6px 10px;
    border-radius: 18px;
  }
}
.dp-editor-pageno { font-size: 15px; color: var(--dp-muted); }

/* ---- image workspace ------------------------------------------------- */

.dp-img-stage { min-width: 0; display: flex; flex-direction: column; gap: 10px; }

/* THE CHECKERBOARD BELONGS TO THE PICTURE, NOT TO THE SURFACE AROUND IT.
   It used to be painted here, on the holder, so it filled the whole letterbox
   area. On a transparent PNG that is genuinely confusing: the picture's own
   transparency and the empty space beside it rendered as the same pattern, so
   there was no telling where the image ended. The holder is now a plain
   surface and the checkerboard sits behind the canvas only. */
.dp-img-holder {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--dp-grad-a);
  border: 1px solid var(--dp-border);
  border-radius: var(--dp-radius);
  padding: 12px;
  min-height: 200px;
}

/* The checkerboard shows through a transparent PNG, so "the background went
   white" stays distinguishable from "the background is transparent" - but only
   within the picture's own bounds. */
.dp-img-canvas {
  max-width: 100%;
  max-height: 62vh;
  height: auto;
  display: block;
  background:
    linear-gradient(45deg, #f2f2f2 25%, transparent 25%, transparent 75%, #f2f2f2 75%) 0 0/20px 20px,
    linear-gradient(45deg, #f2f2f2 25%, #fff 25%, #fff 75%, #f2f2f2 75%) 10px 10px/20px 20px;
  box-shadow: 0 2px 12px rgba(0,0,0,.12);
}

/* Matches .dp-grid-status on the PDF tools: same size, same colour, aligned
   to the start rather than floated in the middle of nowhere. */
.dp-img-meta { margin: 0; font-size: 16px; color: var(--dp-muted); text-align: start; }

/* The image side's rail. `.dp-rail` on the PDF side is sticky with its own
   max-height; here the column is already a fixed work block, so it just needs
   to be a plain full-height container holding the panel and the result. */
.dp-img-rail {
  position: static;
  max-height: none;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.dp-workspace.dp-imgspace.has-pages .dp-img-rail { height: var(--dp-workblock-h); }
.dp-img-rail > .dp-actionpanel,
.dp-img-rail > .dp-result-host { min-height: 0; }
.dp-img-rail > .dp-result-host:empty { display: none; }
/* The result takes the whole block once it is showing, matching the canvas. */
.dp-workspace.dp-imgspace.has-pages .dp-img-rail.is-result > .dp-result-host { height: 100%; }
.dp-workspace.dp-imgspace.has-pages .dp-img-rail .dp-result { height: 100%; }

/* BOTH COLUMNS LEVEL, the settled work-block rule.
   `.dp-workspace.has-pages .dp-actionpanel { height: 600px }` already pins the
   right column; the left one is `.dp-img-stage` on image tools, which that
   rule never named, so the panel stood at 600px next to a canvas of whatever
   height the picture happened to be. The stage now takes the same block and
   the holder absorbs the slack. */
/* ONE BORDERED CARD PER COLUMN, ending level with the panel.
   The stage owns the border; the holder is the checkerboard inside it and the
   size caption is a footer strip within the same card. Previously the caption
   sat outside, so the visible card stopped ~33px short of the panel even
   though the two column boxes measured the same. */
.dp-workspace.dp-imgspace.has-pages .dp-img-stage {
  height: var(--dp-workblock-h);
  min-height: 0;
  gap: 0;
  border: 1px solid var(--dp-border);
  border-radius: var(--dp-radius);
  background: #fff;
  overflow: hidden;
}
.dp-workspace.dp-imgspace.has-pages .dp-img-holder {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  border: 0;
  border-radius: 0;
}
.dp-workspace.dp-imgspace.has-pages .dp-img-meta {
  flex: 0 0 auto;
  padding: 10px 12px;
  border-top: 1px solid var(--dp-border);
  background: #fff;
}
/* Inside a fixed block the picture is bounded by its container, not by the
   viewport: 62vh in a 600px box either overflowed or left a gap. */
.dp-workspace.dp-imgspace.has-pages .dp-img-canvas {
  max-height: 100%;
  width: auto;
  object-fit: contain;
}

/* The collage preview is clickable: tapping two cells swaps the photos in
   them. Only the canvas that actually accepts the click says so. */
.dp-img-canvas.is-cellpick {
  cursor: pointer;
}

/* "Add more photos" sits on the picture card's footer strip, opposite the
   dimensions, so the offer is next to the pictures it adds to.

   NOT called .dp-addmore: that name is already the GRID's dashed add-a-file
   tile, and reusing it handed this button a 200px dashed box. */
.dp-img-meta.has-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
/* THE FOOTER STRIP HOLDS A SENTENCE AND SEVERAL CONTROLS.
   On the blur tool the hint is long enough to wrap, and with no wrapping and no
   gap the text ran straight into the zoom label - "...delete it.Zoom". It wraps
   as a row of items instead, with the sentence free to take a line of its own. */
.dp-img-meta.has-controls { flex-wrap: wrap; row-gap: 8px; }
.dp-img-metatext { flex: 1 1 100%; min-width: 0; }
.dp-img-meta.has-controls > *:not(.dp-img-metatext) { flex: 0 0 auto; }
.dp-stage-addmore {
  flex: 0 0 auto;
  white-space: nowrap;
  /* Apple HIG 44pt / Material 48dp - this is a real target, not a caption. */
  min-height: 44px;
}
.dp-stage-addmore[hidden] { display: none; }

/* The layout cards' diagrams. Drawn from the real layout maths, so the shape
   on the card is the shape the tool builds. Colour carries no meaning here -
   the title states the layout - so this stays a flat neutral fill. */
.dp-card-art {
  display: block;
  width: 100%;
  height: 42px;
  margin-bottom: 8px;
}
.dp-card-art rect { fill: var(--dp-border); }
.dp-card-radio:checked ~ .dp-card-body .dp-card-art rect { fill: var(--dp-orange); }

/* Zoom for the chosen photo, on the picture card's footer beside the
   dimensions. A slider rather than wheel-only: a gesture nobody can reach from
   a keyboard is not a control. */
.dp-cellzoom {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: 0 1 auto;
  min-width: 0;
}
.dp-cellzoom[hidden] { display: none; }

/* Undo / clear for the boxes drawn on the picture, on the same footer strip as
   the dimensions - beside the thing they act on, not across the page in the
   settings rail. */
.dp-regions {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: 0 1 auto;
}
.dp-regions[hidden] { display: none; }
.dp-regions-count { font-size: 15px; color: var(--dp-muted); white-space: nowrap; }
.dp-regions .dp-btn { min-height: 44px; }
/* The picture is a drawing surface, and the cursor is the only thing that says
   so before the first drag. */
.dp-img-holder .dp-img-canvas.is-regiondraw { cursor: crosshair; }
.dp-img-holder.is-drawing .dp-img-canvas { cursor: crosshair; }

/* Zoomed, the picture is bigger than its card, so the card has to scroll. Only
   above 1x - at fit-to-card the suite's normal rules apply untouched. */
.dp-img-holder.is-zoomed { overflow: auto; align-items: flex-start; justify-content: flex-start; }
.dp-viewzoom { display: inline-flex; align-items: center; gap: 8px; flex: 0 1 auto; }
.dp-viewzoom[hidden] { display: none; }
.dp-viewzoom-label { font-size: 15px; color: var(--dp-muted); }
.dp-viewzoom-range { width: 96px; max-width: 22vw; min-height: 44px; }
/* "Find faces" sits with the other picture-card controls. It is the one that
   can take a moment on first use, so it keeps a stable width while its label
   changes to "Looking..." - a button that resizes mid-press reads as broken. */
.dp-facefind { min-height: 44px; min-width: 118px; }
.dp-facefind[hidden] { display: none; }
.dp-cellzoom-label { font-size: 15px; color: var(--dp-muted); }
.dp-cellzoom-range { width: 120px; max-width: 30vw; min-height: 44px; }
/* Says the picture can be moved, and that it is being moved. */
.dp-img-holder .dp-img-canvas.is-cellpick { cursor: grab; }
.dp-img-holder.is-panning .dp-img-canvas.is-cellpick { cursor: grabbing; }

/* The work block is hidden until a picture is loaded, so the page opens on the
   drop zone rather than on an empty 728x600 card. Retargeted from `.dp-body`
   to `.dp-layout` when these tools moved onto the shared shell - the old
   selector matched nothing afterwards, and the empty card came back. */
.dp-imgspace:not(.has-pages) .dp-layout { display: none; }

/* =========================================================================
 * Server tier (Phase 4) — Protect, Unlock, Repair, Optimize for web.
 *
 * Four tools out of thirty-two. Everything here exists because these are the
 * only pages that upload, and the interface has to say so rather than imply
 * the usual "nothing leaves your device".
 * ====================================================================== */

/* The disclosure. Deliberately at the top of the tool, above the file picker,
   and styled as information rather than as a warning: uploading is how the
   tool works, not a hazard the user is being talked into. */
.dp-servernote {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  background: linear-gradient(180deg, var(--dp-grad-a), var(--dp-grad-b));
  border: 1px solid #f6d9c9;
  border-radius: var(--dp-radius);
  padding: 14px 16px;
  margin-bottom: 20px;
}
.dp-servernote svg { flex: 0 0 auto; margin-top: 1px; }
.dp-servernote p {
  margin: 0;
  font-size: 15px;
  line-height: 1.55;
  color: var(--dp-text);
}

/* The chosen file, once picked. */
.dp-chosen {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: var(--dp-radius);
  padding: 12px 16px;
  margin-bottom: 16px;
}
.dp-chosen[hidden] { display: none; }
.dp-chosen-name {
  font-size: 16px;
  font-weight: 600;
  color: var(--dp-text);
  /* A long filename must not push the button off the row or force the page to
     scroll sideways on a phone. */
  overflow-wrap: anywhere;
  min-width: 0;
}

/* Password field: input and reveal button share a row. */
.dp-field-password { position: relative; }
.dp-field-password input { padding-inline-end: 72px; }
.dp-reveal {
  position: absolute;
  inset-inline-end: 6px;
  bottom: 5px;
  /* 44px is the touch-target floor; the visual control is smaller but the hit
     area is not. */
  min-height: 32px;
  padding: 6px 10px;
  border: 0;
  background: transparent;
  /* Muted, not the accent: this is a secondary control sitting inside a field,
     and in the accent colour it outshouted the field and its own label. Muted
     stops at the contrast floor though - the first try, #9a8880, measured
     3.38:1 on white. This is 5.0:1. */
  color: #7b6b63;
  font-size: 14px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  cursor: pointer;
  border-radius: 8px;
  cursor: pointer;
}
.dp-reveal:hover { color: var(--dp-orange); background: rgba(250, 76, 16, .07); }
.dp-reveal:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: 1px; }
/* Pressed = the password is currently visible; keep that legible without
   relying on the glyph alone. */
.dp-reveal[aria-pressed="true"] { color: var(--dp-orange); }
.dp-reveal svg { display: block; }

/* Password strength.
   Scoped to .dp-strength, which existed nowhere before, so no other tool's
   field can pick these up. Only a field declaring `x-strength` builds one. */
/* Sits between the field row and the next label, not inside the row.
   .dp-oppanel-rows is a WRAPPING FLEX ROW, so a plain block here takes only
   its content width; it needs a full-width basis to claim a line the way a
   .dp-field does. */
/* The -4px pulled the track up into the input's bottom border. The field is
   48px now and needs a real gap - but a SMALLER one above than below, or the
   meter sits nearer the next field's label than the box it is measuring and
   reads as belonging to the wrong one. */
.dp-strength { flex: 0 0 100%; margin: 6px 0 18px; }

/* The UA default placeholder grey fails WCAG on white; so did the first
   attempt at fixing it (#8d7d75 measured 3.95:1). This is 5.8:1. */
.dp-field input::placeholder { color: #6f6058; opacity: 1; }

/* A PASSWORD BOX SHOULD LOOK LIKE ONE BEFORE IT IS READ.
   Every tool this is measured against - Smallpdf, iLovePDF - marks the field
   with a lock at its leading edge and starts the placeholder after it, in a box
   with enough height to sit on its own. Ours put bare text against the left
   border and read as a plain text input. Drawn as a background image rather
   than a node so nothing in the DOM or the tab order changes, and scoped to
   .dp-field-password so no other field type grows an icon. */
.dp-field-password input {
  min-height: 48px;
  padding-inline-start: 42px;
  padding-inline-end: 46px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237b6b63' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='4.75' y='10.5' width='14.5' height='9.25' rx='2.2'/%3E%3Cpath d='M8.2 10.5V7.9a3.8 3.8 0 0 1 7.6 0v2.6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: left 14px center;
  background-size: 18px 18px;
}
/* The reveal is anchored to the bottom of the row; a 48px box needs 8px to sit
   on the input's vertical centre. */
.dp-field-password .dp-reveal { bottom: 8px; }
.dp-strength-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 5px;
}
.dp-strength-label {
  font-size: 13px;
  color: var(--dp-muted, #7a6a62);
  /* The verdict is the short half; let the label give up the room in the
     languages where "Password strength" runs long. */
  min-width: 0;
}
.dp-strength-verdict { font-size: 13px; font-weight: 600; white-space: nowrap; }
.dp-strength-bar { display: flex; gap: 4px; }
/* The standing promise line. Quiet: it is reassurance, not an instruction, and
   it must not compete with the field labels above it or the button below. */
.dp-assurance {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin: 18px 0 0;
  padding-top: 14px;
  border-top: 1px solid rgba(0, 0, 0, .06);
  font-size: 13px;
  line-height: 1.5;
  /* 4.6:1 on the panel's peach body - readable, still secondary. */
  color: #6f6058;
}
.dp-assurance svg { flex: none; margin-top: 1px; color: #1f7a4d; }

.dp-strength-bar i {
  flex: 1 1 0;
  height: 4px;
  border-radius: 2px;
  background: #e8ded8;
  transition: background 120ms ease;
}
/* Colour carries the verdict, and so does the word next to it - never colour
   alone. */
.dp-strength[data-tier="weak"]   .dp-strength-verdict { color: #c0392b; }
.dp-strength[data-tier="fair"]   .dp-strength-verdict { color: #b8791b; }
.dp-strength[data-tier="good"]   .dp-strength-verdict { color: #1f7a4d; }
.dp-strength[data-tier="strong"] .dp-strength-verdict { color: #14663f; }
.dp-strength[data-tier="weak"]   .dp-strength-bar i:nth-child(-n+1),
.dp-strength[data-score="1"]     .dp-strength-bar i:nth-child(-n+1) { background: #c0392b; }
.dp-strength[data-tier="fair"]   .dp-strength-bar i:nth-child(-n+2) { background: #d99a2b; }
.dp-strength[data-tier="good"]   .dp-strength-bar i:nth-child(-n+3) { background: #1f7a4d; }
.dp-strength[data-tier="strong"] .dp-strength-bar i:nth-child(-n+4) { background: #14663f; }

/* Progress. */
.dp-jobprogress {
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: var(--dp-radius);
  padding: 24px;
  text-align: center;
}
.dp-jobprogress[hidden] { display: none; }
.dp-jobbar {
  height: 8px;
  background: #f1e8e3;
  border-radius: 999px;
  overflow: hidden;
  margin-bottom: 14px;
}
.dp-jobbar-fill {
  height: 100%;
  width: 0;
  background: var(--dp-orange);
  border-radius: 999px;
  /* Eased, so a jump from 30% to 80% reads as movement rather than a snap.
     Width is animated rather than transform because the bar is 8px tall and
     the reflow cost is nil at this size. */
  transition: width 240ms ease-out;
}
.dp-jobstatus {
  margin: 0 0 14px;
  font-size: 16px;
  color: var(--dp-text);
}

/* Result. */
.dp-jobdone {
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: var(--dp-radius);
  padding: 28px 24px;
  text-align: center;
}
.dp-jobdone[hidden] { display: none; }
.dp-jobdone-msg {
  margin: 0 0 18px;
  font-size: 17px;
  font-weight: 600;
  color: var(--dp-text);
  overflow-wrap: anywhere;
}
.dp-jobdone .dp-btn-ghost { margin-left: 10px; }

.dp-serverspace .dp-panel-host[hidden],
.dp-serverspace .dp-dropzone[hidden],
.dp-serverspace .dp-servernote[hidden] { display: none; }

/* The op panel is the tool's main control here, not a sidebar, so it gets the
   full width rather than the grid layout's narrow column. */
.dp-serverspace .dp-panel-host { max-width: 520px; }

@media (max-width: 575px) {
  /* Stacked, and the button goes full width — a right-aligned "choose a
     different file" next to a wrapped filename ends up somewhere arbitrary. */
  .dp-chosen { flex-direction: column; align-items: stretch; }
  .dp-jobdone .dp-btn-ghost { margin: 10px 0 0; display: block; width: 100%; }
}

/* Compression result. The saving is the answer the user came for, so it is the
   loudest thing on the screen — and when the answer is "we could not help",
   that is said in the same place rather than buried. */
.dp-jobdone-msg.is-warn { color: #8a5a00; }
/* A remark under the result — "3 images were left alone", "this deck has no
   speaker notes".

   THE TOP MARGIN WAS -10px. That pulled the note up over whatever preceded it,
   which was harmless only while it was appended dead last, below everything.
   Placed properly — under the download buttons, above "Do more with this file"
   — it printed on top of Start over. A negative margin that only works in one
   position is a hack waiting for the next caller; this is a real gap. */
.dp-jobdone-note {
  margin: 0 0 16px;
  font-size: 14px;
  color: var(--dp-muted);
}

/* Result-card remarks as short lines, each with the option panels' own info
   icon holding the full sentence (ui/serverworkspace.js resultFact). Same type
   as .dp-jobdone-note; one line each, so four of them cost what one paragraph
   did. */
.dp-jobdone-facts {
  list-style: none;
  margin: 12px 0 16px;
  padding: 0;
  display: grid;
  gap: 6px;
}
/* The icon is placed inside the text it explains (it is named by it), so the
   pair is laid out here: same 6px the option panel's labels use. */
.dp-jobdone-fact-text {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.dp-jobdone-fact {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-size: 14px;
  line-height: 1.35;
  color: var(--dp-muted);
}

/* Helper text under a field. Opt-in via x-help, so it appears only where a
   rule genuinely needs stating. */
.dp-field-help {
  margin: 4px 0 0;
  font-size: 13px;
  line-height: 1.4;
  color: var(--dp-muted);
}

/* Number + unit read as one control, because they are one answer. */
.dp-serverspace .dp-oppanel-rows { display: flex; flex-wrap: wrap; gap: 0 14px; }
.dp-serverspace .dp-field { flex: 1 1 100%; min-width: 0; }
.dp-serverspace .dp-field[data-field="size"] { flex: 2 1 0; }
.dp-serverspace .dp-field[data-field="unit"] { flex: 1 1 0; }

/* The compressor's two size limits share one row.
 *
 * They are one decision — a box to fit inside — and stacking them cost ~85px
 * in a rail that has exactly 600px and is not allowed to scroll. That shortfall
 * is what pushed "Shrink when the image is bigger than" out of sight; pairing
 * them buys the room back so every control stays visible at once.
 *
 * Scoped to the two fields by name rather than to nth-child, so adding or
 * reordering a setting later cannot silently pair the wrong pair. */
/* ---------------------------------------------------------------------------
 * The batch tools' settings panel.
 *
 * THE TINT BELONGS TO THE PANEL, NOT TO A BOX INSIDE IT.
 * The shared `.dp-oppanel` paints its own gradient card, which inside the
 * white action panel read as a second, smaller panel floating in the first —
 * two nested rounded boxes saying the same thing. Here the tint moves out to
 * the panel body so the whole rail is one surface, and the inner card stops
 * drawing itself.
 * ------------------------------------------------------------------------ */
/* GLOBAL, not per-workspace.
 *
 * This was written scoped to .dp-batchspace, so only the image tools got it and
 * every other panel kept the nested-card look this rule exists to remove — the
 * exact thing it was meant to settle once. The tint belongs to the PANEL; the
 * inner form stops drawing a second card inside the first. */
.dp-actionpanel-body {
  background: linear-gradient(180deg, var(--dp-grad-a), var(--dp-grad-b));
}
.dp-oppanel,
.dp-pagesetup {
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
}

/* ---------------------------------------------------------------------------
 * OPTION TILES
 *
 * For a small set of choices that change what the OUTPUT looks like. A select
 * hides two of three answers behind a click and leaves the panel looking
 * emptier than it is; the rail has the room, and the choice is easier to make
 * when you can see it. A radio underneath, so keyboard and screen readers work
 * without re-implementing either.
 * ------------------------------------------------------------------------ */
.dp-tiles {
  display: grid;
  /* 7px, not 8: three tiles plus two gaps have to clear a ~280px rail. */
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 7px;
}
.dp-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  padding: 8px 5px 7px;
  border: 1.5px solid var(--dp-border);
  border-radius: 10px;
  background: #fff;
  color: var(--dp-muted);
  cursor: pointer;
  text-align: center;
  transition: border-color .15s ease, color .15s ease, background .15s ease;
}
/* The radio itself is invisible but still focusable and still the thing being
   clicked — never `display: none`, which removes it from the tab order. */
.dp-tile input {
  position: absolute;
  opacity: 0;
  width: 0; height: 0;
  margin: 0;
}
.dp-tile:hover { border-color: var(--dp-orange); color: var(--dp-text); }
.dp-tile.is-on {
  border-color: var(--dp-orange);
  color: var(--dp-orange);
  background: var(--dp-grad-a);
}
/* Focus lands on the hidden radio, so the ring has to be drawn by the label. */
.dp-tile:focus-within { outline: 2px solid var(--dp-orange); outline-offset: 2px; }
.dp-tile-art { display: flex; line-height: 0; }
.dp-tile-label { font-size: 12px; font-weight: 600; line-height: 1.25; }

/* Second line on a tile — the DPI number under "Good". Quieter and lighter
   than the label so the tile still reads as one word at a glance, with the
   number available when the user is matching a stated requirement.
   `gap` on .dp-tile already spaces it; no margin here. */
.dp-tile-sub {
  font-size: 11px;
  font-weight: 500;
  line-height: 1.2;
  color: var(--dp-muted);
  margin-top: -2px;
}
.dp-tile.is-on .dp-tile-sub { color: inherit; opacity: .8; }

/* PDF to Excel carries FOUR choices — format, content, OCR, layout — where
   other panels have two or three, and the panel must not scroll. Its tiles
   hold one short word each, so they lose the height the picture-heavy tiles
   need without losing anything readable. Measured: 4 rows at 99px overflowed
   by 48px; at 82px they fit with room. Scoped to this panel so no other
   tile row changes. */
.dp-sheetset .dp-tile { padding: 6px 6px; gap: 2px; }
.dp-sheetset .dp-tile-art svg { width: 18px; height: 18px; }
.dp-sheetset .dp-tile-label { font-size: 11.5px; }
.dp-sheetset .dp-field > label { margin-bottom: 0; font-size: 13px; }
.dp-sheetset .dp-oppanel-rows { gap: 7px; }
/* The OCR notice is the tallest thing that can appear here and it appears
   LAST, so any shortfall clips exactly the sentence that must be read. Sized
   for that worst case with headroom, not for the state that happens to fit:
   at 86px rows there were only 16px to spare, and a longer translation or
   different font metrics would eat that. */
.dp-sheetset .dp-field-help { font-size: 12px; line-height: 1.35; margin-top: 2px; }

/* A checkbox row inside a settings panel: label beside the box, in its own
   bordered group so it reads as a switch rather than a stray line of text. */
.dp-field-check > label {
  display: flex;
  align-items: center;
  gap: 9px;
  background: rgba(255, 255, 255, .65);
  border: 1px solid var(--dp-border);
  border-radius: 10px;
  padding: 9px 11px;
  cursor: pointer;
  font-weight: 500;
}
/* A branded checkbox.
 *
 * The native control renders in the OS accent colour — bright blue on macOS —
 * which is the one colour on the page that belongs to nothing else in the
 * design. `accent-color` recolours it in one line while leaving it a real
 * checkbox: still focusable, still keyboard-operable, still announced
 * correctly. Custom-drawn checkboxes give up all three for the same result. */
.dp-field-check input[type="checkbox"] {
  flex: 0 0 auto;
  width: 17px;
  height: 17px;
  accent-color: var(--dp-orange);
  cursor: pointer;
}
.dp-field-check > label:hover { border-color: var(--dp-orange); }
.dp-field-check > label:focus-within { outline: 2px solid var(--dp-orange); outline-offset: 2px; }
/* One line: "Merge all images into one PDF" wrapped to two and made this row
   taller than the tile rows above it. */
.dp-field-check span { font-size: 13.5px; line-height: 1.3; }
/* The panel already names the tool in its own header; repeating it immediately
   below was the same words twice in 40px. */
.dp-batchspace .dp-oppanel-title { display: none; }

.dp-batchspace .dp-oppanel-rows { grid-template-columns: 1fr 1fr; gap: .9rem; }
.dp-batchspace .dp-oppanel-rows > .dp-field { grid-column: 1 / -1; }
/* Target size and its unit are one control split in two, the same way
   compress-pdf pairs them. */
.dp-batchspace .dp-oppanel-rows > .dp-field[data-field="targetSize"] { grid-column: 1 / 2; }
.dp-batchspace .dp-oppanel-rows > .dp-field[data-field="targetUnit"] { grid-column: 2 / -1; }

/* THE RESIZER PAIRS FOUR MORE, BECAUSE ITS PANEL MUST STILL NOT SCROLL.
 *
 * Nine controls stacked came to 713px against a 451px body — a settings rail
 * with its own scrollbar, which is the thing this suite does not do. Paired,
 * the same nine fit: each pair is two halves of one question anyway (how wide
 * by how tall, which platform at which size, what format under what size),
 * so reading them side by side is also the clearer arrangement.
 *
 * Scoped to the op, not to .dp-batchspace: every other batch tool keeps the
 * full-width default above. */
.dp-oppanel-resizeImage .dp-oppanel-rows {
  /* 8px between rows, 12px between the two columns — on the 4/8 rhythm the
     rest of the suite uses. The inherited .9rem was 14.4px, an off-grid number
     that sat unevenly against the 8px padding inside every control, and nine
     controls at that spacing did not fit the 451px body. */
  gap: 8px 12px;
  /* Cells top-align. Stretched, a short left cell grew to match a taller right
     one and its dropdown floated below its own label — which is why "Save
     image as" sat lower than the box beside it. */
  align-items: start;
}
/* WIDTH AND HEIGHT PAIR; NOTHING ELSE DOES.
 *
 * The other pairs were there to buy vertical space, and once the controls were
 * tightened they were spending width to save height the panel already had.
 * Stacked full width, every label has the whole rail to say what it means —
 * which is what stops a longer translation wrapping — and each control reads
 * as its own decision.
 *
 * These two stay side by side because they ARE one decision, written the way
 * a size is written everywhere: 1080 x 1080, left to right. */
.dp-oppanel-resizeImage .dp-oppanel-rows > .dp-field[data-field="width"] {
  grid-column: 1 / 2;
}
.dp-oppanel-resizeImage .dp-oppanel-rows > .dp-field[data-field="height"] {
  grid-column: 2 / -1;
}

/* THE SOCIAL TAB IS THE ONE THAT CANNOT AFFORD IT.
 *
 * "By size" shows five controls and "Percentage" four, so both stack full
 * width with room left over — measured, not assumed. Social shows NINE, and
 * stacked they came to 587px against a 451px body: 136px of scrollbar in the
 * one panel that must not have one.
 *
 * So the pairs live only where the crowding is, selected by the tab that is
 * actually on screen rather than by a class nothing sets. Each pair is two
 * halves of one question — which network at which size, what format under what
 * ceiling — so it survives being read side by side. */
.dp-oppanel-resizeImage .dp-oppanel-rows:has(> .dp-field[data-field="platform"]:not([hidden])) > .dp-field[data-field="platform"],
.dp-oppanel-resizeImage .dp-oppanel-rows:has(> .dp-field[data-field="platform"]:not([hidden])) > .dp-field[data-field="exportFormat"] {
  grid-column: 1 / 2;
}
.dp-oppanel-resizeImage .dp-oppanel-rows:has(> .dp-field[data-field="platform"]:not([hidden])) > .dp-field[data-field="preset"],
.dp-oppanel-resizeImage .dp-oppanel-rows:has(> .dp-field[data-field="platform"]:not([hidden])) > .dp-field[data-field="exportTargetKb"] {
  grid-column: 2 / -1;
}

/* THE LOCK IS A SENTENCE, NOT A HALF-COLUMN.
 *
 * Paired with the fill dropdown it had 113px to say "Lock aspect ratio",
 * so it broke over two lines, and its checkbox — which sits OUTSIDE the
 * white pill by this panel's own markup — ended up level with nothing.
 * Full width, it reads as one line and lines up with the boxes above it. */
.dp-oppanel-resizeImage .dp-oppanel-rows > .dp-field[data-field="keepAspect"] {
  grid-column: 1 / -1;
  align-items: center;
}

/* EXPORT IS A SECOND QUESTION, AND IT SHOULD LOOK LIKE ONE.
 *
 * Everything above answers "what size"; these two answer "written how". Run
 * together they were one undifferentiated stack of nine controls — the reason
 * the panel read as cluttered rather than as two short decisions. A rule
 * across the pair separates them without spending a heading's worth of height,
 * which the 451px body does not have. */
.dp-oppanel-resizeImage .dp-oppanel-rows > .dp-field[data-field="exportFormat"],
.dp-oppanel-resizeImage .dp-oppanel-rows > .dp-field[data-field="exportTargetKb"] {
  border-top: 1px solid var(--dp-border);
  padding-top: 8px;
}
/* ROOM FOR LANGUAGES THAT ARE NOT ENGLISH.
 *
 * The panel fitted its 451px body EXACTLY in English, which is another way of
 * saying it fitted in one language. Spanish overflowed it by 20px on the first
 * try — "Guardar imagen como" wraps to two lines where "Save image as" does
 * not — and German and Russian are longer again.
 *
 * So the nine controls are tightened to leave headroom rather than filled to
 * the brim: a label that wraps must have somewhere to wrap INTO. Half a line
 * off each label and 2px off each control buys about 40px, which is two
 * wrapped labels' worth. Sizes stay on the 4/8 rhythm. */
.dp-oppanel-resizeImage .dp-field > label {
  margin-bottom: 3px;
  line-height: 1.25;
}
.dp-oppanel-resizeImage .dp-field select,
.dp-oppanel-resizeImage .dp-field input[type="number"] {
  padding-top: 8px;
  padding-bottom: 8px;
}
/* The lock's pill carries the same trim, or it stays the tallest thing in a
   panel where it says the least. */
.dp-oppanel-resizeImage .dp-field-check > label { padding: 7px 11px; }

/* Labels: a touch smaller and firmer than the shared muted style, because in a
   280px rail they are scanned rather than read. */
.dp-batchspace .dp-field > label {
  font-size: 14px;
  font-weight: 600;
  color: var(--dp-text);
  letter-spacing: .01em;
}
.dp-batchspace .dp-field select,
.dp-batchspace .dp-field input[type="number"] {
  background: #fff;
  border-radius: 10px;
  padding: 9px 11px;
}

/* The two "also create" switches are one decision, so they sit together in a
   bordered group with a heading rather than as two loose checkboxes. */
.dp-batchspace .dp-field[data-field="alsoWebp"],
.dp-batchspace .dp-field[data-field="alsoAvif"] {
  grid-column: 1 / -1;
  background: rgba(255, 255, 255, .65);
  border: 1px solid var(--dp-border);
  padding: 8px 11px;
}
.dp-batchspace .dp-field[data-field="alsoWebp"] { border-radius: 10px 10px 0 0; border-bottom: 0; }
.dp-batchspace .dp-field[data-field="alsoAvif"] { border-radius: 0 0 10px 10px; }
.dp-batchspace .dp-field[data-field="keepExif"] {
  background: rgba(255, 255, 255, .65);
  border: 1px solid var(--dp-border);
  border-radius: 10px;
  padding: 8px 11px;
}
/* THE ROW IS THE BOX, SO THE LABEL MUST NOT ALSO BE ONE.
 *
 * `.dp-field-check > label` gives every checkbox label its own white pill —
 * border, background, radius, padding — which is right where the label is the
 * whole control. It is wrong here, because the three rules above already put
 * that exact treatment on the ROW. Both applied at once produced a bordered
 * pill inside a bordered box, with the checkbox stranded out in the outer
 * box's padding and a strip of dead space to the right of the pill where the
 * label stopped short of the row.
 *
 * Scoped to .dp-batchspace, which is where those row borders are declared.
 * The page-setup checkbox ("Merge into one PDF") keeps its own pill, because
 * there the label IS the box and nothing wraps it. */
.dp-batchspace .dp-field[data-field="alsoWebp"] > label,
.dp-batchspace .dp-field[data-field="alsoAvif"] > label,
.dp-batchspace .dp-field[data-field="keepExif"] > label {
  font-weight: 500;
  font-size: 14px;
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
  /* Fill the row so the hit area runs the full width — the label already
     carries `for`, so the whole line toggles the box rather than just the
     text it happens to contain. */
  width: 100%;
}
.dp-batchspace .dp-field[data-field="alsoWebp"] > label:hover,
.dp-batchspace .dp-field[data-field="alsoAvif"] > label:hover,
.dp-batchspace .dp-field[data-field="keepExif"] > label:hover { border-color: transparent; }
/* The focus ring belongs on the row now that the row is the visible box. */
.dp-batchspace .dp-field[data-field="alsoWebp"] > label:focus-within,
.dp-batchspace .dp-field[data-field="alsoAvif"] > label:focus-within,
.dp-batchspace .dp-field[data-field="keepExif"] > label:focus-within { outline: 0; }
.dp-batchspace .dp-field[data-field="alsoWebp"]:focus-within,
.dp-batchspace .dp-field[data-field="alsoAvif"]:focus-within,
.dp-batchspace .dp-field[data-field="keepExif"]:focus-within {
  outline: 2px solid var(--dp-orange);
  outline-offset: 2px;
}

@media (pointer: coarse) {
  /* .dp-reveal is not a .dp-btn, so the suite-wide 44px floor above does not
     reach it. It sits inside the password field, where a mis-tap either
     reveals a password the user did not want shown or lands in the input. */
  /* 84px of right padding was reserved for the words "Show"/"Hide". The
     control is a 19px glyph now, so the box kept a thumb's width of empty
     space at its end. The hit area stays 44x44; only the reservation shrinks. */
  .dp-reveal { min-height: 44px; min-width: 44px; padding-left: 0; padding-right: 0; }
  .dp-field-password input { padding-inline-end: 52px; }
}

@media (prefers-reduced-motion: reduce) {
  .dp-jobbar-fill { transition: none; }
}

/* =========================================================================
 * Chain workspace (Phase 6) — /doc/pdf-workflow
 *
 * The page where the architecture becomes visible: several ops stacked against
 * one document. The step list has to read as an ordered, editable plan, not as
 * a log — the user must be able to see and change what will happen BEFORE
 * committing to it.
 * ====================================================================== */

.dp-chain-side { min-width: 0; }

/* The shared .dp-body rule adds a second column only while a .dp-panel-host is
   `is-ready`. On a normal tool page that host is the whole right-hand column,
   so it is always ready and the layout is stable. Here the panel host is the
   STEP FORM, which opens and closes as the user adds steps — so inheriting that
   rule made the page jump between one and two columns on every add. The chain
   page pins its own columns instead. */
@media (min-width: 900px) {
  .dp-chainspace .dp-body,
  .dp-chainspace .dp-body:has(.dp-panel-host.is-ready) {
    grid-template-columns: 1fr 380px;
  }
}

/* NO CARD INSIDE A CARD.
   This drew its own peach gradient and border, which was right when it was a
   698px column of its own. Inside the action panel the rail ALREADY paints that
   gradient, so the workflow rendered as a second panel nested in the first -
   two borders, two backgrounds, one list. It is plain content in the rail now. */
.dp-chain {
  background: none;
  border: 0;
  padding: 0;
  border-radius: 0;
  box-shadow: none;
}
.dp-chain-title { font-size: 18px; margin: 0 0 12px; color: var(--dp-text); }

.dp-chain-steps {
  list-style: none;
  margin: 0 0 12px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.dp-chain-steps:empty { margin: 0; }

/* THREE COLUMNS, NOT ONE FLEX LINE.
   The old row was number + text + four 30px buttons on a single line. In a
   340px rail that left the name about 128px, so "Watermark" rendered as
   "Waterma..." - the one word in the row that has to be readable was the first
   thing cut. The controls are a 2x2 block now, which costs 60px instead of
   132px and hands the name back ~200px. minmax(0,1fr) on the middle column
   because a grid item's default min-width is auto and would refuse to shrink. */
.dp-chain-step {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 10px;
  padding: 10px 12px;
}

/* The number IS the meaning here — these run in order. */
.dp-chain-num {
  flex: 0 0 24px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--dp-orange);
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}

.dp-chain-stepbody { flex: 1 1 auto; min-width: 0; }
.dp-chain-stepname { display: block; font-size: 15px; font-weight: 600; color: var(--dp-text); }
.dp-chain-stepsum {
  display: block;
  font-size: 13px;
  color: var(--dp-muted);
  overflow-wrap: anywhere;
}

/* 2x2 rather than 1x4: same four controls, less than half the width. */
.dp-chain-stepctl {
  display: grid;
  grid-template-columns: repeat(2, 28px);
  gap: 4px;
  flex: 0 0 auto;
}
.dp-chain-stepctl .dp-iconbtn { width: 28px; height: 28px; }

/* BUILT FOR A 698px RAIL, NOW LIVING IN 340px.
   The row is number + text + four 30px buttons. In the old wide column the
   name and its summary sat on one line; in the standard rail they had about
   128px and wrapped to three, so two steps alone ran to 249px and pushed the
   panel 330px past what it can show. A flex child's default min-width is auto,
   so the text refused to shrink - it has to be told it may, and then told to
   ellipsis rather than wrap. */
.dp-chain-stepbody { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
/* The NAME says what the step is and must survive; it may take a second line
   before it is ever cut. The summary is a convenience and clips at one. */
.dp-chain-stepname {
  display: block;
  font-weight: 600;
  line-height: 1.25;
  overflow-wrap: anywhere;
}
.dp-chain-stepsum {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dp-iconbtn {
  width: 30px;
  height: 30px;
  border: 1px solid var(--dp-border);
  background: #fff;
  border-radius: 8px;
  color: var(--dp-text);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.dp-iconbtn:hover:not(:disabled) { border-color: var(--dp-orange); color: var(--dp-orange); }
.dp-iconbtn:disabled { opacity: .35; cursor: default; }
.dp-iconbtn-danger:hover:not(:disabled) { border-color: #b3261e; color: #b3261e; }
.dp-iconbtn:focus-visible { outline: 3px solid var(--dp-orange); outline-offset: 2px; }

/* Outcome of a run, marked per step rather than as one banner — on a failure
   the useful information is WHICH step, and how far it got. */
.dp-chain-step.is-done   { border-color: #b7d9b7; }
.dp-chain-step.is-done .dp-chain-num { background: #3f8f3f; }
.dp-chain-step.is-failed { border-color: #b3261e; background: #fdf3f2; }
.dp-chain-step.is-failed .dp-chain-num { background: #b3261e; }

.dp-chain-empty { margin: 0 0 12px; font-size: 14px; color: var(--dp-muted); }

/* A DRAFT, NOT AN ANSWER.
   A step the assistant proposed used to render identically to one the visitor
   built, so there was nothing to say "this was chosen for you, look at it". The
   dashed edge and the label mark it as pending review; both go the moment the
   step is opened and saved, because that IS the review. */
.dp-chain-step.is-suggested {
  border-style: dashed;
  border-color: var(--dp-orange);
  background: var(--dp-grad-a, #fff7f3);
}
.dp-chain-suggested {
  display: block;
  margin-top: 2px;
  font-size: 12px;
  font-weight: 600;
  color: #a8410f;
}

/* Two steps that each look right but collide on the page. Warning colour, not
   the suggested-step orange: nothing here is a draft, the settings are simply
   going to land on top of each other. */
.dp-chain-clash {
  display: block;
  margin-top: 2px;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.4;
  color: #8a5300;
}

/* The assistant's reason for the plan. Sits under the box it came from. */
.dp-ai-why {
  margin: 6px 0 0;
  font-size: 14px;
  line-height: 1.5;
  color: var(--dp-text);
}
.dp-ai-why[hidden] { display: none; }

.dp-chain-add { margin-bottom: 12px; }
.dp-chain-select {
  width: 100%;
  min-height: 44px;
  padding: 8px 10px;
  font-size: 15px;
  border: 1px solid var(--dp-border);
  border-radius: 8px;
  background: #fff;
  color: var(--dp-text);
}

/* The step's settings form, shown only while a step is being configured. */
.dp-chain-panel { margin-bottom: 12px; }

.dp-chain-run { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.dp-chain-status { font-size: 14px; color: var(--dp-muted); }

@media (max-width: 991px) {
  /* The workflow goes under the pages rather than beside them; a 300px column
     next to a page grid on a phone is unusable for both. */
  .dp-chainspace .dp-body { display: block; }
  .dp-chain-side { margin-top: 16px; }
}

@media (pointer: coarse) {
  .dp-iconbtn { width: 44px; height: 44px; }
}

/* ---- batch mode (Phase 6) — /doc/batch-pdf ----------------------------
 * The page grid is replaced by a file list: rendering thumbnails for fifty
 * documents would decode fifty PDFs to show the user something they already
 * know. What they need is which file is which, and what happened to each.
 */
.dp-batch-files {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 520px;
  /* Fifty files must not push the workflow panel off the screen. */
  overflow-y: auto;
}

.dp-batch-file {
  display: flex;
  align-items: center;
  gap: 10px;
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 10px;
  padding: 9px 12px;
}
.dp-batch-name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 15px;
  color: var(--dp-text);
  overflow-wrap: anywhere;
}
.dp-batch-size   { flex: 0 0 auto; font-size: 13px; color: var(--dp-muted); }
.dp-batch-status { flex: 0 0 auto; font-size: 13px; color: var(--dp-muted); }

/* Per-file outcome. With fifty inputs a single summary line is not enough —
   the user needs to see WHICH ones failed. */
.dp-batch-file.is-done   { border-color: #b7d9b7; }
.dp-batch-file.is-done   .dp-batch-status { color: #3f8f3f; }
.dp-batch-file.is-failed { border-color: #b3261e; background: #fdf3f2; }
.dp-batch-file.is-failed .dp-batch-status { color: #b3261e; }

.dp-notice.is-warn { color: #8a5a00; }

/* ---- the command bar (Phase 9) ----------------------------------------
 * The assistant fills the same step list a person fills by hand, so it needs
 * no result UI of its own — only an input, an honest disclosure, and a status
 * line. The disclosure is styled as ordinary text rather than a warning: this
 * is how the feature works, not a hazard being disclaimed.
 */
.dp-ai { margin-bottom: 14px; }
.dp-ai[hidden] { display: none; }

.dp-ai-form { display: flex; gap: 8px; }
.dp-ai-input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 44px;
  padding: 8px 12px;
  font-size: 15px;
  border: 1px solid var(--dp-border);
  border-radius: 8px;
  background: #fff;
  color: var(--dp-text);
}
.dp-ai-input:focus-visible { outline: 3px solid var(--dp-orange); outline-offset: 1px; }
.dp-ai-form .dp-btn { flex: 0 0 auto; }

.dp-ai-note {
  margin: 8px 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--dp-muted);
}
.dp-ai-status {
  margin: 8px 0 0;
  font-size: 14px;
  color: var(--dp-text);
}
.dp-ai-status:empty { display: none; }

@media (max-width: 575px) {
  /* A 44px button beside an input leaves the input too narrow to read what you
     typed; stacking keeps both usable. */
  .dp-ai-form { flex-direction: column; }
  .dp-ai-form .dp-btn { width: 100%; }
}

/* ---- Editor T2: text editing (Phase 10) --------------------------------
 * A rendered page with a clickable box over every run of existing text. The
 * boxes are transparent until hovered — the user is looking at their document,
 * not at a grid of controls, and a page covered in visible outlines is
 * unreadable.
 */
.dp-te-hint {
  margin: 0 0 14px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--dp-muted);
  background: linear-gradient(180deg, var(--dp-grad-a), var(--dp-grad-b));
  border: 1px solid #f6d9c9;
  border-radius: var(--dp-radius);
  padding: 12px 14px;
}

.dp-te-stage {
  position: relative;
  display: inline-block;
  max-width: 100%;
  border: 1px solid var(--dp-border);
  border-radius: 10px;
  overflow: hidden;
  background: #fff;
}
.dp-te-canvas canvas { display: block; max-width: 100%; height: auto; }

/* The overlay sits exactly on the canvas; pointer-events pass through the
   layer itself so only the boxes are clickable. */
.dp-te-layer { position: absolute; inset: 0; pointer-events: none; }

.dp-te-run {
  position: absolute;
  margin: 0;
  padding: 0 1px;
  border: 1px dashed transparent;
  border-radius: 3px;
  background: transparent;
  color: transparent;              /* the canvas underneath IS the text */
  font: inherit;
  text-align: start;
  white-space: nowrap;
  overflow: hidden;
  cursor: text;
  pointer-events: auto;
}
.dp-te-run:hover { border-color: var(--dp-orange); background: rgba(250, 76, 16, .08); }
.dp-te-run:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: 1px; }

/* An edited run is marked, because the canvas underneath still shows the OLD
   text until export — without this the user cannot see what they changed. */
.dp-te-run.is-edited {
  border-color: #3f8f3f;
  border-style: solid;
  /* Fallback only. The real colour is set inline per run from the background
     sampled at that line, because white is a guess: on a scan or a tinted page
     the export paints the page's own colour and a white preview box would
     promise a result the download does not deliver. */
  background: #fff;
  color: var(--dp-text);
  /* The base rule clips with overflow:hidden, which is right for an UNedited
     run (its text is transparent — the canvas is the text, and the box is just
     a hit target). On an edited run the box IS the text, so clipping hides the
     replacement: typing something longer than the original showed only its
     tail, which read as "my change did not apply" until the file was
     downloaded. It must also sit above its neighbours, or the next run's box
     covers the part that now extends past the original width. */
  overflow: visible;
  white-space: pre;
  z-index: 1;
}

/* The font-substitution warning. Amber, not red: nothing has gone wrong and
   nothing is blocked - it is a limit of the PDF format being stated in time to
   act on. Red would read as an error and stop people editing at all. */
.dp-te-fontnote {
  margin: 8px 0 0;
  padding: 8px 10px;
  border-inline-start: 3px solid #d98324;
  border-radius: 4px;
  background: #fdf6ec;
  color: #6b4a1b;
  font-size: 13px;
  line-height: 1.45;
}

/* The way out of a scanned page: the tools that CAN help, as buttons rather
   than as tool names buried in a sentence. */
.dp-te-scanhelp {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 10px 0 0;
}
.dp-te-scanlink {
  display: block;
  padding: 9px 12px;
  border: 1px solid var(--dp-line, #e6d5cb);
  border-radius: 8px;
  background: #fff;
  color: var(--dp-text);
  font-size: 14px;
  text-align: center;
  text-decoration: none;
}
.dp-te-scanlink:hover {
  border-color: var(--dp-orange);
  color: var(--dp-orange);
}

/* ---- the text style card -----------------------------------------------
 * Font, size, colour and weight for the line being edited. A white inner card
 * inside the action panel, like every other grouped control in this layout,
 * and hidden entirely when no line is selected — controls with nothing to act
 * on are what made the old text workspace look broken.
 */
.dp-te-style {
  margin: 10px 0 0;
  padding: 12px;
  border: 1px solid var(--dp-line, #f0ded4);
  border-radius: 10px;
  background: #fff;
}
.dp-te-sttitle {
  margin: 0 0 10px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--dp-muted);
}
.dp-te-strow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 8px;
}
.dp-te-strow:last-of-type { margin-bottom: 0; }
.dp-te-stlabel { font-size: 14px; color: var(--dp-text); }

.dp-te-stinput {
  flex: 0 0 auto;
  min-width: 0;
  max-width: 58%;
  padding: 5px 8px;
  border: 1px solid var(--dp-line, #e6d5cb);
  border-radius: 6px;
  background: #fff;
  color: var(--dp-text);
  font: inherit;
  font-size: 14px;
}
.dp-te-stinput:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: 1px; }
.dp-te-stsize { width: 84px; }
/* A colour well, not a form field: browsers draw their own swatch inside, so
   the padding that suits a text input leaves a thin ring of white around it. */
.dp-te-stcolor { width: 44px; height: 30px; padding: 2px; cursor: pointer; }

.dp-te-stweight { display: flex; gap: 6px; }
.dp-te-stbtn {
  width: 34px;
  height: 30px;
  border: 1px solid var(--dp-line, #e6d5cb);
  border-radius: 6px;
  background: #fff;
  color: var(--dp-text);
  font-size: 15px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}
.dp-te-stitalic { font-style: italic; font-family: Georgia, "Times New Roman", serif; }
.dp-te-stbtn:hover { border-color: var(--dp-orange); }
.dp-te-stbtn.is-active {
  border-color: var(--dp-orange);
  background: var(--dp-orange);
  color: #fff;
}

.dp-te-streset {
  width: 100%;
  margin-top: 10px;
  padding: 6px;
  border: 1px solid var(--dp-line, #e6d5cb);
  border-radius: 6px;
  background: #fff;
  color: var(--dp-muted);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.dp-te-streset:hover { border-color: var(--dp-orange); color: var(--dp-orange); }

/* The scanned-page note. Blue, not amber: nothing is wrong and nothing is
   degraded — the edit simply works by a different mechanism, and this explains
   which. Amber is reserved for the font substitution, where the result really
   is a compromise. */
.dp-te-scannote {
  border-inline-start-color: #2c6fb5;
  background: #eef4fb;
  color: #24405e;
}

.dp-te-input {
  position: absolute;
  margin: 0;
  padding: 0 1px;
  border: 1px solid var(--dp-orange);
  border-radius: 3px;
  background: #fff;
  color: var(--dp-text);
  font-family: inherit;
  white-space: nowrap;
  z-index: 2;
  pointer-events: auto;
}

.dp-te-nav { display: flex; align-items: center; gap: 10px; margin: 12px 0; }
.dp-te-pageno { font-size: 15px; color: var(--dp-muted); }
.dp-te-status { margin: 10px 0 0; font-size: 14px; color: var(--dp-text); }
.dp-te-status:empty { display: none; }

/* ---- utility tools (Phase 11) ------------------------------------------
 * One input, one obvious output, no editing in between. No preview canvas, no
 * page grid — those belong to tools that transform pixels.
 */
.dp-util { display: flex; flex-direction: column; gap: 14px; }
.dp-util-in, .dp-util-out {
  width: 100%;
  padding: 10px 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  line-height: 1.5;
  border: 1px solid var(--dp-border);
  border-radius: 8px;
  background: #fff;
  color: var(--dp-text);
  /* A base64 string is one enormous unbroken word; without this it forces the
     page to scroll sideways. */
  overflow-wrap: anywhere;
  resize: vertical;
}
.dp-util-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.dp-util-check { font-size: 14px; color: var(--dp-text); display: flex; align-items: center; gap: 6px; }
.dp-util-hint { margin: 0; font-size: 13.5px; line-height: 1.55; color: var(--dp-muted); }

.dp-util-sizes {
  margin: 0; padding: 0; list-style: none;
  display: flex; flex-wrap: wrap; gap: 6px 18px;
  font-size: 13.5px; color: var(--dp-muted);
}
.dp-util-sizes strong { color: var(--dp-text); }

.dp-util-preview {
  display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end;
  padding: 12px; border: 1px solid var(--dp-border); border-radius: 10px; background: #fff;
}
.dp-util-preview:empty { display: none; }
.dp-util-img { max-width: 100%; height: auto; }

/* A checkerboard, so a transparent PNG is visibly transparent rather than
   looking like it has a white background. */
.dp-util-preview img {
  background-image:
    linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%),
    linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%);
  background-size: 16px 16px;
  background-position: 0 0, 8px 8px;
}

/* ---- EXIF viewer (Phase 11) --------------------------------------------
 * GPS first and visually loudest: someone opening this before posting a photo
 * is asking one question. The identifying group is marked too, because a
 * camera serial number is not obviously sensitive until it is pointed out.
 */
.dp-exif { display: flex; flex-direction: column; gap: 14px; }
.dp-exif-gps {
  border: 1px solid #b3261e;
  background: #fdf3f2;
  border-radius: var(--dp-radius);
  padding: 14px 16px;
}
.dp-exif-gps strong { color: #b3261e; font-size: 16px; }
.dp-exif-gps p { margin: 6px 0; font-size: 15px; color: var(--dp-text); }
.dp-exif-gps a { color: var(--dp-orange); font-weight: 600; }
.dp-exif-advice { font-size: 13.5px; color: var(--dp-muted); }
.dp-exif-nogps {
  margin: 0; padding: 12px 14px; font-size: 15px;
  border: 1px solid #b7d9b7; background: #f4faf4; border-radius: var(--dp-radius);
  color: #2f6f2f;
}
.dp-exif-none { margin: 0; font-size: 15px; color: var(--dp-muted); }

.dp-exif-group {
  border: 1px solid var(--dp-border); border-radius: var(--dp-radius);
  padding: 12px 16px; background: #fff;
}
.dp-exif-group h3 { margin: 0 0 8px; font-size: 15px; color: var(--dp-text); }
.dp-exif-group.is-sensitive { border-color: #e6c07a; background: #fffdf6; }

.dp-exif-list { display: grid; grid-template-columns: minmax(120px, auto) 1fr; gap: 4px 16px; margin: 0; }
.dp-exif-list dt { font-size: 14px; color: var(--dp-muted); }
.dp-exif-list dd { margin: 0; font-size: 14px; color: var(--dp-text); overflow-wrap: anywhere; }

/* ===========================================================================
   Phase 11 — the migrated image tools
   =========================================================================== */

/* ---- interactive crop ----------------------------------------------------
   The overlay is positioned over the canvas rather than drawn into it, so the
   handles stay crisp at any zoom and the browser does the hit-testing. */

.dp-crop-ratios {
  display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 14px;
}
.dp-crop-ratios .dp-btn[aria-pressed="true"] {
  background: var(--dp-orange); border-color: var(--dp-orange); color: #fff;
}

.dp-crop-stage {
  position: relative;
  display: inline-block;      /* shrink to the canvas so the overlay lines up */
  max-width: 100%;
  line-height: 0;             /* kill the inline-block baseline gap */
  border-radius: var(--dp-radius);
  overflow: hidden;
  background: #f4efec;
}
.dp-crop-canvas { display: block; max-width: 100%; height: auto; }

.dp-crop-overlay {
  position: absolute; inset: 0;
  cursor: crosshair;
  touch-action: none;         /* or a drag on a phone scrolls the page instead */
}

.dp-crop-rect {
  position: absolute;
  border: 1px solid #fff;
  cursor: move;
  /* The dimming is a huge shadow rather than four positioned panels: one
     element, and it tracks the rectangle automatically. */
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, .45);
  /* Rule-of-thirds guides, drawn with gradients so there is nothing to move. */
  background-image:
    linear-gradient(to right,  transparent 33.33%, rgba(255,255,255,.35) 33.33%, rgba(255,255,255,.35) calc(33.33% + 1px), transparent calc(33.33% + 1px)),
    linear-gradient(to right,  transparent 66.66%, rgba(255,255,255,.35) 66.66%, rgba(255,255,255,.35) calc(66.66% + 1px), transparent calc(66.66% + 1px)),
    linear-gradient(to bottom, transparent 33.33%, rgba(255,255,255,.35) 33.33%, rgba(255,255,255,.35) calc(33.33% + 1px), transparent calc(33.33% + 1px)),
    linear-gradient(to bottom, transparent 66.66%, rgba(255,255,255,.35) 66.66%, rgba(255,255,255,.35) calc(66.66% + 1px), transparent calc(66.66% + 1px));
}

.dp-crop-handle {
  position: absolute;
  width: 14px; height: 14px;
  background: #fff;
  border: 2px solid var(--dp-orange);
  border-radius: 3px;
  /* A 14px visual handle is below the 44px touch minimum, so the tappable area
     is extended past the paint with a transparent border box. */
  box-sizing: border-box;
}
.dp-crop-handle::after {
  content: ''; position: absolute; inset: -15px;   /* -> ~44px hit area */
}
.dp-crop-nw { top: -7px;  left: -7px;  cursor: nwse-resize; }
.dp-crop-n  { top: -7px;  left: 50%; margin-left: -7px; cursor: ns-resize; }
.dp-crop-ne { top: -7px;  right: -7px; cursor: nesw-resize; }
.dp-crop-e  { top: 50%; margin-top: -7px; right: -7px; cursor: ew-resize; }
.dp-crop-se { bottom: -7px; right: -7px; cursor: nwse-resize; }
.dp-crop-s  { bottom: -7px; left: 50%; margin-left: -7px; cursor: ns-resize; }
.dp-crop-sw { bottom: -7px; left: -7px;  cursor: nesw-resize; }
.dp-crop-w  { top: 50%; margin-top: -7px; left: -7px; cursor: ew-resize; }

.dp-crop-readout {
  margin: 12px 0 0; font-size: 15px; color: var(--dp-muted);
  font-variant-numeric: tabular-nums;   /* stops the width jittering on drag */
}
.dp-crop-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 16px; }

/* ---- batch list ---------------------------------------------------------- */

.dp-batch-list { list-style: none; margin: 16px 0 0; padding: 0; }
.dp-batch-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  padding: 10px 14px;
  border: 1px solid var(--dp-border); border-radius: var(--dp-radius);
  background: #fff;
}
.dp-batch-row + .dp-batch-row { margin-top: 8px; }
.dp-batch-row.is-error { border-color: #e6b4ae; background: #fdf3f2; }

.dp-batch-name {
  flex: 1 1 200px; min-width: 0;
  font-size: 15px; color: var(--dp-text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dp-batch-size {
  font-size: 14px; color: var(--dp-muted);
  font-variant-numeric: tabular-nums;   /* sizes line up down the column */
}
.dp-batch-pct { font-size: 14px; color: var(--dp-muted); }
.dp-batch-pct.is-good { color: #2f6f2f; font-weight: 600; }

.dp-batch-flag {
  font-size: 12.5px; padding: 2px 8px; border-radius: 999px;
  background: #f4efec; color: var(--dp-muted);
}
.dp-batch-flag.is-good { background: #eef7ee; color: #2f6f2f; }
/* Not decoration: this is the line telling someone their home address was in
   the file they were about to post. */
.dp-batch-flag.is-warn { background: #fdf3f2; color: #b3261e; font-weight: 600; }

.dp-batch-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 16px; }

/* The `hidden` ATTRIBUTE only works while nothing sets `display`. The UA rule
   is `[hidden] { display: none }` at the lowest specificity, so any author rule
   that gives an element a display value silently defeats it — which is why the
   action bar and its buttons showed on an empty page even though the JS had
   set el.hidden = true.
   Scoped to the DocPivot workspace, and !important because the whole point is
   to beat whatever display the element was given. */
.dp-workspace [hidden],
.dp-workspace[hidden] { display: none !important; }
.dp-batch-progress, .dp-batch-summary { font-size: 14.5px; color: var(--dp-muted); }
.dp-batch-summary { margin: 12px 0 0; }

.dp-btn-sm { padding: 4px 12px; font-size: 13.5px; }

/* ---- the keep-ICC choice ------------------------------------------------- */

.dp-check {
  display: flex; align-items: center; gap: 10px;
  margin: 0 0 4px; font-size: 15px; color: var(--dp-text); cursor: pointer;
}
.dp-check input { width: 18px; height: 18px; accent-color: var(--dp-orange); cursor: pointer; }

/* ---- EXIF viewer --------------------------------------------------------- */

.dp-exif-url { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0 0; }
.dp-exif-url .dp-input {
  flex: 1 1 260px; min-width: 0;
  padding: 10px 14px; font-size: 15px;
  border: 1px solid var(--dp-border); border-radius: var(--dp-radius);
  background: #fff; color: var(--dp-text);
}
.dp-exif-url .dp-input:focus {
  outline: none; border-color: var(--dp-orange);
  box-shadow: 0 0 0 3px rgba(250, 76, 16, .12);
}

.dp-exif-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0 0; }
.dp-exif { display: grid; gap: 12px; margin-top: 16px; }

/* The full tag dump, collapsed. Long enough to bury the answer if it were not. */
details.dp-exif-group > summary {
  cursor: pointer; font-weight: 600; font-size: 15px; color: var(--dp-text);
  list-style-position: inside;
}
details.dp-exif-group > summary:focus-visible {
  outline: 2px solid var(--dp-orange); outline-offset: 2px;
}
details.dp-exif-group[open] > summary { margin-bottom: 10px; }

@media (max-width: 600px) {
  .dp-batch-name { flex-basis: 100%; }
  .dp-crop-ratios .dp-btn { flex: 1 1 auto; }
}

/* ===========================================================================
   DocPivot brand in the shared header and footer
   The markup is ToolsPivot's; only the brand and the nav differ. These rules
   cover the few elements that exist only under this brand.
   =========================================================================== */

/* The theme already pins .themeLogoImg to 40px in the header and .f_logo_box
   img to 44px in the footer, and the logo carries those classes — so it lands
   at exactly ToolsPivot's optical size without a rule here. This only stops a
   narrow viewport from squashing it. */
.dp-logo { max-width: 100%; height: auto; }
.navbar-brand .dp-logo { height: 40px; width: auto; }
.f_logo_box .navbar-brand .dp-logo-light { height: 44px; width: auto; margin-bottom: 18px; }

/* The way back to the parent brand. Set apart from the DocPivot links so it
   does not read as another section of this product — it is the exit. */
#myNavbar .navbar-nav > li.dp-nav-back > a {
  color: var(--dp-muted, #7b716c);
}
#myNavbar .navbar-nav > li.dp-nav-back > a:hover,
#myNavbar .navbar-nav > li.dp-nav-back > a:focus {
  color: var(--dp-orange, #fa4c10);
}

/* "Back" points right in Arabic, Urdu and Farsi, so a hardcoded left arrow
   would read as "forward". Flipped rather than swapped, so one glyph serves. */
.dp-back-arrow { display: inline-block; }
[dir="rtl"] .dp-back-arrow,
html:lang(ar) .dp-back-arrow,
html:lang(ur) .dp-back-arrow,
html:lang(fa) .dp-back-arrow { transform: scaleX(-1); }

@media (max-width: 767px) {
  .navbar-brand .dp-logo { height: 34px; }
  /* On mobile the nav is a stacked list; a rule above it makes the exit read as
     separate from the tool links rather than the last of them. */
  #myNavbar .navbar-nav > li.dp-nav-back {
    border-top: 1px solid var(--dp-border, #e8ded8);
    margin-top: 6px; padding-top: 4px;
  }
}

/* ===========================================================================
   Homepage content sections
   Positioning, differentiators, capability, audiences.
   Reminder: html { font-size: 10px } in this theme — 1rem is TEN pixels.
   Everything here is px so it means what it says.
   =========================================================================== */

.dp-lede {
  max-width: 68ch;              /* past this a line is tiring to track back */
  margin: 0 0 32px;             /* the single heading-block-to-body gap */
  font-size: 17px; line-height: 1.65; color: #57504a;
}
.dp-section-title + .dp-lede { margin-top: -4px; }

/* ---- numbered steps ------------------------------------------------------ */

.dp-steps-list {
  /* margin-top comes from the shared heading-to-body rule; only the list's own
     browser defaults are being cleared here. Writing `margin: 0` reset that
     rule and left the heading sitting 12px off its cards. */
  list-style: none; margin-block: 0; margin-inline: 0; padding: 0;
  display: grid; gap: 18px;
  /* minmax(0,...) not 1fr alone: a plain 1fr floors at the content width, so
     "Middle right" made its column wider than the others and the grid stopped
     reading as a picture of the photo. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  counter-reset: dpstep;
}
.dp-step {
  padding: 26px 24px 24px;
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 14px;
}
.dp-step-n {
  display: grid; place-items: center;
  width: 36px; height: 36px; margin-bottom: 14px;
  border-radius: 50%;
  background: var(--dp-orange); color: #fff;
  font-size: 16px; font-weight: 800;
}
.dp-step strong { display: block; font-size: 18px; color: var(--dp-text); margin-bottom: 8px; }
.dp-step p { margin: 0; font-size: 15px; line-height: 1.6; color: #57504a; }

/* ---- alternating feature rows -------------------------------------------
   Art on one side, copy on the other, sides swapping down the page. A column
   of identical card grids gives the eye nothing to hold on to; alternating
   gives each claim its own beat. */

.dp-feature-row {
  display: grid; gap: 48px; align-items: center;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}
/* Order, not direction: `direction: rtl` would also flip the text inside. */
.dp-feature-row.is-reversed .dp-feature-art { order: 2; }

.dp-art {
  display: block; width: 100%; height: auto;
  max-width: 460px; margin-inline: auto;
  border-radius: 16px;
  /* A soft lift so the drawing reads as an object on the page rather than as
     loose linework floating in the margin. */
  box-shadow: 0 10px 34px rgba(35, 32, 31, .07);
}

.dp-eyebrow {
  display: inline-block; margin-bottom: 10px;
  font-size: 12.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--dp-orange);
}
.dp-feature-copy h2 {
  margin: 0 0 12px; font-size: 28px; line-height: 1.25; color: var(--dp-text);
}
.dp-feature-copy > p { margin: 0 0 18px; font-size: 17px; line-height: 1.65; color: #57504a; }

.dp-ticks { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.dp-ticks li {
  position: relative; padding-inline-start: 30px;
  font-size: 15.5px; line-height: 1.6; color: #57504a;
}
/* A drawn tick rather than a bullet or an emoji: it survives translation and
   inherits the brand colour. */
.dp-ticks li::before {
  content: ''; position: absolute; inset-inline-start: 0; top: 4px;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--dp-grad-b);
}
.dp-ticks li::after {
  content: ''; position: absolute; inset-inline-start: 5px; top: 9px;
  width: 8px; height: 4px;
  /* Physical, not logical: a tick is the same shape in every script, and the
     logical pair flipped it into a "<" in RTL. */
  border-left: 2px solid var(--dp-orange);
  border-bottom: 2px solid var(--dp-orange);
  transform: rotate(-45deg);
}
.dp-ticks strong { color: var(--dp-text); font-weight: 700; }

.dp-feature-note {
  margin: 20px 0 0; font-size: 14.5px; line-height: 1.6; color: var(--dp-muted);
  padding-inline-start: 14px;
  border-inline-start: 3px solid var(--dp-orange);
}

/* ---- them vs us ---------------------------------------------------------- */

.dp-compare-grid {
  display: grid; gap: 18px;
  grid-template-columns: repeat(2, 1fr);
  max-width: 900px; margin-inline: auto;
}
.dp-compare-col {
  padding: 26px 26px 28px;
  border-radius: 16px;
  background: #fff;
  border: 1px solid var(--dp-border);
}
/* The one being rejected is set back: muted, no accent, no lift. The eye should
   land on the right-hand column. */
.dp-compare-col.is-them { background: #faf7f5; }
.dp-compare-col.is-us {
  border-color: var(--dp-orange);
  box-shadow: 0 10px 30px rgba(250, 76, 16, .10);
}

.dp-compare-col h3 {
  margin: 0 0 18px; font-size: 19px; color: var(--dp-text);
  padding-bottom: 14px; border-bottom: 1px solid var(--dp-border);
}
.dp-compare-col.is-us h3 { color: var(--dp-orange); }

.dp-compare-col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px; }
.dp-compare-col li {
  position: relative; padding-inline-start: 30px;
  font-size: 15.5px; line-height: 1.55; color: #57504a;
}
/* A cross on the left column, a tick on the right — drawn, so neither depends
   on an icon font and both survive a translated string. */
.dp-compare-col.is-them li::before,
.dp-compare-col.is-them li::after {
  content: ''; position: absolute; inset-inline-start: 4px; top: 10px;
  width: 13px; height: 2px; background: #b9aca4;
}
.dp-compare-col.is-them li::before { transform: rotate(45deg); }
.dp-compare-col.is-them li::after  { transform: rotate(-45deg); }

.dp-compare-col.is-us li::before {
  content: ''; position: absolute; inset-inline-start: 0; top: 3px;
  width: 18px; height: 18px; border-radius: 50%; background: var(--dp-grad-b);
}
.dp-compare-col.is-us li::after {
  content: ''; position: absolute; inset-inline-start: 5px; top: 8px;
  width: 8px; height: 4px;
  /* Physical, not logical: a tick is the same shape in every script, and the
     logical pair flipped it into a "<" in RTL. */
  border-left: 2px solid var(--dp-orange);
  border-bottom: 2px solid var(--dp-orange);
  transform: rotate(-45deg);
}

.text-center { text-align: center; }
.dp-lede.text-center { margin-inline: auto; }

@media (max-width: 991px) {
  .dp-feature-row { grid-template-columns: 1fr; gap: 28px; }
  /* Art first on every row once stacked. Alternating order vertically just
     looks like a mistake. */
  .dp-feature-row.is-reversed .dp-feature-art { order: 0; }
  .dp-steps-list { grid-template-columns: 1fr; }
  .dp-compare-grid { grid-template-columns: 1fr; }
}

@media (max-width: 767px) {
  .dp-feature-copy h2 { font-size: 23px; }
  .dp-feature-copy > p { font-size: 16px; }
  .dp-step { padding: 20px; }
}

/* ---- differentiators ---------------------------------------------------- */

/* Six items: auto-fit lands on 3x2, which is what this wants. */
.dp-diff-grid {
  display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
}

/* Four items: auto-fit chose 3 columns and left the fourth stranded on a row of
   its own beside two gaps. These are counted sets, so the column count is
   declared rather than derived. */
.dp-cap-grid, .dp-who-grid {
  display: grid; gap: 16px;
  grid-template-columns: repeat(4, 1fr);
}
@media (max-width: 1099px) {
  .dp-cap-grid, .dp-who-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 599px) {
  .dp-cap-grid, .dp-who-grid { grid-template-columns: 1fr; }
}

.dp-diff-item, .dp-cap-item, .dp-who-item {
  padding: 22px 22px 20px;
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 14px;
}
.dp-band-tint .dp-diff-item,
.dp-band-tint .dp-who-item { background: #fff; }

/* The "elsewhere" line. Set quieter and struck through: it is the thing being
   rejected, and it should read as the setup, not as a claim of our own. */
.dp-diff-vs {
  display: block;
  font-size: 13px; color: #6f655f;
  margin-bottom: 8px;
  text-decoration: line-through;
  text-decoration-color: #d9cec7;
}

.dp-diff-item strong, .dp-cap-item strong, .dp-who-item strong {
  display: block; font-size: 17px; line-height: 1.35;
  color: var(--dp-text); margin-bottom: 8px;
}
.dp-diff-item p, .dp-cap-item p, .dp-who-item p {
  margin: 0; font-size: 15px; line-height: 1.6; color: #57504a;
}

@media (max-width: 767px) {
  /* Mobile tightens the heading-to-body gap to 24px — but it has to move in
     BOTH places or a section with a lede ends up 8px tighter than one without,
     which is the inconsistency this rule exists to prevent. */
  .dp-lede { font-size: 16px; margin-bottom: 24px; }
  .dp-band .dp-tool-grid,
  .dp-band .dp-steps-list,
  .dp-band .dp-compare-grid,
  .dp-band .dp-who-grid,
  .dp-band .dp-proof-grid,
  .dp-band .dp-faq-list,
  .dp-band .dp-flow,
  .dp-band .dp-pipe { margin-top: 24px; }
  .dp-where-card { padding: 20px; }
  .dp-where-n { font-size: 38px; }
  .dp-diff-item, .dp-cap-item, .dp-who-item { padding: 18px; }
}

@media (max-width: 767px) {
  .dp-flow-route { padding: 18px 18px 22px; }
  .dp-flow-n { font-size: 34px; }
  /* The chain stacks. Side-scrolling a five-step diagram on a phone hides the
     end of the very sequence it exists to show. */
  .dp-pipe { flex-direction: column; align-items: stretch; gap: 8px; padding: 18px; }
  .dp-pipe-step { justify-content: flex-start; border-radius: 12px; }
  .dp-pipe-arrow { flex: none; width: 2px; height: 16px; margin-inline-start: 22px; }
  .dp-pipe-arrow::after {
    inset-inline-end: auto; inset-inline-start: -3px; top: auto; bottom: 0;
    border-inline: 4px solid transparent;
    border-block-start: 6px solid #e8ded8; border-inline-start: 0;
  }
}

/* ===========================================================================
   Section tones
   The tool icons already carry six category colours; the sections borrow the
   same six so scrolling the page feels varied without introducing a palette
   that exists nowhere else. Each tone sets --dp-tone, and the icons inside
   pick it up — so a section and its icons are always one colour family.
   =========================================================================== */

.dp-tone-orange { --dp-tone: #c0430f; background: linear-gradient(180deg,#fffaf7,#fff3ec); }
.dp-tone-blue   { --dp-tone: #1f5fb0; background: linear-gradient(180deg,#fbfdff,#f0f5fc); }
.dp-tone-purple { --dp-tone: #5a44b8; background: linear-gradient(180deg,#fdfcff,#f4f1fd); }
.dp-tone-green  { --dp-tone: #1c6b3a; background: linear-gradient(180deg,#fbfefb,#f0f8f2); }
.dp-tone-rose   { --dp-tone: #b3263c; background: linear-gradient(180deg,#fffbfb,#fdf1f3); }
.dp-tone-amber  { --dp-tone: #96660a; background: linear-gradient(180deg,#fffdf8,#fdf6e8); }

/* Icons and accents inherit the section's tone, falling back to brand orange
   in any section that has not been given one. */
.dp-cap-ico, .dp-who-ico, .dp-proof-ico {
  display: grid; place-items: center;
  width: 42px; height: 42px; margin-bottom: 14px;
  border-radius: 11px;
  background: #fff;
  border: 1px solid var(--dp-border);
  color: var(--dp-tone, var(--dp-orange));
}
.dp-eyebrow      { color: var(--dp-tone, var(--dp-orange)); }
.dp-step-n       { background: var(--dp-tone, var(--dp-orange)); }
.dp-feature-note { border-inline-start-color: var(--dp-tone, var(--dp-orange)); }

/* The tick marks follow the tone too, so a green section is green throughout
   rather than green with orange ticks. */
.dp-ticks li::after,
.dp-compare-col.is-us li::after {
  border-inline-start-color: var(--dp-tone, var(--dp-orange));
  border-block-end-color:   var(--dp-tone, var(--dp-orange));
}
.dp-ticks li::before,
.dp-compare-col.is-us li::before { background: color-mix(in srgb, var(--dp-tone, #fa4c10) 12%, #fff); }

/* .dp-band-tint also sets a background-image gradient. A toned section carries
   its own, so the two must not stack — the tone classes are declared after it
   and win, but only because both use the background shorthand. Stated here so
   that stays true if either is ever split into longhand. */

/* ---------------------------------------------------------------------------
 * The file list and the result panel.
 *
 * Both were added after watching how the tools actually end and how the two
 * biggest competitors end theirs. All sizes are px on purpose: this theme sets
 * html { font-size: 10px }, so a rem here is 10px and every rem-based guess in
 * this file has been wrong at least once.
 * ------------------------------------------------------------------------ */

.dp-filelist {
  max-width: var(--dp-drop-max);
  margin: 0 auto 20px;
  padding: 14px 16px;
  border: 1px solid var(--dp-border);
  border-radius: var(--dp-radius);
  background: #fff;
}
.dp-filelist[hidden] { display: none !important; }

.dp-filelist-head {
  margin: 0 0 10px;
  font-size: 15px;
  font-weight: 600;
  color: var(--dp-text);
}

.dp-filelist-items { list-style: none; margin: 0; padding: 0; }

.dp-filelist-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 0;
  font-size: 15px;
  border-top: 1px solid var(--dp-grad-a);
}
.dp-filelist-item:first-child { border-top: 0; }

/* The colour that ties this file to its pages in the grid. */
.dp-filelist-dot {
  width: 10px; height: 10px;
  border-radius: 50%;
  flex: 0 0 auto;
}

.dp-filelist-name {
  flex: 1 1 auto;
  min-width: 0;                /* or the ellipsis never triggers inside a flex row */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--dp-text);
}

.dp-filelist-count {
  flex: 0 0 auto;
  font-size: 14px;
  /* #6f655f, not --dp-muted: the muted grey is 3.65:1 on white and this is
     small text that carries real information. */
  color: #6f655f;
}

.dp-filelist-remove {
  flex: 0 0 auto;
  width: 26px; height: 26px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: #6f655f;
  font-size: 19px;
  line-height: 1;
  cursor: pointer;
}
.dp-filelist-remove:hover { background: var(--dp-grad-b); color: var(--dp-text); }

.dp-filelist-note {
  margin: 10px 0 0;
  font-size: 13.5px;
  color: #6f655f;
}

/* The provenance stripe on a page thumbnail. Only visible once more than one
   file is loaded — with a single document every page would carry the same
   stripe, which is noise pretending to be information. */
.dp-page { position: relative; }
.has-files .dp-page::before {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 4px;
  border-radius: var(--dp-radius) 0 0 var(--dp-radius);
  background: var(--dp-page-file, transparent);
  z-index: 1;
}

/* ---- result ------------------------------------------------------------ */

.dp-result {
  max-width: var(--dp-drop-max);
  margin: 22px auto 0;
  padding: 22px 24px;
  border: 1px solid #cfe8d8;
  border-radius: var(--dp-radius);
  /* Green, not brand orange. Orange is the "do it" colour throughout the
     suite; reusing it for "it is done" makes a finished job look like another
     button to press. */
  background: #f3fbf6;
  text-align: center;
}

.dp-result-head {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-bottom: 6px;
}

.dp-result-tick {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px; height: 30px;
  border-radius: 50%;
  background: #1c8a55;
  color: #fff;
  flex: 0 0 auto;
}

.dp-result-title { font-size: 19px; font-weight: 700; color: #15412c; }

.dp-result-file {
  margin: 0 0 16px;
  font-size: 14px;
  line-height: 1.4;
  color: #2f5a44;
  /* break-word first, so a real filename breaks at its own punctuation — the
     hyphens and underscores it is already full of — instead of mid-token.
     anywhere is the fallback for the pathological single-word name, because a
     long filename must never widen the panel. */
  overflow-wrap: anywhere;
  word-break: normal;
}

.dp-result-stats {
  display: flex;
  justify-content: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}

.dp-result-stat {
  min-width: 96px;
  padding: 10px 14px;
  border-radius: 10px;
  background: #fff;
  border: 1px solid #dcece3;
}

.dp-result-stat-value {
  display: block;
  font-size: 21px;
  font-weight: 700;
  color: #15412c;
  /* Tabular figures so 1 file and 11 files do not shift the label under them. */
  font-variant-numeric: tabular-nums;
}

.dp-result-stat-label { display: block; font-size: 13px; color: #4a6b5b; }

.dp-result-actions {
  display: flex;
  justify-content: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* Secondary downloads (the comment list). Under the main action, smaller, and
   never competing with it for the eye. */
.dp-result-extras {
  display: flex;
  justify-content: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 8px;
}
/* 44px, like every other tap target on the page: these are real downloads,
   not footnotes, and 36px missed the minimum on a phone. */
.dp-result-extra { font-size: 13px; padding: 8px 12px; min-height: 44px; }

.dp-result-next { margin-top: 18px; }

.dp-result-next-label {
  margin: 0 0 8px;
  font-size: 14px;
  color: #4a6b5b;
}

.dp-result-next-list {
  display: flex;
  justify-content: center;
  gap: 8px;
  flex-wrap: wrap;
}

.dp-result-next-item {
  padding: 7px 13px;
  border-radius: 999px;
  border: 1px solid #cfe8d8;
  background: #fff;
  font-size: 14px;
  color: #1c5c3d;
  text-decoration: none;
}
.dp-result-next-item:hover { background: #eaf6ef; color: #123f2a; }

@media (max-width: 640px) {
  .dp-result { padding: 18px 16px; }
  .dp-result-stats { gap: 10px; }
  .dp-result-stat { min-width: 84px; padding: 9px 11px; }
}

/* ---------------------------------------------------------------------------
 * Two-pane tool layout: canvas + action rail.
 *
 * Matches how iLovePDF and Smallpdf lay a tool out, for the reason they do it:
 * the primary action must not scroll away from a long page grid. Ours used to
 * sit under the grid, so the more pages you had the further you had to scroll
 * to find the way out.
 * ------------------------------------------------------------------------ */

.dp-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: 20px;
  align-items: start;
}

/* minmax(0,1fr) above and min-width:0 here: a grid item's default min-width is
   auto, so a wide thumbnail row would push the rail off screen instead of
   scrolling inside its own column. */
.dp-canvas { min-width: 0; }

/* The RAIL is what sticks, because it now holds two cards: the action panel
   and, once a job finishes, the result. Sticking the panel itself would have
   left the result card scrolling away underneath it. */
.dp-rail {
  position: sticky;
  /* Clears the theme's fixed header. */
  top: 84px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-height: calc(100vh - 104px);
  overflow-y: auto;
}

.dp-actionpanel {
  display: flex;
  flex-direction: column;
  flex: 0 0 auto;
  border: 1px solid var(--dp-border);
  border-radius: var(--dp-radius);
  background: #fff;
  overflow: hidden;
}

.dp-actionpanel-title {
  margin: 0;
  padding: 16px 18px;
  font-size: 20px;
  font-weight: 700;
  text-align: center;
  color: var(--dp-text);
  border-bottom: 1px solid var(--dp-border);
}

.dp-actionpanel-body {
  flex: 1 1 auto;
  min-height: 0;          /* so the body scrolls, not the whole panel */
  overflow-y: auto;
  padding: 16px 18px;
}

.dp-actionpanel-foot {
  flex: 0 0 auto;
  padding: 14px 18px;
  border-top: 1px solid var(--dp-border);
  background: #fff;
}

/* An empty result host still counted as a flex child, so the rail's 16px gap
   was applied below the panel and the right column finished 616px against the
   canvas's 600 — the two columns visibly out of step at the bottom. The image
   rail already had this exact rule; this is the same fix, not a second one. */
.dp-rail > .dp-result-host:empty { display: none; }

/* The result REPLACES the action panel in the rail. Two cards offering two
   different next moves in the same column made the finished job compete with
   an invitation to run it again. */
.dp-rail.is-result .dp-actionpanel { display: none; }

/* Inside the rail the export button is the whole width; it is the one thing
   the panel exists to offer. */
.dp-actionpanel .dp-exportbar { display: block; }
.dp-actionpanel .dp-exportbar .dp-btn-primary { width: 100%; }
.dp-actionpanel .dp-export-status { display: block; margin-top: 8px; text-align: center; }

/* The result is its own card in the rail, full width, with its own frame. */
.dp-rail .dp-result {
  max-width: none;
  margin: 0;
  flex: 0 0 auto;
}
.dp-rail .dp-result-actions { flex-direction: column; }
.dp-rail .dp-result-actions .dp-btn { width: 100%; }
.dp-rail .dp-result-extras { flex-direction: column; }
.dp-rail .dp-result-extras .dp-btn { width: 100%; }
.dp-rail .dp-result-next-list { flex-direction: column; }
.dp-rail .dp-result-next-item { text-align: center; }

/* The file list is already inside the rail, so it drops its own frame. */
.dp-actionpanel .dp-filelist {
  max-width: none;
  margin: 0 0 14px;
  padding: 0;
  border: 0;
}

/* "Add more files" — a tile in the grid, in the slot after the last page.
   Sized by the grid's own columns, so it lines up with the pages instead of
   sitting under them looking like a separate feature. */
.dp-addmore {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  /* Matches the tile height the grid is laid out with. */
  min-height: 168px;
  border: 2px dashed var(--dp-border);
  border-radius: var(--dp-radius);
  background: #fff;
  color: var(--dp-orange);
  font-size: 14.5px;
  font-weight: 600;
  cursor: pointer;
  transition: border-color .15s, background .15s;
}
.dp-addmore[hidden] { display: none !important; }
.dp-addmore:hover { border-color: var(--dp-orange); background: var(--dp-grad-a); }
.dp-addmore:focus-visible { outline: 3px solid var(--dp-orange); outline-offset: 2px; }
.dp-addmore-plus { font-size: 30px; line-height: 1; font-weight: 400; }

/* A tool with no file loaded is just the drop zone: the rail would be an empty
   box next to it, so the layout collapses to one column until there is work. */
.dp-workspace:not(.has-pages) .dp-layout { grid-template-columns: minmax(0, 1fr); }
.dp-workspace:not(.has-pages) .dp-actionpanel { display: none; }

@media (max-width: 900px) {
  /* The rail stops being a rail and becomes a section under the work. Sticky
     is dropped with it — a sticky block in normal flow on a short screen can
     cover the very grid it is meant to act on. */
  .dp-layout { grid-template-columns: minmax(0, 1fr); }
  .dp-actionpanel { position: static; max-height: none; }
}

/* The drop zone has done its job once a file is in. It was holding the widest,
   highest block on the page to repeat an offer the grid's "Add more files"
   tile already makes, and it pushed the actual document below the fold.
   Dropping still works — the loader is bound to the whole canvas. */
.dp-workspace.has-pages .dp-dropzone { display: none; }

/* THE MIRROR OF THE RULE ABOVE.
   That rule hides the drop zone once a file is in; these hide the controls that
   only make sense ONCE it is. Both used to be free of this: "Add more files"
   did not exist, and the command bar lived in the rail, which is itself hidden
   until a file is loaded. Moving the bar onto the canvas took it out from
   behind that guard, so an empty page offered to plan work on a document that
   was not there and to add more of nothing. */
.dp-workspace:not(.has-pages) .dp-addmore,
.dp-chainspace:not(.has-pages) .dp-ai { display: none; }

/* Feedback while dragging over the work area, now that the canvas is the drop
   target rather than the zone. */
.dp-canvas.dp-dragover .dp-grid-viewport {
  border-color: var(--dp-orange);
  background: var(--dp-grad-a);
}

/* The result must not scroll inside the rail.
   The body scrolls so a long options list stays reachable, but the result is a
   short, fixed block that people READ — an inner scrollbar hid the next-step
   links below an invisible fold and made a finished job look unfinished. In
   the result state the rail sizes to its content instead. */
.dp-rail { max-height: calc(100vh - 88px); }

/* ---------------------------------------------------------------------------
 * Aligning the two columns.
 *
 * The toolbar and the panel header are the same row of the page and were not
 * treated as one: the toolbar carried a 10px top margin the panel did not, and
 * the two heads were different heights (49px against 55px). That put the head
 * row 9px out and everything under it 23px out, which is exactly the kind of
 * near-miss that reads as sloppy without being obviously wrong.
 *
 * One height for both heads, one gap under both, declared together so they
 * cannot drift apart again.
 * ------------------------------------------------------------------------ */
.dp-workspace {
  --dp-head-h: 58px;
  --dp-head-gap: 14px;
}

.dp-toolbar {
  margin-top: 0;
  min-height: var(--dp-head-h);
  /* border-box, so min-height is the OUTER height and matches the panel head
     rather than being that plus padding and border. */
  box-sizing: border-box;
}

.dp-actionpanel-title {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--dp-head-h);
  box-sizing: border-box;
  padding: 0 18px;
  /* Stronger: this is the tool's name and the only label the rail carries once
     the drop zone is gone. */
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.01em;
}

/* The row under the heads starts at the same y on both sides. */
.dp-grid-host { margin-top: var(--dp-head-gap); }
.dp-actionpanel-body { padding-top: var(--dp-head-gap); }

/* The editor view has no toolbar-then-grid stack, so it keeps its own spacing. */
/* No head gap on the editor view. The gap exists to separate a tool's body
   from the page heading, but here the body sits directly under the toolbar
   card inside the same work block — the gap read as a dead band between the
   two, and it also pushed the column 14px past the panel beside it. Declared
   here rather than earlier in the file because the general rule below used to
   win on source order. */
.dp-editor-host { margin-top: var(--dp-head-gap); }
.dp-view-editor .dp-editor-host { margin-top: 0; }

/* The proof numbers stay on ONE row inside the rail.
   Three 96px-min cells plus gaps needed 316px in a 304px column, so the third
   wrapped and the "proof" read as two unrelated groups. grid-auto-flow:column
   with 1fr tracks adapts to however many stats a tool reports — some show two,
   some three — without ever wrapping or leaving an empty cell behind. */
/* WRAP TO A SECOND ROW rather than forcing everything onto one.
 *
 * A single row was fine for the two or three stats a PDF tool reports. A
 * CONVERSION reports four — pages, paragraphs, tables, size — and four tracks
 * in a 340px rail leave about 70px each, which is narrower than the word
 * "paragraphs". It was rendered as "paragrap / hs". auto-fit keeps three on one
 * line and folds four into a tidy 2x2 instead. */
.dp-rail .dp-result-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(76px, 1fr));
  gap: 8px;
}
/* Four go two-by-two. Three fit across; four do not, and flowing them left the
   fourth alone on its own row looking like an afterthought. */
.dp-rail .dp-result-stats.is-4up { grid-template-columns: repeat(2, 1fr); }
.dp-rail .dp-result-stat {
  min-width: 0;            /* grid items default to min-content; this lets them shrink */
  padding: 10px 6px;
}
.dp-rail .dp-result-stat-value { font-size: 18px; }
.dp-rail .dp-result-stat-label {
  font-size: 12px;
  line-height: 1.3;
  /* Wrap BETWEEN words only. break-word was splitting single words across
     lines, which is how a label became "paragrap hs" — unreadable, and it
     looks like a rendering fault rather than a tight column. */
  overflow-wrap: normal;
  word-break: keep-all;
  hyphens: none;
}

/* ---- notices inside the rail ------------------------------------------
 * The shared notice is a bare coloured sentence, which is right in a wide
 * canvas. In a 340px column a four-line warning set in 16px semi-bold brown
 * becomes the loudest thing on the page — louder than the tool's own title,
 * and louder than the Apply button it is trying to qualify. Same words, given
 * the weight of a note rather than an alarm.
 */
.dp-rail .dp-notice {
  margin: 0 0 12px;
  padding: 10px 12px;
  font-size: 13px;
  line-height: 1.45;
  border-radius: 10px;
  border: 1px solid transparent;
}
.dp-rail .dp-notice:empty { display: none; }
.dp-rail .dp-notice-head { font-weight: 500; }
.dp-rail .dp-notice.is-warn {
  color: #6f4a06;
  background: #fff8ec;
  border-color: #f2dcb4;
}
.dp-rail .dp-notice.is-error {
  color: #8c1d18;
  background: #fdf3f2;
  border-color: #f0c9c6;
}
.dp-rail .dp-notice.is-ok { color: var(--dp-muted); background: transparent; }

/* An empty footer still drew its padding and its top border, so every panel
   ended with a blank white strip below the last control. */
.dp-actionpanel-foot:empty { display: none; padding: 0; border-top: 0; }

/* THE PANEL'S ACTION IS THE PANEL'S WIDTH.
   Apply was rendering at its text width, so the one button the panel exists to
   offer sat as a small tab in the bottom-left corner while the controls above
   it ran the full width. Matches the export button, which already does this. */
.dp-actionpanel .dp-oppanel-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
}
.dp-actionpanel .dp-oppanel-actions .dp-btn,
.dp-actionpanel .dp-oppanel button[type="submit"] { width: 100%; }
.dp-actionpanel .dp-oppanel-hint { text-align: center; }

/* ---- the source document, shown beside its settings --------------------
 * The first page of the chosen PDF. The canvas column held a filename and
 * nothing else, which left half the page blank and gave the user no way to
 * confirm they had picked the right file before converting it.
 */
/* Full width, flush with the top of the rail, AND a flex item that grows.
 *
 * This wrapper is the canvas's direct child; the bordered box the user sees is
 * inside it. Sizing only the canvas left this collapsed to its contents, so the
 * column was 600 tall while the visible box was 280 — the short left-hand box.
 * Measuring .dp-canvas is what hid it; the box to measure is .dp-filespace. */
.dp-preview {
  margin: 0;
  text-align: center;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.dp-preview[hidden] { display: none; }

/* The page grid, borrowed wholesale from the editing tools.
 *
 * Same .dp-grid-viewport, .dp-page and .dp-page-num as Merge and Split, so the
 * two look identical without a second implementation to keep in step. The only
 * additions are the ones the editing grid gets from its virtualiser, which this
 * read-only copy does not run: the column template and the tile height. */
.dp-preview-grid {
  padding: 12px;
  max-height: 62vh;
}
/* ---- the file block ----------------------------------------------------
 * FILES, not pages. This tool converts a whole document and has no page-level
 * action, so a grid of page thumbnails offered edits it cannot perform. One
 * card per file, and the same dashed upload card Merge uses beside it.
 *
 * 180 wide keeps the card and the upload tile on one row on any screen the
 * rail leaves room for.
 */
/* THE TWO COLUMNS ARE THE SAME HEIGHT.
 *
 * The work block wrapped its contents, so it stood about a third as tall as
 * the panel beside it and the pair read as one filled box and one empty one.
 * It now stretches to whatever the panel needs, with a floor for the case
 * where the panel is the shorter of the two.
 *
 * The same rule covers before AND after: the input block and the output block
 * are the same element's slot, so the canvas cannot change size when the job
 * finishes and nothing below it moves. */
/* ===========================================================================
 * THE WORK BLOCK — the two-column layout every tool shares.
 * SETTLED. Do not re-derive this per tool; change it here or not at all.
 * ===========================================================================
 *
 * THE CONTRACT
 * ------------
 *   Both columns are exactly --dp-workblock-h tall, and they start and end on
 *   the same pixel, in EVERY state:
 *
 *     left  (.dp-canvas)       input block  ->  output block
 *     right (.dp-rail)         action panel ->  result panel
 *
 *   Nothing scrolls but the contents of a box. The page does not move when a
 *   job finishes. Empty tool pages are not held open.
 *
 * WHY IT IS DECLARED, NOT DERIVED
 * -------------------------------
 * A box that sizes to its contents is one height while asking for settings and
 * another while showing a result — measured at 544 before a job and 415 after.
 * Stretching one column to the other inherits that. A declared height is the
 * only thing that makes input and output identical.
 *
 * 600 was measured against the tallest state any panel reaches (PDF to Word
 * with OCR chosen: 642 before its copy was tightened), so nothing has to
 * scroll to fit.
 *
 * THE FOUR TRAPS, ALL OF WHICH BIT
 * --------------------------------
 * 1. A flex height only reaches the element it is set on. The bordered box the
 *    eye compares sits THREE levels below the canvas — .dp-body, then
 *    .dp-grid-host (editing tools) or .dp-preview (converters). Measured on
 *    Merge: canvas 600, .dp-body 280, .dp-grid-host 266, visible box 238.
 *    Every wrapper must pass the height down.
 * 2. The column, not the box, carries the height: editing tools put a toolbar
 *    above and a status line below, inside the same column.
 * 3. The status line must be INSIDE the border, or the box ends short while the
 *    text runs past. Hence the border lives on .dp-grid-host, not the viewport.
 * 4. .dp-grid-viewport is used by BOTH workspaces. Any rule for the editing
 *    grid must be scoped `.dp-grid-host > .dp-grid-viewport`, or it strips the
 *    converters' file block.
 *
 * AFTER ANY CHANGE HERE, CHECK ALL OF: merge-pdf, split-pdf, crop-pdf,
 * pdf-to-word, compress-pdf, password-protect-pdf — in the file state AND the
 * result state. These selectors are shared; one tool proves nothing.
 */
.dp-workspace { --dp-workblock-h: 600px; }

/* Only once there is work. Before a file is chosen the page is a drop zone;
   holding it open left a screen of empty white under it. */
.dp-workspace.has-pages .dp-canvas {
  display: flex;
  flex-direction: column;
  height: var(--dp-workblock-h);
}
.dp-workspace.has-pages .dp-actionpanel,
.dp-workspace.has-pages .dp-result { height: var(--dp-workblock-h); }

/* Trap 1: the wrappers between the column and the box. */
.dp-workspace .dp-body { flex: 1 1 auto; min-height: 0; }
.dp-workspace .dp-grid-host { min-height: 0; display: flex; flex-direction: column; }
.dp-workspace .dp-grid-viewport {
  flex: 1 1 auto;
  height: auto;
  min-height: 0;
  max-height: none;
}

/* Trap 3: one bordered box per column, containing everything that column owns.
   .dp-grid-host also carried a top margin AND 100% height, so it overran its
   parent by exactly that margin; as a grid item it simply stretches. */
.dp-workspace.has-pages .dp-grid-host {
  height: auto;
  align-self: stretch;
  margin-top: 0;
  border: 1px solid var(--dp-border);
  border-radius: var(--dp-radius);
  background: #fff;
  overflow: hidden;
}
/* Trap 4: scoped to the editing grid only. */
.dp-workspace.has-pages .dp-grid-host > .dp-grid-viewport {
  border: 0;
  border-radius: 0;
  background: transparent;
}
.dp-workspace.has-pages .dp-grid-status {
  flex: 0 0 auto;
  margin: 0;
  padding: 0 12px 12px;
}

/* The rail does not scroll on any tool. It caps itself at the viewport, which
   made sense when it sized to its contents; against a declared 600 that cap is
   smaller than the panel and clipped the panel's own title. */
.dp-workspace .dp-rail {
  max-height: none;
  overflow-y: visible;
}

/* A column: the cards scroll, the caption stays put at the bottom. */
.dp-filespace {
  padding: 14px;
  flex: 1 1 auto;
  min-height: 0;
  max-height: none;
  display: flex;
  flex-direction: column;
}
.dp-filespace .dp-grid-track {
  grid-template-columns: repeat(auto-fill, minmax(180px, 200px));
  gap: 14px;
  justify-content: start;
  /* start, not stretch: with one row of cards in a tall box, stretch made each
     card as tall as the box and the fixed 250 was ignored. */
  align-content: start;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}
.dp-filespace .dp-preview-count { flex: 0 0 auto; margin-top: 10px; }

/* ONE FIXED HEIGHT FOR BOTH CARDS.
   The file card grew with its filename while the upload card did not, so the
   pair sat at two different heights and the row looked broken. The height is
   declared once here and both use it; the cover takes whatever the caption
   leaves. */
.dp-filespace .dp-grid-track { --dp-filecard-h: 250px; }

/* A WEB PAGE PREVIEW EARNS MORE ROOM THAN A FILE CARD.
 *
 * The 200x250 card is sized for a grid of them — twenty files at a glance. A
 * URL job has exactly one source and the canvas beside it is empty, so the same
 * card reads as a thumbnail of nothing much. This widens THE TRACK's two sizing
 * values rather than restyling the card, so it is still the same component with
 * the same rules.
 *
 * 420 x 330 is not arbitrary: the cover ends up about 418 x 260, and the server
 * crops its screenshot to 0.62 of the width — 418 x 259. The picture therefore
 * fills the cover with nothing cropped away. */
.dp-filespace.is-webpage .dp-grid-track {
  grid-template-columns: minmax(280px, 420px);
  --dp-filecard-h: 330px;
}
/* THE SWAP BUTTON IS A LINE, NOT A SECOND CARD.
 *
 * `.dp-file-swap` takes the card's full height so that in a BATCH it sits
 * level with the files beside it. There is no batch here — one address, one
 * card, one column — so it inherited 330px and became a dashed rectangle
 * bigger than the preview it belongs to, and pushed the canvas into scrolling.
 * Here it is what it actually is: one line under the card. */
.dp-filespace.is-webpage .dp-file-swap {
  height: auto;
  padding: 9px 12px;
  flex-direction: row;
  gap: 8px;
}
.dp-filespace.is-webpage .dp-addmore-plus { font-size: 15px; }

.dp-filecard {
  display: flex;
  flex-direction: column;
  height: var(--dp-filecard-h);
  border: 1px solid var(--dp-border);
  border-radius: var(--dp-radius);
  background: #fff;
  overflow: hidden;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .08);
}

/* The cover is page one, shown small and top-anchored: enough to recognise the
   document, not so much that it reads as something to work on. */
.dp-filecard-cover {
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  background: var(--dp-grad-a);
  display: flex;
  align-items: flex-start;
  justify-content: center;
}
.dp-filecard-thumb { width: 100%; height: auto; display: block; }
/* The no-cover case: centred glyph rather than a page that never arrived. */
.dp-filecard-cover.is-generic { align-items: center; }

/* A PHOTO cover, not a document cover.
 *
 * The rule above is written for a rendered PDF page: full width, natural
 * height, top-aligned, and the cover clips whatever hangs below — which is
 * right, because the top of a page is the part that identifies it.
 *
 * A photograph is not a page. Landscape images overflowed the cover by 23px
 * (measured: scrollHeight 149 against clientHeight 126), so every thumbnail sat
 * with its bottom sliced off and a strip of empty panel underneath. `cover`
 * fills the box from the centre instead, which is how a photo thumbnail is
 * expected to behave and never distorts the aspect ratio. */
.dp-filecard-cover.is-photo { align-items: center; }
.dp-filecard-thumb.is-photo {
  width: 100%;
  height: 100%;
  /* CONTAIN, NOT COVER — David, 2026-08-22.
   *
   * `cover` fills the tile and crops whatever does not fit, which is the right
   * instinct for a photo thumbnail and the wrong one here: a wide logo came
   * back reading "TOOI / PIVO" with both ends sliced off. On these tools the
   * thumbnail is not decoration, it is the thing you look at to choose a
   * preset, so it has to show the WHOLE image.
   *
   * The note this replaces warned that fitting leaves "a strip of empty panel"
   * — that was the document rule (natural height, top-aligned), which
   * overflowed. `contain` cannot overflow; it letterboxes, and the checkerboard
   * below turns that empty strip into something deliberate. */
  object-fit: contain;
  object-position: center;
}

/* THE LETTERBOX DOUBLES AS A TRANSPARENCY INDICATOR.
 *
 * Once the image is contained rather than cropped, the tile shows band(s) of
 * background. A flat colour reads as an accident. The checkerboard every image
 * editor uses reads as "this is the empty area" — and, because these tools
 * take transparent PNGs and can remove backgrounds, it also shows THROUGH a
 * transparent image, so you can see at a glance whether yours has an alpha
 * channel before choosing Keep or Remove.
 *
 * Pure CSS gradients, no asset: 10px squares, very low contrast so it never
 * competes with the artwork. */
.dp-filecard-cover.is-photo {
  background-color: #fff;
  background-image:
    linear-gradient(45deg, rgba(0,0,0,.045) 25%, transparent 25%, transparent 75%, rgba(0,0,0,.045) 75%),
    linear-gradient(45deg, rgba(0,0,0,.045) 25%, transparent 25%, transparent 75%, rgba(0,0,0,.045) 75%);
  background-size: 20px 20px;
  background-position: 0 0, 10px 10px;
}
/* A WEB PAGE IS BOTH AT ONCE.
 *
 * Its screenshot is landscape, so it needs the photo rule's fill or it leaves a
 * band of empty cover beneath it. But it is a page, so the part that identifies
 * it is the top — the masthead and the headline — and centring the crop throws
 * exactly that away. Fill like a photo, anchor like a document. */
.dp-filecard-thumb.is-page { object-position: top center; }

.dp-filecard-meta {
  padding: 10px 12px 12px;
  border-top: 1px solid var(--dp-border);
}
.dp-filecard-name {
  margin: 0;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--dp-text);
  /* Two lines, then ellipsis: a long filename must not make one card taller
     than the upload tile next to it. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}
.dp-filecard-facts {
  margin: 4px 0 0;
  font-size: 12.5px;
  color: var(--dp-muted);
  /* Tabular figures: these numbers are rewritten as each file finishes, and
     proportional digits made the row twitch sideways on every update. */
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------------------
 * THE RESULT CARD
 *
 * Rebuilt because the card was half text: a 126px thumbnail above a 122px
 * stack of filename, sizes, percentage and a full-width button, all at similar
 * weight, so nothing led and the picture — the thing that identifies the file —
 * had less room than its own caption.
 *
 * Now: the saving is a badge ON the image, the filename is one quiet line, and
 * the sizes share a row with a compact download. Three elements, one each for
 * "which file", "what happened", "give it to me".
 * ------------------------------------------------------------------------ */

/* `position: relative` IS global, and deliberately so: it only establishes a
   containing block for the badge below, which no other tool renders. Nothing
   about the PDF cards moves. */
.dp-filecard-cover { position: relative; }

.dp-filecard-badge {
  position: absolute;
  top: 8px;
  right: 8px;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 700;
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  letter-spacing: .01em;
  /* Readable over any photograph, which is the whole difficulty with a badge
     on user content: a light pill on a light sky vanishes. */
  background: rgba(21, 87, 36, .94);
  color: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .28);
}
.dp-filecard-badge.is-neutral {
  background: rgba(35, 32, 31, .82);
  font-weight: 600;
}

/* The compare button, opposite the badge: the claim on one corner, the way to
   check it on the other. */
.dp-compare-open {
  position: absolute;
  top: 8px;
  left: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px; height: 30px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgba(35, 32, 31, .72);
  color: #fff;
  cursor: pointer;
  /* Always visible, not hover-only: hover does not exist on a phone, and a
     control nobody can find is the same as one that is not there. */
  opacity: .92;
  transition: background .15s ease, opacity .15s ease;
}
.dp-compare-open:hover { background: rgba(35, 32, 31, .9); opacity: 1; }
.dp-compare-open:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* ---------------------------------------------------------------------------
 * BEFORE / AFTER
 * ------------------------------------------------------------------------ */
.dp-compare-overlay {
  position: fixed;
  inset: 0;
  z-index: 10050;   /* above the site's floating widgets (the chat panel is 10002) */
  background: rgba(18, 16, 15, .72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.dp-compare-sheet {
  background: #fff;
  border-radius: 14px;
  max-width: min(1000px, 100%);
  max-height: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: 0 18px 60px rgba(0, 0, 0, .35);
}
.dp-compare-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--dp-border);
}
.dp-compare-name {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dp-compare-close {
  margin-left: auto;
  border: 0;
  background: none;
  font-size: 26px;
  line-height: 1;
  padding: 0 6px;
  color: var(--dp-muted);
  cursor: pointer;
}
.dp-compare-close:hover { color: var(--dp-text); }

.dp-compare-stage {
  position: relative;
  overflow: hidden;
  min-height: 0;
  /* A checkerboard, so a transparent PNG reads as transparent rather than as
     whatever colour happens to be behind the dialog. */
  background:
    conic-gradient(#f2efed 25%, #fff 0 50%, #f2efed 0 75%, #fff 0) 0 0 / 18px 18px;
  cursor: ew-resize;
  touch-action: none;              /* the drag is ours, not the page's scroll */
  user-select: none;
}
.dp-compare-img {
  display: block;
  max-width: 100%;
  max-height: calc(100vh - 200px);
  width: auto;
  height: auto;
}
.dp-compare-before {
  position: absolute;
  inset: 0 auto 0 0;
  width: 50%;
  overflow: hidden;
}
/* The clipped copy must be laid out at the STAGE's width, not its own box's —
   otherwise narrowing the wrapper squeezes the image and the two halves stop
   being the same picture. */
.dp-compare-before .dp-compare-img {
  position: absolute;
  top: 0; left: 0;
  max-width: none;
  width: auto;
  height: 100%;
}
.dp-compare-handle {
  position: absolute;
  top: 0; bottom: 0;
  width: 2px;
  margin-left: -1px;
  background: #fff;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .25);
  cursor: ew-resize;
}
.dp-compare-handle:focus-visible { outline: 2px solid var(--dp-orange); }
.dp-compare-grip {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 34px; height: 34px;
  border-radius: 999px;
  background: #fff;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .3);
}
.dp-compare-grip::before,
.dp-compare-grip::after {
  content: '';
  position: absolute;
  top: 50%;
  width: 0; height: 0;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  transform: translateY(-50%);
}
.dp-compare-grip::before { left: 7px; border-right: 6px solid var(--dp-text); }
.dp-compare-grip::after { right: 7px; border-left: 6px solid var(--dp-text); }

.dp-compare-tag {
  position: absolute;
  bottom: 12px;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 600;
  color: #fff;
  background: rgba(18, 16, 15, .74);
  pointer-events: none;
  font-variant-numeric: tabular-nums;
}
.dp-compare-tag.is-before { left: 12px; }
.dp-compare-tag.is-after { right: 12px; }

/* LEFT, not centred.
 *
 * The card inherits `text-align: center` from the page, which was fine when the
 * meta was one centred filename. Now there is a name above a row of numbers and
 * a button: centring the name and left-aligning the row beneath it gave every
 * card two competing left edges. One edge, reading top to bottom. */
.dp-batchspace .dp-filecard-meta { text-align: start; }

/* Scoped, not global: the same `.dp-filecard-name` draws the file cards on
   Merge, Split and PDF to Word, where the filename IS the card's headline and
   two lines is right. Here it has been demoted to a label under a result badge,
   so one line with an ellipsis is enough — and it keeps every card the same
   height whatever the name length. */
.dp-batchspace .dp-filecard-name {
  -webkit-line-clamp: 1;
  font-size: 13px;
  font-weight: 600;
}

/* THE IMAGE CARDS ARE WIDER AND TALLER THAN THE PDF ONES.
 *
 * Scoped to .dp-batchspace rather than changed globally, because the shared
 * grid also draws the file cards on Merge, Split and PDF to Word — and those
 * carry a name and a page count, where these carry a name, a before, an after,
 * optional extra formats and a download.
 *
 * At the shared 200x250 that content wrapped onto three lines and the caption
 * stood 135px against a 113px thumbnail: a picture card where the picture had
 * less room than its own text. 240 wide fits the action row on one line, and
 * the extra height goes to the image.
 */
/* THREE COLUMNS, FIXED — not auto-fill.
 *
 * auto-fill packed 4 cards at one width and 2 at another, so the same batch
 * looked like a different tool depending on the window. Three equal columns
 * keep the rhythm steady and give each card enough room for its action row on
 * one line. */
.dp-batchspace .dp-filespace .dp-grid-track {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.dp-batchspace .dp-filecard { height: 268px; }

/* Two on a tablet, one on a phone: three 1fr columns inside a phone width would
   leave each card too narrow for "552 KB → 157 KB" beside a button. */
@media (max-width: 900px) {
  .dp-batchspace .dp-filespace .dp-grid-track { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .dp-batchspace .dp-filespace .dp-grid-track { grid-template-columns: minmax(0, 1fr); }
}

.dp-filecard-actions {
  display: flex;
  align-items: center;
  /* nowrap: this row is exactly two things, and it must be the same height on
     every card whatever the numbers say. */
  flex-wrap: nowrap;
  gap: 6px;
  margin-top: 6px;
}
/* The download menu. Fixed-positioned by JS so it is not clipped by the card's
   own `overflow: hidden`; everything here is appearance only. */
.dp-dlmenu { margin-left: auto; position: relative; }
.dp-dlmenu-caret {
  width: 0; height: 0;
  margin-left: 1px;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 4.5px solid currentColor;
  opacity: .75;
}
.dp-dlmenu-list {
  position: fixed;
  z-index: 1000;
  min-width: 190px;
  padding: 5px;
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .16);
  display: flex;
  flex-direction: column;
  text-align: start;
}
.dp-dlmenu-list[hidden] { display: none; }
.dp-dlmenu-item {
  border: 0;
  background: none;
  text-align: start;
  padding: 9px 11px;
  border-radius: 7px;
  font-size: 13.5px;
  color: var(--dp-text);
  cursor: pointer;
  white-space: nowrap;
  /* Format left, size right — the two things being compared, on one line each
     so the eye can run down the column of numbers. */
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 18px;
}
.dp-dlmenu-fmt { font-weight: 600; letter-spacing: .02em; }
.dp-dlmenu-size {
  color: var(--dp-muted);
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
}
/* The smallest option, named rather than left to be worked out. */
.dp-dlmenu-size.is-best { color: #2f6f2f; font-weight: 700; }
.dp-dlmenu-item:hover { background: var(--dp-grad-a); }
.dp-dlmenu-item:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: -2px; }
/* Inside the action row the sizes are a span, not a paragraph: they sit beside
   the download rather than above it, which is what finally made the picture
   taller than its caption instead of the other way round. */
.dp-filecard-actions .dp-filecard-facts { margin: 0; }

/* The extra formats. Chips, because they are alternatives to one another and
   secondary to the file the user actually asked for.
 *
 * SCOPED TO THE FILE CARD, because `.dp-chip` is not this block's to own.
 * Written unscoped, it sat later in the file than the home page's own chip
 * rule and quietly restyled "Recently used" — 11.5px grey text on a control
 * that is a primary navigation link there. See the note above .dp-chips. */
.dp-filecard .dp-chip {
  border: 1px solid var(--dp-border);
  background: #fff;
  color: var(--dp-muted);
  border-radius: 999px;
  padding: 4px 9px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .02em;
  cursor: pointer;
  font-variant-numeric: tabular-nums;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.dp-filecard .dp-chip:hover { background: var(--dp-grad-a); color: var(--dp-text); }
.dp-filecard .dp-chip:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: 2px; }

.dp-filecard-dl {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 6px 10px;
  border: 1px solid var(--dp-border);
  border-radius: 8px;
  background: #fff;
  color: var(--dp-text);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  position: relative;
  transition: background .15s ease, border-color .15s ease;
}
.dp-filecard-dl:hover { background: var(--dp-grad-a); border-color: var(--dp-orange); }
.dp-filecard-dl:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: 2px; }
/* The visible control is 30px tall, which is right next to two others on a
   200px card. The TAP TARGET is extended to 44px with a pseudo-element instead
   of by padding, so the button meets the touch minimum without the card
   growing to fit it. */
.dp-filecard-dl::after {
  content: '';
  position: absolute;
  left: 0; right: 0;
  top: 50%;
  height: 44px;
  transform: translateY(-50%);
}
.dp-filecard-dl svg { flex: 0 0 auto; }

/* THE LABEL IS HIDDEN VISUALLY, NOT REMOVED.
 *
 * Three fixed columns make each card 223px, and its meta 197px inside. A
 * "Download" button with its word takes 117px, leaving 74px for "548 KB →
 * 156 KB" — which needs about 120 and therefore wrapped onto a second line,
 * putting the whole card back out of proportion.
 *
 * The word is clipped rather than deleted: the button keeps `aria-label` and
 * `title`, so screen readers and hover both still name it, and the download
 * glyph plus a disclosure caret is an unambiguous pair. This is also the shape
 * of the control in the reference David sent.
 */
/* :not(.dp-dlmenu-caret) — the caret is a span too, and hiding it took away the
   only sign that the button opens anything. */
.dp-batchspace .dp-filecard-dl > span:not(.dp-dlmenu-caret) {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
/* The numbers are the point of the row; never let them wrap. */
.dp-batchspace .dp-filecard-actions .dp-filecard-facts {
  white-space: nowrap;
  min-width: 0;
}

/* Drop one file from a batch. Sits on the card rather than under it, so a row
   of eight does not grow a row of eight buttons beneath it. */
.dp-filecard { position: relative; }
.dp-filecard-remove {
  position: absolute;
  top: 6px; right: 6px;
  width: 24px; height: 24px;
  display: flex; align-items: center; justify-content: center;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, .92);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .2);
  color: var(--dp-text);
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity .12s;
}
/* Revealed on hover or focus — always reachable by keyboard, never clutter. */
.dp-filecard:hover .dp-filecard-remove,
.dp-filecard-remove:focus-visible { opacity: 1; }
.dp-filecard-remove:hover { background: #fff; color: #b3261e; }

/* The batch scrolls inside the track (see .dp-filespace above), not on the
   box itself — two nested scrollers gave two scrollbars. */

/* The same declared height, so the two sit as a pair rather than a stack. */
.dp-file-swap {
  min-height: 0;
  height: var(--dp-filecard-h);
  padding: 0 12px;
  text-align: center;
  line-height: 1.35;
}

/* ---- the option cards --------------------------------------------------
 * One choice, both options visible. A lone checkbox made the user work out
 * which side of the distinction their file was on; two named options with a
 * sentence each answer it. This is the shape both competitors use — the
 * difference is that neither of ours carries a Premium badge.
 */
.dp-cards {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.dp-card {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 11px 38px 11px 12px;
  border: 1.5px solid var(--dp-border);
  border-radius: 10px;
  background: #fff;
  cursor: pointer;
  transition: border-color .15s, background .15s;
}
.dp-card:hover { border-color: #f3c3ad; }

/* The radio is the real control and stays focusable; it is only visually
   replaced by the tick, never removed from the accessibility tree. */
.dp-card-radio {
  position: absolute;
  opacity: 0;
  width: 1px; height: 1px;
  margin: 0;
}
.dp-card:has(.dp-card-radio:checked) {
  border-color: var(--dp-orange);
  background: var(--dp-grad-a);
}
.dp-card:has(.dp-card-radio:focus-visible) {
  outline: 3px solid var(--dp-orange);
  outline-offset: 2px;
}

.dp-card-body { display: block; }
.dp-card-title {
  display: block;
  font-size: 14.5px;
  font-weight: 700;
  color: var(--dp-text);
}
.dp-card-desc {
  display: block;
  margin-top: 3px;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--dp-muted);
}

/* FIVE CARDS HAVE TO FIT THE 600px BLOCK.
 *
 * The action panel is a fixed-height block and must not grow an inner
 * scrollbar. Compress PDF is the densest panel in the suite: a four-line
 * privacy note, a heading and FIVE options, which overflowed by 160px at the
 * default card size.
 *
 * Two savings, both scoped to the server rail so no other tool's cards change:
 *   - the duplicate heading goes (see below), worth ~30px
 *   - the cards tighten from 67px to ~55px each, worth ~60px
 * Everything the user reads is still there; only the air around it is gone. */
.dp-serverspace .dp-cards { gap: 5px; }
.dp-serverspace .dp-card { padding: 8px 32px 8px 11px; }
.dp-serverspace .dp-card-title { font-size: 14px; line-height: 1.25; }

.dp-serverspace .dp-card-desc { margin-top: 1px; font-size: 12px; line-height: 1.3; }

/* A card with NO second line does not need the height of one that has one.
   Compress-to-size's five options are bare numbers ("100 KB"), and at the
   two-line size they pushed the panel into a scrollbar the moment "Custom"
   revealed its extra row. :not(:has()) so this applies only to the title-only
   case and never shortens a card whose description it would then crowd. */
.dp-serverspace .dp-card:not(:has(.dp-card-desc)) {
  padding-top: 5px;
  padding-bottom: 5px;
}
/* The unit sits beside the number and its "Unit" label says nothing the
   KB/MB control does not already say. */
.dp-serverspace .dp-field[data-field="unit"] > label { display: none; }
.dp-serverspace .dp-field[data-field="size"] .dp-field-help { margin-top: 2px; }

/* WHEN "CUSTOM" IS CHOSEN, THE PRESET EXPLANATIONS STOP EARNING THEIR HEIGHT.
 *
 * Revealing the number input adds a row, and the panel is a fixed 600px that
 * must never scroll. Something has to give, and it is not going to be an
 * option or the privacy notice: it is four second-lines the user has already
 * read and acted on by deciding to type their own number. The titles — 50 KB,
 * 100 KB, 200 KB, 500 KB — stay, so nothing becomes unidentifiable.
 *
 * Keyed off the size field being visible, which is exactly the condition that
 * created the problem. */
.dp-serverspace .dp-oppanel:has(.dp-field[data-field="size"]:not([hidden])) .dp-card-desc {
  display: none;
}
/* The card directly above the input already says "Custom size", so the label
   is redundant ON SCREEN — but `display: none` would strip it from the
   accessibility tree too and leave the number input with no accessible name.
   Hidden visually, kept for screen readers. */
.dp-serverspace .dp-field[data-field="size"] > label {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.dp-serverspace .dp-cards:not(:has(.dp-card-desc)) { gap: 4px; }

/* ---- chips: a radio group for SHORT labels -------------------------------
 *
 * Cards give each option a full-width row, which is right when the option
 * needs a sentence and wasteful when it is "100 KB". Compress-to-a-size offers
 * eight targets — Zamzar offers ten — and eight stacked rows do not fit a
 * panel that must never scroll. Wrapped chips take three rows instead.
 *
 * The radio itself stays in the DOM and focusable; only its default painting
 * is replaced, so keyboard and screen-reader behaviour is the same as cards. */
/* SCOPED TO THE OP PANEL. Everything from here down describes a radiogroup
 * inside a tool's settings panel, and none of it should reach a plain link
 * that happens to be called a chip. Unscoped, `flex: 0 0 100%` turned the home
 * page's "Recently used" row into six full-width stacked bars with centred
 * grey text — the widest possible presentation of the shortest possible
 * labels. The generic pill at the top of this file is the one the home page
 * wants; this is a different control that borrowed its name. */
.dp-oppanel .dp-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.dp-oppanel .dp-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* The generic pill sets min-height: 44px because it is a tap target on the
     home page. In here the chip is one row of a settings panel that must never
     scroll, and inheriting 44px pushed image-converter's fifteen chips 21px
     past the bottom. Height comes from the padding below, deliberately. */
  min-height: 0;
  /* ONE PER ROW, matching the Custom-size button beneath them.
     A three-across grid made the sizes look like a different kind of control
     from the one below it; stacked full-width rows read as one list of
     equal choices, which is what they are. */
  flex: 0 0 100%;
  padding: 8px 10px;
  border: 1.5px solid var(--dp-border);
  border-radius: 9px;
  background: #fff;
  cursor: pointer;
  transition: border-color .12s ease, background .12s ease;
}
.dp-oppanel .dp-chip:hover { border-color: #f3c3ad; }
.dp-chip-radio {
  position: absolute;
  opacity: 0;
  width: 1px; height: 1px;
  margin: 0;
}
.dp-chip-label {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--dp-text);
  white-space: nowrap;
}
.dp-chip:has(.dp-chip-radio:checked) {
  border-color: var(--dp-orange);
  background: var(--dp-grad-a);
}
.dp-chip:has(.dp-chip-radio:checked) .dp-chip-label { color: var(--dp-orange); }
.dp-chip:has(.dp-chip-radio:focus-visible) {
  outline: 3px solid var(--dp-orange);
  outline-offset: 2px;
}

/* SEVEN CONTROLS, AND CHIPS THAT STACK ONE PER ROW.
 *
 * The shared chip is full width because on the compressor it is one of three
 * long labels. Here there are four margin sizes, two orientations, two
 * declutter choices and two background choices — twenty rows for what is
 * really four questions, and the panel overflowed its body by 518px. These
 * labels are one or two words, so they sit several to a row. Scoped to this
 * op: no other panel's chips change. */
.dp-oppanel-webToPdf .dp-chip {
  flex: 1 1 auto;
  min-width: 0;
  padding: 7px 10px;
}
.dp-oppanel-webToPdf .dp-chip-label { font-size: 13px; }
.dp-oppanel-webToPdf .dp-field > label { margin-bottom: 3px; }
.dp-oppanel-webToPdf .dp-oppanel-rows { gap: 10px 12px; }
/* TWO SHORT QUESTIONS TO A ROW.
   Even with chips wrapping, seven stacked fields ran 206px past the body. The
   four narrow ones pair up; the two that carry wide chip sets keep the full
   width, because splitting those would only push their chips onto two lines
   again and save nothing. */
.dp-oppanel-webToPdf .dp-field { flex: 1 1 46%; min-width: 0; }
/* PAIRED FIELDS LINE UP AT THE TOP, NOT THE BOTTOM.
   A two-line label beside a one-line label pushed its control down, so the
   dropdown and the chips beside it sat on different baselines. */
.dp-oppanel-webToPdf .dp-oppanel-rows { align-items: flex-start; }
.dp-oppanel-webToPdf .dp-field > label { min-height: 18px; display: block; }
.dp-oppanel-webToPdf .dp-field[data-field="margin"] { flex: 1 1 100%; }
/* LIKE WITH LIKE, AND THE WIDE CHIP SETS ON THEIR OWN LINE.
 *
 * Layout's two chips ("Paper pages", "One long page") cannot both fit in a
 * half-width column, so paired with anything they wrapped to two lines and
 * left a hole beside them. Full width, they sit on one line.
 *
 * That frees the two DROPDOWNS to pair with each other — same control, same
 * height, no ragged edge — which is what the top of the panel should have
 * been all along. */
.dp-oppanel-webToPdf .dp-field[data-field="pagination"] { flex: 1 1 100%; }
/* THE DROPDOWNS TAKE THE FULL WIDTH, AND IT IS NOT ABOUT ENGLISH.
 *
 * Paired at 146px, "Letter landscape" rendered as "Letter landscap" — the text
 * runs under the arrow, which overlays the padding rather than sitting after
 * it. English is the SHORT case: the same option is "Letter Querformat" in
 * German and longer again in Russian, so a half-width select would clip in
 * most of the eighteen languages while looking fine in the one it was built
 * in. The panel has the vertical room; it does not have the horizontal room. */
.dp-oppanel-webToPdf .dp-field[data-field="screen"],
.dp-oppanel-webToPdf .dp-field[data-field="paper"] { flex: 1 1 100%; }
/* Still 121px over with seven fields, so the panel gives back what it can:
   the shared server panel sets a generous rhythm for four controls and this
   one has nearly twice that. Trimmed here only. */
.dp-oppanel-webToPdf .dp-field select,
.dp-oppanel-webToPdf .dp-field input[type="number"] { padding-top: 7px; padding-bottom: 7px; }
.dp-oppanel-webToPdf .dp-chip { padding: 5px 9px; }
.dp-oppanel-webToPdf .dp-oppanel-rows { gap: 5px 10px; }
.dp-oppanel-webToPdf .dp-field > label { font-size: 13px; margin-bottom: 2px; }

/* AN ADDRESS BAR, ABOVE THE DROP ZONE.
 *
 * HTML to PDF is given a link far more often than a file, so the link comes
 * first and the picker second — the order of the two intentions, not the order
 * the code happened to build them in. The "or" between them is a real divider
 * rather than a word floating in space, because the two paths are alternatives
 * and the eye should be able to see that without reading. */
.dp-urlrow {
  /* The drop zone's own width, so the two paths line up as one column
     rather than one wide box above a narrower one. */
  width: 100%;
  max-width: var(--dp-drop-max);
  margin: 18px auto 0;
  box-sizing: border-box;
}
.dp-urlrow-label {
  display: block;
  font-size: 14px;
  font-weight: 600;
  color: var(--dp-text);
  margin-bottom: 6px;
}
.dp-urlrow-input {
  width: 100%;
  padding: 13px 14px;
  font-size: 15px;
  border: 1.5px solid var(--dp-border);
  border-radius: 12px;
  background: #fff;
  color: var(--dp-text);
}
.dp-urlrow-input::placeholder { color: #6f6058; }
.dp-urlrow-input:focus {
  outline: none;
  border-color: var(--dp-orange);
  box-shadow: 0 0 0 3px rgba(250, 76, 16, .12);
}
.dp-urlrow.is-alone .dp-urlrow-or { display: none; }
.dp-urlrow-or {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 14px;
  font-size: 13px;
  color: var(--dp-muted);
  text-transform: lowercase;
}
.dp-urlrow-or::before,
.dp-urlrow-or::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--dp-border);
}

/* ---- the paste box, on Text to PDF ------------------------------------
 *
 * Same column and the same divider as the address row above, because it is the
 * same idea: a second way in, under the picker rather than instead of it.
 *
 * MONOSPACE ON PURPOSE. The output of this tool is monospaced — that is the
 * thing it does that the big converters do not — and a proportional box would
 * show the visitor a layout that is not the layout they will get. Columns
 * lined up with spaces, the reason people convert a log at all, either survive
 * in both places or neither.
 *
 * Only `.dp-textrow`; no shared selector is touched, so no other tool changes.
 */
.dp-textrow {
  width: 100%;
  max-width: var(--dp-drop-max);
  margin: 18px auto 0;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
/* Once it is the only thing on the canvas it IS the document, so it takes the
   whole 600px block the layout gives every tool — the bordered card on the
   left, the panel on the right, both ending on the same line. */
.dp-textrow.is-alone {
  flex: 1 1 auto;
  margin-top: 0;
  /* THE FULL COLUMN, NOT THE PICKER'S WIDTH.
     `--dp-drop-max` is 500px, which is right while this sits under a drop zone
     of the same width. Once the box is the only thing on the canvas it is the
     document, and holding it at 500 left 114px of white down each side of a
     728px column — on a monospace box, where width is columns and columns are
     the thing being previewed. */
  max-width: none;
}
.dp-textrow.is-alone .dp-textrow-or { display: none; }
.dp-textrow-or {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 14px;
  font-size: 13px;
  color: var(--dp-muted);
  text-transform: lowercase;
}
.dp-textrow-or::before,
.dp-textrow-or::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--dp-border);
}
.dp-textrow-label {
  display: block;
  font-size: 14px;
  font-weight: 600;
  color: var(--dp-text);
  margin-bottom: 6px;
}
.dp-textrow-input {
  width: 100%;
  flex: 1 1 auto;
  /* Tall enough to read as "paste something substantial here" before it grows,
     and never so short it is mistaken for a one-line field. */
  min-height: 190px;
  padding: 13px 14px;
  box-sizing: border-box;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace;
  /* 14px monospace, not the 15px the address field uses: a monospace face runs
     wider per character, and 15 put fewer columns on the line than the PDF
     will have. */
  font-size: 14px;
  line-height: 1.55;
  border: 1.5px solid var(--dp-border);
  border-radius: var(--dp-radius);
  background: #fff;
  color: var(--dp-text);
  resize: vertical;
}
.dp-textrow-input::placeholder { color: #6f6058; }
.dp-textrow-input:focus {
  outline: none;
  border-color: var(--dp-orange);
  box-shadow: 0 0 0 3px rgba(250, 76, 16, .12);
}
@media (max-width: 575px) {
  /* iOS zooms the page in on any field below 16px, and it does not zoom back
     out — the visitor is left scrolling sideways on a tool they came to use
     with one hand. Fewer columns on the line is the cheaper cost. */
  .dp-textrow-input { font-size: 16px; }
}

/* THE DECK PANEL CARRIES THREE TILE ROWS, WHICH IS ONE MORE THAN FITS.
 *
 * PDF to PowerPoint asks three questions — what the slides are made of, what
 * shape they are, how sharp the picture is — and at the shared tile size that
 * came to 501px against a 448px body: a settings rail with its own scrollbar,
 * which is the thing this suite does not do. Trimmed rather than cut down to
 * two questions, because all three are real. Scoped to this panel so no other
 * tile row changes size. */
.dp-deckset .dp-tile {
  padding: 6px 4px 5px;
  gap: 3px;
}
.dp-deckset .dp-tile-art svg { width: 22px; height: 22px; }
.dp-deckset .dp-field-help {
  font-size: 11.5px;
  line-height: 1.35;
  margin: 0;
}
.dp-deckset .dp-field > label { margin-bottom: 3px; }

/* SLIDER — a track and a box, for one value.
 *
 * Native <input type="range"> rather than a drawn track: it is keyboard
 * operable with the arrow keys, it is announced correctly, and `accent-color`
 * paints it in the brand orange in one line — the same trick the checkboxes
 * in this file already use. A custom-drawn slider gives up all three to look
 * identical. */
.dp-slider {
  display: flex;
  align-items: center;
  gap: 10px;
}
.dp-slider-range {
  flex: 1 1 auto;
  min-width: 0;
  height: 20px;                        /* a 20px band to grab, not a 4px line */
  margin: 0;
  cursor: pointer;
  /* Drawn rather than left to `accent-color`. That property paints the filled
     side orange and leaves the REST near-black in Chrome, which put a heavy
     dark bar through a panel made of warm off-whites. The track below is the
     same #f4ebe6 family as every other empty surface here. */
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
}
/* --dp-pct is set by the panel as the value moves: the filled length. */
.dp-slider-range::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 999px;
  background:
    linear-gradient(to right, var(--dp-orange) calc(var(--dp-pct, 50) * 1%),
                    #f0e4dd calc(var(--dp-pct, 50) * 1%));
}
.dp-slider-range::-moz-range-track {
  height: 6px;
  border-radius: 999px;
  background:
    linear-gradient(to right, var(--dp-orange) calc(var(--dp-pct, 50) * 1%),
                    #f0e4dd calc(var(--dp-pct, 50) * 1%));
}
.dp-slider-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 18px; height: 18px;
  margin-top: -6px;                    /* centred on a 6px track */
  border-radius: 50%;
  background: #fff;
  border: 2px solid var(--dp-orange);
  box-shadow: 0 1px 3px rgba(16, 24, 40, .18);
  transition: transform .12s ease;
}
.dp-slider-range::-moz-range-thumb {
  width: 18px; height: 18px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid var(--dp-orange);
  box-shadow: 0 1px 3px rgba(16, 24, 40, .18);
}
.dp-slider-range:active::-webkit-slider-thumb { transform: scale(1.12); }
.dp-slider-range:focus-visible::-webkit-slider-thumb {
  outline: 2px solid var(--dp-orange);
  outline-offset: 2px;
}
.dp-slider-range:focus-visible::-moz-range-thumb {
  outline: 2px solid var(--dp-orange);
  outline-offset: 2px;
}
/* Wide enough for "400" and no wider: the track is the control that matters,
   and every pixel the box takes comes off it. */
.dp-slider-num {
  flex: 0 0 62px;
  width: 62px;
  text-align: center;
  /* NO SPINNER. Chrome reserves ~16px inside the box for the up/down arrows
     and shows them on hover, which clipped "224" to "22". The arrows are also
     the one thing here that is genuinely redundant: the track beside them
     steps the same value, with a bigger target. */
  -webkit-appearance: textfield;
  -moz-appearance: textfield;
  appearance: textfield;
  /* Tabular figures, so the number does not shuffle its own width as it is
     dragged past 9, 99 and 100. */
  font-variant-numeric: tabular-nums;
}
.dp-slider-num::-webkit-outer-spin-button,
.dp-slider-num::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.dp-slider-suffix {
  flex: 0 0 auto;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--dp-muted);
}

/* TABS — the same radio group as the chips above, in the one shape chips
 * cannot take: a segmented switch that divides the panel.
 *
 * Chips are stacked full-width by the rule above, which is right for a list of
 * equal choices but wrong for a MODE, where the controls underneath change
 * with the selection. Three stacked bars read as three buttons; a segmented
 * strip reads as "you are in this section", which is what it means.
 *
 * Inner classes are shared with the chips (.dp-chip-radio / .dp-chip-label) so
 * there is one set of names for one control. Only the frame differs. */
.dp-oppanel .dp-tabs {
  display: flex;
  gap: 2px;
  padding: 4px;
  border-radius: 12px;
  /* A SHADE DEEPER THAN THE PANEL, ON PURPOSE.
     Filled with --dp-grad-a the track was the same #fff7f3 as the panel behind
     it, so the strip had no edges of its own and the white pill looked like a
     button floating on nothing. A tint of the brand orange makes the unpicked
     tabs read as a groove the active one sits IN. */
  background: rgba(250, 76, 16, .07);
  border: 1px solid rgba(250, 76, 16, .10);
}
.dp-oppanel .dp-tab {
  position: relative;
  flex: 1 1 0;
  min-width: 0;                       /* so a long label shrinks, not overflows */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 10px 8px;
  border-radius: 9px;
  cursor: pointer;
  /* THE THEME PUTS 5px UNDER EVERY <label>, AND A TAB IS A LABEL.
     That margin sat inside the groove, so all three tabs were pushed 5px above
     centre — 5px of gap above them, 10px below. */
  margin: 0;
  transition: background .16s ease, color .16s ease, box-shadow .16s ease;
}
/* The hidden radio stretched to the tab's full width and carried a 4px top
   margin of its own, which is what made the strip taller than its contents. */
.dp-oppanel .dp-tab .dp-chip-radio { width: 1px; margin: 0; }
.dp-oppanel .dp-tab .dp-chip-label {
  font-size: 13px;
  font-weight: 600;
  /* --dp-muted on this tint is about 4:1 — under the 4.5:1 body-text floor,
     and these are the labels the whole panel is steered by. */
  color: #6b5f59;
  /* Truncate rather than wrap: a tab that grows to two lines makes the strip
     taller than the row it sits in and the panel starts to scroll. */
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color .16s ease;
}
.dp-oppanel .dp-tab:hover { background: rgba(255, 255, 255, .55); }
.dp-oppanel .dp-tab:hover .dp-chip-label { color: var(--dp-text); }
.dp-oppanel .dp-tab:has(.dp-chip-radio:checked) {
  background: #fff;
  /* Two shadows, not one: a tight contact shadow to lift the pill off the
     groove and a soft one under it for depth. A single blurred shadow at this
     size reads as a smudge. */
  box-shadow: 0 1px 1px rgba(16, 24, 40, .04), 0 2px 6px rgba(250, 76, 16, .14);
}
.dp-oppanel .dp-tab:has(.dp-chip-radio:checked) .dp-chip-label { color: var(--dp-orange); }
.dp-oppanel .dp-tab:has(.dp-chip-radio:focus-visible) {
  outline: 2px solid var(--dp-orange);
  outline-offset: 1px;
}
/* The strip IS the section header, so the field's own caption above it would
   be a label for something that already says what it is. */
.dp-oppanel .dp-field-tabs > label { display: none; }


/* The privacy note is the other large block competing for the same 600px.
   TYPOGRAPHY ONLY — not a word of it is cut. It is the disclosure that this
   tool uploads, which is the one thing on the page that must not be trimmed to
   make room for a control. */
.dp-serverspace .dp-servernote {
  padding: 8px 10px;
  margin-bottom: 10px;
  font-size: 12.5px;
  line-height: 1.35;
}
.dp-serverspace .dp-servernote p { margin: 0; }

/* ONE HEADING, NOT TWO.
 *
 * The op panel prints `op.label` as its title AND the first field prints its
 * own label, and on these panels they say the same thing twice running:
 * "Compression level" over "How much to compress", "Target size" over "Target
 * size". The field's own label is the more useful of the two — it sits with
 * the control it names — so the panel title is the one that goes.
 *
 * Keyed off the FIRST field being a card group, not the only one: compress to
 * a size has three fields (the presets, plus size and unit revealed by
 * "Custom") and still needs exactly one heading. A panel whose first control
 * is not a card group keeps its title, because there the title is grouping
 * several unrelated settings rather than duplicating one. */
.dp-serverspace .dp-oppanel:has(.dp-oppanel-rows > .dp-field:first-child .dp-cards)
  .dp-oppanel-title,
.dp-serverspace .dp-oppanel:has(.dp-oppanel-rows > .dp-field:first-child .dp-chips)
  .dp-oppanel-title { display: none; }

/* The tick appears only on the chosen one — the competitor's own signal, and
   clearer than colour alone for anyone who cannot rely on it. */
.dp-card-tick {
  position: absolute;
  top: 12px; right: 12px;
  width: 18px; height: 18px;
  border-radius: 50%;
  border: 1.5px solid var(--dp-border);
  background: #fff;
}
.dp-card:has(.dp-card-radio:checked) .dp-card-tick {
  border-color: var(--dp-orange);
  background: var(--dp-orange) no-repeat center;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='11' viewBox='0 0 12 12'%3E%3Cpath d='M2 6.3l2.6 2.6L10 3.5' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* ---- the disclosure, now inside the action panel -----------------------
 * It states what happens to the file, so it belongs beside the button that
 * makes it happen — and beside the OCR checkbox that changes the answer. In a
 * 340px rail it needs to be a note rather than the banner it was on the wide
 * canvas: smaller type, tighter padding, and no centring rule inherited from
 * the canvas layout.
 */
.dp-actionpanel .dp-servernote {
  margin: 0 0 14px;
  padding: 10px 12px;
  gap: 9px;
  max-width: none;
  border-radius: 10px;
}
.dp-actionpanel .dp-servernote p { font-size: 13px; line-height: 1.45; }
.dp-actionpanel .dp-servernote svg { width: 16px; height: 16px; margin-top: 2px; }
.dp-actionpanel .dp-servernote[hidden] { display: none; }

/* Left under the grid, exactly where Merge puts "35 pages, 0 selected".
   It inherits .dp-grid-status for size and colour; only the alignment needs
   saying, because .dp-preview centres its contents for the output card. */
.dp-preview-count { text-align: start; }

/* ---- the finished file -------------------------------------------------
 * Shown where the source was, once there is no longer a decision to make
 * about the source. A .docx cannot be previewed — it is a ZIP of XML and no
 * browser renders it — so it is represented rather than faked: a sheet, the
 * format stamped on it, and the name it saves under.
 */
.dp-output {
  /* Fills the same slot the input block did, so the canvas neither changes
     size nor shifts when the job finishes — and starts level with the panel. */
  margin-top: 0;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 16px 20px;
  text-align: center;
  border-radius: var(--dp-radius);
  /* The suite's brand panel, so the finished state is recognisably ours
     rather than a bare white box. Same two stops every DocPivot panel uses. */
  background: linear-gradient(160deg, var(--dp-grad-a), var(--dp-grad-b));
}
.dp-output[hidden] { display: none; }

.dp-output-card {
  position: relative;
  display: inline-block;
  line-height: 0;               /* no baseline gap under the inline SVG */
  filter: drop-shadow(0 6px 16px rgba(16, 24, 40, .12));
}

.dp-output-sheet { display: block; }

/* Stamped across the lower corner of the sheet, the way a file type badge
   reads on a document icon. */
.dp-output-badge {
  position: absolute;
  left: 50%;
  bottom: 14px;
  transform: translateX(-50%);
  padding: 5px 14px;
  border-radius: 6px;
  background: var(--dp-orange);
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .04em;
  line-height: 1.2;
  white-space: nowrap;
}

.dp-output-name {
  margin: 18px auto 0;
  max-width: 380px;
  font-size: 14px;
  line-height: 1.45;
  color: var(--dp-muted);
  /* Break at the hyphens and underscores a real filename is full of, rather
     than mid-token. */
  overflow-wrap: anywhere;
}

/* .dp-body wraps the grid and already sits below the shared head gap that
   .dp-grid-host applies, so its own 10px margin was stacking on top of it and
   pushing the left column 9px below the right. */
.dp-body { margin-top: 0; }

/* The download button carries an icon: it is the one action the whole flow
   builds towards, and it should not look like the "Start over" next to it. */
.dp-result-download {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

/* ---- drag to reorder --------------------------------------------------- */

.dp-page[draggable="true"] { cursor: grab; }
.dp-page.is-dragging {
  opacity: .4;
  cursor: grabbing;
}

/* The insertion line. A line between tiles, not a highlight ON one — the page
   is going BETWEEN two pages, and highlighting a tile reads as "replace this". */
.dp-page.is-drop-before::after,
.dp-page.is-drop-after::after {
  content: '';
  position: absolute;
  top: -4px;
  bottom: -4px;
  width: 3px;
  border-radius: 2px;
  background: var(--dp-orange);
  z-index: 2;
}
/* Logical sides: in Arabic, Urdu and Persian the grid runs right to left, so
   "before this page" is its RIGHT edge. Physical left/right drew the line on
   the opposite side from where the page then landed. */
.dp-page.is-drop-before::after { inset-inline-start: -8px; }
.dp-page.is-drop-after::after  { inset-inline-end: -8px; }

/* Skipped-file detail under a warning notice. */
.dp-notice-head { margin: 0; font-weight: 600; }

.dp-notice-list {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  font-size: 14px;
}
.dp-notice-list li {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  padding: 4px 0;
  border-top: 1px solid rgba(0, 0, 0, .06);
}
.dp-notice-list li:first-child { border-top: 0; }

.dp-notice-file {
  font-weight: 600;
  word-break: break-all;   /* a long filename must not widen the rail */
}
.dp-notice-reason { opacity: .85; }

/* Server tools put their chooser, progress and disclosure in the canvas, so
   those blocks size to the work column rather than the old full-page width. */
.dp-serverspace .dp-servernote,
.dp-serverspace .dp-chosen,
.dp-serverspace .dp-jobprogress { max-width: var(--dp-drop-max); margin-left: auto; margin-right: auto; }

/* An informational notice — "you need one more file" — is guidance, not a
   problem, so it does not wear the warning colour. */
.dp-notice.is-info {
  color: #1f4b73;
  background: #eef5fb;
  border: 1px solid #cfe0ef;
  border-radius: 10px;
  padding: 10px 12px;
}

/* ---- Split PDF -----------------------------------------------------------
 *
 * Scoped to `.dp-oppanel-split`, so the shared chip and field rules keep doing
 * exactly what compress-to-a-size needs them to do. Changing one tool must not
 * change another, and a generated panel has no other seam to style through. */

/* Two across. The four modes ARE the tool, so all four must be visible at
   once — stacked full-width chips (the compress-to-a-size shape) would push
   the pages-per-file box and the file count below the fold, and the action
   panel must never scroll. */
.dp-oppanel-split .dp-chip {
  flex: 1 1 calc(50% - 3px);
}
.dp-oppanel-split .dp-chip-label {
  white-space: normal;      /* "Cut before pages" wraps rather than overflows */
  text-align: center;
  line-height: 1.25;
}

/* "12 files will be created." The one piece of feedback the settings give
   before the files exist. Sits under the panel, quiet until it has to warn. */
.dp-split-count {
  margin: 8px 0 0;
  font-size: 13px;
  line-height: 1.35;
  color: var(--dp-muted, #64748b);
}
.dp-split-count.is-warn {
  color: #8a4b12;
  font-weight: 600;
}

/* Cut markers on the page grid.
 *
 * The document is the honest place to show where it breaks. iLovePDF labels
 * every thumbnail with its range, Sejda colour-codes the sections, Smallpdf
 * puts scissors between pages — and we had a grid showing none of it, so
 * "every 3 pages" was a number the reader had to picture unaided.
 *
 * Pages OUTSIDE every range carry no mark, which is the truth: in ranges mode
 * they are not in any output file. */
.dp-page.is-part { --dp-page-part: var(--dp-orange); }
.dp-page.is-part::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 4px;
  background: var(--dp-page-part);
  border-radius: 0 0 10px 10px;
  pointer-events: none;
}
/* There is deliberately no separate "cut line" mark. One was built and then
   removed: the badge and the coloured band already say which part a page is
   in, so the boundary is wherever the colour changes. A third marker for the
   same fact was ink with no reader. */
.dp-page-part {
  position: absolute;
  top: 6px; left: 6px;
  padding: 2px 7px;
  font-size: 11px;
  font-weight: 700;
  line-height: 1.4;
  color: #fff;
  background: var(--dp-page-part);
  border-radius: 999px;
  pointer-events: none;
}
/* Not on the last page of a part: one badge per tile is already enough ink. */
.dp-page.is-part .dp-page-num { background: rgba(0, 0, 0, .55); }

/* A caveat attached to a finished job — "two pages are over your limit on
   their own". Warm, not alarming: the work succeeded, this is a footnote to
   what the user is taking away. */
.dp-result-note {
  margin: 10px auto 0;
  max-width: 46ch;
  font-size: 13px;
  line-height: 1.45;
  color: #8a4b12;
  /* A NOTE MUST NEVER OUTGROW ITS CARD.
     `max-width` only limits where the text is ALLOWED to wrap; it cannot break
     a token that has no break opportunity in it. The signing record carries a
     64-character SHA-256, which is exactly such a token, and it ran straight
     out through the right-hand edge of the result panel and off the card.
     A filename or URL in any other note would have done the same, so this is a
     safety net for the shared class rather than a fix for one string. */
  overflow-wrap: anywhere;
  word-break: break-word;
}

/* Cut here — the scissors on a page tile.
 *
 * Sejda and Smallpdf both let you place cuts by clicking the document, and it
 * is far faster than reading page numbers off thumbnails and typing them.
 * Quiet until the tile is hovered or the button is focused, because 500 pages
 * of permanently visible scissors is noise; always visible once a cut is
 * placed there, because a control that hides the state it set is a trap. */
.dp-page-scissors {
  position: absolute;
  top: 6px; right: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px; height: 26px;
  padding: 0;
  border: 1.5px solid var(--dp-border);
  border-radius: 50%;
  background: rgba(255, 255, 255, .94);
  color: var(--dp-muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity .12s ease, color .12s ease, border-color .12s ease;
}
.dp-page:hover .dp-page-scissors,
.dp-page-scissors:focus-visible,
.dp-page-scissors.is-on { opacity: 1; }
.dp-page-scissors:hover { color: var(--dp-orange); border-color: var(--dp-orange); }
.dp-page-scissors.is-on {
  color: #fff;
  background: var(--dp-orange);
  border-color: var(--dp-orange);
}
.dp-page-scissors:focus-visible {
  outline: 2px solid var(--dp-orange);
  outline-offset: 2px;
}
/* Touch has no hover: on a phone the control is simply always there. */
@media (hover: none) {
  .dp-page-scissors { opacity: 1; }
}

/* The way out of a notice — "Open Unlock PDF" under a password-protected
   file. A link, not a button: it navigates, and it must read as the smaller
   of the two things on screen (the problem is the headline). */
.dp-notice-action {
  display: inline-block;
  margin-top: 6px;
  font-size: 13px;
  font-weight: 600;
  color: var(--dp-orange);
  text-decoration: underline;
}
.dp-notice-action:hover { color: #c93a08; }

/* The image converters ask three short questions — quality, size, metadata —
   and the shared chip is full-width, which would stack nine rows and push the
   action out of a panel that must not scroll. Three across, scoped so the
   compress-to-a-size chips keep the shape they were tuned for. */
.dp-oppanel-imageConvert .dp-chip {
  flex: 1 1 calc(33.333% - 4px);
  padding: 7px 6px;
}
.dp-oppanel-imageConvert .dp-chip-label {
  white-space: normal;
  text-align: center;
  font-size: 12.5px;
  line-height: 1.25;
}

/* THE BROWSER CONVERTERS NEED THE SAME TREATMENT, AND NEVER GOT IT.
 *
 * `.dp-oppanel-imageConvert` above is the SERVER tier. The browser tier is
 * `.dp-oppanel-convert` — the panel behind all twenty-five "X to Y" pages —
 * and with no override it inherited the full-width chip. Measured on
 * jfif-to-jpg: Quality four chips = four rows, Keep it under five = five rows,
 * Picture size three = three rows. Twelve stacked rows, 656px of controls in a
 * 451px body, and the panel scrolled on every one of those pages.
 *
 * Three across, the same shape and label size the server panel settled on.
 * Two across cleared the three-control pages but left the four-control ones
 * (the JPG converters, which also carry the background colour) 93px over.
 * Three takes twelve rows to five and clears every page in the family. */
/* -6px, not -4px, for the 6px gap between three items: two gaps over three
   chips is 4px each, and at exactly 4px the rounded-up widths came to 288px in
   a 287px row, so the third chip wrapped to a line of its own. The extra 2px
   is the slack that keeps three on one line at any panel width. */
.dp-oppanel-convert .dp-chip {
  flex: 1 1 calc(33.333% - 6px);
  padding: 7px 6px;
}
.dp-oppanel-convert .dp-chip-label {
  white-space: normal;
  text-align: center;
  font-size: 12.5px;
  line-height: 1.25;
}

/* ---------------------------------------------------------------------------
 * SWATCHES — the colour choice, wearing the same clothes as every other choice
 *
 * Replaces a bare full-width <input type="color">, which rendered as an empty
 * white box: no visible swatch, no resemblance to the chips above it, and read
 * as a text field somebody had forgotten to fill in. White and black are the
 * two answers that matter (both big competitors hardcode one or the other), so
 * they are one tap; the third chip is the picker itself for a brand colour.
 *
 * Each swatch keeps its TEXT label. A colour is not an accessible name, and a
 * white dot on a white chip is invisible without one.
 * ------------------------------------------------------------------------ */
/* align-items, because a flex row STRETCHES by default and the native colour
   input carries an intrinsic height that Chrome will not shrink below. Left
   alone it made itself the tallest item and dragged White and Black up with
   it: 55px chips in a row of 33px ones. */
.dp-swatches {
  align-items: center;
}
.dp-swatches .dp-swatch {
  gap: 6px;
  /* Matches the text chips above it to the pixel. A settings row that is
     visibly taller than the row above reads as a different kind of control. */
  min-height: 33px;
  /* THREE ACROSS, NOT THREE STACKED.
   *
   * `.dp-oppanel .dp-chip` sets `flex: 0 0 100%` on purpose - a favicon size or
   * an output format is a long label, and a column of full-width rows reads as
   * one list of equal choices. A colour swatch is not that: "White", "Black"
   * and "Pick" are three short words next to three dots, and stacking them
   * turned a colour picker into 172px of the 448px panel, taller than any
   * other control in the tool.
   *
   * Overridden HERE rather than by relaxing the base rule, so every other chip
   * group in the suite keeps the one-per-row behaviour it was given. */
  flex: 1 1 auto;
  justify-content: flex-start;
  padding-left: 10px;
  padding-right: 10px;
}

/* The colour circle. A ring rather than a bare fill, so #ffffff is still a
   visible object on a white chip instead of a hole in it. */
.dp-swatch-dot {
  width: 16px;
  height: 16px;
  flex: 0 0 16px;
  border-radius: 50%;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .28);
}

/* The native picker, used AS the swatch. Chrome paints a colour input's own
   border and padding; both are stripped so what is left is the colour itself,
   the same 16px circle as the presets beside it. */
.dp-swatch-custom .dp-swatch-picker {
  width: 16px;
  height: 16px;
  /* min-height as well as height: the UA stylesheet gives a colour input its
     own minimum, and without this it quietly stays taller than the circle it
     is meant to be. */
  min-height: 0;
  flex: 0 0 16px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .28);
}
.dp-swatch-custom .dp-swatch-picker::-webkit-color-swatch-wrapper { padding: 0; }
.dp-swatch-custom .dp-swatch-picker::-webkit-color-swatch {
  border: 0;
  border-radius: 50%;
}
.dp-swatch-custom .dp-swatch-picker::-moz-color-swatch {
  border: 0;
  border-radius: 50%;
}

/* UNTIL IT IS CHOSEN, "Pick" IS NOT A COLOUR — IT IS AN INVITATION.
 *
 * A colour input defaults to #ffffff, so the third chip rendered a white
 * circle sitting next to a chip literally labelled White: two controls that
 * looked the same and did different things. A colour wheel says "choose"
 * instead of naming a colour, and is replaced by the real one the moment the
 * user picks. */
.dp-swatch-custom:not(.is-active) .dp-swatch-picker::-webkit-color-swatch {
  background: conic-gradient(from .25turn, #fa4c10, #f7b500, #2fb344, #2b6cb0, #8b5cf6, #fa4c10);
}
.dp-swatch-custom:not(.is-active) .dp-swatch-picker::-moz-color-swatch {
  background: conic-gradient(from .25turn, #fa4c10, #f7b500, #2fb344, #2b6cb0, #8b5cf6, #fa4c10);
}

/* Selected state matches the chips: the picker has no radio to drive :has(),
   so the class does it. */
.dp-swatch-custom.is-active {
  border-color: var(--dp-orange);
}
.dp-swatch-custom.is-active .dp-chip-label { color: var(--dp-orange); }

/* The picker swallows focus, so the ring has to be drawn on the chip. */
.dp-swatch-custom:focus-within {
  outline: 2px solid var(--dp-orange);
  outline-offset: 2px;
}

/* The full-width colour box is GONE — it is the swatch chip now (see
   .dp-swatches above). The old rule stretched the picker to 100% width and
   38px tall with a white background, which on a white panel is the "empty box
   that looks like an unfilled text field" this redesign replaced. It also
   out-specified the new swatch sizing and quietly kept the Pick chip 55px
   tall, so it could not simply be left in place unused. */

/* The mega-menu styles used to live here. They moved to css/megamenu.css when
   ToolsPivot's navbar started using the same two menus — this file is loaded on
   /doc/* only, and the menus now need to be styled on both brands. */

/* ===========================================================================
   TWO INHERITED DEFECTS, FIXED FOR DOCPIVOT ONLY
   ===========================================================================
   Both live in the shared theme and both were found by auditing the menus.
   This stylesheet is loaded on /doc/* and nowhere else, so these corrections
   reach DocPivot without touching ToolsPivot — which is the standing rule for
   this work. If they should apply site-wide they belong in the theme's own
   CSS, as a separate, deliberate change. */

/* 1. HORIZONTAL SCROLL ON PHONES.
 *
 * `.navbar-header` carries Bootstrap 3's negative gutter (`margin: 0 -15px`),
 * which is designed to cancel the 15px padding on its container. The theme
 * sets that container's padding to 0, so nothing cancels it: the header
 * measured 420px inside a 390px viewport and the whole page scrolled
 * sideways by 15px. Measured, not guessed — body.scrollWidth was 405.
 *
 * Zeroing the margin rather than restoring the padding, because the logo and
 * burger already carry their own insets and re-adding 15px would move them. */
@media (max-width: 767px) {
  /* Matching Bootstrap's own selector, which is `.container-fluid >
     .navbar-header` — a bare `.navbar-header` loses to it and left the header
     at 420px.
     `.navbar-collapse` carries the identical negative gutter and was missed
     the first time: fixing only the header left the page still scrolling
     sideways, because the collapse was also 420px in a 390px viewport. */
  .container > .navbar-header,
  .container-fluid > .navbar-header,
  .container > .navbar-collapse,
  .container-fluid > .navbar-collapse { margin-left: 0; margin-right: 0; }

  /* THE MENU'S OWN SCROLL BOX.
   *
   * Bootstrap 3 caps an open collapsed navbar at max-height: 340px with
   * overflow-y: auto — a sensible default in 2013, when a menu was six links.
   * With a mega panel open the content is ~2,700px, so it was scrolling inside
   * a 339px window: a tiny nested scroller inside the page's own scroll, which
   * is the `scroll-behavior` anti-pattern and miserable on a phone.
   *
   * Raised to what the screen actually offers. Still bounded, so the menu can
   * never cover the whole viewport with no way back to the page. */
  #myNavbar.navbar-collapse { max-height: calc(100vh - 96px); }
}

/* 2. NAVBAR LINK CONTRAST.
 *
 * The theme paints nav links #808080, which is 3.95:1 on white — under the
 * 4.5:1 floor for text this size. It applies to Home, Blog, Contact US and,
 * because the mega-menu triggers were deliberately matched to their
 * neighbours, to those three as well.
 *
 * #6b6b6b is 5.15:1 and is still visibly a muted nav grey, so the bar reads
 * the same; it is the same hue, one step darker. !important because the
 * theme's own rule is !important. */
/* The .dp-mega-toggle button reset lives in megamenu.css, which owns that
   component. A copy here used an ID-level selector, so it beat the real rule
   on specificity regardless of load order — and when the two drifted, the
   stale copy silently won and the three toggle labels sat 11px high. One
   owner per component; see megamenu.css. */

#myNavbar .navbar-nav > li > a,
#myNavbar .navbar-nav > li > .dp-mega-toggle,
#myNavbar .navbar-nav > li > .mega-menu-trigger { color: #6b6b6b !important; }
/* #c2410c, not --dp-orange (#fa4c10). These states sit on the #fff5f0 tint
   where the brand orange is only 3.21:1; this is the same hue two steps darker
   at 4.83:1. Applied to the theme's own trigger as well as mine, so the two
   never diverge — matching a neighbour that fails is not parity worth having. */
#myNavbar .navbar-nav > li > a:hover,
#myNavbar .navbar-nav > li > a:focus,
#myNavbar .navbar-nav > li.active > a,
#myNavbar .navbar-nav > li > .dp-mega-toggle:hover,
#myNavbar .navbar-nav > li > .dp-mega-toggle[aria-expanded="true"],
#myNavbar .navbar-nav > li > .mega-menu-trigger:hover,
#myNavbar .navbar-nav > li.mega-open > .mega-menu-trigger { color: #c2410c !important; }

/* Both source inputs state it themselves: an author `display` rule beats the
   UA stylesheet's [hidden] { display: none }. */
.dp-urlrow[hidden] { display: none; }
.dp-textrow[hidden] { display: none; }

/* PER-PAGE ROTATE, ON THE THUMBNAIL.
 *
 * Same shape and behaviour as the split scissors above — a round control that
 * appears on hover or focus and is permanently visible on touch, where there
 * is no hover to reveal it. Bottom corners rather than the top right, so the
 * two never collide on a tool that shows both, and so they read as a pair. */
.dp-page-rot {
  position: absolute;
  bottom: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px; height: 26px;
  padding: 0;
  border: 1.5px solid var(--dp-border);
  border-radius: 50%;
  background: rgba(255, 255, 255, .94);
  color: var(--dp-muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity .12s ease, color .12s ease, border-color .12s ease;
}
/* BOTH ON THE LEFT — the bottom-right corner is the page number's.
   Put at opposite corners first, the right-hand turn sat underneath the number
   badge on every tile. */
.dp-page-rot.is-left  { left: 6px; }
.dp-page-rot.is-right { left: 38px; }
/* VISIBLE BEFORE YOU GO LOOKING.
 *
 * First cut hid these until hover, copying the split scissors. Smallpdf — the
 * only competitor that has a per-page rotate at all — puts a large button in
 * the CENTRE of every thumbnail, permanently visible, and on a tool whose
 * entire job is turning pages that is the right call: a control nobody can see
 * is a control nobody uses. Held at .55 so six of them do not shout over the
 * pages they belong to, full strength on hover or focus. */
.dp-page-rot { opacity: .55; }
.dp-page:hover .dp-page-rot,
.dp-page-rot:focus-visible { opacity: 1; }
.dp-page-rot:hover { color: var(--dp-orange); border-color: var(--dp-orange); }
.dp-page-rot:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: 2px; }
@media (hover: none) {
  .dp-page-rot { opacity: 1; }
}

/* SELECT-BY-ORIENTATION, UNDER THE TURN CONTROL.
 *
 * Not in the toolbar: that row holds page ACTIONS, and these choose what an
 * action applies to. Two buttons there also pushed it to a second row at
 * ordinary widths. Sized down from the toolbar's buttons because this is a
 * shortcut, not a primary control. */
.dp-pickrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--dp-border);
}
.dp-pickrow-label {
  flex: 1 1 100%;
  font-size: 13px;
  color: var(--dp-muted);
}
.dp-pickrow-btn {
  flex: 1 1 0;
  min-width: 0;
  padding: 7px 10px;
  font-size: 13px;
}

/* ---- form filling -------------------------------------------------------
   Inputs sit directly over their field on the page, so they are deliberately
   see-through: the user must still read the printed label beside the box they
   are typing into. A tinted wash plus a thin border says "this is fillable"
   without hiding the document underneath. */
.dp-formfill-host .dp-te-layer { pointer-events: none; }
.dp-form-input,
.dp-form-check { pointer-events: auto; }
.dp-form-input {
  box-sizing: border-box;
  border: 1px solid var(--dp-orange);
  border-radius: 2px;
  background: rgba(255, 247, 243, .72);
  color: var(--dp-text);
  padding: 0 3px;
  font-family: inherit;
  line-height: 1.1;
  resize: none;
}
.dp-form-input:focus {
  outline: 2px solid var(--dp-orange);
  outline-offset: 0;
  background: #fff;
}
.dp-form-check { accent-color: var(--dp-orange); cursor: pointer; margin: 0; }
/* The flatten choice is a decision, not a field — it reads as a sentence in
   the action panel rather than as another control on the page. */
.dp-form-flatten {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 14px;
  color: var(--dp-text);
  line-height: 1.4;
  margin: 4px 0 10px;
  cursor: pointer;
}
.dp-form-flatten input { margin-top: 2px; accent-color: var(--dp-orange); }

/* ---- meme caption editor ------------------------------------------------
   Lives inside the action panel, so it inherits that panel's gradient and each
   caption reads as a white inner card - the same skin every other tool uses.
   Every selector is prefixed .dp-meme-, and nothing above is modified, so this
   block cannot reach another tool. */
.dp-img-holder:has(.dp-meme-layer) { position: relative; }

/* The caption editor needs more room than the shared 280px column: a caption
   row carries two colour wells, a font select, a size box, a three-way
   alignment control and a delete. At 280px the delete was clipped off the
   right edge and the panel grew a horizontal scrollbar. Scoped to this tool,
   so every other image tool keeps the standard column. */
/* Desktop only. This selector outranks the single-column rule in the 900px
   query, so on a phone the meme tool kept a 380px rail beside the picture and
   the whole page scrolled sideways. */
@media (min-width: 901px) {
.dp-workspace.dp-has-captions .dp-layout,
.dp-workspace.dp-has-captions .dp-body:has(.dp-panel-host.is-ready) {
  /* 380, not 340: the style row carries size + B/I/U + three alignment
     buttons, which needs 346px once every target is a usable 44px.

     `.dp-layout` added when the image tools moved onto the shared shell. The
     old selector named `.dp-body`, which that shell does not build, so the
     meme rail silently fell back to the standard 340 and its style row lost
     the 40px it was given for a reason. The `.dp-body` half is kept for the
     chain workspace, which still builds one. */
  grid-template-columns: 1fr 380px;
}
}
/* Belt and braces: content wraps or shrinks rather than scrolling sideways. */
.dp-workspace.dp-has-captions .dp-panel-host { min-width: 0; overflow-x: hidden; }

.dp-meme-layer { z-index: 2; }
.dp-meme-handle {
  border: 2px dashed rgba(255, 255, 255, .8);
  border-radius: 6px;
  cursor: move;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .45);
  transition: border-color .15s ease, background-color .15s ease;
}
.dp-meme-handle:hover { border-color: var(--dp-orange); background: rgba(250, 76, 16, .1); }
/* The width grip, on the selected caption's right edge. */
.dp-meme-grip {
  position: absolute;
  top: 50%;
  right: -7px;
  width: 14px;
  height: 34px;
  transform: translateY(-50%);
  border-radius: 4px;
  background: var(--dp-orange);
  box-shadow: 0 0 0 2px rgba(255, 255, 255, .9);
  cursor: ew-resize;
  opacity: 0;
  transition: opacity .12s ease;
}
.dp-meme-handle:hover .dp-meme-grip,
.dp-meme-handle.is-selected .dp-meme-grip { opacity: 1; }

.dp-meme-handle.is-selected {
  border-color: var(--dp-orange);
  border-style: solid;
  background: rgba(250, 76, 16, .08);
}

/* Matches the heading the generated op panel used to render, so the panel
   looks the same as every other tool's. */
/* Group label, styled like the "Orientation" / "Page size" headings on the
   PDF tools so the panels read as the same design. */
.dp-meme-grouplabel {
  margin: 0 0 .5rem;
  font-size: 17px;
  font-weight: 600;
  color: var(--dp-text);
}
.dp-meme-rowlabel {
  font-size: 13px;
  font-weight: 600;
  color: var(--dp-muted);
}
/* Two cards side by side, like Orientation on the PDF tools.
   `.dp-cards` is a COLUMN flex container, not a grid, so this switches the
   direction rather than setting grid columns (which did nothing). The right
   padding on .dp-card exists to clear the corner tick; centred content needs
   it balanced or the label sits visibly off-centre. */
.dp-meme-layoutcards { flex-direction: row; gap: 8px; margin-bottom: 14px; }
.dp-meme-layoutcards .dp-card {
  flex: 1 1 0;
  min-width: 0;
  padding: 11px 10px;
  align-items: center;
}
.dp-meme-layoutcard .dp-card-body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  width: 100%;
}
.dp-meme-layoutcard .dp-card-title { text-align: center; }
.dp-meme-layoutcard svg { width: 24px; height: 24px; color: currentColor; }

/* B and I read as letterforms, not icons, so they carry their own weight. */
.dp-meme-style-bold { font-weight: 700; font-size: 16px; }
.dp-meme-style-italic { font-style: italic; font-family: Georgia, serif; font-size: 16px; }

.dp-meme-list { display: flex; flex-direction: column; gap: 10px; }

/* Stickers and effects, below the captions. */
.dp-meme-stickers { margin-top: 16px; display: flex; flex-direction: column; gap: 10px; }
.dp-meme-stickerthumb {
  flex: 0 0 auto;
  width: 44px; height: 44px;
  object-fit: contain;
  border: 1px solid var(--dp-border);
  border-radius: 8px;
  background: #fff;
}
.dp-meme-range { flex: 1 1 0; min-width: 0; height: 44px; accent-color: var(--dp-orange); }

.dp-meme-pen { margin-top: 16px; }
/* Full width on its own line, so the label never wraps. */
.dp-meme-pentoggle {
  width: 100%;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  white-space: nowrap;
}
/* An icon button outside a segmented group has to draw its own border -
   .dp-meme-alignbtn sets border:0 because the group normally provides it. */
.dp-meme-iconbtn {
  border: 1px solid var(--dp-border) !important;
  border-radius: 8px;
  background: #fff;
}
.dp-meme-iconbtn:hover:not(:disabled) { border-color: var(--dp-orange) !important; color: var(--dp-orange); }
.dp-meme-iconbtn:disabled { opacity: .4; cursor: default; }
.dp-meme-pentoggle svg { width: 18px; height: 18px; }
.dp-meme-pentoggle.is-active {
  border-color: var(--dp-orange);
  color: var(--dp-orange);
  background: var(--dp-grad-a);
  font-weight: 600;
}

.dp-meme-effects { margin-top: 16px; }
.dp-meme-effectrow { display: flex; flex-wrap: wrap; gap: 6px; }
.dp-meme-effect { min-height: 44px; padding: 0 12px; font-size: 15px; }
.dp-meme-effect.is-active {
  border-color: var(--dp-orange);
  color: var(--dp-orange);
  background: var(--dp-grad-a);
  font-weight: 600;
}

/* One caption = one white card on the panel gradient. */
.dp-meme-row {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  border: 1px solid var(--dp-border);
  border-radius: 10px;
  background: #fff;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.dp-meme-row.is-selected {
  border-color: var(--dp-orange);
  box-shadow: 0 0 0 3px rgba(250, 76, 16, .12);
}

/* The caption text gets the full width of the panel; the styling controls sit
   under it. Side by side in a 320px panel the text box was unusably narrow. */
.dp-meme-text {
  width: 100%;
  min-width: 0;
  padding: 10px 12px;
  font-size: 16px;              /* 16px or iOS zooms the page on focus */
  color: var(--dp-text);
  border: 1px solid var(--dp-border);
  border-radius: 8px;
}
.dp-meme-text::placeholder { color: var(--dp-muted); }
.dp-meme-text:focus {
  outline: none;
  border-color: var(--dp-orange);
  box-shadow: 0 0 0 3px rgba(250, 76, 16, .14);
}

/* The shared styling block, below the caption list. Two rows, both sized
   against the full panel width rather than a card's inner width. */
/* Sits above the caption list now, so the spacing belongs underneath it. */
.dp-meme-controls { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }

/* A caption row is now the label, the delete and the words - nothing else. */
.dp-meme-rowhead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.dp-meme-ctrlrow { display: flex; align-items: center; gap: 4px; }
/* The delete sits at the far end of its row, away from the controls you use
   constantly - a destructive action should not neighbour the size box. */
.dp-meme-ctrlrow .dp-meme-del { margin-inline-start: auto; }
/* The more-settings toggle sits just before it, still inside the same row.
   No auto margin when the toggle precedes it, or the two push apart and the
   row overflows the card. */
.dp-meme-more { flex: 0 0 auto; margin-inline-start: auto; }
.dp-meme-ctrlrow .dp-meme-more + .dp-meme-del { margin-inline-start: 0; }
.dp-meme-more.is-active { color: var(--dp-orange); background: var(--dp-grad-a); }

/* 44px everywhere: these are the smallest targets on the page. */
.dp-meme-colour {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  padding: 3px;
  border: 1px solid var(--dp-border);
  border-radius: 8px;
  background: #fff;
  cursor: pointer;
}
.dp-meme-colour:hover { border-color: var(--dp-orange); }
.dp-meme-font, .dp-meme-size {
  height: 44px;
  padding: 0 8px;
  font-size: 15px;
  color: var(--dp-text);
  border: 1px solid var(--dp-border);
  border-radius: 8px;
  background: #fff;
}
.dp-meme-font { flex: 1 1 auto; min-width: 88px; }
/* 48px still shows a two-digit value. The one-line style row comes to 326px
   against 327px of panel; at 52 with a 5px gap it was 3px over and the last
   alignment button clipped. */
.dp-meme-size { flex: 0 0 48px; width: 48px; }
.dp-meme-font:focus, .dp-meme-size:focus {
  outline: none;
  border-color: var(--dp-orange);
  box-shadow: 0 0 0 3px rgba(250, 76, 16, .14);
}
/* Segmented control: one bordered group, dividers between, active segment in
   brand orange. Grouped rather than three loose buttons so it reads as one
   either/or choice. */
.dp-meme-align {
  flex: 0 0 auto;
  display: inline-flex;
  border: 1px solid var(--dp-border);
  border-radius: 8px;
  overflow: hidden;
  background: #fff;
}
.dp-meme-alignbtn {
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--dp-muted);
  background: transparent;
  border: 0;
  border-inline-start: 1px solid var(--dp-border);
  cursor: pointer;
  transition: color .15s ease, background-color .15s ease;
}
.dp-meme-alignbtn:first-child { border-inline-start: 0; }
.dp-meme-alignbtn:hover { color: var(--dp-text); background: var(--dp-grad-a); }
.dp-meme-alignbtn.is-active { color: var(--dp-orange); background: var(--dp-grad-a); }
.dp-meme-alignbtn:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: -2px; }
.dp-meme-alignbtn svg { width: 18px; height: 18px; }

.dp-meme-del {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--dp-muted);
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 8px;
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease, background-color .15s ease;
}
.dp-meme-del:hover:not(:disabled) { color: #b3261e; border-color: #b3261e; background: #fff5f4; }
.dp-meme-del:disabled { opacity: .4; cursor: default; }
.dp-meme-del svg { width: 18px; height: 18px; }

/* An add-a-row affordance, not a generic action. The dashed outline is the
   convention for "insert another one of these" and reads as an empty slot
   waiting to be filled, which a solid white box does not. Solid buttons stay
   reserved for the real actions (Download image). */
.dp-meme-add {
  width: 100%;
  margin-top: 10px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 15px;
  font-weight: 500;
  color: var(--dp-muted);
  background: transparent;
  border: 1.5px dashed var(--dp-border);
  border-radius: 10px;
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease, background-color .15s ease;
}
.dp-meme-add:hover {
  color: var(--dp-orange);
  border-color: var(--dp-orange);
  background: var(--dp-grad-a);
}
.dp-meme-add:focus-visible {
  outline: 2px solid var(--dp-orange);
  outline-offset: 2px;
}
.dp-meme-add svg { width: 18px; height: 18px; }

.dp-meme-hint {
  margin: 10px 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--dp-muted);
}

@media (prefers-reduced-motion: reduce) {
  .dp-meme-handle, .dp-meme-row, .dp-meme-del,
  .dp-meme-text, .dp-meme-font, .dp-meme-size { transition: none; }
}


/* Collapsed secondary controls inside an op panel. Only rendered when a field
   is marked x-advanced, so no existing panel is affected. */
/* THE DISCLOSURE MUST SPAN THE WHOLE PANEL.
 *
 * Several panels lay their rows out as a two-column grid and then span every
 * `.dp-field` across both with `grid-column: 1 / -1`. A <details> is not a
 * .dp-field, so it took a single cell: on a 375px phone that is 135px, and
 * everything inside it - the slider, its number box, its help text - was
 * crushed into half the panel while the other half sat empty. */
.dp-oppanel-rows > .dp-oppanel-advanced { grid-column: 1 / -1; }

/* Rows inside the disclosure stack, rather than inheriting a parent grid. */
.dp-oppanel-advanced-rows { display: grid; gap: .7rem; }

.dp-oppanel-advanced { margin-top: 4px; }
.dp-oppanel-advanced > summary {
  cursor: pointer;
  list-style: none;
  font-weight: 600;
  font-size: 15px;
  /* WAS THE BRAND ORANGE, AND IT OUT-SHOUTED THE PANEL.
     Every field label in a panel is 16px grey; this was the one saturated,
     bolded line in the column, so the optional group read as the headline and
     the control the page is named after read as a caption. Neutral text with
     the chevron doing the "this opens" job puts the emphasis back on the
     primary field. */
  color: var(--dp-text);
  /* "Advanced settings" wrapped to two lines in a 135px cell. It fits on one
     now that the disclosure spans the panel, and this keeps it that way. */
  white-space: nowrap;
  padding: 8px 0;
  /* 44px tap target, per the touch rules the rest of the suite follows. */
  min-height: 28px;
  display: flex;
  align-items: center;
  gap: 6px;
}
.dp-oppanel-advanced > summary::-webkit-details-marker { display: none; }
.dp-oppanel-advanced > summary::before {
  content: '';
  width: 0; height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform .15s ease;
}
.dp-oppanel-advanced[open] > summary::before { transform: rotate(90deg); }
.dp-oppanel-advanced > summary:focus-visible {
  outline: 2px solid #fa4c10; outline-offset: 2px; border-radius: 4px;
}
@media (prefers-reduced-motion: reduce) {
  .dp-oppanel-advanced > summary::before { transition: none; }
}


/* TAP TARGETS ON TOUCH DEVICES.
 *
 * `.dp-oppanel .dp-chip` sets `min-height: 0` on purpose - the note there
 * explains that inheriting the generic 44px pushed image-converter's fifteen
 * chips past the bottom of a panel that must not scroll. That reasoning holds
 * for a mouse. It does not hold for a finger: measured on a 375px phone, the
 * single-line chips render at 33px, well under the 44px floor Apple and
 * Material both specify, and a miss-tap is a worse failure than a scroll.
 *
 * So the floor comes back, but only where there is no precise pointer. Desktop
 * panels are byte-for-byte unchanged. `pointer: coarse` rather than a width
 * query, because this is about the input device, not the screen size - a small
 * window on a laptop still has a mouse. */
@media (pointer: coarse) {
  .dp-oppanel .dp-chip { min-height: 44px; }
  /* The disclosure summary is a tap target too. */
  .dp-oppanel-advanced > summary { min-height: 44px; }
}

/* ---- Redact: the action in the foot, and the cost stated up front -------
 * The editor's commit button is mounted in the action panel foot like every
 * other tool's action, above the export bar because that is the order it is
 * used in: remove the content, then save the file. */
.dp-editor-commit-foot { width: 100%; }

/* Generic panel note. Deliberately NOT .dp-te-fontnote — that one belongs to
   the text editor, and borrowing it would tie two unrelated tools' styling
   together. Same amber treatment, own name. */
.dp-panel-note {
  margin: 12px 0 0;
  padding: 8px 10px;
  border-inline-start: 3px solid #d98324;
  border-radius: 4px;
  background: #fdf6ec;
  color: #6b4a1b;
  font-size: 13px;
  line-height: 1.45;
}

/* ---- Redact: search and mark -------------------------------------------
   Sits above the permanence note, because you search before you commit. */
.dp-redact-find {
  margin: 12px 0 0;
  padding: 12px;
  border: 1px solid var(--dp-border);
  border-radius: 8px;
  background: #fff;
}
.dp-redact-find-label {
  display: block;
  margin-bottom: 6px;
  font-size: 14px;
  font-weight: 600;
}
/* 16px, not smaller: iOS zooms the whole page when a focused input is under
   16px, which throws the canvas off screen mid-task. 44px tall for touch. */
.dp-redact-find-input {
  width: 100%;
  min-height: 44px;
  padding: 8px 10px;
  border: 1px solid var(--dp-border);
  border-radius: 6px;
  font-size: 16px;
}
.dp-redact-find-input:focus-visible { outline: 2px solid var(--dp-accent, #fa4c10); outline-offset: 1px; }
.dp-redact-find-opts { display: flex; flex-wrap: wrap; gap: 12px; margin: 8px 0; }
.dp-redact-find-opt {
  display: flex; align-items: center; gap: 6px;
  min-height: 32px; font-size: 13px; cursor: pointer;
}
.dp-redact-find-go { width: 100%; min-height: 44px; }
.dp-redact-find-status {
  margin: 8px 0 0;
  font-size: 13px;
  line-height: 1.45;
  color: var(--dp-muted);
  min-height: 1em;            /* reserve the line so the card never jumps */
}

/* The precise-mode opt-in. Sits between the search card and the permanence
   note, because it changes what that note is warning about. */
.dp-redact-keep {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin: 12px 0 0;
  min-height: 32px;
  font-size: 13px;
  line-height: 1.45;
  cursor: pointer;
}
.dp-redact-keep input { margin-top: 2px; flex: none; }

/* Annotate PDF: keep the marks editable, or fix them into the page. Same shape
   as the redact checkbox above — one decision, stated in the panel. */
.dp-annot-keep {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 12px 0 0;
  /* A tap target, not a line of text: this is the one decision on the page
     besides the marks themselves, and 32px missed the 44px minimum. */
  min-height: 44px;
  font-size: 13px;
  line-height: 1.45;
  cursor: pointer;
}
.dp-annot-keep input { flex: none; }

/* The box drawn while dragging across text. Deliberately a hairline outline
   rather than a fill: the fill is what the mark itself will be, and showing
   both at once read as "the highlight is already there, and crooked". */
.dp-markup-drag {
  position: absolute;
  border: 1px dashed var(--dp-orange);
  background: rgba(250, 76, 16, .08);
  pointer-events: none;
}

/* Detector list inside the find card. */
.dp-redact-det { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--dp-border); }
.dp-redact-det-label { margin: 0 0 6px; font-size: 13px; font-weight: 600; }

/* Bar marking input. */
.dp-redact-label { margin-top: 12px; }
.dp-redact-label-cap { display: block; margin-bottom: 6px; font-size: 13px; font-weight: 600; }

/* The export bar is hidden on tools whose action triggers the export itself.
   `hidden` alone cannot do it: .dp-exportbar.is-ready sets display:flex, which
   outranks the UA sheet's [hidden] rule. */
.dp-exportbar[hidden] { display: none !important; }

/* ---- Searchable select (long enum lists, e.g. 123 OCR languages) --------
   A text box that filters a listbox beneath it. The list is positioned
   absolutely so opening it cannot resize the action panel — a panel that grows
   when you focus a field pushes the button out from under the cursor. */
.dp-searchsel { position: relative; }
.dp-searchsel-input {
  width: 100%;
  min-height: 44px;              /* touch target */
  padding: 8px 10px;
  border: 1px solid var(--dp-border);
  border-radius: 6px;
  font-size: 16px;               /* under 16px iOS zooms the page on focus */
  background: #fff;
}
.dp-searchsel-input:focus-visible {
  outline: 2px solid var(--dp-accent, #fa4c10);
  outline-offset: 1px;
}
.dp-searchsel-list {
  position: absolute;
  z-index: 30;
  left: 0; right: 0; top: calc(100% + 2px);
  margin: 0; padding: 4px;
  list-style: none;
  max-height: 260px;
  overflow-y: auto;
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 6px;
  box-shadow: 0 8px 24px rgba(0,0,0,.12);
}
.dp-searchsel-opt {
  padding: 9px 10px;
  border-radius: 4px;
  font-size: 14px;
  cursor: pointer;
}
.dp-searchsel-opt:hover,
.dp-searchsel-opt.is-active { background: var(--dp-grad-a, #fff7f3); }
.dp-searchsel-opt[aria-selected="true"] { font-weight: 600; }
.dp-searchsel-empty { padding: 9px 10px; font-size: 14px; color: var(--dp-muted); }

/* Crop's chips are SHORT — "Free", "4:5", "16:9". The op-panel default is one
   chip per row, which is right for image-converter's size list where the
   labels are long and read as a list of equal choices. Eight ratio pills
   stacked would run the panel past its 600px and make it scroll, which the
   work block forbids. Scoped the same way .dp-serverspace and .dp-sheetset
   scope their own row rules. */
.dp-cropspace .dp-oppanel .dp-chip { flex: 0 1 auto; }

/* ---- Crop: only what the standard field markup cannot express ----------
   The chips, labels, inputs and focus rings all come from the shared
   .dp-field / .dp-chips block. These four rules are the genuine extras:
   a two-column pair for width+height, an action-chip variant that is a
   button rather than a radio, and the readout as quiet helper text. */
.dp-crop-size { display: grid; grid-template-columns: 1fr 1fr; gap: .7rem; }
.dp-crop-transform .dp-chip-action {
  cursor: pointer;
  background: #fff;
  font: inherit;
}
.dp-crop-transform .dp-chip-action:hover { border-color: var(--dp-orange); }
.dp-crop-transform .dp-chip-action:active { transform: scale(.97); }
.dp-crop-readout {
  margin: 0;
  font-size: 13px;
  color: var(--dp-muted);
  font-variant-numeric: tabular-nums;   /* the numbers change as you drag */
}

/* Crop uses the shared shell. The holder fills the canvas column and centres
   the picture in it; the stage inside stays inline-block so the crop overlay
   stays glued to the canvas.
   `align-items` alone did nothing here — without display:flex those two
   properties are inert, which is why the picture sat against the left edge. */
.dp-crop-holder {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  min-height: 0;
}
.dp-cropspace .dp-crop-ratios,
.dp-cropspace .dp-crop-transform { justify-content: flex-start; }
.dp-cropspace .dp-crop-size { justify-content: flex-start; }
.dp-cropspace .dp-crop-readout { margin: 4px 0 0; font-size: 13px; color: var(--dp-muted); }
.dp-cropspace .dp-crop-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; width: 100%; }
.dp-cropspace .dp-crop-actions .dp-btn-primary { flex: 1 1 auto; }

/* Ratio chips carry the shape above the label, so the chip is a column rather
   than a row. Everything else — pill, border, orange selected state, focus
   ring — still comes from the shared .dp-chip block. */
.dp-cropspace .dp-oppanel .dp-chip {
  flex-direction: column;
  gap: 2px;
  padding: 7px 10px;
  min-width: 58px;
}
.dp-chip-glyph { display: block; line-height: 0; color: inherit; opacity: .75; }
.dp-chip input:checked ~ .dp-chip-glyph { opacity: 1; }
/* The turn/flip chips are labels only; keep them on one line. */
.dp-cropspace .dp-crop-transform .dp-chip-action { flex-direction: row; min-width: 0; }
.dp-crop-subhead {
  margin: .35rem 0 0;
  font-size: 16px;
  color: var(--dp-muted);
}
/* Crop carries more controls than most panels — chips, turn/flip, size and
   position — so its rows sit a little tighter to stay inside the 600px block
   without scrolling. Scoped, like .dp-sheetset does for the same reason. */


/* Turn and flip are icon-only (the words live in title + aria-label), so the
   chip shrinks to a square tap target rather than a text pill. 44px keeps the
   Apple/Material minimum. */
.dp-cropspace .dp-oppanel .dp-chip-icononly {
  flex: 0 0 auto;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  align-items: center;
  justify-content: center;
}
.dp-cropspace .dp-oppanel .dp-chip-icononly .dp-chip-glyph { margin: 0; }
/* Vertical trim, all of it inside this tool. Eight ratio chips, four turn/flip
   chips, size, position and the source hint have to live in the same 600px
   block every other tool gets, so the ratio chips lose 3px of padding top and
   bottom and 2px of glyph. The 44px minimum tap height is NOT traded away —
   the chip is still 54px tall — and the icon-only turn/flip chips keep 44x44
   exactly. */



.dp-cropspace .dp-crop-readout { margin: 0; line-height: 1.3; }

/* ---- Crop Image panel ---------------------------------------------------
   Three sections, each a heading over its controls, instead of the flat run
   of labelled rows the panel grew into. The flat version gave "Shape",
   "Width" and "Position (X)" the same weight, so nothing said which grouped
   which, and every chip row stopped short of the right edge because the chips
   were sized to their text rather than to the column.
   Everything here is scoped to .dp-cropspace: this panel carries more controls
   than any other image tool and must not drag the shared block around with it. */
.dp-cropspace .dp-oppanel-rows { gap: 10px; }

.dp-cropspace .dp-crop-sect { display: grid; gap: 6px; }
.dp-cropspace .dp-crop-secthead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.dp-cropspace .dp-crop-secttitle {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  color: #4a3f39;
  letter-spacing: .01em;
}

/* Chips fill the row EXACTLY four across, so no row ends in dead space and
   every chip is the same width whatever its label. `minmax(0, 1fr)` rather
   than `1fr` — a long label like "1.91:1" would otherwise set a floor wider
   than a quarter of the column and push the fourth chip onto its own line. */
.dp-cropspace .dp-oppanel .dp-crop-chiprow {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 6px;
}
/* The corner row has three chips, so it gets three columns. Left in the
   four-column grid it filled three cells and left the fourth empty — the same
   ragged right edge the redesign was meant to remove, just moved down a row. */
.dp-cropspace .dp-oppanel .dp-crop-chiprow.is-three {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.dp-cropspace .dp-oppanel .dp-crop-chiprow .dp-chip {
  flex: none;
  min-width: 0;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 5px 4px;
  min-height: 46px;
  text-align: center;
}
.dp-cropspace .dp-chip-glyph {
  display: block;
  height: 18px;
  line-height: 0;
  color: currentColor;
}
.dp-cropspace .dp-chip-glyph svg { width: 18px; height: 18px; display: block; }
.dp-cropspace .dp-oppanel .dp-crop-chiprow .dp-chip-label {
  font-size: 12px;
  line-height: 1.1;
  white-space: nowrap;
}

/* Turn and flip: the same four-column grid so the row lines up with the chips
   above it, but icon-only and square. 44px is the Apple/Material minimum and
   is not traded away for space. */
.dp-cropspace .dp-oppanel .dp-crop-transform {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 6px;
}
.dp-cropspace .dp-oppanel .dp-crop-transform .dp-chip {
  flex: none;
  min-width: 0;
  min-height: 44px;
  padding: 0;
  align-items: center;
  justify-content: center;
}

/* Units ride on the section heading, opposite the title — a segmented control
   small enough to cost no height at all. */
.dp-cropspace .dp-crop-units {
  display: inline-flex;
  border: 1px solid #e8ded8;
  border-radius: 7px;
  overflow: hidden;
  background: #fff;
}
.dp-cropspace .dp-crop-unit {
  position: relative;
  margin: 0;
  padding: 3px 8px;
  font-size: 11.5px;
  line-height: 1.5;
  color: #7d716a;
  cursor: pointer;
  user-select: none;
}
.dp-cropspace .dp-crop-unit + .dp-crop-unit { border-inline-start: 1px solid #f0e7e1; }
.dp-cropspace .dp-crop-unit .dp-chip-radio { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
.dp-cropspace .dp-crop-unit:has(.dp-chip-radio:checked) { background: #fa4c10; color: #fff; font-weight: 600; }
.dp-cropspace .dp-crop-unit:has(.dp-chip-radio:focus-visible) { outline: 2px solid #fa4c10; outline-offset: 1px; }

/* Size on one row — width, height and the resolution they are measured at —
   then position on the next. Two questions, two rows. */
.dp-cropspace .dp-crop-size {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
}
.dp-cropspace .dp-crop-pos { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.dp-cropspace .dp-crop-size .dp-field > input { padding: 5px 6px; }

/* The two rows are paid for out of SPACING, never out of the 44px minimum tap
   height: the chips below are still 44, and only the gaps between groups and
   the padding inside the number boxes were tightened. */
.dp-cropspace .dp-oppanel-rows { gap: 7px; }
.dp-cropspace .dp-crop-sect { gap: 5px; }
.dp-cropspace .dp-oppanel .dp-crop-chiprow .dp-chip { min-height: 44px; padding: 4px; }
.dp-cropspace .dp-crop-size .dp-field > label { margin-bottom: 1px; }
/* The input labels are SUBORDINATE to the section heading — that inversion,
   where "Position (X)" looked more important than "Crop position", is what
   made the panel read as a list rather than as groups. */
.dp-cropspace .dp-crop-size .dp-field > label {
  font-size: 11.5px;
  font-weight: 500;
  color: #6f655f;
  margin-bottom: 2px;
}
.dp-cropspace .dp-crop-size .dp-field > input { padding: 6px 8px; font-size: 13.5px; }

/* The advanced disclosure, collapsed to one line. */
.dp-cropspace .dp-crop-advanced > summary { font-size: 12.5px; padding: 2px 0; }
.dp-cropspace .dp-crop-advanced .dp-oppanel-advanced-rows { display: grid; gap: 8px; padding-top: 6px; }
.dp-cropspace .dp-crop-checkline {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  cursor: pointer;
}
.dp-cropspace .dp-crop-checkline input { width: 16px; height: 16px; margin: 0; accent-color: #fa4c10; }
.dp-cropspace .dp-crop-hint { margin: 4px 0 0; font-size: 11.5px; line-height: 1.35; color: #6f655f; }
.dp-cropspace .dp-crop-anchorfield select {
  width: 100%; padding: 6px 8px; font-size: 13px;
  border: 1px solid #e8ded8; border-radius: 7px; background: #fff;
}

/* A control that cannot apply right now says so, rather than accepting the
   click and being ignored at export. */
.dp-cropspace .dp-chip.is-off,
.dp-cropspace .dp-chip[disabled] { opacity: .4; cursor: not-allowed; }

.dp-cropspace .dp-crop-readout { margin: 0; line-height: 1.35; font-size: 11.5px; }
.dp-cropspace .dp-crop-readout.is-warn { color: #b34700; font-weight: 500; }

/* The batch filmstrip sits under the picture in the LEFT column, which is
   where the room is; the panel has none. */
.dp-cropspace .dp-crop-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
  align-items: center;
  justify-content: center;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid #f0e7e1;
  max-height: 46px;
  overflow: hidden;
}
.dp-cropspace .dp-crop-stripcount { font-size: 12px; font-weight: 600; color: #4a3f39; }
.dp-cropspace .dp-crop-stripitem {
  font-size: 11px; color: #6f655f;
  background: #fff7f3; border: 1px solid #f0e7e1; border-radius: 999px;
  padding: 2px 8px; max-width: 160px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* THE CHIP ROWS WERE 5px TALLER THAN THEIR CHIPS.
   An auto-sized grid track measures its items at max-content, where the chip's
   inherited 22.9px line-height counts instead of the 13.2px the label actually
   renders at — so every row of chips reserved 49.2px to hold a 44px chip, three
   times over. Pinning the track to the tap-target height removes 16px of
   nothing. 44px is still the floor, so this costs no touch accuracy. */
.dp-cropspace .dp-oppanel .dp-crop-chiprow,
.dp-cropspace .dp-oppanel .dp-crop-transform {
  grid-auto-rows: 44px;
  align-items: stretch;
}

/* The number fields: the label was reserving a 16px line box for 11.5px text,
   and the inputs carried 6px of vertical padding where 4px reads the same. */
.dp-cropspace .dp-crop-size .dp-field > label { line-height: 1.15; }
.dp-cropspace .dp-crop-size .dp-field > input { padding: 4px 6px; }
.dp-cropspace .dp-crop-unit { padding: 2px 8px; }
.dp-cropspace .dp-oppanel-rows { gap: 6px; }
.dp-cropspace .dp-crop-stripline { display: flex; flex-wrap: wrap; gap: 2px 8px; align-items: baseline; justify-content: center; width: 100%; }
.dp-cropspace .dp-crop-striphint { font-size: 11px; color: #6f655f; }
.dp-cropspace .dp-crop-stripnames { display: flex; flex-wrap: wrap; gap: 4px; justify-content: center; }

/* ---- Signing panel ------------------------------------------------------
   The saved mark, shown at the size it will not be placed at — a preview, so
   it reads as "this is your signature" rather than as the thing on the page. */
.dp-sig-saved {
  display: block;
  width: 100%;
  max-height: 74px;
  object-fit: contain;
  background: #fff;
  border: 1px solid #e8ded8;
  border-radius: 8px;
  padding: 6px;
  margin: 0 0 10px;
}
.dp-insp .dp-insp-btn { width: 100%; margin-top: 6px; }

/* A signature preview on a chequerboard, so "background removed" is something
   you can SEE rather than something the label claims. */
.dp-sig-alpha {
  background-image:
    linear-gradient(45deg, #e9e2dc 25%, transparent 25%, transparent 75%, #e9e2dc 75%),
    linear-gradient(45deg, #e9e2dc 25%, transparent 25%, transparent 75%, #e9e2dc 75%);
  background-size: 14px 14px;
  background-position: 0 0, 7px 7px;
  background-color: #fff;
}
.dp-sig-strip { margin-top: 10px; }

/* ---- Utility tools (favicon, base64) ------------------------------------
   The work column is the same bordered card the image tools use, and it must
   have something in it from the moment the block opens — the first build put
   only the OUTPUT here, so the page opened on 600px of blank white beside a
   full panel. */
.dp-utilspace .dp-util-card {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  overflow: auto;
}
.dp-utilspace .dp-util {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  width: 100%;
  max-height: 100%;
}
/* The picture you chose, before the set exists. */
.dp-utilspace .dp-util-sourceimg {
  display: block;
  max-width: 260px;
  max-height: 260px;
  object-fit: contain;
  border-radius: 10px;
}
/* The generated icons, largest last so the row reads as a scale. */
.dp-utilspace .dp-util-preview {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: center;
  gap: 14px;
}
.dp-utilspace .dp-util-preview img {
  image-rendering: -webkit-optimize-contrast;
  background: #fff;
  border: 1px solid #e8ded8;
  border-radius: 6px;
  padding: 4px;
}
/* The base64 text is the work on those two tools, so it fills the card. */
.dp-utilspace .dp-util-out,
.dp-utilspace .dp-util-in {
  width: 100%;
  flex: 1 1 auto;
  min-height: 340px;
  resize: none;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px;
  line-height: 1.45;
  padding: 12px;
  border: 1px solid #e8ded8;
  border-radius: 10px;
  background: #fff;
}
.dp-utilspace .dp-util-img {
  max-width: 100%;
  max-height: 300px;
  object-fit: contain;
}
.dp-utilspace .dp-util-hint { margin: 0 0 8px; font-size: 12.5px; line-height: 1.4; color: #6f655f; }
.dp-utilspace .dp-util-sizes { margin: 0; padding-left: 18px; font-size: 12.5px; line-height: 1.7; color: #4a3f39; }
.dp-utilspace .dp-util-check { display: flex; align-items: center; gap: 8px; font-size: 13px; }
/* The one action fills the foot, the way it does on every other tool. Appended
   directly rather than inside an actions row, these came out small and
   left-aligned against a full-width foot. */
.dp-utilspace .dp-actionpanel-foot { display: flex; gap: 8px; }
.dp-utilspace .dp-actionpanel-foot .dp-btn { flex: 1 1 auto; justify-content: center; }

/* The chain's action row is the same thing as .dp-oppanel-actions on every
   other tool, so it has to sit and stretch the same way. Without this the
   primary action was a 143px button floating at the left of a 338px foot while
   every other tool filled it. */
.dp-chain-run { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; }
/* Full row, not "whatever is left after the status text". The status wraps to
   its own line the way the op panel's hint does, so the action is the same
   302px on this tool as on every other one. */
.dp-chain-run .dp-btn { flex: 1 1 100%; justify-content: center; }
.dp-chain-run .dp-chain-status { flex: 1 1 100%; margin: 0; }
/* The one checkbox row was 37px tall — under the 44px minimum, caught by the
   design check. The control itself stays 16px; the LABEL grows to a proper
   tap target, which is what a finger actually hits. */
.dp-utilspace .dp-util-check { min-height: 44px; }
.dp-utilspace .dp-util-check input { width: 16px; height: 16px; }

/* ---- The two-pane converter (base64 tools only) -------------------------
   Same total width as .dp-layout so nothing else on the page moves: two equal
   534px panes with the same 20px gap. Every token is the standard one — white
   card, 12px radius, #e8ded8 border, peach foot, orange primary — so this
   reads as a variant of the work block rather than a foreign layout. */
.dp-splitspace .dp-split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 534px));
  gap: 20px;
  justify-content: center;
  align-items: stretch;
  margin: 0 auto;
}
.dp-splitspace .dp-splitpane {
  display: flex;
  flex-direction: column;
  min-width: 0;
  height: 600px;
  background: #fff;
  border: 1px solid #e8ded8;
  border-radius: 12px;
  overflow: hidden;
}
.dp-splitspace .dp-splitpane-head {
  margin: 0;
  padding: 14px 18px;
  font-size: 15px;
  font-weight: 700;
  color: #23201f;
  text-align: center;
  border-bottom: 1px solid #f0e7e1;
}
.dp-splitspace .dp-splitpane-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 16px;
  overflow: auto;
}
/* The foot carries the pane's own action, on the same peach the action panel
   uses, so the two feet line up across the gap. */
.dp-splitspace .dp-splitpane-foot {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 14px 18px;
  background: linear-gradient(#fff7f3, #ffeee6);
  border-top: 1px solid #f0e7e1;
}
/* EXACTLY ONE 44px ACTION per foot. Anything that can wrap to a second line
   — a hint, a checkbox — makes this foot taller than the one beside it, and
   the two buttons stop lining up. Those belong in the body. */
.dp-splitspace .dp-splitpane-foot .dp-btn { width: 100%; justify-content: center; min-height: 44px; }
.dp-splitspace .dp-splitpane-body .dp-util-check {
  min-height: 44px; display: flex; align-items: center; gap: 8px;
  font-size: 13px; align-self: flex-start; flex: 0 0 auto;
}
.dp-splitspace .dp-splitpane-body .dp-util-hint {
  margin: 0; font-size: 11.5px; line-height: 1.35; color: #6f655f; flex: 0 0 auto;
}
/* The drop card is the pane's empty state, so it drops the card chrome it
   would otherwise stack on top of the pane's own, and hides its button: the
   foot owns that action. The card stays clickable, so the affordance is not
   lost by hiding it. */
.dp-splitspace .dp-splitpane-body .dp-dropzone {
  border: 0; background: none; box-shadow: none; padding: 0;
  width: 100%; min-height: 0; flex: 1 1 auto;
  display: flex; align-items: center; justify-content: center;
}
.dp-splitspace .dp-splitpane-body .dp-dropzone [data-dp-choose] { display: none; }

.dp-splitspace .dp-split-img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 8px; }
.dp-splitspace .dp-split-empty { margin: 0; font-size: 13px; color: #6f655f; text-align: center; }
.dp-splitspace .dp-splitpane-body .dp-util-out,
.dp-splitspace .dp-splitpane-body .dp-util-in { flex: 1 1 auto; }
.dp-splitspace .dp-util-out,
.dp-splitspace .dp-util-in {
  width: 100%; height: 100%; min-height: 0; resize: none;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px; line-height: 1.45; padding: 12px;
  border: 1px solid #e8ded8; border-radius: 10px; background: #fff;
}
.dp-splitspace .dp-util-img { max-width: 100%; max-height: 100%; object-fit: contain; }
.dp-splitspace .dp-notice { max-width: 1088px; margin: 12px auto 0; text-align: center; }

@media (max-width: 1100px) {
  .dp-splitspace .dp-split { grid-template-columns: minmax(0, 1fr); }
  .dp-splitspace .dp-splitpane { height: auto; min-height: 360px; }
}

/* ===================================================================== */
/* FAVICON GENERATOR — the preview column.                               */
/*                                                                       */
/* Every rule here is scoped to `.dp-utilspace`, and most to             */
/* `.dp-fav-*` classes that exist nowhere else, because docpivot.css is  */
/* shared by 108 tools and this file's own history is full of rules      */
/* written one selector too wide. The chip and field rules in particular */
/* are only extended here, never redefined.                              */
/* ===================================================================== */

.dp-utilspace .dp-fav-previews {
  display: flex;
  flex-direction: column;
  gap: 22px;
  width: 100%;
  padding: 4px 2px;
}
/* The previews run taller than the card. Centred inside a column capped at
   the card's height, the overflow spilled out ABOVE the top edge as well as
   below, and a scroll box cannot reach content above its own top: "In a
   browser tab" was cut off and unreachable. Let this column grow, start it
   at the top, and let the card scroll it (2026-10-17). Base64 tools share
   .dp-util and fit, so they are left alone. */
.dp-utilspace .dp-util-card:has(> .dp-util > .dp-fav-previews) { align-items: flex-start; }
.dp-utilspace .dp-util:has(> .dp-fav-previews) { justify-content: flex-start; max-height: none; }

.dp-utilspace .dp-fav-section { display: flex; flex-direction: column; gap: 10px; }
.dp-utilspace .dp-fav-sectionhead {
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #6f655f;
}
/* Light and dark sit SIDE BY SIDE, never behind a toggle: a dark-mode icon
   that vanishes is only caught when the failure is on screen next to the one
   that works. */
.dp-utilspace .dp-fav-sectionbody {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
.dp-utilspace .dp-fav-caption {
  margin: 6px 0 0;
  font-size: 11.5px;
  color: #6f655f;
  text-align: center;
}

/* --- browser tab mock --- */
.dp-utilspace .dp-fav-tab {
  border: 1px solid #e8ded8;
  border-radius: 10px;
  padding: 10px 10px 6px;
  background: #fff;
}
.dp-utilspace .dp-fav-tab.is-dark { background: #202124; border-color: #3c4043; }
.dp-utilspace .dp-fav-tabstrip {
  background: #dee1e6;
  border-radius: 8px 8px 0 0;
  padding: 6px 6px 0;
}
.dp-utilspace .dp-fav-tab.is-dark .dp-fav-tabstrip { background: #35363a; }
.dp-utilspace .dp-fav-tabshape {
  display: flex;
  align-items: center;
  gap: 8px;
  background: #f1f3f4;
  border-radius: 8px 8px 0 0;
  padding: 8px 10px;
  min-width: 0;
}
.dp-utilspace .dp-fav-tab.is-dark .dp-fav-tabshape { background: #202124; }
/* The icon renders at exactly 16 CSS px. Scaling it up here would hide the
   only failure this preview exists to reveal. */
.dp-utilspace .dp-fav-tabicon { width: 16px; height: 16px; flex: 0 0 16px; }
.dp-utilspace .dp-fav-tabtitle {
  font-size: 12px;
  color: #3c4043;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.dp-utilspace .dp-fav-tab.is-dark .dp-fav-tabtitle { color: #e8eaed; }

/* --- Google result mock --- */
.dp-utilspace .dp-fav-serp {
  border: 1px solid #e8ded8;
  border-radius: 10px;
  padding: 12px;
  background: #fff;
}
.dp-utilspace .dp-fav-serp.is-dark { background: #202124; border-color: #3c4043; }
.dp-utilspace .dp-fav-serprow { display: flex; align-items: center; gap: 9px; }
/* Google clips the favicon to a circle, which crops square artwork again. */
.dp-utilspace .dp-fav-serpicon {
  width: 26px; height: 26px; flex: 0 0 26px;
  border-radius: 50%;
  border: 1px solid #dadce0;
  background: #fff;
}
.dp-utilspace .dp-fav-serp.is-dark .dp-fav-serpicon { border-color: #5f6368; }
.dp-utilspace .dp-fav-serpmeta { display: flex; flex-direction: column; min-width: 0; }
.dp-utilspace .dp-fav-serpname { font-size: 12.5px; color: #202124; line-height: 1.3; }
.dp-utilspace .dp-fav-serpurl  { font-size: 11.5px; color: #4d5156; line-height: 1.3; }
.dp-utilspace .dp-fav-serp.is-dark .dp-fav-serpname { color: #e8eaed; }
.dp-utilspace .dp-fav-serp.is-dark .dp-fav-serpurl  { color: #bdc1c6; }
.dp-utilspace .dp-fav-serptitle {
  margin: 8px 0 2px; font-size: 15px; color: #1a0dab; line-height: 1.3;
}
.dp-utilspace .dp-fav-serp.is-dark .dp-fav-serptitle { color: #8ab4f8; }
.dp-utilspace .dp-fav-serpsnip {
  margin: 0; font-size: 12px; color: #4d5156; line-height: 1.45;
}
.dp-utilspace .dp-fav-serp.is-dark .dp-fav-serpsnip { color: #bdc1c6; }

/* --- phone crops --- */
.dp-utilspace .dp-fav-platcell {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  border: 1px solid #e8ded8; border-radius: 10px; padding: 14px; background: #fff;
}
/* iOS rounds the tile itself; the artwork inside must already be opaque. */
.dp-utilspace .dp-fav-ios {
  width: 76px; height: 76px; border-radius: 17px;
  box-shadow: 0 1px 3px rgba(0,0,0,.18);
}
/* Chrome on Android masks to a circle. Showing the circle is the only way to
   see whether the artwork breaches the safe zone. */
.dp-utilspace .dp-fav-android {
  width: 76px; height: 76px; border-radius: 50%;
  box-shadow: 0 1px 3px rgba(0,0,0,.18);
}
.dp-utilspace .dp-fav-platlabel { font-size: 11.5px; color: #6f655f; text-align: center; }

/* --- true-size grid --- */
.dp-utilspace .dp-fav-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}
/* The cell IS the size selector. Selected reads as the normal, confident
   state; deselected recedes rather than vanishing, because the card is the
   only way to turn the size back on. */
.dp-utilspace .dp-fav-cell {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  border: 1.5px solid #e8ded8; border-radius: 10px;
  padding: 12px 8px 10px; background: #fff;
  cursor: pointer;
  transition: border-color .15s ease, opacity .15s ease, background-color .15s ease;
}
.dp-utilspace .dp-fav-cell:hover { border-color: #d9c8bd; }
.dp-utilspace .dp-fav-cell.is-on { border-color: #fa4c10; background: #fff9f6; }
.dp-utilspace .dp-fav-cell:not(.is-on) { opacity: .45; }
.dp-utilspace .dp-fav-cell .dp-fav-tick {
  position: absolute; top: 8px; right: 8px;
  width: 17px; height: 17px; border-radius: 5px;
  border: 1.5px solid #cbbdb4; background: #fff;
}
.dp-utilspace .dp-fav-cell.is-on .dp-fav-tick { border-color: #fa4c10; background: #fa4c10; }
.dp-utilspace .dp-fav-cell.is-on .dp-fav-tick::after {
  content: ''; position: absolute; left: 5px; top: 1.5px;
  width: 4px; height: 8px;
  border: solid #fff; border-width: 0 2px 2px 0;
  transform: rotate(43deg);
}
.dp-utilspace .dp-fav-cell .dp-fav-sizecheck:focus-visible + .dp-fav-tick {
  outline: 2px solid #fa4c10; outline-offset: 2px;
}
/* THE CHECKERBOARD IS NOT DECORATION. It is how a transparent background
   becomes visible before iOS turns it into a black square. */
.dp-utilspace .dp-fav-checker {
  display: flex; align-items: center; justify-content: center;
  width: 100%; min-height: 100px;
  border-radius: 8px;
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #e6e6e6 25%, transparent 25%, transparent 75%, #e6e6e6 75%),
    linear-gradient(45deg, #e6e6e6 25%, transparent 25%, transparent 75%, #e6e6e6 75%);
  background-size: 12px 12px;
  background-position: 0 0, 6px 6px;
}
.dp-utilspace .dp-fav-truesize { image-rendering: auto; display: block; }
.dp-utilspace .dp-fav-cellsize { font-size: 12px; font-weight: 700; color: var(--dp-text); }
.dp-utilspace .dp-fav-cellwhy  { font-size: 11px; color: #6f655f; text-align: center; line-height: 1.3; }

/* --- the advisory --- */
/* Advisory, not an error: it sits at the top of the previews it refers to and
   never disables anything. Icon AND text, because colour alone does not carry
   meaning for everyone. */
.dp-utilspace .dp-fav-advisory {
  display: flex; flex-direction: column; gap: 8px;
  margin-top: 4px;
}
.dp-utilspace .dp-fav-advisory:not([hidden]) { padding-top: 12px; border-top: 1px solid #f0e0d4; }
.dp-utilspace .dp-fav-note {
  display: flex; gap: 8px; align-items: flex-start;
  margin: 0; padding: 8px 10px;
  border-radius: 9px;
  font-size: 12.5px; line-height: 1.4;
}
/* #8a5300 on #fff8ec is 4.9:1; #33608a on #eef4fa is 5.1:1. Both clear AA. */
.dp-utilspace .dp-fav-note.is-warn { background: #fff8ec; color: #8a5300; border: 1px solid #f4dfc0; }
.dp-utilspace .dp-fav-note.is-info { background: #eef4fa; color: #33608a; border: 1px solid #cfe0ee; }
.dp-utilspace .dp-fav-noteicon {
  flex: 0 0 18px; width: 18px; height: 18px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 800; color: #fff;
}
.dp-utilspace .dp-fav-note.is-warn .dp-fav-noteicon { background: #c07d1a; }
.dp-utilspace .dp-fav-note.is-info .dp-fav-noteicon { background: #4d7ea8; }

/* --- rail controls --- */
/* Extends the shared chip; never redefines it. */
.dp-utilspace .dp-fav-bgchips { display: flex; flex-wrap: wrap; gap: 8px; }
.dp-utilspace .dp-fav-bgchips .dp-chip { flex: 1 1 auto; min-height: 44px; }
.dp-utilspace .dp-fav-swatch {
  width: 100%; min-height: 44px; padding: 4px;
  border: 1px solid #e8ded8; border-radius: 9px; background: #fff; cursor: pointer;
}
.dp-utilspace .dp-fav-range { width: 100%; min-height: 44px; }

@media (max-width: 900px) {
  .dp-utilspace .dp-fav-sectionbody { grid-template-columns: minmax(0, 1fr); }
  .dp-utilspace .dp-fav-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* The checkbox stays in the DOM for keyboard and screen-reader use; only its
   default appearance is replaced. Never display:none — that would take it out
   of the tab order and off the accessibility tree entirely. */
.dp-utilspace .dp-fav-sizecheck {
  position: absolute; opacity: 0; width: 1px; height: 1px;
  margin: 0; pointer-events: none;
}
.dp-utilspace .dp-fav-sizewarn {
  margin: 10px 0 0; font-size: 12.5px; color: #a33; font-weight: 600; text-align: center;
}

/* Three background chips share one row instead of wrapping the third onto a
   line of its own. */
.dp-utilspace .dp-fav-bgchips .dp-chip {
  flex: 1 1 0; min-width: 0; padding-left: 6px; padding-right: 6px;
  font-size: 13px; white-space: nowrap;
}

/* The two treatment sliders share one row; stacked they pushed the panel into
   a scroll the moment a background was chosen. */
.dp-utilspace .dp-fav-sliders {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}
.dp-utilspace .dp-fav-sliders .dp-field { margin: 0; }
.dp-utilspace .dp-fav-sliders .dp-field > label { font-size: 12px; }

/* The colour field sits on ONE line - label left, swatch right - rather than
   stacking a full-width swatch under its own label. Stacked it cost ~26px and
   was the last thing tipping the panel into a scroll when Custom is chosen. */
.dp-utilspace .dp-fav-colourfield {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.dp-utilspace .dp-fav-colourfield > label { margin: 0; }
.dp-utilspace .dp-fav-colourfield .dp-fav-swatch {
  width: 76px; flex: 0 0 76px; min-height: 40px;
}

/* --- grouped rail section (site.webmanifest details) --- */
/* A <details> now, sharing the op panel's Advanced-settings styling so the two
   disclosures in this suite look like one idea. Only the spacing above it is
   local; the chevron, the hit target and the focus ring all come from
   .dp-oppanel-advanced. */
.dp-utilspace .dp-fav-group {
  margin-top: 16px; padding-top: 14px; border-top: 1px solid #f0e0d4;
}
.dp-utilspace .dp-fav-group > summary { padding-top: 0; }
/* The note is the summary's explanation, so it only shows once opened. */
.dp-utilspace .dp-fav-group:not([open]) > .dp-fav-groupnote { display: none; }
.dp-utilspace .dp-fav-grouphead {
  margin: 0 0 2px; font-size: 13px; font-weight: 800; color: var(--dp-text);
}
.dp-utilspace .dp-fav-groupnote {
  margin: 0 0 10px; font-size: 11.5px; line-height: 1.35; color: #6f655f;
}
.dp-utilspace .dp-fav-text {
  width: 100%; min-height: 40px;
  padding: 8px 10px;
  border: 1px solid #e8ded8; border-radius: 9px;
  background: #fff; font-size: 13px; color: var(--dp-text);
}
.dp-utilspace .dp-fav-text:focus-visible { outline: 2px solid #fa4c10; outline-offset: 1px; }

/* Two fields that are really one control - a number and its unit - sharing a
   row. The number takes the space; the unit only needs enough for "MB". */
.dp-oppanel .dp-field-pair {
  display: grid;
  grid-template-columns: 1fr minmax(0, 96px);
  gap: 10px;
  align-items: end;
}
.dp-oppanel .dp-field-pair > .dp-field { margin: 0; }

/* The live summary on a one-step panel, where there is no button for it to sit
   under. It is the only feedback the panel gives, so it gets room of its own. */
.dp-oppanel .dp-oppanel-hint.is-standalone {
  display: block;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid #f0e0d4;
  font-size: 13px;
  line-height: 1.45;
  color: #6f635c;
}
.dp-oppanel .dp-oppanel-hint.is-standalone.is-error { color: #a33; font-weight: 600; }

/* A checkbox row reads as "[x] thing" on ONE line. The shared field styling
   gave the label its own bordered box, so it looked like a disabled input
   sitting beside a stray tickbox rather than one control. */
.dp-oppanel .dp-field.dp-field-check {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
}
.dp-oppanel .dp-field.dp-field-check > label {
  order: 2;
  margin: 0;
  border: 0;
  padding: 0;
  background: none;
  font-size: 13.5px;
  font-weight: 500;
  line-height: 1.35;
  cursor: pointer;
}
.dp-oppanel .dp-field.dp-field-check > input[type="checkbox"] {
  order: 1;
  flex: 0 0 18px;
  width: 18px;
  height: 18px;
  margin: 0;
  cursor: pointer;
}

/* ---- live watermark preview ------------------------------------------- */
/* Sits over the page thumbnail and shows the mark as it will be stamped.
   pointer-events:none so it never blocks selecting or clicking the page. */
.dp-page .dp-wm-preview {
  position: absolute;
  pointer-events: none;
  overflow: hidden;
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
  z-index: 2;
}
/* Single mark: dead centre of the page, rotated about its own middle. */
.dp-page .dp-wm-preview > span {
  position: absolute;
  left: 50%;
  top: 50%;
  transform-origin: center;
}
/* Tiled: a plain grid, each cell rotated. Spread out rather than packed, to
   match the generous spacing the exporter uses. */
.dp-page .dp-wm-preview.is-tiled {
  display: grid;
  grid-template-columns: repeat(var(--dp-wm-cols, 3), 1fr);
  align-content: space-around;
  justify-items: center;
  align-items: center;
}
.dp-page .dp-wm-preview.is-tiled > span {
  position: static;
  left: auto;
  top: auto;
  transform-origin: center;
}
/* An image mark sits in exactly the same slots as a text one - the selectors
   above are written for `span`, so the img needs the same two rules rather
   than a second layout. Width is set inline from the chosen percentage; height
   follows the picture's own aspect ratio. */
.dp-page .dp-wm-preview > img {
  position: absolute;
  left: 50%;
  top: 50%;
  height: auto;
  transform-origin: center;
}
.dp-page .dp-wm-preview.is-tiled > img {
  position: static;
  left: auto;
  top: auto;
  max-width: 100%;
  transform-origin: center;
}

/* ---------------------------------------------------------------------------
   File picker (schema `format: 'file'`).

   Scoped to .dp-filepick, which only exists for a field that declares the
   format, so the ~104 tools sharing this stylesheet and the op panel are not
   touched. The native input is hidden rather than removed: it is still the
   thing that opens the OS dialog, and it must stay in the DOM and focusable
   for the keyboard path to work.
   -------------------------------------------------------------------------*/
.dp-filepick {
  display: flex;
  align-items: center;
  gap: 10px;
  /* NOWRAP, deliberately. With wrapping on, a long button label - French
     "Choisir une image" - pushed the filename onto a second line and took the
     panel 6px past its body, so it scrolled at rest in exactly one language.
     The filename is the part that gives way: it ellipsizes (see below), which
     costs nothing, because the full name is not what the user is checking. */
  flex-wrap: nowrap;
}
.dp-filepick-input {
  /* Not display:none - that removes it from the accessibility tree and from
     the tab order, so the button below would be the only way in. */
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.dp-filepick-btn {
  flex: 0 0 auto;
  /* No height here on purpose. .dp-btn already sets it, and the coarse-pointer
     rule earlier in this file raises every .dp-btn to the 44px touch target -
     a min-height declared HERE would come later in the cascade and quietly
     undo that on exactly the devices that need it. */
}
.dp-filepick-name {
  /* 0 basis so it is the element that shrinks when the row is tight. */
  flex: 1 1 0;
  min-width: 0;
  font-size: 13px;
  color: #6b5b52;
  /* A long filename must not widen the 340px rail. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dp-filepick-name.is-error { color: #b3261e; }
.dp-filepick-thumb {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  object-fit: contain;
  border: 1px solid #e8ded8;
  border-radius: 6px;
  background: #fff;
}

/* ---------------------------------------------------------------------------
   Field help tooltip.

   Replaces the persistent paragraph that used to sit under a control. The
   panel body is 448px and must not scroll at rest; a single three-line help
   string was enough to break that, so the text moved behind a trigger and the
   space came back to every tool on the suite.
   -------------------------------------------------------------------------*/

/* The trigger rides at the end of the label, so it reads as belonging to that
   field rather than floating in the row.

   SCOPED TO .dp-field-hashelp, which the panel sets only on fields that
   actually have help. Written as `.dp-oppanel .dp-field > label` it matched
   every field in all ~104 tools, and - having equal specificity but coming
   later in this file - it beat `.dp-oppanel .dp-field-tabs > label
   { display: none }` and un-hid the label above every tab strip on the suite.
   Measured as the watermark tab row jumping 49px to 79px. */
.dp-oppanel .dp-field-hashelp > label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.dp-tip-trigger {
  /* 16px of ink; the 44px touch target comes from the padding below, which is
     why the box is content-box and the visual size stays small. */
  box-sizing: content-box;
  width: 16px;
  height: 16px;
  padding: 0;
  border: 0;
  background: none;
  color: #b08a78;
  cursor: pointer;
  line-height: 0;
  border-radius: 50%;
  position: relative;
  transition: color 160ms ease;
}
.dp-tip-trigger svg { width: 16px; height: 16px; display: block; }
.dp-tip-trigger:hover { color: #fa4c10; }
/* A visible ring, never removed: this control is reachable only by keyboard for
   some users and it is the sole route to the text. */
.dp-tip-trigger:focus-visible {
  outline: 2px solid #fa4c10;
  outline-offset: 2px;
}
/* Hit area without layout cost - the pseudo-element grows the target to 44px
   in every direction while the button keeps its 16px footprint in the label. */
.dp-tip-trigger::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

.dp-tip {
  position: fixed;
  z-index: 10060;   /* a help bubble may open inside a dialog (10050) */
  max-width: 260px;
  padding: 12px 14px;
  border-radius: 10px;
  background: #16233a;
  color: #fff;
  font-size: 13px;
  line-height: 1.45;
  font-weight: 400;
  box-shadow: 0 10px 28px rgba(10, 20, 40, .28);
  pointer-events: none;          /* never steals a hover from the field */
  opacity: 0;
  visibility: hidden;
  transform: translateY(2px);
  transition: opacity 160ms ease, transform 160ms ease, visibility 160ms;
}
.dp-tip.is-open {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.dp-tip-text { display: block; }

/* Arrow. Positioned from --dp-tip-arrow-x so it keeps pointing at the trigger
   even after the bubble has been pushed back inside the viewport. */
.dp-tip-arrow {
  position: absolute;
  left: var(--dp-tip-arrow-x, 50%);
  width: 0;
  height: 0;
  margin-left: -7px;
  border-left: 7px solid transparent;
  border-right: 7px solid transparent;
}
.dp-tip:not(.is-below) .dp-tip-arrow {
  top: 100%;
  border-top: 7px solid #16233a;
}
.dp-tip.is-below .dp-tip-arrow {
  bottom: 100%;
  border-bottom: 7px solid #16233a;
}

@media (prefers-reduced-motion: reduce) {
  .dp-tip { transition: opacity 1ms linear, visibility 1ms; transform: none; }
  .dp-tip.is-open { transform: none; }
}

/* Watermark's Layout/Angle pair needs a different split from the default.
   The generic pair is `1fr minmax(0, 96px)`, which suits a value and its unit.
   Here the narrow side is an angle - two or three digits - while the wide side
   is a select whose longest option, measured across all 18 languages, is
   Vietnamese "Dưới cùng chính giữa" at 160px. At the default split that
   cleared the cell by one pixel; a font that loads a fraction wider, or a
   longer translation later, silently truncates the option instead. Twelve
   pixels moved from the angle buys the margin, and the angle does not miss
   them. Scoped to this op, so every other paired row keeps the default. */
.dp-oppanel-watermark .dp-field-pair:has([data-field="layout"]) {
  /* 100px, not the generic 96px and not the 84px this briefly used.
     The angle is a dropdown now, and its last option is a WORD rather than a
     number - at 84px it had 49px of text, which fits "-90" and no language's
     word for Custom. 100px gives 65px, which clears the longest of those.
     Layout keeps 192px, and the longest option across all 18 languages
     (Spanish, 151px) still clears it with room to spare. */
  grid-template-columns: 1fr minmax(0, 100px);
}

/* ---- long card lists in the action panel -------------------------------
 *
 * The card control was built for the server tools, where three compression
 * levels each carry a description and the panel has room. Remove Pages offers
 * FIVE choices above a text field and a hint line, and at the standard 11px
 * padding that ran 36px past the panel body — which must never scroll in its
 * default state.
 *
 * Scoped per op rather than tightened globally: the compression cards are the
 * reason the roomier default exists, and squeezing them to fit a different
 * tool's list would be paying for this in the wrong place. */
.dp-oppanel-remove .dp-cards,
.dp-oppanel-removeDuplicate .dp-cards { gap: 5px; }

/* Padding comes down, the TAP TARGET does not: min-height holds the row at the
   44px floor the rest of the suite is built to, so tightening the list buys
   space from the gaps and the padding rather than from the thing a finger has
   to hit. */
.dp-oppanel-remove .dp-card,
.dp-oppanel-removeDuplicate .dp-card {
  padding: 8px 34px 8px 11px;
  min-height: 44px;
  box-sizing: border-box;
  align-items: center;
}

/* The tick sits against the card's own box, so it follows the padding in. */
.dp-oppanel-remove .dp-card-mark,
.dp-oppanel-removeDuplicate .dp-card-mark { top: 8px; }

/* ---- the quick picks on Remove Pages, as cards --------------------------
 *
 * They read as one list of things that can be combined — tick the cover and
 * the trailing blank and both go — so they get the card treatment the rest of
 * the suite uses for a choice list, rather than four loose checkboxes floating
 * on the gradient. Cards, not a dropdown: five options that fit on screen have
 * no business being hidden behind a click.
 *
 * Scoped to this op. `.dp-field-check` is every checkbox on every tool, and a
 * lone "Save each page as its own PDF" is not a list and must not grow a box. */
.dp-oppanel-remove .dp-field-check {
  align-items: center;
  gap: 0 11px;
  padding: 8px 12px;
  min-height: 44px;                 /* the suite's tap-target floor */
  box-sizing: border-box;
  border: 1.5px solid var(--dp-border);
  border-radius: 10px;
  background: #fff;
  cursor: pointer;
  transition: border-color .15s, background .15s;
}
.dp-oppanel-remove .dp-field-check:hover { border-color: #d8c9c1; }

/* Ticked reads at a glance, the same way a selected card does elsewhere. */
.dp-oppanel-remove .dp-field-check:has(input[type="checkbox"]:checked) {
  border-color: var(--dp-accent, #fa4c10);
  background: #fff7f3;
}
/* Keyboard focus must stay visible now that the row carries the border. */
.dp-oppanel-remove .dp-field-check:focus-within {
  border-color: var(--dp-accent, #fa4c10);
  box-shadow: 0 0 0 3px rgba(250, 76, 16, .18);
}
/* THE WHOLE CARD IS THE TARGET, not the 16px box and the words.
   Giving the row a border and cursor:pointer made it look like one hit area
   while only the checkbox and the label text actually responded — click the
   card anywhere else and nothing happened, which reads as "this option cannot
   be selected". The label is stretched over the row so a click anywhere in it
   activates the control it is already bound to; no JS, and the keyboard and
   screen-reader behaviour is unchanged because the label still belongs to that
   input. */
.dp-oppanel-remove .dp-field-check { position: relative; }
.dp-oppanel-remove .dp-field-check label { cursor: pointer; }
.dp-oppanel-remove .dp-field-check label::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 10px;
}

/* The four sit as one block; the typed range keeps its own spacing below. */
.dp-oppanel-remove .dp-oppanel-rows { gap: 6px; }
.dp-oppanel-remove .dp-field:not(.dp-field-check) { margin-top: 6px; }

/* ---- duplicate-page cards read like the Remove Pages ones ---------------
 *
 * The two page-removal tools sit next to each other in the menu and do the
 * same kind of job, so their option lists should not disagree about where the
 * chosen-state indicator lives. Remove Pages puts it on the left, ahead of the
 * words; the suite's default card puts it on the right. Aligned here, scoped
 * to this op, so the pair match without moving the indicator on the server
 * tools whose cards carry a description and were laid out around it.
 *
 * It stays ROUND, though. Round means one-of and square means many-of, and
 * these three are one-of — dressing a radio up as a checkbox would be a
 * tidier-looking lie. */
.dp-oppanel-removeDuplicate .dp-card {
  padding-left: 40px;
  padding-right: 12px;
}
/* THE LAYOUT PICKER IS A GALLERY OF SHAPES, NOT A LIST OF SENTENCES.
 *
 * Eight full-width stacked cards put three templates on screen and buried the
 * other five under a scroll, inside a panel that is a fixed 600px block. A
 * layout is a shape, and a shape is read at a glance in a grid - so the cards
 * become tiles, two across, diagram first with the name beneath it.
 *
 * Scoped to this op the same way .dp-serverspace scopes its own card density:
 * no other card field in the suite changes. */
.dp-oppanel-collage .dp-cards {
  display: grid;
  /* Two across. Three fitted more rows on screen but shrank the diagram to
     the point where a 2x2 and a 1-over-3 were hard to tell apart at a glance -
     and telling them apart IS the control. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}
.dp-oppanel-collage .dp-card {
  flex-direction: column;
  align-items: center;
  gap: 0;
  /* Even padding: the 38px right gutter existed to clear a tick that now sits
     in the corner, and off-centre tiles read as broken. */
  padding: 10px 10px 9px;
  text-align: center;
}
.dp-oppanel-collage .dp-card-body { width: 100%; }
/* Tall enough to tell a 2x2 from a 1-over-3 at a glance. The SVG keeps its own
   aspect, so a wide template genuinely draws as a wide strip. */
.dp-oppanel-collage .dp-card-art { height: 44px; margin-bottom: 7px; }
.dp-oppanel-collage .dp-card-title {
  font-size: 13px;
  font-weight: 600;
  line-height: 1.3;
  /* Two lines are RESERVED whether or not this label needs them. "Big photo on
     the left" wraps and "One row" does not, which left the tiles at different
     heights and the grid looking broken - and a translation can turn any of
     these into two lines, so it cannot be fixed by shortening the English. */
  min-height: 2.6em;
}
.dp-oppanel-collage .dp-card-tick { top: 7px; right: 7px; width: 15px; height: 15px; }
/* Selection is a filled diagram AND a ticked circle AND a border - never the
   colour alone. */
.dp-oppanel-collage .dp-card:has(.dp-card-radio:checked) { border-width: 2px; }

.dp-oppanel-removeDuplicate .dp-card-tick {
  top: 50%;
  right: auto;
  left: 12px;
  transform: translateY(-50%);
}
/* The radio is visually hidden but still laid out, and it sits at its static
   position — which the new left padding pushed 21px past the panel's edge,
   giving the whole rail a horizontal scrollbar. Pinned to the card's own
   corner so it can never contribute width again. It stays focusable and in
   the accessibility tree; only its position is fixed. */
.dp-oppanel-removeDuplicate .dp-card-radio { left: 0; top: 0; }

/* "The first one" / "The last one" are two words each, so they sit side by
   side rather than stacking — which is exactly the height the panel needed to
   stop scrolling, taken from the layout instead of from hiding a control. */
.dp-oppanel-removeDuplicate .dp-field[data-field="keep"] .dp-cards {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 5px;
}
.dp-oppanel-removeDuplicate .dp-field[data-field="keep"] .dp-card {
  padding-left: 34px;
  padding-right: 8px;
}
.dp-oppanel-removeDuplicate .dp-field[data-field="keep"] .dp-card-tick { left: 9px; }

/* ---- the split presets, three to a row ----------------------------------
 *
 * `.dp-oppanel .dp-chip` is `flex: 0 0 100%` on purpose - most op-panel chips
 * carry a sentence and need the width. Eleven grid shapes do not: stacked one
 * per row they ran 536px in a 448px panel, which is most of a screen of
 * scrolling to choose between "2 x 2" and "3 x 3".
 *
 * A grid rather than wrapping flex, so the columns line up instead of raggedly
 * filling - the same shape every tool in this category shows, and the reason
 * their presets read as a set. Crop already overrides the same rule for its
 * own chips, so this is the established way out of it. */
/* FOUR ACROSS, NOT THREE.
   The rail must never scroll - the content gets shorter, the block does not
   get taller - and this tool carries eleven grid presets plus four output
   formats. At three per row that is six rows of chips on its own. Four per
   row costs nothing legible: the longest label here is "2 across". */
.dp-oppanel-splitImage .dp-chips {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 4px;
}
/* The three "split by" choices are sentences, not sizes - at a quarter of the
   rail "Number of pieces" was cut off mid-word. Three across gives them room
   without touching the eight grid presets above. */
.dp-oppanel-splitImage [data-field="mode"] .dp-chips {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* CHIPS WRAP, BECAUSE ENGLISH IS THE SHORT ONE.
   Every label here fitted on one line in English and then ran off the edge in
   Spanish - "Cuadrícula personalizada" needs 130px of a 95px chip - and the
   same is true of most of the eighteen languages. Widening the chip is a fix
   for one language at a time; letting the text wrap is a fix for all of them.
   The 44px tap target stays the floor, so a two-line chip grows into the
   padding it already had rather than pushing the panel taller. */
.dp-oppanel-splitImage .dp-chip {
  white-space: normal;
  line-height: 1.25;
  padding: 4px 6px;
  hyphens: auto;
}
/* The nowrap lives on the inner span, not the chip, and a flex child will not
   shrink below its text without min-width: 0 - so both have to be said or the
   label still runs past the edge. */
.dp-oppanel-splitImage .dp-chip-label {
  white-space: normal;
  min-width: 0;
  overflow-wrap: anywhere;
  /* 13px, for the languages that need two words where English needs one.
     "2 en columna" wraps to two lines at 14px and the extra line, across three
     chip rows, pushed the Spanish panel 28px past the block. Label text, not
     body text, and the 44px tap target is untouched. */
  font-size: 13px;
}
/* Custom opens two more boxes under the eleven chips, and that was the 57px
   that tipped the panel into scrolling. Taken from the gaps, never from the
   44px tap target. */
/* Four, not eight. The tile-shape row is the eighth control this panel has to
   hold, and in the languages whose summary line wraps it landed 9px over -
   enough for a scrollbar. Taken from the gaps, which nobody reads, rather
   than from a tap target or from the wording of one language. */
.dp-oppanel-splitImage .dp-oppanel-rows { gap: 4px; }
.dp-oppanel-splitImage .dp-chip {
  flex: 0 1 auto;
  justify-content: center;
  padding: .4rem .35rem;
  min-height: 44px;              /* the tap-target floor stands */
  font-size: 14px;
  border-radius: 10px;
  text-align: center;
}

/* ---------------------------------------------------------------------------
 * Watermark Image: the placement grid.
 *
 * Nine placements are a PICTURE of the photo, not a list of ten phrases, and
 * every tool in this category draws them as a three by three grid. Scoped to
 * this op's panel class so the shared chip styling that eighteen other tools
 * rely on is untouched.
 * ------------------------------------------------------------------------ */
.dp-oppanel-imageWatermark .dp-field-position .dp-chips {
  display: grid;
  /* minmax(0,...) not 1fr alone: a plain 1fr floors at the content width, so
     "Middle right" made its column wider than the others and the grid stopped
     reading as a picture of the photo. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
}
/* "Repeated across the photo" is not one of the nine cells - it is a different
   kind of answer, so it sits under them across the full width. */
.dp-oppanel-imageWatermark .dp-field-position .dp-chips > :last-child {
  grid-column: 1 / -1;
}
.dp-oppanel-imageWatermark .dp-field-position .dp-chip {
  min-height: 44px;          /* the touch minimum, on the control most used */
  padding: 6px 4px;
  font-size: 12px;
  line-height: 1.25;
  text-align: center;
  white-space: normal;
}

/* ---------------------------------------------------------------------------
 * Bulk tools that PLACE something: the first-file preview.
 *
 * Only mounted for an op declaring livePreview, which none of the thirty-two
 * existing bulk tools do - see imagebatchworkspace.js. Sits above the file list
 * so the picture is what the eye lands on and the names stay underneath it.
 * ------------------------------------------------------------------------ */
.dp-batch-preview {
  margin-bottom: 14px;
  padding: 12px;
  border: 1px solid var(--dp-line, #e8ded8);
  border-radius: 10px;
  background: #fff;
  text-align: center;
}
.dp-batch-preview-canvas {
  /* The picture is whatever size the photo is; the column is fixed. Capped by
     height as well as width so a tall portrait cannot push the file list off
     the bottom of the screen. */
  max-width: 100%;
  max-height: 320px;
  width: auto;
  height: auto;
  border-radius: 6px;
}
.dp-batch-preview-cap {
  margin-top: 8px;
  font-size: 13px;
  color: var(--dp-muted, #7a6a62);
}
/* Hold to compare (ops with `compare`, e.g. Brighten Image). Beside the caption,
   a real 44px target, and it must not start a text selection or the phone's
   long-press menu while it is held. */
.dp-batch-preview-cap .dp-batch-compare {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-left: 10px;
  padding: 0 14px;
  font-size: 13px;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  touch-action: none;
}
.dp-batch-compare[aria-pressed="true"] { border-color: var(--dp-accent, #fa4c10); }
/* The typeface chips are a touch target like any other. The placement grid
   above sets its own 44px; these sat at 38. Scoped to this panel rather than
   raised across every chip in the suite, which is a separate change with a
   much wider blast radius. */
.dp-oppanel-imageWatermark .dp-field-font .dp-chip {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ---------------------------------------------------------------------------
 * Resize PDF: the placement grid.
 *
 * Same reasoning as the watermark grid - nine positions are a PICTURE of the
 * sheet, not nine phrases to read - and scoped to this op's own panel class so
 * the chips every other tool uses keep their row layout.
 * ------------------------------------------------------------------------ */
.dp-oppanel-scale .dp-field-anchor .dp-chips {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
}
.dp-oppanel-scale .dp-field-anchor .dp-chip {
  min-height: 44px;          /* the touch minimum */
  padding: 6px 4px;
  font-size: 12px;
  line-height: 1.25;
  text-align: center;
  white-space: normal;
}
/* Four ways to choose a size is a 2x2 decision, not a four-item stack: as full
   width buttons they cost most of the rail's first screen before the size
   control is even reached. Scoped to this panel. */
.dp-oppanel-scale .dp-field-mode .dp-chips {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
}
.dp-oppanel-scale .dp-field-mode .dp-chip {
  min-height: 44px;
  padding: 6px 8px;
  font-size: 13px;
  text-align: center;
  white-space: normal;
}
/* Width, height and unit on ONE row.
   The shared pair is a two column GRID, not a flexbox, so flex sizing on the
   children did nothing and the third field simply wrapped to a second line.
   Three columns here instead: the two numbers share the space and the unit
   gets only what a two-character word needs. Matched by the field it
   contains, the same way the watermark panel widens its own pair. */
.dp-oppanel-scale .dp-field-pair:has([data-field="units"]) {
  grid-template-columns: 1fr 1fr 88px;
}

/* ---------------------------------------------------------------------------
 * Compress-to-a-size panel: one control per line, and a labelled number box.
 *
 * `.dp-oppanel-rows` is a flex ROW. With a single field that is invisible, and
 * this panel had exactly one field until the advanced group arrived - at which
 * point the number box and the disclosure became siblings competing for the
 * same line, and the box was crushed to eight pixels wide with "Advanced
 * settings" sitting beside it. Stacking is what every other panel in the suite
 * looks like; scoped to this op so no other server tool's layout moves.
 * ------------------------------------------------------------------------ */
.dp-serverspace .dp-oppanel-compressTarget .dp-oppanel-rows {
  flex-direction: column;
  align-items: stretch;
}

/* The number box's label is hidden on purpose in the normal layout, because the
   "Custom size" card above it already says what the number is for. The
   size-specific pages lock that picker away, so nothing names the box and it
   renders as a bare "200".
   Keyed on size being the FIRST field: on a locked page the picker is gone and
   size leads, while on the 100KB page the picker still leads and the label stays
   hidden as designed. An earlier :only-child version of this broke the moment
   the advanced group was added. */
.dp-serverspace .dp-oppanel-rows > .dp-field[data-field="size"]:first-child > label {
  position: static;
  width: auto;
  height: auto;
  margin: 0 0 5px;
  overflow: visible;
  clip: auto;
  clip-path: none;
  white-space: normal;
}



/* ---- the small secondary group, unboxed -------------------------------
 *
 * Two optional controls do not need a disclosure (the reasoning is on
 * `advancedIsFlat` in oppanel.js). What they do need is to look secondary
 * without looking disabled: a quiet heading, the same white inputs on the same
 * warm ground as every other row, and a hairline to say where the primary
 * question ends.
 *
 * No card. The panel's one boxed element is the privacy note; giving the
 * OPTIONAL pair a box of its own made them the most designed thing in the
 * column, which is the opposite of what they are.
 * ---------------------------------------------------------------------- */
.dp-oppanel-advanced-flat {
  margin-top: 4px;
  padding-top: 12px;
  border-top: 1px solid var(--dp-line, #e8ded8);
}
.dp-oppanel-advanced-head {
  margin: 0 0 10px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  /* #7b716c measures 4.75:1 on white but 4.49:1 over the panel's warm ground,
     which is under the AA floor on the surface it actually sits on. #6f655f is
     the darker grey already used for the same reason on the tool cards. */
  color: #6f655f;
}

/* THE CHECKBOX WAS BEING STRETCHED.
 *
 * The touch rule sets `min-height: 44px` on `.dp-field input`, and a checkbox
 * is an input: on a phone the box rendered 18px wide by 44px tall, inside a
 * 16px grid column. The row still needs its 44px target - the label supplies
 * it, since the label is what a finger lands on - so the floor moves off the
 * box and onto the row. */
@media (pointer: coarse) {
  .dp-oppanel .dp-field-check input[type="checkbox"] { min-height: 0; }
}

/* THE CHECKBOX ROW IS A FLEXBOX, AND ORDER DECIDES EVERYTHING.
 *
 * `.dp-oppanel .dp-field.dp-field-check` lays the row out as flex and hands
 * the tickbox `order: 1` and the label `order: 2`. A third child with no order
 * of its own takes 0 and sorts FIRST, so the consequence line rendered to the
 * LEFT of the tickbox and squeezed the label into a five-line column. It has
 * to claim its own order and its own line. */
.dp-oppanel-advanced-flat .dp-field-check { flex-wrap: wrap; }
.dp-oppanel-advanced-flat .dp-field-check > .dp-field-help {
  order: 3;
  flex: 0 0 100%;
  margin-top: 2px;
}

/* The consequence line is the reason the group is flat, so it has to be read
   comfortably rather than skimmed past. --dp-muted holds 4.75:1 on white but
   4.49:1 over the warm panel ground this sits on. */
.dp-oppanel-advanced-flat .dp-field-help {
  color: #6f655f;
  max-width: 46ch;
}
/* Same reason, and the checkbox label is the smaller text of the two. Without
   this it inherits --dp-muted at 13.5px, which over the warm ground reads as a
   disabled control rather than an offer. */
.dp-oppanel-advanced-flat .dp-field-check > label,
.dp-oppanel-advanced-flat .dp-field > label { color: #6f655f; }

/* ==========================================================================
   Crop PDF — the crop stage
   --------------------------------------------------------------------------
   Scoped under .dp-pdfcrop / .is-cropstage. The stage REUSES Crop Image's
   .dp-crop-* visuals — the 14px handle with its ~44px hit area, the dimming
   box-shadow, touch-action:none — and the panel uses the shared .dp-oppanel /
   .dp-field / .dp-chips markup, so almost nothing needs to be added here.
   Crop Image must render byte-identically after this block, and it does
   because none of these selectors can match it.
   ========================================================================== */

/* The stage lives INSIDE the standard bordered work card, replacing the
   thumbnail grid's contents rather than the card itself. Hiding the card and
   mounting elsewhere is what made this tool look unlike every other one. */
.dp-grid-host.is-cropstage > *:not(.dp-pdfcrop) { display: none !important; }

.dp-pdfcrop {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}

/* A ground that is not the same white as the page, or a page with wide margins
   has no discernible edge against the card behind it. */
.dp-pdfcrop .dp-crop-stage {
  box-shadow: 0 1px 3px rgba(35, 32, 31, .12);
  background: #efe9e5;
}

.dp-pdfcrop .dp-crop-readout {
  margin: 0;
  font-size: 14px;
  color: var(--dp-muted);
}

/* Keyboard. The box is focusable so it can be moved with the arrow keys — a
   crop box that can ONLY be dragged is a gesture-only control. */
.dp-pdfcrop .dp-crop-rect:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: 2px; }
.dp-pdfcrop .dp-crop-handle:focus-visible { outline: 2px solid var(--dp-orange); outline-offset: 3px; }

/* Delete and Extract cannot work on this view: both act on the thumbnail
   grid's selection, and there is no grid to select in — Delete rendered
   enabled and did nothing at all. Rotate stays (a sideways scan needs
   straightening before it is cropped, and the crop now turns with the page),
   as do Undo and Redo.
   Hidden in CSS rather than dropped from the markup: the rest of workspace.js
   holds references to these buttons and sets .disabled on them as state
   changes, and removing them throws "Cannot set properties of null" — the file
   says so itself, right above the toolbar template. */
.dp-view-pdfcrop .dp-toolbar [data-op="remove"],
.dp-view-pdfcrop .dp-toolbar [data-op="extract"] { display: none; }

/* ---- page strip ---------------------------------------------------------- */

.dp-pdfcrop-strip {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 6px; max-width: 100%;
}
.dp-pdfcrop-strip[hidden] { display: none; }

.dp-pdfcrop-page {
  min-width: 44px; min-height: 44px;      /* the touch minimum, not the glyph */
  padding: 0 10px;
  border: 1px solid var(--dp-border);
  border-radius: 8px;
  background: #fff;
  color: var(--dp-text);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.dp-pdfcrop-page:hover { border-color: var(--dp-orange); }
.dp-pdfcrop-page.is-current {
  background: var(--dp-orange); border-color: var(--dp-orange); color: #fff;
}

/* ---- panel additions ----------------------------------------------------- */

/* Shape: four equal columns across the full panel width, each showing the
   proportion it will produce.
   The panel's default is one chip per row (flex: 0 0 100%) and it drops the
   base 44px min-height — four one-word shapes took four rows and stood 38px
   tall, under the touch floor. Sizing them to their text instead left a ragged
   gap on the right, so they share the row equally: `1 1 0` with a ZERO basis
   means the widths come from the row, not from whether the word is "A4" or
   "Letter". Crop Image scopes its own ratio chips the same way — this follows
   that precedent rather than changing the shared default and every tool. */
.dp-oppanel-crop .dp-field-cropshape .dp-chips {
  gap: 8px;
  flex-wrap: nowrap;          /* four across, never three-and-one */
}
.dp-oppanel-crop .dp-field-cropshape .dp-chip {
  flex: 1 1 0;
  min-width: 0;
  flex-direction: column;     /* the proportion, above its name */
  justify-content: center;
  gap: 5px;
  padding: 9px 4px;
  min-height: 64px;
  /* A pill is for a word; this now carries a drawing too, and a rounded
     rectangle frames one without the sides closing in on it. */
  border-radius: 12px;
}
.dp-oppanel-crop .dp-field-cropshape .dp-chip-label {
  font-size: 12.5px;
  /* "Letter" in a quarter of 287px is tight, and tighter in the other
     seventeen languages. A chip that silently clips its own name is worse
     than one that wraps. */
  white-space: normal;
  text-align: center;
  line-height: 1.15;
}
.dp-oppanel-crop .dp-field-cropshape .dp-chip-glyph svg {
  width: 22px; height: 22px; display: block;
}
/* The drawing follows the label into the brand colour when chosen, so a
   selected chip reads as one object rather than a coloured word sitting above
   a grey picture. */
.dp-oppanel-crop .dp-field-cropshape .dp-chip:has(.dp-chip-radio:checked) .dp-chip-glyph {
  color: var(--dp-orange);
}
.dp-oppanel-crop .dp-field-cropshape .dp-chip:hover { background: #fff; }

/* Selects carry the two list choices (units, apply-to). The shared default
   leaves them at 37px; 44 is the touch floor, and these are the controls a
   phone user reaches for most on this tool. Scoped, so no other panel moves. */
.dp-oppanel-crop select { min-height: 44px; }

/* Auto-crop and Reset side by side. Stacked, they cost 96px of a panel that
   must not scroll at rest, and neither is the primary action — that is the
   foot's, on every tool. */
.dp-pdfcrop-actions { grid-template-columns: 1fr 1fr; }
@media (max-width: 400px) {
  .dp-pdfcrop-actions { grid-template-columns: 1fr; }
}

/* Four margins in the shape of the page they describe. A flat list of four
   identical number boxes makes the reader match label to edge every time. */
.dp-pdfcrop-margins { display: grid; grid-template-columns: 1fr 1fr; gap: .5rem; }
.dp-pdfcrop-margin { display: grid; gap: .25rem; }
.dp-pdfcrop-margin > span {
  font-size: 13px;
  color: #6f655f;              /* --dp-muted drops under 4.5:1 on the warm panel */
}
.dp-pdfcrop-margin input { min-height: 44px; font-variant-numeric: tabular-nums; }

/* A checkbox row's explanation, sitting under the row rather than inside it —
   the row itself is a 44px flex line and a third item on it squeezes the label
   into a column. Indented to the label's edge so it reads as belonging to the
   control above rather than to the panel. */
/* Margins and their unit are one decision, so the unit select sits on the
   heading line rather than claiming a field of its own. The field is a grid,
   so the heading and the select share row 1 and the 2x2 box grid spans row 2. */
.dp-pdfcrop-marginfield {
  grid-template-columns: 1fr auto;
  align-items: center;
  column-gap: 8px;
}
.dp-pdfcrop-marginfield > label { grid-column: 1; }
.dp-pdfcrop-marginfield .dp-pdfcrop-unitsel {
  grid-column: 2;
  min-height: 34px;          /* a secondary control beside a heading, not a row */
  padding: 0 6px;
  font-size: 13px;
  width: auto;
}
.dp-pdfcrop-marginfield .dp-pdfcrop-margins { grid-column: 1 / -1; }

.dp-pdfcrop-checknote {
  margin: -2px 0 2px 0;
  margin-inline-start: 28px;
  font-size: 12.5px;
  line-height: 1.4;
  color: #6f655f;
}

/* Permanent deletion has no per-page form, so the row is disabled rather than
   silently ignored. Disabled state carries opacity AND a cursor, so it does not
   read as merely low-contrast text. */
.dp-field-cropburn.is-disabled > label {
  opacity: .45;
  cursor: not-allowed;
}
.dp-field-cropburn.is-disabled input { cursor: not-allowed; }

.dp-pdfcrop-range { margin-top: .5rem; min-height: 44px; width: 100%; }
.dp-pdfcrop-range[hidden] { display: none; }

.dp-pdfcrop-actions { display: grid; gap: .5rem; }
.dp-pdfcrop-actions .dp-btn { min-height: 44px; }

/* The page still leads on a narrow screen; the margins go to one column so the
   inputs keep their 44px height instead of being squeezed side by side. */
@media (max-width: 640px) {
  .dp-pdfcrop-margins { grid-template-columns: 1fr; }
}

/* ---- Text to PDF: six controls in a panel that must not scroll ----------
 *
 * `.dp-oppanel .dp-chip` is `flex: 0 0 100%` on purpose — most op-panel chips
 * carry a sentence and need the width. These do not: "Monospace", "Portrait",
 * "Narrow". Stacked one per row the six controls measured 768px inside a 451px
 * body, and a settings rail with its own scrollbar is the one thing this suite
 * does not do. Same override, same reason, as splitImage and crop above.
 *
 * These controls are the whole point of the page — every large competitor in
 * this category offers none at all — so none of them is hidden behind a
 * disclosure to win the height back. They are made to fit instead.
 *
 * Scoped to `.dp-oppanel-textToPdf`, which is `dp-oppanel-${op.id}`, so no
 * other tool's panel can match any of it.
 */
.dp-oppanel-textToPdf .dp-chips {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 5px;
}
/* Two choices, two columns. In the three-column grid above, Portrait and
   Landscape sat beside an empty third cell that read as a missing option. */
.dp-oppanel-textToPdf .dp-field[data-field="orientation"] .dp-chips {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.dp-oppanel-textToPdf .dp-chip {
  justify-content: center;
  text-align: center;
  /* Narrower padding and one step down in size, because a three-column cell in
     a 340px rail is about 95px and "Monospace" has to fit on one line in every
     language. min-height is deliberately NOT set: the shared rule leaves it at
     0 for a mouse and the `pointer: coarse` block further up already restores
     the 44px floor on a touch device, which is where it matters. */
  padding: 8px 6px;
  font-size: 14px;
}
/* Size and paper are two short answers to two short questions, and side by
   side they read as one line of page setup rather than two settings. The
   serverspace rows are already a wrapping flex; these two opt into sharing. */
.dp-oppanel-textToPdf .dp-field[data-field="fontSize"],
.dp-oppanel-textToPdf .dp-field[data-field="pageSize"] { flex: 1 1 0; min-width: 0; }

/* ---- Organize PDF's panel ----------------------------------------------
 *
 * The one tool in the suite with no op, and therefore the one whose action
 * rail was 600px of white. Everything here is scoped to `.dp-pagetools`, which
 * only `config.pageTools` builds and only Organize PDF sets, so no other
 * panel can match any of it.
 *
 * Shaped like `.dp-pickrow` above — the same label-over-buttons block, the
 * same 13px muted label — because that control answers the same kind of
 * question ("which pages?") on the rotate tool, and two shapes for one idea is
 * how a panel stops looking designed.
 */
.dp-pagetools {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.dp-pagetools-group {
  display: flex;
  flex-direction: column;
  gap: 7px;
}
/* Separators BETWEEN the groups, not around them: a rule above the first
   group would sit directly under the panel title and read as a second border. */
.dp-pagetools-group + .dp-pagetools-group {
  padding-top: 14px;
  border-top: 1px solid var(--dp-border);
}
.dp-pagetools-label {
  font-size: 13px;
  color: var(--dp-muted);
}
.dp-pagetools-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
/* TWO PER ROW, NOT FOUR.
   Four across a 302px panel gives 71px a button, and "Odd pages" needs 75 and
   "Even pages" 78 — measured, clipped in ENGLISH, which is the short one. Every
   other language is longer: "Ungerade Seiten", "Нечетные страницы",
   "الصفحات الفردية". Two per row gives ~148px and the row that only holds one
   button still stretches it full width, which is what `Reverse` wants anyway. */
.dp-pagetools-btn {
  flex: 1 1 calc(50% - 3px);
  min-width: 0;
  padding: 7px 10px;
  font-size: 13px;
  /* Wrapping to a second line inside the button is the fallback for a language
     that needs more than half the panel; clipping the word is not. */
  white-space: normal;
  line-height: 1.25;
  text-align: center;
}
/* The move field is one control in two parts, so it stays on one line and the
   number box takes the slack — "Move" must never wrap under its own input. */
.dp-pagetools-move {
  display: flex;
  gap: 6px;
  width: 100%;
}
.dp-pagetools-input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 7px 10px;
  font-size: 14px;
  border: 1.5px solid var(--dp-border);
  border-radius: 9px;
  background: #fff;
  color: var(--dp-text);
}
.dp-pagetools-move .dp-pagetools-btn { flex: 0 0 auto; }
.dp-pagetools-input:focus {
  outline: none;
  border-color: var(--dp-orange);
  box-shadow: 0 0 0 3px rgba(250, 76, 16, .12);
}
/* Disabled until pages are chosen — the state IS the instruction, so it has to
   read as switched off rather than as broken. */
.dp-pagetools-input:disabled,
.dp-pagetools-btn:disabled {
  opacity: .45;
  cursor: not-allowed;
}
/* The zoom slider fills its row: a range input at its intrinsic width sits
   short of the panel edge and reads as unfinished beside the full-width
   buttons above it. */
.dp-pagetools-zoom {
  width: 100%;
  accent-color: var(--dp-orange);
  cursor: pointer;
}
.dp-pagetools-zoom:focus-visible {
  outline: 2px solid var(--dp-orange);
  outline-offset: 3px;
}
.dp-pagetools-hint {
  margin: 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--dp-muted);
}

@media (pointer: coarse) {
  /* Same rule, same reason, as the op-panel chips: a finger needs 44px and a
     mouse does not, and the panel's height budget only matters for the mouse. */
  .dp-pagetools-btn,
  .dp-pagetools-input { min-height: 44px; }
  /* A range track is a few pixels tall; the THUMB is what a finger has to
     catch, so the height goes on the control itself. */
  .dp-pagetools-zoom { height: 44px; }
}

/* ---- Organize PDF: the file list, in the panel's own language ----------
 *
 * The list is shared with Merge PDF, where it is the whole panel and a
 * titled white card is the right shape. On Organize it sits ABOVE the page
 * tools, and there the card read as something from another page: a 15px bold
 * title and 15px filenames against groups that are a 13px grey label over
 * white controls. So on this tool only it becomes one more group — same label,
 * same white box as the buttons below it, same hairline between groups.
 *
 * Scoped to `.dp-has-pagetools`, which only config.pageTools sets, so Merge
 * PDF's list is untouched.
 */
.dp-has-pagetools .dp-actionpanel .dp-filelist {
  background: transparent;
  border-radius: 0;
  margin: 0 0 14px;
  padding: 0 0 14px;
  /* The same divider that separates the page-tool groups below. */
  border-bottom: 1px solid var(--dp-border);
}
.dp-has-pagetools .dp-filelist-head {
  margin: 0 0 7px;
  font-size: 13px;
  font-weight: 400;
  color: var(--dp-muted);
}
.dp-has-pagetools .dp-filelist-items {
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 9px;
  overflow: hidden;
}
.dp-has-pagetools .dp-filelist-item {
  gap: 8px;
  padding: 8px 10px;
  font-size: 13px;
  /* A divider that shows on white: --dp-grad-a is the peach the panel is
     painted with, and against the white box it all but vanished. */
  border-top: 1px solid var(--dp-border);
}
.dp-has-pagetools .dp-filelist-item:first-child { border-top: 0; }
.dp-has-pagetools .dp-filelist-dot { width: 8px; height: 8px; }
.dp-has-pagetools .dp-filelist-count {
  font-size: 12px;
  /* Page counts line up down the column instead of jittering with the digits. */
  font-variant-numeric: tabular-nums;
}
/* The "drag pages to change the order" note is said again, more fully, by the
   hint at the foot of the page tools — two instructions for the same gesture
   in one panel is the clutter this pass removes. Merge keeps it; there it is
   the only instruction on screen. */
.dp-has-pagetools .dp-filelist-note { display: none; }

/* ---------------------------------------------------------------------------
 * Rotate Image — the four turns, two to a row.
 *
 * Chips are full-width rows by default, which is right for "Slides only /
 * Slides and speaker notes" where the words carry the meaning. Here the words
 * are "Quarter turn right" and the picture above them has already shown what
 * that does, so four stacked rows spent 164px saying what 84px says — and with
 * the straighten slider and the advanced disclosure below them, that was the
 * difference between a panel that fits its 600px block and one that scrolls at
 * rest, which this suite does not do.
 *
 * Scoped to this tool's panel AND to its degrees field: `.dp-chips` is shared
 * by every op that uses a radio group, and several of them have two options
 * whose labels are a sentence long.
 * ------------------------------------------------------------------------ */
.dp-oppanel-rotateImage .dp-field-degrees .dp-chips {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: .4rem;
}
.dp-oppanel-rotateImage .dp-field-degrees .dp-chip { width: auto; }

/* ---------------------------------------------------------------------------
 * Chip groups as a grid of cards.
 *
 * The default is one chip per row, and the comment on that rule explains why:
 * stacked full-width rows read as one list of equal choices. That holds for a
 * single group of two or three. It stops holding when a panel carries four
 * groups — the eye sees four lists, the panel runs past its 600px block, and
 * the settings end up hidden behind a disclosure where nobody finds them.
 *
 * Same chip, same states, laid out in columns. Driven by `x-chipcols` in the
 * op's schema so the choice lives with the field it describes, rather than in a
 * per-tool override. Rotate Image still carries its own degrees rule above;
 * this is the mechanism to move it to, not a change that has moved it.
 * ------------------------------------------------------------------------ */
.dp-oppanel .dp-chips.is-cols-2,
.dp-oppanel .dp-chips.is-cols-3,
.dp-oppanel .dp-chips.is-cols-4 { display: grid; gap: 6px; }
.dp-oppanel .dp-chips.is-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.dp-oppanel .dp-chips.is-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.dp-oppanel .dp-chips.is-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
/* The one-per-row rule sets flex-basis; in a grid the cell decides the width. */
/* The flex chip carries a 5px bottom margin for wrapped rows. A grid spaces
 * its own rows, so that margin is dead weight that lands under every row. */
.dp-oppanel .dp-chips[class*="is-cols-"] .dp-chip {
  flex: initial;
  padding: 8px 6px;
  margin-bottom: 0;
}
/* A label that cannot fit its cell shrinks rather than pushing the grid wide. */
.dp-oppanel .dp-chips[class*="is-cols-"] .dp-chip-label {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

/* ---------------------------------------------------------------------------
 * SVG to PDF — four groups of cards in one block.
 *
 * The shared panel sets a comfortable rhythm for one or two controls. This page
 * asks four short questions and shows every answer, which is the right call —
 * a paper size is worth little if "which way round" is hidden behind a
 * disclosure — but six paper cards plus three more groups ran 54px past the
 * body. Same trim HTML to PDF already makes for the same reason, at the same
 * 13px chip label, and no deeper than is needed to bring it inside the block.
 *
 * Scoped on the FIELD classes, not on the op's class and not through :has().
 * Office to PDF shares dp-oppanel-officeToPdf and must not be squeezed — it has
 * two roomy cards — but it LOCKS all four of these fields, and a locked field
 * is filtered out before the row is built (oppanel.js), so .dp-field-svgPage
 * and its three siblings exist only on this page. That is the hook oppanel.js
 * already documents for exactly this, it needs no parent selector, and unlike
 * :has() it works on Firefox ESR 115 — where the trim silently not applying
 * would put the panel back to scrolling at rest.
 * ------------------------------------------------------------------------ */
.dp-oppanel .dp-field-svgPage > label,
.dp-oppanel .dp-field-svgOrient > label,
.dp-oppanel .dp-field-svgMargin > label,
.dp-oppanel .dp-field-svgFit > label {
  font-size: 13px;
  margin-bottom: 0;
}
.dp-oppanel .dp-field-svgPage .dp-chips[class*="is-cols-"] .dp-chip,
.dp-oppanel .dp-field-svgOrient .dp-chips[class*="is-cols-"] .dp-chip,
.dp-oppanel .dp-field-svgMargin .dp-chips[class*="is-cols-"] .dp-chip,
.dp-oppanel .dp-field-svgFit .dp-chips[class*="is-cols-"] .dp-chip {
  padding: 5px;
}
.dp-oppanel .dp-field-svgPage .dp-chip-label,
.dp-oppanel .dp-field-svgOrient .dp-chip-label,
.dp-oppanel .dp-field-svgMargin .dp-chip-label,
.dp-oppanel .dp-field-svgFit .dp-chip-label { font-size: 13px; }

/* ---- live page-number preview ----------------------------------------- */
/* Sits over the page thumbnail and shows each page's label where bake() will
   draw it. pointer-events:none so it never blocks selecting a page. */
.dp-page .dp-pn-preview {
  position: absolute;
  pointer-events: none;
  overflow: hidden;
  z-index: 2;
}
.dp-page .dp-pn-preview > span {
  position: absolute;
  line-height: 1;
  white-space: nowrap;
}
/* Each typeface chip is set in the face it names, so the choice is its own
   sample. Keyed on the radio value, not chip order, so a reordered enum
   cannot put the wrong face on a label. Scoped to this op. */
.dp-oppanel-pageNumbers .dp-field-font .dp-chip:has(input[value="sans"]) .dp-chip-label,
.dp-oppanel-headerFooter .dp-field-font .dp-chip:has(input[value="sans"]) .dp-chip-label { font-family: Helvetica, Arial, sans-serif; }
.dp-oppanel-pageNumbers .dp-field-font .dp-chip:has(input[value="serif"]) .dp-chip-label,
.dp-oppanel-headerFooter .dp-field-font .dp-chip:has(input[value="serif"]) .dp-chip-label { font-family: "Times New Roman", Times, serif; }
.dp-oppanel-pageNumbers .dp-field-font .dp-chip:has(input[value="mono"]) .dp-chip-label,
.dp-oppanel-headerFooter .dp-field-font .dp-chip:has(input[value="mono"]) .dp-chip-label { font-family: "Courier New", Courier, monospace; }

/* ---- load notice on an empty workspace -------------------------------- */
/* The rail's notice is hidden with the rail until a page loads, so the grid
   workspace mirrors it under the drop zone (see showNotice in workspace.js).
   Same weight and colours as .dp-rail .dp-notice; gone once pages exist. */
.dp-workspace .dp-zero-notice {
  margin: 14px auto 0;
  max-width: 460px;
  padding: 10px 12px;
  font-size: 14px;
  line-height: 1.45;
  text-align: start;
  border-radius: 10px;
  border: 1px solid transparent;
  cursor: auto;
}
.dp-workspace .dp-zero-notice:empty,
.dp-workspace .dp-zero-notice.is-ok,
.dp-workspace.has-pages .dp-zero-notice { display: none; }
.dp-workspace .dp-zero-notice .dp-notice-head { font-weight: 500; }
.dp-workspace .dp-zero-notice.is-warn { color: #6f4a06; background: #fff8ec; border-color: #f2dcb4; }
.dp-workspace .dp-zero-notice.is-error { color: #8c1d18; background: #fdf3f2; border-color: #f0c9c6; }

/* ---- AI PDF Editor -------------------------------------------------------
 * The chain page with the prompt at the head of the action panel. Scoped to
 * .dp-aieditor so the workflow and batch pages, which keep the bar on the
 * canvas, are untouched.
 */
.dp-aieditor .dp-ai { margin: 0 0 14px; }
.dp-aieditor .dp-ai-form { flex-direction: column; gap: 8px; }
.dp-aieditor .dp-ai-label { font-size: 14px; font-weight: 600; color: var(--dp-text); }
.dp-aieditor .dp-ai-input {
  width: 100%;
  min-height: 76px;
  resize: vertical;
  font: inherit;
  font-size: 15px;
  line-height: 1.45;
}
.dp-aieditor .dp-ai-form .dp-btn { width: 100%; min-height: 44px; justify-content: center; }
.dp-aieditor .dp-ai-note { margin-top: 6px; }

/* A step the assistant proposed that still needs the visitor: a password it
   may not choose, words it was not given. */
.dp-aieditor .dp-chain-step.is-incomplete { border-color: #d99a00; background: #fffaf0; }

/* The form step's settings - one control per field in the document. */
.dp-fill-form { display: flex; flex-direction: column; gap: 10px; }
.dp-fill-row { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.dp-fill-row > label { font-size: 13.5px; color: var(--dp-text); overflow-wrap: anywhere; }
.dp-fill-row input[type="text"],
.dp-fill-row select {
  width: 100%;
  min-height: 44px;
  padding: 8px 10px;
  font-size: 15px;
  border: 1px solid var(--dp-border);
  border-radius: 8px;
  background: #fff;
  color: var(--dp-text);
}
.dp-fill-row.dp-fill-check { flex-direction: row; align-items: center; gap: 10px; min-height: 44px; }
.dp-fill-check input[type="checkbox"] { width: 20px; height: 20px; flex: 0 0 auto; }
.dp-fill-form .dp-btn { width: 100%; min-height: 44px; justify-content: center; }

/* The editor's summary and translated-text downloads, on its result card. */
.dp-ai-textresult { margin: 12px 0 0; display: flex; flex-direction: column; gap: 8px; text-align: start; }
.dp-ai-textresult-title { margin: 0; font-size: 14px; font-weight: 600; color: var(--dp-text); }
.dp-ai-summary {
  max-height: 220px;
  overflow-y: auto;
  padding: 10px 12px;
  font-size: 14px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--dp-text);
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 8px;
}
.dp-ai-textresult-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.dp-ai-textresult .dp-btn { min-height: 44px; flex: 1 1 auto; justify-content: center; }

/* THE N-UP SHEET PICTURE.
 *
 * A strip at the top of the work card, above the page grid, drawn from the same
 * plan the export uses. Every selector here starts at .dp-nup-preview, which no
 * other tool creates: the flag that builds it is opt-in per tool, and the grid
 * below keeps every pixel of behaviour it had.
 */
.dp-nup-preview {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 10px 8px 8px;
  border-bottom: 1px solid var(--dp-border);
  background: linear-gradient(180deg, #fffaf7 0%, #fff 100%);
}
.dp-nup-stage {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 168px;
}
/* The sheet's own shadow, so a white sheet on a near-white strip still reads
   as a sheet rather than as empty space. */
.dp-nup-stage canvas {
  display: block;
  border-radius: 2px;
  box-shadow: 0 1px 4px rgba(80, 50, 35, .16);
}
.dp-nup-nav {
  display: flex;
  align-items: center;
  gap: 4px;
}
.dp-nup-count {
  min-width: 96px;
  text-align: center;
  font-size: 12.5px;
  color: var(--dp-muted);
}
/* 44px, like every other tap target in the suite. */
.dp-nup-arrow {
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  line-height: 1;
  color: var(--dp-text);
  background: transparent;
  border: 0;
  border-radius: 8px;
  cursor: pointer;
}
.dp-nup-arrow:hover:not(:disabled) { background: #fff0e8; }
.dp-nup-arrow:disabled { opacity: .3; cursor: default; }
.dp-nup-arrow:focus-visible { outline: 3px solid var(--dp-orange); outline-offset: -3px; }


/* The card is a GRID, not a stack: a strip inserted into it lands in an
   implicit row that the editor's own rule above pins to zero height, which is
   exactly what happened the first time. Scoped with :has() the same way that
   rule is, and appended after it so it wins, so only the tool that opts in
   changes shape. */
.dp-body:has(> .dp-nup-preview) {
  grid-template-rows: auto minmax(0, 1fr);
}


/* -------------------------------------------------------------------------
 * INSERT BLANK PAGE - the result strip
 *
 * The document as it will be, above the document as it is. Every selector
 * starts at .dp-insert-preview, which no other tool creates: the flag that
 * builds it is opt-in per tool, and the grid below keeps every pixel of
 * behaviour it had.
 * ---------------------------------------------------------------------- */
.dp-insert-preview {
  flex: 0 0 auto;
  /* A GRID ITEM IS AS WIDE AS ITS CONTENT UNLESS TOLD OTHERWISE, and a row of
     fourteen cards is wider than the card that holds it: the strip pushed the
     bordered viewport out to 884px inside a 728px column the first time. */
  min-width: 0;
  max-width: 100%;
  padding: 10px 12px 12px;
  border-bottom: 1px solid var(--dp-border);
  background: linear-gradient(180deg, #fffaf7 0%, #fff 100%);
}
.dp-insert-caption {
  font-size: 12.5px;
  color: var(--dp-muted);
  margin-bottom: 8px;
}
/* Horizontal, and allowed to scroll: the CARD may scroll, the action panel
   may not. Capped at fourteen slots so it rarely has to. */
.dp-insert-strip {
  min-width: 0;
  display: flex;
  align-items: flex-end;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 4px;
}
.dp-insert-card {
  position: relative;
  flex: 0 0 auto;
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 3px;
  box-shadow: 0 1px 3px rgba(80, 50, 35, .14);
  overflow: hidden;
}
.dp-insert-card img {
  width: 100%;
  height: 100%;
  object-fit: fill;
  display: block;
}
/* The page's own number, in the corner, never over the middle of the page. */
.dp-insert-card b {
  position: absolute;
  left: 2px;
  top: 2px;
  padding: 0 4px;
  font-size: 10px;
  font-weight: 600;
  line-height: 14px;
  color: #7a5c4e;
  background: rgba(255, 255, 255, .88);
  border-radius: 2px;
}
/* A blank reads as blank: dashed, empty, and labelled - because an
   unlabelled white rectangle beside a white page is not obviously a page. */
.dp-insert-card.is-blank {
  border-style: dashed;
  border-color: var(--dp-orange);
  background: #fffaf7;
  box-shadow: none;
  display: flex;
  align-items: center;
  justify-content: center;
}
.dp-insert-card.is-blank span {
  font-size: 10px;
  color: var(--dp-orange);
  text-align: center;
  padding: 0 2px;
}
/* A page borrowed from another file is not a blank: solid edge, filled tint,
   and it names the page it came from. */
.dp-insert-card.is-fromfile {
  border-style: solid;
  border-color: #c9b7ad;
  background: #f6efe9;
}
.dp-insert-card.is-fromfile span { color: #7a5c4e; }

.dp-insert-more {
  flex: 0 0 auto;
  align-self: center;
  font-size: 12px;
  color: var(--dp-muted);
  white-space: nowrap;
}

/* Same reason as the n-up strip one rule up: the card is a grid, and a strip
   inserted into it lands in an implicit row the editor's rule pins to zero. */
.dp-body:has(> .dp-insert-preview) {
  grid-template-rows: auto minmax(0, 1fr);
}

/* ---------------------------------------------------------------------------
 * Office to PDF: the two yes/no boxes share a row on a deck.
 *
 * The generic pair is "field + 96px unit box"; two checkboxes want halves.
 * When the deck question is hidden (Word, spreadsheet, a deck with no hidden
 * slides) the pair collapses and PDF/A takes the full width, so no other kind
 * of file sees a half-empty row. Scoped to this op: no other panel moves.
 * ------------------------------------------------------------------------ */
.dp-oppanel-officeToPdf .dp-field-pair:has([data-field="pdfA"]) {
  /* The rows container is a wrapping flex row and the generic pair sizes to
     its content - fine beside a unit box, 139px for a lone checkbox. */
  flex: 1 1 100%;
  grid-template-columns: 1fr 1fr;
  align-items: start;
}
.dp-oppanel-officeToPdf .dp-field-pair:has(> [data-field="hiddenSlides"][hidden]) {
  grid-template-columns: 1fr;
}

/* ---------------------------------------------------------------------------
 * TIFF to WebP: Width and Height share one row, as equal halves.
 * The generic pair is "field + 96px unit box"; two size boxes want halves, and
 * the pair is a flex child that otherwise sizes to its content. Scoped to the
 * image converter and to these two fields: no other panel moves.
 * ------------------------------------------------------------------------ */
/* Size dropdown + Width + Height on one row; the boxes fold away (and the
   dropdown takes the row) whenever Custom is not picked. */
.dp-oppanel-imageConvert .dp-field-pair:has([data-field="resizeW"]) {
  flex: 1 1 100%;
  grid-template-columns: 1fr 74px 74px;
  gap: 8px;
}
.dp-oppanel-imageConvert .dp-field-pair:has(> [data-field="resizeW"][hidden]) {
  grid-template-columns: 1fr;
}
/* Pages dropdown + seconds-per-page in the narrow box, folding the same way. */
.dp-oppanel-imageConvert .dp-field-pair:has([data-field="frameDelay"]) {
  flex: 1 1 100%;
}
.dp-oppanel-imageConvert .dp-field-pair:has(> [data-field="frameDelay"][hidden]) {
  grid-template-columns: 1fr;
}

/* ---------------------------------------------------------------------------
 * Brighten Image: "Save image as" and "Photo details" share one row as halves,
 * and the row folds back to one full-width dropdown when the photo-details
 * question does not apply (PNG / WebP output). Scoped to this op.
 * ------------------------------------------------------------------------ */
.dp-oppanel-colorAdjust .dp-field-pair:has([data-field="exportMeta"]) {
  flex: 1 1 100%;
  grid-template-columns: 1fr 1fr;
  align-items: start;
}
.dp-oppanel-colorAdjust .dp-field-pair:has(> [data-field="exportMeta"][hidden]) {
  grid-template-columns: 1fr;
}

/* ===========================================================================
 * Audit 2026-10-01 - accessibility and touch fixes that need no layout change.
 * Each rule is scoped to the one control it names.
 * ======================================================================== */

/* The file-card remove button was opacity:0 until hover - and a phone has no
   hover, so a tap in the card's corner removed a file through a control nobody
   had seen. Shown where there is no hover (as the scissors and the rotate
   button already are), with a hit area the size of a fingertip. */
@media (hover: none) {
  .dp-filecard-remove { opacity: 1; }
}
@media (pointer: coarse) {
  .dp-filecard-remove::after { content: ''; position: absolute; inset: -10px; }

  /* 44px where it costs no layout: the dialog's close button sits alone in a
     corner, and a menu row only grows the menu. */
  .dp-modal-close { width: 44px; height: 44px; }
  .dp-pagesmenu-item { min-height: 44px; }

  /* The editor's page bar is a pill that must not grow, so its 34px buttons
     keep their size and gain reach above and below instead. */
  .dp-editor-nav .dp-btn { position: relative; }
  .dp-editor-nav .dp-btn::after { content: ''; position: absolute; inset: -5px 0; }
}

/* 16px is this file's own floor for anything typed into: below it iOS Safari
   zooms the page on focus and throws the work block off screen. These were
   the fields the floor had missed. Phones only, so the desktop panels keep
   their compact size. */
@media (max-width: 767px) {
  .dp-urlrow-input,
  .dp-chain-select,
  .dp-ai-input,
  .dp-te-stinput,
  .dp-editor-pagebox,
  .dp-meme-font, .dp-meme-size,
  .dp-pagetools-input,
  .dp-fill-row input[type="text"],
  .dp-fill-row select,
  .dp-utilspace .dp-fav-text,
  .dp-cropspace .dp-crop-size .dp-field > input,
  .dp-cropspace .dp-crop-anchorfield select,
  .dp-pdfcrop-marginfield .dp-pdfcrop-unitsel {
    font-size: 16px;
  }
}

/* Windows High Contrast replaces border and background colours with system
   ones, and "selected" was carried by exactly those on controls whose real
   input is hidden - so chosen and unchosen looked the same. */
@media (forced-colors: active) {
  .dp-tile.is-on,
  .dp-card:has(.dp-card-radio:checked),
  .dp-chip:has(.dp-chip-radio:checked) {
    outline: 3px solid Highlight;
    outline-offset: -3px;
  }
}

/* RTL (audit 2026-10-01). Two things no logical property can state: where a
   background image sits, and which way a gradient runs. The lock belongs at
   the field's leading edge, and a range input fills from its start - which is
   the right-hand end in Arabic, Urdu and Farsi. Keyed on `lang` because this
   site sets direction in CSS and emits no `dir` attribute. */
[dir="rtl"] .dp-field-password input,
html:lang(ar) .dp-field-password input,
html:lang(ur) .dp-field-password input,
html:lang(fa) .dp-field-password input { background-position: right 14px center; }

[dir="rtl"] .dp-slider-range::-webkit-slider-runnable-track,
html:lang(ar) .dp-slider-range::-webkit-slider-runnable-track,
html:lang(ur) .dp-slider-range::-webkit-slider-runnable-track,
html:lang(fa) .dp-slider-range::-webkit-slider-runnable-track {
  background:
    linear-gradient(to left, var(--dp-orange) calc(var(--dp-pct, 50) * 1%),
                    #f0e4dd calc(var(--dp-pct, 50) * 1%));
}
[dir="rtl"] .dp-slider-range::-moz-range-track,
html:lang(ar) .dp-slider-range::-moz-range-track,
html:lang(ur) .dp-slider-range::-moz-range-track,
html:lang(fa) .dp-slider-range::-moz-range-track {
  background:
    linear-gradient(to left, var(--dp-orange) calc(var(--dp-pct, 50) * 1%),
                    #f0e4dd calc(var(--dp-pct, 50) * 1%));
}

/* ==========================================================================
   Split experiment (2026-10-22): home body for DocPivot / PicsPivot as sites of
   their own (output/doc/home-split.php). Scoped to .dpx-* so the combined hub
   on the main site is untouched. Brand tokens only; no new colours beyond the
   green/orange the existing feature art already uses.
   ========================================================================== */
.dpx-chips { list-style: none; margin: 14px auto 0; padding: 0; display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; }
.dpx-chips li { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 999px;
  background: #fff; border: 1px solid var(--dp-border); font-size: 14px; font-weight: 600; color: var(--dp-text); }
.dpx-chips li::before { content: ""; width: 14px; height: 14px; border-radius: 50%; flex: 0 0 14px;
  background: #1c6b3a url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M4 7.2l2 2 4-4.4' fill='none' stroke='%23fff' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/14px no-repeat; }


/* Content sections below the tool grid (home-split.php + home-split-faq.php):
   modern minimal, one accent (brand orange), hairline borders, real product
   screenshots in a browser frame. Site font is inherited. Scoped to .dpn-*. */
.dpn-sec, .dpn-formats{
  --n-ink: #141211; --n-ink2: #3d3833; --n-muted: #6f6863; --n-line: #ece7e3; --n-surface: #fafaf9;
  --n-accent: #fa4c10; --n-accent-ink: #c2410c; --n-accent-soft: #fff3ed; --n-ok: #167a43; --n-ok-soft: #eaf6ef;
  color: var(--n-ink);
}
.dpn-sec { padding: 88px 0; border-top: 1px solid var(--n-line); background: #fff; }
.dpn-sec h2, .dpn-sec h3{ margin: 0; color: var(--n-ink); text-wrap: balance; }
.dpn-h2 { font-size: clamp(26px, 3.2vw, 38px); line-height: 1.14; letter-spacing: -.02em; font-weight: 700; margin-top: 12px !important; }
.dpn-lede { color: var(--n-muted); font-size: 18px; line-height: 1.6; max-width: 62ch; margin: 14px 0 0; }
.dpn-eyebrow { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 700; color: var(--n-accent-ink); letter-spacing: .02em; text-transform: uppercase; }
.dpn-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--n-accent); }
.dpn-sec a:focus-visible, .dpn-q summary:focus-visible { outline: 2px solid var(--n-accent); outline-offset: 3px; border-radius: 4px; }

/* works-with strip */
.dpn-formats { border-top: 1px solid var(--n-line); background: #fff; }
.dpn-formats .container { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 22px; padding-top: 22px; padding-bottom: 22px; font-size: 14px; }
.dpn-formats strong { color: var(--n-ink2); font-weight: 600; margin-right: 4px; }
.dpn-formats span { font-weight: 700; letter-spacing: .03em; color: #8f8680; }
.dpn-ticks { list-style: none; padding: 0; margin: 22px 0 0; display: grid; gap: 12px; }
.dpn-ticks li { display: flex; gap: 10px; color: var(--n-ink2); line-height: 1.55; }
.dpn-ticks svg { flex: 0 0 18px; margin-top: 3px; color: var(--n-accent); }
.dpn-steps { list-style: none; counter-reset: dpn; padding: 0; margin: 22px 0 0; display: grid; gap: 12px; }
.dpn-steps li { counter-increment: dpn; display: flex; gap: 12px; color: var(--n-ink2); line-height: 1.55; }
.dpn-steps li::before { content: counter(dpn); flex: 0 0 26px; height: 26px; border-radius: 50%; display: grid; place-items: center;
  background: var(--n-accent-soft); color: var(--n-accent-ink); font-weight: 700; font-size: 13px; }
.dpn-link { display: inline-flex; align-items: center; gap: 6px; min-height: 44px; margin-top: 14px; font-weight: 700; color: var(--n-accent-ink); text-decoration: none; }
.dpn-link:hover { text-decoration: underline; }
.dpn-frame { margin: 0; min-width: 0; border-radius: 16px; background: #fff; border: 1px solid var(--n-line); overflow: hidden;
  box-shadow: 0 1px 0 rgba(0,0,0,.02), 0 24px 60px -24px rgba(40,20,10,.22); }
.dpn-frame-bar { height: 38px; display: flex; align-items: center; gap: 7px; padding: 0 14px; border-bottom: 1px solid var(--n-line); background: var(--n-surface); }
.dpn-frame-bar i { width: 10px; height: 10px; border-radius: 50%; background: #e2dbd6; flex: 0 0 10px; }
.dpn-frame-bar span { margin-left: 10px; font-size: 12px; color: var(--n-muted); background: #fff; border: 1px solid var(--n-line); border-radius: 7px; padding: 3px 10px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.dpn-frame img { display: block; width: 100%; height: auto; }

/* FAQ, two columns */
.dpn-faq-grid { display: grid; grid-template-columns: .8fr 1.2fr; gap: 56px; align-items: start; }
.dpn-faq-head { position: sticky; top: 32px; }
.dpn-q { border-bottom: 1px solid var(--n-line); }
.dpn-q:first-child { border-top: 1px solid var(--n-line); }
.dpn-q summary { list-style: none; cursor: pointer; padding: 18px 0; display: flex; justify-content: space-between; align-items: center; gap: 16px; min-height: 44px; }
.dpn-q summary::-webkit-details-marker { display: none; }
.dpn-q summary h3 { font-size: 16.5px; line-height: 1.45; font-weight: 700; margin: 0; }
.dpn-pm { position: relative; flex: 0 0 14px; height: 14px; }
.dpn-pm::before, .dpn-pm::after { content: ""; position: absolute; left: 0; right: 0; top: 6px; height: 2px; border-radius: 2px; background: var(--n-muted); transition: transform .2s; }
.dpn-pm::after { transform: rotate(90deg); }
.dpn-q[open] .dpn-pm::after { transform: rotate(0); }
.dpn-q p { margin: 0 0 18px; color: var(--n-ink2); line-height: 1.65; max-width: 64ch; }

@media (max-width: 991px) {
  .dpn-sec{ padding: 64px 0; } .dpn-faq-grid { grid-template-columns: 1fr; gap: 28px; }
  .dpn-faq-head { position: static; }
}
@media (max-width: 600px) {
  .dpn-lede{ font-size: 16.5px; }
}
@media (prefers-reduced-motion: reduce) { .dpn-pm::before, .dpn-pm::after{ transition: none; } }
/* bento visuals: network check, EXIF fields{ margin-top: 18px; border: 1px solid var(--n-line); border-radius: 10px; overflow: hidden; font-size: 13px; background: #fff; }
/* Split sites: hero title in the mockup's treatment (large, tight, accent phrase) */
.dp-hero.is-split:not(.is-collapsed) .dp-hero-title { font-size: clamp(34px, 5vw, 58px); line-height: 1.06; letter-spacing: -.035em; font-weight: 700;
  color: #141211; max-width: 1000px; margin: 0 auto .9rem; text-wrap: balance; }
.dp-hero.is-split .dp-hero-title em { font-style: normal; color: #fa4c10; }
.dp-hero.is-split:not(.is-collapsed) .dp-hero-title em { display: block; }
.dp-hero.is-split:not(.is-collapsed) .dp-hero-sub { font-size: 18px; line-height: 1.6; max-width: 60ch; margin-left: auto; margin-right: auto; }
.dp-hero.is-split.is-collapsed + .dpn-formats { display: none; }
.dp-hero + .dpn-formats .container { justify-content: center; }
/* trust badges sit under the drop box on split sites */
.dp-hero.is-split .dpx-chips { margin-top: 28px; }
.dp-hero.is-collapsed .dpx-chips { display: none; }
@media (max-width: 600px) { .dp-hero.is-split:not(.is-collapsed) .dp-hero-sub { font-size: 16px; } }
/* The site sets overflow-x:hidden on html/body, which makes body a scroll
   container and silently disables position:sticky. clip hides the same
   sideways overflow without that side effect; scoped to pages with this FAQ. */
html:has(.dpn-faq), html:has(.dpn-faq) body { overflow-x: clip !important; }
/* languages section on split sites: left-aligned heading like the other sections, map below */
.dp-langs.is-split { text-align: center; }
.dp-langs.is-split .dpn-lede { margin: 14px auto 32px; }

/* ==========================================================================
   Split homes v2 (from _mockup/home-v2.html): why bento, showcase rows,
   numbers band, closing CTA. Scoped to .dpv2. Headings in Inter Bold,
   self-hosted latin subset (fonts/home, SIL OFL).
   ========================================================================== */
@font-face { font-family: "DP Inter"; src: url("../fonts/home/inter-700-latin.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap; }
.dpv2 {
  --v-ink: #141211; --v-ink2: #3d3833; --v-muted: #6f6863; --v-line: #ece7e3; --v-soft: #f6f4f2; --v-soft2: #fbfaf9;
  --v-accent: #fa4c10; --v-accent-ink: #c2410c; --v-accent-soft: #fff1ea; --v-accent-line: #fbd9c8;
  --v-ok: #167a43; --v-dark: #141211; --v-dark2: #1e1b19; --v-dark-line: #2e2a27; --v-dark-muted: #a59d97;
  --v-head: "DP Inter", "Source Sans Pro", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --v-shadow: 0 1px 2px rgba(20,18,17,.04), 0 8px 24px -12px rgba(20,18,17,.12);
  --v-shadow-lg: 0 2px 4px rgba(20,18,17,.04), 0 30px 70px -30px rgba(60,25,5,.35);
  color: var(--v-ink); padding: 104px 0; background: #fff;
}
.dpv2 h2, .dpv2 h3 { font-family: var(--v-head); font-weight: 700; color: var(--v-ink); margin: 0; text-wrap: balance; }
.dpv2 h2 { font-size: clamp(30px, 3.6vw, 44px); line-height: 1.08; letter-spacing: -.035em; }
.dpv2 h3 { font-size: 19px; line-height: 1.3; letter-spacing: -.015em; }
.dpv2 a:focus-visible { outline: 2px solid var(--v-accent); outline-offset: 3px; border-radius: 6px; }
.dpv2-eyebrow { display: inline-flex; align-items: center; font: 700 12px/1 var(--v-head); letter-spacing: .08em; text-transform: uppercase;
  color: var(--v-accent-ink); padding: 7px 12px; border-radius: 999px; background: var(--v-accent-soft); border: 1px solid var(--v-accent-line); margin-bottom: 18px; }
.dpv2-lede { color: var(--v-muted); font-size: 18px; line-height: 1.6; max-width: 62ch; margin: 16px auto 0; }
.dpv2-intro { text-align: center; }

/* why: bento on soft grey */
.dpv2-why { background: var(--v-soft); }
.dpv2-bento { display: grid; grid-template-columns: repeat(4, 1fr); grid-auto-rows: minmax(232px, auto); gap: 16px; margin-top: 48px; }
.dpv2-cell { position: relative; min-width: 0; background: #fff; border: 1px solid var(--v-line); border-radius: 20px; padding: 26px;
  display: flex; flex-direction: column; overflow: hidden; box-shadow: var(--v-shadow); transition: transform .25s ease, box-shadow .25s ease; }
.dpv2-cell:hover { transform: translateY(-3px); box-shadow: var(--v-shadow-lg); }
.dpv2-cell p { color: var(--v-muted); font-size: 15px; line-height: 1.6; margin: 8px 0 0; }
.dpv2-cell p a { color: var(--v-accent-ink); font-weight: 600; text-decoration: none; }
.dpv2-cell p a:hover { text-decoration: underline; }
.dpv2-cell.is-w2 { grid-column: span 2; }
.dpv2-cell.is-h2 { grid-row: span 2; }
.dpv2-vis { margin-top: auto; padding-top: 20px; }
.dpv2-big { font: 700 64px/1 var(--v-head); letter-spacing: -.05em; color: var(--v-ink); margin-bottom: 6px; }

.dpv2-cell.is-dark { background: var(--v-dark); border-color: var(--v-dark); color: #fff; }
.dpv2-cell.is-dark::before { content: ""; position: absolute; right: -20%; bottom: -40%; width: 420px; height: 420px; border-radius: 50%;
  background: radial-gradient(closest-side, rgba(250,76,16,.35), transparent); pointer-events: none; }
.dpv2-cell.is-dark h3 { color: #fff; font-size: 26px; letter-spacing: -.03em; position: relative; }
.dpv2-cell.is-dark p { color: var(--v-dark-muted); position: relative; }
.dpv2-cell.is-dark small { display: block; position: relative; color: #8a827c; font-size: 13px; margin-top: 12px; }
.dpv2-lanes { position: relative; display: grid; grid-template-columns: 1.25fr 1fr; gap: 12px; }
.dpv2-lane { border-radius: 14px; padding: 18px; border: 1px solid var(--v-dark-line); background: var(--v-dark2); }
.dpv2-lane .n { font: 700 44px/1 var(--v-head); letter-spacing: -.04em; }
.dpv2-lane.is-dev .n { color: #5be39a; }
.dpv2-lane.is-srv .n { color: #ff8a5c; }
.dpv2-lane .t { font: 700 15px var(--v-head); margin-top: 8px; color: #fff; }
.dpv2-lane .d { font-size: 13.5px; line-height: 1.5; color: var(--v-dark-muted); margin-top: 4px; }
.dpv2-net { position: relative; margin-top: 14px; border: 1px solid var(--v-dark-line); border-radius: 12px; background: #0e0c0b;
  font: 13px ui-monospace, SFMono-Regular, Menlo, monospace; overflow: hidden; }
.dpv2-net-bar { display: flex; gap: 16px; padding: 9px 14px; border-bottom: 1px solid var(--v-dark-line); color: #7d756f; }
.dpv2-net-bar b { color: #e8e2dd; font-weight: 600; }
.dpv2-net-row { display: flex; justify-content: space-between; gap: 10px; padding: 12px 14px; color: #cfc8c2; flex-wrap: wrap; }
.dpv2-ok { color: #5be39a; display: inline-flex; align-items: center; gap: 8px; font-weight: 700; }
.dpv2-ok::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: #5be39a; animation: dpv2-pulse 2s infinite; }
@keyframes dpv2-pulse { 0% { box-shadow: 0 0 0 0 rgba(91,227,154,.6); } 70%, 100% { box-shadow: 0 0 0 8px rgba(91,227,154,0); } }

.dpv2-fan { position: relative; height: 104px; margin-bottom: 16px; }
.dpv2-fan svg { position: absolute; top: 0; width: 96px; height: 72px; border-radius: 10px; border: 3px solid #fff; box-shadow: 0 8px 18px -8px rgba(0,0,0,.35); }
.dpv2-fan svg.is-page { width: 64px; height: 86px; border-radius: 6px; border: 1px solid var(--v-line); }
.dpv2-fan svg:nth-child(1) { left: 0; transform: rotate(-9deg) translateY(12px); }
.dpv2-fan svg:nth-child(2) { left: 44px; transform: rotate(-2deg) translateY(4px); z-index: 1; }
.dpv2-fan svg:nth-child(3) { left: 90px; transform: rotate(7deg) translateY(14px); z-index: 2; }
.dpv2-count { position: absolute; left: 168px; top: 30px; z-index: 3; font: 700 13px var(--v-head); background: var(--v-ink); color: #fff; border-radius: 999px; padding: 5px 10px; white-space: nowrap; }

.dpv2-tiles { display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; }
.dpv2-tiles span { display: grid; place-items: center; min-height: 36px; padding: 0 4px; border-radius: 9px; background: var(--v-soft); border: 1px solid var(--v-line);
  font: 700 11.5px var(--v-head); letter-spacing: .02em; color: var(--v-ink2); text-align: center; }
.dpv2-tiles span.is-hot { background: var(--v-accent-soft); border-color: var(--v-accent-line); color: var(--v-accent-ink); }

.dpv2-target { display: flex; gap: 8px; flex-wrap: wrap; }
.dpv2-target span { font: 700 13px var(--v-head); padding: 8px 12px; border-radius: 10px; border: 1px solid var(--v-line); background: #fff; }
.dpv2-target span.is-on { border-color: var(--v-accent); color: var(--v-accent-ink); background: var(--v-accent-soft); }
.dpv2-meter { height: 10px; border-radius: 999px; background: var(--v-soft); overflow: hidden; margin-top: 14px; border: 1px solid var(--v-line); }
.dpv2-meter i { display: block; height: 100%; width: 94%; border-radius: 999px; background: linear-gradient(90deg, #ffb38f, var(--v-accent)); }
.dpv2-meter-l { display: flex; justify-content: space-between; gap: 8px; font: 700 12.5px var(--v-head); color: var(--v-muted); margin-top: 8px; }
.dpv2-meter-l b { color: var(--v-ok); }

.dpv2-exif { display: grid; grid-template-columns: 1fr 200px; gap: 22px; height: 100%; }
.dpv2-rows { margin-top: 16px; display: grid; gap: 6px; font-size: 13.5px; }
.dpv2-rows div { display: flex; justify-content: space-between; gap: 10px; padding: 7px 10px; border-radius: 8px; background: var(--v-soft); }
.dpv2-rows s { color: var(--v-muted); text-decoration-color: var(--v-accent); text-decoration-thickness: 2px; }
.dpv2-map { position: relative; min-height: 160px; border-radius: 14px; border: 1px solid var(--v-line); overflow: hidden;
  background: repeating-linear-gradient(0deg, #eef0ea 0 1px, transparent 1px 22px), repeating-linear-gradient(90deg, #eef0ea 0 1px, transparent 1px 22px), #f7f8f4; }
.dpv2-map::before { content: ""; position: absolute; left: -10%; top: 58%; width: 130%; height: 14px; background: #e3e7dc; transform: rotate(-12deg); }
.dpv2-map::after { content: ""; position: absolute; left: 44%; top: -10%; width: 12px; height: 130%; background: #e3e7dc; transform: rotate(8deg); }
.dpv2-map svg { position: absolute; left: 50%; top: 42%; transform: translate(-50%, -100%); z-index: 2; }

.dpv2-flow { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.dpv2-flow span { font: 700 13px var(--v-head); padding: 9px 12px; border-radius: 10px; background: var(--v-soft); border: 1px solid var(--v-line); }
.dpv2-flow span.is-out { background: var(--v-ink); color: #fff; border-color: var(--v-ink); }
.dpv2-flow i { font-style: normal; color: var(--v-accent); font-weight: 700; }

/* showcase rows */
.dpv2-show { display: grid; grid-template-columns: .85fr 1.15fr; gap: 64px; align-items: center; }
.dpv2-show + .dpv2-show { margin-top: 120px; }
.dpv2-show.is-rev { grid-template-columns: 1.15fr .85fr; }
.dpv2-show.is-rev .dpv2-copy { order: 2; }
.dpv2-copy { min-width: 0; display: flex; flex-direction: column; justify-content: center; align-items: flex-start; }
.dpv2 .dpn-ticks { gap: 14px; margin-top: 24px; }
.dpv2 .dpn-ticks svg { flex: 0 0 22px; width: 22px; height: 22px; padding: 4px; margin-top: 1px; border-radius: 50%; background: var(--v-accent-soft); color: var(--v-accent-ink); }
.dpv2 .dpn-ticks a, .dpv2-also a { color: var(--v-accent-ink); font-weight: 600; text-decoration: none; }
.dpv2 .dpn-ticks a:hover, .dpv2-also a:hover { text-decoration: underline; }
.dpv2 .dpn-steps { margin-top: 24px; }
.dpv2-also { font-size: 14.5px; color: var(--v-muted); margin: 16px 0 0; line-height: 1.6; }
.dpv2-go { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; margin-top: 26px; padding: 0 18px; border-radius: 12px;
  background: var(--v-ink); color: #fff !important; font: 700 14.5px var(--v-head); text-decoration: none !important; transition: transform .2s, background .2s; }
.dpv2-go:hover { background: #2a2622; transform: translateY(-1px); }
.dpv2-stage { position: relative; min-width: 0; border-radius: 28px; padding: 36px; border: 1px solid var(--v-line);
  background: radial-gradient(80% 70% at 85% 10%, rgba(250,76,16,.18), transparent 60%), radial-gradient(70% 60% at 0% 100%, rgba(255,179,143,.28), transparent 60%), var(--v-soft2); }
.dpv2-stage .dpn-frame { box-shadow: var(--v-shadow-lg); }
.dpv2-badge { position: absolute; z-index: 2; display: flex; align-items: center; gap: 10px; max-width: calc(100% - 16px); background: #fff; border: 1px solid var(--v-line);
  border-radius: 14px; padding: 12px 16px; box-shadow: var(--v-shadow-lg); font: 700 13.5px var(--v-head); color: var(--v-ink2); }
.dpv2-badge b { font-size: 20px; letter-spacing: -.03em; color: var(--v-ink); }
.dpv2-badge b.is-sm { font-size: 15px; letter-spacing: -.01em; }
.dpv2-badge .is-g { color: var(--v-ok); }
.dpv2-badge .is-o { color: var(--v-accent); }
.dpv2-badge.is-br { right: -14px; bottom: -18px; }
.dpv2-badge.is-tl { left: -14px; top: -18px; }
.dpv2-fmt { width: 100%; border-collapse: separate; border-spacing: 0; margin-top: 24px; font-size: 14.5px; border: 1px solid var(--v-line); border-radius: 14px; overflow: hidden; }
.dpv2-fmt th, .dpv2-fmt td { padding: 11px 14px; border-bottom: 1px solid var(--v-line); text-align: left; vertical-align: top; }
.dpv2-fmt tr:last-child th, .dpv2-fmt tr:last-child td { border-bottom: 0; }
.dpv2-fmt th { font: 700 13px var(--v-head); width: 76px; background: var(--v-soft2); color: var(--v-ink); }
.dpv2-fmt td { color: var(--v-ink2); }

/* numbers band */
.dpv2-stats { position: relative; overflow: hidden; background: var(--v-dark); color: #fff; padding: 72px 0; }
.dpv2-stats::before { content: ""; position: absolute; left: 50%; top: -60%; width: 900px; height: 500px; transform: translateX(-50%);
  background: radial-gradient(closest-side, rgba(250,76,16,.22), transparent); pointer-events: none; }
.dpv2-stat-grid { position: relative; display: grid; grid-template-columns: repeat(4, 1fr); }
.dpv2-stat { padding: 8px 28px; border-left: 1px solid var(--v-dark-line); }
.dpv2-stat:first-child { border-left: 0; padding-left: 0; }
.dpv2-stat b { display: block; font: 700 52px/1 var(--v-head); letter-spacing: -.05em; color: #fff; }
.dpv2-stat b em { font-style: normal; color: var(--v-accent); }
.dpv2-stat span { display: block; margin-top: 10px; color: var(--v-dark-muted); font-size: 15px; }

/* closing CTA: dark card, copy left, actions right */
.dpv2-cta-wrap { padding: 24px 0 96px; }
.dpv2-cta { position: relative; overflow: hidden; display: flex; align-items: center; justify-content: space-between; gap: 40px;
  border-radius: 32px; background: var(--v-dark); color: #fff; padding: 64px 64px; }
.dpv2-cta::before { content: ""; position: absolute; right: -10%; bottom: -80%; width: 900px; height: 560px;
  background: radial-gradient(closest-side, rgba(250,76,16,.5), transparent); pointer-events: none; }
.dpv2-cta::after { content: ""; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(rgba(255,255,255,.06) 1px, transparent 1px) 0 0 / 22px 22px; -webkit-mask: linear-gradient(90deg, #000, transparent 70%); mask: linear-gradient(90deg, #000, transparent 70%); }
.dpv2-cta > * { position: relative; z-index: 1; }
.dpv2-cta-copy { max-width: 620px; }
.dpv2-cta h2 { color: #fff; }
.dpv2-cta h2 em { display: block; font-style: normal; color: #ff8a5c; }
.dpv2-cta p { color: var(--v-dark-muted); font-size: 18px; line-height: 1.6; margin: 14px 0 0; }
.dpv2-cta-actions { display: flex; flex-direction: column; gap: 12px; flex: 0 0 auto; width: 240px; }
.dpv2-cta-actions .dpv2-btn { width: 100%; }
.dpv2-btn { display: inline-flex; align-items: center; justify-content: center; height: 52px; padding: 0 26px; border-radius: 12px; border: 1px solid transparent;
  font: 700 15.5px var(--v-head); text-decoration: none !important; transition: transform .2s, background .2s, border-color .2s; }
.dpv2-btn.is-primary { background: var(--v-accent); color: #fff !important; box-shadow: 0 10px 24px -10px rgba(250,76,16,.7); }
.dpv2-btn.is-primary:hover { background: #e8430a; transform: translateY(-1px); }
.dpv2-btn.is-ghost { background: rgba(255,255,255,.06); color: #fff !important; border-color: rgba(255,255,255,.28); }
.dpv2-btn.is-ghost:hover { border-color: rgba(255,255,255,.6); }

@media (max-width: 991px) {
  .dpv2 { padding: 80px 0; }
  .dpv2-bento { grid-template-columns: 1fr 1fr; }
  .dpv2-cell.is-w2, .dpv2-cell.is-h2 { grid-column: 1 / -1; grid-row: auto; }
  .dpv2-show, .dpv2-show.is-rev { grid-template-columns: 1fr; gap: 40px; }
  .dpv2-show.is-rev .dpv2-copy { order: 0; }
  .dpv2-show + .dpv2-show { margin-top: 88px; }
  .dpv2-stat-grid { grid-template-columns: 1fr 1fr; gap: 28px 0; }
  .dpv2-stat:nth-child(3) { border-left: 0; padding-left: 0; }
  .dpv2-cta { flex-direction: column; align-items: flex-start; padding: 48px 40px; }
}
@media (max-width: 600px) {
  .dpv2 { padding: 64px 0; }
  .dpv2-bento { grid-template-columns: 1fr; }
  .dpv2-lanes, .dpv2-exif { grid-template-columns: 1fr; }
  .dpv2-lede, .dpv2-cta p { font-size: 16.5px; }
  .dpv2-stage { padding: 16px; border-radius: 20px; }
  .dpv2-badge.is-br { right: 8px; bottom: -22px; }
  .dpv2-badge.is-tl { left: 8px; top: -22px; }
  .dpv2-stat { padding: 0 16px; }
  .dpv2-stat b { font-size: 42px; }
  .dpv2-cta-wrap { padding-bottom: 64px; }
  .dpv2-cta { padding: 40px 24px; border-radius: 24px; }
  .dpv2-cta-actions, .dpv2-btn { width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .dpv2-cell, .dpv2-go, .dpv2-btn { transition: none; }
  .dpv2-cell:hover { transform: none; }
  .dpv2-ok::before { animation: none; }
}

/* FAQ head on split homes, from the mockup: pill label, Inter heading,
   description, contact card. Questions keep their own style. */
.dpn-faq-head .dpv2-eyebrow { font-family: "DP Inter", "Source Sans Pro", ui-sans-serif, system-ui, sans-serif; color: #c2410c; background: #fff1ea; border-color: #fbd9c8; }
.dpn-faq-head h2 { font-family: "DP Inter", "Source Sans Pro", ui-sans-serif, system-ui, sans-serif; font-weight: 700;
  font-size: clamp(30px, 3.6vw, 44px); line-height: 1.08; letter-spacing: -.035em; color: #141211; margin: 0; }
.dpv2-faq-sub { color: #6f6863; font-size: 18px; line-height: 1.6; margin: 16px 0 0; }
.dpv2-faq-desc { color: #3d3833; font-size: 15.5px; line-height: 1.7; margin: 12px 0 0; max-width: 46ch; }
.dpv2-help { margin-top: 28px; border: 1px solid #ece7e3; border-radius: 18px; padding: 24px; background: #fbfaf9; }
.dpv2-help h3 { font-family: "DP Inter", "Source Sans Pro", ui-sans-serif, system-ui, sans-serif; font-weight: 700; font-size: 17px; letter-spacing: -.01em; color: #141211; margin: 0; }
.dpv2-help p { color: #6f6863; font-size: 15px; line-height: 1.6; margin: 8px 0 0; }
.dpv2-help .dpv2-help-tip { font-size: 14px; color: #6f6863; }
.dpv2-help a { display: inline-flex; align-items: center; gap: 6px; min-height: 44px; margin-top: 8px; color: #c2410c !important; font: 700 15px "DP Inter", "Source Sans Pro", sans-serif; text-decoration: none !important; }
.dpv2-help a:hover { text-decoration: underline !important; }

/* Works-with strip v2: file badges with a per-format tint, in a soft band under the hero */
.dpn-formats.is-v2 { border-top: 0; border-bottom: 1px solid #ece7e3; background: linear-gradient(#fff, #fbfaf9); }
.dpn-formats.is-v2 .container { display: block; padding-top: 22px; padding-bottom: 22px; }
.dpv2-works { display: flex; align-items: center; justify-content: center; gap: 14px 22px; flex-wrap: wrap; }
.dpv2-works-label { font: 700 12px/1 "DP Inter", "Source Sans Pro", ui-sans-serif, system-ui, sans-serif; letter-spacing: .1em; text-transform: uppercase; color: #8a827c;
  display: inline-flex; align-items: center; gap: 12px; }
.dpv2-works-label::after { content: ""; width: 28px; height: 1px; background: #ddd5cf; }
.dpv2-works-list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; }
.dpv2-works-list li { display: inline-flex; align-items: center; gap: 7px; height: 36px; padding: 0 12px 0 10px; border-radius: 10px; background: #fff;
  border: 1px solid #ece7e3; box-shadow: 0 1px 2px rgba(20,18,17,.04); color: var(--fc, #6f6863);
  transition: transform .2s ease, border-color .2s ease, box-shadow .2s ease; }
.dpv2-works-list li:hover { transform: translateY(-2px); border-color: color-mix(in srgb, var(--fc) 35%, #ece7e3); box-shadow: 0 8px 18px -10px rgba(20,18,17,.25); }
.dpv2-works-list li span { font: 700 13.5px "DP Inter", "Source Sans Pro", ui-sans-serif, system-ui, sans-serif; letter-spacing: -.005em; color: #141211; }
@media (max-width: 600px) {
  .dpv2-works { gap: 12px; }
  .dpv2-works-label::after { display: none; }
  .dpv2-works-list li { height: 34px; padding: 0 10px 0 8px; }
}
@media (prefers-reduced-motion: reduce) { .dpv2-works-list li { transition: none; } .dpv2-works-list li:hover { transform: none; } }
/* Trust badges in the mockup style: white pill, plain green tick (no filled circle) */
.dp-hero.is-split .dpx-chips li { padding: 8px 16px 8px 14px; gap: 8px; font-size: 14.5px; font-weight: 600; color: #141211; border-color: #ece7e3; box-shadow: 0 1px 2px rgba(20,18,17,.04); }
.dp-hero.is-split .dpx-chips li::before { width: 14px; height: 14px; flex: 0 0 14px; border-radius: 0;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M2.5 7.4l3 3 6-6.6' fill='none' stroke='%23167a43' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/14px no-repeat; }
.dpn-formats .dpv2-works-label { font-size: 15px; font-weight: 700; color: #141211; letter-spacing: .08em; }
/* keep "Works with" beside the badges; badges wrap inside their own column */
.dpv2-works { flex-wrap: nowrap; }
.dpv2-works-label { flex: 0 0 auto; }
.dpv2-works-list { flex: 0 1 auto; min-width: 0; justify-content: flex-start; }
.dpv2-works { gap: 14px 18px; }
.dpv2-works-list { gap: 7px; }
@media (max-width: 767px) { .dpv2-works { flex-wrap: wrap; } .dpv2-works-list { flex: 1 1 100%; justify-content: center; } }
.dpv2-works-label::after { display: none; }
@media (min-width: 768px) and (max-width: 1199px) {
  .dpv2-works { gap: 12px; }
  .dpv2-works-list { gap: 5px; }
  .dpv2-works-list li { height: 32px; padding: 0 8px 0 7px; gap: 5px; }
  .dpv2-works-list li svg { width: 13px; height: 16px; }
  .dpv2-works-list li span { font-size: 12.5px; }
  .dpn-formats .dpv2-works-label { font-size: 13px; letter-spacing: .06em; }
}

/* showcase copy sized to sit beside the screenshot (no stretched stage) */
.dpv2-proof .dpv2-copy h2 { font-size: clamp(26px, 2.6vw, 34px); line-height: 1.12; }
.dpv2-proof .dpn-ticks, .dpv2-proof .dpn-steps { margin-top: 18px; gap: 10px; }
.dpv2-proof .dpv2-go { margin-top: 20px; }
.dpv2-fmtgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin: 20px 0 0; }
.dpv2-fmtgrid > div { border: 1px solid #ece7e3; border-radius: 12px; padding: 10px 12px; background: #fbfaf9; }
.dpv2-fmtgrid dt { font: 700 13.5px "DP Inter", "Source Sans Pro", ui-sans-serif, system-ui, sans-serif; color: #141211; margin: 0; }
.dpv2-fmtgrid dd { margin: 2px 0 0; font-size: 13px; line-height: 1.45; color: #3d3833; }
@media (max-width: 520px) { .dpv2-fmtgrid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 992px) and (max-width: 1199px) {
  .dpv2-show, .dpv2-show.is-rev { grid-template-columns: 1fr; gap: 36px; }
  .dpv2-show.is-rev .dpv2-copy { order: 0; }
  .dpv2-show .dpv2-stage { max-width: 820px; }
}
