/* ==========================================================================
   wizard-ui.css — mobile design layer for the applicant wizard.

   WHAT THIS FILE IS
   The visual replacement for the wizard rendered by js/app.js. It is loaded
   AFTER custom.css / app.css / datepicker.css and every rule is scoped under
   #app, so an id selector outranks their plain class selectors. Nothing in
   the older stylesheets is deleted — removing the single <link> in index.php
   restores the previous UI in full.

   WHY IT IS SCOPED
   static/css/app.css is also loaded by dashboard.php (the admin login screen)
   and popup-test.php. Rewriting it in place would put the admin login at risk.
   #app only exists in index.php, so nothing outside the wizard can be touched
   by anything in here.

   COLOUR IS NOT HARDCODED
   api/theme.php injects --primary, --btn, --bg-page and friends from the admin
   Theme modal, in a <style> emitted after this file. Every token below is an
   ALIAS onto those variables, never a literal, so changing the palette in the
   dashboard still repaints the whole wizard. The design's #0066cc / #f5f5f7
   appear only as the fallback an unconfigured install falls back to.

   TYPE
   -apple-system resolves to real SF Pro on iOS/macOS, which is what the design
   was drawn in. Inter (already loaded by index.php at 400/500/600/700/800) is
   the substitute everywhere else. Weight 300 is deliberately unused — it is
   not in the loaded Inter subset and would silently synthesise.
   ========================================================================== */

/* .ui-portal is for overlays mounted on <body> rather than inside #app, so a
   re-render cannot remove them mid-interaction. They need the same tokens. */
#app,
.ui-portal {
    /* --- Brand: aliases onto the admin-controlled theme variables ---------- */
    --ui-accent: var(--primary, #0066cc);
    --ui-accent-hover: var(--primary-hover, #0071e3);
    --ui-accent-on-dark: var(--primary-light, #2997ff);
    --ui-btn: var(--btn, var(--primary, #0066cc));
    --ui-btn-hover: var(--btn-hover, var(--primary-hover, #0071e3));
    /* On the near-black tile the accent can fall below contrast. theme.php
       derives --btn-light as a +20% lightness step off the admin's button
       colour, so this stays legible whatever hue they pick. */
    --ui-btn-on-dark: var(--btn-light, var(--primary-light, #2997ff));
    --ui-on-accent: #ffffff;
    /* Very light wash of the brand color for tinted surfaces (trust panel,
       badge pucks) — theme.php already computes this at 92% lightness. */
    --ui-accent-tint: var(--primary-ultra-light, #eef6f1);

    /* --- Surface ---------------------------------------------------------- */
    --ui-canvas: var(--bg-page, #f5f5f7);
    --ui-card: #ffffff;
    --ui-tile-dark: #272729;
    --ui-tile-puck: rgba(255, 255, 255, 0.14);

    /* --- Ink -------------------------------------------------------------- */
    --ui-ink: #1d1d1f;
    --ui-ink-muted: #7a7a7a;
    --ui-ink-on-dark: #ffffff;
    --ui-ink-muted-on-dark: #cccccc;
    /* Placeholders sit a clear step lighter than muted body copy, so an empty
       field never reads as filled in. This is Apple's own placeholder tone. */
    --ui-placeholder: rgba(60, 60, 67, 0.35);

    /* --- Hairlines -------------------------------------------------------- */
    --ui-hairline: rgba(0, 0, 0, 0.08);
    --ui-hairline-solid: #e0e0e0;

    /* --- Semantic --------------------------------------------------------- */
    --ui-success: #16a34a;
    --ui-success-tint: #e6f4ea;
    --ui-danger: #d92c20;
    --ui-danger-tint: #fdeceb;

    /* --- Shape ------------------------------------------------------------ */
    --ui-r-sm: 8px;
    --ui-r-md: 11px;
    --ui-r-lg: 18px;
    --ui-r-sheet: 24px;
    --ui-r-pill: 9999px;

    /* --- Elevation. The system uses surface + hairline, not shadow; the only
           drop shadows are on photo previews and floating overlays. --------- */
    --ui-shadow-photo: 0 4px 16px rgba(0, 0, 0, 0.1);
    --ui-shadow-modal: 0 20px 50px rgba(0, 0, 0, 0.25);
    --ui-shadow-sheet: 0 -8px 30px rgba(0, 0, 0, 0.15);

    /* --- Motion ----------------------------------------------------------- */
    --ui-press: 0.95;
    --ui-ease: cubic-bezier(0.28, 0.11, 0.32, 1);
    --ui-fast: 120ms;

    /* --- Type ------------------------------------------------------------- */
    --ui-font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter", system-ui, sans-serif;
    --ui-font-display: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Inter", system-ui, sans-serif;

    /* --- Layout ----------------------------------------------------------- */
    /* The design is drawn at 375px. Content is capped so a 430px phone gets
       breathing room at the edges rather than stretched fields, and the gutter
       tightens on a 320px screen. */
    --ui-gutter: 20px;
    --ui-measure: 460px;
    --ui-safe-top: env(safe-area-inset-top, 0px);
    --ui-safe-bottom: env(safe-area-inset-bottom, 0px);
}

@media (max-width: 359px) {
    #app {
        --ui-gutter: 16px;
    }
}

/* app.css only paints `body`, not `html`. On iOS Safari's momentum-scroll
   overscroll (rubber-banding past the top/bottom edge) that gap shows through
   as flat white instead of the page canvas — the verification pages don't
   have this gap because their <body> IS the scroll container. Painting html
   too closes it, so the background is the canvas colour edge-to-edge with no
   flash, matching those pages. Safe to set here: this file loads only on
   index.php, so it cannot repaint any other page. */
html {
    background: var(--ui-canvas);
}

/* ==========================================================================
   SHELL
   The old UI centred a 480px card on a page background. The design is a
   full-bleed phone screen: parchment edge to edge, chrome pinned to the top,
   content scrolling beneath it. .wizard-shell / .application-form keep their
   class names (js/app.js:1055 writes them, along with .is-fullscreen and
   .is-welcome) — only their appearance changes.
   ========================================================================== */

#app .wizard-shell {
    display: block;
    min-height: 100dvh;
    /* js/app.js:5389 keeps --app-height in sync with window.innerHeight, which
       is what mobile Safari actually gives us once its toolbars collapse. */
    min-height: var(--app-height, 100dvh);
    padding: 0;
    background: var(--ui-canvas);
}

/* static/css/custom.css:267-302 declares this card's geometry with !important
   (it has 209 !important declarations in total, most of them for Flatpickr,
   which this app does not even load — it uses js/datepicker.js). Specificity
   cannot beat !important, so the properties that collide are re-declared the
   same way here. This is a response to custom.css's own weight, not a
   shortcut: everything that does NOT collide is left as a normal declaration.
   custom.css is loaded only by index.php and popup-test.php, so once the
   redesign covers everything it uniquely styles, dropping it removes the need
   for this block entirely. */
#app .application-form {
    display: flex;
    flex-direction: column;
    /* A fixed side inset (not just centring) so the card shows a safe gray
       margin on every screen width, not only once the viewport exceeds
       --ui-measure. */
    width: calc(100% - 28px);
    /* Capped so a 430px phone gets edge breathing room instead of stretched
       44px fields; below the cap the screen is genuinely full-bleed. */
    max-width: var(--ui-measure) !important;
    min-height: calc(var(--app-height, 100dvh) - 28px) !important;
    max-height: none !important;
    height: auto !important;
    /* custom.css:563 re-adds a 10px margin inside its 575.98px media query. */
    margin: 14px auto !important;
    padding: 0 !important;
    padding-bottom: 0 !important;
    background: var(--ui-card);
    border: 1px solid rgba(0, 0, 0, 0.12);
    border-radius: var(--ui-r-sheet) !important;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 14px 36px rgba(0, 0, 0, 0.09) !important;
    overflow: hidden !important;
}

/* The liveness step takes over the viewport, and its geometry is load-bearing:
   #webcam is sized with height:100%, which only resolves if .application-form
   has a DEFINITE height. Stretching it as a flex item is what makes it
   definite — a block box with min-height is not, and the video silently falls
   back to its intrinsic 4:3 size, leaving the face-guide oval (positioned at
   top:50% of the full-height container) stranded below the picture. So this
   step keeps the original flex + stretch shell rather than the block shell the
   other steps use. */
#app .wizard-shell.is-fullscreen {
    display: flex;
    align-items: stretch;
    padding: 0;
}

#app .wizard-shell.is-fullscreen,
#app .application-form.is-fullscreen {
    /* !important because the base .application-form rule above caps width for
       the scrolling steps; fullscreen means fullscreen. */
    max-width: 100% !important;
    background: #000;
}

#app .application-form.is-fullscreen {
    /* The camera view stays true edge-to-edge — no card chrome, no side inset. */
    width: 100% !important;
    margin: 0 !important;
    border-radius: 0 !important;
    box-shadow: none !important;
    border: 0 !important;
}

/* app.css:2107-2136 gave the welcome step a 14px inset and stretched the card
   to fill the viewport, because the old screen was one illustration and a CTA
   that would otherwise bunch at the top. Home is now a scrolling dashboard, so
   those rules only reintroduce a gap. */
#app .wizard-shell.is-welcome {
    padding: 0;
}

#app .application-form.is-welcome {
    min-height: calc(var(--app-height, 100dvh) - 28px) !important;
    margin: 14px auto !important;
}

/* custom.css:300 sets this padding with !important. */
#app .form-body {
    flex: 1;
    min-height: 0;
    padding: 8px var(--ui-gutter) calc(24px + var(--ui-safe-bottom)) !important;
}

/* ==========================================================================
   APP BAR
   Replaces the brand lockup + gradient banner + 4px progress bar with the
   design's screen title over a 3px hairline track.
   ========================================================================== */

#app .ui-appbar {
    flex-shrink: 0;
    padding: calc(18px + var(--ui-safe-top)) var(--ui-gutter) 12px;
    background: var(--ui-card);
}

/* The camera/liveness step is still true black full-bleed (see
   .application-form.is-fullscreen above), so its header keeps the old
   canvas-coloured bar instead of turning white-on-black. */
#app .application-form.is-fullscreen .ui-appbar {
    background: var(--ui-canvas);
}

