/*!
 * W-Harambee design layer.
 *
 * The component vocabulary every dashboard page is built from: page headers,
 * cards, stat tiles, tables, badges, toolbars and the support dock. It sits on
 * top of the purchased admin theme rather than replacing it — the theme still
 * owns the sidebar and header chrome — and only ever restyles theme classes
 * under `body.wh-ui`, so a page opts in by adding that one class.
 *
 * Colours are the existing brand: #00c206 green, #0e0e23 ink, #eaedf7 canvas.
 * Load after public/css/style.css and after assets/css/wh-ui.css.
 */

/* ============================================================ 1. tokens */

.wh-ui {
    /* Brand. --wh-primary is the identity colour and is used for fills that
       carry no text, borders, icons and rings. White text on it reaches only
       2.4:1, so anything with a label on top uses --wh-primary-strong, the
       same hue darkened to 5.5:1. Change these two together to re-brand. */
    --wh-primary: #00c206;
    --wh-primary-strong: #007a04;
    --wh-primary-darker: #006103;
    --wh-primary-soft: rgba(0, 194, 6, 0.1);
    --wh-primary-softer: rgba(0, 194, 6, 0.05);
    --wh-primary-ring: rgba(0, 194, 6, 0.35);

    --wh-ink-brand: #0e0e23;
    --wh-whatsapp: #128c7e;

    /* Status. Each pairs a text colour that clears 4.5:1 on its own tint. */
    --wh-ok: #0f7b3d;
    --wh-ok-bg: rgba(15, 123, 61, 0.1);
    --wh-warn: #92610a;
    --wh-warn-bg: rgba(240, 147, 43, 0.14);
    --wh-danger: #c02a2a;
    --wh-danger-bg: rgba(192, 42, 42, 0.1);
    --wh-info: #1d5fa8;
    --wh-info-bg: rgba(29, 95, 168, 0.1);
    --wh-neutral: #52606d;
    --wh-neutral-bg: rgba(82, 96, 109, 0.1);

    /* Surfaces and text */
    --wh-canvas: #eaedf7;
    --wh-surface: #ffffff;
    --wh-surface-2: #f7f9fc;
    --wh-surface-3: #eef1f7;
    --wh-fg: #1b2430;
    --wh-fg-soft: #55606e;
    --wh-fg-faint: #7c8794;
    --wh-line: #e6eaf2;
    --wh-line-strong: #d6dbe6;

    --wh-shadow-xs: 0 1px 2px rgba(16, 24, 40, 0.05);
    --wh-shadow-sm: 0 1px 3px rgba(16, 24, 40, 0.08), 0 1px 2px rgba(16, 24, 40, 0.04);
    --wh-shadow-md: 0 4px 12px rgba(16, 24, 40, 0.08), 0 2px 4px rgba(16, 24, 40, 0.04);
    --wh-shadow-lg: 0 12px 32px rgba(16, 24, 40, 0.12);

    --wh-r-xs: 6px;
    --wh-r-sm: 8px;
    --wh-r-md: 12px;
    --wh-r-lg: 16px;
    --wh-r-pill: 999px;

    /* 4px scale. Everything spaced in the layer uses one of these. */
    --wh-s1: 0.25rem;
    --wh-s2: 0.5rem;
    --wh-s3: 0.75rem;
    --wh-s4: 1rem;
    --wh-s5: 1.5rem;
    --wh-s6: 2rem;
    --wh-s7: 3rem;

    /* Type scale, 1.2 ratio off a 14px base to match the theme. */
    --wh-t-xs: 0.75rem;
    --wh-t-sm: 0.8125rem;
    --wh-t-base: 0.875rem;
    --wh-t-md: 1rem;
    --wh-t-lg: 1.125rem;
    --wh-t-xl: 1.375rem;
    --wh-t-2xl: 1.75rem;
    --wh-t-3xl: 2.125rem;

    --wh-dur: 0.18s;
    --wh-ease: cubic-bezier(0.4, 0, 0.2, 1);
}

/* The theme's dark mode is a body class toggled from the header. Only the
   surface tokens move; the brand stays put so the palette reads the same. */
.wh-ui.dark-theme {
    --wh-canvas: #24243e;
    --wh-surface: #0e0e23;
    --wh-surface-2: #1b1b38;
    --wh-surface-3: #2a2a4a;
    --wh-fg: #e6e6f2;
    --wh-fg-soft: #b4b4cd;
    --wh-fg-faint: #9494b0;
    --wh-line: rgba(255, 255, 255, 0.12);
    --wh-line-strong: rgba(255, 255, 255, 0.2);
    --wh-primary-soft: rgba(0, 194, 6, 0.16);
    --wh-primary-softer: rgba(0, 194, 6, 0.08);
    --wh-ok-bg: rgba(38, 197, 105, 0.16);
    --wh-ok: #4ade80;
    --wh-warn: #fbbf5a;
    --wh-danger: #ff8080;
    --wh-info: #7db4f0;
    --wh-neutral: #b4b4cd;
    --wh-neutral-bg: rgba(255, 255, 255, 0.1);
    --wh-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4);
    --wh-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
    --wh-shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.5);
}

/* ============================================================== 2. base */

.wh-ui {
    background: var(--wh-canvas);
    color: var(--wh-fg);
    -webkit-font-smoothing: antialiased;
}

/* Stated here, not inherited: the guest pages load this layer with no
   framework beneath it, and a 100%-wide input with padding needs it. */
.wh-ui,
.wh-ui *,
.wh-ui *::before,
.wh-ui *::after {
    box-sizing: border-box;
}

/* A display value of our own — a flex button, say — outranks the browser's
   rule for [hidden], so hiding one has to be said here to take. */
.wh-ui [hidden] {
    display: none !important;
}

.wh-ui .main-content {
    padding-top: 0;
    padding-bottom: var(--wh-s7);
}

/* The theme ships a 30px inline margin on some pages and none on others. */
.wh-ui .inner-body {
    padding-top: var(--wh-s5);
}

.wh-ui h1,
.wh-ui h2,
.wh-ui h3,
.wh-ui h4,
.wh-ui h5,
.wh-ui h6 {
    color: var(--wh-fg);
    letter-spacing: -0.01em;
}

/* Figures that sit in a column read as noise unless they share a width. */
.wh-num {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

.wh-muted {
    color: var(--wh-fg-soft);
}

.wh-faint {
    color: var(--wh-fg-faint);
}

.wh-eyebrow {
    display: block;
    font-size: var(--wh-t-xs);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--wh-fg-faint);
}

/* ======================================================= 3. page header */

.wh-page-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--wh-s4);
    margin-bottom: var(--wh-s5);
}

.wh-page-head__text {
    min-width: 0;
}

.wh-page-title {
    margin: 0;
    font-size: var(--wh-t-2xl);
    font-weight: 700;
    line-height: 1.2;
    color: var(--wh-fg);
}

.wh-page-sub {
    margin: var(--wh-s2) 0 0;
    max-width: 62ch;
    font-size: var(--wh-t-base);
    line-height: 1.55;
    color: var(--wh-fg-soft);
}

.wh-page-head__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--wh-s2);
}

/* On a phone the actions become the full-width stack they read as anyway,
   rather than two buttons of different widths. */
@media (max-width: 575.98px) {
    .wh-page-head__actions,
    .wh-page-head__actions > * {
        width: 100%;
    }
}

/* Breadcrumb: the theme's version inherits list padding and a blue link. */
.wh-crumbs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--wh-s2);
    margin: 0 0 var(--wh-s2);
    padding: 0;
    list-style: none;
    font-size: var(--wh-t-sm);
    color: var(--wh-fg-faint);
}

.wh-crumbs li {
    display: flex;
    align-items: center;
    gap: var(--wh-s2);
}

.wh-crumbs li + li::before {
    content: "/";
    color: var(--wh-line-strong);
}

.wh-crumbs a {
    color: var(--wh-fg-soft);
    text-decoration: none;
}

.wh-crumbs a:hover {
    color: var(--wh-primary-strong);
    text-decoration: underline;
}

.wh-crumbs [aria-current="page"] {
    color: var(--wh-fg);
    font-weight: 600;
}

/* ============================================================== 4. card */

.wh-card {
    display: flex;
    flex-direction: column;
    height: 100%;
    background: var(--wh-surface);
    border: 1px solid var(--wh-line);
    border-radius: var(--wh-r-lg);
    box-shadow: var(--wh-shadow-sm);
    overflow: hidden;
}

.wh-card__head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--wh-s3);
    padding: var(--wh-s4) var(--wh-s5);
    border-bottom: 1px solid var(--wh-line);
}

.wh-card__title {
    margin: 0;
    font-size: var(--wh-t-md);
    font-weight: 600;
    line-height: 1.35;
}

.wh-card__desc {
    margin: var(--wh-s1) 0 0;
    font-size: var(--wh-t-sm);
    color: var(--wh-fg-soft);
}

.wh-card__body {
    flex: 1 1 auto;
    padding: var(--wh-s5);
}

.wh-card__body--tight {
    padding: var(--wh-s4);
}

.wh-card__body--flush {
    padding: 0;
}

.wh-card__foot {
    padding: var(--wh-s3) var(--wh-s5);
    border-top: 1px solid var(--wh-line);
    background: var(--wh-surface-2);
    font-size: var(--wh-t-sm);
}

/* The one card on a screen that carries the primary number or action. */
/* Opts a card out of stretching to its neighbour's height, for the short one
   in a two-column row where the empty space would otherwise read as a bug. */
.wh-card--fit {
    height: auto;
}

.wh-card--feature {
    border-color: rgba(0, 194, 6, 0.35);
    background:
        linear-gradient(180deg, var(--wh-primary-softer), transparent 55%),
        var(--wh-surface);
}

/* ========================================================== 5. stat tile */

.wh-stat {
    display: flex;
    flex-direction: column;
    gap: var(--wh-s2);
    height: 100%;
    padding: var(--wh-s5);
    background: var(--wh-surface);
    border: 1px solid var(--wh-line);
    border-radius: var(--wh-r-lg);
    box-shadow: var(--wh-shadow-sm);
    text-decoration: none;
    color: inherit;
    transition: box-shadow var(--wh-dur) var(--wh-ease),
        transform var(--wh-dur) var(--wh-ease),
        border-color var(--wh-dur) var(--wh-ease);
}

.wh-stat__top {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--wh-s3);
}

.wh-stat__label {
    display: block;
    font-size: var(--wh-t-sm);
    font-weight: 600;
    color: var(--wh-fg-soft);
}

.wh-stat__hint {
    display: block;
    margin-top: 2px;
    font-size: var(--wh-t-xs);
    color: var(--wh-fg-faint);
}

.wh-stat__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 40px;
    height: 40px;
    border-radius: var(--wh-r-md);
    background: var(--wh-primary-soft);
    color: var(--wh-primary-strong);
    font-size: 1.05rem;
}

.wh-stat__icon--neutral {
    background: var(--wh-neutral-bg);
    color: var(--wh-neutral);
}

.wh-stat__icon--info {
    background: var(--wh-info-bg);
    color: var(--wh-info);
}

.wh-stat__value {
    margin: 0;
    font-size: var(--wh-t-3xl);
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -0.02em;
    color: var(--wh-fg);
    word-break: break-word;
}

.wh-stat__value small,
.wh-stat__unit {
    font-size: var(--wh-t-md);
    font-weight: 600;
    color: var(--wh-fg-faint);
    margin-right: 0.3em;
}

