/* ==========================================================================
   app-mobile.css — Stream AI native-app design layer (v1)
   ==========================================================================

   WHAT THIS IS
   The Capacitor app ships the exact same SPA shell as the website
   (templates/index.html → mobile/scripts/build-www.mjs → mobile/www). This
   sheet is the ONLY thing that makes the app look like an app: a bottom tab
   bar, a slim header, card surfaces, phone-sized type and touch targets.

   THE ONE RULE — every selector in this file is scoped under `body.stream-app`.
   That class is added by static/js/capacitor-shim.js (and, earlier in boot, by
   static/js/app-shell.js) ONLY when window.STREAM_IS_APP === true, which only
   www/env.js sets. A browser hitting streamaiworkout.com never gets the class,
   so this file is inert there even though it is served to it. If you add a
   rule here without `body.stream-app` in the selector you have changed the
   website — tests/test_spa_shell.py fails the build for exactly that.

   ACCENT — never hardcode a brand hue. The user picks the accent in
   Settings → Appearance; the whole palette derives from --accent-h/-s/-l in
   styles.css. Use var(--primary-color) / var(--accent-color) /
   hsla(var(--accent-hsl), a) so a repaint follows the user's choice.
   Neutral greys (surfaces, borders, muted ink) are hue-free on purpose.

   DOM — this sheet never assumes an element it did not create, except the
   stable IDs listed in index.html. The chrome elements (#app-header,
   #app-tabbar, #app-more-sheet, #app-sheet-scrim) are built by app-shell.js.
   ========================================================================== */


/* --------------------------------------------------------------------------
   1. Tokens + page frame
   -------------------------------------------------------------------------- */
body.stream-app {
    /* geometry */
    --app-gutter: 16px;
    --app-radius: 18px;
    --app-radius-sm: 12px;
    --app-tabbar-h: 62px;
    --app-header-h: 52px;
    --app-safe-t: env(safe-area-inset-top, 0px);
    --app-safe-b: env(safe-area-inset-bottom, 0px);
    --app-tabbar-space: calc(var(--app-tabbar-h) + var(--app-safe-b));

    /* surfaces — hue-free so the accent stays the only colored thing */
    --app-surface: #14181f;
    --app-surface-2: #1b212b;
    --app-border: rgba(255, 255, 255, 0.07);
    --app-ink-dim: #8a94a4;
    --app-chrome-bg: rgba(9, 12, 19, 0.82);
    --app-chrome-line: rgba(255, 255, 255, 0.06);

    /* room for the fixed tab bar (footer + last card must clear it) */
    padding-bottom: var(--app-tabbar-space);
    -webkit-tap-highlight-color: transparent;
}

/* Light theme keeps the same layout, lighter surfaces. data-theme lives on
   <html>, so this still resolves to "the app body inside a light document". */
[data-theme="light"] body.stream-app {
    --app-surface: #ffffff;
    --app-surface-2: #f3f5f8;
    --app-border: rgba(15, 23, 42, 0.10);
    --app-ink-dim: #5b6577;
    --app-chrome-bg: rgba(255, 255, 255, 0.88);
    --app-chrome-line: rgba(15, 23, 42, 0.08);
}

/* Full-bleed: kill the desktop container max-widths, keep one 16px gutter. */
body.stream-app .container,
body.stream-app .container-fluid,
body.stream-app .container-sm,
body.stream-app .container-md,
body.stream-app .container-lg,
body.stream-app .container-xl {
    max-width: 100%;
    padding-left: var(--app-gutter);
    padding-right: var(--app-gutter);
}

body.stream-app > .container.py-4 {
    padding-top: 12px;
    padding-bottom: 8px;
}

/* styles.css gives every <section> 64px top / 48px bottom padding — a website
   rhythm that reads as a dead band under the header on every single app screen
   (audited 2026-07-28: 64px above "Ready to train?", "Welcome back", etc.). */
body.stream-app .section-content {
    margin-bottom: 0 !important;
    padding-top: 0 !important;
    padding-bottom: 8px !important;
}


/* --------------------------------------------------------------------------
   2. Website chrome that has no place in an app
   -------------------------------------------------------------------------- */
/* The Bootstrap navbar + hamburger drawer are replaced by #app-header and
   #app-tabbar. They stay in the DOM (navigation.js binds them) — CSS-hidden
   only, never removed. */
body.stream-app > nav.navbar,
body.stream-app > #hamburger-menu,
body.stream-app > footer {
    display: none !important;
}

/* Links that would dead-end in the WebView. /dashboard is the only one still
   doing real work here — it sits in the logged-out hero, inside the SPA body,
   and has no in-app destination. The other three are redundant belt-and-braces:
   they only appear inside the navbar / hamburger / footer, all hidden above, AND
   app-shell.js's link interceptor now routes them through the session handoff, so
   they no longer dead-end. Kept anyway so nothing regresses if a copy of one of
   those links ever lands in the SPA body. login/register are handled by the
   shim's nav guard. Do NOT add new entries here for pages the interceptor can
   reach — add them to REMOTE_PAGES / ROUTABLE_EXTRA instead. */
