/* =============================================================================
   OwnerApp — shared UI framework
   -----------------------------------------------------------------------------
   Loaded from inc/header.php AFTER _style.css and overrides.css, so it wins where
   it deliberately overlaps. Everything here is either a token or an `sb-` prefixed
   component class, so it cannot collide with existing selectors.

   Why this exists: every page was hand-rolling its own <style> block with its own
   magic numbers. The same card, stat tile, row and error banner were written more
   than once with different values, which is what makes an app read as a website —
   spacing that doesn't repeat and type that jumps between screens.

   Rules for using it:
     1. Use the spacing scale. Never invent a one-off px value.
     2. Use the type scale. Eight sizes is enough for this app.
     3. New shared UI goes here as an `sb-` component, not in a page's <style>.
     4. Anything below 16px must not be an <input> — iOS zooms on focus.
   ============================================================================= */

:root {
    /* ---- Spacing: 4pt grid. ------------------------------------------------ */
    --sb-1: 4px;
    --sb-2: 8px;
    --sb-3: 12px;
    --sb-4: 16px;
    --sb-5: 20px;
    --sb-6: 24px;
    --sb-8: 32px;

    /* ---- Type scale -------------------------------------------------------- */
    --sb-fs-caption: 11.5px;
    --sb-fs-meta:    12.5px;
    --sb-fs-sm:      13.5px;
    --sb-fs-body:    15px;
    --sb-fs-input:   16px;   /* never lower — iOS auto-zooms below this */
    --sb-fs-title:   17px;
    --sb-fs-h:       21px;
    --sb-fs-display: 26px;

    --sb-lh-tight: 1.25;
    --sb-lh-body:  1.5;

    /* ---- Colour ------------------------------------------------------------ */
    --sb-brand:        #12305C;
    --sb-brand-mid:    #285DA9;
    --sb-ink:          #16202E;
    --sb-ink-2:        #45536B;
    --sb-muted:        #6B7A8D;
    --sb-line:         rgba(0,0,0,.06);
    --sb-line-strong:  rgba(0,0,0,.12);
    --sb-surface:      #FFFFFF;
    --sb-surface-2:    #EDF1F6;   /* segmented-control track, subtle fills */
    --sb-ground:       #F4F6F9;

    --sb-danger:       #B3261E;
    --sb-danger-bg:    #FBEAE8;
    --sb-warn:         #9A6212;
    --sb-warn-bg:      #FBF1DF;
    --sb-ok:           #1F6B45;
    --sb-ok-bg:        #E7F3EC;

    /* ---- Shape & depth ----------------------------------------------------- */
    --sb-r-sm: 8px;
    --sb-r:    12px;
    --sb-r-lg: 16px;
    --sb-r-pill: 999px;
    --sb-shadow: 0 1px 2px rgba(16,29,48,.06), 0 4px 14px rgba(16,29,48,.05);

    /* ---- Motion ------------------------------------------------------------
       120ms is the ceiling for "this felt instant". 320ms with an iOS-like curve
       is the sheet/drawer duration. */
    --sb-t-press: 120ms;
    --sb-t-base:  200ms;
    --sb-t-sheet: 320ms;
    --sb-ease:    cubic-bezier(.32,.72,0,1);

    /* ---- Safe areas -------------------------------------------------------- */
    --sb-safe-top:    env(safe-area-inset-top, 0px);
    --sb-safe-bottom: env(safe-area-inset-bottom, 0px);

    /* Minimum comfortable touch target (Apple HIG). */
    --sb-touch: 44px;
}

/* =============================================================================
   1. Native-feel base
   These are the details that separate a wrapped web page from an app.
   ============================================================================= */

/* iOS paints a grey flash on every tap. Remove it and provide our own intentional
   press states instead — never remove it without replacing it. */
html { -webkit-tap-highlight-color: transparent; }

/* Chrome (buttons, labels, nav, badges) should not be text-selectable — long-press
   selecting a tab label is an instant tell. Content remains selectable. */
button, .sb-btn, .sb-stat, .sb-tab, .sb-badge, .sb-empty, label,
#footer-bar, .header, .tab-controls {
    -webkit-user-select: none;
    user-select: none;
}
input, textarea, select, p, td, .sb-selectable { -webkit-user-select: text; user-select: text; }

