/* ============================================================================
   Sasulwa design system

   The Pace Ring is the signature element; everything here stays quiet so it can
   be the memorable thing. Brand green is deliberately not yellow and not red —
   the app shows MTN and Airtel side by side and must never imply allegiance.
   ========================================================================== */

:root {
    /* --- Ground and surfaces -------------------------------------------- */
    --s-bg: #F7F5F0;
    --s-surface: #FFFFFF;
    --s-surface-2: #F1EEE7;
    --s-surface-raised: #FFFFFF;
    --s-border: #E2DDD3;
    --s-border-strong: #CFC8BA;

    /* --- Text ------------------------------------------------------------ */
    --s-text: #16191C;
    --s-text-dim: #5C6169;
    --s-text-faint: #8C9199;
    --s-text-on-brand: #FFFFFF;

    /* --- Brand ----------------------------------------------------------- */
    --s-brand: #1B5E4A;
    --s-brand-hover: #17513F;
    --s-brand-soft: #E4EFEA;
    --s-brand-line: #B9D6C9;

    /* --- The one warm note: the pace marker ------------------------------ */
    --s-marker: #C2603A;
    --s-marker-soft: #F7E6DE;

    /* --- Status. Tint only; meaning is carried by icon + label + position. */
    --s-ok: #1B5E4A;
    --s-ok-soft: #E4EFEA;
    --s-warn: #9A6B12;
    --s-warn-soft: #FBF0DA;
    --s-attention: #A33F2E;
    --s-attention-soft: #F9E5E1;

    /* --- Ring ------------------------------------------------------------ */
    --s-ring-track: #E2DDD3;
    --s-ring-fill: #1B5E4A;
    --s-ring-today: #C2603A;

    /* --- Carrier marks. Used only on network badges, never as brand. ----- */
    --s-mtn: #FFCC00;
    --s-mtn-ink: #2B2100;
    --s-airtel: #E40000;
    --s-airtel-ink: #FFFFFF;

    /* --- Type ------------------------------------------------------------ */
    --s-font-display: "Gabarito", "Outfit", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --s-font-body: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
    --s-font-mono: ui-monospace, SFMono-Regular, "Cascadia Mono", "Segoe UI Mono", Consolas, monospace;

    --s-fs-xs: 0.6875rem;
    --s-fs-sm: 0.8125rem;
    --s-fs-base: 0.9375rem;
    --s-fs-md: 1.0625rem;
    --s-fs-lg: 1.375rem;
    --s-fs-xl: 1.875rem;
    --s-fs-hero: clamp(2.75rem, 11vw, 3.75rem);
    --s-fs-portfolio: clamp(1.75rem, 6vw, 2.375rem);

    /* --- Rhythm ---------------------------------------------------------- */
    --s-1: 0.25rem;
    --s-2: 0.5rem;
    --s-3: 0.75rem;
    --s-4: 1rem;
    --s-5: 1.25rem;
    --s-6: 1.5rem;
    --s-7: 2rem;
    --s-8: 3rem;

    --s-r-sm: 8px;
    --s-r: 12px;
    --s-r-lg: 18px;
    --s-r-xl: 26px;
    --s-r-pill: 999px;

    --s-shadow-sm: 0 1px 2px rgba(20, 24, 28, 0.05);
    --s-shadow: 0 4px 16px rgba(20, 24, 28, 0.07);
    --s-shadow-lg: 0 18px 44px -14px rgba(20, 24, 28, 0.22);

    --s-t-fast: 120ms cubic-bezier(0.4, 0, 0.2, 1);
    --s-t: 200ms cubic-bezier(0.4, 0, 0.2, 1);
    --s-t-slow: 420ms cubic-bezier(0.22, 1, 0.36, 1);

    --s-shell-max: 1180px;
    --s-tenant-max: 460px;

    /* The column the site bar rides, and what the front door and the wide account screens
       line their content up with. One number, so the wordmark never moves between them. */
    --s-page-max: 1040px;

    /* The fixed bottom nav overlays the page, so every scroll container has to end above it.
       Kept as one token because getting it wrong hides the last control on every screen. */
    --s-navh: calc(64px + max(var(--s-2), env(safe-area-inset-bottom, 0px)));
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --s-bg: #0E1218;
        --s-surface: #161B22;
        --s-surface-2: #1D242D;
        --s-surface-raised: #1A2028;
        --s-border: #262E38;
        --s-border-strong: #38424F;

        --s-text: #EDEFF2;
        --s-text-dim: #A2AAB5;
        --s-text-faint: #6F7883;
        --s-text-on-brand: #06120D;

        --s-brand: #45B490;
        --s-brand-hover: #55C4A0;
        --s-brand-soft: #14312A;
        --s-brand-line: #1F4A3E;

        --s-marker: #E58256;
        --s-marker-soft: #35211A;

        --s-ok: #45B490;
        --s-ok-soft: #14312A;
        --s-warn: #E0B457;
        --s-warn-soft: #322912;
        --s-attention: #E2836E;
        --s-attention-soft: #37201B;

        --s-ring-track: #262E38;
        --s-ring-fill: #45B490;
        --s-ring-today: #E58256;

        --s-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
        --s-shadow: 0 4px 16px rgba(0, 0, 0, 0.45);
        --s-shadow-lg: 0 18px 44px -14px rgba(0, 0, 0, 0.6);
    }
}