body.stream-app a[href="/dashboard"],
body.stream-app a[href="/chatbot"],
body.stream-app a[href="/about"],
body.stream-app a[href="/how-to-use"] {
    display: none !important;
}


/* --------------------------------------------------------------------------
   3. Slim app header
   -------------------------------------------------------------------------- */
body.stream-app #app-header {
    position: sticky;
    top: 0;
    z-index: 1035;                       /* under Bootstrap modals (1055) */
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: var(--app-header-h);
    padding: 0 var(--app-gutter);
    padding-top: var(--app-safe-t);
    background: var(--app-chrome-bg);
    border-bottom: 1px solid var(--app-chrome-line);
    -webkit-backdrop-filter: saturate(160%) blur(18px);
    backdrop-filter: saturate(160%) blur(18px);
}

/* workout_session.js pins #global-workout-bar at top:0 (z-index 4000) and adds
   body.gwb-active with a 44px top pad. Slide the sticky header under it. */
body.stream-app.gwb-active #app-header { top: 44px; }

body.stream-app #app-header-title {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 1.15rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

body.stream-app #app-header-avatar {
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    border: 1px solid hsla(var(--accent-hsl), 0.55);
    background: hsla(var(--accent-hsl), 0.16);
    color: var(--accent-color);
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
}

body.stream-app #app-header-avatar:active {
    background: hsla(var(--accent-hsl), 0.3);
}


/* --------------------------------------------------------------------------
   4. Bottom tab bar
   -------------------------------------------------------------------------- */
body.stream-app #app-tabbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1040;                       /* over content, under modals */
    display: flex;
    align-items: stretch;
    height: var(--app-tabbar-space);
    padding-bottom: var(--app-safe-b);
    background: var(--app-chrome-bg);
    border-top: 1px solid var(--app-chrome-line);
    -webkit-backdrop-filter: saturate(160%) blur(18px);
    backdrop-filter: saturate(160%) blur(18px);
    transition: transform 0.22s cubic-bezier(0.4, 0, 0.2, 1);
}

body.stream-app .app-tab {
    flex: 1 1 0;
    min-width: 0;
    min-height: 48px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    border: 0;
    background: none;
    padding: 6px 2px;
    color: var(--app-ink-dim);
    font-family: inherit;
    font-size: 11px;
    font-weight: 500;
    line-height: 1;
    letter-spacing: 0.01em;
    transition: color 0.15s ease;
}

body.stream-app .app-tab i {
    font-size: 18px;
    line-height: 1;
}

body.stream-app .app-tab > span {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

body.stream-app .app-tab.is-active {
    color: var(--primary-color);
}

body.stream-app .app-tab:active {
    color: var(--accent-color);
}

/* Camera workouts own the whole screen — app-shell.js flips this class while a
   tracking video is actually playing. */
body.stream-app.app-camera-live {
    padding-bottom: 0;
}

body.stream-app.app-camera-live #app-tabbar {
    transform: translateY(110%);
    pointer-events: none;
}


/* --------------------------------------------------------------------------
   5. "More" bottom sheet
   -------------------------------------------------------------------------- */
body.stream-app #app-sheet-scrim {
    position: fixed;
    inset: 0;
    z-index: 1044;
    background: rgba(0, 0, 0, 0.55);
    opacity: 0;
    transition: opacity 0.2s ease;
}

body.stream-app #app-sheet-scrim.is-open {
    opacity: 1;
}

body.stream-app #app-more-sheet {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1045;
    max-height: 78vh;
    overflow-y: auto;
    padding: 10px var(--app-gutter) calc(16px + var(--app-safe-b));
    background: var(--app-surface);
    border-top: 1px solid var(--app-border);
    border-radius: 22px 22px 0 0;
    box-shadow: 0 -18px 40px rgba(0, 0, 0, 0.5);
    transform: translateY(100%);
    transition: transform 0.26s cubic-bezier(0.32, 0.72, 0, 1);
}

body.stream-app #app-more-sheet.is-open {
    transform: translateY(0);
}

body.stream-app .app-sheet-grabber {
    width: 38px;
    height: 4px;
    margin: 2px auto 12px;
    border-radius: 999px;
    background: var(--app-border);
}

body.stream-app .app-sheet-heading {
    margin: 0 0 8px;
    padding: 0 4px;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--app-ink-dim);
}

body.stream-app .app-sheet-item {
    display: flex;
    align-items: center;
    gap: 14px;
    width: 100%;
    min-height: 52px;
    padding: 0 12px;
    border: 0;
    border-radius: var(--app-radius-sm);
    background: none;
    color: inherit;
    font-family: inherit;
    font-size: 1rem;
    font-weight: 500;
    text-align: left;
    text-decoration: none;
}

body.stream-app .app-sheet-item i {
    flex: 0 0 22px;
    font-size: 1rem;
    text-align: center;
    color: var(--primary-color);
}

