/* DocuLens — Image to Text & Word to PDF.
 *
 * Not one hex appears below this block. Every colour, radius and shadow is a
 * token, and this is the only place they are given values.
 *
 * That rule was written for his previous site, where `body.dark` redefined the
 * same `--clr-*` names and our pages inherited his dark mode for free. He then
 * rebuilt the site in Tailwind and those tokens stopped existing — no
 * `--clr-brand-main`, no `--ui-corner`, no dark mode at all. Every rule here
 * would have fallen back to browser defaults.
 *
 * The discipline is what saved it. Because nothing hard-coded a colour, the
 * whole stylesheet was repointed by defining the same names once, here, in his
 * new palette. Five hundred lines of layout did not need to be touched.
 *
 * The values are read off his own pages — slate and blue-600, the rounded-xl
 * corners his cards use, the light-only ground. If he repaints again, this
 * block is the whole change.
 *
 * Scope: everything is under .dl, so nothing leaks into his other 33 tools.
 */

.dl {
  /* --- his palette, measured from includes/header.php and tools/*.php --- */
  --clr-brand-main:    #2563eb;  /* blue-600  — his single accent */
  --clr-brand-accent:  #1d4ed8;  /* blue-700  — hover/pressed */
  --brand-gradient:    linear-gradient(90deg, #2563eb, #4f46e5);

  /* The primary button fills with blue-700, one step darker than the accent.
     Deliberate: white on #2563eb measures 5.8:1, white on #1d4ed8 measures
     7.7:1, and the darker fill also reads as solid rather than as a tint —
     which is what it looked like next to the pale note panels. */
  --clr-action:        #1d4ed8;
  --clr-action-hover:  #1e40af;

  --clr-bg-base:       #f8fafc;  /* slate-50  — his body ground */
  --clr-bg-surface:    #ffffff;
  --clr-stroke:        #e2e8f0;  /* slate-200 — his border colour */

  --clr-text-heading:  #0f172a;  /* slate-900 */
  --clr-text-body:     #1e293b;  /* slate-800 — his body class */
  --clr-text-muted:    #64748b;  /* slate-500 */

  --clr-state-success: #10b981;
  --clr-state-error:   #ef4444;
  --clr-state-danger:  #ef4444;
  --clr-state-alert:   #f59e0b;

  /* Tailwind's rounded-xl / 2xl / full, which is what his cards and pills use */
  --ui-corner-sm:      0.5rem;
  --ui-corner:         0.75rem;
  --ui-corner-md:      1rem;
  --ui-corner-full:    9999px;

  --elevation-low:     0 1px 2px rgba(15, 23, 42, .04),
                       0 4px 12px rgba(15, 23, 42, .05);
  --elevation-mid:     0 10px 30px rgba(15, 23, 42, .08);
  --elevation-high:    0 20px 50px rgba(15, 23, 42, .14);

  --ui-speed:          .18s;

  --dl-gap: 1rem;
  color: var(--clr-text-body);
}

.dl-card {
  background: var(--clr-bg-surface);
  border: 1px solid var(--clr-stroke);
  border-radius: var(--ui-corner);
  box-shadow: var(--elevation-low);
}

/* ---------------------------------------------------------------- dropzone */

.dl-drop {
  border: 2px dashed var(--clr-stroke);
  border-radius: var(--ui-corner);
  padding: 2.75rem 1.25rem;
  text-align: center;
  cursor: pointer;
  background: var(--clr-bg-surface);
  transition: border-color var(--ui-speed), background-color var(--ui-speed);
}
.dl-drop:hover,
.dl-drop:focus-visible,
.dl-drop.is-over {
  border-color: var(--clr-brand-main);
  background: color-mix(in srgb, var(--clr-brand-main) 5%, var(--clr-bg-surface));
}
.dl-drop-icon { color: var(--clr-brand-main); margin-bottom: .6rem; }
.dl-drop strong { color: var(--clr-text-heading); display: block; font-size: 1.05rem; }
.dl-drop span { color: var(--clr-text-muted); font-size: .875rem; }

.dl-file {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  margin-top: 1rem; padding: .65rem .9rem;
  border: 1px solid var(--clr-stroke); border-radius: var(--ui-corner-sm);
  background: var(--clr-bg-base);
}
.dl-file-name { color: var(--clr-text-heading); font-weight: 600; font-size: .9rem; }
.dl-file-size { color: var(--clr-text-muted); font-size: .78rem; }

/* -------------------------------------------------------------- the reader */

/* The frame the document is "read" in. overflow:hidden is load-bearing — the
   zoom below crops against it instead of spilling over the layout. */
.dl-scanframe {
  position: relative;
  overflow: hidden;
  border-radius: var(--ui-corner-sm);
  background: var(--clr-bg-base);
  min-height: 15rem;
}
.dl-scanframe img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; display: block;
}

/* The stand-in sheet, for a document the browser cannot render — a .docx.
   Drawn rather than photographed on purpose: it represents the file, and
   nothing about it claims to be a picture of the file. */
.dl-scanpage {
  position: absolute; inset: 12% 18%;
  display: flex; flex-direction: column; justify-content: flex-start; gap: .55rem;
  padding: 1.1rem .95rem;
  background: var(--clr-bg-surface);
  border: 1px solid var(--clr-stroke);
  border-radius: var(--ui-corner-sm);
  box-shadow: var(--elevation-low);
}
.dl-scanpage > i {
  display: block; height: .42rem; border-radius: var(--ui-corner-full);
  background: color-mix(in srgb, var(--clr-text-muted) 22%, var(--clr-bg-surface));
}
/* A page of text has a shape: a heading, full lines, and a short last line.
   Seven identical bars read as a loading skeleton, which is the one thing this
   must not be mistaken for — it is not waiting for content, it IS the content. */
.dl-scanpage > i:nth-child(1) { height: .62rem; width: 55%;
  background: color-mix(in srgb, var(--clr-text-muted) 38%, var(--clr-bg-surface)); }
.dl-scanpage > i:nth-child(4) { width: 88%; }
.dl-scanpage > i:nth-child(7) { width: 42%; }

/* The same zoom the image gets, so the carriage behaves identically whichever
   is underneath it. */
.dl-scanframe.is-reading .dl-scanpage {
  --dl-pass: 1.5s;
  transform-origin: center;
  will-change: transform;
  animation: dl-scan-zoom var(--dl-pass) ease-in-out infinite alternate;
}

/* The carriage turns around INSIDE the frame — top → bottom → top — rather than
   sliding off one edge and reappearing at the other. A scanner head does not
   teleport, and an off-edge sweep spends a third of every pass invisible, which
   reads as stalling rather than working. */
