/*! dp.css — OmniLetter custom utility layer. Pure hand-written utility classes
   (pure hand-written CSS). Complements BassCSS (layout/spacing) + daisyUI (components).
   Prefix "dp-" avoids colliding with any daisyUI/BassCSS class. */

/* Layout helpers */
.dp-min-h-screen { min-height: 100vh; }
.dp-max-w-md { max-width: 28rem; }
.dp-max-w-4xl { max-width: 56rem; }
.dp-container { max-width: 64rem; margin-inline: auto; }
.dp-sticky { position: sticky; top: 0; }
.dp-z-30 { z-index: 30; }
.dp-shrink-0 { flex-shrink: 0; }
.dp-min-w-0 { min-width: 0; }
.dp-inline { display: inline; }
.dp-cursor-pointer { cursor: pointer; }

/* Label-wraps-hidden-input pattern: a real <button> swallows the file-picker click, so the file
   input itself is hidden via CSS (never type="hidden" — that also drops it from form submission
   in some browsers), and a visible <label for="..."> triggers it instead. */
.dp-visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }

/* Grid helpers */
.dp-grid { display: grid; }
.dp-grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.dp-grid-cols-sidebar { grid-template-columns: 12rem 1fr; }
.dp-gap2 { gap: 0.5rem; }
.dp-gap4 { gap: 1rem; }
.dp-space-y3 > * + * { margin-top: 0.75rem; }

/* Sizing */
.dp-h-8 { height: 2rem; }
.dp-h-10 { height: 2.5rem; }
.dp-w-8 { width: 2rem; }
.dp-w-10 { width: 2.5rem; }

/* Typography / text helpers */
.dp-extrabold { font-weight: 800; }
.dp-normal-case { text-transform: none; }
.dp-no-underline { text-decoration: none; }
a.dp-no-underline:hover, a.dp-no-underline:focus { text-decoration: none; }
.dp-recovery-code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.dp-muted { color: color-mix(in oklab, var(--color-base-content) 70%, transparent); }
.dp-list-decimal { list-style-type: decimal; }
.dp-list-inside { list-style-position: inside; }

/* BassCSS does not ship color/opacity utilities; keep warm text on neutral surfaces */

/* daisyUI form helpers (the standalone daisyUI build ships these via its plugin; here we define the small subset used so forms lay out and bordered inputs render). */
.form-control { display: flex; flex-direction: column; gap: 0.25rem; }
.form-horizontal { display: flex; flex-direction: row; gap: 0.5rem; align-items: center; }
.input-bordered { border-color: color-mix(in oklab, var(--color-base-300) 100%, transparent); }
.label-text { color: var(--color-base-content); }

/* App shell (dashboard + top bar) spacing/text utilities. */
.dp-gap3 { gap: 0.75rem; }
.dp-text-sm { font-size: 0.875rem; }
.dp-muted-70 { color: color-mix(in oklab, var(--color-base-content) 70%, transparent); }

/* Basic pill badge container; the brand color variants (.badge-brand*) live in app-shell.css. */
.dp-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.25rem 0.75rem;
    border-radius: 999px;
    font-size: 0.8rem;
    font-weight: 600;
    white-space: nowrap;
}


/* Settings page: additional dp-* layout/form utilities (Tailwind-free). */
.dp-w-full { width: 100%; }
.dp-max-w-xs { max-width: 20rem; }
/* Fixed (not max-) width — for side-by-side <select>s that must line up regardless of how long
   their own option text is (e.g. Packets' Add Template / Add Document selects), where a
   max-width alone still lets each shrink to its own content. */
.dp-w-xs { width: 20rem; }
.dp-shadow { box-shadow: 0 10px 30px -12px rgba(90, 60, 20, 0.25); }
.dp-form-field { margin-bottom: 1rem; }
.dp-settings-list { display: flex; flex-direction: column; gap: 1.25rem; }