/* Proportion bar. Reads a --wh-meter-value between 0 and 1. */
.wh-meter {
    height: 6px;
    margin-top: auto;
    border-radius: var(--wh-r-pill);
    background: var(--wh-surface-3);
    overflow: hidden;
}

.wh-meter__fill {
    display: block;
    height: 100%;
    width: calc(var(--wh-meter-value, 0) * 100%);
    border-radius: inherit;
    background: var(--wh-primary);
}

/* The headline meter: thicker, and sat where it is put rather than pushed to
   the bottom of a flex card. */
.wh-meter--lg {
    height: 10px;
    margin-top: 0;
}

.wh-stat__meta {
    margin: 0;
    font-size: var(--wh-t-sm);
    color: var(--wh-fg-soft);
}

.wh-stat__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--wh-s2);
    margin-top: auto;
    padding-top: var(--wh-s3);
}

a.wh-stat:hover,
a.wh-stat:focus-visible {
    border-color: var(--wh-primary);
    box-shadow: var(--wh-shadow-md);
    transform: translateY(-2px);
}

a.wh-stat::after {
    content: "";
    display: block;
    margin-top: auto;
    padding-top: var(--wh-s2);
    font-size: var(--wh-t-sm);
    font-weight: 600;
    color: var(--wh-primary-strong);
}

a.wh-stat[data-wh-cta]::after {
    content: attr(data-wh-cta) " →";
}

/* ==================================================== 5b. money summary */

/* One card that leads with the headline figure, instead of a row of equal
   tiles where every number shouts and none of them leads.
   The 1px grid gap shows the card's line colour through, so the panels are
   divided however they happen to wrap — no breakpoint to keep in step. */
.wh-moneypanel {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
    gap: 1px;
    background: var(--wh-line);
}

.wh-moneypanel__main,
.wh-moneypanel__side {
    display: flex;
    flex-direction: column;
    gap: var(--wh-s3);
    padding: var(--wh-s5);
    background: var(--wh-surface);
}

/* The column carrying the money that can be moved right now. */
.wh-moneypanel__side {
    background:
        linear-gradient(180deg, var(--wh-primary-softer), transparent 60%),
        var(--wh-surface);
}

.wh-progress {
    display: flex;
    flex-direction: column;
    gap: var(--wh-s2);
    margin-top: auto;
}

.wh-progress__meta {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: var(--wh-s2);
    margin: 0;
    font-size: var(--wh-t-sm);
    color: var(--wh-fg-soft);
}

.wh-progress__pct {
    font-weight: 700;
    color: var(--wh-fg);
}

/* Secondary figures, quieter than the panel above and divided the same way. */
.wh-metrics {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
    gap: 1px;
    background: var(--wh-line);
    border-top: 1px solid var(--wh-line);
}

.wh-metric {
    padding: var(--wh-s4) var(--wh-s5);
    background: var(--wh-surface-2);
}

.wh-metric__label {
    display: block;
    font-size: var(--wh-t-xs);
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--wh-fg-faint);
}

.wh-metric__value {
    margin: var(--wh-s1) 0 0;
    font-size: var(--wh-t-xl);
    font-weight: 700;
    line-height: 1.2;
    color: var(--wh-fg);
}

.wh-metric__value .wh-stat__unit {
    font-size: var(--wh-t-sm);
}

.wh-metric__meta {
    margin: 2px 0 0;
    font-size: var(--wh-t-xs);
    color: var(--wh-fg-soft);
}

/* ======================================================= 6. action tile */

.wh-actions {
    display: grid;
    gap: var(--wh-s4);
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

.wh-action {
    display: flex;
    align-items: center;
    gap: var(--wh-s3);
    width: 100%;
    padding: var(--wh-s4);
    text-align: left;
    text-decoration: none;
    color: inherit;
    background: var(--wh-surface);
    border: 1px solid var(--wh-line);
    border-radius: var(--wh-r-md);
    cursor: pointer;
    transition: border-color var(--wh-dur) var(--wh-ease),
        box-shadow var(--wh-dur) var(--wh-ease),
        transform var(--wh-dur) var(--wh-ease);
}

.wh-action:hover,
.wh-action:focus-visible {
    border-color: var(--wh-primary);
    box-shadow: var(--wh-shadow-md);
    transform: translateY(-1px);
    color: inherit;
}

.wh-action__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    border-radius: var(--wh-r-md);
    background: var(--wh-primary-soft);
    color: var(--wh-primary-strong);
    font-size: 1.25rem;
}

.wh-action__icon--whatsapp {
    background: rgba(18, 140, 126, 0.12);
    color: var(--wh-whatsapp);
}

.wh-action__title {
    display: block;
    font-size: var(--wh-t-base);
    font-weight: 600;
    line-height: 1.35;
}

.wh-action__desc {
    display: block;
    margin-top: 2px;
    font-size: var(--wh-t-sm);
    color: var(--wh-fg-soft);
    line-height: 1.45;
}

/* =========================================================== 7. buttons */

.wh-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--wh-s2);
    min-height: 40px;
    padding: 0.5rem 1.1rem;
    font-size: var(--wh-t-base);
    font-weight: 600;
    line-height: 1.3;
    text-align: center;
    text-decoration: none;
    white-space: nowrap;
    border: 1px solid transparent;
    border-radius: var(--wh-r-sm);
    cursor: pointer;
    transition: background-color var(--wh-dur) var(--wh-ease),
        border-color var(--wh-dur) var(--wh-ease),
        color var(--wh-dur) var(--wh-ease),
        box-shadow var(--wh-dur) var(--wh-ease);
}

.wh-btn:disabled,
.wh-btn[aria-disabled="true"] {
    opacity: 0.55;
    cursor: not-allowed;
}

.wh-btn--primary {
    background: var(--wh-primary-strong);
    border-color: var(--wh-primary-strong);
    color: #fff;
}

.wh-btn--primary:hover:not(:disabled),
.wh-btn--primary:focus-visible {
    background: var(--wh-primary-darker);
    border-color: var(--wh-primary-darker);
    color: #fff;
}

/* Lifted, not tinted: the page sits on --wh-canvas but cards are white, so a
   grey fill that showed up on a card would disappear on the page behind it.
   A shadow reads as a button on both. */
.wh-btn--secondary {
    background: var(--wh-surface);
    border-color: var(--wh-line-strong);
    color: var(--wh-fg);
    box-shadow: var(--wh-shadow-sm);
}

.wh-btn--secondary:hover:not(:disabled),
.wh-btn--secondary:focus-visible {
    border-color: var(--wh-primary);
    color: var(--wh-primary-strong);
    background: var(--wh-primary-softer);
    box-shadow: var(--wh-shadow-md);
}

.wh-btn--ghost {
    background: transparent;
    border-color: transparent;
    color: var(--wh-fg-soft);
}

.wh-btn--ghost:hover:not(:disabled),
.wh-btn--ghost:focus-visible {
    background: var(--wh-surface-3);
    color: var(--wh-fg);
}

.wh-btn--danger {
    background: var(--wh-danger);
    border-color: var(--wh-danger);
    color: #fff;
}

.wh-btn--danger:hover:not(:disabled) {
    filter: brightness(0.92);
    color: #fff;
}

.wh-btn--whatsapp {
    background: var(--wh-whatsapp);
    border-color: var(--wh-whatsapp);
    color: #fff;
}

.wh-btn--whatsapp:hover:not(:disabled),
.wh-btn--whatsapp:focus-visible {
    filter: brightness(0.93);
    color: #fff;
}

.wh-btn--sm {
    min-height: 32px;
    padding: 0.3rem 0.7rem;
    font-size: var(--wh-t-sm);
    border-radius: var(--wh-r-xs);
}

.wh-btn--lg {
    min-height: 48px;
    padding: 0.7rem 1.5rem;
    font-size: var(--wh-t-md);
}

.wh-btn--block {
    width: 100%;
}

.wh-btn--icon {
    padding: 0.4rem;
    min-width: 36px;
}

/* Bridge: theme buttons still appear inside modals and third-party markup,
   so they are given the same shape and the accessible green. */
.wh-ui .btn {
    font-weight: 600;
    border-radius: var(--wh-r-sm);
    min-height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--wh-s2);
}

.wh-ui .btn-sm {
    min-height: 32px;
    border-radius: var(--wh-r-xs);
}

.wh-ui .btn-primary,
.wh-ui .btn-success {
    background-color: var(--wh-primary-strong);
    border-color: var(--wh-primary-strong);
    color: #fff;
}

.wh-ui .btn-primary:hover,
.wh-ui .btn-primary:focus,
.wh-ui .btn-success:hover,
.wh-ui .btn-success:focus {
    background-color: var(--wh-primary-darker);
    border-color: var(--wh-primary-darker);
}

.wh-ui .btn-secondary,
.wh-ui .btn-light {
    background-color: var(--wh-surface);
    border-color: var(--wh-line-strong);
    color: var(--wh-fg);
}

.wh-ui .btn-outline-danger {
    color: var(--wh-danger);
    border-color: currentColor;
}

/* ============================================================ 8. badges */

.wh-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.35em;
    padding: 0.2em 0.65em;
    font-size: var(--wh-t-xs);
    font-weight: 600;
    line-height: 1.6;
    letter-spacing: 0.01em;
    border-radius: var(--wh-r-pill);
    background: var(--wh-neutral-bg);
    color: var(--wh-neutral);
    white-space: nowrap;
}

.wh-badge::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
}

.wh-badge--plain::before {
    display: none;
}

.wh-badge--success {
    background: var(--wh-ok-bg);
    color: var(--wh-ok);
}

.wh-badge--warning {
    background: var(--wh-warn-bg);
    color: var(--wh-warn);
}

.wh-badge--danger {
    background: var(--wh-danger-bg);
    color: var(--wh-danger);
}

.wh-badge--info {
    background: var(--wh-info-bg);
    color: var(--wh-info);
}

/* The admin retry screen swaps a pill's className between these three states
   from JavaScript, so the names are kept and mapped onto the badge above. */
.status-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.35em;
    padding: 0.2em 0.65em;
    font-size: var(--wh-t-xs);
    font-weight: 600;
    line-height: 1.6;
    border-radius: var(--wh-r-pill);
    white-space: nowrap;
    background: var(--wh-neutral-bg);
    color: var(--wh-neutral);
}

.status-badge.badge-pending {
    background: var(--wh-warn-bg);
    color: var(--wh-warn);
}

.status-badge.badge-success {
    background: var(--wh-ok-bg);
    color: var(--wh-ok);
}

.status-badge.badge-error {
    background: var(--wh-danger-bg);
    color: var(--wh-danger);
}

.feedback-msg {
    font-size: var(--wh-t-sm);
    line-height: 1.45;
    color: var(--wh-fg-soft);
}

/* =========================================================== 9. banners */

.wh-banner {
    display: flex;
    align-items: flex-start;
    gap: var(--wh-s3);
    padding: var(--wh-s4);
    margin-bottom: var(--wh-s5);
    border: 1px solid rgba(0, 194, 6, 0.3);
    border-left-width: 4px;
    border-radius: var(--wh-r-md);
    background: var(--wh-primary-softer);
    color: var(--wh-fg);
}

.wh-banner__icon {
    flex: 0 0 auto;
    font-size: 1.15rem;
    line-height: 1.4;
    color: var(--wh-primary-strong);
}