#app .ui-appbar-row {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 14px;
}

#app .ui-appbar-title {
    font: 600 17px/1.3 var(--ui-font-display);
    letter-spacing: -0.01em;
    color: var(--ui-ink);
    margin: 0;
    min-width: 0;
}

/* Drawn at the design's 20px, but with the tap area extended past the glyph so
   it clears 44px without pushing the title off the design's baseline. */
#app .ui-appbar-back {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--ui-ink);
    cursor: pointer;
    position: relative;
    -webkit-tap-highlight-color: transparent;
}

#app .ui-appbar-back::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 44px;
    height: 44px;
    transform: translate(-50%, -50%);
}

#app .ui-appbar-back:active {
    opacity: 0.5;
}

#app .ui-appbar-brand {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-bottom: 14px;
}

#app .ui-brand {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font: 600 15px/1.2 var(--ui-font);
    letter-spacing: -0.01em;
    color: var(--ui-ink);
}

#app .ui-brand > i {
    font-size: 16px;
    color: var(--ui-accent);
}

/* --- Trust badge: header-right "Trusted by 10,000+ Renters" ---------------- */
#app .ui-trust-badge {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

#app .ui-trust-badge-icon {
    width: 30px;
    height: 30px;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--ui-canvas);
    display: flex;
    align-items: center;
    justify-content: center;
}

#app .ui-trust-badge-text {
    font: 500 11px/1.35 var(--ui-font);
    color: var(--ui-ink-muted);
    text-align: left;
}

/* ==========================================================================
   COMPONENT LIBRARY
   Defined here, applied by the render functions in js/app.js from Phase 1 on.
   ========================================================================== */

/* --- Typography ---------------------------------------------------------- */
#app .ui-title {
    font: 600 20px/1.25 var(--ui-font-display);
    letter-spacing: -0.01em;
    color: var(--ui-ink);
    margin: 0;
}

#app .ui-title-lg {
    font: 600 24px/1.2 var(--ui-font-display);
    letter-spacing: -0.02em;
    color: var(--ui-ink);
    margin: 0;
}

#app .ui-copy {
    font: 400 14px/1.5 var(--ui-font);
    color: var(--ui-ink-muted);
    margin: 0;
}

#app .ui-copy-sm {
    font: 400 12px/1.5 var(--ui-font);
    color: var(--ui-ink-muted);
    margin: 0;
}

/* --- Section header: numbered circle + label ----------------------------- */
#app .ui-section-head {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 12px 0 14px;
}

#app .ui-step-badge {
    width: 24px;
    height: 24px;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--ui-accent);
    color: var(--ui-on-accent);
    font: 600 12px/1 var(--ui-font);
    display: flex;
    align-items: center;
    justify-content: center;
}

#app .ui-section-title {
    font: 600 15px/1.3 var(--ui-font);
    letter-spacing: -0.01em;
    color: var(--ui-ink);
    margin: 0;
}

/* --- Fields -------------------------------------------------------------- */
#app .ui-field {
    display: block;
    margin-bottom: 10px;
}

/* Centred, as the design draws them. The control's own text stays left —
   see .ui-input/.ui-textarea below — so a centred label sits over a
   left-reading value, which is what the reference shows. */
#app .ui-label {
    display: block;
    font: 600 13px/1.2 var(--ui-font);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    text-align: center;
    color: var(--ui-ink-muted);
    margin-bottom: 6px;
    transition: color var(--ui-fast) var(--ui-ease);
}

/* 16px is the floor: anything smaller makes iOS Safari zoom the viewport on
   focus, which breaks the layout and cannot be undone without user-scalable=0
   side effects. The design's 15px is honoured visually via the tighter
   letter-spacing rather than by shrinking below the zoom threshold. */
#app .ui-input,
#app .ui-select,
#app .ui-textarea {
    -webkit-appearance: none;
    appearance: none;
    display: block;
    width: 100%;
    background: var(--ui-card);
    border: 1px solid rgba(0, 0, 0, 0.22);
    border-radius: var(--ui-r-sm);
    color: var(--ui-ink);
    font: 400 16px/1.3 var(--ui-font);
    letter-spacing: -0.01em;
    /* Explicit: the form centres its labels, and without this the value and
       placeholder would inherit that and centre too. */
    text-align: left;
    transition: border-color var(--ui-fast) var(--ui-ease);
}

