/* ==========================================================================
   Hostel Washing Management — design system
   Layered on top of Bootstrap 5. Only tokens and component polish live here;
   layout comes from Bootstrap's grid and utilities.
   ========================================================================== */

/* Design intent: restraint. One accent colour used sparingly, generous
   whitespace, hairline borders instead of heavy fills, and typography doing
   most of the work. Saturated gradients are reserved for the marketing pages;
   the app itself stays calm so the numbers stand out. */

:root {
    /* A true blue rather than indigo: brighter, reads as "utility software you
       can trust" rather than "consumer app", which is the register the hostel
       poster is already in. */
    --hw-primary: #2563eb;
    --hw-primary-rgb: 37, 99, 235;
    --hw-primary-soft: #eff5ff;
    --hw-primary-hover: #1d4ed8;
    --hw-primary-ink: #1e40af;

    --hw-ink: #0f172a;
    --hw-body: #3f4a5c;
    --hw-muted: #6b7688;
    --hw-faint: #98a2b3;

    --hw-surface: #ffffff;
    --hw-surface-2: #fbfcfe;
    --hw-canvas: #f7f8fb;
    --hw-border: #e8ecf2;
    --hw-border-strong: #d3dae4;

    --hw-success: #0f9d68;
    --hw-warning: #d97706;
    --hw-danger: #dc2626;

    /* Surface/foreground pairs, after shadcn's convention: the base token is the
       fill, the -fg token is whatever sits on top of it. Having both named means
       a tinted badge or pill never has to guess its own text colour. */
    --hw-on-primary: #ffffff;
    --hw-success-soft: #e7f7f0;
    --hw-success-fg: #0b7a51;
    --hw-warning-soft: #fef4e6;
    --hw-warning-fg: #b45309;
    --hw-danger-soft: #fdeaea;
    --hw-danger-fg: #b91c1c;
    --hw-neutral-soft: #f1f4f9;
    --hw-neutral-fg: #52607a;

    /* Focus ring, so every control agrees on one. */
    --hw-ring: rgba(var(--hw-primary-rgb), .18);

    /* Illustration palette — one accent plus tints, the Flowbite convention:
       flat fills, no outlines, so an empty state reads as art, not as an icon. */
    --hw-art-1: var(--hw-primary);
    --hw-art-2: #93b4fd;
    --hw-art-3: #d5e3fe;
    --hw-art-4: #eff5ff;
    --hw-art-line: #cbd5e1;

    --hw-radius: 16px;
    --hw-radius-sm: 11px;
    --hw-radius-lg: 22px;

    /* Softer, wider shadows read as depth rather than as a drop-shadow. */
    --hw-shadow-xs: 0 1px 2px rgba(16, 24, 40, .04);
    --hw-shadow-sm: 0 1px 3px rgba(16, 24, 40, .05), 0 1px 2px rgba(16, 24, 40, .03);
    --hw-shadow-md: 0 4px 16px -4px rgba(16, 24, 40, .08), 0 2px 6px -2px rgba(16, 24, 40, .04);
    --hw-shadow-lg: 0 20px 44px -12px rgba(16, 24, 40, .14), 0 4px 12px -4px rgba(16, 24, 40, .06);

    --hw-sidebar-width: 264px;

    --bs-primary: var(--hw-primary);
    --bs-link-color: var(--hw-primary);
    --bs-link-hover-color: var(--hw-primary-hover);
    --bs-body-color: var(--hw-body);
    --bs-body-bg: var(--hw-canvas);
    --bs-border-color: var(--hw-border);
    --bs-border-radius: var(--hw-radius-sm);
}

/* --------------------------------------------------------------------------
   Base
   -------------------------------------------------------------------------- */

body {
    background-color: var(--hw-canvas);
    color: var(--hw-body);
    font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;
    font-size: .9375rem;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
    color: var(--hw-ink);
    font-weight: 650;
    letter-spacing: -.011em;
}

a { text-decoration: none; }
a:hover { text-decoration: none; }

.text-muted { color: var(--hw-muted) !important; }
.text-faint { color: var(--hw-faint) !important; }

.text-mono {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
}

.fw-650 { font-weight: 650; }

.page-title {
    font-size: 1.5rem;
    margin-bottom: .125rem;
}

.page-subtitle {
    color: var(--hw-muted);
    font-size: .9375rem;
    margin-bottom: 0;
}

/* Accessible focus ring everywhere, not just on Bootstrap components. */
:focus-visible {
    outline: 3px solid rgba(var(--hw-primary-rgb), .35);
    outline-offset: 2px;
}

.btn:focus,
.form-control:focus,
.form-select:focus,
.form-check-input:focus {
    box-shadow: 0 0 0 3px var(--hw-ring);
    border-color: rgba(var(--hw-primary-rgb), .5);
}

/* --------------------------------------------------------------------------
   App shell: fixed sidebar + fluid main column
   -------------------------------------------------------------------------- */

.app-shell { min-height: 100vh; }

.app-sidebar {
    background: var(--hw-surface);
    border-right: 1px solid var(--hw-border);
    width: var(--hw-sidebar-width);
    display: flex;
    flex-direction: column;
}

@media (min-width: 992px) {
    .app-sidebar {
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        z-index: 1030;
    }

    .app-main { margin-left: var(--hw-sidebar-width); }
}

.app-main {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

.app-content {
    flex: 1 1 auto;
    padding: 1.5rem 1.25rem 3rem;
}

@media (min-width: 768px) {
    .app-content { padding: 2rem 2rem 3.5rem; }
}

/* Brand block at the top of the sidebar */
.app-brand {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: 1.25rem 1.25rem;
    border-bottom: 1px solid var(--hw-border);
    color: var(--hw-ink);
}

.app-brand:hover { color: var(--hw-ink); }

.app-brand-mark {
    width: 40px;
    height: 40px;
    border-radius: 11px;
    background: linear-gradient(135deg, var(--hw-primary), #3b82f6);
    color: #fff;
    display: grid;
    place-items: center;
    font-size: 1.15rem;
    flex: 0 0 auto;
    box-shadow: 0 4px 10px rgba(var(--hw-primary-rgb), .28);
}

.app-brand-text { line-height: 1.2; min-width: 0; }
.app-brand-title { font-weight: 700; font-size: .9375rem; display: block; }
.app-brand-sub { font-size: .75rem; color: var(--hw-muted); }

/* Navigation */
.app-nav {
    padding: 1rem .75rem;
    overflow-y: auto;
    flex: 1 1 auto;
    /* A flex item defaults to min-height:auto, which refuses to shrink below its
       content. Without this the nav pushes the footer down and the last links
       (Notifications, Settings, My profile) end up underneath it, unclickable,
       on any screen short enough to matter. */
    min-height: 0;
    scrollbar-width: thin;
}

.app-nav::-webkit-scrollbar { width: 5px; }
.app-nav::-webkit-scrollbar-track { background: transparent; }
.app-nav::-webkit-scrollbar-thumb { background: var(--hw-line); border-radius: 999px; }

.app-nav-label {
    font-size: .6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .07em;
    color: var(--hw-faint);
    padding: 0 .75rem;
    margin: 1.25rem 0 .5rem;
}

.app-nav-label:first-child { margin-top: 0; }

.app-nav-link {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .625rem .75rem;
    border-radius: var(--hw-radius-sm);
    color: var(--hw-body);
    font-weight: 500;
    margin-bottom: .125rem;
    transition: background-color .12s ease, color .12s ease;
}

.app-nav-link i { font-size: 1.05rem; width: 1.25rem; text-align: center; flex: 0 0 auto; }

.app-nav-link:hover {
    background: var(--hw-canvas);
    color: var(--hw-ink);
}

.app-nav-link.active {
    background: var(--hw-primary-soft);
    color: var(--hw-primary);
    font-weight: 600;
}

.app-nav-link .badge { margin-left: auto; }

.app-sidebar-footer {
    border-top: 1px solid var(--hw-border);
    padding: .875rem;
}

/* Top bar */
.app-topbar {
    background: rgba(255, 255, 255, .88);
    backdrop-filter: saturate(180%) blur(12px);
    border-bottom: 1px solid var(--hw-border);
    padding: .75rem 1.25rem;
    position: sticky;
    top: 0;
    z-index: 1020;
}

@media (min-width: 768px) {
    .app-topbar { padding: .75rem 2rem; }
}

/* --------------------------------------------------------------------------
   Cards
   -------------------------------------------------------------------------- */

.card {
    border: 1px solid var(--hw-border);
    border-radius: var(--hw-radius);
    background: var(--hw-surface);
    box-shadow: var(--hw-shadow-sm);
}

/* Card headers read as small blue eyebrow labels rather than bold black titles.
   The card's own content is the thing being read; the header only says which
   card you are in, so it should be quieter than everything under it — and being
   the accent colour ties the whole page together. */
.card-header {
    background: transparent;
    border-bottom: 1px solid var(--hw-border);
    padding: 1.125rem 1.25rem .875rem;
    font-size: .6875rem;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--hw-primary);
}

/* Links and controls that live in a header keep their own case and size. */
.card-header a:not(.btn),
.card-header .live-dot,
.card-header .small { text-transform: none; letter-spacing: 0; }

.card-header a:not(.btn) { font-size: .8125rem; font-weight: 620; }

.card-body { padding: 1.25rem; }
.card-footer { background: transparent; border-top: 1px solid var(--hw-border); padding: .875rem 1.25rem; }

/* --------------------------------------------------------------------------
   Stat tiles.

   Quiet by default: the figure carries the weight and the accent is a small
   icon chip, not a coloured card. A tile only lifts on hover when it links
   somewhere, so decoration always matches behaviour.
   -------------------------------------------------------------------------- */

.stat-card {
    display: block;
    height: 100%;
    color: inherit;
    border-radius: var(--hw-radius);
    transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
}

a.stat-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--hw-shadow-md);
    border-color: var(--hw-border-strong);
    color: inherit;
}

/* Stat tile, after the TailAdmin pattern: the icon sits on its own line, the
   label reads underneath it, and the number carries a status pill on the same
   baseline. Reading order becomes what -> how many -> is it a problem, rather
   than a number competing with its own icon for the top-left corner. */
.stat-card .card-body { padding: 1.125rem 1.25rem 1.125rem; }

.stat-top { display: flex; align-items: center; gap: .6875rem; margin-bottom: .875rem; }

.stat-icon {
    width: 34px;
    height: 34px;
    border-radius: 10px;
    display: grid;
    place-items: center;
    font-size: .95rem;
    flex: 0 0 auto;
    background: var(--hw-neutral-soft);
    color: var(--hw-neutral-fg);
}

.stat-figure {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: .625rem;
}

.stat-figure .sparkline { flex: 1 1 auto; max-width: 7rem; margin-left: auto; }

.stat-value {
    font-size: 1.9375rem;
    font-weight: 700;
    color: var(--hw-ink);
    line-height: 1;
    letter-spacing: -.032em;
    font-variant-numeric: tabular-nums;
}

.stat-label {
    font-size: .8125rem;
    color: var(--hw-muted);
    font-weight: 550;
}

.stat-hint { font-size: .72rem; color: var(--hw-faint); margin: .3rem 0 0; }

/* The pill that says whether the number needs acting on. */
.stat-pill {
    display: inline-flex;
    align-items: center;
    gap: .25rem;
    padding: .2rem .5rem;
    border-radius: 999px;
    font-size: .7rem;
    font-weight: 640;
    line-height: 1.4;
    white-space: nowrap;
    background: var(--hw-neutral-soft);
    color: var(--hw-neutral-fg);
}

.stat-pill.is-attention { background: var(--hw-warning-soft); color: var(--hw-warning-fg); }
.stat-pill.is-good      { background: var(--hw-success-soft); color: var(--hw-success-fg); }
.stat-pill.is-bad       { background: var(--hw-danger-soft);  color: var(--hw-danger-fg); }

/* Draws the eye to a queue that actually needs work. */
.stat-card.is-actionable {
    border-color: rgba(var(--hw-primary-rgb), .28);
    background:
        linear-gradient(180deg, rgba(var(--hw-primary-rgb), .045), transparent 60%),
        var(--hw-surface);
}

.stat-card.is-actionable .stat-value { color: var(--hw-primary-ink); }

/* --------------------------------------------------------------------------
   Toolbar above a table: title on the left, filters on the right
   -------------------------------------------------------------------------- */

.toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    margin-bottom: 1rem;
}

.toolbar-title { font-size: 1.0625rem; font-weight: 660; color: var(--hw-ink); margin: 0; }
.toolbar-sub   { font-size: .8125rem; color: var(--hw-muted); margin: 0; }

/* Pill tabs used for status filters */
.nav-pills .nav-link {
    color: var(--hw-muted);
    font-weight: 570;
    font-size: .8375rem;
    padding: .4rem .8rem;
    border-radius: 9px;
    border: 1px solid transparent;
}

.nav-pills .nav-link:hover { background: var(--hw-surface); color: var(--hw-ink); border-color: var(--hw-border); }

.nav-pills .nav-link.active {
    background: var(--hw-ink);
    color: #fff;
    border-color: var(--hw-ink);
}

.nav-pills .nav-link.active .badge { background: rgba(255, 255, 255, .2) !important; color: #fff !important; }

/* --------------------------------------------------------------------------
   Tables — lighter rules, roomier cells, a sticky header on tall lists
   -------------------------------------------------------------------------- */

.table-card > .table-scroll { max-height: none; }

.table > thead > tr > th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--hw-surface-2);
}

.cell-primary { font-weight: 620; color: var(--hw-ink); }
.cell-muted   { color: var(--hw-muted); font-size: .8375rem; }

/* Identity cell: avatar + name + secondary line */
.who { display: flex; align-items: center; gap: .625rem; min-width: 0; }
.who-name { font-weight: 620; color: var(--hw-ink); line-height: 1.25; }
.who-sub  { font-size: .72rem; color: var(--hw-faint); }

/* Row action buttons stay quiet until you hover the row */
.table-hover > tbody > tr:hover .row-action { opacity: 1; }
.row-action { opacity: .75; transition: opacity .12s ease; }

/* ==========================================================================
   USAGE — the dashboard headline.

   A quiet white panel with a stroked ring, not a saturated gradient block.
   The number is the loudest thing on the page, which is the point.
   ========================================================================== */

.usage-panel {
    position: relative;
    overflow: hidden;
    background: var(--hw-surface);
    border: 1px solid var(--hw-border);
    border-radius: var(--hw-radius-lg);
    box-shadow: var(--hw-shadow-sm);
}

.usage-panel-body {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 1.75rem;
    padding: 1rem 1.75rem 1.75rem;
}

/* The text column must be allowed to shrink, or `flex-grow` plus a min-width
   makes it claim the whole row and the ring wraps underneath — which is the one
   thing the approved layout does not do. */
.usage-panel-text { flex: 1 1 16rem; min-width: 0; }

@media (max-width: 575px) {
    .usage-panel-body { padding: 1.375rem; gap: 1.25rem; justify-content: center; text-align: center; }
}

/* A single faint wash of accent, so the panel is not clinically flat. */
.usage-panel::before {
    content: "";
    position: absolute;
    inset: 0 auto auto 0;
    width: 340px;
    height: 340px;
    background: radial-gradient(circle at 0 0, rgba(var(--hw-primary-rgb), .07), transparent 70%);
    pointer-events: none;
}

.usage-panel > * { position: relative; }

/* Ring */
.usage-ring { width: 148px; height: 148px; flex: 0 0 auto; }

@media (max-width: 575px) { .usage-ring { width: 128px; height: 128px; } }

.usage-ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }

.usage-ring-track { fill: none; stroke: var(--hw-border); stroke-width: 9; }

.usage-ring-fill {
    fill: none;
    stroke: var(--hw-primary);
    stroke-width: 9;
    stroke-linecap: round;
    transition: stroke-dashoffset .7s cubic-bezier(.22, 1, .36, 1), stroke .3s ease;
}

.usage-ring.is-full .usage-ring-fill { stroke: var(--hw-danger); }
.usage-ring.is-warning .usage-ring-fill { stroke: var(--hw-warning); }

/* The headline figure. Display-sized, because on a page whose whole job is
   "how many washes have I got left", the number is the content. */
.usage-figure { display: flex; align-items: baseline; gap: .35rem; line-height: 1; }

.usage-figure-value {
    font-size: 4.25rem;
    font-weight: 780;
    color: var(--hw-primary);
    letter-spacing: -.045em;
    font-variant-numeric: tabular-nums;
}