.wh-banner__body {
    flex: 1 1 auto;
    min-width: 0;
    font-size: var(--wh-t-base);
    line-height: 1.55;
}

.wh-banner__title {
    display: block;
    font-weight: 600;
    margin-bottom: 2px;
}

.wh-banner__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--wh-s2);
    margin-top: var(--wh-s3);
}

/* A value the user is meant to copy: monospaced, boxed, selectable. */
.wh-code {
    display: inline-block;
    padding: 0.15em 0.5em;
    font-family: SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
    font-size: 0.95em;
    font-weight: 600;
    letter-spacing: 0.04em;
    background: var(--wh-surface);
    border: 1px dashed var(--wh-line-strong);
    border-radius: var(--wh-r-xs);
    user-select: all;
}

/* =========================================================== 10. tables */

.wh-table-wrap {
    width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

.wh-table {
    width: 100%;
    margin: 0;
    border-collapse: separate;
    border-spacing: 0;
    font-size: var(--wh-t-base);
    color: var(--wh-fg);
}

.wh-table thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    padding: var(--wh-s3) var(--wh-s4);
    background: var(--wh-surface-2);
    border-bottom: 1px solid var(--wh-line);
    font-size: var(--wh-t-xs);
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--wh-fg-soft);
    text-align: left;
}

.wh-table tbody td {
    padding: var(--wh-s3) var(--wh-s4);
    border-bottom: 1px solid var(--wh-line);
    vertical-align: middle;
}

.wh-table tbody tr:last-child td {
    border-bottom: 0;
}

.wh-table tfoot th {
    padding: var(--wh-s3) var(--wh-s4);
    background: var(--wh-surface-2);
    border-top: 2px solid var(--wh-line-strong);
    font-size: var(--wh-t-base);
    font-weight: 700;
    text-align: left;
}

.wh-table tbody tr {
    transition: background-color var(--wh-dur) var(--wh-ease);
}

.wh-table tbody tr:hover {
    background: var(--wh-primary-softer);
}

/* .wh-num only asks for tabular figures. Alignment is stated separately, so a
   reference number or a phone number can use the same even digits without
   being pushed to the right like a money column. */
.wh-table th.wh-align-end,
.wh-table td.wh-align-end {
    text-align: right;
}

.wh-table__primary {
    font-weight: 600;
    color: var(--wh-fg);
    text-decoration: none;
}

.wh-table__primary:hover {
    color: var(--wh-primary-strong);
    text-decoration: underline;
}

.wh-table__sub {
    display: block;
    margin-top: 2px;
    font-size: var(--wh-t-xs);
    color: var(--wh-fg-faint);
}

.wh-table__actions {
    text-align: right;
    white-space: nowrap;
}

/* Reserve the row-action strip its own width, so a long name in the first
   column cannot squeeze the buttons onto a second line. */
@media (min-width: 768px) {
    .wh-table th.wh-table__actions,
    .wh-table td.wh-table__actions {
        width: 1%;
        min-width: 200px;
    }
}

/* Row actions. Icon-only on wide screens with the name kept for assistive
   tech; the label becomes visible once the row turns into a card, where
   there is room for it and no hover to reveal a tooltip. */
.wh-actionbar {
    display: inline-flex;
    flex-wrap: nowrap;
    justify-content: flex-end;
    gap: var(--wh-s1);
}

.wh-iconaction {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    width: 34px;
    height: 34px;
    padding: 0;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--wh-r-xs);
    color: var(--wh-fg-soft);
    text-decoration: none;
    cursor: pointer;
    transition: background-color var(--wh-dur) var(--wh-ease),
        color var(--wh-dur) var(--wh-ease),
        border-color var(--wh-dur) var(--wh-ease);
}

.wh-iconaction:hover,
.wh-iconaction:focus-visible {
    background: var(--wh-primary-soft);
    border-color: var(--wh-primary);
    color: var(--wh-primary-strong);
}

.wh-iconaction__text {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

@media (max-width: 767.98px) {
    .wh-actionbar {
        flex-wrap: wrap;
    }

    .wh-iconaction {
        width: auto;
        padding: 0 0.6rem;
        border-color: var(--wh-line);
    }

    .wh-iconaction__text {
        position: static;
        width: auto;
        height: auto;
        margin: 0;
        overflow: visible;
        clip: auto;
        font-size: var(--wh-t-xs);
        font-weight: 600;
    }
}

.wh-table caption {
    caption-side: top;
    padding: 0;
}

/* Below the tablet breakpoint each row becomes its own card, with the header
   text repeated per cell from data-label. Set the labels in markup or let
   WH.ui.stackTable copy them from <thead>. */
@media (max-width: 767.98px) {
    .wh-table--stack thead {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
    }

    .wh-table--stack tbody tr {
        display: block;
        margin-bottom: var(--wh-s3);
        padding: var(--wh-s3) var(--wh-s4);
        background: var(--wh-surface);
        border: 1px solid var(--wh-line);
        border-radius: var(--wh-r-md);
        box-shadow: var(--wh-shadow-xs);
    }

    .wh-table--stack tbody td {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        gap: var(--wh-s4);
        padding: var(--wh-s2) 0;
        border-bottom: 1px dashed var(--wh-line);
        text-align: right;
    }

    .wh-table--stack tbody td:last-child {
        border-bottom: 0;
        padding-bottom: 0;
    }

    .wh-table--stack tbody td::before {
        content: attr(data-label);
        flex: 0 0 auto;
        font-size: var(--wh-t-xs);
        font-weight: 700;
        letter-spacing: 0.04em;
        text-transform: uppercase;
        color: var(--wh-fg-faint);
        text-align: left;
    }

    /* The row's own heading cell spans the card instead of pairing up. */
    .wh-table--stack tbody td[data-wh-lead]::before {
        display: none;
    }

    .wh-table--stack tbody td[data-wh-lead] {
        display: block;
        text-align: left;
        padding-top: 0;
        font-size: var(--wh-t-md);
    }

    .wh-table--stack tbody td.wh-table__actions {
        justify-content: flex-end;
    }

    /* A cell with nothing in it would otherwise print its label over a blank
       value, which reads as missing data rather than "not applicable". */
    .wh-table--stack tbody td:empty,
    .wh-table--stack tbody td:has(> :only-child:empty) {
        display: none;
    }

    /* The totals row keeps its label/value pairing rather than becoming a card. */
    .wh-table--stack tfoot tr {
        display: flex;
        justify-content: space-between;
        gap: var(--wh-s4);
        border-radius: var(--wh-r-md);
        background: var(--wh-surface-2);
    }

    .wh-table--stack tfoot th {
        border: 0;
    }

    .wh-table--stack tfoot th:empty {
        display: none;
    }
}

/* ========================================================== 11. toolbar */

.wh-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--wh-s3);
    padding: var(--wh-s4) var(--wh-s5);
    border-bottom: 1px solid var(--wh-line);
}

.wh-toolbar__group {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--wh-s2);
}

.wh-search {
    position: relative;
    flex: 1 1 220px;
    max-width: 340px;
}

.wh-search__icon {
    position: absolute;
    top: 50%;
    left: 0.75rem;
    transform: translateY(-50%);
    color: var(--wh-fg-faint);
    pointer-events: none;
    font-size: 0.95rem;
}

.wh-search input {
    width: 100%;
    min-height: 40px;
    padding: 0.45rem 0.75rem 0.45rem 2.25rem;
    font-size: var(--wh-t-base);
    color: var(--wh-fg);
    background: var(--wh-surface);
    border: 1px solid var(--wh-line-strong);
    border-radius: var(--wh-r-sm);
    transition: border-color var(--wh-dur) var(--wh-ease),
        box-shadow var(--wh-dur) var(--wh-ease);
}

.wh-search input:focus {
    outline: none;
    border-color: var(--wh-primary);
    box-shadow: 0 0 0 3px var(--wh-primary-ring);
}

/* A region a live search is refreshing. The old rows stay where they are and
   only fade: emptying the table on every keystroke would read as flicker. */
.wh-live {
    transition: opacity var(--wh-dur) var(--wh-ease);
}

.wh-live[aria-busy="true"] {
    opacity: 0.5;
}

/* ====================================================== 12. list / rows */

.wh-list {
    margin: 0;
    padding: 0;
    list-style: none;
}

.wh-list__item {
    display: flex;
    align-items: center;
    gap: var(--wh-s3);
    padding: var(--wh-s3) var(--wh-s5);
    border-bottom: 1px solid var(--wh-line);
    text-decoration: none;
    color: inherit;
    transition: background-color var(--wh-dur) var(--wh-ease);
}

.wh-list__item:last-child {
    border-bottom: 0;
}

a.wh-list__item:hover {
    background: var(--wh-primary-softer);
    color: inherit;
}

.wh-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background: var(--wh-primary-soft);
    color: var(--wh-primary-strong);
    font-size: var(--wh-t-sm);
    font-weight: 700;
    text-transform: uppercase;
}

.wh-list__body {
    flex: 1 1 auto;
    min-width: 0;
}

.wh-list__title {
    display: block;
    font-size: var(--wh-t-base);
    font-weight: 600;
    line-height: 1.35;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.wh-list__meta {
    display: block;
    margin-top: 2px;
    font-size: var(--wh-t-xs);
    color: var(--wh-fg-faint);
}

.wh-list__end {
    flex: 0 0 auto;
    text-align: right;
}

.wh-list__amount {
    display: block;
    font-size: var(--wh-t-base);
    font-weight: 700;
    color: var(--wh-fg);
}

/* ================================================= 12b. notifications */

/* The bell and its dropdown. The theme owns opening and closing (.show) and
   the panel's position; everything inside it is the layer's. */
.wh-notif__toggle {
    position: relative;
    display: block;
    padding: 0.5rem;
    color: inherit;
    line-height: 1;
}

.wh-notif__badge {
    position: absolute;
    top: 0;
    right: 0;
    min-width: 18px;
    padding: 0 4px;
    border-radius: var(--wh-r-pill);
    /* Literal, not --wh-danger: that token lightens in dark mode, and white
       on the lightened red does not carry. */
    background: #d92d20;
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    line-height: 18px;
    text-align: center;
}

.wh-ui .wh-notif__menu {
    width: 340px;
    max-width: calc(100vw - 2rem);
    padding: 0;
    border: 1px solid var(--wh-line);
    border-radius: var(--wh-r-md);
    background: var(--wh-surface);
    box-shadow: var(--wh-shadow-lg);
    overflow: hidden;
}

.wh-notif__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--wh-s2);
    padding: var(--wh-s3) var(--wh-s4);
    border-bottom: 1px solid var(--wh-line);
}

.wh-ui .wh-notif__title {
    margin: 0;
    font-size: var(--wh-t-base);
    font-weight: 600;
}

.wh-notif__list {
    max-height: 340px;
    overflow-y: auto;
}

.wh-notif__menu .wh-notif__item {
    padding: var(--wh-s3) var(--wh-s4);
}

/* Unread carries a tint and an edge, so the state survives greyscale and
   never rests on colour alone. */
.wh-notif__item.is-unread {
    box-shadow: inset 3px 0 0 var(--wh-primary);
    background: var(--wh-primary-softer);
}

.wh-notif__dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    margin-top: var(--wh-s1);
    border-radius: 50%;
    background: var(--wh-primary);
}

.wh-notif__new {
    color: var(--wh-primary-strong);
    font-weight: 600;
}