#app .ui-input,
#app .ui-select {
    height: 44px;
    padding: 0 14px;
}

#app .ui-textarea {
    min-height: 84px;
    padding: 12px 14px;
    line-height: 1.5;
    /* No drag handle: it is dead weight on touch and the corner grabber is
       visible in the resting state, which the design does not have. */
    resize: none;
}

#app .ui-input::placeholder,
#app .ui-textarea::placeholder {
    color: var(--ui-placeholder);
    opacity: 1; /* Firefox dims placeholders by default */
}

#app .ui-input:focus,
#app .ui-select:focus,
#app .ui-textarea:focus {
    outline: none;
    border: 1.5px solid var(--ui-accent);
    /* Compensate the extra half-pixel of border so the control does not shift
       its neighbours when it takes focus. */
    padding-left: 13.5px;
    padding-right: 13.5px;
}

#app .ui-field:focus-within .ui-label {
    color: var(--ui-accent);
}

/* Native select chevron is suppressed above; this is the design's own. */
#app .ui-select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%237a7a7a' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 14px center;
    padding-right: 38px;
}

#app .ui-select:invalid,
#app .ui-select option[value=""] {
    color: var(--ui-ink-muted);
}

/* Validation. Reuses Bootstrap's .is-invalid hook so the existing
   validateForm() in js/app.js keeps working untouched. */
#app .ui-input.is-invalid,
#app .ui-select.is-invalid,
#app .ui-textarea.is-invalid {
    border: 1.5px solid var(--ui-danger);
    padding-left: 13.5px;
    padding-right: 13.5px;
    background-image: none;
}

#app .ui-field:has(.is-invalid) .ui-label {
    color: var(--ui-danger);
}

#app .ui-error {
    display: none;
    font: 400 12px/1.4 var(--ui-font);
    color: var(--ui-danger);
    margin-top: 5px;
}

#app .is-invalid ~ .ui-error {
    display: block;
}

#app .ui-counter {
    font: 400 11px/1 var(--ui-font);
    color: var(--ui-ink-muted);
    text-align: right;
    margin-top: 6px;
}

/* Two-up field row (first/last name, city/zip). */
#app .ui-field-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px;
}

#app .ui-field-row > .ui-field {
    margin-bottom: 10px;
    min-width: 0;
}

/* --- Buttons ------------------------------------------------------------- */
.ui-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-height: 48px;
    padding: 11px 22px;
    border: 1px solid transparent;
    border-radius: var(--ui-r-pill);
    font: 600 15px/1.2 var(--ui-font);
    letter-spacing: -0.01em;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
    transition: transform var(--ui-fast) var(--ui-ease),
                background-color var(--ui-fast) var(--ui-ease);
}

.ui-btn:active:not(:disabled) {
    transform: scale(var(--ui-press));
}

.ui-btn:focus-visible {
    outline: 2px solid var(--ui-accent-hover);
    outline-offset: 2px;
}

.ui-btn-primary {
    background: var(--ui-btn);
    color: var(--ui-on-accent);
}

.ui-btn-primary:hover:not(:disabled) {
    background: var(--ui-btn-hover);
}

.ui-btn-secondary {
    background: var(--ui-card);
    border-color: var(--ui-hairline);
    color: var(--ui-ink);
}

/* On the dark tile the accent loses contrast, so the CTA switches to the
   lighter on-dark tone the design specifies. */
.ui-btn-on-dark {
    background: var(--ui-btn-on-dark);
    color: var(--ui-on-accent);
}

.ui-btn:disabled,
.ui-btn[disabled] {
    background: var(--ui-hairline);
    border-color: transparent;
    color: var(--ui-ink-muted);
    cursor: not-allowed;
    transform: none;
}

.ui-btn-block {
    display: flex;
    width: 100%;
}

.ui-btn-tall {
    min-height: 50px;
}

/* Paired Next / Re-take row. */
.ui-btn-row {
    display: flex;
    gap: 12px;
    width: 100%;
}

.ui-btn-row > .ui-btn {
    flex: 1;
    min-width: 0;
    padding-left: 12px;
    padding-right: 12px;
}

/* --- Cards --------------------------------------------------------------- */
#app .ui-card {
    background: var(--ui-card);
    border: 1px solid var(--ui-hairline);
    border-radius: var(--ui-r-lg);
    padding: 14px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 14px 36px rgba(0, 0, 0, 0.09);
}

#app .ui-card-active {
    border-color: var(--ui-accent);
}

#app .ui-card-pending {
    opacity: 0.85;
}

#app .ui-card-pad {
    padding: 20px;
}

/* Icon puck that sits at the top of a card. */
#app .ui-puck {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: var(--ui-canvas);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--ui-accent);
    flex-shrink: 0;
}

#app .ui-puck-lg {
    width: 56px;
    height: 56px;
}

#app .ui-puck-success {
    background: var(--ui-success-tint);
    color: var(--ui-success);
}

/* --- Dark tile ----------------------------------------------------------- */
#app .ui-tile {
    background: var(--ui-tile-dark);
    border-radius: var(--ui-r-lg);
    padding: 20px;
    color: var(--ui-ink-on-dark);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 14px 36px rgba(0, 0, 0, 0.09);
}

#app .ui-tile .ui-copy,
#app .ui-tile .ui-copy-sm {
    color: var(--ui-ink-muted-on-dark);
}

#app .ui-tile-puck {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background: var(--ui-tile-puck);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    color: var(--ui-ink-on-dark);
}

/* --- Progress ------------------------------------------------------------ */
#app .ui-track {
    height: 3px;
    background: var(--ui-hairline);
    border-radius: var(--ui-r-pill);
    overflow: hidden;
}

#app .ui-track-fill {
    height: 100%;
    width: 0;
    background: var(--ui-accent);
    border-radius: var(--ui-r-pill);
    transition: width 450ms var(--ui-ease);
}

#app .ui-track-on-dark {
    height: 5px;
    background: var(--ui-tile-puck);
}

#app .ui-track-on-dark .ui-track-fill {
    background: var(--ui-accent-on-dark);
}

/* --- Overlays ------------------------------------------------------------ */
#app .ui-scrim {
    position: fixed;
    inset: 0;
    z-index: 1040;
    background: rgba(29, 29, 31, 0.45);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    padding-bottom: calc(24px + var(--ui-safe-bottom));
    animation: ui-fade var(--ui-fast) var(--ui-ease);
}

/* Over the camera screens the backdrop is pure black, so the scrim needs to be
   heavier to keep the card reading as foreground. */
#app .ui-scrim-heavy {
    background: rgba(29, 29, 31, 0.55);
}

#app .ui-modal {
    width: 100%;
    max-width: 340px;
    max-height: calc(100dvh - 48px - var(--ui-safe-bottom));
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    background: var(--ui-card);
    border-radius: var(--ui-r-lg);
    padding: 32px 24px 28px;
    text-align: center;
    box-shadow: var(--ui-shadow-modal);
    animation: ui-pop 200ms var(--ui-ease);
}