:root[data-theme="dark"] {
    --s-bg: #0E1218;
    --s-surface: #161B22;
    --s-surface-2: #1D242D;
    --s-surface-raised: #1A2028;
    --s-border: #262E38;
    --s-border-strong: #38424F;

    --s-text: #EDEFF2;
    --s-text-dim: #A2AAB5;
    --s-text-faint: #6F7883;
    --s-text-on-brand: #06120D;

    --s-brand: #45B490;
    --s-brand-hover: #55C4A0;
    --s-brand-soft: #14312A;
    --s-brand-line: #1F4A3E;

    --s-marker: #E58256;
    --s-marker-soft: #35211A;

    --s-ok: #45B490;
    --s-ok-soft: #14312A;
    --s-warn: #E0B457;
    --s-warn-soft: #322912;
    --s-attention: #E2836E;
    --s-attention-soft: #37201B;

    --s-ring-track: #262E38;
    --s-ring-fill: #45B490;
    --s-ring-today: #E58256;

    --s-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --s-shadow: 0 4px 16px rgba(0, 0, 0, 0.45);
    --s-shadow-lg: 0 18px 44px -14px rgba(0, 0, 0, 0.6);
}

/* ============================================================================
   Reset and base
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html, body { height: 100%; }

/* A phone can pan a page sideways whenever anything is wider than the viewport, and
   overflow-x: hidden on body alone does not stop the pan on every browser. clip does, and
   unlike hidden it makes no scroll container, so sticky bars and dropdowns keep working. */
html { overflow-x: clip; }

/* Native controls — number spinners, pickers, autofill — follow the theme, not the OS. Without
   this a dark screen gets a white spinner and Chrome's pale-blue autofill box on every field. */
:root { color-scheme: light; }

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) { color-scheme: dark; }
}

:root[data-theme="dark"] { color-scheme: dark; }

input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
    -webkit-box-shadow: 0 0 0 1000px var(--s-surface-2) inset;
    -webkit-text-fill-color: var(--s-text);
    caret-color: var(--s-text);
    transition: background-color 9999s ease-out;
}

body {
    margin: 0;
    background: var(--s-bg);
    color: var(--s-text);
    font-family: var(--s-font-body);
    font-size: var(--s-fs-base);
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    overflow-x: hidden;
}

h1, h2, h3, h4 {
    font-family: var(--s-font-display);
    font-weight: 600;
    letter-spacing: -0.02em;
    margin: 0;
    line-height: 1.15;
}

p { margin: 0; }

a { color: var(--s-brand); text-decoration: none; }
a:hover { text-decoration: underline; }

button { font: inherit; color: inherit; }

/* The browser paints its own highlight over a tapped control, and it paints a rectangle — so a
   pill toggle flashes square corners on a handset and never on a mouse. Touch only, so the ring
   below still answers the keyboard. */
a,
button,
label,
summary,
input,
select,
textarea,
[role="tab"],
[role="button"] {
    -webkit-tap-highlight-color: transparent;
}

/* A control's label is not prose: selecting it is never what the tap meant, and manipulation
   drops the wait the browser keeps in case the tap was the first half of a zoom. Anchors are
   named rather than swept in — a link inside a paragraph is text, and only the two drawn as
   controls belong here. Nothing carrying .s-code or .s-num is listed: a member id and a
   merchant code exist to be read aloud and copied. */
button,
summary,
a.s-btn,
a.roomtile,
[role="tab"],
[role="button"] {
    touch-action: manipulation;
    -webkit-user-select: none;
    user-select: none;
}

/* Money and any figure that gets compared down a column must not jitter. */
.s-num {
    font-family: var(--s-font-display);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.03em;
    font-weight: 600;
}

/* Codes exist to be read aloud down a phone line — spec §6.3, §7.2. */
.s-code {
    font-family: var(--s-font-mono);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.08em;
}

:focus-visible {
    outline: 2px solid var(--s-brand);
    outline-offset: 2px;
    border-radius: var(--s-r-sm);
}

/* FocusOnNavigate parks the caret on the new page's h1 so a screen reader announces where it has
   landed. It does that by giving the heading tabindex="-1", which nothing can tab to — so the
   ring around a headline is telling a sighted reader about a move they already saw. The focus
   stays; only the box goes. */
[tabindex="-1"]:focus,
[tabindex="-1"]:focus-visible { outline: none; }

::selection { background: var(--s-brand-soft); color: var(--s-text); }

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

/* ============================================================================
   Shell — landlord (dense, sidebar) and tenant (single calm column)
   ========================================================================== */

.s-shell { min-height: 100dvh; display: flex; flex-direction: column; overflow-x: clip; }

.s-topbar {
    position: sticky;
    top: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    gap: var(--s-3);
    /* Android 15 draws edge-to-edge, so the bar starts under the clock without this.
       The inset is 0 on desktop, where it costs nothing. */
    padding: max(var(--s-3), env(safe-area-inset-top)) var(--s-4) var(--s-3);
    background: color-mix(in srgb, var(--s-bg) 86%, transparent);
    backdrop-filter: blur(14px);
    border-bottom: 1px solid var(--s-border);
}

/* Language, theme and the way out all live here, so the bar has to stay narrow on a phone. */
@media (max-width: 420px) {
    .s-topbar {
        gap: var(--s-2);
        padding-left: var(--s-3);
        padding-right: var(--s-3);
    }
}

.s-wordmark {
    font-family: var(--s-font-display);
    font-weight: 700;
    font-size: var(--s-fs-md);
    letter-spacing: -0.03em;
    color: var(--s-text);
    display: inline-flex;
    align-items: center;
    gap: var(--s-2);
}

.s-wordmark:hover { text-decoration: none; }