@keyframes dl-scan-sweep { from { top: 3%; } to { top: 97%; } }

/* The tail marks what was just read, so it has to trail the head — which means
   switching sides at each turn. One flip cycle spans both passes, hence the
   doubled duration; the stops either side of 50% put the flip on the turn
   instead of easing through it. */
@keyframes dl-scan-tail { 0%, 49.9% { transform: scaleY(1); } 50%, 100% { transform: scaleY(-1); } }

/* The document breathes with the carriage: in on the way down, out on the way
   back. Same duration and `alternate` as the sweep, so the two reverse on the
   same frame instead of drifting apart. */
@keyframes dl-scan-zoom { from { transform: scale(1); } to { transform: scale(1.06); } }

/* Hidden unless the engine is actually reading. The pair below shares
   `--dl-pass` so the carriage's turn and the image's zoom reversal land on the
   same frame; a beam sweeping a finished document would be decoration
   pretending to be progress. */
.dl-scanframe .dl-scanline { display: none; }
.dl-scanframe.is-reading .dl-scanline { display: block; }

.dl-scanline {
  --dl-pass: 1.5s;
  position: absolute; left: 0; right: 0; height: 3px;
  border-radius: var(--ui-corner-full);
  background: var(--brand-gradient);
  box-shadow: 0 0 18px 2px color-mix(in srgb, var(--clr-brand-accent) 55%, transparent);
  animation: dl-scan-sweep var(--dl-pass) ease-in-out infinite alternate;
  pointer-events: none;
}
.dl-scanline::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 100%; height: 44px;
  transform-origin: bottom center;   /* pivot on the beam, not on thin air */
  background: linear-gradient(to top,
    color-mix(in srgb, var(--clr-brand-accent) 16%, transparent), transparent);
  animation: dl-scan-tail calc(var(--dl-pass) * 2) linear infinite;
  pointer-events: none;
}
.dl-scanframe.is-reading img {
  --dl-pass: 1.5s;
  transform-origin: center;
  will-change: transform;
  animation: dl-scan-zoom var(--dl-pass) ease-in-out infinite alternate;
}

/* ------------------------------------------------------------ stage trail */

.dl-stages { list-style: none; margin: 0; padding: 0; }
.dl-stage {
  display: flex; align-items: center; gap: .6rem;
  font-size: .9rem; padding: .2rem 0;
  color: color-mix(in srgb, var(--clr-text-muted) 70%, transparent);
  transition: color var(--ui-speed);
}
.dl-stage-dot {
  width: 1rem; height: 1rem; display: grid; place-items: center; flex: none;
}
.dl-stage-dot i {
  width: .4rem; height: .4rem; border-radius: 50%;
  background: var(--clr-text-muted); opacity: .45; display: block;
  transition: all var(--ui-speed);
}
.dl-stage.is-active { color: var(--clr-text-heading); font-weight: 600; }
.dl-stage.is-active .dl-stage-dot i {
  width: .5rem; height: .5rem; opacity: 1;
  background: var(--clr-brand-main);
  box-shadow: 0 0 8px var(--clr-brand-main);
  animation: dl-pulse 1.4s ease-in-out infinite;
}
.dl-stage.is-done { color: var(--clr-text-muted); }
.dl-stage.is-done .dl-stage-dot i {
  width: .55rem; height: .55rem; opacity: 1; background: var(--clr-state-success);
}
@keyframes dl-pulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.35); } }

/* --------------------------------------------------------------- progress */

.dl-bar {
  height: 6px; border-radius: var(--ui-corner-full);
  background: color-mix(in srgb, var(--clr-brand-main) 12%, var(--clr-bg-surface));
  overflow: hidden;
}
.dl-bar > i {
  display: block; height: 100%; width: 0;
  border-radius: var(--ui-corner-full);
  background: var(--brand-gradient);
  transition: width .45s cubic-bezier(.22,.61,.36,1);
}

/* The transcript, the moment it exists — roughly 40 s before the vision model
   finishes. The strongest possible "something is happening", because it is the
   thing they came for. */
.dl-pre {
  margin: 0; white-space: pre-wrap; word-break: break-word;
  font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--clr-text-heading);
  background: var(--clr-bg-base);
  border: 1px solid var(--clr-stroke);
  border-radius: var(--ui-corner-sm);
  padding: .9rem; max-height: 28rem; overflow: auto;
}

/* ----------------------------------------------------------------- fields */

/* Fields are the answer; the transcript is the evidence. So they get the
   typographic weight — a recessive micro-label over a strong value, not two
   columns of equally grey text. */
.dl-fields { margin: 0; }
.dl-field {
  display: grid; gap: .1rem; padding: .6rem .5rem;
  border-top: 1px solid var(--clr-stroke);
  border-radius: var(--ui-corner-sm);
  transition: background-color var(--ui-speed);
}
.dl-field:first-child { border-top: 0; }
.dl-field:hover { background: var(--clr-bg-base); }
.dl-field dt {
  font-size: .68rem; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--clr-text-muted);
}
.dl-field dd {
  margin: 0; font-size: .93rem; font-weight: 600;
  color: var(--clr-text-heading); word-break: break-word;
  /* the value and its Copy button share the row */
  display: flex; align-items: flex-start; justify-content: space-between; gap: .5rem;
}
.dl-field dd.is-num { font-variant-numeric: tabular-nums; }
.dl-field dd.is-blank { font-weight: 400; font-style: italic; color: var(--clr-text-muted); }
/* Not confirmed by the text pass — flagged, never hidden. */
.dl-field dd.is-flagged {
  text-decoration: underline dotted var(--clr-state-alert);
  text-underline-offset: 4px;
}
@media (min-width: 576px) {
  .dl-field { grid-template-columns: minmax(0, 12rem) 1fr; gap: .35rem 1rem; align-items: baseline; }
}

/* A button that has to read as a link, because it sits mid-sentence inside the
   quota chip and a button-shaped thing there would compete with the chip. */
/* On a phone the pill's text wraps and the link lands on its own line, boxed
   and centred, looking like a stray button. Let it wrap as a block of text
   instead of a pill trying to stay one line. */
@media (max-width: 480px) {
  .dl-quota {
    display: block; width: 100%;
    border-radius: var(--ui-corner-sm);
    line-height: 1.5;
  }
}

.dl-quota-out {
  border: 0; background: none; padding: 0; font: inherit;
  color: var(--clr-action); font-weight: 600; text-decoration: underline;
  text-underline-offset: 2px; cursor: pointer;
}
.dl-quota-out:hover { color: var(--clr-action-hover); }
.dl-quota-out:disabled { opacity: .6; cursor: default; text-decoration: none; }