body.stream-app .app-sheet-item:active {
    background: var(--app-surface-2);
}

body.stream-app .app-sheet-item.is-active {
    background: hsla(var(--accent-hsl), 0.12);
    color: var(--accent-color);
}

body.stream-app .app-sheet-item.is-danger i,
body.stream-app .app-sheet-item.is-danger {
    color: var(--danger-color);
}

body.stream-app .app-sheet-sep {
    height: 1px;
    margin: 8px 4px;
    background: var(--app-border);
}


/* --------------------------------------------------------------------------
   6. Section shells — strip the desktop "panel in a panel" look
   -------------------------------------------------------------------------- */
body.stream-app .card {
    background: var(--app-surface);
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius);
    box-shadow: none;
}

body.stream-app .card-header {
    background: var(--app-surface-2);
    border-bottom: 1px solid var(--app-border);
    border-radius: var(--app-radius) var(--app-radius) 0 0;
    padding: 14px 16px;
}

body.stream-app .card-body {
    padding: 16px;
}

/* The outermost card of a section is just a wrapper on the phone: no border,
   no background, no blue title bar. Its <h2> duplicates #app-header-title and
   is hidden by app-shell.js when it is the header's only content. */
body.stream-app .section-content > .card {
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
}

body.stream-app .section-content > .card > .card-header {
    background: transparent !important;
    border: 0;
    border-radius: 0;
    padding: 4px 0 12px;
}

/* #app-header-title already names the screen — a second copy inside the card
   header ("Nutrition" twice, "Exercises" over "Exercise Library") is pure
   duplication. Hide the <h2> itself so headers that ALSO carry controls (the
   nutrition tab pills, the library count badge) keep those controls. */
body.stream-app .section-content > .card > .card-header > h2 {
    display: none;
}

/* …and a header left holding only controls shouldn't reserve title space. */
body.stream-app .section-content > .card > .card-header {
    min-height: 0;
}

body.stream-app .section-content > .card > .card-body {
    padding: 0;
}

/* Nested cards keep their surface but breathe less. */
body.stream-app .card .card {
    border-radius: var(--app-radius-sm);
}

body.stream-app .list-group {
    border-radius: var(--app-radius-sm);
}

body.stream-app .list-group-item {
    background: var(--app-surface);
    border-color: var(--app-border);
    padding: 14px 16px;
}

/* Desktop column classes (col-md-*) collapse to full width below md anyway, so
   no stacking rule is needed here — deliberately left to Bootstrap so this
   layer cannot double-space grids that already carry g-* gutters. */


/* --------------------------------------------------------------------------
   7. Home dashboard — de-marketed
   -------------------------------------------------------------------------- */
/* The hero + feature grid sell the product to a logged-out website visitor.
   In the app the user is always signed in, so they are pure noise. */
body.stream-app #home-section .hero-fade-in,
body.stream-app #home-section .hero-fade-in-delayed,
body.stream-app #home-section > .row.mt-5,
body.stream-app #home-section > .row.mt-4,
body.stream-app #home-section [data-auth-state="guest"],
body.stream-app #home-section [data-auth-state="complete"] > p.lead,
body.stream-app #home-section [data-auth-state="incomplete"] > p.lead {
    display: none !important;
}

body.stream-app #home-section > .row.py-5 {
    padding-top: 4px !important;
    padding-bottom: 8px !important;
}

body.stream-app #home-section > .row.py-5 > [class*="col-"] {
    margin-bottom: 0;
    text-align: left !important;
}

/* "Welcome back, <name>" becomes the dashboard greeting. Left-aligned: it sits
   inside the website's .text-center CTA column, which reads as a marketing
   banner rather than a dashboard salutation on a phone. */
body.stream-app #home-section [data-auth-state="complete"] > h2,
body.stream-app #home-section [data-auth-state="incomplete"] > h2 {
    margin-bottom: 14px !important;
    font-size: 1.5rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    text-align: left;
}

body.stream-app #home-section .row.text-center {
    text-align: left !important;
}

/* The CTA row loses the dashboard link (hidden above), so Start Workout goes
   full-width as the screen's one primary action. */
body.stream-app #home-section [data-auth-state] > .d-flex {
    display: block !important;
}

body.stream-app #start-training-btn,
body.stream-app #home-section [data-auth-state="incomplete"] a.btn-lg {
    display: block;
    width: 100%;
    min-height: 54px;
    border: 0;
    border-radius: var(--app-radius-sm);
    /* Same ink-on-accent convention as .btn-primary in styles.css — theme.py
       guarantees the accent is light enough for dark text to stay legible. */
    background: var(--primary-gradient);
    color: #0A0E17;
    font-size: 1.05rem;
    font-weight: 700;
    box-shadow: 0 8px 22px hsla(var(--accent-hsl), 0.28);
}

/* Oversized stat numerals read as "app", not "web page". */
body.stream-app #streak-badge {
    font-size: 0.8rem;
    padding: 7px 12px;
    border-radius: 999px;
}