.wh-notif__foot {
    display: block;
    padding: var(--wh-s3);
    border-top: 1px solid var(--wh-line);
    background: var(--wh-surface-2);
    color: var(--wh-primary-strong);
    font-size: var(--wh-t-sm);
    font-weight: 600;
    text-align: center;
}

.wh-notif__foot:hover,
.wh-notif__foot:focus {
    background: var(--wh-primary-soft);
    color: var(--wh-primary-darker);
}

/* Day headings on the notifications page. */
.wh-notif__day {
    display: flex;
    align-items: center;
    gap: var(--wh-s2);
    margin: 0;
    padding: var(--wh-s3) var(--wh-s5);
    background: var(--wh-surface-2);
    border-bottom: 1px solid var(--wh-line);
    font-size: var(--wh-t-xs);
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--wh-fg-soft);
}

.wh-notif__day span {
    padding: 0 var(--wh-s2);
    border-radius: var(--wh-r-pill);
    background: var(--wh-primary-soft);
    color: var(--wh-primary-strong);
    letter-spacing: 0;
}

.wh-notif__actions {
    display: flex;
    align-items: center;
    gap: var(--wh-s2);
}

/* ===================================================== 13. empty states */

/* wh-ui.css owns the base .wh-empty; these are the size variants the
   dashboard needs and the dark-mode correction. */
.wh-empty--sm {
    padding: var(--wh-s5) var(--wh-s4);
}

.wh-empty--sm .wh-empty-icon {
    font-size: 1.75rem;
}

.wh-ui .wh-empty {
    color: var(--wh-fg-soft);
}

.wh-ui .wh-empty-title {
    color: var(--wh-fg);
}

.wh-ui .wh-empty-icon {
    color: var(--wh-primary);
}

/* ========================================================== 14. dialogs */

.wh-ui .modal-content {
    border: 1px solid var(--wh-line);
    border-radius: var(--wh-r-lg);
    background: var(--wh-surface);
    box-shadow: var(--wh-shadow-lg);
}

.wh-ui .modal-header {
    align-items: center;
    gap: var(--wh-s3);
    padding: var(--wh-s4) var(--wh-s5);
    border-bottom: 1px solid var(--wh-line);
}

.wh-ui .modal-title {
    font-size: var(--wh-t-lg);
    font-weight: 600;
    margin: 0;
}

.wh-ui .modal-body {
    padding: var(--wh-s5);
}

.wh-ui .modal-footer {
    gap: var(--wh-s2);
    padding: var(--wh-s3) var(--wh-s5);
    border-top: 1px solid var(--wh-line);
    background: var(--wh-surface-2);
}

.wh-ui .modal-footer > * {
    margin: 0;
}

/* Bootstrap 5 draws this button's × entirely as a background image, and the
   dashboard theme sets background-image: none for its own Bootstrap 4 markup —
   which left every modal header with an invisible, unlabelled close box. Put
   the glyph back, and give it the hit area and focus ring a button needs. */
.wh-ui .btn-close {
    box-sizing: content-box;
    float: none;
    width: 1em;
    height: 1em;
    padding: 0.35em;
    margin-inline-start: auto;
    color: var(--wh-fg);
    text-shadow: none;
    background: transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;
    border: 0;
    border-radius: var(--wh-r-sm);
    opacity: 0.55;
    transition: opacity var(--wh-dur) var(--wh-ease), background-color var(--wh-dur) var(--wh-ease);
}

.wh-ui .btn-close:hover,
.wh-ui .btn-close:focus-visible {
    opacity: 1;
    background-color: var(--wh-surface-3);
}

.wh-ui .btn-close:focus-visible {
    outline: 2px solid var(--wh-primary);
    outline-offset: 2px;
}

.wh-ui.dark-theme .btn-close {
    filter: invert(1) grayscale(1) brightness(2);
}

/* ============================================================ 15. forms */

.wh-ui .form-label {
    display: block;
    margin-bottom: var(--wh-s2);
    font-size: var(--wh-t-sm);
    font-weight: 600;
    color: var(--wh-fg);
}

/* width/display are stated here rather than inherited from Bootstrap: the
   guest pages load this layer on its own, with no framework under it. */
.wh-ui .form-control,
.wh-ui .form-select {
    display: block;
    width: 100%;
    min-height: 42px;
    padding: 0.5rem 0.75rem;
    font-size: var(--wh-t-base);
    color: var(--wh-fg);
    background-color: var(--wh-surface);
    border: 1px solid var(--wh-line-strong);
    border-radius: var(--wh-r-sm);
}

.wh-ui .form-control:focus,
.wh-ui .form-select:focus {
    border-color: var(--wh-primary);
    box-shadow: 0 0 0 3px var(--wh-primary-ring);
}

.wh-ui .form-control::placeholder {
    color: var(--wh-fg-faint);
}

/* File inputs render with the platform's own button, which sits at whatever
   contrast the OS picked — unreadable on the dark theme. */
.wh-ui input[type="file"].form-control {
    padding: 0.35rem 0.5rem;
    color: var(--wh-fg);
}

.wh-ui input[type="file"]::file-selector-button {
    margin-right: 0.75rem;
    padding: 0.35rem 0.75rem;
    font-size: var(--wh-t-sm);
    font-weight: 600;
    color: var(--wh-fg);
    background: var(--wh-surface-3);
    border: 1px solid var(--wh-line-strong);
    border-radius: var(--wh-r-xs);
    cursor: pointer;
}

.wh-ui input[type="file"]::file-selector-button:hover {
    border-color: var(--wh-primary);
    color: var(--wh-primary-strong);
}

/* A short explanation under a field, distinct from an error. */
.wh-field-hint {
    display: block;
    margin-top: var(--wh-s2);
    font-size: var(--wh-t-xs);
    line-height: 1.5;
    color: var(--wh-fg-faint);
}

/* ================================================ 15b. section sub-nav */

/* Tabs within a section (Settings → Profile / Security). A real <nav> of
   links, so each tab keeps its own URL and back button. */
.wh-subnav {
    display: flex;
    gap: var(--wh-s1);
    margin-bottom: var(--wh-s5);
    border-bottom: 1px solid var(--wh-line);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

.wh-subnav__link {
    padding: 0.6rem 0.9rem;
    font-size: var(--wh-t-base);
    font-weight: 600;
    color: var(--wh-fg-soft);
    text-decoration: none;
    white-space: nowrap;
    border-bottom: 2px solid transparent;
    transition: color var(--wh-dur) var(--wh-ease),
        border-color var(--wh-dur) var(--wh-ease);
}

.wh-subnav__link:hover {
    color: var(--wh-fg);
}

.wh-subnav__link[aria-current="page"] {
    color: var(--wh-primary-strong);
    border-bottom-color: var(--wh-primary);
}

/* ================================================== 15c. detail values */

.wh-dl {
    display: grid;
    gap: var(--wh-s4);
    margin: 0;
}

.wh-dl--cols {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
}

.wh-dl dt {
    font-size: var(--wh-t-xs);
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--wh-fg-faint);
}

.wh-dl dd {
    margin: var(--wh-s1) 0 0;
    font-size: var(--wh-t-md);
    color: var(--wh-fg);
    word-break: break-word;
}

/* A value with no natural break — a public link — takes the whole row and
   wraps, rather than being cut off mid-address in a single column. */
.wh-dl__wide {
    grid-column: 1 / -1;
}

.wh-dl__link {
    display: inline-block;
    max-width: 100%;
    overflow-wrap: anywhere;
}

/* An identity block: avatar, name, one line of context. */
.wh-identity {
    display: flex;
    align-items: center;
    gap: var(--wh-s4);
}

.wh-avatar--lg {
    width: 64px;
    height: 64px;
    font-size: var(--wh-t-lg);
}

.wh-identity__name {
    margin: 0;
    font-size: var(--wh-t-xl);
    font-weight: 700;
    line-height: 1.25;
}

/* ==================================================== 15d. form layout */

/* Forms are read a field at a time, so they are capped well short of the
   full content width however wide the screen is. */
.wh-narrow {
    max-width: 640px;
}

.wh-narrow--wide {
    max-width: 900px;
}

.wh-form {
    display: grid;
    gap: var(--wh-s4);
}

.wh-form__row {
    display: grid;
    gap: var(--wh-s4);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
}

/* Centred, so a row mixing button sizes lines up on its middle rather than
   hanging from the top of the tallest one. */
.wh-form__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--wh-s2);
    padding-top: var(--wh-s2);
}

.wh-form__actions--center {
    justify-content: center;
}

.wh-form__actions--end {
    justify-content: flex-end;
}

/* The cell already has its own padding, and vertical-align centres the box
   with it, which would sit the buttons below the rest of the row. */
.wh-table td > .wh-form__actions {
    padding-top: 0;
}

.wh-fieldset {
    margin: 0;
    padding: 0;
    border: 0;
}

.wh-fieldset__legend {
    padding: 0;
    margin-bottom: var(--wh-s3);
    font-size: var(--wh-t-md);
    font-weight: 600;
    color: var(--wh-fg);
}

.wh-ui .form-control[readonly],
.wh-ui .form-control:disabled {
    background-color: var(--wh-surface-3);
    color: var(--wh-fg-soft);
    cursor: not-allowed;
}

/* Radio or checkbox presented as a card with its own explanation, for
   choices that need more than a one-word label. */
.wh-choices {
    display: grid;
    gap: var(--wh-s3);
}

.wh-choices--cols {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
}

.wh-choice {
    display: flex;
    align-items: flex-start;
    gap: var(--wh-s3);
    margin: 0;
    padding: var(--wh-s4);
    background: var(--wh-surface);
    border: 1px solid var(--wh-line);
    border-radius: var(--wh-r-md);
    cursor: pointer;
    transition: border-color var(--wh-dur) var(--wh-ease),
        background-color var(--wh-dur) var(--wh-ease);
}

.wh-choice:hover {
    border-color: var(--wh-primary);
}

/* The theme absolutely positions .form-check-input and pulls it left, which
   drops the control on top of its own label here. Put it back in flow. */
.wh-choice input {
    flex: 0 0 auto;
    position: static;
    float: none;
    width: 1.05rem;
    height: 1.05rem;
    margin: 0.15rem 0 0;
    accent-color: var(--wh-primary-strong);
}

.wh-choice__title {
    display: block;
    font-size: var(--wh-t-base);
    font-weight: 600;
    line-height: 1.35;
}

.wh-choice__desc {
    display: block;
    margin-top: 2px;
    font-size: var(--wh-t-sm);
    line-height: 1.5;
    color: var(--wh-fg-soft);
}

/* Progressive enhancement: browsers without :has() simply show no
   highlight, and the radio itself still says which one is selected. */
.wh-choice:has(input:checked) {
    border-color: var(--wh-primary);
    background: var(--wh-primary-softer);
    box-shadow: inset 0 0 0 1px var(--wh-primary);
}

/* ================================================= 16. callout / notice */

.wh-notice {
    padding: var(--wh-s3) var(--wh-s4);
    border-radius: var(--wh-r-md);
    border: 1px solid transparent;
    font-size: var(--wh-t-sm);
    line-height: 1.55;
}

.wh-notice--warning {
    background: var(--wh-warn-bg);
    border-color: rgba(240, 147, 43, 0.35);
    color: var(--wh-warn);
}

.wh-notice--danger {
    background: var(--wh-danger-bg);
    border-color: rgba(192, 42, 42, 0.3);
    color: var(--wh-danger);
}

.wh-notice--info {
    background: var(--wh-info-bg);
    border-color: rgba(29, 95, 168, 0.25);
    color: var(--wh-info);
}