/* Something was refused because of what they just did. The strip is scrolled
   into view and then says so — a message that arrives silently above the fold
   they are not looking at is a message that did not arrive. */
@keyframes dl-flash {
  0%, 100% { transform: translateX(0); }
  20%      { transform: translateX(-5px); }
  40%      { transform: translateX(5px); }
  60%      { transform: translateX(-3px); }
  80%      { transform: translateX(3px); }
}
.dl-alert.is-flash { animation: dl-flash .38s ease-in-out 1; }
@media (prefers-reduced-motion: reduce) {
  .dl-alert.is-flash { animation: none; }
}

/* The modal's own action row. `d-flex gap-2` came from the Bootstrap his old
   site loaded and resolves to nothing on the rebuilt one. */
.dl-modal-acts { display: flex; gap: .5rem; flex-shrink: 0; }

/* ----------------------------------------------------------- the result --
 * The document's NAME, first and largest.
 *
 * It used to be a tab label on a batch and nothing at all on a single result,
 * which the client reported as "name not visible" on both tools — and he was
 * right. Whoever is looking at a finished document wants to know which one it
 * is before anything else, and how long it took immediately after. */

.dl-done {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: .5rem 1rem; flex-wrap: wrap;
  margin-bottom: 1rem; padding-bottom: .75rem;
  border-bottom: 1px solid var(--clr-stroke);
}
.dl-done-name {
  min-width: 0; flex: 1 1 14rem;
  font-size: 1.05rem; font-weight: 700; line-height: 1.3;
  color: var(--clr-text-heading);
  /* A long name wraps rather than being clipped: the middle of a file name is
     often the only part that distinguishes it from the one beside it. */
  overflow-wrap: anywhere;
}
.dl-done-meta {
  display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; flex: none;
}
.dl-done-badge {
  font-size: .72rem; font-weight: 700; padding: .15rem .55rem;
  border-radius: var(--ui-corner-full);
  color: #fff; background: var(--brand-gradient);
}
.dl-done-time {
  font-size: .78rem; font-weight: 600; padding: .15rem .5rem;
  border-radius: var(--ui-corner-full);
  color: var(--clr-text-heading);
  background: color-mix(in srgb, var(--clr-text-muted) 12%, var(--clr-bg-surface));
}
.dl-done-kind { font-size: .78rem; color: var(--clr-text-muted); }

/* -------------------------------------------------------------- downloads */

/* Preview on the card, save on the arrow. The arrow overlaps the card's corner
   rather than sitting in its own column, because a fifth column would push the
   four formats onto two rows on a phone. */
.dl-dl-wrap { position: relative; }
.dl-dl-save {
  position: absolute; top: .3rem; right: .3rem;
  display: grid; place-items: center; width: 1.85rem; height: 1.85rem;
  border-radius: var(--ui-corner-sm);
  color: var(--clr-text-muted); background: var(--clr-bg-surface);
  border: 1px solid transparent;
  transition: color var(--ui-speed), border-color var(--ui-speed),
              background-color var(--ui-speed);
}
.dl-dl-save:hover, .dl-dl-save:focus-visible {
  color: var(--clr-action); border-color: var(--clr-stroke);
  background: color-mix(in srgb, var(--clr-action) 8%, var(--clr-bg-surface));
}
/* Touch has no hover, so it never dims; on a pointer it stays quiet until the
   card is approached, so four arrows do not compete with four labels. */
@media (hover: hover) {
  .dl-dl-save { opacity: .45; }
  .dl-dl-wrap:hover .dl-dl-save,
  .dl-dl-save:focus-visible { opacity: 1; }
}

/* Every format of ONE document, zipped. The batch bar below only appears from
   two documents up, so without this a single result meant four separate saves. */
.dl-zip {
  display: flex; align-items: center; justify-content: center; gap: .5rem;
  width: 100%; padding: .55rem .8rem;
  border: 1px dashed var(--clr-stroke); border-radius: var(--ui-corner-sm);
  font-size: .82rem; font-weight: 600; color: var(--clr-text-muted);
  background: transparent;
  transition: color var(--ui-speed), border-color var(--ui-speed);
}
.dl-zip:hover {
  color: var(--clr-action); border-color: var(--clr-action);
  border-style: solid;
}

.dl-dl {
  display: flex; align-items: center; gap: .65rem; width: 100%;
  padding: .7rem .8rem; text-align: left;
  border: 1px solid var(--clr-stroke); border-radius: var(--ui-corner-sm);
  background: var(--clr-bg-surface); color: inherit;
  transition: transform var(--ui-speed), border-color var(--ui-speed), box-shadow var(--ui-speed);
}
.dl-dl:hover {
  transform: translateY(-2px);
  border-color: var(--clr-brand-main);
  box-shadow: var(--elevation-mid);
}
.dl-dl-badge {
  width: 2rem; height: 2rem; flex: none; display: grid; place-items: center;
  border-radius: var(--ui-corner-sm);
  font-size: .58rem; font-weight: 800; text-transform: uppercase; letter-spacing: .03em;
}
.dl-dl-label { display: block; font-size: .87rem; font-weight: 600; color: var(--clr-text-heading); line-height: 1.2; }
.dl-dl-hint  { display: block; font-size: .7rem; color: var(--clr-text-muted); line-height: 1.25; }

/* --------------------------------------------------------------- preview --
 * Clicking a format shows you the file before it saves. People want to see what
 * they are getting, and a wrong download is a wasted upload. */

.dl-modal {
  position: fixed; inset: 0; z-index: 2000;
  display: grid; place-items: center; padding: 1rem;
  background: rgb(0 0 0 / .48);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
}
.dl-modal[hidden] { display: none; }
.dl-modal-box {
  display: flex; flex-direction: column;
  width: 100%; max-width: 52rem; max-height: 86dvh;
  background: var(--clr-bg-surface);
  border: 1px solid var(--clr-stroke);
  border-radius: var(--ui-corner);
  box-shadow: var(--elevation-high);
  overflow: hidden;
}
.dl-modal-head {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  padding: .85rem 1.1rem; border-bottom: 1px solid var(--clr-stroke); flex: none;
}
.dl-modal-title { font-size: .95rem; font-weight: 700; color: var(--clr-text-heading); }
.dl-modal-sub   { display: block; font-size: .74rem; color: var(--clr-text-muted); }
.dl-modal-body  { flex: 1; min-height: 0; overflow: auto; padding: 1.1rem; }
.dl-modal-body .dl-pre { max-height: none; border: 0; background: transparent; padding: 0; }
.dl-modal-frame { width: 100%; height: 68dvh; border: 0; display: block; background: var(--clr-bg-base); }