/* The Next Step popup sits a step larger than the guide popups. */
#app .ui-modal-lg {
    padding: 36px 26px 30px;
}

#app .ui-modal-icon {
    width: 56px;
    height: 56px;
    margin: 0 auto 18px;
    border-radius: 50%;
    background: var(--ui-canvas);
    color: var(--ui-accent);
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Ringed variant — used where the icon is the screen's whole subject. */
#app .ui-modal-icon-ring {
    width: 64px;
    height: 64px;
    margin-bottom: 20px;
    border: 2px solid var(--ui-accent);
}

#app .ui-modal-title {
    font: 600 22px/1.25 var(--ui-font-display);
    letter-spacing: -0.01em;
    color: var(--ui-ink);
    margin: 0 0 12px;
}

#app .ui-modal-copy {
    font: 400 14px/1.55 var(--ui-font);
    color: var(--ui-ink-muted);
    margin: 0 0 26px;
}

#app .ui-modal-cta {
    padding-left: 32px;
    padding-right: 32px;
}

/* Content behind a blocking overlay recedes, as the design draws it. */
#app .ui-body-dimmed {
    opacity: 0.5;
    pointer-events: none;
}

#app .ui-sheet-wrap {
    position: fixed;
    inset: 0;
    z-index: 1040;
    background: rgba(29, 29, 31, 0.45);
    display: flex;
    align-items: flex-end;
    animation: ui-fade var(--ui-fast) var(--ui-ease);
}

#app .ui-sheet {
    width: 100%;
    max-height: 88dvh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    background: var(--ui-card);
    border-radius: var(--ui-r-sheet) var(--ui-r-sheet) 0 0;
    padding: 20px var(--ui-gutter) calc(28px + var(--ui-safe-bottom));
    box-shadow: var(--ui-shadow-sheet);
    animation: ui-rise 240ms var(--ui-ease);
}

@keyframes ui-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
}

@keyframes ui-pop {
    from { opacity: 0; transform: scale(0.96); }
    to   { opacity: 1; transform: scale(1); }
}

@keyframes ui-rise {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
    #app .ui-scrim,
    #app .ui-modal,
    #app .ui-sheet-wrap,
    #app .ui-sheet {
        animation: none;
    }

    #app .ui-btn:active:not(:disabled) {
        transform: none;
    }

    #app .ui-track-fill {
        transition: none;
    }
}

/* --- Photo preview ------------------------------------------------------- */
#app .ui-photo {
    /* .ui-review-body is a flex column with align-items: center, so with no
       width/height/background of its own this hugs the img's rendered size
       instead of drawing a letterboxed canvas-colour box around it. */
    max-width: 270px;
    max-height: 340px;
    border-radius: var(--ui-r-lg);
    overflow: hidden;
    box-shadow: var(--ui-shadow-photo);
}

#app .ui-photo > img {
    /* ID cards are landscape, selfies are portrait — forcing a 3:4 box with
       object-fit: cover cropped whichever shape didn't match. max-width/
       max-height + auto sizing shows the whole capture at its real ratio
       with no cropping and no extra background around it. */
    display: block;
    max-width: 100%;
    max-height: 340px;
    width: auto;
    height: auto;
}

/* ==========================================================================
   FORM
   ========================================================================== */

#app .ui-form-section {
    margin-bottom: 20px;
}

#app .ui-req {
    color: var(--ui-accent);
}

/* The design centres a pill of natural width here, not a full-bleed bar. */
#app .ui-form-actions {
    margin-top: 24px;
    text-align: center;
}

#app .ui-form-actions .ui-btn {
    padding-left: 40px;
    padding-right: 40px;
}

#app .ui-error,
#app .ui-counter {
    text-align: center;
}

/* custom.css:405-415 styles this button by id (gradient, 14px radius, bold
   uppercase, drop shadow) with !important on most of it. The id is kept
   because it reads as an external automation/QA hook, so the presentation is
   overridden here instead of renaming the element. */
#app .ui-btn#btn-go-to-photo-id {
    background: var(--ui-btn) !important;
    border: 1px solid transparent !important;
    border-radius: var(--ui-r-pill) !important;
    padding: 11px 22px !important;
    font: 600 15px/1.2 var(--ui-font) !important;
    letter-spacing: -0.01em;
    text-transform: none;
    box-shadow: none !important;
}

#app .ui-btn#btn-go-to-photo-id:hover:not(:disabled) {
    transform: none !important;
    background: var(--ui-btn-hover) !important;
    box-shadow: none !important;
}

#app .ui-btn#btn-go-to-photo-id:active:not(:disabled) {
    transform: scale(var(--ui-press)) !important;
}

#app .ui-btn#btn-go-to-photo-id:disabled {
    background: var(--ui-hairline) !important;
    color: var(--ui-ink-muted);
}

#app .ui-spinner {
    width: 16px;
    height: 16px;
    border: 2px solid rgba(255, 255, 255, 0.35);
    border-top-color: #fff;
    border-radius: 50%;
    animation: ui-spin 700ms linear infinite;
}

@keyframes ui-spin {
    to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    #app .ui-spinner { animation-duration: 2s; }
}

/* Until something is chosen the select reads as placeholder text, matching the
   greyed "Select gender" in the design. */
#app .ui-select.is-placeholder {
    color: var(--ui-placeholder);
}

/* --- Date field ----------------------------------------------------------
   .rdp-field and its children are js/datepicker.js's contract, so the markup
   is untouched and only the presentation is replaced here. The visible input
   is readonly and opens the picker, so the whole row is the tap target. */

#app .rdp-field {
    position: relative;
    display: flex;
    align-items: center;
    height: 44px;
    padding: 0 14px;
    background: var(--ui-card);
    border: 1px solid var(--ui-hairline);
    border-radius: var(--ui-r-sm);
    transition: border-color var(--ui-fast) var(--ui-ease);
}

#app .rdp-field.has-value {
    border-color: var(--ui-accent);
}

#app .rdp-input {
    flex: 1;
    min-width: 0;
    /* Fills the row so the tap target is the full 44px, not just the text. */
    height: 44px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--ui-ink);
    font: 400 16px/1.3 var(--ui-font);
    letter-spacing: -0.01em;
    cursor: pointer;
}

#app .rdp-input:focus {
    outline: none;
}

#app .rdp-input::placeholder {
    color: var(--ui-placeholder);
    opacity: 1;
}

#app .rdp-trail {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 24px;
    height: 44px;
    margin-right: -4px;
    color: var(--ui-ink-muted);
    cursor: pointer;
}

#app .rdp-field.has-value .rdp-trail {
    color: var(--ui-accent);
}

/* Only offered once there is a value to clear; sized to stay tappable. */
#app .rdp-clear {
    display: none;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 32px;
    height: 44px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--ui-ink-muted);
    font-size: 12px;
    cursor: pointer;
}

#app .rdp-field.has-value .rdp-clear {
    display: flex;
}

/* js/datepicker.js appends the calendar sheet to document.body, not #app, so
   it needs an unscoped override. Safe here: this file loads only on
   index.php, so it cannot leak onto any other page. Matches the wizard
   card's 14px safe side margin instead of docking flush to the screen edge. */