/* --------------------------------------------------------------------------
   8. Controls — thumb-sized, iOS-zoom-proof
   -------------------------------------------------------------------------- */
body.stream-app .btn:not(.btn-sm):not(.btn-close) {
    min-height: 46px;
    border-radius: var(--app-radius-sm);
    font-weight: 600;
}

body.stream-app .btn-sm {
    min-height: 36px;
    border-radius: 10px;
}

/* 16px is the threshold below which mobile browsers zoom a focused field. */
body.stream-app .form-control:not(.form-control-sm),
body.stream-app .form-select:not(.form-select-sm),
body.stream-app input[type="text"]:not(.form-control-sm),
body.stream-app input[type="number"]:not(.form-control-sm),
body.stream-app input[type="email"]:not(.form-control-sm),
body.stream-app input[type="search"]:not(.form-control-sm),
body.stream-app textarea:not(.form-control-sm) {
    min-height: 46px;
    border-radius: var(--app-radius-sm);
    font-size: 16px;
    background: var(--app-surface-2);
    border-color: var(--app-border);
}

body.stream-app .form-control-sm,
body.stream-app .form-select-sm {
    font-size: 16px;
}

body.stream-app .input-group > .form-control,
body.stream-app .input-group > .form-select {
    border-radius: var(--app-radius-sm) 0 0 var(--app-radius-sm);
}

body.stream-app .input-group > .btn:last-child {
    border-radius: 0 var(--app-radius-sm) var(--app-radius-sm) 0;
}

/* Segmented controls (workout type, weights sub-mode). Full width, but labels
   stay on ONE line — "Train with camera" was breaking into three stacked words
   and leaving the three segments wildly uneven (audited 2026-07-28). If the
   labels genuinely cannot fit, the group scrolls sideways instead of wrapping. */
/* Segmented controls (.btn-group) are styled with the tab rows further down —
   see the TAB ROWS block. They deliberately share one treatment because the
   app uses .nav-pills, .nav-tabs and .btn-group interchangeably as mode
   switchers, and a user should not be able to tell which is which. */

/* ── TAB ROWS ────────────────────────────────────────────────────────────────
   POLICY (set 2026-07-28 after S22 feedback): a tab the user cannot SEE is
   worse than a tab with a smaller label. v1 made every strip a horizontal
   scroller, which hid Nutrition's third tab ("Recipes") off-screen entirely.
   So: 2-4 items become a segmented control that FILLS the width — every tab
   visible, equal widths, real gaps, labels allowed to wrap to two lines.
   Scrolling is reserved for strips of 5+ (see the :has() branch below), and
   those get a fade + peek so the hidden content is at least advertised.

   The same treatment covers .nav-pills, .nav-tabs and .btn-group because all
   three are used as screen-level mode switchers in this app. */
body.stream-app .nav-pills,
body.stream-app .nav-tabs,
body.stream-app .btn-group {
    display: flex;
    flex-wrap: nowrap;
    gap: 7px;                    /* "too close together" was the complaint */
    width: 100%;
    max-width: 100%;
    overflow: visible;
    border-bottom: 0;
}

body.stream-app .nav-pills > .nav-item,
body.stream-app .nav-tabs > .nav-item,
body.stream-app .nav-pills > .nav-link,
body.stream-app .nav-tabs > .nav-link,
body.stream-app .btn-group > .btn {
    flex: 1 1 0;                 /* equal widths, all visible */
    min-width: 0;
}

body.stream-app .nav-pills .nav-link,
body.stream-app .nav-tabs .nav-link,
body.stream-app .btn-group > .btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    width: 100%;
    min-height: 46px;            /* touch target */
    padding: 8px 6px;
    font-size: 0.8rem;
    line-height: 1.15;
    text-align: center;
    white-space: normal;         /* two readable lines beat one hidden tab */
    /* .btn-group joins its children with a -1px margin and square inner
       corners; with a gap between them each has to round itself again. */
    margin: 0 !important;
    border-radius: var(--app-radius-sm) !important;
}

/* The flex `gap` above owns the icon/label spacing — Bootstrap's me-1 would
   double it and steal width from an already tight label. */
body.stream-app .nav-pills .nav-link > i,
body.stream-app .nav-tabs .nav-link > i,
body.stream-app .btn-group > .btn > i {
    margin: 0 !important;
    flex: 0 0 auto;
}

/* A tab strip reads as a segmented control: one recessed track, the selected
   segment filled with the user's accent, the rest clearly still tappable.
   !important is unavoidable here — the site styles the active tab per-strip,
   including an #nutritionTopTabs id rule and .text-white utilities, both of
   which outrank any class-only selector this file could write. */
body.stream-app .nav-pills,
body.stream-app .nav-tabs,
body.stream-app .btn-group {
    gap: 4px;
    padding: 4px;
    background: var(--app-surface-2);
    border-radius: 15px;
}

body.stream-app .card-header-pills {
    margin-left: 0;
    margin-right: 0;
}