/* The mark is the ring at its smallest: a part-filled circle with the marker on it. */
.s-mark {
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background:
        conic-gradient(var(--s-marker) 0 3deg, transparent 3deg),
        conic-gradient(var(--s-brand) 0 232deg, var(--s-border-strong) 232deg 100%);
    -webkit-mask: radial-gradient(circle, transparent 45%, #000 46%);
    mask: radial-gradient(circle, transparent 45%, #000 46%);
    flex: 0 0 auto;
}

/* --- The first paint ---------------------------------------------------------
   Interactive rendering is not prerendered (see the host page), so between the
   HTML arriving and the circuit opening there is nothing on screen at all. This
   is what stands in that gap.

   It is the mark at its largest, for the same reason the mark is the ring: the
   thing you wait in front of should be the thing the product is about. A generic
   spinner would be a smaller lie than a blank screen, but still a lie.

   Removed by sasulwa.js once a page has actually rendered — not merely when the
   circuit opened, which is earlier and still blank. */

.s-splash {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: grid;
    place-content: center;
    justify-items: center;
    gap: var(--s-4);
    padding: var(--s-6);
    background: var(--s-bg);
    text-align: center;
    transition: opacity 280ms ease, visibility 280ms;
}

.s-splash.is-done {
    opacity: 0;
    visibility: hidden;
}

.s-splash__ring { width: 72px; height: 72px; }

.s-splash__track {
    fill: none;
    stroke: var(--s-border);
    stroke-width: 6.5;
}

.s-splash__arc {
    fill: none;
    stroke: var(--s-brand);
    stroke-width: 6.5;
    stroke-linecap: round;
    /* A third of the ring covered, the same proportion the app icon carries, so the
       gap reads as the part of the month still to come rather than as a break. */
    stroke-dasharray: 55 109;
}

.s-splash__today { fill: var(--s-marker); }

.s-splash__spin {
    transform-origin: 32px 32px;
    transform-box: view-box;
    animation: s-splash-turn 1.5s linear infinite;
}

@keyframes s-splash-turn {
    to { transform: rotate(360deg); }
}

.s-splash__name {
    font-family: var(--s-font-display);
    font-size: var(--s-fs-lg);
    font-weight: 600;
    letter-spacing: -0.03em;
    color: var(--s-text);
    line-height: 1;
}

.s-splash__line {
    margin: calc(var(--s-2) * -1) 0 0;
    font-size: var(--s-fs-sm);
    color: var(--s-text-faint);
}

/* Only after the wait stops being a wait and starts being a fault. */
.s-splash__slow {
    margin: 0;
    font-size: var(--s-fs-sm);
    color: var(--s-text-dim);
    opacity: 0;
    transition: opacity var(--s-t-slow);
}

.s-splash.is-slow .s-splash__slow { opacity: 1; }

.s-splash__slow a { color: var(--s-brand); }

@media (prefers-reduced-motion: reduce) {
    .s-splash__spin { animation: none; }
    .s-splash__arc { animation: s-splash-breathe 2.4s ease-in-out infinite; }

    @keyframes s-splash-breathe {
        50% { opacity: 0.45; }
    }
}

.s-tabcount {
    display: inline-grid;
    place-items: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    margin-left: 2px;
    border-radius: var(--s-r-pill);
    background: var(--s-marker);
    color: #fff;
    font-size: 11px;
    font-weight: 700;
}

.s-topbar-context {
    font-size: var(--s-fs-sm);
    color: var(--s-text-dim);
    padding-left: var(--s-3);
    border-left: 1px solid var(--s-border);
}

.s-topbar-spacer { margin-left: auto; }

/* ---- The account menu: one press holding the name, the desk, both toggles and the way out.
   Four controls in the bar were what pushed the way out off a phone's edge. ---- */

.s-acct { flex: 0 0 auto; }

.s-acct__btn {
    display: inline-grid;
    place-items: center;
    width: 38px;
    height: 38px;
    padding: 0;
    border: 1px solid var(--s-border);
    border-radius: var(--s-r-pill);
    background: var(--s-brand-soft);
    color: var(--s-brand);
    font-family: var(--s-font-display);
    font-weight: 700;
    font-size: var(--s-fs-sm);
    letter-spacing: 0;
    cursor: pointer;
    transition: border-color var(--s-t-fast), box-shadow var(--s-t-fast);
}

.s-acct__btn:hover, .s-acct__btn[aria-expanded="true"] {
    border-color: var(--s-brand-line);
    box-shadow: 0 0 0 3px var(--s-brand-soft);
}

/* Hangs from the right edge: the menu sits at the end of the bar and must not run off it. */
.s-navgroup .s-acct__menu {
    left: auto;
    right: 0;
    min-width: 240px;
    gap: var(--s-2);
}

.s-acct__who {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--s-2) var(--s-2) var(--s-3);
    border-bottom: 1px solid var(--s-border);
}

.s-acct__who strong {
    font-family: var(--s-font-display);
    font-size: var(--s-fs-md);
    letter-spacing: -0.02em;
}

.s-acct__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-3);
    padding: 2px var(--s-2);
}

.s-acct__out {
    display: flex;
    align-items: center;
    gap: var(--s-2);
    margin-top: var(--s-1);
    padding: var(--s-2) var(--s-3);
    border: 0;
    border-radius: var(--s-r-sm);
    background: var(--s-surface-2);
    font: inherit;
    font-weight: 600;
    cursor: pointer;
}

.s-acct__out:hover { background: var(--s-border); }

.s-main { flex: 1; width: 100%; }

.s-main--landlord {
    max-width: var(--s-shell-max);
    margin: 0 auto;
    padding: var(--s-5) var(--s-4) calc(var(--s-navh) + var(--s-6));
}

.s-main--tenant {
    max-width: var(--s-tenant-max);
    margin: 0 auto;
    padding: var(--s-4) var(--s-4) calc(var(--s-navh) + var(--s-7));
}