/* Momentum scrolling, and stop scroll chaining so a scrolled list doesn't drag the
   page (or the native webview) behind it. */
.sb-scroll, .page-content {
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-y: contain;
}

/* Kill the document-level rubber-band. When the page overscrolls past its end, iOS
   momentarily detaches position:fixed chrome (the footer nav floats up leaving a gap).
   `overscroll-behavior: none` on the scroll root stops the bounce and keeps the footer
   pinned. NOTE: the definitive fix is native — the WKWebView wrapper setting
   scrollView.bounces = false — this only covers the web side. */
html, body { overscroll-behavior: none; }

/* ---- Desktop: the whole document scrolls, not an inner pane -----------------
   On phones the app is a fixed shell whose `.page-content` is the scroll region,
   and `overscroll-behavior-y: contain` (above) stops scroll-chaining into the
   native webview. On a desktop mouse that same setup breaks the wheel: because
   `.page-content` also carries `overflow-x: hidden` (the mobile horizontal-spill
   guard from _style.css), the desktop `overflow-y: visible` COMPUTES TO `auto`
   per the CSS overflow spec — so `.page-content` silently becomes a fits-content
   scroll box that, combined with `overscroll-behavior: contain`, swallows every
   wheel tick instead of chaining to the document. The scrollbar (on <html>) then
   shows and drags, but the wheel does nothing.

   Fix: at >=768px let <html>/<body> own the scroll and take `.page-content` out of
   the scroll-container business. `overflow-x: clip` hides horizontal spill exactly
   like `hidden` did, but stays in the non-scrolling group so `overflow-y` remains
   truly `visible` (no auto promotion), and `overscroll-behavior: auto` restores
   chaining. This supersedes the per-page desktop overrides, which only set
   overflow-y and so hit the same quirk. */
@media (min-width: 768px) {
    html, body { height: auto !important; min-height: 100% !important; overflow-y: auto !important; overscroll-behavior: auto !important; }
    #page, .page-content { height: auto !important; overflow-x: clip !important; overflow-y: visible !important; overscroll-behavior: auto !important; }
}

/* Honour the system setting rather than animating regardless. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
    }
}

/* =============================================================================
   2. Components
   ============================================================================= */

/* ---- Card ---- */
.sb-card {
    background: var(--sb-surface);
    border: 1px solid var(--sb-line);
    border-radius: var(--sb-r);
    padding: var(--sb-4);
    margin-bottom: var(--sb-3);
}
.sb-card-tight { padding: var(--sb-3); }

/* ---- Section heading ---- */
.sb-h {
    font-size: var(--sb-fs-title);
    font-weight: 650;
    color: var(--sb-ink);
    line-height: var(--sb-lh-tight);
    margin: 0 0 var(--sb-2);
}
.sb-sub { font-size: var(--sb-fs-sm); color: var(--sb-muted); margin: 0 0 var(--sb-3); }

/* ---- Stat tiles ---- */
.sb-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
    gap: var(--sb-2);
    margin-bottom: var(--sb-3);
}
.sb-stat {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: 72px;
    padding: var(--sb-3) var(--sb-2);
    background: var(--sb-surface);
    border: 1px solid var(--sb-line);
    border-radius: var(--sb-r);
    text-align: center;
    text-decoration: none;
    color: inherit;
    transition: transform var(--sb-t-press) var(--sb-ease);
}
.sb-stat:active { transform: scale(.97); }
.sb-stat .n {
    font-size: var(--sb-fs-h);
    font-weight: 700;
    line-height: 1.1;
    color: var(--sb-brand);
    font-variant-numeric: tabular-nums;
}
.sb-stat .l {
    font-size: var(--sb-fs-caption);
    color: var(--sb-muted);
    margin-top: var(--sb-1);
    line-height: var(--sb-lh-tight);
}
.sb-stat.is-flagged .n { color: var(--sb-danger); }