.rdp-panel.is-sheet {
    left: 14px;
    right: 14px;
    width: auto;
}

/* ==========================================================================
   HOME
   Photo hero + greeting, appointment status tile, the landlord-pipeline
   tracker, and the trust panel.
   ========================================================================== */

/* The section heading stays centred; the hero and every card below it read
   left-aligned, so alignment is set locally on each block rather than
   inherited from here. */
#app .ui-home {
    /* How far .ui-hero-tile is pulled down out of the hero panel (see its
       negative bottom margin below). The part that ends up outside the panel
       is this minus the hero's own 20px bottom padding, and everything after
       the hero has to clear it — so the pull and the clearance read one
       number here instead of drifting apart as two literals. */
    --ui-hero-pull: 56px;
    text-align: center;
}

/* --- Hero: greeting + tile, over a photo/gradient panel ------------------- */
/* No real photo asset ships yet — this gradient is a placeholder standing in
   for one. Swap it for `background: url(...) center/cover` (plus this same
   gradient as a ::before scrim, for text contrast) once a photo is supplied. */
/* No overflow:hidden here — .ui-hero-tile's negative bottom margin is what
   makes it visually protrude past the photo panel onto the canvas below, and
   clipping would cut that protrusion off. border-radius still rounds the
   gradient itself with no help from overflow. */
#app .ui-hero {
    position: relative;
    text-align: left;
    padding: 22px 20px 20px;
    border-radius: var(--ui-r-lg);
    background:
        radial-gradient(120% 90% at 85% 0%, rgba(255, 255, 255, 0.16), transparent 60%),
        linear-gradient(155deg, var(--ui-accent) 0%, var(--ui-accent-hover) 100%);
}

#app .ui-hero-icon {
    display: block;
    margin-bottom: 14px;
    opacity: 0.9;
}

#app .ui-hero-title {
    font: 700 26px/1.2 var(--ui-font-display);
    letter-spacing: -0.02em;
    color: #ffffff;
    margin: 0 0 6px;
}

#app .ui-hero-sub {
    font: 400 14px/1.45 var(--ui-font);
    color: rgba(255, 255, 255, 0.85);
    margin: 0 0 20px;
    max-width: 260px;
}

/* Sits inside .ui-hero in the markup; the negative bottom margin shrinks
   .ui-hero's own auto height so the tile's opaque background visibly
   protrudes past the photo panel's rounded bottom edge onto the page canvas. */
/* Slightly transparent so the photo reads through the tile. Done on the
   background rather than with `opacity`, which would fade the white title and
   CTA along with it and cost more contrast than the effect is worth. The plain
   hex is declared first as the fallback: a browser without color-mix() drops
   the second declaration and keeps the tile fully opaque. */
#app .ui-hero-tile {
    background: var(--ui-accent);
    background: color-mix(in srgb, var(--ui-accent) 90%, transparent);
    margin: 4px 0 calc(var(--ui-hero-pull) * -1);
}

/* The shared on-dark tokens (.ui-tile-pct, .ui-track-on-dark, .ui-btn-on-dark)
   were tuned for the near-black .ui-tile-dark background elsewhere (Complete
   screen) — against this tile's flat accent-color background they read as
   low-contrast accent-on-accent, so this hero variant overrides them locally
   rather than changing the shared tokens themselves. */
#app .ui-hero-tile .ui-tile-pct {
    color: #ffffff;
}

#app .ui-hero-tile .ui-track-on-dark {
    background: rgba(255, 255, 255, 0.3);
}

#app .ui-hero-tile .ui-track-on-dark .ui-track-fill {
    background: #ffffff;
}

#app .ui-hero-tile .ui-btn-on-dark {
    background: #ffffff;
    color: var(--ui-accent);
}

#app .ui-tile-head {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 14px;
}

#app .ui-tile-head-text {
    flex: 1;
    min-width: 0;
    text-align: left;
}

/* Auto width — the design does not stretch this one edge to edge the way it
   does the screen-level CTAs. .ui-btn is inline-flex, which auto margins
   cannot centre, so this switches to a block-level flex box kept at its
   shrink-to-fit width and centred in the tile.
   position: relative anchors .ui-tap-hint to the button's corner. */
#app .ui-tile-cta {
    display: flex;
    width: fit-content;
    margin-inline: auto;
    padding: 11px 28px;
    position: relative;
}

/* --- Tap hint -------------------------------------------------------------
   The looping finger on the Home CTA. Hangs off the button's bottom-right so
   it sits over the tile rather than over the white button face, where a white
   glyph would vanish. Purely decorative: pointer-events:none keeps it from
   swallowing the tap it is advertising, and the button has no overflow
   clipping, so hanging outside its pill is safe. */
#app .ui-tap-hint {
    position: absolute;
    right: -14px;
    bottom: -16px;
    width: 30px;
    height: 30px;
    pointer-events: none;
    color: #ffffff;
    filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.35));
}

#app .ui-tap-finger {
    display: block;
    /* Pivot at the fingertip, so the press reads as the finger hinging onto
       the button rather than the whole hand sliding. */
    transform-origin: 46% 12%;
    animation: ui-tap-press 2.2s var(--ui-ease) infinite;
}

/* Ripple, centred on the fingertip and timed to the bottom of the press. */
#app .ui-tap-ring {
    position: absolute;
    left: 1px;
    top: -6px;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    border: 2px solid #ffffff;
    opacity: 0;
    animation: ui-tap-ripple 2.2s var(--ui-ease) infinite;
}

/* Two thirds of the cycle is a hold — a continuous jiggle reads as a broken
   asset, while a beat between taps reads as a gesture. */
@keyframes ui-tap-press {
    0%, 55%, 100% { transform: translate(0, 0) scale(1); }
    68%           { transform: translate(-3px, -4px) scale(0.88); }
    82%           { transform: translate(0, 0) scale(1); }
}

@keyframes ui-tap-ripple {
    0%, 62%  { transform: scale(0.35); opacity: 0; }
    70%      { opacity: 0.6; }
    92%, 100% { transform: scale(1.5); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    /* The finger still marks the button; only the motion goes. */
    #app .ui-tap-finger { animation: none; }
    #app .ui-tap-ring { display: none; }
}

#app .ui-tile-title {
    display: block;
    font: 600 15px/1.25 var(--ui-font);
    letter-spacing: -0.01em;
    color: var(--ui-ink-on-dark);
    margin: 0;
}

#app .ui-tile-pct {
    flex-shrink: 0;
    font: 600 12px/1 var(--ui-font);
    color: var(--ui-accent-on-dark);
}

#app .ui-tile .ui-track {
    margin-bottom: 14px;
}

/* The padding has to absorb the tile's overhang before it can open any gap at
   all: at a flat 32px it fell 4px short of the 36px protrusion, and since
   .ui-hero is positioned it painted over the top of this heading. Subtracting
   the hero's bottom padding from the pull gives the protrusion; the 24px on
   top of it is the gap the section actually shows. */
#app .ui-progress-header {
    margin: 0 0 14px;
    padding-top: calc(var(--ui-hero-pull) - 20px + 24px);
}