/* Bottom nav sits in the thumb zone — landlords read this standing in a corridor. */
.s-bottomnav {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 30;
    display: flex;
    justify-content: space-around;
    gap: var(--s-1);
    padding: var(--s-2) var(--s-2) max(var(--s-2), env(safe-area-inset-bottom));
    background: color-mix(in srgb, var(--s-surface) 92%, transparent);
    backdrop-filter: blur(16px);
    border-top: 1px solid var(--s-border);
}

.s-bottomnav a {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    padding: var(--s-2) var(--s-1);
    border-radius: var(--s-r);
    font-size: var(--s-fs-xs);
    font-weight: 600;
    color: var(--s-text-faint);
    text-decoration: none;
    transition: color var(--s-t-fast), background var(--s-t-fast);
    position: relative;
}

.s-bottomnav a:hover { color: var(--s-text-dim); text-decoration: none; }
.s-bottomnav a.active { color: var(--s-brand); background: var(--s-brand-soft); }

.s-bottomnav .s-navdot {
    position: absolute;
    top: 4px;
    right: calc(50% - 20px);
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: var(--s-r-pill);
    background: var(--s-marker);
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    display: grid;
    place-items: center;
}

/* Above 900px the tab rail takes over, so the reserved strip goes back to ordinary breathing room. */
@media (min-width: 900px) {
    .s-bottomnav { display: none; }

    .s-main--landlord { padding-top: var(--s-6); padding-bottom: var(--s-8); }
    .s-main--tenant { padding-bottom: var(--s-8); }
}

/* Desktop tab rail replaces the bottom nav above 900px. */
.s-tabs {
    display: none;
    gap: var(--s-1);
    margin-left: var(--s-5);
    /* The rail gives way before the account menu does. It shrinks by clipping its own labels
       rather than by scrolling: a scrolling rail is an overflow context, and the menus that hang
       from it were being clipped into nothing — a dropdown that opens and cannot be seen. */
    flex: 0 1 auto;
    min-width: 0;
}

.s-tabs a, .s-tabs .s-navgroup { flex: 0 1 auto; min-width: 0; }

.s-tabs a, .s-tabs .s-navgroup__btn {
    max-width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

@media (min-width: 900px) {
    .s-tabs { display: flex; }
}

.s-tabs a {
    padding: var(--s-2) var(--s-3);
    border-radius: var(--s-r-pill);
    font-size: var(--s-fs-sm);
    font-weight: 600;
    color: var(--s-text-dim);
    text-decoration: none;
    transition: background var(--s-t-fast), color var(--s-t-fast);
    display: inline-flex;
    align-items: center;
    gap: var(--s-2);
}

.s-tabs a:hover { background: var(--s-surface-2); color: var(--s-text); text-decoration: none; }
.s-tabs a.active { background: var(--s-brand-soft); color: var(--s-brand); }

/* ============================================================================
   Surfaces
   ========================================================================== */

.s-card {
    background: var(--s-surface);
    border: 1px solid var(--s-border);
    border-radius: var(--s-r-lg);
    padding: var(--s-5);
}

.s-card--flush { padding: 0; overflow: hidden; }
.s-card--quiet { background: var(--s-surface-2); border-color: transparent; }

.s-stack { display: flex; flex-direction: column; gap: var(--s-4); }

/*
   The way back out of a screen. One rule for the lot of them: a chevron the size of a word,
   pulled left so the label still lines up with the column, and a tap target around it big
   enough for a thumb that is walking. It renders as a link or as a button depending on whether
   it is going somewhere fixed or one step back, and neither may look like the other.
*/
.s-back {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    align-self: flex-start;
    margin-left: calc(-1 * var(--s-2));
    padding: var(--s-1) var(--s-2);
    border: 0;
    border-radius: var(--s-r);
    background: none;
    font-family: inherit;
    font-size: var(--s-fs-sm);
    font-weight: 600;
    color: var(--s-text-dim);
    cursor: pointer;
    transition: color var(--s-t-fast), background var(--s-t-fast);
}

.s-back:hover {
    color: var(--s-text);
    background: var(--s-surface-2);
    text-decoration: none;
}
.s-row { display: flex; align-items: center; gap: var(--s-3); }
.s-row--between { justify-content: space-between; }
.s-grow { flex: 1; min-width: 0; }

.s-eyebrow {
    font-size: var(--s-fs-xs);
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--s-text-faint);
}

.s-dim { color: var(--s-text-dim); }
.s-faint { color: var(--s-text-faint); }
.s-sm { font-size: var(--s-fs-sm); }
.s-xs { font-size: var(--s-fs-xs); }

/* ============================================================================
   Buttons
   ========================================================================== */

.s-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--s-2);
    padding: var(--s-3) var(--s-5);
    border: 1px solid transparent;
    border-radius: var(--s-r-pill);
    background: var(--s-surface-2);
    color: var(--s-text);
    font-size: var(--s-fs-base);
    font-weight: 600;
    cursor: pointer;
    transition: background var(--s-t-fast), transform var(--s-t-fast), border-color var(--s-t-fast);
    text-decoration: none;
}

.s-btn:hover { background: var(--s-border); text-decoration: none; }
.s-btn:active { transform: scale(0.985); }

.s-btn--primary { background: var(--s-brand); color: var(--s-text-on-brand); }
.s-btn--primary:hover { background: var(--s-brand-hover); }

.s-btn--ghost { background: transparent; border-color: var(--s-border-strong); }
.s-btn--ghost:hover { background: var(--s-surface-2); }

/* The far side of a confirmation. Its whole job is to be pressed on purpose, so it wears the
   attention colour rather than the brand's — an irreversible act must not look like a next step. */