.wh-notice strong {
    color: inherit;
}

/* A read-only summary of what the user is about to confirm. */
.wh-summary {
    display: grid;
    gap: var(--wh-s2);
    padding: var(--wh-s4);
    background: var(--wh-surface-2);
    border: 1px solid var(--wh-line);
    border-radius: var(--wh-r-md);
}

.wh-summary__row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--wh-s4);
    font-size: var(--wh-t-base);
}

.wh-summary__key {
    color: var(--wh-fg-soft);
}

.wh-summary__val {
    font-weight: 600;
    text-align: right;
    word-break: break-word;
}

/* ============================================== 17. support dock (fixed) */

/* Three separate fixed elements used to be positioned by hand on every page
   and overlapped each other on small screens. One dock owns that corner. */
/* Regulator/partner line. Centred on desktop, out of the dock's way on
   phones where it becomes a plain full-width strip above the dock.
   Light, because the Onit wordmark is dark artwork with no light variant —
   on the navy pill this replaces it was invisible. */
.wh-poweredby {
    position: fixed;
    left: 50%;
    bottom: 1rem;
    transform: translateX(-50%);
    z-index: 1020;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    max-width: min(560px, calc(100vw - 9rem));
    padding: 0.35rem 0.85rem;
    font-size: var(--wh-t-xs);
    line-height: 1.3;
    color: var(--wh-fg-soft);
    text-decoration: none;
    background: var(--wh-surface);
    border: 1px solid var(--wh-line);
    border-radius: var(--wh-r-pill);
    box-shadow: var(--wh-shadow-md);
}

.wh-poweredby:hover {
    color: var(--wh-fg);
    border-color: var(--wh-line-strong);
}

.wh-poweredby img {
    height: 16px;
    width: auto;
}

@media (max-width: 767.98px) {
    /* Stays centred between the two buttons rather than sliding under the
       left one; narrower buttons leave it a little more room. */
    .wh-poweredby {
        max-width: calc(100vw - 8rem);
        font-size: 0.625rem;
        padding: 0.25rem 0.6rem;
    }
}

/* ================================================ 18. theme corrections */

/* The theme's own chrome, nudged into the same visual language. */
.wh-ui .main-header {
    background: var(--wh-surface);
    border-bottom: 1px solid var(--wh-line);
    box-shadow: none;
}

.wh-ui .main-header.fixed-header {
    box-shadow: var(--wh-shadow-sm);
}

.wh-ui .main-sidebar {
    border-right: 1px solid var(--wh-line);
}

/* Under 992px the theme fixes the header, zeroes the spacer that sticky.js
   sizes (.jumps-prevent, !important) and leaves the negative margins that
   script writes in place — so the top of every page sat underneath the
   header. Undo the pull, then clear the header's own height. */
@media (max-width: 991.98px) {
    .wh-ui .page > .sticky {
        margin-bottom: 0 !important;
    }

    .wh-ui .main-content.side-content {
        padding-top: 64px;
    }
}

/* The phone header's logo had no size of its own, so it rendered at the
   image's natural size — a ~100px band that also broke sticky.js, which
   measures the header before the image has loaded and offsets the content
   by the smaller number. A fixed height makes that measurement correct. */
@media (max-width: 991.98px) {
    .wh-ui .responsive-logo img {
        height: 34px;
        width: auto;
        max-width: 170px;
    }
}

/* Extra room at the foot of the page so the fixed partner strip never
   covers the copyright line once the user scrolls to the bottom. */
.wh-ui .main-footer {
    padding: var(--wh-s4) 0 var(--wh-s7);
    border-top: 1px solid var(--wh-line);
    background: transparent;
    box-shadow: none;
    font-size: var(--wh-t-sm);
    color: var(--wh-fg-faint);
}

/* The theme offsets the content for the sidebar but not the footer, so the
   copyright line ran underneath it. Same offsets, including the collapsed
   and hidden sidebar states the theme toggles on <body>. */
@media (min-width: 992px) {
    .wh-ui .main-footer {
        margin-left: 240px;
    }

    .wh-ui.main-sidebar-hide .main-footer {
        margin-left: 80px;
    }

    .wh-ui.closed-leftmenu.main-sidebar-hide .main-footer {
        margin-left: 0;
    }
}

/* Google's translate gadget injects its own type scale; keep it from
   setting the height of the header row. */
.wh-ui .google_translate {
    max-width: 190px;
    font-size: var(--wh-t-xs);
    line-height: 1.2;
}

.wh-ui .goog-te-combo {
    max-width: 100%;
    padding: 0.25rem 0.4rem;
    border: 1px solid var(--wh-line-strong);
    border-radius: var(--wh-r-xs);
    background: var(--wh-surface);
    color: var(--wh-fg);
}

@media (max-width: 767.98px) {
    .wh-ui .google_translate {
        display: none;
    }
}

.wh-ui .page-header {
    margin: 0;
    padding: 0;
    border: 0;
    min-height: 0;
}

/* The purchased card styling is heavier than the layer's; where a page still
   uses it, quieten it so both read as one system. */
.wh-ui .card.custom-card {
    border: 1px solid var(--wh-line);
    border-radius: var(--wh-r-lg);
    box-shadow: var(--wh-shadow-sm);
}

.wh-ui .main-profile-menu .dropdown-menu {
    border: 1px solid var(--wh-line);
    border-radius: var(--wh-r-md);
    box-shadow: var(--wh-shadow-lg);
}

/* Header controls the theme styles as anchors; ours are real buttons. */
.wh-ui .main-header button.nav-link,
.wh-iconbtn {
    background: transparent;
    border: 0;
    padding: 0.5rem;
    color: inherit;
    line-height: 1;
    cursor: pointer;
}

.wh-ui .main-profile-menu .wh-avatar {
    width: 34px;
    height: 34px;
    font-size: var(--wh-t-xs);
}

.wh-ui .main-profile-menu .header-navheading {
    padding: var(--wh-s3) var(--wh-s4);
}

.wh-ui .main-profile-menu .dropdown-item {
    display: flex;
    align-items: center;
    gap: var(--wh-s2);
    padding: 0.55rem var(--wh-s4);
    font-size: var(--wh-t-base);
}

/* DataTables chrome, dropped into the layer's toolbar/footer shells. The
   layout string in WH.ui.dataTable is what puts the pieces in these. */
.wh-ui div.dataTables_wrapper div.dataTables_length {
    display: none;
}

.wh-ui div.dataTables_wrapper div.dataTables_filter {
    margin: 0;
    text-align: left;
}

.wh-ui div.dataTables_filter label {
    display: block;
    margin: 0;
    font-weight: 400;
}

.wh-ui div.dataTables_filter input {
    width: 100%;
    min-width: 200px;
    min-height: 40px;
    margin: 0;
    padding: 0.45rem 0.75rem;
    font-size: var(--wh-t-base);
    color: var(--wh-fg);
    background: var(--wh-surface);
    border: 1px solid var(--wh-line-strong);
    border-radius: var(--wh-r-sm);
}

.wh-ui div.dataTables_filter input:focus {
    outline: none;
    border-color: var(--wh-primary);
    box-shadow: 0 0 0 3px var(--wh-primary-ring);
}

.wh-dt-search {
    flex: 1 1 220px;
    max-width: 340px;
}

.wh-dt-foot {
    border-bottom: 0;
    border-top: 1px solid var(--wh-line);
}

.wh-ui div.dataTables_wrapper div.dataTables_info {
    padding: 0;
    font-size: var(--wh-t-sm);
    color: var(--wh-fg-faint);
}

.wh-ui div.dataTables_wrapper div.dataTables_paginate {
    padding: 0;
    margin: 0;
}

.wh-ui .page-link {
    border-radius: var(--wh-r-xs);
    border-color: var(--wh-line);
    color: var(--wh-fg-soft);
}

.wh-ui .page-item.active .page-link {
    background: var(--wh-primary-strong);
    border-color: var(--wh-primary-strong);
    color: #fff;
}

.wh-ui .dt-buttons .btn {
    min-height: 32px;
    padding: 0.3rem 0.7rem;
    font-size: var(--wh-t-sm);
    font-weight: 600;
    background: var(--wh-surface);
    border: 1px solid var(--wh-line-strong);
    color: var(--wh-fg);
    border-radius: var(--wh-r-xs);
}

.wh-ui .dt-buttons .btn:hover {
    border-color: var(--wh-primary);
    color: var(--wh-primary-strong);
}

.wh-ui table.dataTable thead th.sorting,
.wh-ui table.dataTable thead th.sorting_asc,
.wh-ui table.dataTable thead th.sorting_desc {
    cursor: pointer;
    padding-right: 1.75rem;
}

/* ========================================================= 19. utilities */

.wh-stack {
    display: flex;
    flex-direction: column;
}

.wh-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
}

.wh-grid {
    display: grid;
    gap: var(--wh-s4);
}

.wh-grid--3 {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
}

.wh-grid--2 {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
}

.wh-gap-1 { gap: var(--wh-s1); }
.wh-gap-2 { gap: var(--wh-s2); }
.wh-gap-3 { gap: var(--wh-s3); }
.wh-gap-4 { gap: var(--wh-s4); }
.wh-gap-5 { gap: var(--wh-s5); }

.wh-mt-2 { margin-top: var(--wh-s2); }
.wh-mt-3 { margin-top: var(--wh-s3); }
.wh-mt-4 { margin-top: var(--wh-s4); }
.wh-mt-5 { margin-top: var(--wh-s5); }
.wh-mb-4 { margin-bottom: var(--wh-s4); }
.wh-mb-5 { margin-bottom: var(--wh-s5); }

.wh-between {
    justify-content: space-between;
}

.wh-nowrap {
    white-space: nowrap;
}

.wh-truncate {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.wh-section {
    margin-bottom: var(--wh-s5);
}

.wh-section__title {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--wh-s3);
    margin-bottom: var(--wh-s3);
    font-size: var(--wh-t-md);
    font-weight: 600;
}

/* ==================================================== 19b. guest layout */

/* The signed-out pages — OTP, password reset, 403/404 — are one card on the
   brand background. Same tokens as the dashboard, different frame. */
.wh-guest {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    margin: 0;
    padding: var(--wh-s5) var(--wh-s4);
    background: linear-gradient(160deg, #00a651, #007a04 65%, #006103);
    font-family: "Montserrat", system-ui, -apple-system, "Segoe UI", sans-serif;
    color: var(--wh-fg);
}

.wh-guest__inner {
    width: 100%;
    max-width: 440px;
}

.wh-guest__inner--md { max-width: 560px; }
.wh-guest__inner--lg { max-width: 720px; }

/* The wordmark is dark green artwork with no light variant, so it is given
   its own white chip rather than being lost against the background. */
.wh-guest__brand {
    display: flex;
    align-items: center;
    justify-content: center;
    width: fit-content;
    margin: 0 auto var(--wh-s5);
    padding: 0.55rem 1.25rem;
    background: #fff;
    border-radius: var(--wh-r-pill);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.15);
}

.wh-guest__brand img {
    height: 34px;
    width: auto;
}

.wh-guest-card {
    padding: var(--wh-s6);
    background: var(--wh-surface);
    border-radius: var(--wh-r-lg);
    box-shadow: 0 18px 45px rgba(0, 0, 0, 0.22);
}