/* Recipients list: full-width plain HTML table replacing the Syncfusion SfGrid. */.w100 { width: 100%; }
.dp-table { width: 100%; border-collapse: collapse; background: var(--color-base-100, #fffdf7); }
.dp-table thead th {
    padding: 0.6rem 0.75rem;
    border-bottom: 2px solid var(--color-base-300, #e8dfce);
    text-align: left;
    font-weight: 700;
    color: var(--color-base-content, inherit);
    white-space: nowrap;
}
.dp-table tbody td {
    padding: 0.6rem 0.75rem;
    border-bottom: 1px solid var(--color-base-200, #efebdf);
    vertical-align: top;
}
.dp-table tbody th {
    text-align: left;   /* browsers center <th> by default; label-column tables (
                          e.g. Data tab Core Fields) must match the thead's left align */
}
.dp-table tbody tr:last-child td { border-bottom: none; }
.dp-table tbody tr:hover td { background: var(--color-base-200, #f4efe3); }
.dp-table .badge { white-space: nowrap; } /* status pills (e.g. "Completed with Errors") stay on one line */
.dp-table thead a:hover, .dp-table thead a:focus { text-decoration: underline; }
/* Plain text links in a data cell (e.g. run detail's Recipient name) — colored + underlined so
   they read as clickable, unlike the icon/pill buttons in the Actions column. */
.dp-table tbody td a:not(.dp-icon-btn):not(.btn) {
    color: var(--color-primary, #9a5a2f);
    text-decoration: underline;
}
.dp-table tbody td a:not(.dp-icon-btn):not(.btn):hover,
.dp-table tbody td a:not(.dp-icon-btn):not(.btn):focus {
    text-decoration: none;
}
/* ---------- Auto-fit (shrink-to-content) columns ----------
   width:1px is the standard "as narrow as the content allows" hint for auto table layout: a
   column can never render narrower than its widest cell, so the browser resolves the 1px up to
   exactly the content width and hands the leftover width to the free-text columns (name,
   company, description) instead of spreading it evenly across every column. Always paired with
   nowrap — without it a date, a pill or a button group simply buys the width back by wrapping
   onto a second line. Ported from BalanceForecasting's .bf-shrink-col.

   Put .dp-col-fit on BOTH the <th> and every matching <td> of any column whose content has a
   fixed shape (dates, counts, enums, statuses, booleans, pills); leave the user-typed columns
   (names, company, filenames, addresses, error text) alone so they absorb the slack. Qualified
   under .dp-table so the <th> case still wins over .dp-table thead th. */
.dp-table .dp-col-fit { width: 1px; white-space: nowrap; }

/* Actions column: the same auto-fit, right-aligned. It used to be pinned at 10rem (and 18rem for
   the wider grids), which had to be re-guessed every time a grid gained or lost a button — and
   was already too narrow for the Recipients grid's four btn-sm buttons, which then wrapped onto
   a second line. Sizing to content means a grid's button count can change freely. */
.dp-table .dp-table-actions {
    width: 1px;
    white-space: nowrap;
    text-align: right;
}
.dp-table .dp-table-actions a { vertical-align: middle; }

/* Generic right-aligned column (e.g. the Document Answers table's Confidence %) — was referenced
   on a <th>/<td> pair with no matching rule at all until now (a bare .dp-text-right selector would
   still lose the <th> case to .dp-table thead th's higher-specificity text-align: left). */
.dp-table .dp-text-right { text-align: right; }

.dp-table-sep { margin: 0 0.35rem; color: var(--color-base-300, #e8dfce); }

/* ---------- Responsive table scrolling (mobile: many-column tables) ----------
   Every .dp-table is wrapped in a .dp-table-wrap div. Without this, a table wider than the
   viewport used to overflow the whole page sideways (the page itself scrolled horizontally);
   now only this bounded region scrolls, matching the standard "table-responsive" pattern
   (e.g. Bootstrap's .table-responsive) — the page shell/toolbar stay put while the table pans
   under the user's thumb. */
.dp-table-wrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}
/* Let the table grow to its natural content width instead of being squeezed to 100% (which
   would cram every column and defeat the point of scrolling) — min-width keeps it filling the
   wrapper on desktop, where the content is normally narrower than the viewport anyway. No column
   is pinned: on a table with one very wide column (recipients' Custom Data, etc.), pinning it
   would eat most of the viewport and leave the scrollable remainder unusable, so the whole table
   scrolls as one unit instead. */
.dp-table-wrap .dp-table { width: max-content; min-width: 100%; }

/* ---------- Full-width table page (Recipients list) ---------- */
.recipients-page { width: 100%; }

/* ---------- Table icon action buttons (ports Blazor's SfButton IconCss e-eye/e-send/e-edit) ---------- */
.dp-table-icons { display: flex; align-items: center; gap: 0.35rem; }
.dp-icon-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 2rem; height: 2rem; border-radius: 0.4rem;
    color: var(--color-primary, #9a5a2f); background: transparent; text-decoration: none;
    transition: background 0.15s ease, color 0.15s ease;
}
.dp-icon-btn:hover, .dp-icon-btn:focus { background: var(--color-base-200, #f4efe3); color: var(--color-primary, #9a5a2f); }
.dp-icon-btn .ti { font-size: 1.15rem; }

/* ---------- Recipient Data tab tree (ports Blazor's SfTreeView for RecipientDataTab) ---------- */
.dp-tree { list-style: none; margin: 0; padding-left: 0; }
.dp-tree ul { list-style: none; margin: 0; padding-left: 1.25rem; border-left: 1px solid var(--color-base-300, #e8dfce); }
.dp-tree li { margin: 0; }
.dp-tree summary {
    cursor: pointer;
    padding: 0.3rem 0;
    font-weight: 600;
    color: var(--color-base-content, inherit);
    user-select: none;
    width: fit-content;
    border-radius: 0.25rem;
}
.dp-tree summary:hover { color: var(--color-primary, #9a5a2f); }
.dp-tree .dp-tree-leaf { padding: 0.3rem 0; color: var(--color-base-content, inherit); }

/* ---------- Pagination ---------- */
.dp-pagination { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 0.75rem; }
.dp-pagination-buttons { display: flex; align-items: center; gap: 0.3rem; }
.dp-pagination-btn {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 2.25rem; height: 2.25rem; padding: 0 0.5rem;
    border: 1px solid var(--color-base-300, #e8dfce); border-radius: 0.4rem;
    background: var(--color-base-100, #fffdf7); color: var(--color-base-content, inherit);
    font-size: 0.9rem; text-decoration: none;
}
.dp-pagination-btn:hover { background: var(--color-base-200, #f4efe3); border-color: var(--color-primary, #9a5a2f); }
.dp-pagination-btn .ti { font-size: 1rem; }
.dp-pagination-active {
    background: var(--color-primary, #9a5a2f); border-color: var(--color-primary, #9a5a2f);
    color: #fff; font-weight: 700; cursor: default;
}
.dp-pagination-disabled {
    background: var(--color-base-200, #f4efe3); color: var(--color-base-content, #888);
    border-color: var(--color-base-300, #e8dfce); cursor: default; pointer-events: none; opacity: 0.6;
}
/* Campaign edit: each form section is a card-like panel (ports the Blazor .panel chunk). */
.panel {
    background: var(--color-base-100, #fffdf7);
    border: 1px solid var(--color-base-300, #e8dfce);
    border-radius: 0.75rem;
    box-shadow: 0 10px 30px -12px rgba(90, 60, 20, 0.18);
    padding: 1.25rem 1.5rem;
    margin-bottom: 1.25rem;
}
.panel h2 { margin-top: 0; margin-bottom: 1rem; font-weight: 700; }

/* Cron schedule editor (port of the Blazor CronScheduleEditor) on the campaign edit page. */
.cron-editor {
    padding: 0.75rem 1rem;
    border: 1px solid var(--color-base-300, #e8dfce);
    border-radius: 0.5rem;
}
.cron-summary {
    padding-top: 0.5rem;
    border-top: 1px solid var(--color-base-300, #e8dfce);
    font-size: 0.875rem;
}
.cron-summary-cron code {
    background-color: var(--color-base-200, #f4f0e6);
    padding: 0.125rem 0.375rem;
    border-radius: 3px;
    font-family: monospace;
    font-size: 0.9em;
}
.cron-summary-next ul { margin: 0.25rem 0 0 0; padding-left: 1.25rem; }

/* Recipient-preview dialog: constrain the modal-box width (960px, no Tailwind w-[..]) and make
   the results body scrollable so the dialog never outgrows the viewport. The header is a flex
   row (title + close) so the close button is always reachable; the modal-backdrop form closes the
   dialog on outside click. */
.dp-preview-box { width: min(960px, 94vw); max-width: 94vw; }
.dp-preview-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; }
.dp-preview-body { max-height: 70vh; overflow-y: auto; }

/* Recipient Browse dialog: wider than the standard dp-preview-box — its table has 8 columns
   (First/Last/Middle/Company/Email/City/State/Actions) and gets cramped at 960px. */
.dp-browse-box { width: min(1200px, 96vw); max-width: 96vw; }

/* Confirmation dialogs (delete, remove-row, etc.) interpolate a user-supplied name/filename
   into their body text. Two things were needed to stop a long value from forcing a horizontal
   scrollbar: (1) a dialog nested inside a `.dp-table-actions` cell (e.g. a table row's Delete
   button) inherits that cell's own `white-space: nowrap` — set for its own icon-button content —
   right down through the dialog's native top-layer promotion, since `white-space` is an
   inherited CSS property, not scoped by the top layer; reset it back to normal at the dialog
   itself. (2) even with wrapping re-enabled, a value with NO spaces at all (e.g. a hashed
   filename) still needs overflow-wrap to break mid-word. */
dialog.modal { white-space: normal; }
.modal-box p { overflow-wrap: anywhere; }

/* Campaign run detail: the summary card above the recipient-status table (ports the Blazor
   CampaignRunDetail.razor.css .summary-grid). */
.run-summary .summary-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    gap: 1.25rem;
    text-align: center;
}
.summary-item { display: flex; flex-direction: column; align-items: center; gap: 0.25rem; }
.summary-label { font-size: 0.8rem; font-weight: 700; color: var(--color-base-content, #555); text-transform: uppercase; }
.summary-value { font-size: 1rem; }
.summary-value.error { color: #d32f2f; font-size: 0.875rem; }

/* Engagement badges in the run-recipient table (Opened/Clicked/Viewed/Completed green, Spam red,
   Unsubscribed amber) — ports the Blazor .engagement-pill styling. */
.engagement-pill {
    font-size: 11px;
    font-weight: 700;
    padding: 3px 8px;
    border-radius: 999px;
    white-space: nowrap;
    margin-right: 4px;
    display: inline-block;
}
.engagement-pill.opened,
.engagement-pill.clicked,
.engagement-pill.viewed,
.engagement-pill.completed { background: rgba(47,110,74,.12); color: #2b6e49; }
.engagement-pill.spam { background: rgba(180,40,40,.12); color: #c0392b; }
.engagement-pill.unsubscribed { background: rgba(180,122,20,.14); color: #9a6a12; }

/* Campaign runs list / run detail: truncated error message cell (full text on hover via
   title), never wraps — keeps the row a single line no matter how narrow the column. */
.run-error {
    color: #c0392b;
    display: inline-block;
    max-width: 260px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: bottom;
}
.dp-preview-body .dp-table { margin-bottom: 0; }

/* Questionnaire Run: live recipient search results dropdown (a plain list, not a popover —
   it sits directly under the search input and pushes the rest of the panel down). */
.dp-search-results {
    max-width: 28rem;
    border: 1px solid var(--color-base-300, #e8dfce);
    border-radius: 0.5rem;
    overflow: hidden;
}
.dp-search-result-item {
    display: block;
    padding: 0.5rem 0.75rem;
    text-decoration: none;
    color: var(--color-base-content, inherit);
    border-bottom: 1px solid var(--color-base-200, #efebdf);
}
.dp-search-result-item:last-child { border-bottom: none; }
.dp-search-result-item:hover, .dp-search-result-item:focus {
    background: var(--color-base-200, #f4efe3);
}

/* _BlankLayout: chrome-free shell for the anonymous /q/{token} and /d/{token} recipient pages —
   no sidebar/topbar, just a centered responsive column (mirrors Blazor's BlankLayout.razor.css). */
.dp-blank-layout {
    min-height: 100vh;
    background: var(--color-base-200, #f5f6f8);
    padding: 1.5rem 1rem 3rem;
    display: flex;
    justify-content: center;
    align-items: flex-start;
    overflow-x: hidden;
}
.dp-blank-content { width: 100%; max-width: 1000px; min-width: 0; }
@media (max-width: 600px) {
    .dp-blank-layout { padding: 0.75rem 0.5rem 2rem; }
}

/* Recipient-facing link pages: message states (not found/expired/completed) and the interview
   layout (questionnaire form beside/above the PDF preview + signature pads). */
.dp-link-message {
    background: var(--color-base-100, #fffdf7);
    border: 1px solid var(--color-base-300, #e8dfce);
    border-radius: 0.75rem;
    padding: 2rem;
    text-align: center;
}
.dp-interview-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 1.5rem;
    align-items: start;
}
@media (max-width: 800px) {
    .dp-interview-layout { grid-template-columns: 1fr; }
}
.dp-pdf-preview-frame {
    width: 100%;
    height: 70vh;
    border: 1px solid var(--color-base-300, #e8dfce);
    border-radius: 0.5rem;
}
.dp-signature-pad-wrap {
    border: 1px solid var(--color-base-300, #e8dfce);
    border-radius: 0.5rem;
    padding: 0.5rem;
    background: var(--color-base-100, #fffdf7);
    touch-action: none;
}
.dp-signature-pad-wrap canvas {
    width: 100%;
    height: 150px;
    display: block;
    background: #fff;
    border: 1px dashed var(--color-base-300, #e8dfce);
    border-radius: 0.25rem;
    cursor: crosshair;
}
.dp-signature-status { font-size: 0.875rem; margin-top: 0.35rem; }
.dp-signature-status.signed { color: #2b6e49; }
.table-answer-view {
    width: 100%;
    border-collapse: collapse;
    margin-top: 0.5rem;
}
.table-answer-view th, .table-answer-view td {
    border: 1px solid var(--color-base-300, #e8dfce);
    padding: 0.4rem 0.6rem;
    text-align: left;
}

/* Campaign edit email-notification lists (Email on Failure/Success/Link Completion). Force soft
   wrap so pasted address lists never produce a horizontal scrollbar inside the textarea:
   overflow-wrap:anywhere breaks unbroken tokens, overflow-x:hidden suppresses any residual
   internal scrollbar. */
.dp-email-list { overflow-x: hidden; overflow-wrap: anywhere; word-break: break-word; }

/* Recipient-facing /d/{Token} document link: the EJ2 PDF Viewer host. The viewer fits a single
   PDF page to the column width (US-letter 612:792 ≈ 1.29), so the host height is derived from its
   own width via a container-query unit (container-type on the wrapper): pageHeight + toolbar(~46px).
   This is deterministic from CSS — no "too tall then shrinks on render" pop — and tall enough that
   a single fitted page never needs an internal vertical scrollbar. Falls back to a vh height where
   container-query units aren't supported. */
.dp-pdf-viewer-frame { container-type: inline-size; }
.dp-pdf-viewer-host {
    width: 100%;
    /* !important: SfPdfViewer2 writes height:100%;min-height:500px onto the host element inline,
       which would otherwise override the width-derived height below. */
    height: calc(129cqw + 46px) !important;
    border: 1px solid var(--color-base-300, #e8dfce);
    border-radius: 0.5rem;
    overflow: hidden;
}
@supports not (height: 1cqw) {
    .dp-pdf-viewer-host { height: 82vh !important; }
}

/* Templates Fields dialog (MergeExpressionBuilder). Needs room for the segment list + the Insert
   Field picker + the recipient preview row all open at once — the daisyUI default (max-width:32rem)
   is cramped for that. */
.dp-modal-wide { width: 50vw; max-width: 50vw; }

/* Insert Field picker: field lists can run into the hundreds (every observed CustomData key plus
   every questionnaire question), so the list scrolls internally instead of growing the dialog to
   match. Groups (Core/Custom/Questionnaire/System) are hidden by templates-field-dialog.js's
   applyFilter when a search term leaves none of their items visible. */
.dp-field-picker-list { max-height: 16rem; overflow-y: auto; }
.dp-field-picker-group + .dp-field-picker-group { margin-top: 0.5rem; }
.dp-field-picker-group-label { padding: 0.125rem 0.5rem; }

/* Visual-mode segment row: the literal-text input / field-token chip stays a fixed, left-aligned
   width regardless of its content length, so the Move/Edit/Remove button cluster holds a stable
   right-aligned position down the whole list instead of drifting with every segment's text length. */
.dp-segment-row { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.dp-segment-value { flex: 0 0 16rem; width: 16rem; min-width: 0; }
.dp-segment-actions { display: flex; align-items: center; gap: 0.25rem; flex: 0 0 auto; }

.token-chip {
    display: block;
    background: var(--color-base-200);
    border: 1px solid var(--color-base-300);
    border-radius: var(--radius-field, 0.25rem);
    padding: 0.375rem 0.6rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Templates HTML editor RTE: {{...}} tokens are inserted as non-editable chips
   (RteMergeHelper.Chip/BuildChip — Syncfusion's .e-mention-chip shape, contenteditable="false",
   which is what makes Backspace delete the whole token as one unit) carrying an
   .ol-token--{kind} category class (RteTokenChipifier.Classify) that drives the color. Ported
   verbatim from the Blazor original's TemplateDetail.razor.css (::deep dropped — that's Blazor
   CSS-isolation syntax, meaningless here; this file is already global and the RTE has no iframe/
   shadow boundary to cross). */
.e-mention-chip,
.ol-token {
    border-radius: 4px;
    padding: 0.05em 0.3em;
    margin: 0 0.1em;
    font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
    font-size: 0.92em;
    border: 1px solid transparent;
}

/* Merge field {{FirstName}} — the default chip color; toolbar-inserted merge fields
   carry only .e-mention-chip (no category class, including {{qr:...}} tokens — the Blazor
   original never defined an .ol-token--qr either), so this is also their color. */
.e-mention-chip,
.ol-token--field {
    background: #eff6ff;
    color: #2563eb;
    border-color: #c7dbff;
}

/* Dotted path {{Account.Balance}} / loop row field {{Car.Year}} */
.ol-token--path {
    background: #f0fdfa;
    color: #0d9488;
    border-color: #b9ebe4;
}

/* Reserved content slot {{content}} */
.ol-token--slot {
    background: #fffbeb;
    color: #d97706;
    border-color: #f6df9e;
}

/* Document link {{packet:7}} / {{template:3}} */
.ol-token--packet,
.ol-token--template {
    background: #f0fdf4;
    color: #16a34a;
    border-color: #bce8c6;
}

/* Questionnaire link {{questionnaire:3}} */
.ol-token--questionnaire {
    background: #faf5ff;
    color: #9333ea;
    border-color: #e0cbfa;
}

/* Condition tags {{if …}} / {{else if …}} / {{else}} / {{/if}} */
.ol-token--if {
    background: #fff1f2;
    color: #e11d48;
    border-color: #fcc7d3;
}

/* Loop tags {{foreach … as …}} / {{/foreach}} */
.ol-token--loop {
    background: #eef2ff;
    color: #4f46e5;
    border-color: #c9d4ff;
}

/* Unrecognized or empty {{…}} — dashed so a typo or invalid tag stands out */
.ol-token--empty,
.ol-token--unknown {
    background: #f8fafc;
    color: #64748b;
    border-color: #cbd5e1;
    border-style: dashed;
}

/* Search/filter button that swaps between a static icon (at rest) and a .loading spinner (while
   its own htmx request is in flight) — see Campaigns/RunDetail.cshtml etc. Only one of the two is
   ever laid out; htmx's own default .htmx-indicator{opacity:0} still leaves the element's box in
   flow (just invisible), which reserved dead space to the icon's left, so this overrides to
   display:none/inline-block instead. htmx adds .htmx-request directly to the resolved indicator
   element itself (not to a shared ancestor), so the icon rule keys off the spinner's own state via
   an adjacent-sibling selector — the icon element MUST immediately follow the spinner span in the
   markup for this to match. */
.loading.htmx-indicator {
    display: none;
}
.loading.htmx-indicator.htmx-request {
    display: inline-block;
}
.htmx-indicator.htmx-request + .dp-btn-icon-idle {
    display: none;
}

/* Survey Results page (Pages/Questionnaires/Results.cshtml + _AnswerSummary.cshtml). */
.dp-results-stats { width: 100%; overflow-x: auto; }
.dp-results-bars { display: flex; flex-direction: column; gap: 0.5rem; }
.dp-results-bar-row { display: flex; align-items: center; gap: 0.75rem; }
.dp-results-bar-label { flex: 0 0 14rem; overflow-wrap: anywhere; }
.dp-results-bar { flex: 1 1 auto; min-width: 4rem; }
.dp-results-bar-value { flex: 0 0 6.5rem; text-align: right; white-space: nowrap; }
@media (max-width: 40em) {
    .dp-results-bar-row { flex-wrap: wrap; gap: 0.25rem 0.75rem; }
    .dp-results-bar-label { flex-basis: 100%; }
    /* 2x2 tiles instead of one row that runs off the right edge. */
    .dp-results-stats { grid-auto-flow: row; grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.dp-results-column {
    border-left: 3px solid var(--color-base-300, #e8dfce);
    padding-left: 1rem;
}
.dp-results-text-list { list-style: none; margin: 0; padding: 0; }
.dp-results-text-list li { padding: 0.5rem 0; border-bottom: 1px solid var(--color-base-300, #e8dfce); }
.dp-results-text-list li:last-child { border-bottom: 0; }
.dp-results-text { white-space: pre-wrap; overflow-wrap: anywhere; }
/* SafeMarkdown output (AI answers): restore list markers the CSS reset strips. */
.dp-md-list { list-style: disc; padding-left: 1.5rem; }
.dp-md-ol { list-style: decimal; }
.dp-md-list li { margin-bottom: 0.25rem; }
.dp-ai-answer { overflow-wrap: anywhere; }
.dp-md-rule { border: 0; border-top: 1px solid var(--color-base-300, #e8dfce); margin: 1rem 0; }