.s-btn--danger {
    background: var(--s-attention-soft);
    color: var(--s-attention);
    border-color: var(--s-attention);
}

.s-btn--danger:hover { background: var(--s-attention); color: #fff; }

.s-btn--block { width: 100%; }
.s-btn--lg { padding: var(--s-4) var(--s-6); font-size: var(--s-fs-md); }
.s-btn--sm { padding: var(--s-2) var(--s-3); font-size: var(--s-fs-sm); }

.s-btn:disabled { opacity: 0.45; cursor: not-allowed; }

/* Every glyph the app draws (Components/Icon.razor). The set is inline SVG on purpose: it inherits
   currentColor, so one icon works on paper, on green and in the dark, and it costs no request on a
   network where a request is the expensive part.

   `flex: none` because an icon in a flex row next to a long label is otherwise the thing that gets
   squashed, and a squashed icon reads as a rendering fault rather than as a tight fit. */
.s-icon {
    flex: none;
    display: inline-block;
    vertical-align: -0.125em;
}

.s-iconbtn {
    display: inline-grid;
    place-items: center;
    width: 38px;
    height: 38px;
    border: 1px solid var(--s-border);
    border-radius: var(--s-r-pill);
    background: var(--s-surface);
    cursor: pointer;
    transition: background var(--s-t-fast), border-color var(--s-t-fast);
}

.s-iconbtn:hover { background: var(--s-surface-2); border-color: var(--s-border-strong); }

/* ============================================================================
   Chips, badges, status
   ========================================================================== */

.s-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--s-1);
    padding: 5px var(--s-3);
    border-radius: var(--s-r-pill);
    background: var(--s-surface-2);
    border: 1px solid transparent;
    font-size: var(--s-fs-sm);
    font-weight: 600;
    color: var(--s-text-dim);
    cursor: pointer;
    transition: background var(--s-t-fast), color var(--s-t-fast), border-color var(--s-t-fast);
}

.s-chip:hover { background: var(--s-border); }
.s-chip--on { background: var(--s-brand-soft); color: var(--s-brand); border-color: var(--s-brand-line); }

/* Status always pairs a glyph with a word — colour is never the only signal (spec §12). */
.s-status {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px var(--s-2);
    border-radius: var(--s-r-sm);
    font-size: var(--s-fs-xs);
    font-weight: 700;
    letter-spacing: 0.01em;
    white-space: nowrap;
}

.s-status--cleared { background: var(--s-brand-soft); color: var(--s-brand); }
.s-status--ontrack { background: var(--s-ok-soft); color: var(--s-ok); }
.s-status--slipping { background: var(--s-warn-soft); color: var(--s-warn); }
.s-status--attention { background: var(--s-attention-soft); color: var(--s-attention); }

.s-net {
    display: inline-flex;
    align-items: center;
    padding: 2px var(--s-2);
    border-radius: var(--s-r-sm);
    font-size: var(--s-fs-xs);
    font-weight: 700;
    letter-spacing: 0.02em;
}

.s-net--mtn { background: var(--s-mtn); color: var(--s-mtn-ink); }
.s-net--airtel { background: var(--s-airtel); color: var(--s-airtel-ink); }

/* ============================================================================
   List rows — the landlord's densest surface
   ========================================================================== */

.s-list { display: flex; flex-direction: column; }

.s-listrow {
    display: flex;
    align-items: center;
    gap: var(--s-3);
    width: 100%;
    padding: var(--s-3) var(--s-4);
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--s-border);
    text-align: left;
    cursor: pointer;
    text-decoration: none;
    color: inherit;
    transition: background var(--s-t-fast);
}

.s-listrow:last-child { border-bottom: 0; }
.s-listrow:hover { background: var(--s-surface-2); text-decoration: none; }

.s-avatar {
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    flex: 0 0 38px;
    border-radius: var(--s-r-pill);
    background: var(--s-surface-2);
    color: var(--s-text-dim);
    font-size: var(--s-fs-sm);
    font-weight: 700;
    letter-spacing: 0.02em;
}

.s-rowtitle {
    font-weight: 600;
    font-size: var(--s-fs-base);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.s-rowmeta {
    font-size: var(--s-fs-sm);
    color: var(--s-text-dim);
    display: flex;
    align-items: center;
    gap: var(--s-2);
    flex-wrap: wrap;
}

/* Facts in a row need a visible break or they read as one run-on string. */
.s-rowmeta > * + *::before {
    content: "·";
    margin-right: var(--s-2);
    color: var(--s-text-faint);
}

/* ============================================================================
   Section headers
   ========================================================================== */

.s-sectionhead {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s-3);
    margin-bottom: var(--s-3);
}

.s-sectionhead h2 { font-size: var(--s-fs-lg); }

/* ============================================================================
   Empty and loading states
   ========================================================================== */

.s-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--s-3);
    padding: var(--s-8) var(--s-5);
    text-align: center;
    color: var(--s-text-dim);
}

.s-empty h3 { font-size: var(--s-fs-md); color: var(--s-text); }

.s-skeleton {
    background: linear-gradient(90deg,
        var(--s-surface-2) 25%,
        color-mix(in srgb, var(--s-surface-2) 60%, var(--s-border)) 37%,
        var(--s-surface-2) 63%);
    background-size: 400% 100%;
    animation: s-shimmer 1.4s ease infinite;
    border-radius: var(--s-r-sm);
}

.s-skeleton--hero { min-height: 180px; }
.s-skeleton--row { min-height: 72px; }

@keyframes s-shimmer {
    0% { background-position: 100% 50%; }
    100% { background-position: 0 50%; }
}