.wh-guest__title {
    margin: 0 0 var(--wh-s2);
    font-size: var(--wh-t-xl);
    font-weight: 700;
    line-height: 1.25;
    text-align: center;
    color: var(--wh-fg);
}

.wh-guest__lede {
    margin: 0 0 var(--wh-s5);
    font-size: var(--wh-t-base);
    line-height: 1.6;
    text-align: center;
    color: var(--wh-fg-soft);
}

.wh-guest__foot,
.wh-guest__legal {
    margin: var(--wh-s4) 0 0;
    text-align: center;
    font-size: var(--wh-t-sm);
    color: rgba(255, 255, 255, 0.92);
}

.wh-guest__legal {
    margin-top: var(--wh-s5);
    font-size: var(--wh-t-xs);
    color: rgba(255, 255, 255, 0.7);
}

.wh-guest__foot a {
    color: #fff;
    text-decoration: underline;
}

/* A code the user copies off a text message: wide, spaced and centred, so
   six digits are easy to check before submitting. */
.wh-otp-input {
    width: 100%;
    padding: 0.75rem 1rem;
    font-family: SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 1.75rem;
    font-weight: 600;
    letter-spacing: 0.5em;
    text-align: center;
    text-indent: 0.5em;
    color: var(--wh-fg);
    background: var(--wh-surface);
    border: 1px solid var(--wh-line-strong);
    border-radius: var(--wh-r-md);
}

.wh-otp-input:focus {
    outline: none;
    border-color: var(--wh-primary);
    box-shadow: 0 0 0 3px var(--wh-primary-ring);
}

/* A password field with a reveal button sharing its border. */
.wh-input-reveal {
    position: relative;
    display: block;
}

.wh-input-reveal .form-control {
    padding-right: 3rem;
}

.wh-input-reveal button {
    position: absolute;
    top: 50%;
    right: 0.35rem;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    font-size: 1rem;
    line-height: 1;
    color: var(--wh-fg-faint);
    background: transparent;
    border: 0;
    border-radius: var(--wh-r-xs);
    cursor: pointer;
}

.wh-input-reveal button:hover {
    color: var(--wh-fg);
    background: var(--wh-surface-3);
}

/* Live requirements list. Items gain .met as each one is satisfied. */
.wh-rules {
    margin: var(--wh-s3) 0 0;
    padding: 0;
    list-style: none;
    display: grid;
    gap: var(--wh-s1);
}

.wh-rules li {
    display: flex;
    align-items: center;
    gap: var(--wh-s2);
    font-size: var(--wh-t-sm);
    color: var(--wh-fg-faint);
    transition: color var(--wh-dur) var(--wh-ease);
}

.wh-rules li::before {
    content: "○";
    font-size: 0.9em;
    line-height: 1;
}

.wh-rules li.met {
    color: var(--wh-ok);
}

.wh-rules li.met::before {
    content: "●";
}

/* Expiry countdown and attempts remaining, either side of one line. */
.wh-guest__meta {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: var(--wh-s2);
    margin-top: var(--wh-s3);
    font-size: var(--wh-t-sm);
    color: var(--wh-fg-faint);
}

@media (max-width: 480px) {
    .wh-guest-card {
        padding: var(--wh-s5) var(--wh-s4);
    }

    .wh-otp-input {
        font-size: 1.4rem;
        letter-spacing: 0.35em;
        text-indent: 0.35em;
    }
}

/* =============================================== 19c. sign-in panel */

/* The landing page's sign-in / sign-up / reset panel. It keeps the theme's
   class names — script.js finds the tab buttons by their onclick attribute
   and toggles .active — and only the styling comes from here. */
.wh-ui .sign-up-form-wrap {
    padding: var(--wh-s5);
    background: var(--wh-surface);
    border: 1px solid var(--wh-line);
    border-radius: var(--wh-r-lg);
    box-shadow: 0 18px 45px rgba(0, 0, 0, 0.18);
}

.wh-ui .sign-up-form-header {
    margin-bottom: var(--wh-s4);
}

.wh-ui .tabs {
    display: flex;
    gap: var(--wh-s1);
    padding: var(--wh-s1);
    background: var(--wh-surface-3);
    border-radius: var(--wh-r-md);
}

.wh-ui .tab-btn {
    flex: 1 1 0;
    padding: 0.55rem 0.75rem;
    font-family: inherit;
    font-size: var(--wh-t-base);
    font-weight: 600;
    color: var(--wh-fg-soft);
    background: transparent;
    border: 0;
    border-radius: var(--wh-r-sm);
    cursor: pointer;
    transition: background-color var(--wh-dur) var(--wh-ease),
        color var(--wh-dur) var(--wh-ease);
}

.wh-ui .tab-btn:hover {
    color: var(--wh-fg);
}

.wh-ui .tab-btn.active {
    color: var(--wh-primary-strong);
    background: var(--wh-surface);
    box-shadow: var(--wh-shadow-xs);
}

.wh-ui .login-signup-form .form-group {
    margin-bottom: var(--wh-s4);
}

.wh-ui .login-signup-form label {
    display: block;
    margin-bottom: var(--wh-s2);
    font-size: var(--wh-t-sm);
    font-weight: 600;
    color: var(--wh-fg);
}

/* --- One field treatment for all three panels ------------------------
   Every row is a single bordered control. Whatever sits inside it — an
   icon, a dial code, a number, a reveal button — is flush and borderless,
   so sign-in, sign-up and reset all read the same. */

.wh-ui .login-signup-form .input-group {
    position: relative;
    display: flex;
    flex-wrap: nowrap;
    align-items: stretch;
    background: var(--wh-surface);
    border: 1px solid var(--wh-line-strong);
    border-radius: var(--wh-r-sm);
    /* Not clipped: the country combobox drops its popup out of this row.
       The children are transparent, so nothing needs trimming anyway. */
    transition: border-color var(--wh-dur) var(--wh-ease),
        box-shadow var(--wh-dur) var(--wh-ease);
}

.wh-ui .login-signup-form .input-group:focus-within {
    border-color: var(--wh-primary);
    box-shadow: 0 0 0 3px var(--wh-primary-ring);
}

.wh-ui .login-signup-form .input-group.wh-invalid-group {
    border-color: var(--wh-danger);
    box-shadow: 0 0 0 3px rgba(221, 51, 51, 0.14);
}

/* The single height every control in these forms shares. */
.wh-ui .login-signup-form .form-control,
.wh-ui .login-signup-form .wh-dial-select {
    height: 52px;
    min-height: 52px;
    flex: 1 1 auto;
    width: auto;
    min-width: 0;
    padding: 0 1rem;
    font-size: var(--wh-t-base);
    background: transparent;
    border: 0;
    border-radius: 0;
    text-indent: 0;
    box-shadow: none;
}

.wh-ui .login-signup-form .form-control:focus,
.wh-ui .login-signup-form .wh-dial-select:focus {
    border: 0;
    box-shadow: none;
}

/* The icon sits inside the field rather than in a Bootstrap 4 add-on. */
.wh-ui .login-signup-form .input-group-merge {
    display: flex;
}

.wh-ui .login-signup-form .input-icon ~ .form-control {
    padding-left: 2.9rem;
}

/* --- Searchable select ----------------------------------------------
   Built by WH.ui.searchableSelect over a native <select>, for lists too
   long to scan. The native control stays in the form and stays the source
   of truth; without JavaScript it is simply what the user gets. */

.wh-select {
    position: relative;
    display: flex;
    min-width: 0;
}

.wh-select__native {
    /* Present for the form, absent from the tab order and the a11y tree. */
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    border: 0;
}

.wh-select__button {
    display: flex;
    align-items: center;
    width: 100%;
    min-width: 0;
    padding: 0 1.75rem 0 1rem;
    font: inherit;
    text-align: left;
    color: var(--wh-fg);
    background: transparent;
    border: 0;
    cursor: pointer;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2355606e'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.55rem center;
    background-size: 14px;
}

.wh-select__popup {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    z-index: 1200;
    width: max(100%, 17rem);
    max-width: min(22rem, calc(100vw - 2rem));
    background: var(--wh-surface);
    border: 1px solid var(--wh-line-strong);
    border-radius: var(--wh-r-md);
    box-shadow: var(--wh-shadow-lg);
    overflow: hidden;
}

.wh-select__search {
    display: block;
    width: 100%;
    height: 42px;
    padding: 0 0.85rem;
    font: inherit;
    font-size: var(--wh-t-base);
    color: var(--wh-fg);
    background: var(--wh-surface);
    border: 0;
    border-bottom: 1px solid var(--wh-line);
    border-radius: 0;
}

.wh-select__search:focus {
    outline: none;
    background: var(--wh-primary-softer);
}

.wh-select__list {
    max-height: 15rem;
    margin: 0;
    padding: var(--wh-s1);
    overflow-y: auto;
    list-style: none;
}

.wh-select__option {
    padding: 0.5rem 0.65rem;
    font-size: var(--wh-t-base);
    line-height: 1.35;
    color: var(--wh-fg);
    border-radius: var(--wh-r-xs);
    cursor: pointer;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.wh-select__option.is-active {
    background: var(--wh-surface-3);
}

.wh-select__option.is-selected {
    font-weight: 600;
    color: var(--wh-primary-strong);
    background: var(--wh-primary-softer);
}

.wh-select__empty {
    margin: 0;
    padding: var(--wh-s4);
    font-size: var(--wh-t-sm);
    text-align: center;
    color: var(--wh-fg-faint);
}

/* Inside the sign-in rows the combobox is the leading segment of the
   number field, so it inherits that row's height and divider. */
.wh-ui .login-signup-form .wh-select {
    flex: 0 0 auto;
    width: 8.25rem;
    max-width: 45%;
    border-right: 1px solid var(--wh-line);
}

.wh-ui .login-signup-form .wh-select__button {
    height: 52px;
}

/* The dial code is the leading segment of the number, not a control of its
   own: fixed and narrow, with a hairline separating it from the digits. */
.wh-ui .login-signup-form .wh-dial-select {
    flex: 0 0 auto;
    width: 8.25rem;
    max-width: 45%;
    padding-right: 1.75rem;
    border-right: 1px solid var(--wh-line);
    background-position: right 0.55rem center;
    cursor: pointer;
}

/* The theme sizes this box to a 3.4rem-tall field and pads the glyph inside
   it by 15px; both are reset so the icon lines up with our field height. */
.wh-ui .login-signup-form .input-icon {
    position: absolute;
    top: 50%;
    bottom: auto;
    left: 0;
    z-index: 2;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 1.2rem;
    color: var(--wh-fg-faint);
    pointer-events: none;
}

.wh-ui .login-signup-form .input-icon span {
    position: static;
    height: auto;
    padding: 0;
    font-size: 0.95rem;
    color: inherit;
}

.wh-reveal-btn {
    position: absolute;
    top: 50%;
    right: 0.5rem;
    z-index: 2;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    color: var(--wh-fg-faint);
    background: transparent;
    border: 0;
    border-radius: var(--wh-r-xs);
    cursor: pointer;
}

.wh-reveal-btn:hover {
    color: var(--wh-fg);
    background: var(--wh-surface-3);
}

.wh-ui .login-signup-form .form-control[type="password"],
.wh-ui .login-signup-form .form-control[type="text"]#password1,
.wh-ui .login-signup-form .form-control[type="text"]#password2 {
    padding-right: 3rem;
}

.wh-ui .login-signup-form .form-check {
    display: flex;
    align-items: flex-start;
    gap: var(--wh-s2);
    margin-bottom: var(--wh-s4);
}