body.stream-app .nav-pills .nav-link,
body.stream-app .nav-tabs .nav-link {
    background: transparent !important;
    border: 0 !important;
    color: var(--app-ink-dim) !important;
    font-weight: 600;
}

body.stream-app .nav-pills .nav-link.active,
body.stream-app .nav-tabs .nav-link.active,
body.stream-app .nav-pills .show > .nav-link,
body.stream-app .nav-tabs .nav-item.show .nav-link {
    background: hsla(var(--accent-hsl), 0.20) !important;
    color: var(--accent-color) !important;
}

/* styles.css draws an underline bar under the active tab; with a filled
   segment it just floats loose under the pill. */
body.stream-app .nav-pills .nav-link.active::after,
body.stream-app .nav-tabs .nav-link.active::after {
    display: none;
}

/* A segment must never let a long word bleed into its neighbour. */
body.stream-app .nav-pills .nav-link,
body.stream-app .nav-tabs .nav-link,
body.stream-app .btn-group > .btn {
    overflow: hidden;
}

/* 360px is the tightest common Android width; buy the labels a little room. */
@media (max-width: 380px) {
    body.stream-app .nav-pills .nav-link,
    body.stream-app .nav-tabs .nav-link,
    body.stream-app .btn-group > .btn {
        font-size: 0.75rem;
        padding-left: 4px;
        padding-right: 4px;
        gap: 4px;
    }

    /* At four segments a 360px row gives each label ~69px. The leading icon
       costs ~17px of that, which is what pushed "Restaurants" over its
       neighbour. The word is the information; the icon is decoration — so on
       the narrowest phones the icon goes and the label stays whole. Strips of
       two or three keep their icons, where there is room for both. */
    body.stream-app .nav-pills:has(> :nth-child(4)) .nav-link > i,
    body.stream-app .nav-tabs:has(> :nth-child(4)) .nav-link > i {
        display: none;
    }

    /* Even iconless, "Restaurants" cleared its 4-up segment by only 1px in
       Chrome's Poppins metrics — too little to survive a different device's
       font rounding. One more step down buys ~6px of real slack. 11px is
       still a legible tab label (iOS ships 10pt), and a legible label the
       user can see beats a pretty one they cannot. */
    body.stream-app .nav-pills:has(> :nth-child(4)) .nav-link,
    body.stream-app .nav-tabs:has(> :nth-child(4)) .nav-link {
        font-size: 0.7rem;
        padding-left: 2px;
        padding-right: 2px;
        letter-spacing: -0.01em;
    }

    /* THREE-item rows need the same relief, and .btn-group was left out of the
       block above entirely. The row that exposed it is the weights sub-mode
       toggle: "Train with camera | Plan & log | Powerlifting". "Powerlifting" is
       a single unbreakable twelve-character word, so at 360px it either widened
       its segment past its neighbours or got sliced by the `overflow: hidden`
       further up. `:nth-child(3)` also matches the four-item strips, which is
       intended — they already get this treatment, the declarations are identical,
       and a 3-vs-4 split would just be two rules to keep in sync.
       styles.css carries the same defense unscoped, because the bug was reported
       from the phone BROWSER, which never loads this file. */
    body.stream-app .nav-pills:has(> :nth-child(3)) .nav-link,
    body.stream-app .nav-tabs:has(> :nth-child(3)) .nav-link,
    body.stream-app .btn-group:has(> .btn:nth-child(3)) > .btn {
        font-size: 0.7rem;
        padding-left: 2px;
        padding-right: 2px;
        letter-spacing: -0.01em;
    }

    body.stream-app .nav-pills:has(> :nth-child(3)) .nav-link > i,
    body.stream-app .nav-tabs:has(> :nth-child(3)) .nav-link > i,
    body.stream-app .btn-group:has(> .btn:nth-child(3)) > .btn > i {
        display: none;
    }
}

/* Wrapping defense for every segmented control, at any width. `white-space:
   normal` above lets a label wrap at SPACES; this adds the last-resort break for
   a single word that still cannot fit its segment.
   Deliberately NOT word-break: break-all / overflow-wrap: anywhere — both break
   mid-word eagerly ("Powerlift / ing"), which is the reported symptom, not the
   fix. overflow-wrap: break-word only engages when the word has a line to
   itself and still overflows. */
body.stream-app .nav-pills .nav-link,
body.stream-app .nav-tabs .nav-link,
body.stream-app .btn-group > .btn {
    overflow-wrap: break-word;
    word-break: normal;
}

/* ── 5+ items: scrolling is unavoidable, so ADVERTISE it ────────────────────
   A right-edge mask fades the last item out and the padding leaves it
   deliberately half-cut ("peek") instead of ending on a clean boundary, so the
   row reads as continuing rather than finished. No row in the app hits this
   today (the largest is 4) — it is here so a future 5th tab degrades well. */