/* ============================================================================
   Banner — the non-dismissable "you cannot collect yet" gate (spec §6.1)
   ========================================================================== */

.s-banner {
    display: flex;
    align-items: flex-start;
    gap: var(--s-3);
    padding: var(--s-4);
    border-radius: var(--s-r-lg);
    background: var(--s-warn-soft);
    border: 1px solid color-mix(in srgb, var(--s-warn) 28%, transparent);
    color: var(--s-text);
}

/* A note rather than a warning. Nothing is wrong — there is just something about this the
   reader has to know, and the loud banner beside it would stop meaning anything if used here. */
.s-banner--quiet {
    background: var(--s-surface-2);
    border-color: var(--s-border);
}

.s-banner--quiet .s-banner__icon { color: var(--s-text-dim); }

.s-banner__icon { color: var(--s-warn); flex: 0 0 auto; margin-top: 1px; }
.s-banner__title { font-weight: 700; font-size: var(--s-fs-base); }
.s-banner__body { font-size: var(--s-fs-sm); color: var(--s-text-dim); margin-top: 2px; }

/* ============================================================================
   Entry / role chooser
   ========================================================================== */

.s-entry {
    min-height: 100dvh;
    display: grid;
    place-items: center;
    padding: max(var(--s-6), env(safe-area-inset-top)) var(--s-4)
             max(var(--s-6), env(safe-area-inset-bottom));
}

.s-entry__inner { width: 100%; max-width: 520px; }

/* The two documents, at the very foot of the front door. */
.entry-legal {
    display: flex;
    justify-content: center;
    gap: var(--s-2);
    padding-top: var(--s-6);
    grid-column: 1 / -1;
}

.entry-legal a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }

.s-entry__lead {
    font-family: var(--s-font-display);
    font-size: clamp(2rem, 8vw, 2.75rem);
    font-weight: 600;
    letter-spacing: -0.035em;
    line-height: 1.05;
    margin-bottom: var(--s-3);
}

/* The figure the whole front door is computed from, typed at the size of what it decides.
   It lives here rather than in Entry.razor.css because TextBox renders it, and a page's scoped
   styles do not reach inside a component it happens to use. */
.entry-ask__box {
    flex: 1;
    min-width: 0;
    width: 100%;
    border: 0;
    background: transparent;
    color: var(--s-text);
    font-family: var(--s-font-display);
    font-size: clamp(2rem, 9vw, 2.75rem);
    font-weight: 600;
    letter-spacing: -0.03em;
    line-height: 1.05;
    padding: 0;
}

.entry-ask__box:focus { outline: none; }

.s-rolecard {
    display: flex;
    align-items: center;
    gap: var(--s-4);
    width: 100%;
    padding: var(--s-5);
    border: 1px solid var(--s-border);
    border-radius: var(--s-r-lg);
    background: var(--s-surface);
    cursor: pointer;
    text-align: left;
    text-decoration: none;
    color: inherit;
    transition: border-color var(--s-t), transform var(--s-t), box-shadow var(--s-t);
}

.s-rolecard:hover {
    border-color: var(--s-brand-line);
    transform: translateY(-2px);
    box-shadow: var(--s-shadow);
    text-decoration: none;
}

/* Nothing behind it, so it does not offer to be pressed. */
.s-rolecard--still {
    cursor: default;
    padding-block: var(--s-4);
}

.s-rolecard--still:hover {
    border-color: var(--s-border);
    transform: none;
    box-shadow: none;
}

.s-rolecard__title {
    display: block;
    font-family: var(--s-font-display);
    font-weight: 600;
    font-size: var(--s-fs-md);
}

.s-rolecard__body {
    display: block;
    font-size: var(--s-fs-sm);
    color: var(--s-text-dim);
    margin-top: 2px;
}

/* ============================================================================
   Utility
   ========================================================================== */

.s-divider { height: 1px; background: var(--s-border); border: 0; margin: var(--s-4) 0; }
.s-hide-sm { display: none; }

@media (min-width: 640px) {
    .s-hide-sm { display: initial; }
}

.s-grid-2 { display: grid; grid-template-columns: 1fr; gap: var(--s-4); }

@media (min-width: 760px) {
    .s-grid-2 { grid-template-columns: 1fr 1fr; }
}

.s-scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ============================================================================
   Role chip — three doors now, so every shell says which one you came through
   ========================================================================== */

.s-rolechip {
    display: inline-flex;
    align-items: center;
    padding: 2px var(--s-2);
    border-radius: var(--s-r-sm);
    font-size: var(--s-fs-xs);
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    white-space: nowrap;
}

.s-rolechip--owner { background: var(--s-brand-soft); color: var(--s-brand); }
.s-rolechip--agent { background: var(--s-marker-soft); color: var(--s-marker); }

@media (max-width: 420px) {
    .s-rolechip { display: none; }
}

/* A status with no opinion — scheduled, reserved, out of service. */
.s-status--quiet { background: var(--s-surface-2); color: var(--s-text-dim); }

/* ============================================================================
   Onboarding flows — one rail, used by both the owner and the tenant journeys
   ========================================================================== */