#app .ui-progress-title {
    font: 600 15px/1.3 var(--ui-font-display);
    letter-spacing: -0.01em;
    color: var(--ui-ink);
    margin: 0 0 4px;
}

#app .ui-progress-sub {
    font: 400 12.5px/1.45 var(--ui-font);
    color: var(--ui-ink-muted);
    margin: 0;
    max-width: 320px;
    margin-left: auto;
    margin-right: auto;
}

#app .ui-progress-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-bottom: 20px;
    padding: 0;
    list-style: none;
}

/* Each card is icon (left) / title+copy (middle, grows) / status (right) —
   overriding .ui-home's centred default, which only applies to the section
   heading now. */
#app .ui-progress-card {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    text-align: left;
}

#app .ui-progress-card-body {
    flex: 1;
    min-width: 0;
}

#app .ui-progress-card-title {
    font: 600 13px/1.35 var(--ui-font);
    color: var(--ui-ink);
    margin: 0;
}

#app .ui-progress-card-sub {
    font: 400 12px/1.35 var(--ui-font);
    color: var(--ui-ink-muted);
    margin: 3px 0 0;
}

#app .ui-progress-card-status {
    flex-shrink: 0;
    font: 600 11.5px/1.3 var(--ui-font);
    white-space: nowrap;
    margin-top: 1px;
}

#app .ui-progress-card-status-done {
    color: var(--ui-success);
}

#app .ui-progress-card-status-active {
    color: var(--ui-accent);
    background: var(--ui-accent-tint);
    padding: 4px 10px;
    border-radius: var(--ui-r-pill);
}

#app .ui-progress-card-status-pending {
    color: var(--ui-ink-muted);
    background: var(--ui-canvas);
    padding: 4px 10px;
    border-radius: var(--ui-r-pill);
}

/* --- Trust panel ----------------------------------------------------------- */
#app .ui-trust {
    text-align: left;
    background: var(--ui-accent-tint);
    border-radius: var(--ui-r-lg);
    padding: 18px;
}

#app .ui-trust-head {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    margin-bottom: 18px;
}

#app .ui-trust-icon {
    flex-shrink: 0;
    margin-top: 2px;
}

#app .ui-trust-head-text {
    flex: 1;
    min-width: 0;
}

#app .ui-trust-title {
    display: block;
    font: 700 15px/1.3 var(--ui-font-display);
    letter-spacing: -0.01em;
    color: var(--ui-accent);
    margin: 0 0 4px;
}

#app .ui-trust-sub {
    display: block;
    font: 400 12.5px/1.4 var(--ui-font);
    color: var(--ui-ink-muted);
}

#app .ui-trust-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
}

#app .ui-trust-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 6px;
}

#app .ui-trust-item-icon {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background: #ffffff;
    display: flex;
    align-items: center;
    justify-content: center;
}

#app .ui-trust-item-title {
    font: 600 10.5px/1.25 var(--ui-font);
    letter-spacing: -0.01em;
    color: var(--ui-ink);
}

#app .ui-trust-item-sub {
    font: 400 9.5px/1.25 var(--ui-font);
    color: var(--ui-ink-muted);
}

/* ==========================================================================
   CAPTURE + REVIEW
   The photo steps sit on the white card like every other step now (see the
   .application-form card rules above) — .ui-surface-white used to also
   paint the outer .wizard-shell canvas white, which erased the gray safe
   margin around the card since both were the same colour. Its content is a
   single block centred in the space under the app bar.
   ========================================================================== */

#app .form-body.ui-body-centred {
    display: flex;
    flex-direction: column;
}

#app .ui-capture {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 20px 8px;
}

#app .ui-capture-icon {
    width: 64px;
    height: 64px;
    margin-bottom: 20px;
    border-radius: 50%;
    background: var(--ui-canvas);
    color: var(--ui-accent);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

#app .ui-capture-title {
    font: 600 20px/1.25 var(--ui-font-display);
    letter-spacing: -0.01em;
    color: var(--ui-ink);
    margin: 0 0 8px;
}

#app .ui-capture-copy {
    font: 400 14px/1.5 var(--ui-font);
    color: var(--ui-ink-muted);
    max-width: 280px;
    margin: 0 0 24px;
}

#app .ui-capture-sample {
    width: 100%;
    border-radius: var(--ui-r-lg);
    overflow: hidden;
    margin-bottom: 28px;
}

#app .ui-capture-sample img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
    background: var(--ui-canvas);
}

/* The ID sample is a landscape card; the selfie sample is a portrait figure
   that would be cropped by a fixed box, so it is capped rather than framed. */
#app .ui-capture-sample-id {
    aspect-ratio: 16 / 10;
}

#app .ui-capture-sample-selfie {
    max-width: 230px;
    margin-left: auto;
    margin-right: auto;
}

#app .ui-capture-sample-selfie img {
    max-height: 260px;
    background: none;
}

#app .ui-capture-cta {
    padding-left: 28px;
    padding-right: 28px;
}

/* --- Review -------------------------------------------------------------- */

#app .ui-review {
    flex: 1;
    display: flex;
    flex-direction: column;
}

#app .ui-review-head {
    flex-shrink: 0;
    padding-top: 8px;
}

#app .ui-review-copy {
    font: 400 13px/1.5 var(--ui-font);
    color: var(--ui-ink-muted);
    margin: 0 0 16px;
}

#app .ui-review-body {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 28px;
}

/* --- Upload status -------------------------------------------------------
   Not in the design (its mock shows an already-uploaded photo), but it is the
   only feedback during a slow upload and the only place an error surfaces, so
   it stays. Drawn as a full-width bar rather than a shrink-to-fit pill: it
   reads as a progress indicator, and it stops the box resizing as the label
   changes from "Uploading now" to "Uploaded". */

#app .upload-status {
    width: 100%;
    margin: 0;
    padding: 8px 12px;
    background: var(--ui-card);
    border: 1px solid var(--ui-hairline);
    border-radius: var(--ui-r-sm);
    box-sizing: border-box;
}

#app .upload-status-row {
    font: 600 12px/1.3 var(--ui-font);
    letter-spacing: -0.01em;
    color: var(--ui-ink);
    gap: 8px;
}

#app .status-dot {
    width: 8px;
    height: 8px;
    margin-right: 2px;
}

#app .progress-track {
    height: 4px;
    margin-top: 6px;
    background: var(--ui-hairline);
}

#app .progress-fill {
    background: var(--ui-accent);
}

/* Sits directly under the photo rather than at the section's own rhythm. */
#app .ui-review-body .upload-status {
    margin-top: -14px;
}

#app .ui-capture ~ .upload-status {
    margin-top: 16px;
}

/* --- Liveness instruction checklist ---------------------------------------
   updateLivenessIntro() (js/app.js) toggles .active on the row whose audio is
   playing, along with Bootstrap's .text-success / .fw-bold / .text-muted. The
   check mark is rendered into every row and revealed by .active, and colour and
   weight are set explicitly on the label span — a child's own declaration beats
   an inherited one, so those Bootstrap classes are neutralised without touching
   the rotation code. */

#app .ui-liveness-steps {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin: 0;
    padding: 0;
    list-style: none;
}