/* ---- Row (list item) ---- */
.sb-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sb-3);
    min-height: var(--sb-touch);
    padding: var(--sb-3) 0;
    border-bottom: 1px solid var(--sb-line);
    text-decoration: none;
    color: inherit;
    transition: background var(--sb-t-press) var(--sb-ease);
}
.sb-row:last-child { border-bottom: 0; }
.sb-row:active { background: rgba(0,0,0,.03); }
.sb-row-main { min-width: 0; }
.sb-row-title {
    font-size: var(--sb-fs-body);
    font-weight: 600;
    color: var(--sb-ink);
    line-height: var(--sb-lh-tight);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sb-row-meta {
    font-size: var(--sb-fs-meta);
    color: var(--sb-muted);
    margin-top: 2px;
}
.sb-row-side { flex: none; text-align: right; }

/* ---- Badge ---- */
.sb-badge {
    display: inline-block;
    font-size: var(--sb-fs-caption);
    font-weight: 600;
    line-height: 1;
    padding: 5px 9px;
    border-radius: var(--sb-r-pill);
    background: var(--sb-ground);
    color: var(--sb-ink-2);
    border: 1px solid var(--sb-line);
    white-space: nowrap;
}
.sb-badge-warn   { background: var(--sb-warn-bg);   color: var(--sb-warn);   border-color: transparent; }
.sb-badge-ok     { background: var(--sb-ok-bg);     color: var(--sb-ok);     border-color: transparent; }
.sb-badge-danger { background: var(--sb-danger-bg); color: var(--sb-danger); border-color: transparent; }

/* ---- Buttons ---- */
.sb-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sb-2);
    min-height: var(--sb-touch);
    padding: 0 var(--sb-4);
    border: none;
    border-radius: var(--sb-r);
    font-size: var(--sb-fs-body);
    font-weight: 600;
    cursor: pointer;
    transition: transform var(--sb-t-press) var(--sb-ease), filter var(--sb-t-press) var(--sb-ease);
}
.sb-btn:active { transform: scale(.985); filter: brightness(.97); }
.sb-btn:disabled { opacity: .55; }
.sb-btn-primary { background: var(--sb-brand); color: #fff; }
.sb-btn-quiet {
    background: var(--sb-surface);
    color: var(--sb-brand);
    border: 1px solid var(--sb-line-strong);
}
.sb-btn-block { display: flex; width: 100%; }
.sb-btn:focus-visible { outline: 3px solid var(--sb-brand-mid); outline-offset: 2px; }

/* ---- States ---- */
.sb-note {
    display: flex;
    align-items: flex-start;
    gap: var(--sb-3);
    padding: var(--sb-3);
    border-radius: var(--sb-r-sm);
    font-size: var(--sb-fs-sm);
}
.sb-note p { margin: 0; flex: 1; }
.sb-note-error { background: var(--sb-danger-bg); color: #8A1F18; }
.sb-note-warn  { background: var(--sb-warn-bg);   color: var(--sb-warn); }

.sb-empty {
    text-align: center;
    padding: var(--sb-8) var(--sb-4);
    color: var(--sb-muted);
    font-size: var(--sb-fs-body);
    line-height: var(--sb-lh-body);
}
.sb-empty .sb-empty-title {
    display: block;
    font-size: var(--sb-fs-title);
    font-weight: 600;
    color: var(--sb-ink-2);
    margin-bottom: var(--sb-1);
}

/* ---- Skeletons ----
   Sized to match the real content they stand in for, so nothing shifts when data
   arrives. A skeleton that doesn't match its content is worse than a spinner. */
.sb-sk {
    background: linear-gradient(90deg, rgba(0,0,0,.05), rgba(0,0,0,.10), rgba(0,0,0,.05));
    background-size: 200% 100%;
    animation: sb-shimmer 1.3s linear infinite;
    border-radius: var(--sb-r-sm);
}
@keyframes sb-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
.sb-sk-line { height: 13px; margin-bottom: var(--sb-2); }
.sb-sk-title { height: 16px; width: 62%; margin-bottom: var(--sb-2); }

/* ---- Accessibility helper ----
   For status text that should reach a screen reader but not the screen — e.g. the
   "Loading…" announcement paired with a visual skeleton. */
.sb-visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

/* ---- Wide content ----
   Tables and grids must scroll inside their own container so the page body never
   scrolls sideways. Several screens had `overflow:hidden` here instead, which
   clipped columns off-screen with no way to reach them. */
.sb-scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ---- pjax busy indicator ----
   A thin top progress sliver while the next tab's content is fetched. Deliberately
   subtle — the swap is fast, so this is reassurance, not a spinner. */
.pjax-busy::after {
    content: ""; position: fixed; top: 0; left: 0; height: 3px; width: 100%;
    z-index: 2147482000; background: var(--sb-brand-mid);
    animation: sb-pjax 900ms var(--sb-ease) infinite;
    transform-origin: left;
}
@keyframes sb-pjax {
    0% { transform: scaleX(0); opacity: 1; }
    60% { transform: scaleX(.85); opacity: 1; }
    100% { transform: scaleX(1); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .pjax-busy::after { animation: none; opacity: .6; } }

/* ---- Safe-area helpers ---- */
.sb-safe-bottom { padding-bottom: calc(var(--sb-4) + var(--sb-safe-bottom)); }
.sb-safe-top    { padding-top: calc(var(--sb-4) + var(--sb-safe-top)); }

/* ---- Native touch feel ----
   Remove the browser tells that make a WebView read as a web page: the blue tap
   highlight, the long-press callout/selection on controls, and the ~300ms tap delay.
   Inputs and readable content stay selectable so an owner can still copy a
   confirmation number or a statement figure. */
html { -webkit-tap-highlight-color: transparent; }
body { touch-action: manipulation; }   /* removes the double-tap-to-zoom click delay */
button, a, .btn, [role="button"], .sb-btn, .tile, .stat-tile, .row-item,
.list-group-item, .resv-tab, .resv-subtab, .view-mode-control button,
#footer-bar a, .footer-bar a, .st2-seg button, .st2-tax-year, .stmt-chip,
.money-cell, .change-unit {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
}
input, textarea, select, [contenteditable="true"], .sb-selectable, .sb-selectable * {
    -webkit-user-select: text;
    user-select: text;
}

/* ---- Page transitions ----
   Cross-fade + subtle rise between pjax swaps so navigation reads like a native push
   rather than a web reload. Header and footer are identical across tabs, so animating
   the root view only visibly affects the content region. Driven by app-shell.js via
   document.startViewTransition(); the fallback class covers WebKit without the API. */
@media (prefers-reduced-motion: no-preference) {
    ::view-transition-old(root) { animation: sb-vt-out .16s var(--sb-ease) both; }
    ::view-transition-new(root) { animation: sb-vt-in  .24s var(--sb-ease) both; }
}
@keyframes sb-vt-out { to { opacity: 0; } }
@keyframes sb-vt-in  { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* Fallback (no View Transitions API): fade the incoming content only. Opacity, not
   transform — a transform on .page-content would re-anchor its position:fixed
   children (FABs, sticky headers). */
.sb-page-enter        { opacity: 0; }
.sb-page-enter-active { opacity: 1; transition: opacity .22s var(--sb-ease); }
@media (prefers-reduced-motion: reduce) {
    .sb-page-enter, .sb-page-enter-active { opacity: 1; transition: none; }
}

/* ---- Pull-to-refresh ----
   Custom PTR (WKWebView disables the native gesture). A pill fades/drops in below the
   header as the user drags a scrolled-to-top list down; releasing past the threshold
   spins it and runs the page's refresh. Driven by scripts/pull-to-refresh.js. */
.sb-ptr {
    position: fixed; left: 0; right: 0;
    top: calc(58px + var(--sb-safe-top, 0px));
    display: flex; align-items: center; justify-content: center;
    pointer-events: none; z-index: 45; opacity: 0;
    transform: translateY(-6px);
    transition: opacity .15s var(--sb-ease), transform .15s var(--sb-ease);
}
.sb-ptr .sb-ptr-dot {
    width: 34px; height: 34px; border-radius: 50%;
    background: #fff; box-shadow: 0 2px 10px rgba(16,32,64,.18);
    display: flex; align-items: center; justify-content: center;
    color: var(--sb-brand-mid, #33779B);
}
.sb-ptr i { font-size: 15px; transition: transform .15s var(--sb-ease); }
.sb-ptr.is-ready i { transform: rotate(180deg); }
.sb-ptr.is-refreshing { opacity: 1 !important; transform: translateY(0) !important; }
.sb-ptr.is-refreshing i { transform: none; animation: sb-ptr-spin .8s linear infinite; }
@keyframes sb-ptr-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .sb-ptr.is-refreshing i { animation: none; } }