.s-steps {
    display: flex;
    gap: var(--s-2);
    margin: 0;
    padding: 0 0 2px;
    list-style: none;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

.s-step { flex: 0 0 auto; }

.s-step__btn {
    display: inline-flex;
    align-items: center;
    gap: var(--s-2);
    padding: 5px var(--s-3) 5px 5px;
    border: 1px solid var(--s-border);
    border-radius: var(--s-r-pill);
    background: var(--s-surface);
    cursor: pointer;
    transition: border-color var(--s-t-fast), background var(--s-t-fast);
}

.s-step__btn:disabled { opacity: 0.55; cursor: default; }

.s-step__n {
    display: grid;
    place-items: center;
    width: 22px;
    height: 22px;
    flex: 0 0 22px;
    border-radius: 50%;
    background: var(--s-surface-2);
    color: var(--s-text-dim);
    font-size: var(--s-fs-xs);
    font-weight: 700;
}

.s-step__t {
    font-size: var(--s-fs-sm);
    font-weight: 600;
    color: var(--s-text-dim);
    white-space: nowrap;
}

.s-step.is-now .s-step__btn { border-color: var(--s-brand); background: var(--s-brand-soft); }
.s-step.is-now .s-step__n { background: var(--s-brand); color: var(--s-text-on-brand); }
.s-step.is-now .s-step__t { color: var(--s-brand); }

.s-step.is-done .s-step__n { background: var(--s-ok-soft); color: var(--s-ok); }
.s-step.is-done .s-step__t { color: var(--s-text); }

.s-field { display: flex; flex-direction: column; gap: var(--s-2); }

.s-input {
    width: 100%;
    padding: var(--s-3) var(--s-4);
    border: 1px solid var(--s-border);
    border-radius: var(--s-r);
    background: var(--s-surface-2);
    color: var(--s-text);
    font: inherit;
    font-size: var(--s-fs-md);
    outline: none;
    transition: border-color var(--s-t-fast);
}

.s-input:focus { border-color: var(--s-brand-line); }
.s-input::placeholder { color: var(--s-text-faint); }

/* A native picker, themed rather than rebuilt: twenty-six banks on a phone want the platform's
   own wheel, and a custom list would be the thing that breaks in dark mode. appearance:none
   strips the OS chrome, so the caret is drawn back on. */
.s-select {
    width: 100%;
    padding: var(--s-3) calc(var(--s-4) + 20px) var(--s-3) var(--s-4);
    border: 1px solid var(--s-border);
    border-radius: var(--s-r);
    background: var(--s-surface-2);
    color: var(--s-text);
    font: inherit;
    font-size: var(--s-fs-md);
    outline: none;
    appearance: none;
    background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                      linear-gradient(135deg, currentColor 50%, transparent 50%);
    background-position: right calc(var(--s-4) + 4px) center, right var(--s-4) center;
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
    transition: border-color var(--s-t-fast);
}

.s-select:focus { border-color: var(--s-brand-line); }

/* The option list is drawn by the OS, which does not inherit our tokens — name the colours so a
   dark handset does not paint black text on a black sheet. */
.s-select option { background: var(--s-surface-2); color: var(--s-text); }

.s-flownav {
    display: flex;
    align-items: center;
    gap: var(--s-3);
    margin-top: var(--s-6);
    padding-top: var(--s-4);
    border-top: 1px solid var(--s-border);
}

/* ============================================================================
   Five-up bottom nav — the agent shell carries one more destination
   ========================================================================== */

.s-bottomnav--five a { font-size: 10px; }

@media (max-width: 360px) {
    .s-bottomnav--five a { padding-left: 2px; padding-right: 2px; }
}

.s-main--agent {
    max-width: var(--s-shell-max);
    margin: 0 auto;
    padding: var(--s-5) var(--s-4) calc(var(--s-navh) + var(--s-6));
}

@media (min-width: 900px) {
    .s-main--agent { padding-top: var(--s-6); padding-bottom: var(--s-8); }
}

/* ============================================================================
   Partner shell — a fourth door, calm like the tenant's rather than dense
   ========================================================================== */

/* Graphite rather than a fourth hue. The palette stays tight (see DECISIONS §6.3). */
.s-rolechip--partner {
    background: color-mix(in srgb, var(--s-text) 12%, transparent);
    color: var(--s-text);
}

.s-main--partner {
    max-width: 620px;
    margin: 0 auto;
    padding: var(--s-5) var(--s-4) calc(var(--s-navh) + var(--s-7));
}

@media (min-width: 900px) {
    .s-main--partner { padding-top: var(--s-6); padding-bottom: var(--s-8); }
}

/* A nav destination wearing an icon button. Active state still has to read. */
.s-iconbtn--nav { color: var(--s-text-dim); text-decoration: none; }
.s-iconbtn--nav:hover { color: var(--s-text); text-decoration: none; }

.s-iconbtn--nav.active {
    background: var(--s-brand-soft);
    border-color: var(--s-brand-line);
    color: var(--s-brand);
}

/* ============================================================================
   Blazor's unhandled-error bar — hidden until Blazor itself unhides it
   ========================================================================== */

/* The markup is emitted unconditionally by the host page, so without this rule
   it renders as ordinary flow content on first paint and reads as a crash. */
#blazor-error-ui {
    display: none;
    position: fixed;
    left: 0;
    bottom: 0;
    width: 100%;
    box-sizing: border-box;
    padding: var(--s-3) var(--s-4) calc(var(--s-3) + env(safe-area-inset-bottom));
    background: var(--s-warn-soft);
    border-top: 1px solid color-mix(in srgb, var(--s-warn) 28%, transparent);
    color: var(--s-warn);
    font-size: var(--s-fs-sm);
    box-shadow: var(--s-shadow-lg);
    z-index: 1000;
}

#blazor-error-ui .reload { color: inherit; font-weight: 600; text-decoration: underline; }
#blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: var(--s-3); top: var(--s-3); }

/* ============================================================================
   Scroll rails — a row wider than the screen, with somewhere to aim
   ========================================================================== */

.s-rail {
    display: flex;
    align-items: center;
    gap: var(--s-1);
    min-width: 0;
}

