/* ====== BASE ====== */
html, body {
    /* System font stack — native look per OS, zero network call, zero tracking.
       Order: Apple → Windows → Android → fallback. */
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
    background: var(--bg-page);
    height: 100dvh;
    color: var(--text-primary);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    overflow: hidden;
}

/* JasakuIntro (.onboarding) is a full-bleed blue gradient hosted directly on
   <body>. The page-root height is forced to calc(100dvh / --app-zoom) so it fits
   the display-zoom; ×zoom that can miss the physical viewport by a sub-pixel,
   which rounds up to a ~1px line on the device where the white --bg-page canvas
   (painted from <html>) peeks below the gradient. Backstop the canvas with the
   gradient's bottom-edge colour (#42A5F5 darkened by the .ob-bottom overlay) so
   any seam blends in instead of flashing white. The white auth pages don't need
   this — their wrapper bg already matches the canvas. */
html:has(.onboarding) {
    background: #2D70A9;
}

/* The marketing landing page is a WEBSITE, not an app screen, and must scroll the
   document the way every other website does.
   The shell above deliberately locks html/body to the viewport with overflow:hidden
   — that is load-bearing for the app (soft-keyboard resize behaviour, fixed bottom
   bars, per-page inner scrollers) and must not be relaxed globally. But the landing
   page is one long document with no bottom bar and no keyboard, and under the lock
   it could not be scrolled AT ALL by wheel, trackpad or keyboard.
   Why it was easy to miss: an overflow:hidden container is still scrollable
   PROGRAMMATICALLY, so scripted checks (element.scrollIntoView, window.scrollTo)
   moved the page and screenshots looked perfectly fine while a real visitor was
   stuck on the hero.
   Releasing the lock — rather than making .landing its own scroller — keeps native
   document scrolling, which is what carries keyboard paging (Space / PageDown with
   nothing focused), scroll anchoring and the browser's own restore-on-back. A nested
   div scroller silently loses all three. Same :has() opt-out shape as the
   .onboarding rule above. */
/* .doc-scroll is the same opt-out in reusable form: put it on the root element of any
   page that is one long DOCUMENT rather than an app screen, and it scrolls natively.
   The public catalog directory (/cari, CariJasa.razor) carries it.
   It fell into this trap exactly as the landing did, and hid exactly the same way:
   measurements reported no overflow and the screenshots looked correct, because the
   list was clipped rather than overflowing, while a real visitor could not scroll past
   the first screen. Verify this one by pressing PageDown, not by calling scrollTo. */
html:has(.landing),
html:has(.landing) body,
html:has(.doc-scroll),
html:has(.doc-scroll) body {
    height: auto;
    min-height: 100%;
    overflow: visible;
}

/* Hide scrollbars globally — scrolling still works via touch/wheel/keyboard */
* {
    scrollbar-width: none;
    -ms-overflow-style: none;
}
*::-webkit-scrollbar {
    display: none;
}

/* Opt-in subtle scrollbar — re-enables a thin, muted bar over the global hide
   above. Page-level scroll stays hidden for the clean mobile look, but a
   scrollable popover / select-list benefits from a visible bar that signals
   "more options below the fold". Single source so every such list matches —
   applied to .cc-dropdown (country picker) and .selected-summary (chip lists). */
.subtle-scroll {
    scrollbar-width: thin;
    scrollbar-color: rgba(21, 101, 192, 0.35) transparent;
}
.subtle-scroll::-webkit-scrollbar {
    display: block;
    width: 6px;
    height: 6px;
}
.subtle-scroll::-webkit-scrollbar-track {
    background: transparent;
}
.subtle-scroll::-webkit-scrollbar-thumb {
    background: rgba(21, 101, 192, 0.25);
    border-radius: 4px;
}
.subtle-scroll::-webkit-scrollbar-thumb:hover {
    background: rgba(21, 101, 192, 0.45);
}

/* Constrain to portrait width on wide screens (desktop/tablet landscape) */
#app {
    max-width: 480px;
    height: 100dvh;
    margin: 0 auto;
    position: relative;
    overflow: hidden;
}

@media (min-width: 481px) {
    /* No background override here ON PURPOSE (2026-08-27). This block used to paint
       html/body a hardcoded navy #0a1628 as the wide-screen backdrop, and because
       interactive pages are served with an EMPTY body (prerender:false), that navy
       was the entire viewport for the second the circuit takes to connect, in both
       themes: the "loading screen is always navy" report. The base rule's
       var(--bg-page) already resolves before first paint (the boot script sets
       data-theme ahead of the stylesheets), so the loading gap now matches the
       user's theme, the same canvas the splash and theme-color meta use. Post-load
       nothing changes: the page surfaces are full bleed and cover the body at every
       width, and the 1024+ gutter veil tints with --bg-page tokens of its own. */
    #app {
        box-shadow: 0 0 8px rgba(0,0,0,0.18), 0 0 16px rgba(0,0,0,0.14), 0 0 32px rgba(0,0,0,0.10);
    }
}