.wh-ui .login-signup-form .form-check-input {
    position: static;
    float: none;
    width: 1.05rem;
    height: 1.05rem;
    margin: 0.15rem 0 0;
    accent-color: var(--wh-primary-strong);
}

.wh-ui .login-signup-form .form-check-label {
    margin: 0;
    font-weight: 400;
    font-size: var(--wh-t-sm);
    color: var(--wh-fg-soft);
}

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

.wh-landing .hero-content-left {
    max-width: 34rem;
}

.wh-landing .hero-content-left h1 {
    margin-bottom: var(--wh-s4);
    line-height: 1.15;
    text-wrap: balance;
    text-shadow: 0 2px 18px rgba(0, 0, 0, 0.35);
}

.wh-hero-sub {
    max-width: 30rem;
    margin: 0 0 var(--wh-s5);
    font-size: var(--wh-t-lg);
    line-height: 1.6;
    color: rgba(255, 255, 255, 0.92);
}

.wh-hero-cta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--wh-s4);
}

/* The video link keeps its play affordance but stops looking like a button
   competing with the primary action beside it. */
.wh-hero-play {
    display: inline-flex;
    align-items: center;
    gap: var(--wh-s3);
    font-size: var(--wh-t-base);
    font-weight: 600;
    color: #fff;
    text-decoration: none;
}

.wh-hero-play:hover {
    color: #fff;
    text-decoration: underline;
}

.wh-hero-play__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    font-size: 0.9rem;
    color: #fff;
    background: rgba(255, 255, 255, 0.16);
    border: 1px solid rgba(255, 255, 255, 0.35);
    border-radius: 50%;
    transition: background-color var(--wh-dur) var(--wh-ease);
}

.wh-hero-play:hover .wh-hero-play__icon {
    background: rgba(255, 255, 255, 0.28);
}

.wh-hero-proof {
    display: flex;
    flex-wrap: wrap;
    gap: var(--wh-s3) var(--wh-s5);
    margin: var(--wh-s5) 0 0;
    padding: 0;
    list-style: none;
}

.wh-hero-proof li {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    font-size: var(--wh-t-sm);
    font-weight: 600;
    color: rgba(255, 255, 255, 0.92);
}

.wh-hero-proof i {
    color: #8ef0a5;
}

/* The theme flattened the whole photograph under a near-opaque green wash so
   the headline would clear it. Ramp the scrim instead — heavy under the copy,
   light by the time it reaches the right — and give the one line that sits
   lowest its own backing, so the picture stays a picture. */
.wh-landing #home.gradient-overlay::before {
    background-image: linear-gradient(100deg,
            rgba(1, 52, 18, 0.88) 0%,
            rgba(1, 58, 20, 0.78) 46%,
            rgba(2, 88, 30, 0.46) 62%,
            rgba(3, 116, 38, 0.22) 100%);
}

/* Stacked columns put the copy over the full width, so the ramp turns vertical
   rather than leaving the lower half of it on the bright side of the photo. */
@media (max-width: 767.98px) {
    .wh-landing #home.gradient-overlay::before {
        background-image: linear-gradient(180deg,
                rgba(1, 52, 18, 0.86) 0%,
                rgba(1, 58, 20, 0.82) 55%,
                rgba(2, 88, 30, 0.78) 100%);
    }
}

.wh-hero-trust {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.3rem;
    margin: var(--wh-s5) 0 0;
    padding: var(--wh-s3) var(--wh-s4);
    font-size: var(--wh-t-sm);
    line-height: 1.5;
    color: rgba(255, 255, 255, 0.94);
    background: rgba(0, 0, 0, 0.24);
    border: 1px solid rgba(255, 255, 255, 0.22);
    border-radius: var(--wh-r-md);
}

.wh-hero-trust__figure {
    font-size: var(--wh-t-base);
    font-weight: 700;
    color: #fff;
    font-variant-numeric: tabular-nums;
}

.wh-hero-trust + .wh-hero-proof {
    margin-top: var(--wh-s4);
}

/* The navbar rides straight on the photograph, so with the wash lightened it
   carries its own scrim instead of borrowing the section's. */
.wh-landing .header nav.navbar {
    background-image: linear-gradient(180deg,
            rgba(1, 34, 12, 0.66) 0%,
            rgba(1, 34, 12, 0.24) 62%,
            rgba(1, 34, 12, 0) 100%);
}

/* The theme's affix colour is an invalid 7-digit hex, so the bar dropped to
   fully transparent once it left the hero — white links on the white band. */
.wh-landing .header nav.navbar.affix,
.wh-landing .navbar-collapse.show {
    background-image: none;
    background-color: var(--wh-primary-darker);
}

.wh-landing .navbar-collapse.show {
    padding: var(--wh-s3) var(--wh-s4);
    border-radius: var(--wh-r-md);
}

/* Links sit on the brightest corner of the photo, so they get a scrim the size
   of the links rather than one the size of the picture. */
@media (min-width: 992px) {
    .wh-landing .navbar-nav.menu {
        padding: 0.4rem 1.1rem;
        background: rgba(1, 34, 12, 0.62);
        border: 1px solid rgba(255, 255, 255, 0.16);
        border-radius: 999px;
    }

    .wh-landing .navbar.affix .navbar-nav.menu {
        background: transparent;
        border-color: transparent;
    }
}

/* Below the breakpoint the links become the toggler, which lands on the same
   bright corner — so it takes the same scrim, sized to itself. */
@media (max-width: 991.98px) {
    .wh-landing .navbar-toggler {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 44px;
        height: 44px;
        padding: 0;
        background: rgba(1, 34, 12, 0.62);
        border: 1px solid rgba(255, 255, 255, 0.16);
        border-radius: 50%;
    }

    .wh-landing .navbar.affix .navbar-toggler {
        background: transparent;
        border-color: transparent;
    }
}

/* The sign-in panel's own heading, in place of the marketing paragraph. */
.wh-panel-title {
    margin: 0 0 0.35rem;
    font-size: var(--wh-t-lg);
    font-weight: 700;
    color: var(--wh-fg);
}

.wh-panel-sub {
    margin: 0 0 var(--wh-s4);
    font-size: var(--wh-t-sm);
    color: var(--wh-fg-soft);
}

/* --------------------------------------------------- shared section head */

.wh-section-head {
    max-width: 40rem;
    margin: 0 0 var(--wh-s6);
}

.wh-section-head h2 {
    margin: 0 0 var(--wh-s2);
    font-size: var(--wh-t-2xl);
    font-weight: 700;
    color: var(--wh-fg);
    text-wrap: balance;
}

.wh-section-head p {
    margin: 0;
    font-size: var(--wh-t-md);
    line-height: 1.6;
    color: var(--wh-fg-soft);
}

/* ------------------------------------------------------------ figures --- */

.wh-stats-band {
    padding: var(--wh-s7) 0;
    background: var(--wh-surface-2);
}

/* Between two and five figures depending on which the chosen source carries,
   so the track is narrow enough for five to sit on one row. */
.wh-stats-grid {
    display: grid;
    gap: var(--wh-s4);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
}

.wh-figure {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--wh-s2);
    padding: var(--wh-s5);
    background: var(--wh-surface);
    border: 1px solid var(--wh-line);
    border-radius: var(--wh-r-lg);
    box-shadow: var(--wh-shadow-sm);
}

.wh-figure__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin-bottom: var(--wh-s2);
    font-size: 1.15rem;
    color: var(--wh-primary-strong);
    background: var(--wh-primary-soft);
    border-radius: var(--wh-r-md);
}

.wh-figure__value {
    font-size: var(--wh-t-3xl);
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.02em;
    color: var(--wh-fg);
    font-variant-numeric: tabular-nums;
}

.wh-figure__label {
    font-size: var(--wh-t-base);
    color: var(--wh-fg-soft);
}

/* -------------------------------------------------------------- why ---- */

.wh-why {
    padding: var(--wh-s7) 0;
}

.wh-why-grid {
    display: grid;
    gap: var(--wh-s4);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
}

.wh-why-card {
    padding: var(--wh-s5);
    background: var(--wh-surface);
    border: 1px solid var(--wh-line);
    border-radius: var(--wh-r-lg);
    box-shadow: var(--wh-shadow-sm);
}

.wh-why-card__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 46px;
    height: 46px;
    margin-bottom: var(--wh-s4);
    font-size: 1.2rem;
    color: var(--wh-primary-strong);
    background: var(--wh-primary-soft);
    border-radius: var(--wh-r-md);
}

.wh-why-card h3 {
    margin: 0 0 var(--wh-s2);
    font-size: var(--wh-t-md);
    font-weight: 700;
    color: var(--wh-fg);
}

.wh-why-card p {
    margin: 0;
    font-size: var(--wh-t-base);
    line-height: 1.65;
    color: var(--wh-fg-soft);
}

/* ---------------------------------------------------------- contact ---- */

.wh-contact {
    padding: var(--wh-s7) 0;
}

.wh-contact-card {
    display: grid;
    gap: var(--wh-s6);
    padding: var(--wh-s6);
    color: #fff;
    background: linear-gradient(160deg, #00a651, #007a04 70%);
    border-radius: var(--wh-r-lg);
    box-shadow: var(--wh-shadow-lg);
}

@media (min-width: 992px) {
    .wh-contact-card {
        grid-template-columns: 1.1fr 1fr;
        align-items: center;
    }
}

.wh-contact-card h2 {
    margin: 0 0 var(--wh-s3);
    font-size: var(--wh-t-2xl);
    font-weight: 700;
    color: #fff;
    text-wrap: balance;
}

.wh-contact-card__text p {
    margin: 0 0 var(--wh-s5);
    font-size: var(--wh-t-md);
    line-height: 1.6;
    color: rgba(255, 255, 255, 0.92);
}

/* On the green card the primary button needs to read as the brighter of the
   two, so it inverts rather than sitting green-on-green. */
.wh-contact-card .wh-btn--primary {
    color: var(--wh-primary-darker);
    background: #fff;
    border-color: #fff;
}

.wh-contact-card .wh-btn--primary:hover {
    color: var(--wh-primary-darker);
    background: #eafaee;
    border-color: #eafaee;
}

.wh-contact-list {
    display: grid;
    gap: var(--wh-s4);
    margin: 0;
    padding: 0;
    list-style: none;
}

.wh-contact-list li {
    display: flex;
    align-items: flex-start;
    gap: var(--wh-s3);
    font-size: var(--wh-t-base);
    line-height: 1.5;
    color: rgba(255, 255, 255, 0.92);
}

.wh-contact-list i {
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.16);
    border-radius: 50%;
}

.wh-contact-list a {
    color: #fff;
    text-decoration: underline;
}

@media (max-width: 575.98px) {
    .wh-hero-cta,
    .wh-hero-cta > * {
        width: 100%;
    }

    .wh-hero-play {
        justify-content: center;
    }

    .wh-contact-card {
        padding: var(--wh-s5) var(--wh-s4);
    }
}

/* ------------------------------------------------- marketing sections --
   Layout and copy stay as the theme wrote them; these correct what was
   broken and bring the details onto the brand. */

/* The step connector ended in a themify glyph, and that font never loads —
   its files are empty. Drawn as a chevron so it needs no icon font. */
.wh-landing .single-work-process .work-process-divider::after {
    content: "";
    font-size: 0;
    right: -24px;
    width: 9px;
    height: 9px;
    border-top: 2px solid #73778c;
    border-right: 2px solid #73778c;
    transform: translateY(-50%) rotate(45deg);
}