.s-rail__track {
    flex: 1 1 auto;
    min-width: 0;
    overflow-x: auto;
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;

    /* The chevrons say what the bar said, without spending a row of height on it. */
    scrollbar-width: none;
}

.s-rail__track::-webkit-scrollbar { display: none; }

/* The track is the scroller. Anything that scrolls on its own stops doing so inside one. */
.s-rail__track > .s-steps,
.s-rail__track > .s-scroll-x { overflow-x: visible; }

.s-rail__chev {
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    flex: 0 0 28px;
    padding: 0;
    border: 1px solid var(--s-border);
    border-radius: 50%;
    background: var(--s-surface);
    color: var(--s-text-dim);
    cursor: pointer;
    transition: opacity var(--s-t-fast), color var(--s-t-fast), border-color var(--s-t-fast);
}

.s-rail__chev:hover { color: var(--s-text); border-color: var(--s-brand-line); }

/* Nothing off the edge: the rail is a plain row again. */
.s-rail.is-still .s-rail__chev { display: none; }

.s-rail.at-start .s-rail__chev--prev,
.s-rail.at-end .s-rail__chev--next {
    opacity: 0.3;
    pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
    .s-rail__track { scroll-behavior: auto; }
}

/* ============================================================================
   Clustered nav — the agent rail carries more places than a rail can hold flat
   ========================================================================== */

.s-navgroup { position: relative; }

.s-navgroup__btn {
    display: inline-flex;
    align-items: center;
    gap: var(--s-2);
    padding: var(--s-2) var(--s-3);
    border: 0;
    border-radius: var(--s-r-pill);
    background: none;
    color: var(--s-text-dim);
    font: inherit;
    font-size: var(--s-fs-sm);
    font-weight: 600;
    cursor: pointer;
    transition: background var(--s-t-fast), color var(--s-t-fast);
}

.s-navgroup__btn:hover { background: var(--s-surface-2); color: var(--s-text); }

/* Lit from anywhere inside it, so the rail never goes dark on a child screen. */
.s-navgroup__btn.active { background: var(--s-brand-soft); color: var(--s-brand); }

.s-navgroup__caret {
    display: inline-flex;
    margin-left: -2px;
    opacity: 0.6;
    transition: transform var(--s-t-fast);
}

.s-navgroup__caret.is-open { transform: rotate(180deg); }

/* Unused since the menus close through sasulwa.outside; kept for any page that still draws one. */
.s-navgroup__scrim {
    position: fixed;
    inset: 0;
    z-index: 30;
    border: 0;
    padding: 0;
    background: none;
    cursor: default;
}

.s-navgroup__menu {
    position: absolute;
    top: calc(100% + var(--s-2));
    left: 0;
    z-index: 31;
    min-width: 210px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--s-2);
    border: 1px solid var(--s-border);
    border-radius: var(--s-r);
    background: var(--s-surface);
    box-shadow: 0 12px 32px rgb(0 0 0 / 22%);
}

/* Stacked, so each destination is a full row rather than a pill in a queue. */
.s-navgroup__menu a {
    display: flex;
    align-items: center;
    gap: var(--s-2);
    padding: var(--s-2) var(--s-3);
    border-radius: var(--s-r-sm);
    font-size: var(--s-fs-sm);
    font-weight: 600;
    color: var(--s-text-dim);
    text-decoration: none;
    white-space: nowrap;
}

.s-navgroup__menu a:hover { background: var(--s-surface-2); color: var(--s-text); text-decoration: none; }
.s-navgroup__menu a.active { background: var(--s-brand-soft); color: var(--s-brand); }

/* A door that only exists while the rail above is not drawing one. */
@media (min-width: 900px) {
    .s-hide-lg { display: none; }
}

/* ============================================================================
   Operator shell — the same chrome as the consoles, for a desk with two things on it
   ========================================================================== */

/* Not a fourth hue and not a role: an operator holds no Party, so the chip borrows the
   marker's weight without claiming a door in the product. */
.s-rolechip--platform {
    background: color-mix(in srgb, var(--s-text) 12%, transparent);
    color: var(--s-text);
}

.s-main--platform {
    max-width: 44rem;
    margin: 0 auto;
    padding: var(--s-5) var(--s-4) calc(var(--s-navh) + var(--s-6));
    display: flex;
    flex-direction: column;
    gap: var(--s-6);
}

@media (min-width: 900px) {
    .s-main--platform { padding-top: var(--s-6); padding-bottom: var(--s-8); }
}

/* ============================================================================
   "I paid and it is not showing" — the way back for money already sent
   ========================================================================== */

.claim {
    display: flex;
    flex-direction: column;
    padding: var(--s-5);
    border-radius: var(--s-r-lg);
    background: var(--s-surface-2);
    border: 1px solid var(--s-border);
}

.claim--done { flex-direction: row; gap: var(--s-3); align-items: flex-start; }

.claim__mark { color: var(--s-ok); flex: 0 0 auto; }

.claim__title { font-weight: 700; font-size: var(--s-fs-base); }

/* The file input is the browser's, so it is themed rather than rebuilt — a custom one is what
   breaks the camera shortcut on a phone, which is how most of these pictures get taken. */
.claim__file {
    width: 100%;
    font-size: var(--s-fs-sm);
    color: var(--s-text-dim);
}

.claim__file::file-selector-button {
    margin-right: var(--s-3);
    padding: var(--s-2) var(--s-4);
    border: 1px solid var(--s-border);
    border-radius: var(--s-r);
    background: var(--s-surface);
    color: var(--s-text);
    font: inherit;
    font-size: var(--s-fs-sm);
    cursor: pointer;
}