/* ------------------------------------------------------------------ misc */

.dl-tabs { display: flex; gap: .25rem; border-bottom: 1px solid var(--clr-stroke); }
.dl-tab {
  border: 0; background: none; padding: .55rem .9rem;
  font-size: .88rem; font-weight: 600; color: var(--clr-text-muted);
  border-bottom: 2px solid transparent; transition: all var(--ui-speed);
}
.dl-tab:hover { color: var(--clr-text-heading); }
.dl-tab[aria-selected="true"] { color: var(--clr-brand-main); border-bottom-color: var(--clr-brand-main); }

.dl-note {
  font-size: .8rem; color: var(--clr-text-muted);
  border-left: 3px solid var(--clr-brand-accent);
  padding: .5rem .75rem; border-radius: 0 var(--ui-corner-sm) var(--ui-corner-sm) 0;
  background: color-mix(in srgb, var(--clr-brand-accent) 6%, var(--clr-bg-surface));
}
.dl-alert {
  font-size: .87rem; padding: .7rem .9rem; border-radius: var(--ui-corner-sm);
  border: 1px solid color-mix(in srgb, var(--clr-state-error) 35%, var(--clr-bg-surface));
  background: color-mix(in srgb, var(--clr-state-error) 8%, var(--clr-bg-surface));
  color: var(--clr-state-error);
}
.dl-hidden { display: none !important; }
.dl-muted { color: var(--clr-text-muted); }
.dl-num { font-variant-numeric: tabular-nums; }

/* Every always-on animation dies here. The scan line is removed rather than
   frozen — a frozen zoom would leave the document cropped mid-scale. */
@media (prefers-reduced-motion: reduce) {
  .dl-scanline { display: none; }
  .dl-scanframe.is-reading img,
  .dl-scanframe.is-reading .dl-scanpage { animation: none !important; transform: none !important; }
  .dl-stage.is-active .dl-stage-dot i { animation: none; }
  .dl-bar > i { transition: none; }
}

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

/* A review signal, not a confidence score — so it is amber and worded as
   "worth a second look", never a percentage. */
.dl-warn {
  font-size: .84rem; line-height: 1.5;
  padding: .7rem .9rem; border-radius: var(--ui-corner-sm);
  border: 1px solid color-mix(in srgb, var(--clr-state-alert) 38%, transparent);
  background: color-mix(in srgb, var(--clr-state-alert) 9%, var(--clr-bg-surface));
  color: var(--clr-text-heading);
}
.dl-warn strong { color: var(--clr-state-alert); }

/* How much of the transcript made it into the fields. A meter, not a score:
   the track is a lighter step of the same hue, so it reads as one object. */
.dl-meter {
  flex: 1; height: 6px; border-radius: var(--ui-corner-full); overflow: hidden;
  background: color-mix(in srgb, var(--clr-brand-main) 14%, var(--clr-bg-surface));
}
.dl-meter > i {
  display: block; height: 100%; border-radius: var(--ui-corner-full);
  background: var(--brand-gradient);
}

.dl-chip-ok {
  font-size: .72rem; font-weight: 600; padding: .15rem .5rem;
  border-radius: var(--ui-corner-full);
  color: var(--clr-state-success);
  background: color-mix(in srgb, var(--clr-state-success) 12%, var(--clr-bg-surface));
}

/* Revealed on hover on pointer devices, always visible on touch — a Copy button
   on every row is more chrome than content. */
.dl-copy {
  border: 0; background: none; padding: 0 0 0 .5rem; flex: none;
  font-size: .72rem; font-weight: 600; color: var(--clr-brand-main);
  transition: opacity var(--ui-speed);
}
@media (hover: hover) {
  .dl-copy { opacity: 0; }
  .dl-field:hover .dl-copy, .dl-copy:focus-visible { opacity: 1; }
}

/* Quota. Quiet by default — it is context, not a warning — and only turns amber
   on the last read, where it becomes information you act on. */
.dl-quota {
  display: inline-block;
  font-size: .74rem; font-weight: 600;
  padding: .2rem .6rem; border-radius: var(--ui-corner-full);
  color: var(--clr-text-muted);
  background: var(--clr-bg-base);
  border: 1px solid var(--clr-stroke);
  font-variant-numeric: tabular-nums;
}
.dl-quota.is-low {
  color: var(--clr-state-alert);
  background: color-mix(in srgb, var(--clr-state-alert) 10%, var(--clr-bg-surface));
  border-color: color-mix(in srgb, var(--clr-state-alert) 32%, transparent);
}

/* ----------------------------------------------------------- showcase --
 * The demo performs the work rather than displaying its result. Rows are always
 * in the DOM and merely transparent until reached — mounting them one by one
 * would reflow on every step and make the follow-the-newest-row scroll (the
 * thing that sells "it is reading right now") impossible to compute. */

.dl-sample {
  font-size: .68rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  padding: .18rem .55rem; border-radius: var(--ui-corner-full);
  color: var(--clr-brand-main);
  background: color-mix(in srgb, var(--clr-brand-main) 12%, var(--clr-bg-surface));
}
.dl-show-left {
  background: var(--clr-bg-base);
  border-right: 1px solid var(--clr-stroke);
  display: flex; flex-direction: column;
}
@media (max-width: 767.98px) {
  .dl-show-left { border-right: 0; border-bottom: 1px solid var(--clr-stroke); }
}
.dl-show-frame { flex: 1; min-height: 16rem; border-radius: 0; }
.dl-show-cap {
  margin: 0; padding: .5rem .9rem 0;
  font-size: .68rem; color: var(--clr-text-muted);
}
/* Fixed height so the line swapping never nudges anything below it. */
.dl-show-say {
  display: flex; align-items: center; gap: .5rem; min-height: 3.4rem;
  padding: .6rem .9rem; font-size: .76rem; line-height: 1.35;
  color: var(--clr-text-body);
  border-top: 1px solid var(--clr-stroke); margin-top: .5rem;
}
.dl-show-tick { color: var(--clr-state-success); font-weight: 700; flex: none; }
.dl-show-replay {
  margin-left: auto; flex: none; border: 0; background: none; padding: 0;
  font-size: .72rem; font-weight: 600; color: var(--clr-brand-main);
}

.dl-show-tabs { display: flex; gap: .3rem; }
.dl-show-tab {
  border: 1px solid var(--clr-stroke); background: var(--clr-bg-surface);
  color: var(--clr-text-muted);
  border-radius: var(--ui-corner-full); padding: .25rem .75rem;
  font-size: .74rem; font-weight: 600; transition: all var(--ui-speed);
}
.dl-show-tab[aria-pressed="true"] {
  color: #fff; border-color: transparent; background: var(--brand-gradient);
}