body.stream-app .nav-pills:has(> :nth-child(5)),
body.stream-app .nav-tabs:has(> :nth-child(5)),
body.stream-app .btn-group:has(> .btn:nth-child(6)) {
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-right: 26px;
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 34px), transparent 100%);
    mask-image: linear-gradient(to right, #000 calc(100% - 34px), transparent 100%);
}

body.stream-app .nav-pills:has(> :nth-child(5))::-webkit-scrollbar,
body.stream-app .nav-tabs:has(> :nth-child(5))::-webkit-scrollbar,
body.stream-app .btn-group:has(> .btn:nth-child(6))::-webkit-scrollbar {
    display: none;
}

body.stream-app .nav-pills:has(> :nth-child(5)) > .nav-item,
body.stream-app .nav-tabs:has(> :nth-child(5)) > .nav-item,
body.stream-app .nav-pills:has(> :nth-child(5)) > .nav-link,
body.stream-app .nav-tabs:has(> :nth-child(5)) > .nav-link,
body.stream-app .btn-group:has(> .btn:nth-child(6)) > .btn {
    flex: 0 0 auto;
    min-width: 92px;
}


/* --------------------------------------------------------------------------
   9. Typography — desktop display sizes are absurd on a 390px screen
   -------------------------------------------------------------------------- */
body.stream-app h1, body.stream-app .h1 { font-size: 1.6rem; }
body.stream-app h2, body.stream-app .h2 { font-size: 1.35rem; }
body.stream-app h3, body.stream-app .h3 { font-size: 1.2rem; }
body.stream-app h4, body.stream-app .h4 { font-size: 1.08rem; }
body.stream-app h5, body.stream-app .h5 { font-size: 1rem; }

body.stream-app .display-1,
body.stream-app .display-2,
body.stream-app .display-3,
body.stream-app .display-4,
body.stream-app .display-5,
body.stream-app .display-6 {
    font-size: 1.7rem;
    line-height: 1.2;
}

body.stream-app .lead {
    font-size: 0.98rem;
}


/* --------------------------------------------------------------------------
   10. Tables — scroll, never squish
   -------------------------------------------------------------------------- */
/* app-shell.js wraps any bare .table in <div class="app-scroll-x">. */
body.stream-app .app-scroll-x,
body.stream-app .table-responsive {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

body.stream-app .app-scroll-x > .table {
    margin-bottom: 0;
}

/* nowrap ONLY inside a scroll container — an unwrapped table must still be able
   to reflow rather than push the page sideways. */
body.stream-app .app-scroll-x > .table,
body.stream-app .table-responsive > .table {
    white-space: nowrap;
}

body.stream-app .table {
    font-size: 0.9rem;
}


/* --------------------------------------------------------------------------
   11. Modals as bottom sheets (CSS only — Bootstrap's JS is untouched)
   -------------------------------------------------------------------------- */
/* The dialog is a bottom-anchored flex column. `height:auto` is load-bearing:
   Bootstrap gives .modal-dialog-scrollable a fixed `height:calc(100% - 1rem)`,
   which left a short .modal-content stranded at the TOP of a full-height dialog
   with the page showing through underneath (audited 2026-07-28, #ct-food-modal).
   justify-content:flex-end then keeps a short sheet glued to the bottom. */
body.stream-app .modal-dialog:not(.modal-fullscreen) {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    max-width: 100%;
    margin: 0;
    height: auto;
    min-height: 0;
    max-height: 94%;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
}

body.stream-app .modal-content {
    border: 0;
    border-radius: 22px 22px 0 0;
    background: var(--app-surface);
    max-height: 100%;
    min-height: 0;
}

/* min-height:0 is what actually lets a flex child shrink below its content and
   hand the overflow to its own scrollbar — without it max-height above is inert
   and tall modals push their header off the top of the screen. */
body.stream-app .modal-body {
    overflow-y: auto;
    min-height: 0;
    padding-bottom: 16px;
}

body.stream-app .modal-header,
body.stream-app .modal-footer {
    border-color: var(--app-border);
    padding: 14px 16px;
    flex: 0 0 auto;
}

/* Last row of controls must clear the gesture bar. */
body.stream-app .modal-footer {
    padding-bottom: calc(14px + var(--app-safe-b));
}

/* A modal with no footer gets the safe-area pad on the body instead. */
body.stream-app .modal-content > .modal-body:last-child {
    padding-bottom: calc(16px + var(--app-safe-b));
}

body.stream-app .modal-header .modal-title {
    font-size: 1.05rem;
    font-weight: 600;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

body.stream-app .modal.fade .modal-dialog:not(.modal-fullscreen) {
    transform: translateY(24px);
    transition: transform 0.26s cubic-bezier(0.32, 0.72, 0, 1);
}

body.stream-app .modal.show .modal-dialog:not(.modal-fullscreen) {
    transform: none;
}


/* --------------------------------------------------------------------------
   12. Other fixed furniture must clear the tab bar
   -------------------------------------------------------------------------- */
/* PUMP floats over the content. At its website size (80px) it sat squarely on
   top of the End Exercise button at 360px, so shrink it in the app. */
body.stream-app .pump-container {
    right: var(--app-gutter);
    bottom: calc(var(--app-tabbar-space) + 12px);
}

body.stream-app .pump-avatar {
    width: 52px;
    height: 52px;
}

body.stream-app .pump-bubble {
    max-width: min(74vw, 250px);
    font-size: 0.85rem;
}

body.stream-app #workout-pill {
    bottom: calc(var(--app-tabbar-space) + 12px);
}

/* #builder-rest-bar carries an inline bottom:0 — hence !important. */
body.stream-app #builder-rest-bar {
    bottom: var(--app-tabbar-space) !important;
}