#app .ui-liveness-step {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0;
}

#app .ui-liveness-dot {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: var(--ui-hairline);
    color: transparent;
    transition: background-color var(--ui-fast) var(--ui-ease);
}

#app .ui-liveness-step.active .ui-liveness-dot {
    background: var(--ui-accent);
    color: #fff;
}

#app .ui-liveness-label {
    font: 400 13px/1.4 var(--ui-font);
    color: var(--ui-ink-muted);
}

#app .ui-liveness-step.active .ui-liveness-label {
    font-weight: 600;
    color: var(--ui-ink);
}

#app .ui-liveness-actions {
    margin-top: 32px;
    text-align: center;
}

/* --- Instruction modal --------------------------------------------------- */

#app .ui-modal-instruction {
    padding: 24px 22px;
}

#app .ui-instruction-image {
    display: block;
    width: 100%;
    max-height: 220px;
    object-fit: contain;
    border-radius: 12px;
    margin-bottom: 18px;
}

#app .ui-instruction-copy {
    font: 400 14px/1.5 var(--ui-font);
    color: var(--ui-ink);
    margin: 0 0 22px;
}

#app .ui-instruction-note {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    text-align: left;
    background: var(--ui-canvas);
    border-radius: var(--ui-r-sm);
    padding: 12px;
    margin: -8px 0 18px;
    font: 400 12px/1.5 var(--ui-font);
    color: var(--ui-ink-muted);
}

#app .ui-instruction-note svg {
    flex-shrink: 0;
    margin-top: 1px;
}

#app .ui-instruction-note strong {
    color: var(--ui-ink);
}

/* ==========================================================================
   COMPLETE — dark prompt tile, selfie booster card, Finish
   ========================================================================== */

#app .ui-complete-tile {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    margin-bottom: 20px;
    text-align: left;
}

#app .ui-complete-tile .ui-tile-puck {
    width: 44px;
    height: 44px;
}

#app .ui-complete-tile-title {
    font: 600 16px/1.3 var(--ui-font);
    letter-spacing: -0.01em;
    color: var(--ui-ink-on-dark);
    margin: 0 0 4px;
}

#app .ui-complete-tile-copy {
    font: 400 13px/1.5 var(--ui-font);
    color: var(--ui-ink-muted-on-dark);
    margin: 0;
}

#app .ui-complete-tile-copy strong {
    color: var(--ui-ink-on-dark);
}

#app .ui-boost {
    text-align: center;
    margin-bottom: 20px;
}

#app .ui-boost-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin: 0 auto 12px;
    border-radius: 50%;
    background: var(--ui-canvas);
    color: var(--ui-accent);
}

#app .ui-boost-title {
    font: 600 16px/1.3 var(--ui-font-display);
    letter-spacing: -0.01em;
    color: var(--ui-ink);
    margin: 0 0 6px;
}

#app .ui-boost-lead {
    font: 400 13px/1.5 var(--ui-font);
    color: var(--ui-ink-muted);
    margin: 0 0 16px;
}

#app .ui-boost-examples {
    display: flex;
    gap: 8px;
    justify-content: center;
    margin-bottom: 18px;
}

#app .ui-boost-example {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 52px;
    height: 52px;
    border-radius: var(--ui-r-sm);
    background: var(--ui-canvas);
    color: var(--ui-ink-muted);
    overflow: hidden;
}

#app .ui-boost-example svg {
    width: 28px;
    height: 28px;
}

#app .ui-dropzone {
    border: 1.5px dashed var(--ui-hairline);
    border-radius: var(--ui-r-lg);
    padding: 20px;
    margin-bottom: 16px;
}

#app .ui-dropzone-icon {
    display: block;
    color: var(--ui-accent);
    margin-bottom: 8px;
}

#app .ui-dropzone-title {
    font: 600 14px/1.3 var(--ui-font);
    color: var(--ui-ink);
    margin: 0 0 4px;
}

#app .ui-dropzone-hint {
    font: 400 12px/1.4 var(--ui-font);
    color: var(--ui-ink-muted);
    margin: 0 0 12px;
}

#app .ui-dropzone-btn {
    min-height: 44px;
    padding: 0 24px;
}

#app .ui-dropzone-count {
    font: 600 11px/1 var(--ui-font);
    color: var(--ui-ink-muted);
    margin: 10px 0 0;
}

#app .ui-boost-note {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    text-align: left;
    margin-top: 10px;
}

#app .ui-boost-note p {
    font: 400 12px/1.5 var(--ui-font);
    color: var(--ui-ink-muted);
    margin: 0;
}

#app .ui-boost-note strong {
    color: var(--ui-ink);
}

#app .ui-boost-note-icon {
    flex-shrink: 0;
    margin-top: 2px;
    color: var(--ui-ink-muted);
    line-height: 0;
}

#app .ui-boost-note:first-of-type .ui-boost-note-icon {
    color: var(--ui-accent);
}

/* Reads as "not yet" without being unusable — see renderComplete(). */
#app .ui-btn-quiet {
    background: var(--ui-hairline);
    color: var(--ui-ink-muted);
}

#app .ui-complete-hint {
    font: 400 12px/1.4 var(--ui-font);
    color: var(--ui-ink-muted);
    text-align: center;
    margin: 8px 0 0;
}

/* ==========================================================================
   THANK YOU
   ========================================================================== */

#app .ui-thanks-overlay {
    position: fixed;
    inset: 0;
    z-index: 1050;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px 14px;
    padding-bottom: calc(20px + var(--ui-safe-bottom));
    box-sizing: border-box;
    background: var(--ui-canvas);
    overflow-y: auto;
}

#app .ui-thanks-card {
    width: calc(100% - 24px);
    max-width: var(--ui-measure, 440px);
    box-sizing: border-box;
    background: var(--ui-card);
    border-radius: var(--ui-r-sheet, 24px);
    padding: 32px 20px 26px;
    text-align: center;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 20px 40px rgba(0, 0, 0, 0.15);
    border: 1px solid rgba(0, 0, 0, 0.08);
}

@media (max-width: 480px) {
    #app .ui-thanks-card {
        padding: 28px 16px 22px;
        width: calc(100% - 20px);
    }
}

#app .ui-thanks-diag {
    background: #f8fafc;
    border: 1px dashed #cbd5e1;
    border-radius: 12px;
    padding: 9px 12px;
    margin: 0 0 18px;
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: 6px 10px;
    box-sizing: border-box;
    width: 100%;
    max-width: 100%;
    overflow: hidden;
}

#app .ui-thanks-check {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    margin: 0 auto 20px;
    border-radius: 50%;
    background: var(--ui-success-tint);
    color: var(--ui-success);
}

#app .ui-thanks-title {
    font: 600 22px/1.25 var(--ui-font-display);
    letter-spacing: -0.01em;
    color: var(--ui-ink);
    margin: 0 0 6px;
}

#app .ui-thanks-sub {
    font: 400 14px/1.45 var(--ui-font);
    color: var(--ui-ink-muted);
    margin: 0 0 22px;
}

#app .ui-thanks-next {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    text-align: left;
    background: var(--ui-canvas);
    border: 1px solid var(--ui-hairline);
    border-radius: var(--ui-r-lg);
    padding: 16px;
    margin-bottom: 22px;
}