/* ====== WIDE-SCREEN GUTTER VEIL ======
   On screens wider than the ~880 content column, dress the leftover gutter space
   (outside the caps on header/bar/content). body::after is one fixed panel over
   the RIGHT gutter only (see the dated note below for why the left has none):
   the centred column (header content, cards, bar content, FAB) is never covered.

   PAINTED, NOT SAMPLED. This was real frosted glass until 2026-08-18: a live
   backdrop-filter that re-blurred whatever sat behind it. backdrop-filter cannot be
   cached, so it costs GPU work on every frame the backdrop moves, and that cost
   scales with the gutter area. The gutter grows as the window widens, which is why
   the app scrolled smoothly in a narrow window and stuttered the moment the window
   was maximised: at 1024px each gutter is 72px, at a maximised 2560px display it is
   over 400px a side. A gradient is rasterised once into the layer and costs nothing
   to scroll, which is what the first version of this frame shipped with.

   What the veil is for: the header and the page surface are full bleed, so they run
   edge to edge underneath this panel. The veil washes their outer thirds so the eye
   settles on the 880 column, and a hairline marks where that column begins. These
   gutters only render at 1024px and up; mobile and MAUI are narrower and never paint
   them. Below modals (9500+). */
@media (min-width: 1024px) {
    /* RIGHT GUTTER ONLY (2026-08-19). The veil used to be a mirrored pair washing
       both gutters with --bg-page. That one light wash could not serve both ends of
       the header gradient: over the light right end (--header-grad-end) it dissolved
       beautifully into the page, but over the dark left start (--header-grad-start)
       the same wash sat as a milky film fighting the navy, and every attempt to fix
       the left side properly failed. A dark replacement wash bled below the header
       onto the light page (header heights vary per page, so no fixed mask can hug
       them all), and clipping a deepener to each header means plumbing five separate
       gradient rules (AppHeader, Search, Wallet, ProfilTukang, auth). The dark side
       turns out to need no falloff at all: the header's own gradient already settles
       into deep navy at the left edge, which is exactly the restful edge the veil was
       reaching for. So the left panel is gone, and only the right gutter carries the
       wash. */
    body::after {
        content: "";
        position: fixed;
        top: 0;
        bottom: 0;
        right: 0;
        width: max(0px, calc(50% - 440px));   /* gutter = (viewport - 880)/2 */
        z-index: 9200;
        pointer-events: none;
    }
    /* The ramp runs from the column outward: nothing against the content, deepest at
       the screen edge. It replaces what the old two-layer blur was reaching for (a
       frost that deepened away from the column) with the one thing that actually
       reads at this size, a falloff.

       IT MUST START AT FULLY TRANSPARENT, and there must be no hairline or border on
       the column side. Both were tried on 2026-08-18 and both drew a visible vertical
       line down the page: the panel begins exactly where the content column ends, so
       any tint or rule with substance at the 0% stop is a hard step against an
       untinted column, with no blur left to smear it away. It shows worst with an
       AppSheet open, because the page behind goes soft under filter:blur while a
       crisp line stays crisp, and the seam is the one thing that gives the overlay
       away. Starting from transparent means the boundary carries nothing to see.

       The stops are eased rather than linear for the reason the old mask block gave:
       a straight alpha ramp reads as the wash arriving abruptly rather than growing.

       THE WASH LIVES IN THE TOP HALF ONLY (2026-08-19). The vertical mask fades the
       panel out before mid-screen: the wash earns its keep where it dissolves the
       header's light end into the page, while lower down it had nothing to do
       (bg-page over bg-page) except faintly grey full-bleed white surfaces and the
       bottom bar at the far edge, which read as dirt rather than design. */
    body::after {
        background: linear-gradient(to right,
            transparent 0%,
            color-mix(in srgb, var(--bg-page) 5%, transparent) 22%,
            color-mix(in srgb, var(--bg-page) 17%, transparent) 45%,
            color-mix(in srgb, var(--bg-page) 33%, transparent) 70%,
            color-mix(in srgb, var(--bg-page) 48%, transparent) 100%);
        -webkit-mask-image: linear-gradient(to bottom,
            #000 0, #000 20vh, rgba(0,0,0,0.6) 32vh, rgba(0,0,0,0.2) 43vh, transparent 52vh);
        mask-image: linear-gradient(to bottom,
            #000 0, #000 20vh, rgba(0,0,0,0.6) 32vh, rgba(0,0,0,0.2) 43vh, transparent 52vh);
    }

    /* Guest sign-in: the whole viewport is filter-blurred (scale 1.06 covers the gutters)
       and never animates, so drop the gutter veil entirely. */
    body:has(.guest-body-wrap)::after {
        display: none;
    }
    /* Marketing / landing page: it's a full-bleed site design with its own centred
       1120 container (not the mobile-first ~880 app column), so the gutter frost just
       clips its edges. Drop it entirely — this page is meant to be fullscreen. */
    body:has(.landing)::after,
    /* .doc-scroll pages are marketing surface too (see the scroll opt-out at the top of
       this file): a full-bleed document with its own wide container, not the mobile-first
       ~880 app column. The veil would only clip their edges, so drop it for them as well
       and let the page run to the viewport like every other marketing page. */
    body:has(.doc-scroll)::after {
        display: none;
    }

    /* NOTE: there is deliberately no open-sheet rule here any more. While the veil was a
       live backdrop-filter it had to be switched off whenever an AppSheet opened, because
       sampling an already blurred page doubled the intensity into a seam and re-blurred
       every animation frame. A painted gradient samples nothing, so it composites over an
       open sheet's scrim unchanged and none of that applies. The scrim is full bleed and
       carries its own blur (AppSheet.razor.css), so it covers this veil evenly end to end;
       if a sheet ever looks wrong at the gutter, look there, not at this block. */
}

a {
    color: var(--blue-primary);
    text-decoration: none;
    font-weight: 600;
}

button {
    font-family: inherit;
    cursor: pointer;
}

/* Hide native calendar picker indicator on date inputs. We render our own
   chevron in the field-input-wrap to match select-wrap fields visually.
   Tap on the input itself still opens the native picker on Chromium/WebView.

   Target input[type="date"] directly (not via .date-input class) because:
   1. Blazor scoped CSS attribute rewriting can break ::-webkit-* pseudo-elements
   2. Some Chromium versions only match the indicator selector against the
      bare element type, not class-qualified.
   Cover any future date input on the app without needing the class. */
input[type="date"]::-webkit-calendar-picker-indicator {
    background: transparent;
    color: transparent;
    opacity: 0;
    width: 0;
    height: 0;
    padding: 0;
    margin: 0;
    display: none !important;
    -webkit-appearance: none;
    appearance: none;
}
input[type="date"]::-webkit-inner-spin-button,
input[type="date"]::-webkit-clear-button {
    display: none !important;
    -webkit-appearance: none;
    appearance: none;
}
/* Some WebViews ignore display:none on the indicator. Belt-and-suspenders:
   strip the input's own native chrome too. */
input[type="date"] {
    -webkit-appearance: none;
    appearance: none;
}

/* Native date/time picker theming — Chromium reads color-scheme from the
   input element itself (not always inherited from root) to decide whether
   to render the UA picker UI in light or dark mode.
   Default = follow the theme. */
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"],
input[type="week"] {
    color-scheme: light;
}
[data-theme="dark"] input[type="date"],
[data-theme="dark"] input[type="time"],
[data-theme="dark"] input[type="datetime-local"],
[data-theme="dark"] input[type="month"],
[data-theme="dark"] input[type="week"] {
    color-scheme: dark;
}
@media (prefers-color-scheme: dark) {
    [data-theme="system"] input[type="date"],
    [data-theme="system"] input[type="time"],
    [data-theme="system"] input[type="datetime-local"],
    [data-theme="system"] input[type="month"],
    [data-theme="system"] input[type="week"] {
        color-scheme: dark;
    }
}

/* ====== BROWSER AUTOFILL ======
   Chromium/WebKit paint a solid rectangular background ON the input element
   when it autofills (yellow by default). Two problems for our rounded fields:
   the square corners poke past the wrapper's rounded border, and the color
   clashes with our surface. Fixed app-wide here so EVERY field — Login,
   Profil, ContactChangeModal, Wallet, and any future form — is covered, not
   just the .input-wrapper auth forms.

   The inset box-shadow repaints the autofill background to our surface colour,
   so there's no coloured rectangle left — and box-shadow already follows the
   element's OWN border-radius, so fields that carry their radius on the input
   itself (e.g. Wallet's .form-input) round correctly with no extra work. The
   9999s transition defers the UA's default yellow flash indefinitely.
   var(--bg-white) is theme-aware (white light / dark surface), so this holds in
   both themes.

   NOTE: we deliberately do NOT set border-radius here. Inputs that are a
   borderless child of a rounded wrapper (.input-wrapper, .field-input-wrap)
   instead carry `border-radius: inherit` on their own base rule so the fill
   picks up the wrapper's curve — setting it here would clobber the radius of
   inputs that own their corners (.form-input). */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
    -webkit-box-shadow: 0 0 0 1000px var(--bg-white) inset;
    box-shadow: 0 0 0 1000px var(--bg-white) inset;
    -webkit-text-fill-color: var(--text-primary);
    caret-color: var(--text-primary);
    transition: background-color 9999s ease-in-out 0s;
}

/* ====== SHARED KEYFRAMES ======
   Used by spinners across feedback (PTR), utilities (filter loading, lazy
   load, chat upload), and any future loading indicator. Lives here in base
   so any later file can rely on it without depending on a specific feature
   bundle. */
@keyframes spin {
    to { transform: rotate(360deg); }
}

/* ====== ACCESSIBILITY UTILITY ======
   Visually hides content while keeping it readable by screen readers (TalkBack,
   VoiceOver, NVDA). Use for text labels that complement icon-only or color-only
   indicators so users with vision impairment get the same information sighted
   users do. Standard pattern from WAI-ARIA / HTML5 Boilerplate. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ══════════════════════════════════════════════════════════════════════════════
   GUEST BLUR: LOCK THE SHELL SCROLLER

   The guest shell blurs the page body and lays a sign-in card over it, and
   GuestBodyBlur marks that body `inert` so it cannot be clicked, typed into or
   tabbed through. None of that stops a wheel or a swipe, because the scroller is
   not inside the blurred body at all: .scroll-area is MainLayout's shell scroller
   and it WRAPS the guest wrapper. inert can never reach an ancestor.

   Measured in a clean guest session on /profil, 2026-09-10: .scroll-area is
   overflow-y:scroll with 930px of content in a 767px box, and six wheel notches
   moved it from 0 to 162px while the blurred body itself never moved. That is the
   "the page behind the blur still scrolls" report, and it is also why focusing or
   clicking anything scrolled it: every one of those gestures scrolls this element,
   not the inert one.

   Locking it here rather than in GuestBodyBlur's scoped sheet because the element
   belongs to MainLayout, two components up. Same body:has() shape AppShell already
   uses for its sheet backdrop.
   ══════════════════════════════════════════════════════════════════════════════ */
/* EVERY ancestor, named by shape rather than by class. The first attempt at this
   locked `.scroll-area`, MainLayout's shell scroller, and that fixed 15 of the 16
   guest pages. Pelamar kept scrolling because it runs its own scroller,
   .pelamar-scroll, in the same position: an ANCESTOR of the guest wrapper, not a
   descendant. Measured on /pelamar/1: overflow-y auto, 924px of content in a 767px
   box, sitting directly above .guest-body-wrap.

   Naming classes was the wrong shape of fix; the next page to add a scroller would
   have reopened the hole silently. :has() selects an ancestor, so this covers every
   page that exists and every page that does not yet.

   The leading `body:has(...)` is load-bearing and not decoration. A bare
   `*:has(.guest-body-wrap)` computes to (0,1,0) and LOSES to the scoped page rule
   it has to beat: MainLayout's `.scroll-area` and Pelamar's `.pelamar-scroll` both
   come out of a scoped sheet as `.x[b-xxxxx]`, which is (0,2,0). Prefixing brings
   this to (0,2,1). Measured without it: both pages scrolled again at all three
   widths while the other fourteen stayed locked, which is exactly what a silent
   specificity loss looks like. */
body:has(.guest-body-wrap) *:has(.guest-body-wrap) {
    overflow: hidden;
    /* Stops a swipe that reaches the end from chaining out to the document. */
    overscroll-behavior: contain;
}

/* And every scroller INSIDE the blurred body, for the same reason in the other
   direction. .guest-body-content is overflow:hidden, but that only clips it; a
   page that runs its own scroller inside (Pelamar's .pelamar-scroll) still took
   the wheel. Measured after the shell lock above: 15 of the 16 guest pages were
   already still, Pelamar moved 0 to 156px.

   The universal selector is deliberate and cheap here: this subtree is inert and
   purely decorative, so clipping anything inside it costs nothing, and matching by
   class name would miss the next page that adds a scroller. No !important needed:
   this selector is (0,2,1) and a page's own scoped `.x-scroll { overflow: auto }`
   is (0,2,0). */
body:has(.guest-body-wrap) .guest-body-content * {
    overflow: hidden;
    overscroll-behavior: contain;
}