.usage-figure-of {
    font-size: 1.875rem;
    font-weight: 620;
    color: var(--hw-faint);
    letter-spacing: -.02em;
}

/* The allowance fraction in a sidebar panel. Smaller than the dashboard's hero
   figure, but still the loudest thing in its own card. */
/* "How it works": an accent mark, then a numbered list whose numerals are the
   accent colour, so the steps read as a sequence rather than as prose. */
.how-head {
    display: flex;
    align-items: center;
    gap: .5rem;
    font-size: .6875rem;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--hw-primary);
    margin-bottom: .875rem;
}

.how-mark {
    width: 26px;
    height: 26px;
    border-radius: 8px;
    display: grid;
    place-items: center;
    background: var(--hw-primary-soft);
    color: var(--hw-primary);
    font-size: .875rem;
}

.how-list {
    margin: 0;
    padding-left: 1.15rem;
    font-size: .8125rem;
    color: var(--hw-body);
    display: grid;
    gap: .4rem;
}

.how-list::marker { color: var(--hw-primary); }
.how-list li::marker { color: var(--hw-primary); font-weight: 700; }

/* The account block at the foot of the sidebar, as a card. */
.sidebar-account {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .5rem .625rem;
    border: 1px solid var(--hw-border);
    border-radius: var(--hw-radius-sm);
    background: var(--hw-surface);
}

.sidebar-account-mark { color: var(--hw-faint); font-size: .875rem; flex: 0 0 auto; }

/* The cycle panel: a label, the fraction at display size, then the four
   supporting figures as a list with tinted marks. */
/* Countdown in the unlock queue, with unit labels under the digits. */
.queue-countdown { text-align: right; }

.queue-countdown-label { font-size: .7rem; color: var(--hw-muted); }

.queue-countdown-clock {
    font-weight: 700;
    font-size: 1.0625rem;
    font-variant-numeric: tabular-nums;
    letter-spacing: .01em;
    color: var(--hw-primary);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* Three evenly spread labels under a hh:mm:ss clock. */
.queue-countdown-units {
    display: flex;
    justify-content: space-between;
    font-size: .5625rem;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--hw-faint);
    padding: 0 .1rem;
}

/* Day strip with scroll arrows. */
.day-strip-wrap { position: relative; display: flex; align-items: center; gap: .375rem; }

/* With arrows present the scrollbar is redundant and reads as clutter. */
.day-strip-wrap .day-strip { scrollbar-width: none; padding-bottom: 0; }
.day-strip-wrap .day-strip::-webkit-scrollbar { display: none; }
/* A flex item defaults to min-width:auto, which means it refuses to shrink
   below its own content — so the strip's own overflow-x never engaged and the
   row pushed the whole page sideways instead. This is what lets it scroll. */
.day-strip-wrap .day-strip { flex: 1 1 auto; min-width: 0; }

.day-strip-wrap .day-strip-nav { flex: 0 0 auto; }

.day-strip-nav {
    flex: 0 0 auto;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    border: 1px solid var(--hw-border);
    background: var(--hw-surface);
    color: var(--hw-muted);
    display: grid;
    place-items: center;
    font-size: .8rem;
    box-shadow: var(--hw-shadow-xs);
    transition: color .12s ease, border-color .12s ease;
}

.day-strip-nav:hover { color: var(--hw-primary); border-color: var(--hw-primary); }

/* The unlock queue while somebody is actually waiting. */
.queue-card.is-live {
    background:
        linear-gradient(90deg, rgba(var(--hw-primary-rgb), .055), transparent 42%),
        var(--hw-surface);
    border-color: rgba(var(--hw-primary-rgb), .28);
    box-shadow: inset 4px 0 0 var(--hw-primary), var(--hw-shadow-sm);
}

/* The countdown beside each waiting resident. */
.queue-card .countdown-clock,
.queue-card [data-countdown-clock] {
    color: var(--hw-primary);
    font-variant-numeric: tabular-nums;
    letter-spacing: -.01em;
}

/* How long a resident has been waiting for a decision. Neutral for the first
   day, warm after that, red past two — the warden should be able to see a
   neglected registration without reading a single date. */
.waited {
    display: inline-grid;
    justify-items: start;
    padding: .3rem .625rem;
    border-radius: 9px;
    background: var(--hw-neutral-soft);
    line-height: 1.25;
}

.waited strong { font-size: .875rem; font-weight: 700; color: var(--hw-neutral-fg); }
.waited span   { font-size: .6875rem; color: var(--hw-faint); }

.waited.is-due  { background: var(--hw-warning-soft); }
.waited.is-due strong { color: var(--hw-warning-fg); }

.waited.is-late { background: var(--hw-danger-soft); }
.waited.is-late strong { color: var(--hw-danger-fg); }

/* Email and phone stacked in one table cell, each with its own mark so the two
   are told apart without a column heading for each. */
.contact-line {
    display: flex;
    align-items: center;
    gap: .45rem;
    font-size: .8125rem;
    color: var(--hw-body);
    line-height: 1.6;
}

.contact-line i { color: var(--hw-faint); font-size: .8em; flex: 0 0 auto; }

/* Fee status as three coloured choices rather than a dropdown. Each state
   carries its own tint when selected, so the warden reads the current one
   without opening anything. */
.fee-switch {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: .375rem;
    padding: .25rem;
    border: 1px solid var(--hw-border);
    border-radius: var(--hw-radius-sm);
    background: var(--hw-canvas);
}

.fee-switch input { position: absolute; opacity: 0; width: 0; height: 0; }

.fee-switch label {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .35rem;
    padding: .5rem .375rem;
    border-radius: 8px;
    font-size: .8125rem;
    font-weight: 620;
    color: var(--hw-muted);
    cursor: pointer;
    text-align: center;
    transition: background-color .12s ease, color .12s ease, box-shadow .12s ease;
}

.fee-switch label:hover { color: var(--hw-ink); }

.fee-switch input:checked + label            { background: var(--hw-surface); box-shadow: var(--hw-shadow-xs); }
.fee-switch input:checked + label.is-success { color: var(--hw-success-fg); box-shadow: inset 0 0 0 1px rgba(15, 157, 104, .3), var(--hw-shadow-xs); }
.fee-switch input:checked + label.is-warning { color: var(--hw-warning-fg); box-shadow: inset 0 0 0 1px rgba(217, 119, 6, .3), var(--hw-shadow-xs); }
.fee-switch input:checked + label.is-danger  { color: var(--hw-danger-fg);  box-shadow: inset 0 0 0 1px rgba(220, 38, 38, .3), var(--hw-shadow-xs); }
.fee-switch input:checked + label.is-primary { color: var(--hw-primary);    box-shadow: inset 0 0 0 1px rgba(var(--hw-primary-rgb), .3), var(--hw-shadow-xs); }
.fee-switch input:checked + label.is-info    { color: #0e7490;              box-shadow: inset 0 0 0 1px rgba(14, 116, 144, .3), var(--hw-shadow-xs); }

.fee-switch input:focus-visible + label { outline: 3px solid var(--hw-ring); outline-offset: 1px; }

/* A mark before a detail label, so a column of rows is scannable by icon. */
.detail-icon { color: var(--hw-faint); margin-right: .5rem; font-size: .9em; }

/* The detail identity card carries a photo rather than initials sometimes. */
.identity-avatar img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }

/* The count row above a listing. Four small cards, each a figure with its own
   tinted mark, so a warden reads the shape of the list before the list. */
.count-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: .75rem;
}

.count-card {
    display: flex;
    align-items: center;
    gap: .875rem;
    padding: .875rem 1rem;
    border: 1px solid var(--hw-border);
    border-radius: var(--hw-radius-sm);
    background: var(--hw-surface);
    box-shadow: var(--hw-shadow-xs);
}

.count-icon {
    width: 38px;
    height: 38px;
    border-radius: 11px;
    display: grid;
    place-items: center;
    font-size: 1.05rem;
    flex: 0 0 auto;
}