/* Step tiles: the theme's flat green square, given the layer's radius. */
.wh-landing .work-process-icon-wrap {
    border-radius: var(--wh-r-md) !important;
}

.wh-landing .work-process-content h5 {
    font-size: var(--wh-t-md);
    font-weight: 700;
    color: var(--wh-fg);
}

.wh-landing .work-process-content p {
    font-size: var(--wh-t-base);
    line-height: 1.6;
    color: var(--wh-fg-soft);
}

/* The footer wordmark is the same dark artwork used everywhere else, sitting
   on navy. Given the white chip the guest pages and donation card use. */
.wh-landing .footer-section .footer-top a[href] > img[alt="footer logo"] {
    display: block;
    padding: 0.5rem 0.9rem;
    background: #fff;
    border-radius: var(--wh-r-md);
}

/* Footer social buttons were a washed-out lilac; on the brand now. */
.wh-landing .social-list li a span {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    padding: 0;
    font-size: 0.95rem;
    color: var(--wh-ink-brand) !important;
    background: #fff !important;
    transition: background-color var(--wh-dur) var(--wh-ease),
        color var(--wh-dur) var(--wh-ease);
}

.wh-landing .social-list li a:hover span {
    color: #fff !important;
    background: var(--wh-primary) !important;
}

/* Newsletter field and button sit on one row rather than overlapping. */
.wh-landing .newsletter-form .input-group {
    display: flex;
    flex-wrap: wrap;
    gap: var(--wh-s2);
}

/* The theme half-rounds each control so they read as one joined pill; now
   they sit apart, so each gets its own full radius. */
.wh-landing .newsletter-form .input-group > input[type="email"].form-control {
    flex: 1 1 200px;
    min-height: 44px;
    border-radius: var(--wh-r-pill);
    text-indent: 0;
}

.wh-landing .newsletter-form .subscribe-btn.solid-btn {
    flex: 0 0 auto;
    min-height: 44px;
    padding: 0.6rem 1.4rem;
    border-radius: var(--wh-r-pill);
    /* The theme's offset shadow read as a stray shape behind the button. */
    box-shadow: none;
}

.wh-landing .newsletter-form .subscribe-btn.solid-btn:hover {
    background: var(--wh-primary-darker);
    border-color: var(--wh-primary-darker);
    color: #fff;
}

/* ------------------------------------------- Bootstrap 4 compatibility --
   The marketing pages were built against Bootstrap 4 and now run on 5.
   These two rules restore the only behaviours their theme depended on. */

/* Bootstrap 4 gave every column `position: relative`; Bootstrap 5 does not.
   The theme's section overlays (.gradient-overlay:before) are absolutely
   positioned with no z-index of their own, so without it the veil paints
   over the hero copy instead of behind it. Restored rather than worked
   around, so any other overlay section keeps its original stacking too. */
.wh-landing .row > [class*="col-"] {
    position: relative;
}

/* .form-group was Bootstrap 4's field wrapper and does not exist in 5. The
   marketing pages still use it, so the spacing it used to get is kept here.
   Scoped to those pages: the dashboard forms space themselves with a grid. */
.wh-landing .form-group {
    margin-bottom: 1rem;
}

/* The landing page's own partner pill, scoped so it cannot reach the one
   inside the donation card. Light for the same reason as the others: the
   Onit wordmark is dark artwork and vanished on the green. */
.wh-landing .powered-by {
    color: var(--wh-fg-soft);
    background: var(--wh-surface);
    border: 1px solid var(--wh-line);
    box-shadow: var(--wh-shadow-md);
}

.wh-landing .powered-by img {
    vertical-align: middle;
}

/* The dial-code picker is a native select: the platform's own picker on a
   phone, keyboard type-ahead on a desktop, and no plugin behind it. It is
   capped so a long country name cannot crowd out the number beside it. */
.wh-ui .wh-dial-select {
    flex: 0 0 auto;
    width: auto;
    max-width: 42%;
    min-height: 42px;
    padding: 0.5rem 1.9rem 0.5rem 0.75rem;
    font-size: var(--wh-t-base);
    color: var(--wh-fg);
    background-color: var(--wh-surface);
    border: 1px solid var(--wh-line-strong);
    border-radius: var(--wh-r-sm);
    cursor: pointer;
    /* Own chevron: -webkit-appearance:none removes the platform one. */
    -webkit-appearance: none;
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2355606e'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.6rem center;
    background-size: 14px;
}

.wh-ui .wh-dial-select:focus {
    outline: none;
    border-color: var(--wh-primary);
    box-shadow: 0 0 0 3px var(--wh-primary-ring);
}

/* ============================================== 19d. long-form content */

/* The FAQ and the two legal documents. Their job is to be read, so the
   measure is capped and the rhythm comes from the type scale rather than
   from whatever the marketing theme left behind. */
.wh-prose {
    max-width: 68ch;
    font-size: var(--wh-t-md);
    line-height: 1.7;
    color: var(--wh-fg-soft);
}

.wh-prose > * + * {
    margin-top: var(--wh-s4);
}

.wh-prose h1,
.wh-prose h2,
.wh-prose h3,
.wh-prose h4 {
    margin: var(--wh-s6) 0 var(--wh-s3);
    color: var(--wh-fg);
    line-height: 1.3;
    /* Clears the theme's fixed navbar when a heading is jumped to. */
    scroll-margin-top: 90px;
}

.wh-prose > :first-child {
    margin-top: 0;
}

.wh-prose h1 {
    font-size: var(--wh-t-2xl);
    font-weight: 700;
}

.wh-prose h2 {
    font-size: var(--wh-t-xl);
    font-weight: 700;
}

.wh-prose h3 {
    font-size: var(--wh-t-lg);
    font-weight: 600;
}

.wh-prose h4 {
    font-size: var(--wh-t-md);
    font-weight: 600;
}

.wh-prose p {
    margin: 0;
}

.wh-prose ul,
.wh-prose ol {
    margin: var(--wh-s4) 0;
    padding-left: 1.35rem;
}

.wh-prose li + li {
    margin-top: var(--wh-s2);
}

.wh-prose a {
    color: var(--wh-primary-strong);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.wh-prose a:hover {
    color: var(--wh-primary-darker);
}

.wh-prose strong {
    color: var(--wh-fg);
    font-weight: 600;
}

.wh-prose table {
    width: 100%;
    margin: var(--wh-s4) 0;
    border-collapse: collapse;
    font-size: var(--wh-t-base);
}

.wh-prose th,
.wh-prose td {
    padding: var(--wh-s2) var(--wh-s3);
    border: 1px solid var(--wh-line);
    text-align: left;
}

.wh-prose hr {
    margin: var(--wh-s6) 0;
    border: 0;
    border-top: 1px solid var(--wh-line);
}

/* These pages inherited the landing page's hero, but with no content in it —
   a tall empty band that pushed the document itself below the fold. Trimmed
   to a branded header strip. */
.wh-doc-page .hero-equal-height,
.wh-doc-page section.home {
    min-height: 0;
    height: auto;
    /* Enough for the fixed navbar to clear the wave shape below it. */
    padding-top: 150px;
    padding-bottom: 40px;
}

.wh-doc-page .hero-content-left {
    padding-bottom: 0 !important;
    margin-top: 0 !important;
}

.wh-doc-page .shape-bottom {
    margin-top: -1px;
}

/* A document's own contents list, built from its headings by WH.ui.toc(). */
.wh-toc {
    position: sticky;
    top: 96px;
    padding: var(--wh-s4);
    background: var(--wh-surface);
    border: 1px solid var(--wh-line);
    border-radius: var(--wh-r-md);
}

.wh-toc__title {
    margin: 0 0 var(--wh-s3);
    font-size: var(--wh-t-xs);
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--wh-fg-faint);
}

.wh-toc ol {
    margin: 0;
    padding: 0;
    list-style: none;
    display: grid;
    gap: var(--wh-s2);
}

.wh-toc a {
    display: block;
    font-size: var(--wh-t-sm);
    line-height: 1.45;
    color: var(--wh-fg-soft);
    text-decoration: none;
}

.wh-toc a:hover {
    color: var(--wh-primary-strong);
}

/* Two columns on a wide screen: contents beside the document. */
.wh-doc {
    display: grid;
    gap: var(--wh-s5);
    align-items: start;
}

@media (min-width: 992px) {
    .wh-doc {
        grid-template-columns: 250px minmax(0, 1fr);
        gap: var(--wh-s6);
    }
}

@media (max-width: 991.98px) {
    /* Stacked above the document on a phone, so it is capped rather than
       filling the screen before the reader reaches any of the text. */
    .wh-toc {
        position: static;
        max-height: 220px;
        overflow-y: auto;
    }
}

/* Bootstrap 5 accordion, as used by the FAQ. */
.wh-ui .accordion {
    display: grid;
    gap: var(--wh-s2);
}

.wh-ui .accordion-item {
    background: var(--wh-surface);
    border: 1px solid var(--wh-line);
    border-radius: var(--wh-r-md) !important;
    overflow: hidden;
}

.wh-ui .accordion-button {
    padding: var(--wh-s4);
    font-size: var(--wh-t-md);
    font-weight: 600;
    color: var(--wh-fg);
    background: var(--wh-surface);
    box-shadow: none;
}

.wh-ui .accordion-button:not(.collapsed) {
    color: var(--wh-primary-strong);
    background: var(--wh-primary-softer);
    box-shadow: none;
}

.wh-ui .accordion-button:focus {
    border-color: transparent;
    box-shadow: 0 0 0 3px var(--wh-primary-ring);
}

.wh-ui .accordion-body {
    padding: 0 var(--wh-s4) var(--wh-s4);
    font-size: var(--wh-t-base);
    line-height: 1.65;
    color: var(--wh-fg-soft);
}

.wh-ui .accordion-body ul {
    margin: 0;
    padding-left: 1.2rem;
}

.wh-ui .accordion-body li + li {
    margin-top: var(--wh-s2);
}

/* ================================================= 20. a11y and motion */

/* First tab stop on every page: jump the sidebar and header entirely. */
.wh-skip {
    position: absolute;
    top: -100px;
    left: var(--wh-s4);
    z-index: 2000;
    padding: 0.65rem 1rem;
    background: var(--wh-primary-strong);
    color: #fff;
    font-weight: 600;
    border-radius: 0 0 var(--wh-r-sm) var(--wh-r-sm);
    transition: top var(--wh-dur) var(--wh-ease);
}

.wh-skip:focus {
    top: 0;
    color: #fff;
}

.wh-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.wh-ui :focus-visible {
    outline: 3px solid var(--wh-primary-ring);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    .wh-ui *,
    .wh-ui *::before,
    .wh-ui *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ============================================================ 21. print */

@media print {
    .wh-ui .main-sidebar,
    .wh-ui .main-header,
    .wh-ui .main-footer,
    .fab_phone,
    .wabtn,
    .wh-poweredby,
    .wh-toolbar,
    .wh-page-head__actions,
    .wh-table__actions,
    .dt-buttons,
    #back-to-top {
        display: none !important;
    }

    .wh-ui .main-content {
        margin: 0 !important;
        padding: 0 !important;
    }

    .wh-card,
    .wh-stat {
        border: 1px solid #ccc;
        box-shadow: none;
        break-inside: avoid;
    }

    .wh-table thead th {
        position: static;
    }
}