.dl-show-box {
  position: relative; height: 16rem; overflow: auto;
  background: var(--clr-bg-base); border-radius: var(--ui-corner-sm);
  padding: .25rem .75rem;
}
.dl-show-box .dl-pre { border: 0; background: transparent; padding: .25rem 0; max-height: none; }
.dl-show-line { display: block; }

/* Faded in place, never absent — the box keeps its height and its scrollbar. */
.dl-field.is-waiting, .dl-show-line.is-waiting {
  opacity: 0; transform: translateY(4px);
}
.dl-field, .dl-show-line { transition: opacity .3s, transform .3s; }

.dl-num.is-live::before {
  content: ""; display: inline-block; width: .4rem; height: .4rem;
  margin-right: .35rem; border-radius: 50%;
  background: var(--clr-brand-main); box-shadow: 0 0 6px var(--clr-brand-main);
}

/* The convert page is DRAWN, not read: a wipe with a bright edge at the
   boundary, rather than a scan line. */
.dl-show-edge {
  position: absolute; left: 0; right: 0; height: 2px;
  background: var(--brand-gradient); border-radius: var(--ui-corner-full);
  box-shadow: 0 0 14px 2px color-mix(in srgb, var(--clr-brand-accent) 60%, transparent);
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .dl-show-edge { display: none; }
  .dl-field, .dl-show-line { transition: none; }
  .dl-field.is-waiting, .dl-show-line.is-waiting { opacity: 1; transform: none; }
}

/* The Simple / Intelligent choice. A bordered row rather than a bare radio:
   each option carries a consequence (speed, thoroughness, what it costs the
   visitor's allowance) and that only reads as a pair when both are visible. */
.dl-pick {
  display: flex; gap: .6rem; align-items: flex-start;
  padding: .7rem .85rem; margin-bottom: .5rem; cursor: pointer;
  border: 1px solid var(--clr-stroke); border-radius: var(--ui-corner-md);
  background: var(--clr-bg-surface);
}
.dl-pick:hover { border-color: var(--clr-brand-main); }
.dl-pick:has(input:checked) {
  border-color: var(--clr-brand-main);
  box-shadow: inset 0 0 0 1px var(--clr-brand-main);
}
.dl-pick input { margin-top: .25rem; flex: none; }
.dl-pick > span { display: block; }
.dl-pick-note {
  display: block; font-size: .82rem; color: var(--clr-text-muted);
  margin-top: .12rem;
}

/* The chosen-files list. Each row removes itself; the whole selection is not
   an all-or-nothing choice. */
.dl-filelist { list-style: none; margin: .75rem 0 0; padding: 0; }
.dl-fileitem {
  display: flex; align-items: center; gap: .6rem;
  padding: .45rem .6rem; border: 1px solid var(--clr-stroke);
  border-radius: var(--ui-corner-sm); margin-bottom: .35rem;
  background: var(--clr-bg-surface); font-size: .86rem;
}
.dl-fileitem-name { flex: 1; min-width: 0; overflow: hidden;
                    text-overflow: ellipsis; white-space: nowrap; }
