/* 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 grew 231px after load and pushed everything below it
   — the same class of shift as an unreserved ad slot, just self-inflicted.
   Values are the measured zero-state height at each breakpoint. The reservation
   is dropped the moment the module marks the element, so a loaded document is
   never held to a minimum it has outgrown. */
#dp-workspace:not(.dp-workspace) {
  min-height: 231px;
  /* .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;
}

@media (max-width: 560px) {
  #dp-workspace:not(.dp-workspace) { min-height: 198px; }
}

/* .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; }

[dir="rtl"] .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); }
.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. */
  height: 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 { 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 { margin-left: 0; margin-right: auto; }

/* ---- 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: #9a8f89; letter-spacing: .01em; }
.dp-zero-privacy { margin: .2rem 0 0; font-size: 13px; color: #9a8f89; }

/* 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; }

@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. */
  position: absolute; inset-inline-end: 18px; top: 50%; transform: translateY(-50%);
  color: var(--dp-muted); pointer-events: none;
}
.dp-search input {
  width: 100%;
  /* Logical padding: the 48px clears the icon on whichever side it sits. */
  padding-block: 12px;
  padding-inline: 20px 48px;
  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);
  transition: border-color .15s ease, box-shadow .15s ease;
}
.dp-search input::placeholder { color: #a2968f; }
.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);
}
.dp-search input:focus + .dp-search-icon,
.dp-search:focus-within .dp-search-icon { color: var(--dp-orange); }

/* ---- 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;
}

/* ---- 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%; }
  .dp-search input { padding-block: 12px; padding-inline: 16px 44px; font-size: 16px; }
  .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) {
  .dp-faq-mark::before, .dp-faq-mark::after { 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. */
.dp-langs-map {
  position: relative;
  /* Sized to the spread of the pill coordinates. Taller leaves dead space
     under the map; shorter and the pills crowd. */
  min-height: 340px;
  margin: 0 auto;
  max-width: 1000px;
  padding: 1rem 0;
  background-image: radial-gradient(circle, rgba(35, 32, 31, .10) 1.5px, transparent 1.6px);
  background-size: 22px 22px;
  border-radius: var(--dp-radius);
}

.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: 1000;
  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;
  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; }

/* Category heading over each block of actions. Small and quiet: it is a
   signpost between groups, not a competing title. */
.dp-modal-group {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: .85rem 0 .3rem;
  padding-inline: 1.15rem;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--dp-muted);
}
.dp-modal-group:first-child { margin-top: .35rem; }

/* The category mark, shown once per group. Sized down from the 24-box the
   source SVGs use so it sits with 12.5px uppercase text rather than shouting
   over it. */
.dp-modal-group-ico { display: inline-flex; color: var(--dp-orange); }
.dp-modal-group-ico svg { width: 15px; height: 15px; }

/* Text-only actions keep the same left edge as the few that kept an icon, so
   the labels in a group line up whether or not a card has a mark. */
.dp-action.is-plain { padding-inline-start: 1.15rem; }

/* Two columns from 620px. auto-fit rather than a fixed count so a group with
   one action does not leave a hole, and so the single-column phone layout
   needs no separate rule. */
.dp-modal-actions {
  padding: 0 1rem .35rem;
  display: grid;
  gap: .5rem;
  grid-template-columns: 1fr;
}

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

.dp-action {
  display: flex;
  align-items: center;
  gap: .9rem;
  width: 100%;
  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; }
}

.dp-article {
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: 14px;
  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); }

@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; }

.dp-editor-bar {
  display: flex; flex-wrap: wrap; gap: .5rem; align-items: center;
  padding: .6rem .75rem;
  background: #fff;
  border: 1px solid var(--dp-border);
  border-radius: var(--dp-radius);
}
.dp-editor-tool.is-active {
  background: var(--dp-orange);
  border-color: var(--dp-orange);
  color: #fff;
}

.dp-editor-stage { display: flex; justify-content: center; }

/* 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; }
.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; }
.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; }

.dp-img-holder {
  display: flex;
  align-items: center;
  justify-content: center;
  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;
  border: 1px solid var(--dp-border);
  border-radius: var(--dp-radius);
  padding: 12px;
  min-height: 200px;
}

/* The checkerboard above shows through a transparent PNG, so "the background
   went white" is distinguishable from "the background is transparent". */
.dp-img-canvas {
  max-width: 100%;
  max-height: 62vh;
  height: auto;
  display: block;
  box-shadow: 0 2px 12px rgba(0,0,0,.12);
}

.dp-img-meta { margin: 0; font-size: 14px; color: var(--dp-muted); text-align: center; }

.dp-imgspace:not(.has-pages) .dp-body { 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-right: 72px; }
.dp-reveal {
  position: absolute;
  right: 8px;
  bottom: 6px;
  /* 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;
  color: var(--dp-orange);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.dp-reveal:hover { text-decoration: underline; }

/* 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);
}

/* 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. */
  .dp-reveal { min-height: 44px; padding-left: 14px; padding-right: 14px; }
  .dp-field-password input { padding-right: 84px; }
}

@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;
  }
}

.dp-chain {
  background: linear-gradient(180deg, var(--dp-grad-a), var(--dp-grad-b));
  border: 1px solid #f6d9c9;
  border-radius: var(--dp-radius);
  padding: 18px;
}
.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; }

.dp-chain-step {
  display: flex;
  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;
}

.dp-chain-stepctl { display: flex; gap: 4px; flex: 0 0 auto; }

.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); }

.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: left;
  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;
  background: #fff;
  color: var(--dp-text);
}

.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; }

.dp-textspace .dp-te-host { margin-top: 1rem; }
.dp-textspace:not(.has-pages) .dp-te-host { 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 { 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;
  grid-template-columns: repeat(3, 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;
  border-inline-start: 2px solid var(--dp-orange);
  border-block-end: 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;
  border-inline-start: 2px solid var(--dp-orange);
  border-block-end: 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: #9a8f89;
  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;
}

.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;
}

/* 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-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; }

/* 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. */
.dp-editor-host { margin-top: var(--dp-head-gap); }

/* 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%;
  object-fit: cover;
  object-position: center;
}
/* 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: 2000;
  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: left; }

/* 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: left;
}
.dp-dlmenu-list[hidden] { display: none; }
.dp-dlmenu-item {
  border: 0;
  background: none;
  text-align: left;
  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: #a89f9a; }
.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 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: left; }

/* ---- 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;
}
.dp-page.is-drop-before::after { left: -8px; }
.dp-page.is-drop-after::after  { right: -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;
}

/* 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;
}

/* 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; }

/* 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;
}