body.stream-app.app-camera-live .pump-container,
body.stream-app.app-camera-live #workout-pill {
    bottom: 16px;
}

body.stream-app.app-camera-live #builder-rest-bar {
    bottom: 0 !important;
}


/* --------------------------------------------------------------------------
   13. Camera surface — edge to edge while tracking
   -------------------------------------------------------------------------- */
body.stream-app #camera-container,
body.stream-app #yoga-camera-feed,
body.stream-app #pl-camera-feed {
    border-radius: var(--app-radius-sm);
    overflow: hidden;
}

body.stream-app.app-camera-live > .container.py-4 {
    padding-bottom: 0;
}

/* In-camera HUD + form-feedback banner.
   ------------------------------------------------------------------------
   The geometry itself lives in styles.css (`--camera-hud-inset` /
   `--camera-hud-height` on #camera-container, read by BOTH the HUD strip and
   the banner offset beneath it), and this sheet is loaded on top of it inside
   the WebView, so the app inherits the whole layout for free. Only two things
   genuinely differ here, and both are app-shell facts rather than styling
   taste — keep them in lockstep with the styles.css block or the app and the
   website drift the way §14 did:

     * the camera card is rounded to --app-radius-sm above, so the inset has to
       clear a bigger corner radius than the website's 8px;
     * the app hides the browser chrome and the workout card fills more of the
       screen, so the same two-line banner is a smaller share of the view and
       can carry the website's full type size at phone widths. */
body.stream-app #camera-container {
    --camera-hud-inset: 10px;
    --camera-hud-height: 1.75rem;
}

@media (max-width: 575.98px) {
    body.stream-app #camera-container {
        --camera-hud-inset: 8px;
        --camera-hud-height: 1.6rem;
    }

    body.stream-app .form-feedback-overlay span {
        font-size: 0.9rem;
    }
}


/* --------------------------------------------------------------------------
   14. Nutrition — the reported Galaxy S22 cutoff
   --------------------------------------------------------------------------
   The dashboard opens with one `.ct-daybar` row holding TWO nowrap groups: the
   date stepper (`.ct-date-nav`) and the capture cluster (`.ct-capture-cluster`
   = Add food / Snap meal / Scan barcode / Scan label / Edit Goals). The capture
   group has no flex-wrap, so at 360px it measured wider than its column and the
   surrounding `.card` (overflow:hidden, for its rounded corners) CLIPPED it —
   "Edit Goals" simply vanished past the right edge. That is the bug Seth hit.

   Fix: below 768px each group takes the full width on its own line and the five
   capture buttons stack one per row. This is the SAME shape styles.css applies
   to the phone browser (`@media (max-width: 767.98px)`) — keep the two in
   lockstep so the app and the website never diverge. The breakpoint matters
   here too: a tablet-sized WebView has room for the horizontal row.
   -------------------------------------------------------------------------- */
@media (max-width: 767.98px) {

    body.stream-app .ct-daybar > .ct-date-nav,
    body.stream-app .ct-daybar > .ct-capture-cluster {
        width: 100%;
        min-width: 0;
    }

    /* One button per row. .d-flex is `display:flex !important` so the direction
       flips without needing !important; the gap and align-items utilities on the
       element (.gap-1, .align-items-center) are !important and do need beating. */
    body.stream-app .ct-capture-cluster {
        flex-direction: column;
        align-items: stretch !important;
        flex-wrap: nowrap;
        gap: 0.5rem !important;
    }

    body.stream-app .ct-capture-cluster > .btn {
        width: 100%;
        min-width: 0;
        margin: 0;
    }

    /* Date stepper: let the date field take the slack instead of the chevrons. */
    body.stream-app .ct-date-nav {
        flex-wrap: wrap;
        gap: 0.5rem !important;
    }

    body.stream-app .ct-date-nav #ct-date-input {
        flex: 1 1 auto;
        width: auto !important;
        min-width: 0;
    }

    body.stream-app .ct-date-nav > .btn {
        flex: 0 0 auto;
    }
}

/* The calories-remaining figure is the screen's hero stat — the global
   .display-* clamp made it the same size as a heading. */
body.stream-app #ct-remaining {
    font-size: 2.6rem;
    line-height: 1.05;
    letter-spacing: -0.03em;
}