.dl-fileitem-size { color: var(--clr-text-muted); font-size: .78rem; flex: none; }
.dl-fileitem-x {
  flex: none; border: 0; background: none; cursor: pointer; line-height: 1;
  font-size: 1.25rem; padding: 0 .25rem; color: var(--clr-text-muted);
}
.dl-fileitem-x:hover { color: var(--clr-state-danger, #dc2626); }

/* One tab per document in a batch. Named `filetab` and not `tab` on purpose —
   each panel already contains a Fields / Text / JSON tab strip, and sharing a
   class or a data attribute with it makes one click handler answer for both. */
.dl-filetabs {
  display: flex; gap: .35rem; flex-wrap: wrap;
  border-bottom: 1px solid var(--clr-stroke); padding-bottom: .5rem;
  margin-bottom: 1rem;
}
.dl-filetab {
  border: 1px solid var(--clr-stroke); background: var(--clr-bg-surface);
  border-radius: var(--ui-corner-sm); padding: .3rem .7rem; cursor: pointer;
  font-size: .82rem; font-weight: 600; color: var(--clr-text-muted);
  max-width: 16rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dl-filetab:hover { border-color: var(--clr-brand-main); }
.dl-filetab.is-on {
  border-color: var(--clr-brand-main); color: var(--clr-brand-main);
  box-shadow: inset 0 0 0 1px var(--clr-brand-main);
}
/* A document that failed says so on its own tab, so one failure in a batch is
   not hidden behind a row of successes. */
.dl-filetab.is-failed { color: var(--clr-state-danger, #dc2626); }

/* The whole batch in one archive, offered once every document is finished. */
.dl-bundle {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; padding: .75rem 1rem;
  border: 1px solid var(--clr-brand-main); border-radius: var(--ui-corner-md);
  background: color-mix(in srgb, var(--clr-brand-main) 6%, var(--clr-bg-surface));
  font-size: .88rem; font-weight: 600;
}

/* Asked once, after the work is on screen. Quiet by design — a rating prompt
   that competes with the result is a prompt people close. */
.dl-feedback {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; margin-top: 1.5rem; padding: .7rem 1rem;
  border-top: 1px solid var(--clr-stroke);
  font-size: .86rem; color: var(--clr-text-muted);
}
.dl-feedback-btns { display: flex; gap: .4rem; flex-wrap: wrap; }
.dl-feedback-btns button {
  border: 1px solid var(--clr-stroke); background: var(--clr-bg-surface);
  border-radius: var(--ui-corner-sm); padding: .3rem .8rem; cursor: pointer;
  font-size: .82rem; font-weight: 600; color: var(--clr-text-body);
}
.dl-feedback-btns button:hover {
  border-color: var(--clr-brand-main); color: var(--clr-brand-main);
}

/* ---------------------------------------------------------------- buttons */

/* His previous site loaded Bootstrap, so these were `btn btn-gradient` and
   `btn btn-sm btn-outline-primary`. The new one is Tailwind-only and those
   classes resolve to nothing — a submit button that renders as bare text.
   Owning them here also means the one control people actually press cannot be
   restyled out from under us the next time he changes framework. */

.dl-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  font: inherit; font-weight: 700; font-size: .875rem;
  padding: .625rem 1.25rem;
  border-radius: var(--ui-corner);
  border: 1px solid transparent;
  cursor: pointer;
  transition: background-color var(--ui-speed), border-color var(--ui-speed),
              color var(--ui-speed), transform var(--ui-speed);
}
.dl-btn:focus-visible {
  outline: 2px solid var(--clr-brand-main);
  outline-offset: 2px;
}
.dl-btn:disabled { opacity: .5; cursor: not-allowed; }
.dl-btn:not(:disabled):active { transform: translateY(1px); }

.dl-btn-primary {
  background: var(--clr-action);
  color: #fff;
  /* Lifts it off the card so it is plainly the thing to press, and so it can
     never be mistaken for one of the tinted information panels. */
  box-shadow: 0 1px 2px rgba(15, 23, 42, .12), 0 4px 10px rgba(29, 78, 216, .22);
}
.dl-btn-primary:not(:disabled):hover { background: var(--clr-action-hover); }
.dl-btn-primary:disabled { box-shadow: none; }

.dl-btn-ghost {
  background: var(--clr-bg-surface);
  color: var(--clr-brand-main);
  border-color: var(--clr-stroke);
}
.dl-btn-ghost:not(:disabled):hover {
  background: color-mix(in srgb, var(--clr-brand-main) 6%, var(--clr-bg-surface));
  border-color: var(--clr-brand-main);
}

.dl-btn-sm { padding: .4rem .85rem; font-size: .8125rem; }

/* The page heading. `gradient-heading` was his; it no longer exists. */
.dl-h1 {
  font-size: clamp(1.5rem, 4vw, 2rem);
  font-weight: 900;
  letter-spacing: -.02em;
  color: var(--clr-text-heading);
  margin: 0 0 .5rem;
}

/* The three reassurances under the Word to PDF picker. Was a Bootstrap
   `list-unstyled` with inline-styled ticks; the tick is a pseudo-element now so
   the colour comes from the token like everything else. */
.dl-checks { list-style: none; margin: 0; padding: 0; }
.dl-checks li {
  position: relative;
  padding-left: 1.5rem;
  margin-bottom: .35rem;
  font-size: .8125rem;
  color: var(--clr-text-muted);
}
.dl-checks li:last-child { margin-bottom: 0; }
.dl-checks li::before {
  content: "✓";
  position: absolute; left: 0; top: 0;
  color: var(--clr-state-success);
  font-weight: 700;
}

/* =========================================================== the utilities
 *
 * His previous site loaded Bootstrap, so our markup and our JS string templates
 * leaned on its utilities — `d-flex`, `col-12`, `mb-3`, `btn`, `form-control`.
 * The rebuilt site is Tailwind-only. None of those class names exist any more,
 * and the failure is silent: the page keeps every word of its content and loses
 * every bit of its shape.
 *
 * Rewriting eighteen hundred lines of generated markup to Tailwind class names
 * would have been a large, risky diff across code that is otherwise correct and
 * working. Defining the thirty utilities we actually use is a small one.
 *
 * Everything is scoped under .dl — which the admin also carries, as `dl dla` —
 * so these names cannot leak into his other thirty-three tools even though they
 * collide with Bootstrap's. If he ever loads a CSS framework again, his wins
 * outside our section and ours wins inside it.
 *
 * This list is exactly what is used. It is not a framework and should not grow
 * into one: anything new should be a Tailwind class in the markup instead.
 */

:is(.dl,.dl *).container     { width: 100%; max-width: 64rem; margin-inline: auto; padding-inline: .75rem; }
:is(.dl,.dl *).row           { display: flex; flex-wrap: wrap; margin-inline: -.5rem; }
:is(.dl,.dl *).row > [class*="col-"] { padding-inline: .5rem; width: 100%; }
:is(.dl,.dl *).col-12        { flex: 0 0 100%; }
:is(.dl,.dl *).g-3 > *       { margin-bottom: 1rem; }

@media (min-width: 576px) { :is(.dl,.dl *).col-sm-8  { flex: 0 0 66.6667%; max-width: 66.6667%; } }
@media (min-width: 768px) {
  :is(.dl,.dl *).col-md-6    { flex: 0 0 50%; max-width: 50%; }
  :is(.dl,.dl *).p-md-4      { padding: 1.5rem; }
}
@media (min-width: 992px) {
  :is(.dl,.dl *).col-lg-5    { flex: 0 0 41.6667%; max-width: 41.6667%; }
  :is(.dl,.dl *).col-lg-6    { flex: 0 0 50%; max-width: 50%; }
  :is(.dl,.dl *).py-lg-5     { padding-block: 3rem; }
}

:is(.dl,.dl *).d-flex        { display: flex; }
:is(.dl,.dl *).d-block       { display: block; }
:is(.dl,.dl *).d-none        { display: none; }
:is(.dl,.dl *).flex-wrap     { flex-wrap: wrap; }
:is(.dl,.dl *).align-items-center      { align-items: center; }
:is(.dl,.dl *).align-items-baseline    { align-items: baseline; }
:is(.dl,.dl *).justify-content-between { justify-content: space-between; }
:is(.dl,.dl *).justify-content-center  { justify-content: center; }
:is(.dl,.dl *).h-100         { height: 100%; }
:is(.dl,.dl *).gap-2         { gap: .5rem; }
:is(.dl,.dl *).gap-3         { gap: 1rem; }

:is(.dl,.dl *).small         { font-size: .8125rem; }
:is(.dl,.dl *).text-center   { text-align: center; }
:is(.dl,.dl *).fw-bold       { font-weight: 700; }
:is(.dl,.dl *).fw-semibold   { font-weight: 600; }
:is(.dl,.dl *).text-muted    { color: var(--clr-text-muted); }
:is(.dl,.dl *).list-unstyled { list-style: none; padding-left: 0; }
:is(.dl,.dl *).text-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
:is(.dl,.dl *).h5            { font-size: 1.125rem; font-weight: 700; color: var(--clr-text-heading); }
:is(.dl,.dl *).h6            { font-size: .9375rem; font-weight: 700; color: var(--clr-text-heading); }

:is(.dl,.dl *).m-0  { margin: 0; }
:is(.dl,.dl *).mb-0 { margin-bottom: 0; }
:is(.dl,.dl *).mb-1 { margin-bottom: .25rem; }
:is(.dl,.dl *).mb-2 { margin-bottom: .5rem; }
:is(.dl,.dl *).mb-3 { margin-bottom: 1rem; }
:is(.dl,.dl *).mb-4 { margin-bottom: 1.5rem; }
:is(.dl,.dl *).mt-2 { margin-top: .5rem; }
:is(.dl,.dl *).mt-3 { margin-top: 1rem; }
:is(.dl,.dl *).mt-4 { margin-top: 1.5rem; }
:is(.dl,.dl *).my-3 { margin-block: 1rem; }
:is(.dl,.dl *).p-3  { padding: 1rem; }
:is(.dl,.dl *).p-4  { padding: 1.5rem; }
:is(.dl,.dl *).py-4 { padding-block: 1.5rem; }
:is(.dl,.dl *).ps-3 { padding-left: 1rem; }

/* Bootstrap's button names, our button looks. Keeping the class names means the
   JS templates that emit them did not have to change. */
:is(.dl,.dl *).btn                { display: inline-flex; align-items: center; justify-content: center;
                          gap: .5rem; font: inherit; font-weight: 700; font-size: .875rem;
                          padding: .625rem 1.25rem; border-radius: var(--ui-corner);
                          border: 1px solid transparent; cursor: pointer;
                          transition: background-color var(--ui-speed),
                                      border-color var(--ui-speed), color var(--ui-speed); }
:is(.dl,.dl *).btn:focus-visible  { outline: 2px solid var(--clr-brand-main); outline-offset: 2px; }
:is(.dl,.dl *).btn:disabled       { opacity: .5; cursor: not-allowed; }
:is(.dl,.dl *).btn-sm             { padding: .4rem .85rem; font-size: .8125rem; }
:is(.dl,.dl *).btn-gradient {
  background: var(--clr-action); color: #fff;
  box-shadow: 0 1px 2px rgba(15, 23, 42, .12), 0 4px 10px rgba(29, 78, 216, .22);
}
:is(.dl,.dl *).btn-gradient:not(:disabled):hover { background: var(--clr-action-hover); }
:is(.dl,.dl *).btn-gradient:disabled { box-shadow: none; }
:is(.dl,.dl *).btn-outline-primary{ background: var(--clr-bg-surface); color: var(--clr-brand-main);
                          border-color: var(--clr-stroke); }
:is(.dl,.dl *).btn-outline-primary:not(:disabled):hover {
                          background: color-mix(in srgb, var(--clr-brand-main) 6%, var(--clr-bg-surface));
                          border-color: var(--clr-brand-main); }

:is(.dl,.dl *).form-label     { display: block; font-size: .8125rem; font-weight: 600;
                      color: var(--clr-text-heading); margin-bottom: .35rem; }
:is(.dl,.dl *).form-control   { display: block; width: 100%; font: inherit; font-size: .875rem;
                      padding: .55rem .8rem; color: var(--clr-text-body);
                      background: var(--clr-bg-surface);
                      border: 1px solid var(--clr-stroke);
                      border-radius: var(--ui-corner-sm); }
:is(.dl,.dl *).form-control:focus { outline: none; border-color: var(--clr-brand-main);
                      box-shadow: 0 0 0 3px color-mix(in srgb, var(--clr-brand-main) 18%, transparent); }
:is(.dl,.dl *).form-control-sm{ padding: .4rem .6rem; font-size: .8125rem; }

/* --- the rest of the set, found by listing every class our markup and JS
       emit and subtracting what Tailwind's CDN already provides --- */

:is(.dl,.dl *).col-6         { flex: 0 0 50%; max-width: 50%; }
:is(.dl,.dl *).g-0 > *       { margin-bottom: 0; }
:is(.dl,.dl *).g-2 > *       { margin-bottom: .5rem; }
:is(.dl,.dl *).g-4 > *       { margin-bottom: 1.5rem; }
:is(.dl,.dl *).row.g-0       { margin-inline: 0; }
:is(.dl,.dl *).row.g-0 > [class*="col-"] { padding-inline: 0; }

@media (min-width: 576px) { :is(.dl,.dl *).col-sm-6 { flex: 0 0 50%;      max-width: 50%; } }
@media (min-width: 768px) {
  :is(.dl,.dl *).col-md-5    { flex: 0 0 41.6667%; max-width: 41.6667%; }
  :is(.dl,.dl *).col-md-7    { flex: 0 0 58.3333%; max-width: 58.3333%; }
}
@media (min-width: 992px) {
  :is(.dl,.dl *).col-lg-3    { flex: 0 0 25%;      max-width: 25%; }
  :is(.dl,.dl *).col-lg-9    { flex: 0 0 75%;      max-width: 75%; }
  :is(.dl,.dl *).col-lg-10   { flex: 0 0 83.3333%; max-width: 83.3333%; }
}
@media (min-width: 1200px) { :is(.dl,.dl *).col-xl-9 { flex: 0 0 75%; max-width: 75%; } }

:is(.dl,.dl *).align-items-start { align-items: flex-start; }
:is(.dl,.dl *).flex-shrink-0 { flex-shrink: 0; }
:is(.dl,.dl *).w-100         { width: 100%; }
:is(.dl,.dl *).pb-5          { padding-bottom: 3rem; }
:is(.dl,.dl *).h4            { font-size: 1.375rem; font-weight: 800; color: var(--clr-text-heading); }
:is(.dl,.dl *).text-danger   { color: var(--clr-state-error); }
:is(.dl,.dl *).table-responsive { overflow-x: auto; }

:is(.dl,.dl *).badge {
  display: inline-block; padding: .2rem .55rem;
  font-size: .6875rem; font-weight: 700; line-height: 1.4;
  border-radius: var(--ui-corner-full);
  background: color-mix(in srgb, var(--clr-brand-main) 12%, var(--clr-bg-surface));
  color: var(--clr-brand-accent);
}

:is(.dl,.dl *).form-select {
  display: block; width: 100%; font: inherit; font-size: .875rem;
  padding: .55rem 2rem .55rem .8rem;
  color: var(--clr-text-body); background: var(--clr-bg-surface);
  border: 1px solid var(--clr-stroke); border-radius: var(--ui-corner-sm);
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2364748b'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right .6rem center;
  background-size: .8rem;
}
:is(.dl,.dl *).form-select:focus { outline: none; border-color: var(--clr-brand-main); }
:is(.dl,.dl *).form-select-sm { padding-block: .4rem; font-size: .8125rem; }

/* Screen-reader-only. Real, not display:none — the point is that it IS read. */
:is(.dl,.dl *).visually-hidden {
  position: absolute !important; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* --- Tailwind's preflight, undone where it breaks us ---
 *
 * `cdn.tailwindcss.com` ships Preflight, which sets `img, svg, video { display:
 * block }`. That is a sensible default and it silently broke the dropzone: a
 * block-level SVG ignores the `text-align: center` it sits in, so the upload
 * icon sat hard against the left edge of a centred panel.
 *
 * Scoped to .dl, so his own thirty-three pages keep Preflight's behaviour. */
:is(.dl,.dl *).dl-drop-icon svg,
:is(.dl,.dl *).dl-drop-icon { display: block; margin-inline: auto; width: max-content; }

/* His floating dock is `position: fixed; bottom: 3` and sits above everything.
   Without room reserved for it, the last few lines of any page are read through
   a dark bar. His own pages have the same problem; ours does not need to. */
.dl:last-of-type { padding-bottom: 6.5rem; }
@media (min-width: 768px) { .dl:last-of-type { padding-bottom: 8rem; } }

/* A reading mode the signed-in customer's token does not include. Shown and
   explained rather than hidden — somebody told the plan includes it needs to
   see that this token does not, or they just think the page is broken. */
/* Dim the parts that are unavailable — NOT the whole label. The note carries
   the way out of this state, and fading the one actionable thing on the row to
   55% is how a dead end gets made. */
.dl-pick.is-off > span > strong,
.dl-pick.is-off input { opacity: .55; }
.dl-pick.is-off,
.dl-pick.is-off input { cursor: not-allowed; }
.dl-pick.is-off .dl-pick-note { opacity: 1; }
/* The label is not clickable, but the link inside it must be. */
.dl-pick.is-off a { cursor: pointer; }

/* ====================================================== plans & sign-in */

.dl-planrow {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}

.dl-plan {
  display: flex; flex-direction: column;
  background: var(--clr-bg-surface);
  border: 1px solid var(--clr-stroke);
  border-radius: var(--ui-corner);
  box-shadow: var(--elevation-low);
  padding: 1.25rem;
}
/* The chosen plan, marked on the card as well as in the form's wording. A
   selection that only shows up as a sentence further down the page reads as
   nothing having happened. */
.dl-plan.is-on {
  border-color: var(--clr-brand-main);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--clr-brand-main) 15%, transparent);
}
.dl-plan-name  { font-size: .75rem; font-weight: 700; letter-spacing: .08em;
                 text-transform: uppercase; color: var(--clr-brand-main); }
.dl-plan-price { font-size: 1.75rem; font-weight: 900; letter-spacing: -.02em;
                 color: var(--clr-text-heading); margin: .25rem 0 .75rem; }
.dl-plan-points { list-style: none; margin: 0 0 .75rem; padding: 0;
                  font-size: .8125rem; color: var(--clr-text-body); }
.dl-plan-points li { padding: .3rem 0; border-bottom: 1px solid var(--clr-stroke); }
.dl-plan-points li:last-child { border-bottom: 0; }
.dl-plan-desc  { font-size: .78rem; color: var(--clr-text-muted);
                 margin: 0 0 1rem; flex: 1; }

.dl-rule { border: 0; border-top: 1px solid var(--clr-stroke); margin: 1.5rem 0 1rem; }

.dl-contacts { display: flex; flex-wrap: wrap; gap: .5rem; }
.dl-contact {
  display: inline-flex; align-items: center; gap: .4rem;
  font-size: .8125rem; font-weight: 600; text-decoration: none;
  padding: .45rem .8rem;
  border: 1px solid var(--clr-stroke); border-radius: var(--ui-corner-full);
  color: var(--clr-text-body); background: var(--clr-bg-surface);
}
.dl-contact:hover { border-color: var(--clr-brand-main); color: var(--clr-brand-main); }

/* Two columns on anything but a phone, so the label and its value stay on one
   line and the eye can run down the values. */
.dl-summary { display: grid; grid-template-columns: 1fr auto; gap: .55rem 1rem;
              margin: 0; font-size: .875rem; }
.dl-summary dt { color: var(--clr-text-muted); }
.dl-summary dd { margin: 0; font-weight: 700; color: var(--clr-text-heading);
                 text-align: right; }
.dl-summary dd.is-low { color: var(--clr-state-alert); }

/* The page title block.
 *
 * His <body> carries a dotted radial gradient, and every other block on the
 * site sits on a white card over it. A bare heading floating on the dots was
 * the one thing on our pages that read as unfinished rather than designed, so
 * it gets the same surface as everything else. */
:is(.dl,.dl *).dl-head {
  background: var(--clr-bg-surface);
  border: 1px solid var(--clr-stroke);
  border-radius: var(--ui-corner);
  box-shadow: var(--elevation-low);
  padding: 1.5rem 1.25rem;
}
@media (min-width: 768px) {
  :is(.dl,.dl *).dl-head { padding: 2rem 1.5rem; }
}
/* The allowance pill sits under the subtitle inside the panel now, so it needs
   its own breathing room rather than the margin it had against the page. */
:is(.dl,.dl *).dl-head .dl-quota { margin-top: .75rem; }


/* ============================================== links inside the panels ==
 *
 * `Contact us`, `Sign in`, `See plans`, `top up` — each of these is the only
 * way out of the state its panel is describing, and each was rendering as
 * ordinary sentence text. The reader has to *find* the exit before they can
 * take it, and on the greyed-out Intelligent row it was invisible.
 *
 * A pill rather than an underline: these sit inside tinted panels where an
 * underlined word still reads as emphasis, and the panels are the one place on
 * our pages where a link carries the whole point.
 *
 * Deliberately smaller and lighter than .dl-btn — this is the secondary way
 * forward, not the primary action, and two things competing to be the button
 * is worse than one that is slightly quiet.
 */
:is(.dl,.dl *).dl-note a,
:is(.dl,.dl *).dl-pick-note a,
:is(.dl,.dl *).dl-quota a {
  display: inline-flex; align-items: center;
  margin-inline: .15rem;
  padding: .15rem .55rem;
  font-weight: 700;
  font-size: .95em;          /* relative: these panels vary in size */
  line-height: 1.5;
  white-space: nowrap;
  text-decoration: none;
  color: var(--clr-action);
  background: var(--clr-bg-surface);
  border: 1px solid color-mix(in srgb, var(--clr-action) 45%, var(--clr-bg-surface));
  border-radius: var(--ui-corner-full);
  transition: background-color var(--ui-speed), color var(--ui-speed),
              border-color var(--ui-speed);
}
:is(.dl,.dl *).dl-note a:hover,
:is(.dl,.dl *).dl-pick-note a:hover,
:is(.dl,.dl *).dl-quota a:hover {
  background: var(--clr-action);
  border-color: var(--clr-action);
  color: #fff;
}
:is(.dl,.dl *).dl-note a:focus-visible,
:is(.dl,.dl *).dl-pick-note a:focus-visible,
:is(.dl,.dl *).dl-quota a:focus-visible {
  outline: 2px solid var(--clr-action);
  outline-offset: 2px;
}