#app .ui-thanks-next-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background: var(--ui-accent);
    color: var(--ui-on-accent);
}

#app .ui-thanks-next-title {
    font: 600 14px/1.3 var(--ui-font);
    color: var(--ui-ink);
    margin-bottom: 3px;
}

#app .ui-thanks-next-copy,
#app .ui-thanks-inbox-copy {
    font: 400 12px/1.5 var(--ui-font);
    color: var(--ui-ink-muted);
    margin: 0;
}

#app .ui-thanks-inbox {
    margin-bottom: 22px;
}

#app .ui-thanks-inbox-title {
    font: 600 14px/1.3 var(--ui-font);
    color: var(--ui-ink);
    margin: 0 0 3px;
}

/* ==========================================================================
   PROCESSING OVERLAY
   Mounted on <body> by showProcessingOverlay(), so not #app-scoped. The ids
   (#processing-ring, #processing-number) and the .processing-* class names are
   the contract with setProcessingProgress()/updateProcessingRing() and are left
   alone — only presentation changes here. showProcessingError() swaps in its
   own card, which keeps its existing styling from app.css.
   ========================================================================== */

.processing-overlay {
    position: fixed;
    inset: 0;
    z-index: 1090;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 0 32px;
    box-sizing: border-box;
    text-align: center;
    background: rgba(255, 255, 255, 0.96);
    font-family: var(--ui-font);
}

.processing-circle-wrap {
    position: relative;
    margin-bottom: 20px;
}

.processing-circle-bg {
    fill: none;
    stroke: var(--ui-hairline);
    stroke-width: 8;
}

.processing-circle-fill {
    fill: none;
    stroke: url(#processingGradient);
    stroke-width: 8;
    stroke-linecap: round;
    transform: rotate(-90deg);
    transform-origin: 70px 70px;
    transition: stroke-dashoffset 400ms var(--ui-ease);
}

.processing-percent {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font: 700 26px/1 var(--ui-font-display);
    letter-spacing: -0.01em;
    color: var(--ui-ink);
}

/* app.css:1837 shrinks both spans to 1rem, dims them and adds a left margin —
   which shrank the number too, since the value lives in a span as well. The
   design reads "69%" as one figure. */
.processing-percent > span {
    font: inherit;
    color: inherit;
    opacity: 1;
    margin: 0;
}

.processing-title {
    font: 600 19px/1.25 var(--ui-font-display);
    letter-spacing: -0.01em;
    color: var(--ui-ink);
    margin: 0 0 8px;
}

.processing-subtitle {
    font: 400 14px/1.45 var(--ui-font);
    color: var(--ui-ink-muted);
    margin: 0 0 4px;
}

.processing-subtitle + .processing-subtitle {
    font-size: 13px;
    margin-bottom: 0;
}

/* ==========================================================================
   EMAIL GUIDE POPUP
   Mounted on <body> by showEmailGuide() in js/app.js, so these rules are not
   #app-scoped. The class names are unique to this file and it is only loaded
   by index.php, so nothing else can be reached by them.
   ========================================================================== */

.ui-email-overlay {
    position: fixed;
    inset: 0;
    z-index: 1080;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    padding-bottom: calc(24px + var(--ui-safe-bottom));
    box-sizing: border-box;
    background: rgba(29, 29, 31, 0.45);
    opacity: 0;
    transition: opacity 200ms var(--ui-ease);
}

.ui-email-overlay.visible {
    opacity: 1;
}

.ui-email-card {
    width: 100%;
    max-width: 340px;
    box-sizing: border-box;
    background: var(--ui-card);
    border-radius: var(--ui-r-lg);
    padding: 32px 24px 28px;
    text-align: center;
    box-shadow: var(--ui-shadow-modal);
    transform: scale(0.96);
    transition: transform 200ms var(--ui-ease);
}

.ui-email-overlay.visible .ui-email-card {
    transform: scale(1);
}

.ui-email-icon {
    width: 56px;
    height: 56px;
    margin: 0 auto 18px;
    border-radius: 50%;
    background: var(--ui-canvas);
    color: var(--ui-accent);
    display: flex;
    align-items: center;
    justify-content: center;
}

.ui-email-title {
    font: 600 19px/1.25 var(--ui-font-display);
    letter-spacing: -0.01em;
    color: var(--ui-ink);
    margin: 0 0 10px;
}

.ui-email-copy {
    font: 400 14px/1.55 var(--ui-font);
    color: var(--ui-ink-muted);
    margin: 0 0 24px;
}

.ui-email-cta {
    padding-left: 32px;
    padding-right: 32px;
}

@media (prefers-reduced-motion: reduce) {
    .ui-email-overlay,
    .ui-email-card {
        transition: none;
    }
}

/* ==========================================================================
   DEVICE GATE
   The client half of the desktop block (js/app.js: enforceDeviceGate). Mounted
   on <body>, so these are NOT #app-scoped — .ui-portal on the node is what
   carries the --ui-* tokens out here.

   Opaque rather than a scrim, and above every other overlay in the file
   (.ui-email-overlay 1080, .processing-overlay 1090, and app.css's legacy
   99999 pair): this one is terminal, so nothing may ever sit on top of it.
   ========================================================================== */

.ui-devgate {
    position: fixed;
    inset: 0;
    z-index: 100000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    padding-bottom: calc(24px + var(--ui-safe-bottom));
    box-sizing: border-box;
    background: var(--ui-canvas);
    opacity: 0;
    transition: opacity 200ms var(--ui-ease);
}

.ui-devgate.visible {
    opacity: 1;
}

.ui-devgate-card {
    width: 100%;
    max-width: 360px;
    box-sizing: border-box;
    background: var(--ui-card);
    border-radius: var(--ui-r-lg);
    padding: 40px 28px 32px;
    text-align: center;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 18px 48px rgba(0, 0, 0, 0.10);
}

.ui-devgate-icon {
    width: 72px;
    height: 72px;
    margin: 0 auto 24px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #ffffff;
    background: linear-gradient(155deg, var(--ui-accent) 0%, var(--ui-accent-hover) 100%);
}

.ui-devgate-title {
    font: 700 22px/1.25 var(--ui-font-display);
    letter-spacing: -0.02em;
    color: var(--ui-ink);
    margin: 0 0 12px;
}

/* The wording is admin-authored and written with line breaks; app.js sets it
   with textContent, so pre-line is what preserves them. */
.ui-devgate-copy {
    font: 400 14px/1.6 var(--ui-font);
    color: var(--ui-ink-muted);
    white-space: pre-line;
    margin: 0;
}

@media (prefers-reduced-motion: reduce) {
    .ui-devgate {
        transition: none;
    }
}

/* --- Utilities ----------------------------------------------------------- */
#app .ui-stack {
    display: flex;
    flex-direction: column;
}

#app .ui-gap-10 { gap: 10px; }
#app .ui-gap-14 { gap: 14px; }

#app .ui-center {
    text-align: center;
}

/* Images must never be able to push the layout wider than the viewport. */
#app img,
#app svg,
#app video,
#app canvas {
    max-width: 100%;
}