/* --------------------------------------------------------------------------
   15. Long-value controls
   --------------------------------------------------------------------------
   Native <select> clips its own text with no ellipsis, so long option labels
   ("USB2.0 HD UVC WebCam (322e:202c)", "Other / prefer not to say") ended
   mid-word against the control's edge. Ellipsis reads as deliberate; the tight
   right pad keeps the caret clear of the text.
   -------------------------------------------------------------------------- */
body.stream-app select.form-select,
body.stream-app select.form-control {
    text-overflow: ellipsis;
    padding-right: 2rem;
}

/* Long single-line values elsewhere (badges, chips, table-ish rows) should
   truncate rather than force their container wider than the screen. */
body.stream-app .text-truncate {
    min-width: 0;
}

/* Flex children default to min-width:auto, which is what lets a long word push
   a row past the viewport. Opt every flex/grid row in this app out of that. */
body.stream-app .d-flex > *,
body.stream-app .row > [class*="col"] {
    min-width: 0;
}


/* --------------------------------------------------------------------------
   16. Header rows + small controls
   -------------------------------------------------------------------------- */
/* A card header that pairs a title with a control cluster (e.g. the nutrition
   chart's 7d/30d/90d group) is a nowrap flex row on the desktop site, so the
   cluster ran off the right edge at every phone width. Let those rows wrap. */
body.stream-app .card-header.d-flex,
body.stream-app .modal-header.d-flex {
    flex-wrap: wrap;
    row-gap: 8px;
}

/* Bootstrap's close button is a 32px hit area — under the 44px guideline and
   genuinely fiddly at the top corner of a bottom sheet. */
body.stream-app .btn-close {
    width: 44px;
    height: 44px;
    min-width: 44px;
    background-size: 14px;
    margin: -6px -6px -6px auto;
    opacity: 0.85;
}

/* Toggle switches: the native 1rem square is a poor thumb target. */
body.stream-app .form-check-input {
    width: 1.25rem;
    height: 1.25rem;
    margin-top: 0.1rem;
}

body.stream-app .form-switch .form-check-input {
    width: 2.6rem;
    height: 1.4rem;
}

body.stream-app .form-switch {
    padding-left: 3.2rem;
    min-height: 1.6rem;
}

body.stream-app .form-check-label {
    line-height: 1.4;
}


/* --------------------------------------------------------------------------
   17. Standalone server pages inside the app
   --------------------------------------------------------------------------
   /chatbot, /profile and /about are Flask pages on templates/base.html that the
   app opens through capacitor.config.ts's server.allowNavigation. They are NOT
   the SPA shell: no .section-content, no window.showSection. app-shell.js sets
   body.stream-app-standalone on them and routes its tab bar back into the
   bundle. Everything above applies to them unchanged (base.html carries the same
   navbar/hamburger/footer/container structure as index.html); these rules only
   cover what is specific to a pushed page.
   -------------------------------------------------------------------------- */
/* base.html wraps its content in .container, but pages own their own top-level
   spacing — give the pushed page the same 12px rhythm the SPA container has. */
body.stream-app-standalone > .container,
body.stream-app-standalone > main,
body.stream-app-standalone > .container-fluid {
    padding-top: 12px;
    padding-bottom: 8px;
}

/* Back chevron in the pushed-page header. */
body.stream-app #app-header-back {
    flex: 0 0 auto;
    width: 38px;
    height: 38px;
    margin-left: -8px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 50%;
    background: none;
    color: var(--text-primary);
    font-size: 1.05rem;
    padding: 0;
}

body.stream-app #app-header-back:active {
    background: var(--app-surface-2);
}

/* base.html's Tailwind build ships its own utility surfaces on some pages; keep
   any stray white/near-white panel from punching a hole in the dark chrome. */
body.stream-app-standalone .bg-white,
body.stream-app-standalone .bg-light {
    background: var(--app-surface) !important;
    color: var(--text-primary);
}

/* The PUMP chat page embeds a Spline scene in an iframe (left as-is by
   convention — we only make sure the new chrome does not crop it). */
body.stream-app-standalone iframe {
    max-width: 100%;
    border: 0;
}

/* profile.html runs its own Chart.js canvas; canvases do not shrink on their
   own and would otherwise force the page sideways at 360px. */
body.stream-app-standalone canvas {
    max-width: 100%;
    height: auto;
}


/* --------------------------------------------------------------------------
   18. Motion preferences
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    body.stream-app #app-tabbar,
    body.stream-app #app-more-sheet,
    body.stream-app #app-sheet-scrim,
    body.stream-app .app-tab,
    body.stream-app .modal.fade .modal-dialog:not(.modal-fullscreen) {
        transition: none !important;
    }
}

/* Very small phones (<=360px): the 5 tab labels need one more notch of room. */
@media (max-width: 360px) {
    body.stream-app .app-tab {
        font-size: 10px;
    }
    body.stream-app .app-tab i {
        font-size: 17px;
    }
}

/* gwb-active drops to a 40px bar under 480px — keep the header glued to it. */
@media (max-width: 480px) {
    body.stream-app.gwb-active #app-header {
        top: 40px;
    }
}