.count-icon.is-blue  { background: #e8f1ff; color: #2563eb; }
.count-icon.is-green { background: #e6f6ee; color: #0f9d68; }
.count-icon.is-amber { background: #fef3e2; color: #d97706; }
.count-icon.is-red   { background: #fdecec; color: #dc2626; }

.count-value { font-size: 1.375rem; font-weight: 720; color: var(--hw-ink); line-height: 1.05; letter-spacing: -.03em; }
.count-label { font-size: .78rem; color: var(--hw-muted); }

@media (max-width: 767.98px) {
    .count-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Soft status pills. One tint family per state, used everywhere a status is
   shown so the same word never looks different on two screens. */
.badge-soft {
    font-weight: 620;
    border: 1px solid transparent;
}

.badge-soft.is-success   { background: var(--hw-success-soft); color: var(--hw-success-fg); border-color: rgba(15, 157, 104, .22); }
.badge-soft.is-warning   { background: var(--hw-warning-soft); color: var(--hw-warning-fg); border-color: rgba(217, 119, 6, .22); }
.badge-soft.is-danger    { background: var(--hw-danger-soft);  color: var(--hw-danger-fg);  border-color: rgba(220, 38, 38, .22); }
.badge-soft.is-primary   { background: var(--hw-primary-soft); color: var(--hw-primary);    border-color: rgba(var(--hw-primary-rgb), .22); }
.badge-soft.is-secondary,
.badge-soft.is-light     { background: var(--hw-neutral-soft); color: var(--hw-neutral-fg); border-color: var(--hw-border); }
.badge-soft.is-dark      { background: #eef1f6; color: #334155; border-color: #dde3ec; }
.badge-soft.is-info      { background: #e6f4fb; color: #0e7490; border-color: rgba(14, 116, 144, .2); }

/* The identity card. A tinted banner behind the avatar turns a white box into
   something that reads as an account rather than as another panel. */
.identity { position: relative; padding: 0 1.5rem 1.5rem; text-align: center; }

.identity-banner {
    display: block;
    height: 92px;
    margin: 0 -1.5rem 0;
    border-radius: var(--hw-radius) var(--hw-radius) 0 0;
    background:
        radial-gradient(120% 140% at 20% 0%, rgba(var(--hw-primary-rgb), .18), transparent 62%),
        linear-gradient(135deg, #eaf2ff, #dbe8ff);
}

.identity-avatar {
    position: relative;
    display: inline-grid;
    place-items: center;
    width: 96px;
    height: 96px;
    margin-top: -48px;
    border-radius: 50%;
    background: var(--hw-primary-soft);
    color: var(--hw-primary);
    font-size: 1.875rem;
    font-weight: 720;
    letter-spacing: -.02em;
    border: 5px solid var(--hw-surface);
    box-shadow: var(--hw-shadow-md);
}

.identity-check {
    position: absolute;
    right: 0;
    top: .25rem;
    width: 26px;
    height: 26px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: var(--hw-surface);
    color: var(--hw-primary);
    font-size: 1.05rem;
    box-shadow: var(--hw-shadow-sm);
}

.identity-name   { font-size: 1.25rem; margin: .875rem 0 .2rem; }
.identity-handle { font-size: .875rem; color: var(--hw-primary); font-weight: 560; margin: 0 0 .2rem; }
.identity-email  { font-size: .8125rem; color: var(--hw-faint); word-break: break-word; margin: 0 0 1rem; }
.identity-badges { display: flex; flex-wrap: wrap; gap: .5rem; justify-content: center; }

/* A quieter primary: accent colour on a tint rather than a solid fill, for
   actions that matter but are not the main one on the screen. */
.btn-soft {
    background: var(--hw-primary-soft);
    color: var(--hw-primary);
    border: 1px solid rgba(var(--hw-primary-rgb), .18);
    font-weight: 600;
}

.btn-soft:hover { background: #e2ecff; color: var(--hw-primary-hover); border-color: rgba(var(--hw-primary-rgb), .3); }

/* Notification inbox: a summary strip, day headings, and a timeline rail down
   the left connecting each event's icon. */
.notif-summary {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: .875rem 1.125rem;
    border: 1px solid var(--hw-border);
    border-radius: var(--hw-radius);
    background: var(--hw-surface);
    box-shadow: var(--hw-shadow-xs);
}

.notif-summary-counts { display: flex; align-items: center; gap: 0; flex-wrap: wrap; }

.notif-summary-item {
    display: flex;
    align-items: baseline;
    gap: .5rem;
    padding: 0 1.25rem;
    border-left: 1px solid var(--hw-border);
}

.notif-summary-item:first-child { border-left: 0; padding-left: .25rem; }
.notif-summary-item strong { font-size: 1.5rem; font-weight: 720; color: var(--hw-ink); letter-spacing: -.03em; }
.notif-summary-item.is-unread strong { color: var(--hw-primary); }
.notif-summary-item > span { display: grid; font-size: .8125rem; font-weight: 560; color: var(--hw-body); line-height: 1.2; }
.notif-summary-item small { font-size: .6875rem; color: var(--hw-faint); font-weight: 400; }

.notif-summary-side { display: flex; align-items: center; gap: .75rem; }

.notif-day {
    margin: 1.5rem 0 .5rem;
    padding-left: 1.25rem;
    font-size: .6875rem;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--hw-primary);
}

/* The rail. Drawn as a border on the group rather than as an element, so it
   stretches to whatever the group contains without being measured. */
.notif-group {
    position: relative;
    margin-left: 1.9375rem;
    padding-left: 1.75rem;
    border-left: 2px solid var(--hw-border);
    display: grid;
    gap: .625rem;
}

/* Each event's icon sits ON the rail, half outside its card. */
.notif-group .notif-item {
    position: relative;
    border: 1px solid var(--hw-border);
    border-radius: var(--hw-radius-sm);
    background: var(--hw-surface);
}

.notif-group .notif-icon {
    position: absolute;
    left: -2.9375rem;
    top: 1rem;
    z-index: 1;
    border: 3px solid var(--hw-canvas);
}

/* Unread: a wash, a coloured edge and a bolder title. Read recedes. */
.notif-group .notif-item.is-unread {
    background: linear-gradient(90deg, rgba(var(--hw-primary-rgb), .055), transparent 46%), var(--hw-surface);
    border-color: rgba(var(--hw-primary-rgb), .25);
    box-shadow: inset 3px 0 0 var(--hw-primary);
}

.notif-group .notif-item:not(.is-unread) { opacity: .82; }
.notif-group .notif-item:not(.is-unread) h2 { font-weight: 560; color: var(--hw-body); }
.notif-group .notif-item:not(.is-unread) .notif-icon { filter: grayscale(.35); opacity: .8; }

@media (max-width: 575.98px) {
    .notif-group { margin-left: 1.25rem; padding-left: 1.25rem; }
    .notif-group .notif-icon { left: -2.375rem; }
    .notif-summary-item { padding: 0 .75rem; }
}

.cycle-panel { position: relative; overflow: hidden; }

.cycle-label {
    font-size: .8125rem;
    color: var(--hw-muted);
    margin: 0 0 .35rem;
}

/* A dotted corner, purely to stop the panel reading as a plain white box. */
.cycle-dots {
    position: absolute;
    top: .875rem;
    right: .875rem;
    width: 84px;
    height: 46px;
    background-image: radial-gradient(rgba(var(--hw-primary-rgb), .22) 1.2px, transparent 1.2px);
    background-size: 9px 9px;
    pointer-events: none;
}

.cycle-stats {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
}

.cycle-stats li {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .6875rem 0;
    border-top: 1px solid var(--hw-border);
}

.cycle-stats li:first-child { border-top: 0; }

.cycle-stat-icon {
    width: 30px;
    height: 30px;
    border-radius: 9px;
    display: grid;
    place-items: center;
    font-size: .875rem;
    flex: 0 0 auto;
}

.cycle-stat-icon.is-blue   { background: #e8f1ff; color: #2563eb; }
.cycle-stat-icon.is-green  { background: #e6f6ee; color: #0f9d68; }
.cycle-stat-icon.is-amber  { background: #fef3e2; color: #d97706; }
.cycle-stat-icon.is-purple { background: #f0eaff; color: #7c3aed; }

.cycle-stat-label { font-size: .8125rem; color: var(--hw-body); flex: 1 1 auto; }
.cycle-stat-value { font-size: 1rem; font-weight: 700; color: var(--hw-ink); font-variant-numeric: tabular-nums; }

.allowance-figure {
    font-size: 1.75rem;
    font-weight: 720;
    color: var(--hw-ink);
    letter-spacing: -.03em;
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

.usage-figure-label {
    font-size: .8125rem;
    color: var(--hw-muted);
    margin: .3rem 0 1.25rem;
    letter-spacing: .01em;
}

/* Eyebrow label above a panel's content. */
.section-eyebrow {
    display: block;
    padding: 1.375rem 1.75rem 0;
    font-size: .6875rem;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--hw-primary);
}

/* The ring keeps the fraction as a shape and drops the number from its middle,
   which the figure beside it now carries. An icon sits there instead. */
.usage-ring { position: relative; }

.usage-ring-mark {
    position: absolute;
    inset: 0;
    margin: auto;
    width: 3rem;
    height: 3rem;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: var(--hw-primary);
    color: #fff;
    font-size: 1.15rem;
    box-shadow: 0 8px 20px -6px rgba(var(--hw-primary-rgb), .55);
}

.usage-ring-pill {
    position: absolute;
    right: -.5rem;
    bottom: 1.25rem;
    display: grid;
    justify-items: center;
    padding: .3rem .6rem;
    border-radius: 10px;
    background: var(--hw-surface);
    border: 1px solid var(--hw-border);
    box-shadow: var(--hw-shadow-sm);
    line-height: 1.15;
}

.usage-ring-pill strong { font-size: .8125rem; color: var(--hw-primary); font-weight: 700; }
.usage-ring-pill span   { font-size: .625rem; color: var(--hw-faint); }

/* A soft swell along the panel's foot, so the card has a base rather than an
   edge. Drawn rather than an image: one gradient, no request. */
.usage-panel-wave {
    display: block;
    height: 4.5rem;
    margin-top: -3rem;
    background:
        radial-gradient(120% 100% at 20% 100%, rgba(var(--hw-primary-rgb), .09), transparent 60%),
        radial-gradient(90% 100% at 78% 100%, rgba(var(--hw-primary-rgb), .06), transparent 60%);
    border-bottom-left-radius: var(--hw-radius);
    border-bottom-right-radius: var(--hw-radius);
    pointer-events: none;
}

.usage-ring-wrap { position: relative; display: inline-grid; place-items: center; }

.usage-ring-center {
    position: absolute;
    inset: 0;
    display: grid;
    place-content: center;
    text-align: center;
    line-height: 1;
}

.usage-ring-value {
    font-size: 2.125rem;
    font-weight: 700;
    color: var(--hw-ink);
    letter-spacing: -.03em;
    font-variant-numeric: tabular-nums;
}

.usage-ring-value small {
    font-size: 1rem;
    font-weight: 550;
    color: var(--hw-faint);
    letter-spacing: -.01em;
}

.usage-ring-caption {
    display: block;
    margin-top: .3rem;
    font-size: .6875rem;
    font-weight: 650;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--hw-faint);
}

/* Copy beside the ring */
.usage-headline {
    font-size: 1.375rem;
    font-weight: 680;
    letter-spacing: -.02em;
    color: var(--hw-ink);
    margin-bottom: .25rem;
}

.usage-subline { color: var(--hw-muted); font-size: .9rem; margin-bottom: 1rem; }

/* One pip per allowed wash — a glanceable count of what is left. */
.usage-pips { display: flex; flex-wrap: wrap; gap: .3rem; }

@media (max-width: 575px) { .usage-pips { justify-content: center; } }

.usage-pip {
    width: 26px;
    height: 6px;
    border-radius: 999px;
    background: var(--hw-border);
    transition: background-color .3s ease;
}

.usage-pip.is-used  { background: var(--hw-primary); }
.usage-pip.is-extra { background: #fde9c8; }
.usage-pip.is-extra.is-used { background: var(--hw-warning); }

/* Compact meter reused in tables and side cards */
.mini-meter {
    display: block;
    height: 6px;
    border-radius: 999px;
    background: var(--hw-border);
    overflow: hidden;
    min-width: 72px;
}

.mini-meter > span {
    display: block;
    height: 100%;
    border-radius: 999px;
    background: var(--hw-primary);
    transition: width .5s cubic-bezier(.22, 1, .36, 1);
}

.mini-meter.is-full > span { background: var(--hw-danger); }
.mini-meter.is-warning > span { background: var(--hw-warning); }

/* ==========================================================================
   Refined building blocks used across the resident pages
   ========================================================================== */

/* Page header: greeting + context chips */
.page-head { margin-bottom: 1.5rem; }

.page-head h1,
.page-head .h1 {
    font-size: 1.5rem;
    font-weight: 700;
    letter-spacing: -.025em;
    margin-bottom: .5rem;
}

@media (min-width: 768px) { .page-head h1, .page-head .h1 { font-size: 1.75rem; } }

.chip-row { display: flex; flex-wrap: wrap; gap: .5rem; }

.chip {
    display: inline-flex;
    align-items: center;
    gap: .375rem;
    padding: .3rem .625rem;
    border-radius: 8px;
    background: var(--hw-surface);
    border: 1px solid var(--hw-border);
    font-size: .78rem;
    font-weight: 550;
    color: var(--hw-muted);
    white-space: nowrap;
}

/* A chip that carries a state wears that state's colour. The neutral chips
   around it stay grey, so the coloured ones are the ones that get noticed. */
.chip.is-success { background: var(--hw-success-soft); border-color: rgba(15, 157, 104, .25); color: var(--hw-success-fg); }
.chip.is-warning { background: var(--hw-warning-soft); border-color: rgba(217, 119, 6, .25);  color: var(--hw-warning-fg); }
.chip.is-danger  { background: var(--hw-danger-soft);  border-color: rgba(220, 38, 38, .25);  color: var(--hw-danger-fg); }
.chip.is-dark    { background: #eef1f6; border-color: #dde3ec; color: #334155; }

.chip.is-success i, .chip.is-warning i, .chip.is-danger i, .chip.is-dark i { color: currentColor; }
.chip.is-success strong, .chip.is-warning strong, .chip.is-danger strong, .chip.is-dark strong { color: currentColor; }

.chip i { font-size: .85em; color: var(--hw-faint); }
.chip strong { color: var(--hw-ink); font-weight: 620; }

/* A quiet label above a group */
.section-label {
    font-size: .6875rem;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    /* Accent, not grey: these labels are the page's structure, and in the
       approved design they read as part of the brand rather than as fine print. */
    color: var(--hw-primary);
    margin-bottom: .75rem;
}

/* A dashboard feed whose length is configurable scrolls inside its card. */
.timeline.is-scrollable {
    max-height: 26rem;
    overflow-y: auto;
    padding-right: .25rem;
    overscroll-behavior: contain;
}

/* Only cap it where there is a column beside it to stay level with. On a phone
   the cards are stacked, so a cap just truncates the feed mid-entry. */
@media (max-width: 1199.98px) {
    .timeline.is-scrollable { max-height: none; overflow-y: visible; }
}

.timeline.is-scrollable::-webkit-scrollbar { width: 6px; }
.timeline.is-scrollable::-webkit-scrollbar-thumb {
    background: var(--hw-line);
    border-radius: 999px;
}

/* Bootstrap 5 ships no min-width utility, but the markup leans on this one in
   a dozen places to let a flex child shrink so its text can truncate. Without
   it every one of those guards is inert: a long page title stops the topbar
   from fitting and pushes the bell and account menu off the screen. */
/* The live poller shows and hides panels with the `hidden` attribute, but
   Bootstrap's `.d-flex` is `display: flex !important` at the same specificity
   as reboot's `[hidden]` and comes later in its stylesheet, so a hidden
   `.d-flex` element stays on screen. Doubling the attribute raises specificity
   enough to settle it, for every `hidden` toggle in the app rather than just
   the one that exposed it. */
[hidden][hidden] { display: none !important; }

.min-w-0 { min-width: 0; }

/* The 3D machine on the sign-in and registration screens. It sits between the
   headline and the highlight list, and is the first thing to go when the panel
   gets short — the copy matters, the decoration does not. */
.auth-stage {
    position: relative;
    /* Sized from the viewport height, not the column width: the panel also holds
       a headline and three highlights, and on a laptop screen a fixed-size
       square pushes the last of them off the bottom. */
    height: min(300px, 26vh);
    aspect-ratio: 1;
    width: auto;
    margin: .5rem auto 1rem;
}

/* A podium under the machine. Against a pale panel a floating object reads as
   cut out; a disc gives it somewhere to stand. */
.auth-stage::after {
    content: '';
    position: absolute;
    left: 18%;
    right: 18%;
    bottom: 4%;
    height: 14%;
    border-radius: 50%;
    background:
        radial-gradient(ellipse at center, rgba(37, 99, 235, .16), transparent 72%),
        rgba(255, 255, 255, .85);
    box-shadow: 0 12px 26px -14px rgba(30, 58, 138, .45);
    z-index: 0;
}

.auth-stage canvas, .auth-stage .scene-fallback { position: relative; z-index: 1; }

.auth-stage canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    filter: drop-shadow(0 18px 34px rgba(30, 58, 138, .22));
}

/* Below this there is no room for decoration at all. */
@media (max-height: 640px) {
    .auth-stage { display: none; }
}

/* --------------------------------------------------------------------------
   Share poster
   -------------------------------------------------------------------------- */

.qr-frame {
    display: inline-block;
    padding: 1.25rem 1.25rem 1rem;
    border-radius: var(--hw-radius);
    background: #fff;
    border: 1px solid var(--hw-border);
    box-shadow: var(--hw-shadow-sm);
    max-width: 320px;
    width: 100%;
}

.qr-canvas {
    display: grid;
    place-items: center;
    min-height: 200px;
    border-radius: var(--hw-radius-sm);
    overflow: hidden;
}

.qr-canvas canvas { display: block; border-radius: 6px; }

.qr-caption {
    margin-top: .75rem;
    font-size: .8125rem;
    line-height: 1.35;
}

/* Printing: the poster and nothing else. The app chrome on a pinned-up sheet
   is wasted ink and makes the code smaller than it needs to be. */
@media print {
    body.printing-qr .app-sidebar,
    body.printing-qr .app-topbar,
    body.printing-qr .toolbar,
    body.printing-qr .col-lg-7,
    body.printing-qr .btn { display: none !important; }

    body.printing-qr .app-main { margin-left: 0; }
    body.printing-qr .card { border: none; box-shadow: none; }

    body.printing-qr .qr-frame {
        max-width: 90vw;
        border: 2px solid #0f172a;
        box-shadow: none;
    }
}

/* --------------------------------------------------------------------------
   Motion
   -------------------------------------------------------------------------- */

/* Motion One sets the starting state and animates from it, so nothing here
   hides anything. If that script never runs, the page simply appears — which is
   the behaviour you want from a decoration. */
.motion-ready [data-reveal] { will-change: opacity, transform; }
.motion-ready [data-reveal].is-revealed { will-change: auto; }

@media (prefers-reduced-motion: reduce) {
    [data-reveal] { opacity: 1 !important; transform: none !important; }
}

/* Tab switch — one tinted track, the active tab lifted out of it in
   white. Replaces a row of loose buttons that all looked equally clickable. */
.tab-switch {
    display: inline-flex;
    align-items: center;
    gap: .125rem;
    padding: .25rem;
    border-radius: 999px;
    background: var(--hw-neutral-soft);
    max-width: 100%;
    overflow-x: auto;
    scrollbar-width: none;
    /* It usually sits in a flex row next to a search box. Without this it is
       treated as shrinkable, and because it scrolls, its minimum size resolves
       to zero — the track collapses to a stub and squashes every tab inside
       it. Keep its natural width and let the row wrap instead. */
    flex: 0 0 auto;
}

.tab-switch::-webkit-scrollbar { display: none; }

.tab-switch-item {
    display: inline-flex;
    align-items: center;
    gap: .375rem;
    /* Tabs keep their label's width; the track scrolls if they don't all fit. */
    flex: 0 0 auto;
    padding: .4rem .875rem;
    border-radius: 999px;
    font-size: .8125rem;
    font-weight: 560;
    color: var(--hw-muted);
    white-space: nowrap;
    transition: background-color .14s ease, color .14s ease, box-shadow .14s ease;
}

.tab-switch-item:hover { color: var(--hw-ink); }

.tab-switch-item.is-active {
    background: var(--hw-surface);
    color: var(--hw-ink);
    font-weight: 620;
    box-shadow: var(--hw-shadow-xs);
}

.tab-switch-item .count {
    font-size: .6875rem;
    font-weight: 700;
    padding: 0 .3rem;
    min-width: 1.15rem;
    text-align: center;
    border-radius: 999px;
    background: var(--hw-neutral-soft);
    color: var(--hw-neutral-fg);
}

.tab-switch-item.is-active .count {
    background: rgba(var(--hw-primary-rgb), .12);
    color: var(--hw-primary);
}

/* Idle-machine panel — the "nothing booked" card sits beside the much taller
   usage ring, so without something here the card is mostly empty space. */
.machine-idle {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: .5rem;
    padding: 1.25rem 1rem 1.5rem;
    border: 1px solid var(--hw-border);
    border-radius: var(--hw-radius);
    background:
        radial-gradient(120% 100% at 50% 0%, rgba(var(--hw-primary-rgb), .06), transparent 70%),
        var(--hw-canvas);
    text-align: center;
}

/* A soft ellipse under the machine. Without a ground the object floats, which
   is exactly what the approved design fixed. */
.machine-idle .scene-stage::after {
    content: '';
    position: absolute;
    left: 12%;
    right: 12%;
    bottom: 2%;
    height: 12%;
    border-radius: 50%;
    background: radial-gradient(ellipse at center, rgba(15, 23, 42, .16), transparent 70%);
    z-index: 0;
}

.machine-idle-glyph {
    display: grid;
    place-items: center;
    width: 3.25rem;
    height: 3.25rem;
    border-radius: 50%;
    font-size: 1.4rem;
    color: var(--hw-primary);
    background: #fff;
    box-shadow: var(--hw-shadow-sm);
}

.machine-idle-state {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    font-size: .8125rem;
    font-weight: 620;
    color: var(--hw-text);
}

.machine-idle-dot {
    width: .5rem;
    height: .5rem;
    border-radius: 50%;
    background: var(--hw-success, #10b981);
    box-shadow: 0 0 0 3px rgba(16, 185, 129, .16);
}

.machine-idle-hint { font-size: .75rem; color: var(--hw-faint); }

/* Quick action tiles */
.action-grid { display: grid; gap: .875rem; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }

.action-tile {
    display: flex;
    align-items: center;
    gap: .875rem;
    padding: 1rem 1.125rem;
    border-radius: var(--hw-radius);
    background: var(--hw-surface);
    border: 1px solid var(--hw-border);
    box-shadow: var(--hw-shadow-xs);
    color: var(--hw-body);
    transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}

.action-tile:hover {
    color: var(--hw-body);
    border-color: var(--hw-border-strong);
    box-shadow: var(--hw-shadow-md);
    transform: translateY(-2px);
}

.action-tile.is-primary {
    background: var(--hw-primary);
    border-color: var(--hw-primary);
    color: #fff;
    box-shadow: 0 8px 20px -8px rgba(var(--hw-primary-rgb), .6);
}

.action-tile.is-primary:hover { color: #fff; background: var(--hw-primary-hover); }
.action-tile.is-primary .action-tile-icon { background: rgba(255, 255, 255, .18); color: #fff; }
.action-tile.is-primary .action-tile-sub { color: rgba(255, 255, 255, .78); }

.action-tile-icon {
    width: 40px; height: 40px;
    border-radius: 11px;
    display: grid; place-items: center;
    background: var(--hw-primary-soft);
    color: var(--hw-primary);
    font-size: 1.1rem;
    flex: 0 0 auto;
}

/* Trailing chevron. On the highlighted tile it is a filled disc, so the primary
   action is the one that looks most like a button. */
.action-tile-go {
    margin-left: auto;
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-size: .8rem;
    color: var(--hw-faint);
    transition: transform .16s ease, background-color .16s ease, color .16s ease;
}

.action-tile:hover .action-tile-go { transform: translateX(2px); color: var(--hw-primary); }

.action-tile.is-primary .action-tile-go {
    background: rgba(255, 255, 255, .22);
    color: #fff;
}

.action-tile-title { font-weight: 640; font-size: .9375rem; color: inherit; line-height: 1.25; }
.action-tile-sub   { font-size: .78rem; color: var(--hw-muted); }

/* Definition rows — used on profile and detail panels */
.detail-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding: .8125rem 0;
    border-bottom: 1px solid var(--hw-border);
}

.detail-row:last-child { border-bottom: 0; }
.detail-row dt { font-size: .8125rem; color: var(--hw-muted); font-weight: 500; margin: 0; }
.detail-row dd { margin: 0; font-weight: 600; color: var(--hw-ink); text-align: right; word-break: break-word; }

/* Edit-on-demand panels.
   The form is only hidden once JS has claimed the panel (.js-ready), so with
   JavaScript off both blocks render and the form still works. */
[data-edit-panel].js-ready [data-edit-form] { display: none; }
[data-edit-panel].js-ready.is-editing [data-edit-form] { display: block; animation: hw-fade-in .25s ease; }
[data-edit-panel].js-ready.is-editing [data-edit-view] { display: none; }
[data-edit-panel].js-ready.is-editing [data-edit-open] { display: none; }

@keyframes hw-fade-in {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: none; }
}

/* Timeline entries on the resident side */
.entry {
    display: flex;
    align-items: center;
    gap: .875rem;
    padding: .875rem 1.25rem;
    border-bottom: 1px solid var(--hw-border);
}

.entry:last-child { border-bottom: 0; }

.entry-icon {
    width: 36px; height: 36px;
    border-radius: 10px;
    display: grid; place-items: center;
    background: var(--hw-canvas);
    color: var(--hw-muted);
    flex: 0 0 auto;
    font-size: .95rem;
}

.entry-icon.is-accent { background: var(--hw-primary-soft); color: var(--hw-primary); }
.entry-icon.is-success { background: #e7f7f0; color: var(--hw-success); }
.entry-icon.is-warning { background: #fdf3e3; color: var(--hw-warning); }

.entry-title { font-weight: 600; font-size: .875rem; color: var(--hw-ink); }
.entry-sub   { font-size: .78rem; color: var(--hw-muted); }

/* --------------------------------------------------------------------------
   Badges, avatars, tables
   -------------------------------------------------------------------------- */

.badge-status {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    font-weight: 600;
    font-size: .75rem;
    padding: .3rem .55rem;
    border-radius: 7px;
    letter-spacing: .01em;
}

.badge-status i { font-size: .8em; }

.badge-soft {
    background: var(--hw-primary-soft);
    color: var(--hw-primary);
    font-weight: 600;
}

.avatar {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    object-fit: cover;
    background: var(--hw-primary-soft);
    color: var(--hw-primary);
    display: inline-grid;
    place-items: center;
    font-weight: 650;
    font-size: .8125rem;
    flex: 0 0 auto;
    border: 1px solid var(--hw-border);
}

.avatar-sm { width: 30px; height: 30px; font-size: .6875rem; }
.avatar-lg { width: 64px; height: 64px; font-size: 1.25rem; }

.table { --bs-table-bg: transparent; margin-bottom: 0; }

.table > thead > tr > th {
    font-size: .75rem;
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: .045em;
    color: var(--hw-muted);
    background: #fafbfd;
    border-bottom: 1px solid var(--hw-border);
    padding: .75rem 1rem;
    white-space: nowrap;
}

.table > tbody > tr > td {
    /* Roomier rows. A records table is read down a column, and tight leading
       makes the eye lose its place between one date and the next. */
    padding: 1.0625rem 1rem;
    border-bottom: 1px solid var(--hw-border);
    vertical-align: middle;
}

.table > tbody > tr:last-child > td { border-bottom: 0; }
.table-hover > tbody > tr:hover > td { background: #fafbfd; }

.table-card { overflow: hidden; }
.table-card .card-body { padding: 0; }

/* Any wide table scrolls inside its own box rather than the page. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* --------------------------------------------------------------------------
   Forms & buttons
   -------------------------------------------------------------------------- */

.form-label {
    font-weight: 600;
    font-size: .8125rem;
    color: var(--hw-ink);
    margin-bottom: .375rem;
}

/* Inputs sit on a tinted fill with a hairline border rather than a hard box,
   after shadcn: the field reads as a slot in the surface, and the border only
   asserts itself on hover and focus. */
.form-control, .form-select {
    background-color: var(--hw-surface-2);
    border-color: var(--hw-border);
    border-radius: var(--hw-radius-sm);
    padding: .5625rem .75rem;
    font-size: .9375rem;
    transition: background-color .14s ease, border-color .14s ease, box-shadow .14s ease;
}

.form-control:hover:not(:disabled):not([readonly]),
.form-select:hover:not(:disabled) { border-color: var(--hw-border-strong); }

.form-control:focus, .form-select:focus {
    background-color: var(--hw-surface);
    box-shadow: 0 0 0 3px var(--hw-ring);
}

/* The shared padding above also lands on <select>, whose chevron is a
   background image that Bootstrap keeps clear with extra right padding. Put it
   back, or the longest option runs underneath the arrow and gets clipped. */
.form-select { padding-right: 2.25rem; }
.form-select-sm { padding-right: 2rem; }

.form-control::placeholder { color: var(--hw-faint); }
.form-control:disabled, .form-control[readonly] { background-color: #f8fafc; color: var(--hw-muted); }

.form-text { font-size: .8125rem; color: var(--hw-muted); }

.btn {
    border-radius: var(--hw-radius-sm);
    font-weight: 600;
    font-size: .875rem;
    padding: .5625rem 1rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .4rem;
}

.btn-sm { padding: .375rem .75rem; font-size: .8125rem; }
.btn-lg { padding: .75rem 1.5rem; font-size: .9375rem; }

.btn-primary {
    background-color: var(--hw-primary);
    border-color: var(--hw-primary);
}

.btn-primary:hover, .btn-primary:focus {
    background-color: var(--hw-primary-hover);
    border-color: var(--hw-primary-hover);
}

.btn-outline-primary { color: var(--hw-primary); border-color: var(--hw-border-strong); }
.btn-outline-primary:hover { background-color: var(--hw-primary); border-color: var(--hw-primary); }

.btn-google {
    background: #fff;
    border: 1px solid var(--hw-border-strong);
    color: var(--hw-ink);
    font-weight: 600;
    box-shadow: var(--hw-shadow-xs);
}

.btn-google:hover { background: #f8fafc; border-color: var(--hw-faint); color: var(--hw-ink); }

/* Radio cards for the extra-usage quantity picker */
.choice-grid { display: grid; gap: .75rem; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }

.choice-card { position: relative; }

.choice-card input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.choice-card label {
    display: block;
    border: 2px solid var(--hw-border);
    border-radius: var(--hw-radius);
    padding: 1rem;
    text-align: center;
    cursor: pointer;
    transition: border-color .12s ease, background-color .12s ease, transform .12s ease;
    background: var(--hw-surface);
}

.choice-card label:hover { border-color: var(--hw-border-strong); }

.choice-card input:checked + label {
    border-color: var(--hw-primary);
    background: var(--hw-primary-soft);
}

.choice-card input:focus-visible + label {
    outline: 3px solid rgba(var(--hw-primary-rgb), .35);
    outline-offset: 2px;
}

.choice-card input:disabled + label { opacity: .55; cursor: not-allowed; }

.choice-qty {
    font-size: 1.75rem;
    font-weight: 700;
    color: var(--hw-ink);
    line-height: 1.1;
}

.choice-unit { font-size: .8125rem; color: var(--hw-muted); font-weight: 500; }

/* --------------------------------------------------------------------------
   Empty / loading / error states
   -------------------------------------------------------------------------- */

.empty-state { text-align: center; padding: 2.5rem 1.5rem 2.75rem; }

.illus {
    width: 100%;
    max-width: 240px;
    height: auto;
    margin: 0 auto .875rem;
    display: block;
}

.empty-state-icon {
    width: 60px;
    height: 60px;
    border-radius: 16px;
    background: var(--hw-canvas);
    color: var(--hw-faint);
    display: inline-grid;
    place-items: center;
    font-size: 1.6rem;
    margin-bottom: 1rem;
}

.empty-state h3 { font-size: 1.0625rem; margin-bottom: .375rem; }
.empty-state p { color: var(--hw-muted); max-width: 32rem; margin: 0 auto 1.25rem; font-size: .9375rem; }

/* Shown while a form is submitting */
.btn .spinner-border { display: none; }
.btn.is-loading .spinner-border { display: inline-block; }
.btn.is-loading .btn-label { opacity: .75; }
.btn.is-loading { pointer-events: none; }

/* --------------------------------------------------------------------------
   Notifications & timeline
   -------------------------------------------------------------------------- */

.notif-item {
    display: flex;
    gap: .875rem;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--hw-border);
}

.notif-item:last-child { border-bottom: 0; }
.notif-item.is-unread { background: rgba(var(--hw-primary-rgb), .035); }

.notif-icon {
    width: 38px;
    height: 38px;
    border-radius: 11px;
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    font-size: 1rem;
    background: var(--hw-primary-soft);
    color: var(--hw-primary);
}

.notif-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--hw-primary);
    flex: 0 0 auto;
    margin-top: .5rem;
}

.timeline { position: relative; padding-left: 1.75rem; }

.timeline::before {
    content: "";
    position: absolute;
    left: 12px;
    top: .5rem;
    bottom: .5rem;
    width: 2px;
    background: var(--hw-border);
}

.timeline-item { position: relative; padding-bottom: 1.125rem; }
.timeline-item:last-child { padding-bottom: 0; }

.timeline-marker {
    position: absolute;
    left: -1.75rem;
    top: .1rem;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: var(--hw-surface);
    border: 2px solid var(--hw-border);
    display: grid;
    place-items: center;
    font-size: .6875rem;
    color: var(--hw-muted);
}

/* --------------------------------------------------------------------------
   Auth screens — split brand panel + form panel
   -------------------------------------------------------------------------- */

.auth-body { background: var(--hw-surface); }

.auth-split {
    min-height: 100vh;
    display: grid;
    grid-template-columns: 1fr;
}

@media (min-width: 992px) {
    .auth-split { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); }
}

/* Left: brand panel (decorative, desktop only) */
/* A LIGHT brand panel. It used to be a solid dark blue block, which on a page
   whose other half is white read as two different products bolted together.
   Pale blue with dark type keeps both halves in the same register, and lets the
   machine be the only saturated thing on the screen. */
.auth-brand {
    display: none;
    position: relative;
    overflow: hidden;
    color: var(--hw-body);
    background:
        radial-gradient(120% 90% at 15% 0%, #ffffff 0%, transparent 55%),
        linear-gradient(160deg, #eff5ff 0%, #dceafe 55%, #cfe0fd 100%);
}

/* Soft wave curves across the panel, drawn rather than fetched. */
.auth-brand::before,
.auth-brand::after {
    content: '';
    position: absolute;
    inset: -20% -40%;
    border-radius: 45%;
    border: 1px solid rgba(37, 99, 235, .10);
    pointer-events: none;
}

.auth-brand::after { inset: -10% -55%; border-color: rgba(37, 99, 235, .07); }

@media (min-width: 992px) {
    .auth-brand { display: block; }
}

.auth-brand-inner {
    position: relative;
    z-index: 2;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 2.5rem;
    padding: 3.5rem;
    max-width: 32rem;
    margin: 0 auto;
}

.auth-brand-logo {
    display: flex;
    align-items: center;
    gap: .875rem;
    font-size: 1rem;
}

.auth-brand-logo .app-brand-mark {
    background: linear-gradient(135deg, var(--hw-primary), #3b82f6);
    color: #fff;
    box-shadow: var(--hw-shadow-sm);
}

.auth-brand-logo strong,
.auth-brand-logo .fw-bold { color: var(--hw-ink); }

.auth-brand-copy h2 {
    color: var(--hw-ink);
    font-size: 2.25rem;
    font-weight: 700;
    letter-spacing: -.025em;
    line-height: 1.15;
    margin-bottom: .875rem;
}

.auth-brand-copy p {
    color: var(--hw-muted);
    font-size: 1.0625rem;
    margin: 0;
    max-width: 26rem;
}

.auth-highlights { list-style: none; padding: 0; margin: 0; display: grid; gap: 1.125rem; }

.auth-highlights li {
    display: flex;
    gap: .875rem;
    align-items: flex-start;
    color: var(--hw-muted);
    font-size: .9375rem;
    padding-bottom: 1.125rem;
    border-bottom: 1px solid rgba(37, 99, 235, .12);
}

.auth-highlights li:last-child { padding-bottom: 0; border-bottom: 0; }

.auth-highlights strong { color: var(--hw-primary); font-weight: 640; display: block; }

.auth-highlight-icon {
    width: 42px;
    height: 42px;
    border-radius: 12px;
    background: var(--hw-surface);
    border: 1px solid rgba(37, 99, 235, .14);
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    color: var(--hw-primary);
    font-size: 1.05rem;
    box-shadow: var(--hw-shadow-xs);
}

/* Soft light blooms behind the panel */
.auth-brand-orb {
    position: absolute;
    border-radius: 50%;
    filter: blur(70px);
    opacity: .5;
    z-index: 1;
}

.auth-brand-orb-1 { width: 320px; height: 320px; background: #bfdbfe; top: -90px; right: -80px; opacity: .5; }
.auth-brand-orb-2 { width: 280px; height: 280px; background: #dbeafe; bottom: -70px; left: -60px; opacity: .55; }

/* Right: form panel */
.auth-panel {
    display: grid;
    place-items: center;
    padding: 2rem 1.25rem 3rem;
    background:
        radial-gradient(900px 400px at 50% -10%, rgba(99, 102, 241, .07), transparent 60%),
        var(--hw-surface);
}

@media (min-width: 576px) { .auth-panel { padding: 2.5rem; } }

.auth-panel-inner { width: 100%; max-width: 25rem; }

.auth-form-wide { max-width: 100%; }
.auth-panel-inner:has(.auth-form-wide) { max-width: 38rem; }

.auth-heading {
    font-size: 2rem;
    font-weight: 720;
    letter-spacing: -.026em;
    margin-bottom: .375rem;
}

/* Roomier fields and a taller button on the sign-in screens. This is the first
   thing a resident touches, and on a half-empty panel there is space for it. */
.auth-form .input-group-lg .form-control,
.auth-form .input-group-lg .input-group-text { padding-top: .8125rem; padding-bottom: .8125rem; }

.auth-form .input-group-lg .input-group-text { background: var(--hw-surface); }

.auth-form .btn-lg { padding-top: .8125rem; padding-bottom: .8125rem; font-size: 1rem; }

/* The credit sits below a rule, separated from the form's own footnotes. */
.auth-panel-inner .app-credit { margin-top: 2rem; }

.auth-sub { color: var(--hw-muted); margin-bottom: 1.75rem; }

.auth-mark {
    width: 56px;
    height: 56px;
    border-radius: 16px;
    background: linear-gradient(135deg, var(--hw-primary), #3b82f6);
    color: #fff;
    display: inline-grid;
    place-items: center;
    font-size: 1.5rem;
    box-shadow: 0 10px 24px rgba(var(--hw-primary-rgb), .32);
}

/* The staff mark. Blue rather than near-black — the page is a restricted door,
   not a warning — with two rings of glow so it reads as a seal. */
.auth-mark-dark {
    position: relative;
    background: var(--hw-surface);
    color: var(--hw-primary);
    width: 72px;
    height: 72px;
    border-radius: 50%;
    font-size: 1.75rem;
    box-shadow:
        0 0 0 10px rgba(var(--hw-primary-rgb), .06),
        0 0 0 20px rgba(var(--hw-primary-rgb), .035),
        var(--hw-shadow-md);
}

/* --------------------------------------------------------------------------
   Staff entrance: same shell, cooler ground, shield motif
   -------------------------------------------------------------------------- */

.auth-split.is-staff .auth-brand {
    background:
        radial-gradient(120% 90% at 15% 0%, #ffffff 0%, transparent 55%),
        linear-gradient(160deg, #f2f5fb 0%, #e4eaf6 55%, #d8e2f4 100%);
}

/* A shield behind the machine, and the two things the warden does floating
   beside it: unlocking, and approving. */
.auth-split.is-staff .auth-stage::before {
    content: '';
    position: absolute;
    inset: -6% 8% 6% 8%;
    background: linear-gradient(180deg, rgba(255, 255, 255, .75), rgba(255, 255, 255, .25));
    border: 1px solid rgba(37, 99, 235, .16);
    /* A shield outline: square shoulders, tapered to a point. */
    clip-path: polygon(50% 0%, 100% 14%, 100% 58%, 50% 100%, 0% 58%, 0% 14%);
    z-index: 0;
}

.auth-split.is-staff .auth-stage .auth-badge {
    position: absolute;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: var(--hw-surface);
    color: var(--hw-primary);
    box-shadow: var(--hw-shadow-sm);
    z-index: 2;
    font-size: .95rem;
}

.auth-split.is-staff .auth-stage .auth-badge.is-lock { left: -4%; top: 34%; }
.auth-split.is-staff .auth-stage .auth-badge.is-tick { right: 2%; bottom: 26%; color: var(--hw-success); }

.auth-divider {
    display: flex;
    align-items: center;
    gap: .75rem;
    color: var(--hw-faint);
    font-size: .75rem;
    text-transform: uppercase;
    letter-spacing: .07em;
    margin: 1.5rem 0;
}

.auth-divider::before, .auth-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--hw-border);
}

/* Grouped form sections on the registration form */
.form-section-label {
    font-size: .6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--hw-faint);
    margin: 1.75rem 0 .875rem;
    display: flex;
    align-items: center;
    gap: .75rem;
}

.form-section-label::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--hw-border);
}

.form-section-label:first-of-type { margin-top: 1.25rem; }

.req { color: #dc2626; font-weight: 700; }

.input-group-text {
    background: #fff;
    border-color: var(--hw-border-strong);
    color: var(--hw-faint);
}

.input-group:focus-within .input-group-text {
    border-color: rgba(var(--hw-primary-rgb), .5);
    color: var(--hw-primary);
}

/* Segmented radio control (fee status) */
.segmented {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: .25rem;
    padding: .25rem;
    background: var(--hw-canvas);
    border: 1px solid var(--hw-border);
    border-radius: var(--hw-radius-sm);
}

.segmented input { position: absolute; opacity: 0; width: 0; height: 0; }

.segmented label {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .35rem;
    padding: .5rem .375rem;
    border-radius: 8px;
    font-size: .8125rem;
    font-weight: 600;
    color: var(--hw-muted);
    cursor: pointer;
    text-align: center;
    transition: background-color .12s ease, color .12s ease, box-shadow .12s ease;
}

.segmented label:hover { color: var(--hw-ink); }

.segmented input:checked + label {
    background: var(--hw-surface);
    color: var(--hw-primary);
    box-shadow: var(--hw-shadow-xs);
}

.segmented input:focus-visible + label {
    outline: 3px solid rgba(var(--hw-primary-rgb), .35);
    outline-offset: 1px;
}

@media (max-width: 400px) {
    .segmented { grid-auto-flow: row; }
}

/* Password strength meter */
.strength { margin-top: .5rem; }

.strength-bar {
    display: block;
    height: 5px;
    border-radius: 999px;
    background: var(--hw-border);
    overflow: hidden;
    margin-bottom: .35rem;
}

.strength-bar > span {
    display: block;
    height: 100%;
    width: 0;
    border-radius: 999px;
    background: #ef4444;
    transition: width .25s ease, background-color .25s ease;
}

.strength[data-level="2"] .strength-bar > span { background: #f59e0b; }
.strength[data-level="3"] .strength-bar > span { background: #10b981; }
.strength[data-level="4"] .strength-bar > span { background: #059669; }

/* Status screens (pending / rejected / blocked) */
.status-panel { max-width: 34rem; margin: 0 auto; }

.status-panel .card {
    border-radius: var(--hw-radius-lg);
    box-shadow: var(--hw-shadow-md);
}

.status-lottie { width: 118px; height: 118px; margin-bottom: .25rem; }

@media (max-width: 575px) { .status-lottie { width: 96px; height: 96px; } }

.status-icon {
    width: 72px;
    height: 72px;
    border-radius: 20px;
    display: grid;
    place-items: center;
    font-size: 2rem;
    margin: 0 auto 1.25rem;
}

.steps { display: flex; flex-direction: column; gap: .875rem; }

.step { display: flex; gap: .875rem; align-items: flex-start; text-align: left; }

.step-marker {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-size: .75rem;
    font-weight: 700;
    flex: 0 0 auto;
    background: var(--hw-border);
    color: var(--hw-muted);
}

.step.is-done .step-marker { background: #16a34a; color: #fff; }
.step.is-current .step-marker { background: var(--hw-primary); color: #fff; }

/* --------------------------------------------------------------------------
   Misc
   -------------------------------------------------------------------------- */

.dropdown-menu {
    border-color: var(--hw-border);
    border-radius: var(--hw-radius);
    box-shadow: var(--hw-shadow-lg);
    padding: .375rem;
}

.dropdown-item { border-radius: var(--hw-radius-sm); padding: .5rem .75rem; font-size: .875rem; }
.dropdown-item:active { background-color: var(--hw-primary); }

.modal-content { border: 0; border-radius: var(--hw-radius); box-shadow: var(--hw-shadow-lg); }
.modal-header { border-bottom: 1px solid var(--hw-border); padding: 1.125rem 1.25rem; }
.modal-body { padding: 1.25rem; }
.modal-footer { border-top: 1px solid var(--hw-border); padding: .875rem 1.25rem; }

.alert { border: 0; border-radius: var(--hw-radius); font-size: .9rem; padding: .875rem 1rem; }
.alert-success { background: #ecfdf5; color: #065f46; }
.alert-danger { background: #fef2f2; color: #991b1b; }
.alert-warning { background: #fffbeb; color: #92400e; }
.alert-info { background: #eff6ff; color: #1e40af; }

.toast-container { z-index: 1090; }

.filter-bar {
    background: var(--hw-surface);
    border: 1px solid var(--hw-border);
    border-radius: var(--hw-radius);
    padding: .875rem 1rem;
    box-shadow: var(--hw-shadow-xs);
}

.pagination { margin-bottom: 0; }
.page-link { border-color: var(--hw-border); color: var(--hw-body); font-size: .875rem; }
.page-item.active .page-link { background-color: var(--hw-primary); border-color: var(--hw-primary); }

.definition-list dt { font-size: .75rem; text-transform: uppercase; letter-spacing: .045em; color: var(--hw-muted); font-weight: 650; }
.definition-list dd { color: var(--hw-ink); font-weight: 500; margin-bottom: 1rem; }

.sparkline { display: flex; align-items: flex-end; gap: 3px; height: 44px; }

.sparkline span {
    flex: 1 1 auto;
    background: var(--hw-primary-soft);
    border-radius: 3px 3px 0 0;
    min-height: 3px;
    position: relative;
}

.sparkline span.has-value { background: var(--hw-primary); }

/* ==========================================================================
   BOOKING — day strip, slot grid, live countdown
   ========================================================================== */

.day-strip {
    display: flex;
    gap: .5rem;
    overflow-x: auto;
    padding-bottom: .375rem;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
}

.day-strip::-webkit-scrollbar { height: 5px; }
.day-strip::-webkit-scrollbar-track { background: transparent; }
.day-strip::-webkit-scrollbar-thumb { background: var(--hw-line); border-radius: 999px; }
.day-strip:hover::-webkit-scrollbar-thumb { background: var(--hw-border-strong); }

.day-chip {
    flex: 0 0 auto;
    min-width: 84px;
    padding: .8125rem .875rem;
    border-radius: var(--hw-radius-sm);
    border: 1px solid var(--hw-border);
    background: var(--hw-surface);
    text-align: center;
    color: var(--hw-body);
    box-shadow: var(--hw-shadow-xs);
    transition: border-color .12s ease, background-color .12s ease, transform .12s ease;
}

.day-chip:hover { border-color: var(--hw-border-strong); color: var(--hw-ink); transform: translateY(-1px); }

.day-chip.is-active {
    background: var(--hw-primary);
    border-color: var(--hw-primary);
    color: #fff;
    box-shadow: 0 6px 16px rgba(var(--hw-primary-rgb), .28);
}

.day-chip-label { display: block; font-size: .8125rem; font-weight: 650; }
.day-chip-date  { display: block; font-size: .6875rem; opacity: .7; }

/* Slot grid */
.slot-grid {
    display: grid;
    gap: .625rem;
    grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
}

.slot-grid-compact { grid-template-columns: repeat(auto-fill, minmax(116px, 1fr)); gap: .5rem; }

.slot { position: relative; }

.slot input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.slot label {
    display: block;
    padding: 1.0625rem .75rem;
    border: 1.5px solid var(--hw-border);
    border-radius: var(--hw-radius-sm);
    background: var(--hw-surface);
    cursor: pointer;
    text-align: center;
    transition: border-color .14s ease, background-color .14s ease, transform .14s ease, box-shadow .14s ease;
}

.slot label:hover { box-shadow: var(--hw-shadow-sm); }

.slot-time  { display: block; font-weight: 700; color: var(--hw-ink); font-size: 1rem; letter-spacing: -.01em; }
.slot-state { display: block; font-size: .7rem; color: var(--hw-muted); margin-top: .3rem; }

/* On the admin's slot map a free hour is a good thing — something the warden
   can still give away — so it reads green, against the red of one already
   taken. On the resident's grid a free slot is the default and stays neutral. */
.slot-map .slot[data-state="free"] label {
    border-color: #bbf7d0;
    background: #f0fdf6;
}

.slot-map .slot[data-state="free"] .slot-time  { color: #047857; }
.slot-map .slot[data-state="free"] .slot-state { color: #059669; font-weight: 600; }

.slot[data-state="free"] label:hover {
    border-color: var(--hw-primary);
    background: var(--hw-primary-soft);
    transform: translateY(-2px);
}

.slot input:checked + label {
    border-color: var(--hw-primary);
    background: var(--hw-primary-soft);
    box-shadow: 0 0 0 3px rgba(var(--hw-primary-rgb), .16);
}

.slot input:focus-visible + label {
    outline: 3px solid rgba(var(--hw-primary-rgb), .35);
    outline-offset: 2px;
}

/* Taken / mine / past states */
.slot[data-state="taken"] label {
    border-color: #fecaca;
    background: #fef2f2;
    cursor: not-allowed;
}
.slot[data-state="taken"] .slot-time  { color: #b91c1c; }
.slot[data-state="taken"] .slot-state { color: #dc2626; font-weight: 600; }

.slot[data-state="mine"] label {
    border-color: #6ee7b7;
    background: #ecfdf5;
    cursor: default;
}
.slot[data-state="mine"] .slot-time  { color: #047857; }
.slot[data-state="mine"] .slot-state { color: #059669; font-weight: 700; }

.slot[data-state="past"] label {
    border-color: var(--hw-border);
    background: #f8fafc;
    opacity: .5;
    cursor: not-allowed;
}

/* A slot taken by somebody else while you were looking at it */
@keyframes hw-slot-taken {
    0%   { transform: scale(1); }
    40%  { transform: scale(0.94); }
    100% { transform: scale(1); }
}

.slot.just-taken label { animation: hw-slot-taken .45s ease; }

.slot-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    font-size: .75rem;
    color: var(--hw-muted);
}

.slot-legend span { display: inline-flex; align-items: center; gap: .3rem; }

/* Live booking banner with countdown */
.booking-live {
    background: linear-gradient(135deg, #1d4ed8 0%, #2563eb 55%, #3b82f6 100%);
    color: #fff;
    border: 0;
}

.booking-live .card-body { padding: 1.5rem; }
.booking-live .badge { font-weight: 600; }

.countdown-label { font-size: .6875rem; text-transform: uppercase; letter-spacing: .07em; }

.countdown-clock {
    font-size: 2rem;
    font-weight: 700;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

.countdown-clock.is-live { color: #bbf7d0; }
.countdown-clock.is-over { opacity: .6; }

/* ==========================================================================
   LANDING PAGE
   ========================================================================== */

.landing-body { background: var(--hw-surface); }

.landing-container {
    width: 100%;
    max-width: 1160px;
    margin: 0 auto;
    padding: 0 1.25rem;
}

/* Nav */
.landing-nav {
    position: sticky;
    top: 0;
    z-index: 1030;
    padding: .875rem 0;
    background: rgba(255, 255, 255, .72);
    backdrop-filter: saturate(180%) blur(14px);
    border-bottom: 1px solid transparent;
    transition: border-color .2s ease, box-shadow .2s ease, background-color .2s ease;
}

.landing-nav.is-stuck {
    border-bottom-color: var(--hw-border);
    box-shadow: 0 4px 20px rgba(15, 23, 42, .05);
}

.landing-brand { display: flex; align-items: center; gap: .75rem; color: var(--hw-ink); }
.landing-brand:hover { color: var(--hw-ink); }
.landing-brand-sub { font-size: .7rem; color: var(--hw-muted); }

.landing-links a {
    color: var(--hw-muted);
    font-weight: 550;
    font-size: .9rem;
    transition: color .12s ease;
}

.landing-links a:hover { color: var(--hw-primary); }

.btn-ghost {
    background: transparent;
    color: var(--hw-body);
    border: 1px solid var(--hw-border);
}

.btn-ghost:hover { background: var(--hw-canvas); color: var(--hw-ink); border-color: var(--hw-border-strong); }

.btn-xl { padding: .875rem 1.5rem; font-size: 1rem; border-radius: 12px; }

/* Hero */
/* A white hero. The indigo-and-violet wash that used to sit here made the page
   look like a different product from the one the hostel poster describes, and it
   fought the photographs further down. Colour belongs on the artwork and the
   buttons, not spread across the whole first screen. */
.landing-hero {
    position: relative;
    overflow: hidden;
    padding: 4rem 0 5rem;
    background: var(--hw-surface);
}

@media (min-width: 992px) { .landing-hero { padding: 6rem 0 7rem; } }

.hero-orb {
    position: absolute;
    border-radius: 50%;
    filter: blur(90px);
    opacity: .5;
    pointer-events: none;
}

/* One faint blue haze behind the artwork, and nothing on the text side. */
.hero-orb-1 { display: none; }
.hero-orb-2 {
    width: 620px; height: 620px;
    background: #bfdbfe;
    top: -120px; right: -180px;
    opacity: .55;
}

.hero-grid-lines {
    position: absolute;
    inset: 0;
    /* Barely-there ruling. At the old strength it read as graph paper. */
    background-image:
        linear-gradient(rgba(37, 99, 235, .028) 1px, transparent 1px),
        linear-gradient(90deg, rgba(37, 99, 235, .028) 1px, transparent 1px);
    background-size: 56px 56px;
    mask-image: radial-gradient(ellipse 70% 55% at 50% 40%, #000 40%, transparent 100%);
    -webkit-mask-image: radial-gradient(ellipse 70% 55% at 50% 40%, #000 40%, transparent 100%);
    pointer-events: none;
}

.landing-hero .landing-container { position: relative; z-index: 2; }

.hero-pill {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    padding: .375rem .875rem;
    border-radius: 999px;
    background: var(--hw-primary-soft);
    color: var(--hw-primary);
    font-size: .8125rem;
    font-weight: 650;
    margin-bottom: 1.25rem;
}

.hero-pill-dot {
    width: 7px; height: 7px; border-radius: 50%;
    background: var(--hw-primary);
    animation: hw-live 2s infinite;
}

.hero-title {
    font-size: clamp(2.25rem, 5.5vw, 3.75rem);
    font-weight: 800;
    line-height: 1.06;
    letter-spacing: -.03em;
    margin-bottom: 1.125rem;
}

.hero-gradient {
    display: block;
    background: linear-gradient(100deg, #1d4ed8, #2563eb 45%, #3b82f6);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

.hero-lead {
    font-size: 1.0625rem;
    color: var(--hw-muted);
    max-width: 34rem;
    margin-bottom: 1.75rem;
}

.hero-actions { display: flex; flex-wrap: wrap; gap: .75rem; margin-bottom: 2.5rem; }

.hero-stats { display: flex; flex-wrap: wrap; gap: 2.25rem; }
.hero-stat-value { display: block; font-size: 1.75rem; font-weight: 800; color: var(--hw-ink); line-height: 1; }
.hero-stat-label { display: block; font-size: .8125rem; color: var(--hw-muted); margin-top: .2rem; }

/* Hero visual */
/* Room at the bottom for the summary card, which now hangs below the disc. */
.hero-visual { position: relative; padding: 1rem 1rem 5.5rem; }

/* Build credit. Quiet by design: it belongs on the page but should never
   compete with the hostel's own name. */
.app-credit {
    margin: 2.5rem 0 0;
    padding-top: 1.25rem;
    border-top: 1px solid var(--hw-border);
    text-align: center;
    font-size: .78rem;
    color: var(--hw-faint);
    letter-spacing: .01em;
}

.app-credit strong { color: var(--hw-muted); font-weight: 640; }
.app-credit-sep { margin: 0 .4rem; opacity: .6; }

/* On the landing footer the rule above would double up with the one already
   there, and the ground is tinted rather than white. */
.landing-footer .app-credit { border-top: 0; margin-top: .75rem; padding-top: 0; }

/* --------------------------------------------------------------------------
   Landing hero — the reference layout
   -------------------------------------------------------------------------- */

/* The headline's closing line carries the accent. A flat colour rather than a
   gradient: at display size a gradient fights the photograph beside it. */
.hero-accent { color: var(--hw-primary); display: block; }

/* Four capabilities, side by side under the buttons. */
.hero-features {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 1.25rem;
    margin-top: 2.5rem;
}

.hero-feature { display: flex; flex-direction: column; gap: .5rem; }

.hero-feature-icon {
    width: 46px;
    height: 46px;
    border-radius: 13px;
    display: grid;
    place-items: center;
    font-size: 1.15rem;
}

/* Each capability gets its own hue, so the row reads as four things rather
   than one striped block. */
.hero-feature-icon.is-blue   { background: #e8f1ff; color: #2563eb; }
.hero-feature-icon.is-green  { background: #e6f6ee; color: #0f9d68; }
.hero-feature-icon.is-purple { background: #f0eaff; color: #7c3aed; }
.hero-feature-icon.is-amber  { background: #fef3e2; color: #d97706; }

.hero-feature-title { font-weight: 640; font-size: .9375rem; color: var(--hw-ink); }
.hero-feature-sub   { font-size: .8125rem; color: var(--hw-muted); line-height: 1.4; }

.hero-trust {
    display: flex;
    align-items: flex-start;
    gap: .875rem;
    margin-top: 2.25rem;
    font-size: .875rem;
}

.hero-trust-icon {
    width: 40px;
    height: 40px;
    border-radius: 12px;
    display: grid;
    place-items: center;
    background: var(--hw-primary-soft);
    color: var(--hw-primary);
    flex: 0 0 auto;
}

.hero-trust strong { color: var(--hw-ink); }

/* The dashboard sample, floating over the artwork. */
.hero-summary {
    position: absolute;
    right: 0;
    /* Sits below the machine rather than across it. Overlapping the subject is
       what made the first pass look like a dialog had opened over the artwork. */
    bottom: -1.5rem;
    width: min(23rem, 96%);
    background: var(--hw-surface);
    border: 1px solid var(--hw-border);
    border-radius: var(--hw-radius);
    box-shadow: var(--hw-shadow-lg);
    padding: 1.125rem 1.25rem 1.25rem;
    z-index: 3;
}

.hero-summary-head {
    display: flex;
    align-items: center;
    gap: .625rem;
    padding-bottom: .875rem;
    margin-bottom: .875rem;
    border-bottom: 1px solid var(--hw-border);
}

.hero-summary-mark {
    width: 34px;
    height: 34px;
    border-radius: 10px;
    display: grid;
    place-items: center;
    background: var(--hw-primary-soft);
    color: var(--hw-primary);
}

.hero-summary-body { display: flex; align-items: center; gap: 1.125rem; }

.hero-summary-ring { position: relative; width: 92px; height: 92px; flex: 0 0 auto; }
.hero-summary-ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }

.hero-summary-ring-text {
    position: absolute;
    inset: 0;
    display: grid;
    place-content: center;
    text-align: center;
    line-height: 1.1;
}

.hero-summary-ring-text strong { font-size: 1.25rem; color: var(--hw-ink); }
.hero-summary-ring-text span   { font-size: .6875rem; color: var(--hw-muted); }

.hero-summary-title { font-weight: 650; color: var(--hw-ink); font-size: 1.0625rem; }
.hero-summary-sub   { font-size: .8125rem; color: var(--hw-muted); margin: .15rem 0 .75rem; }

/* The four facts, on one card that straddles the bottom of the hero. */
.hero-facts {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: .5rem;
    margin-top: 3.5rem;
    padding: 1.25rem 1.5rem;
    background: var(--hw-surface);
    border: 1px solid var(--hw-border);
    border-radius: var(--hw-radius-lg);
    box-shadow: var(--hw-shadow-md);
}

.hero-fact { display: flex; align-items: center; gap: .875rem; padding: .25rem .5rem; }

.hero-fact-icon {
    width: 44px;
    height: 44px;
    border-radius: 13px;
    display: grid;
    place-items: center;
    font-size: 1.15rem;
    flex: 0 0 auto;
}

.hero-fact-icon.is-blue   { background: #e8f1ff; color: #2563eb; }
.hero-fact-icon.is-green  { background: #e6f6ee; color: #0f9d68; }
.hero-fact-icon.is-purple { background: #f0eaff; color: #7c3aed; }
.hero-fact-icon.is-amber  { background: #fef3e2; color: #d97706; }

.hero-fact-value { font-size: 1.375rem; font-weight: 700; color: var(--hw-ink); line-height: 1.1; letter-spacing: -.02em; }
.hero-fact-label { font-size: .8125rem; font-weight: 560; color: var(--hw-body); }
.hero-fact-sub   { font-size: .72rem; color: var(--hw-faint); }

/* Outline button, the quieter half of the hero's pair. */
.btn-outline {
    background: var(--hw-surface);
    border: 1px solid var(--hw-border-strong);
    color: var(--hw-ink);
    font-weight: 560;
}

.btn-outline:hover { background: var(--hw-canvas); border-color: var(--hw-primary); color: var(--hw-primary); }

@media (max-width: 991.98px) {
    .hero-features { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; }
    .hero-facts    { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .hero-summary  { position: static; width: 100%; margin-top: 1.25rem; }
}

@media (max-width: 575.98px) {
    .hero-features { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .hero-facts    { grid-template-columns: 1fr; gap: .25rem; }
}

/* The stage holds both renderings: WebGL paints into a canvas it appends here,
   and the fallback sits underneath until that happens. */
.hero-stage {
    position: relative;
    width: 100%;
    max-width: 430px;
    aspect-ratio: 1;
    margin: 0 auto;
}

/* A soft blue disc behind the machine. It gives the object a ground to sit
   against, which is what stops it reading as cut out and floating. */
.hero-stage::before {
    content: '';
    position: absolute;
    inset: -6% -6% 14% -6%;
    border-radius: 50% 50% 44% 44%;
    background: linear-gradient(165deg, #dbeafe 0%, #bfdbfe 55%, #93c5fd 100%);
    z-index: 0;
}

.hero-stage canvas,
.hero-stage .scene-fallback { position: relative; z-index: 1; }

.hero-stage canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    filter: drop-shadow(0 26px 52px rgba(67, 56, 202, .26));
}

/* Once a canvas is in place the fallback would only show through it. */
.has-webgl .scene-fallback { display: none; }

.hero-lottie {
    width: 100%;
    max-width: 420px;
    aspect-ratio: 1;
    margin: 0 auto;
    filter: drop-shadow(0 24px 48px rgba(67, 56, 202, .22));
}

/* Scene hosts on the two dashboards. */
.scene-stage {
    position: relative;
    width: 100%;
    aspect-ratio: 1;
    max-width: 260px;
    margin-inline: auto;
}

.scene-stage canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
}

.scene-stage-sm { max-width: 190px; aspect-ratio: 1; margin: 0; }

/* The warden's status strip: the machine, then what it means in words. */
.machine-status {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: .25rem .25rem 1rem;
}

.machine-status-label {
    font-weight: 660;
    color: var(--hw-ink);
    font-size: 1.0625rem;
    letter-spacing: -.012em;
}

.machine-status-hint { font-size: .8125rem; color: var(--hw-muted); }

@media (max-width: 575.98px) {
    .machine-status { flex-direction: column; text-align: center; gap: .5rem; }
    .scene-stage-sm { margin-inline: auto; }
}

.hero-card {
    position: absolute;
    display: flex;
    align-items: center;
    gap: .625rem;
    padding: .625rem .875rem;
    border-radius: 12px;
    background: rgba(255, 255, 255, .92);
    backdrop-filter: blur(10px);
    border: 1px solid var(--hw-border);
    box-shadow: var(--hw-shadow-lg);
    font-size: .8125rem;
    animation: hw-float 5s ease-in-out infinite;
}

.hero-card i { font-size: 1.15rem; }

.hero-card-1 { top: 8%;  left: -2%;  animation-delay: 0s; }
.hero-card-2 { bottom: 16%; right: -2%; animation-delay: 1.1s; }
.hero-card-3 { bottom: 2%; left: 6%;   animation-delay: 2.2s; }

@keyframes hw-float {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-10px); }
}

@media (max-width: 575px) {
    .hero-card { position: static; margin-top: .625rem; animation: none; }
    .hero-visual { padding: 0; }
}

/* Sections */
.landing-section { padding: 4.5rem 0; }
.landing-section-tint { background: var(--hw-canvas); }

.section-head { text-align: center; max-width: 40rem; margin: 0 auto 3rem; }

.section-tag {
    display: inline-block;
    font-size: .6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .1em;
    color: var(--hw-primary);
    margin-bottom: .75rem;
}

.section-title {
    font-size: clamp(1.65rem, 3.6vw, 2.4rem);
    font-weight: 750;
    letter-spacing: -.025em;
    margin-bottom: .75rem;
}

.section-lead { color: var(--hw-muted); font-size: 1.0125rem; margin: 0; }

/* Steps */
.step-grid { display: grid; gap: 1.25rem; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }

.step-card {
    position: relative;
    background: var(--hw-surface);
    border: 1px solid var(--hw-border);
    border-radius: var(--hw-radius);
    padding: 1.75rem 1.375rem 1.375rem;
    box-shadow: var(--hw-shadow-sm);
    transition: transform .16s ease, box-shadow .16s ease;
}

.step-card:hover { transform: translateY(-4px); box-shadow: var(--hw-shadow-md); }

.step-number {
    position: absolute;
    top: -14px; left: 1.375rem;
    width: 30px; height: 30px;
    border-radius: 9px;
    background: linear-gradient(135deg, var(--hw-primary), #3b82f6);
    color: #fff;
    display: grid; place-items: center;
    font-weight: 700; font-size: .8125rem;
    box-shadow: 0 6px 14px rgba(var(--hw-primary-rgb), .3);
}

.step-icon {
    display: inline-grid; place-items: center;
    width: 44px; height: 44px;
    border-radius: 12px;
    background: var(--hw-primary-soft);
    color: var(--hw-primary);
    font-size: 1.2rem;
    margin-bottom: .875rem;
}

.step-card h3 { font-size: 1.0625rem; margin-bottom: .375rem; }
.step-card p  { color: var(--hw-muted); font-size: .875rem; margin: 0; }

/* Pricing */
.price-card {
    height: 100%;
    border-radius: var(--hw-radius-lg);
    padding: 2rem 1.75rem;
    border: 1px solid var(--hw-border);
    background: var(--hw-surface);
    box-shadow: var(--hw-shadow-sm);
    transition: transform .16s ease, box-shadow .16s ease;
}

.price-card:hover { transform: translateY(-4px); box-shadow: var(--hw-shadow-lg); }

/* Both plans are light cards. The recommended one is marked by a blue tint and
   a blue rule, not by being a dark slab — two dark blocks in the middle of a
   white page read as holes punched in it. */
.price-card-free {
    background: linear-gradient(170deg, #f5f9ff, #eaf2ff);
    border: 1px solid rgba(37, 99, 235, .22);
    box-shadow: 0 10px 30px -12px rgba(37, 99, 235, .22);
}

.price-card-free .price-value { color: var(--hw-primary); }
.price-card-free .price-badge { background: var(--hw-primary); color: #fff; }

.price-card-paid { background: var(--hw-surface); }
.price-card-paid .price-value { color: var(--hw-ink); }
.price-card-paid .price-badge { background: var(--hw-neutral-soft); color: var(--hw-neutral-fg); }

.price-badge {
    display: inline-block;
    padding: .3rem .75rem;
    border-radius: 999px;
    font-size: .75rem;
    font-weight: 700;
    margin-bottom: 1rem;
}

.price-value {
    font-size: 3rem;
    font-weight: 800;
    line-height: 1;
    letter-spacing: -.03em;
    color: var(--hw-ink);
    margin-bottom: .75rem;
}

.price-value span { font-size: 1rem; font-weight: 600; color: var(--hw-muted); margin-left: .35rem; }

.price-note { font-size: .9rem; margin-bottom: 1.25rem; color: var(--hw-body); }

.price-list { list-style: none; padding: 0; margin: 0; display: grid; gap: .5rem; font-size: .875rem; color: var(--hw-body); }
.price-list i { color: var(--hw-success); margin-right: .4rem; }

/* Limit banner */
.limit-banner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1.25rem;
    margin-top: 2rem;
    padding: 1.5rem 1.75rem;
    border-radius: var(--hw-radius-lg);
    /* Was near-black. A tinted panel says the same thing without a slab. */
    background: linear-gradient(135deg, #eef4ff, #e4edff);
    border: 1px solid rgba(37, 99, 235, .18);
    color: #fff;
    box-shadow: var(--hw-shadow-lg);
}

.limit-icon {
    width: 52px; height: 52px;
    border-radius: 14px;
    background: rgba(37, 99, 235, .12);
    color: var(--hw-primary);
    display: grid; place-items: center;
    font-size: 1.4rem;
    flex: 0 0 auto;
}

.limit-banner h3 { color: var(--hw-ink); font-size: 1.15rem; margin-bottom: .25rem; }
.limit-banner h3 strong { color: var(--hw-primary); }
.limit-banner p { color: var(--hw-muted); font-size: .875rem; margin: 0; max-width: 44rem; }

.limit-hours { margin-left: auto; text-align: right; }
.limit-hours-label { display: block; font-size: .6875rem; text-transform: uppercase; letter-spacing: .08em; color: var(--hw-faint); }
.limit-hours-value { display: block; font-weight: 700; font-size: 1.05rem; color: var(--hw-ink); }

/* Rules */
.rule-grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }

.rule-card {
    background: var(--hw-surface);
    border: 1px solid var(--hw-border);
    border-radius: var(--hw-radius);
    padding: 1.5rem 1.25rem;
    text-align: center;
    transition: transform .16s ease, border-color .16s ease;
}

.rule-card:hover { transform: translateY(-3px); border-color: var(--hw-primary); }

.rule-icon {
    display: inline-grid; place-items: center;
    width: 52px; height: 52px;
    border-radius: 50%;
    background: var(--hw-primary-soft);
    color: var(--hw-primary);
    font-size: 1.35rem;
    margin-bottom: .875rem;
}

.rule-card h3 { font-size: .9375rem; margin-bottom: .375rem; }
.rule-card p  { color: var(--hw-muted); font-size: .8125rem; margin: 0; }

/* Gallery */
.gallery-grid {
    display: grid;
    gap: .875rem;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}

/* On a phone the 200px minimum collapses to one column, and ten portrait
   photos become several screens of scrolling before the rest of the page.
   Two narrower columns keep the gallery to a glance. */
/* Dense packing so a later small tile backfills any hole the feature leaves. */
.gallery-grid { grid-auto-flow: dense; }

@media (max-width: 575.98px) {
    .gallery-grid {
        grid-template-columns: repeat(2, 1fr);
        gap: .625rem;
    }
}

.gallery-item {
    margin: 0;
    border-radius: var(--hw-radius);
    overflow: hidden;
    box-shadow: var(--hw-shadow-sm);
    aspect-ratio: 4 / 5;
    background: var(--hw-border);
}

/* The first photo is the feature: two columns wide and two rows tall, so it
   reads as the establishing shot rather than one tile among many. It has to
   span BOTH axes — spanning only columns while keeping a 4/5 ratio made it
   twice as tall as its row and left holes the rest of the grid flowed around. */
.gallery-item-wide { grid-column: span 2; grid-row: span 2; aspect-ratio: auto; }

@media (max-width: 575px) { .gallery-item-wide { grid-column: span 2; grid-row: span 1; } }

.gallery-item img {
    width: 100%; height: 100%;
    object-fit: cover;
    display: block;
    transition: transform .5s cubic-bezier(.2, .8, .2, 1);
}

.gallery-item:hover img { transform: scale(1.06); }

/* CTA */
.landing-cta { padding: 4rem 0 5rem; }

.cta-panel {
    position: relative;
    overflow: hidden;
    text-align: center;
    padding: 3.5rem 1.75rem;
    border-radius: var(--hw-radius-lg);
    background:
        radial-gradient(120% 140% at 0% 0%, rgba(37, 99, 235, .10) 0%, transparent 58%),
        linear-gradient(135deg, #f2f7ff, #e9f1ff);
    border: 1px solid rgba(37, 99, 235, .18);
    color: var(--hw-ink);
    box-shadow: var(--hw-shadow-lg);
}

.cta-lottie { width: 110px; height: 110px; margin: 0 auto 1rem; }
.cta-panel h2 { color: var(--hw-ink); font-size: clamp(1.6rem, 3.4vw, 2.25rem); margin-bottom: .625rem; }
.cta-panel p  { color: var(--hw-muted); max-width: 34rem; margin: 0 auto 1.75rem; }

/* Footer */
.landing-footer {
    padding: 2.5rem 0;
    background: var(--hw-canvas);
    border-top: 1px solid var(--hw-border);
    color: var(--hw-muted);
}

.landing-footer hr { margin: 1.5rem 0 1rem; border-color: var(--hw-border); }

/* Scroll reveal */
.reveal {
    opacity: 0;
    transform: translateY(22px);
    transition: opacity .6s cubic-bezier(.2, .8, .2, 1), transform .6s cubic-bezier(.2, .8, .2, 1);
    will-change: opacity, transform;
}

.reveal.is-visible { opacity: 1; transform: none; }

/* Never hide content when motion is reduced or JS never runs. */
@media (prefers-reduced-motion: reduce) {
    .reveal { opacity: 1 !important; transform: none !important; transition: none; }
    .hero-card, .hero-pill-dot { animation: none !important; }
}

.no-js .reveal { opacity: 1; transform: none; }

/* --------------------------------------------------------------------------
   Live notifications
   -------------------------------------------------------------------------- */

/* The bell badge pulses once when the count goes up. */
@keyframes hw-pop {
    0%   { transform: translate(-50%, -50%) scale(1); }
    45%  { transform: translate(-50%, -50%) scale(1.45); }
    100% { transform: translate(-50%, -50%) scale(1); }
}

.badge-pop { animation: hw-pop .45s cubic-bezier(.34, 1.56, .64, 1); }

/* A freshly arrived row flashes, then settles. */
@keyframes hw-flash-in {
    from { background: rgba(var(--hw-primary-rgb), .16); transform: translateY(-6px); opacity: 0; }
    to   { background: rgba(var(--hw-primary-rgb), .035); transform: none; opacity: 1; }
}

.notif-item.is-fresh { animation: hw-flash-in .55s ease-out; }

/* "Live" indicator next to the notifications heading. */
.live-dot {
    display: inline-flex;
    align-items: center;
    gap: .375rem;
    font-size: .6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .07em;
    color: #059669;
}

.live-dot::before {
    content: "";
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #10b981;
    box-shadow: 0 0 0 0 rgba(16, 185, 129, .6);
    animation: hw-live 2s infinite;
}

@keyframes hw-live {
    0%   { box-shadow: 0 0 0 0 rgba(16, 185, 129, .55); }
    70%  { box-shadow: 0 0 0 7px rgba(16, 185, 129, 0); }
    100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
}

.live-dot.is-offline { color: var(--hw-faint); }
.live-dot.is-offline::before { background: var(--hw-faint); animation: none; box-shadow: none; }

/* Toasts raised by the poller */
.toast-live { border-left: 4px solid var(--hw-primary); }

@media (prefers-reduced-motion: reduce) {
    .badge-pop, .notif-item.is-fresh, .live-dot::before { animation: none !important; }
    .stat-card, .strength-bar > span, .usage-ring-fill, .mini-meter > span { transition: none !important; }
}

/* ==========================================================================
   MOBILE — checked at 360, 390 and 768 px.

   The rules below are the ones that actually break on a phone: fixed-width
   grids, side-by-side headers, and controls that fall under the thumb.
   ========================================================================== */

@media (max-width: 767.98px) {

    /* Tighter frame, but never edge-to-edge text. */
    .app-content { padding: 1.125rem .875rem 3rem; }

    .page-head h1, .page-head .h1 { font-size: 1.3rem; }
    .page-title { font-size: 1.15rem; }

    /* Cards and panels lose a little padding so content keeps its width. */
    .card-body { padding: 1rem; }
    .card-header { padding: .875rem 1rem; }

    /* Stat tiles: two per row instead of squeezing four. */
    .stat-value { font-size: 1.5rem; }
    .stat-icon { width: 36px; height: 36px; font-size: 1rem; }

    /* Quick actions become a single column — full-width tap targets. */
    .action-grid { grid-template-columns: 1fr; }

    /* Slots: three per row on a phone, still comfortably tappable. */
    .slot-grid { grid-template-columns: repeat(3, 1fr); gap: .5rem; }
    .slot label { padding: .625rem .375rem; }
    .slot-time { font-size: .8125rem; }
    .slot-state { font-size: .625rem; }

    /* Day chips scroll horizontally rather than wrapping into a tall block. */
    .day-strip { margin-inline: -.875rem; padding-inline: .875rem; }

    /* Booking banner stacks; the countdown gets its own line. */
    .booking-live .card-body { padding: 1.125rem; }
    .countdown-clock { font-size: 1.6rem; }

    /* Detail rows stack label over value so long values are not squeezed. */
    .detail-row { flex-direction: column; align-items: flex-start; gap: .15rem; padding: .6875rem 0; }
    .detail-row dd { text-align: left; }

    /* Filter bars: controls go full width instead of cramming onto one row. */
    .filter-bar { padding: .75rem; }

    /* Notifications: a little tighter, and the actions wrap under the text. */
    .notif-item { padding: .875rem 1rem; gap: .75rem; }
    .notif-icon { width: 32px; height: 32px; font-size: .9rem; }

    /* Modals sit flush to the bottom, which is easier to reach one-handed. */
    .modal-dialog { margin: .625rem; }

    /* Tables keep their own scroll container, never the page. */
    .table > thead > tr > th,
    .table > tbody > tr > td { padding: .6875rem .75rem; }

    /* Landing */
    .landing-section { padding: 3rem 0; }
    .landing-hero { padding: 2.5rem 0 3rem; }
    .hero-stats { gap: 1.5rem; }
    .hero-actions .btn { width: 100%; }
    .limit-banner { padding: 1.25rem; }
    .limit-hours { margin-left: 0; text-align: left; }
    .price-card { padding: 1.5rem 1.25rem; }
    .price-value { font-size: 2.5rem; }
    .cta-panel { padding: 2.5rem 1.25rem; }
    .btn-xl { padding: .75rem 1.25rem; font-size: .9375rem; }
}

@media (max-width: 400px) {
    /* Very small phones: two slots per row keeps the label readable. */
    .slot-grid { grid-template-columns: repeat(2, 1fr); }
    .usage-ring-value { font-size: 1.85rem; }
    .chip { font-size: .72rem; padding: .25rem .5rem; }
}

/* Give every interactive control a comfortable touch target. */
@media (pointer: coarse) {
    .btn { min-height: 42px; }
    .btn-sm { min-height: 36px; }
    .slot label { min-height: 58px; display: grid; align-content: center; }
    .day-chip { min-height: 54px; }
    .form-control, .form-select { min-height: 44px; }
    .page-link { min-width: 40px; min-height: 40px; display: grid; place-items: center; }
}

@media print {
    .app-sidebar, .app-topbar, .btn, .filter-bar { display: none !important; }
    .app-main { margin-left: 0; }
    body { background: #fff; }
}

/* ==========================================================================
   Usage roster — stat tiles, bucket chips, per-row rings
   ========================================================================== */

/* Horizontal tile: the icon anchors the left edge and the number, label and
   caption stack beside it. Distinct from .stat-card, which stacks vertically —
   a page that leads with three of these needs them shorter than the dashboard's,
   or the table below is pushed off the first screen. */
.stat-tile .card-body {
    display: flex;
    align-items: center;
    gap: .875rem;
    padding: 1.125rem 1.25rem;
}

.stat-tile-icon {
    width: 46px;
    height: 46px;
    border-radius: 14px;
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    font-size: 1.15rem;
    background: var(--hw-neutral-soft);
    color: var(--hw-neutral-fg);
}

.stat-tile-icon.is-primary { background: var(--hw-primary-soft); color: var(--hw-primary); }
.stat-tile-icon.is-success { background: var(--hw-success-soft); color: var(--hw-success-fg); }
.stat-tile-icon.is-warning { background: var(--hw-warning-soft); color: var(--hw-warning-fg); }

.stat-tile-value {
    font-size: 1.75rem;
    font-weight: 700;
    line-height: 1;
    letter-spacing: -.03em;
    color: var(--hw-ink);
}

.stat-tile-label {
    font-size: .875rem;
    font-weight: 620;
    color: var(--hw-ink);
    margin-top: .3125rem;
}

.stat-tile-sub {
    font-size: .75rem;
    color: var(--hw-faint);
    margin-top: .125rem;
}

/* Bucket chips. Links rather than buttons, so each list is addressable and the
   browser's own history works on them. */
.bucket-row {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    margin-top: .875rem;
}

.bucket {
    display: inline-flex;
    align-items: center;
    gap: .4375rem;
    padding: .4375rem .6875rem;
    border: 1px solid var(--hw-border);
    border-radius: 999px;
    background: var(--hw-surface);
    color: var(--hw-muted);
    font-size: .8125rem;
    font-weight: 560;
    text-decoration: none;
    transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}

.bucket:hover { border-color: var(--hw-border-strong); color: var(--hw-ink); background: var(--hw-canvas); }
.bucket i { font-size: .875rem; }

.bucket-count {
    min-width: 1.375rem;
    padding: 0 .3125rem;
    border-radius: 999px;
    background: var(--hw-canvas);
    color: var(--hw-muted);
    font-size: .6875rem;
    font-weight: 700;
    line-height: 1.375rem;
    text-align: center;
}

.bucket.is-active {
    background: var(--hw-primary-soft);
    border-color: rgba(37, 99, 235, .3);
    color: var(--hw-primary);
}

.bucket.is-active .bucket-count { background: var(--hw-primary); color: #fff; }

/* Row-level ring. Same reading as the dashboard's big one, at a size that fits
   a table cell without stretching the row. */
.ring-mini {
    position: relative;
    width: 58px;
    height: 58px;
    flex: 0 0 auto;
    display: grid;
    place-items: center;
}

.ring-mini svg {
    width: 100%;
    height: 100%;
    transform: rotate(-90deg);
    grid-area: 1 / 1;
}

.ring-mini-track { fill: none; stroke: var(--hw-border); stroke-width: 4.5; }

.ring-mini-fill {
    fill: none;
    stroke: var(--hw-primary);
    stroke-width: 4.5;
    stroke-linecap: round;
    transition: stroke-dasharray .4s ease;
}

.ring-mini.is-warning .ring-mini-fill { stroke: var(--hw-warning); }
.ring-mini.is-full .ring-mini-fill    { stroke: var(--hw-danger); }

.ring-mini-label {
    grid-area: 1 / 1;
    font-size: .6875rem;
    font-weight: 700;
    letter-spacing: -.01em;
    color: var(--hw-ink);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.usage-cell { display: flex; align-items: center; gap: .75rem; }

.usage-cell-note { font-size: .75rem; color: var(--hw-faint); }
.usage-cell-note.is-spent { color: var(--hw-danger-fg); font-weight: 560; }

/* The roster reads as a stack of cards rather than a ruled grid: taller rows,
   a hairline instead of a full border, and a coloured edge on the one state
   that needs the warden to act. */
.roster-card tbody tr > td { padding-top: .875rem; padding-bottom: .875rem; }

.roster-card tbody tr > td:first-child { box-shadow: inset 3px 0 0 transparent; }

.roster-card tbody tr.is-spent > td:first-child { box-shadow: inset 3px 0 0 var(--hw-danger); }

.roster-card tbody tr.is-spent { background: color-mix(in srgb, var(--hw-danger-soft) 45%, transparent); }

/* Secondary until you reach for it — recording by hand is the override, not
   the normal path, so it should not outshout the rest of the row. */
.btn-record {
    background: var(--hw-surface);
    border: 1px solid var(--hw-border);
    color: var(--hw-primary);
    font-weight: 600;
}

.btn-record:hover,
.btn-record:focus-visible {
    background: var(--hw-primary);
    border-color: var(--hw-primary);
    color: #fff;
}

@media (max-width: 575.98px) {
    .stat-tile .card-body { padding: .9375rem 1rem; gap: .75rem; }
    .stat-tile-icon { width: 40px; height: 40px; border-radius: 12px; font-size: 1rem; }
    .stat-tile-value { font-size: 1.5rem; }
    .bucket { font-size: .75rem; padding: .375rem .5625rem; }
}

/* ==========================================================================
   Usage log — day headings and quiet destructive actions
   ========================================================================== */

.stat-tile-icon.is-danger { background: var(--hw-danger-soft); color: var(--hw-danger-fg); }

/* A heading row inside the table body. It sits in the table rather than above
   it so the columns stay aligned — a separate header per group would have to
   guess the column widths. */
.day-row > th {
    background: var(--hw-canvas);
    padding: .5rem 1rem;
    font-size: .75rem;
    font-weight: 700;
    letter-spacing: .02em;
    color: var(--hw-muted);
    border-top: 1px solid var(--hw-border);
    border-bottom: 1px solid var(--hw-border);
}

.day-row > th i { margin-right: .4375rem; color: var(--hw-primary); }

.roster-card tbody tr.day-row:first-child > th { border-top: 0; }

/* A cancelled record stays in the log — greyed rather than removed, because
   the page's whole claim is that nothing is deleted. */
.roster-card tbody tr.is-voided { color: var(--hw-faint); }
.roster-card tbody tr.is-voided .avatar { opacity: .6; }

/* Destructive, and rarely the right answer, so it reads as plain text until
   the pointer is on it. */
.btn-ghost-danger {
    background: transparent;
    border: 1px solid transparent;
    color: var(--hw-muted);
    font-weight: 560;
}

.btn-ghost-danger:hover,
.btn-ghost-danger:focus-visible {
    background: var(--hw-danger-soft);
    border-color: rgba(220, 38, 38, .28);
    color: var(--hw-danger-fg);
}

/* ==========================================================================
   Extra usage requests — the ask, the reason, the decision
   ========================================================================== */

.stat-tile-icon.is-info { background: #eef2ff; color: #4338ca; }

/* The requested quantity is the number the decision turns on, so it is the
   largest thing in the row after the resident's name. */
.ask-pill {
    display: inline-flex;
    align-items: baseline;
    gap: .3125rem;
    padding: .3125rem .625rem;
    border-radius: 10px;
    background: var(--hw-primary-soft);
    color: var(--hw-primary);
    font-size: .8125rem;
    font-weight: 600;
    white-space: nowrap;
}

.ask-pill strong { font-size: 1.0625rem; font-weight: 750; line-height: 1; }

/* The reason is free text a resident typed and the one thing the warden has
   to actually read, so it wraps to three lines instead of hiding behind a
   tooltip at sixty characters. */
.reason-cell { max-width: 16rem; min-width: 10rem; }

.reason-text {
    margin: 0;
    font-size: .8125rem;
    line-height: 1.45;
    color: var(--hw-muted);
    display: -webkit-box;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Awaiting a decision: the only row state that asks the warden to do something. */
.roster-card tbody tr.is-awaiting > td:first-child { box-shadow: inset 3px 0 0 var(--hw-primary); }

/* Approve above Reject rather than beside it — at this width a pair of buttons
   in one line is the first thing to overflow. */
.decide-cell form { display: block; }

.decide-cell .btn { min-width: 5.75rem; }

.decide-cell form + .btn { margin-top: .375rem; }

/* ==========================================================================
   User logs — timeline markers
   ========================================================================== */

.stat-tile-icon.is-neutral { background: var(--hw-neutral-soft); color: var(--hw-neutral-fg); }

.log-marker-cell { width: 3.25rem; padding-right: 0; }

/* The thread runs through the marker column, so the rows read as one sequence
   rather than as unrelated lines that happen to be stacked. */
.log-card tbody tr:not(.day-row) .log-marker-cell { position: relative; }

.log-card tbody tr:not(.day-row) .log-marker-cell::before {
    content: "";
    position: absolute;
    left: 2.5rem;
    top: 0;
    bottom: 0;
    width: 2px;
    background: var(--hw-border);
}

.log-marker {
    position: relative;
    z-index: 1;
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    font-size: .8125rem;
    background: var(--hw-neutral-soft);
    color: var(--hw-neutral-fg);
    box-shadow: 0 0 0 4px var(--hw-surface);
}

.log-marker.is-success   { background: var(--hw-success-soft); color: var(--hw-success-fg); }
.log-marker.is-danger    { background: var(--hw-danger-soft);  color: var(--hw-danger-fg); }
.log-marker.is-warning   { background: var(--hw-warning-soft); color: var(--hw-warning-fg); }
.log-marker.is-primary   { background: var(--hw-primary-soft); color: var(--hw-primary); }

/* The detail is the sentence that explains the badge, so it sits a step
   quieter than the resident's name beside it. */
.log-detail { font-size: .8125rem; color: var(--hw-muted); }

/* A heading row breaks the thread, which is the point — it is a new day. */
.log-card tbody tr.day-row + tr .log-marker-cell::before { top: .5rem; }
.log-card tbody tr:last-child .log-marker-cell::before   { bottom: auto; height: 50%; }

/* ==========================================================================
   Settings — card headers, switch rows, section nav
   ========================================================================== */

.set-head { display: flex; align-items: center; gap: .75rem; }

.set-icon {
    width: 36px;
    height: 36px;
    border-radius: 11px;
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    font-size: .9375rem;
    background: var(--hw-neutral-soft);
    color: var(--hw-neutral-fg);
}

.set-icon.is-primary { background: var(--hw-primary-soft); color: var(--hw-primary); }
.set-icon.is-success { background: var(--hw-success-soft); color: var(--hw-success-fg); }
.set-icon.is-info    { background: #eef2ff; color: #4338ca; }
.set-icon.is-neutral { background: var(--hw-neutral-soft); color: var(--hw-neutral-fg); }

.set-title {
    display: block;
    font-size: .9375rem;
    font-weight: 650;
    color: var(--hw-ink);
    line-height: 1.3;
}

.set-sub {
    display: block;
    margin-top: .0625rem;
    font-size: .75rem;
    font-weight: 400;
    color: var(--hw-faint);
    text-transform: none;
    letter-spacing: 0;
}

/* A rule and its switch on one line. The switch sits last because the sentence
   is the thing being agreed to; the control is the answer. */
.switch-row {
    display: flex;
    align-items: center;
    gap: 1.25rem;
    padding: 1rem 0;
    border-bottom: 1px solid var(--hw-border);
}

.switch-row:last-child { border-bottom: 0; }

.switch-row > div:first-child { flex: 1 1 auto; }

.switch-label {
    display: block;
    margin: 0;
    font-size: .875rem;
    font-weight: 600;
    color: var(--hw-ink);
    cursor: pointer;
}

.switch-help {
    margin: .1875rem 0 0;
    font-size: .78125rem;
    line-height: 1.45;
    color: var(--hw-muted);
}

/* Bootstrap's switch keeps its own left padding for a label that is no longer
   beside it, which would push the control away from the right edge. */
.switch-row .form-check {
    flex: 0 0 auto;
    min-height: 0;
    margin: 0;
    padding-left: 0;
}

.switch-row .form-check-input {
    width: 2.5rem;
    height: 1.375rem;
    margin: 0;
    cursor: pointer;
}

.section-nav .card-body { padding: .875rem 1rem; }

.section-nav-title {
    margin: 0 0 .5rem;
    font-size: .6875rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--hw-faint);
}

.section-nav a {
    display: flex;
    align-items: center;
    gap: .5625rem;
    padding: .4375rem .5rem;
    border-radius: 8px;
    font-size: .8125rem;
    font-weight: 560;
    color: var(--hw-muted);
    text-decoration: none;
}

.section-nav a:hover { background: var(--hw-canvas); color: var(--hw-primary); }
.section-nav a i { color: var(--hw-faint); font-size: .875rem; }
.section-nav a:hover i { color: var(--hw-primary); }

/* The anchor lands under the sticky topbar without this. */
.set-head[id] { scroll-margin-top: 5.5rem; }

/* An allowance the warden has not released yet: waiting on a decision, not
   spent. Blue rather than red — nothing has gone wrong. */
.usage-cell-note.is-held { color: var(--hw-primary); font-weight: 560; }

/* "Waiting for the warden" — an explanation, not a failure. Blue and calm, so
   a resident reads it as a step in the process rather than as a fault. */
.held-notice { border-color: rgba(37, 99, 235, .28); background: var(--hw-primary-soft); }

.held-icon {
    width: 40px;
    height: 40px;
    border-radius: 12px;
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    font-size: 1.0625rem;
    background: var(--hw-surface);
    color: var(--hw-primary);
}

.held-title { margin: 0 0 .25rem; font-size: 1rem; font-weight: 650; color: var(--hw-ink); }
.held-text  { font-size: .875rem; line-height: 1.5; color: var(--hw-muted); }

/* ==========================================================================
   Share portal — what happens after a scan
   ========================================================================== */

.steps { list-style: none; margin: 0; padding: 0; display: grid; gap: 1rem; }

@media (min-width: 768px) {
    .steps { grid-template-columns: repeat(3, 1fr); gap: 1.5rem; }
}

.steps > li { display: flex; align-items: flex-start; gap: .75rem; }

.step-no {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    font-size: .8125rem;
    font-weight: 700;
    background: var(--hw-primary-soft);
    color: var(--hw-primary);
}

.step-title { display: block; font-size: .875rem; font-weight: 620; color: var(--hw-ink); }
.step-text  { display: block; margin-top: .1875rem; font-size: .78125rem; line-height: 1.45; color: var(--hw-muted); }

/* ==========================================================================
   Wide screens
   ==========================================================================
   The container was drawn for a 1366 laptop, where 1160px fills the screen.
   On a 1920 monitor the same number leaves close to 400px of blank page down
   each side, which reads as the layout having stopped early rather than as
   deliberate margin. The column grows in two steps so the line length still
   stays readable — text does not get more legible past roughly 75 characters,
   so the extra width goes to the layout, not to the paragraphs.
   ========================================================================== */

@media (min-width: 1500px) {
    .landing-container { max-width: 1320px; }
    .auth-panel-inner:has(.auth-form-wide) { max-width: 42rem; }
}

@media (min-width: 1800px) {
    .landing-container { max-width: 1440px; }
    .auth-panel-inner { max-width: 27rem; }
    .auth-panel-inner:has(.auth-form-wide) { max-width: 46rem; }
}

/* The admin and resident shells already stretch, but their inner rhythm was
   tuned at the narrower width; give the content a little more room to match. */
@media (min-width: 1800px) {
    .app-main { padding-left: 2.5rem; padding-right: 2.5rem; }
}

/* The build credit is a link now. It stays as quiet as the text around it
   until the pointer is on it — a footer credit that shouts is worse than one
   that is simply there. */
.app-credit-link {
    color: inherit;
    text-decoration: none;
    border-bottom: 1px solid transparent;
    transition: color .15s ease, border-color .15s ease;
}

.app-credit-link:hover,
.app-credit-link:focus-visible {
    color: var(--hw-primary);
    border-bottom-color: currentColor;
}

/* ==========================================================================
   Toolbar search forms on a narrow screen
   ==========================================================================
   These sit in a page header as a row of inputs beside a button, and the row
   cannot wrap: a date field plus a 10rem search box plus a button is wider
   than a 360px phone, so the whole page gained a sideways scroll. The fields
   are told they may shrink — the inline min-width was there to stop the box
   collapsing on a desktop, which is not the constraint that matters here.
   ========================================================================== */

@media (max-width: 575.98px) {
    form:has(input[type="search"]) {
        flex-wrap: wrap;
    }

    form:has(input[type="search"]) .form-control {
        min-width: 0;
        flex: 1 1 7rem;
    }
}

/* ==========================================================================
   Landing — quieter type
   ==========================================================================
   The headings were set at 750–800, which is the weight a 16px label needs to
   register. At 60px it is not emphasis any more, it is shouting: the letters
   thicken faster than they grow, so the same weight that reads as confident
   small reads as heavy large. Large type earns its presence from size and
   spacing, not from stroke — so the weight comes down as the size goes up, and
   the tracking tightens to close the gaps that opening up leaves behind.
   ========================================================================== */

.hero-title {
    font-size: clamp(2.25rem, 5.2vw, 3.5rem);
    font-weight: 660;
    line-height: 1.08;
    letter-spacing: -.035em;
}

/* The accent line was the loudest thing on the page: a full-strength blue at
   display size on its own row. Same colour, one notch lighter, so it leads the
   eye without competing with the sentence it belongs to. */
.hero-accent { font-weight: 640; }

.section-title {
    font-weight: 640;
    letter-spacing: -.03em;
}

.price-value {
    font-weight: 680;
    letter-spacing: -.035em;
}

.hero-stat-value { font-weight: 680; letter-spacing: -.02em; }
.hero-fact-value { font-weight: 660; }

/* The eyebrow above each section is small enough that 700 still reads as a
   label rather than as weight — but the tracking was doing the work twice. */
.section-tag { font-weight: 640; letter-spacing: .09em; }

/* A little more air between the title and the paragraph under it: at a lighter
   weight the heading needs the space to hold its own. */
.section-title + .section-lead,
.section-title + p { margin-top: .875rem; }

/* A numbered badge is a marker, not a call to action. Four of them in solid
   brand blue with a coloured drop shadow pulled more attention than the steps
   they were numbering — the eye went to the badges and had to come back for
   the words. Same blue, held quietly. */
.step-number {
    background: var(--hw-primary-soft);
    color: var(--hw-primary);
    box-shadow: none;
    border: 1px solid rgba(var(--hw-primary-rgb), .18);
    font-weight: 660;
}

/* The price is the loudest word in the pricing block and it is already the
   biggest. A deeper blue carries the same meaning without the glare that
   full-strength brand blue has at 48px. */
.price-value { color: #1d4ed8; }

/* The hero heading again: at this size the type still renders heavier than the
   number suggests, so it comes down one more step. Below this it starts to
   read as thin rather than as restrained. */
.hero-title  { font-weight: 620; }
.hero-accent { font-weight: 600; color: #1d4ed8; }

/* Room for the lighter heading to breathe, and a longer measure so the lead
   does not wrap into a narrow column beside it. */
.hero-lead {
    font-size: 1.09375rem;
    line-height: 1.65;
    max-width: 36rem;
}

/* ==========================================================================
   Paid washing cycles — request, pay, verify
   ========================================================================== */

.set-icon.is-warning { background: var(--hw-warning-soft); color: var(--hw-warning-fg); }

/* A stat tile that is also a link keeps the tile look, gains a hover. */
a.stat-tile { transition: border-color .12s ease, box-shadow .12s ease; }
a.stat-tile:hover { border-color: var(--hw-border-strong); box-shadow: var(--hw-shadow-md); }

.chip-link { text-decoration: none; }
.chip-link:hover { border-color: var(--hw-border-strong); color: var(--hw-ink); }

/* The next step on the resident dashboard: calm blue, one button. */
.cycle-cta { border-color: rgba(var(--hw-primary-rgb), .28); background: var(--hw-primary-soft); }

.cycle-cta-icon,
.cycle-live-icon {
    width: 44px;
    height: 44px;
    border-radius: 12px;
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    font-size: 1.15rem;
    background: var(--hw-surface);
    color: var(--hw-primary);
}

.cycle-live-icon { background: var(--hw-success-soft); color: var(--hw-success-fg); }

.cycle-cta-title { margin: 0 0 .25rem; font-size: 1rem; font-weight: 650; color: var(--hw-ink); }
.cycle-cta-text  { font-size: .875rem; line-height: 1.5; color: var(--hw-muted); }
.cycle-cta-warn  { margin-top: .5rem; font-size: .8125rem; color: var(--hw-danger-fg); }

/* What is being bought, stated once above the form. */
.cycle-price {
    display: flex;
    align-items: center;
    gap: .875rem;
    padding: .875rem 1rem;
    margin-bottom: 1.25rem;
    border: 1px solid var(--hw-border);
    border-radius: var(--hw-radius-sm);
    background: var(--hw-surface-2);
}

.cycle-price-icon {
    width: 40px;
    height: 40px;
    border-radius: 11px;
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    background: var(--hw-primary-soft);
    color: var(--hw-primary);
}

.cycle-price-title { font-weight: 650; color: var(--hw-ink); }
.cycle-price-sub   { font-size: .78rem; color: var(--hw-muted); }

.cycle-price-value {
    margin-left: auto;
    font-size: 1.5rem;
    font-weight: 720;
    color: #1d4ed8;
    font-variant-numeric: tabular-nums;
}

/* Cash / Online: two big, obvious answers. */
.pay-choices { grid-template-columns: repeat(2, minmax(0, 1fr)); }

.pay-choice { display: flex !important; flex-direction: column; align-items: center; gap: .125rem; }
.pay-choice > i { font-size: 1.5rem; color: var(--hw-primary); margin-bottom: .25rem; }
.pay-choice-title { font-weight: 680; color: var(--hw-ink); }
.pay-choice-sub   { font-size: .75rem; color: var(--hw-muted); }

/* Where to pay: the QR beside the words that explain it. */
.upi-box {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    padding: 1rem;
    border: 1px dashed rgba(var(--hw-primary-rgb), .35);
    border-radius: var(--hw-radius);
    background: var(--hw-primary-soft);
}

.upi-qr {
    width: 132px;
    flex: 0 0 auto;
    aspect-ratio: 1;
    display: grid;
    place-items: center;
    border-radius: 12px;
    background: #fff;
    overflow: hidden;
    box-shadow: var(--hw-shadow-sm);
}

.upi-qr canvas { width: 100%; height: auto; display: block; }

.upi-id { display: flex; align-items: center; gap: .5rem; margin-bottom: .5rem; flex-wrap: wrap; }
.upi-id .text-mono { font-weight: 650; color: var(--hw-ink); word-break: break-all; }

@media (max-width: 420px) {
    .upi-box { flex-direction: column; align-items: center; text-align: center; }
    .upi-id { justify-content: center; }
}

/* The screenshot picker: a big target that becomes the preview. */
.proof-drop {
    display: grid;
    place-items: center;
    min-height: 140px;
    padding: 1rem;
    border: 2px dashed var(--hw-border-strong);
    border-radius: var(--hw-radius);
    background: var(--hw-surface-2);
    cursor: pointer;
    text-align: center;
    transition: border-color .12s ease, background-color .12s ease;
}

.proof-drop:hover,
.proof-drop.is-dragover { border-color: var(--hw-primary); background: var(--hw-primary-soft); }
.proof-drop.has-file { border-style: solid; border-color: rgba(var(--hw-primary-rgb), .45); }
.proof-drop.is-invalid { border-color: var(--hw-danger); }

.proof-drop-empty { display: grid; gap: .25rem; justify-items: center; color: var(--hw-body); }
.proof-drop-empty > i { font-size: 1.75rem; color: var(--hw-primary); }

.proof-preview { max-height: 260px; max-width: 100%; border-radius: 10px; box-shadow: var(--hw-shadow-sm); }

/* Resident "how it works" list. */
.cycle-steps { list-style: none; margin: 0; padding: 0; display: grid; gap: .875rem; }
.cycle-steps > li { display: flex; gap: .75rem; align-items: flex-start; }
.cycle-steps strong { display: block; font-size: .875rem; color: var(--hw-ink); }
.cycle-steps p { margin: .125rem 0 0; font-size: .8rem; color: var(--hw-muted); line-height: 1.45; }

/* Admin: the evidence column. */
.pay-amount { font-size: 1.0625rem; font-weight: 720; color: var(--hw-ink); font-variant-numeric: tabular-nums; }

.pay-flag {
    display: inline-flex;
    align-items: center;
    gap: .3125rem;
    padding: .125rem .5rem;
    border-radius: 8px;
    background: var(--hw-danger-soft);
    color: var(--hw-danger-fg);
    font-size: .72rem;
    font-weight: 600;
}

.pay-flag a { color: inherit; text-decoration: underline; }

.proof-thumb {
    position: relative;
    display: block;
    width: 56px;
    height: 72px;
    padding: 0;
    border: 1px solid var(--hw-border);
    border-radius: 9px;
    overflow: hidden;
    background: var(--hw-canvas);
    cursor: zoom-in;
}

.proof-thumb img { width: 100%; height: 100%; object-fit: cover; object-position: top; display: block; }

.proof-thumb-zoom {
    position: absolute;
    inset: auto 4px 4px auto;
    width: 20px;
    height: 20px;
    border-radius: 6px;
    display: grid;
    place-items: center;
    font-size: .7rem;
    background: rgba(15, 23, 42, .65);
    color: #fff;
}

.proof-thumb:focus-visible { outline: 3px solid rgba(var(--hw-primary-rgb), .35); outline-offset: 2px; }

.proof-full { background: var(--hw-canvas); text-align: center; }
.proof-full img { max-width: 100%; max-height: 75vh; border-radius: 10px; box-shadow: var(--hw-shadow-sm); }

.reason-picks { display: flex; flex-wrap: wrap; gap: .375rem; }
.reason-picks .btn { font-size: .75rem; }

.pay-filter { flex-wrap: nowrap; max-width: 100%; }
.pay-filter .form-select { width: auto; flex: 0 0 auto; }
.pay-filter .form-control { min-width: 0; width: 15rem; flex: 1 1 auto; }
