/* ── Champion Color System ───────────────────────── */
:root {
    --gold:         #d4a017;
    --gold-dim:     rgba(212, 160, 23, 0.18);
    --gold-border:  rgba(212, 160, 23, 0.35);
    --green:        #4caf50;
    --green-dim:    rgba(76, 175, 80, 0.15);
    --green-border: rgba(76, 175, 80, 0.35);
    --red:          #e53935;
    --red-dim:      rgba(229, 57, 53, 0.15);
    --red-border:   rgba(229, 57, 53, 0.35);

    --bg:           #1a1200;
    --bg-card:      #261c00;
    --bg-input:     #312300;
    --bg-hover:     rgba(255, 255, 255, 0.05);

    --text:         #ffffff;
    --text-muted:   #aaa;
    --text-hint:    #666;

    --border:       rgba(255, 255, 255, 0.1);
    --border-hover: rgba(255, 255, 255, 0.2);

    --radius-sm:    8px;
    --radius-md:    10px;
    --radius-lg:    14px;

    /* Mobile bottom navigation (Sprint 9 Task 2) -- one source of truth
       for both the nav's own height and the page-content compensation
       that keeps it from covering the last bit of content. */
    --mobile-bottom-nav-height:    64px;
    --mobile-bottom-nav-safe-area: env(safe-area-inset-bottom, 0px);
}


/* ── Light theme overrides ───────────────────────── */
[data-theme="light"] {
    --bg:           #f5f5f0;
    --bg-card:      #ffffff;
    --bg-input:     #ebebe4;
    --bg-hover:     rgba(0, 0, 0, 0.04);

    --text:         #1a1200;
    --text-muted:   #555;
    --text-hint:    #888;

    --border:       rgba(0, 0, 0, 0.1);
    --border-hover: rgba(0, 0, 0, 0.2);
}



/* ── Theme toggle button (now a row inside the profile menu, see
   .nav-profile-menu below) ──────────────────────── */
.nav-theme-btn {
    width: 100%;
    border: none;
    background: none;
    color: var(--text-muted);
    font-size: 14px;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 18px;
    transition: background 0.15s, color 0.15s;
    font-family: 'Inter', sans-serif;
    text-align: left;
}

.nav-theme-btn:hover {
    background: var(--bg-hover);
    color: var(--text);
}

.nav-theme-label {
    font-size: 14px;
}

/* show/hide sun and moon based on current theme */
[data-theme="dark"]  .theme-icon-light { display: none; }
[data-theme="dark"]  .theme-icon-dark  { display: block; }
[data-theme="light"] .theme-icon-dark  { display: none; }
[data-theme="light"] .theme-icon-light { display: block; }

/* ── Screen-reader-only text (visually hidden, still announced) ───── */
.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;
}

/* In style.css — gold logo in nav */
nav ul li:first-child a {
    padding: 4px 8px;
    display: flex;
    align-items: center;
}

.nav-logo {
    color: var(--gold);
    transition: color 0.2s;
    display: block;
}

.nav-logo-link:hover .nav-logo {
    color: var(--text);
}


/* ── Auth guard popup ────────────────────────────── */
.ag-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    z-index: 500;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s;
}

.ag-overlay.open {
    opacity: 1;
    pointer-events: auto;
}

.ag-card {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: 36px 32px;
    max-width: 400px;
    width: 100%;
    text-align: center;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
}

.ag-icon {
    font-size: 40px;
    margin-bottom: 16px;
}

.ag-title {
    font-family: 'Space Grotesk', sans-serif;
    font-size: 22px;
    font-weight: 700;
    color: var(--text);
    margin-bottom: 10px;
}

.ag-sub {
    font-size: 14px;
    color: var(--text-muted);
    line-height: 1.6;
    margin-bottom: 24px;
}

.ag-btns {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.ag-btn-primary {
    background: var(--red);
    color: #fff;
    border: none;
    border-radius: var(--radius-md);
    padding: 12px 0;
    font-size: 14px;
    font-weight: 600;
    text-decoration: none;
    display: block;
    transition: background 0.2s;
    font-family: 'Inter', sans-serif;
}

.ag-btn-primary:hover { background: #c62828; }

.ag-btn-secondary {
    background: none;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: 12px 0;
    font-size: 14px;
    color: var(--text-muted);
    cursor: pointer;
    font-family: 'Inter', sans-serif;
    transition: all 0.2s;
}

.ag-btn-secondary:hover {
    border-color: var(--text-muted);
    color: var(--text);
}



/* ── Base ────────────────────────────────────────── */
* {
    box-sizing: border-box;
}

body {
    margin: 0;
    font-family: "Inter", sans-serif;
    color: var(--text);
    background: var(--bg);
}

h1, h2, h3 {
    letter-spacing: -0.5px;
    font-family: "Space Grotesk", sans-serif;
    color: var(--text);
    margin: 0 0 12px;
}

h1 { font-size: 48px; font-weight: 700; }
h2 { font-size: 32px; font-weight: 600; }
h3 { font-size: 22px; font-weight: 500; }

p {
    line-height: 1.6;
    font-size: 16px;
    color: var(--text-muted);
    margin: 0 0 12px;
}

/* ── Page wrapper ────────────────────────────────── */
.page-content {
    max-width: 1200px;
    margin: 0 auto;
    padding: 40px 32px;
}

/* ── Shared card ─────────────────────────────────── */
.card {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: 16px;
    color: var(--text);
    transition: border-color 0.2s;
}

.card:hover {
    border-color: var(--border-hover);
}

.card-content h2 {
    font-size: 18px;
    margin-bottom: 6px;
    color: var(--text);
}

.card-content p {
    font-size: 14px;
    color: var(--text-muted);
    margin: 0;
}

/* ── Section label ───────────────────────────────── */
.section-label {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--text-hint);
    margin-bottom: 16px;
}

/* ── Shared buttons ──────────────────────────────── */
.btn {
    border: none;
    border-radius: var(--radius-sm);
    padding: 10px 22px;
    font-size: 14px;
    font-weight: 600;
    font-family: 'Inter', sans-serif;
    cursor: pointer;
    transition: all 0.2s;
}

.btn-primary {
    background: var(--red);
    color: #fff;
}

.btn-primary:hover { background: #c62828; transform: scale(1.02); }
.btn-primary:active { transform: scale(0.97); }

.btn-secondary {
    background: var(--bg-input);
    color: var(--text-muted);
    border: 1px solid var(--border);
}

.btn-secondary:hover {
    background: var(--bg-hover);
    color: var(--text);
}

.btn-gold {
    background: var(--gold);
    color: #1a1200;
}

.btn-gold:hover { background: #b8860b; transform: scale(1.02); }

.btn-green {
    background: var(--green);
    color: #fff;
}

.btn-green:hover { background: #388e3c; transform: scale(1.02); }

/* ── Shared input / select ───────────────────────── */
input, select, textarea {
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 8px 12px;
    font-size: 14px;
    color: var(--text);
    font-family: 'Inter', sans-serif;
    outline: none;
    transition: border-color 0.2s;
}

input:focus, select:focus, textarea:focus {
    border-color: var(--gold);
}

select option {
    background: var(--bg-card);
}

/* iOS Safari auto-zooms the page when a focused input/select/textarea
   renders below 16px. This override only raises font-size on mobile
   widths (incl. large-iPhone landscape) so desktop styling is untouched
   and no viewport zoom-locking meta is needed. See Task 9B.1. */
@media (max-width: 1024px) {
    input, select, textarea {
        font-size: 16px;
    }
}

/* ── Shared pill / badge ─────────────────────────── */
.pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 12px;
    border-radius: 20px;
    font-size: 12px;
    font-weight: 500;
}

.pill-gold   { background: var(--gold-dim);  color: var(--gold);  border: 1px solid var(--gold-border);  }
.pill-green  { background: var(--green-dim); color: var(--green); border: 1px solid var(--green-border); }
.pill-red    { background: var(--red-dim);   color: var(--red);   border: 1px solid var(--red-border);   }
.pill-muted  { background: var(--bg-hover);  color: var(--text-muted); border: 1px solid var(--border); }

/* ── Progress bar ────────────────────────────────── */
.bar-bg {
    height: 6px;
    background: rgba(255, 255, 255, 0.08);
    border-radius: 3px;
    overflow: hidden;
}

.bar-fill {
    height: 100%;
    border-radius: 3px;
    background: var(--green);
    transition: width 0.5s ease;
}

.bar-fill-gold { background: var(--gold); }
.bar-fill-red  { background: var(--red);  }

/* ── Toggle switch ───────────────────────────────── */
.toggle {
    width: 38px;
    height: 22px;
    border-radius: 11px;
    border: none;
    background: rgba(255, 255, 255, 0.15);
    cursor: pointer;
    position: relative;
    transition: background 0.2s;
    flex-shrink: 0;
}

.toggle.on { background: var(--green); }

.toggle::after {
    content: '';
    position: absolute;
    width: 16px;
    height: 16px;
    background: #fff;
    border-radius: 50%;
    top: 3px;
    left: 3px;
    transition: left 0.2s;
}

.toggle.on::after { left: 19px; }

/* ── Animations ──────────────────────────────────── */
@keyframes pulse {
    0%   { box-shadow: 0 0 0 0 rgba(229, 57, 53, 0.4); }
    70%  { box-shadow: 0 0 0 12px rgba(229, 57, 53, 0); }
    100% { box-shadow: 0 0 0 0 rgba(229, 57, 53, 0); }
}

@keyframes micPulse {
    0%   { box-shadow: 0 0 0 0 rgba(229, 57, 53, 0.5); }
    70%  { box-shadow: 0 0 0 16px rgba(229, 57, 53, 0); }
    100% { box-shadow: 0 0 0 0 rgba(229, 57, 53, 0); }
}

@keyframes livePulse {
    0%, 100% { opacity: 1; }
    50%       { opacity: 0.3; }
}

/* ── Nav ─────────────────────────────────────────── */
nav ul {
    display: flex;
    align-items: center;
    list-style: none;
    margin: 0;
    padding: 0 24px;
    height: 60px;
    background: var(--bg-card);
    border-bottom: 1px solid var(--border);
    gap: 4px;
}

nav ul li a {
    color: var(--text-muted);
    text-decoration: none;
    font-size: 14px;
    padding: 6px 12px;
    border-radius: var(--radius-sm);
    transition: color 0.15s, background 0.15s;
    display: flex;
    align-items: center;
}

nav ul li a:hover {
    color: var(--text);
    background: var(--bg-hover);
}

nav ul li:first-child a { padding: 4px 8px; }

nav ul li:first-child img {
    width: 32px;
    height: 32px;
    object-fit: contain;
}

.nav-active {
    color: var(--gold) !important;
    font-weight: 600;
}

/* ── Profile/avatar menu (replaces the old standalone Profile link and
   the old separate theme-toggle button — everything account- and
   preference-related lives behind this one trigger) ─────────────── */
.nav-profile {
    position: relative;
    list-style: none;
    margin-left: auto;
}

.nav-profile-btn {
    width: 44px;
    height: 44px;
    min-width: 44px;
    min-height: 44px;
    border: none;
    background: none;
    padding: 0;
    cursor: pointer;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 0;
}

.nav-profile-btn:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
}

.nav-auth-icon {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    object-fit: cover;
    transition: opacity 0.2s;
}

.nav-profile-btn:hover .nav-auth-icon { opacity: 0.8; }

.nav-profile-menu {
    display: none;
    position: absolute;
    top: calc(100% + 10px);
    right: 0;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    min-width: 170px;
    overflow: hidden;
    z-index: 1100;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

.nav-profile-menu.open { display: block; }

.nav-profile-menu a {
    display: block;
    padding: 12px 18px;
    font-size: 14px;
    color: var(--text-muted);
    text-decoration: none;
    transition: background 0.15s, color 0.15s;
}

.nav-profile-menu a:hover {
    background: var(--bg-hover);
    color: var(--text);
}

.nav-auth-user {
    padding: 12px 18px 6px;
    font-size: 12px;
    color: var(--text-hint);
    border-bottom: 1px solid var(--border);
    margin-bottom: 4px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ── Mobile bottom navigation (Sprint 9 Task 2) ──────────────────────
   Hidden by default; shown only inside the 900px mobile breakpoint
   below. z-index 50 sits above ordinary page content (auto/0) but
   below every existing overlay -- auth-guard popup (500), the Workout
   Hub / Progress drawers (100-300ish) -- so an open drawer or the
   auth-guard popup still correctly covers it. */
.mobile-bottom-nav {
    display: none;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 50;
    background: var(--bg-card);
    border-top: 1px solid var(--border);
    padding-bottom: var(--mobile-bottom-nav-safe-area);
}

.mobile-bottom-nav__item {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    height: var(--mobile-bottom-nav-height);
    min-height: 44px;
    border: none;
    background: none;
    color: var(--text-muted);
    text-decoration: none;
    font-family: 'Inter', sans-serif;
    cursor: pointer;
    padding: 6px 4px;
    transition: color 0.15s;
}

.mobile-bottom-nav__item i {
    font-size: 20px;
    line-height: 1;
}

.mobile-bottom-nav__item span {
    font-size: 11px;
    line-height: 1.1;
    white-space: nowrap;
}

.mobile-bottom-nav__item:hover {
    color: var(--text);
}

/* Active state: color change + a small top accent bar, so it doesn't
   rely on color alone (matters for colorblind users and in bright
   sunlight where subtle color shifts are hard to see). */
.mobile-bottom-nav__item.nav-active {
    color: var(--gold);
    font-weight: 600;
    box-shadow: inset 0 2px 0 var(--gold);
}

/* ── Auth-state visibility utility (Sprint 9 Task 3) ─────────────────
   Used only by the mobile More panel's [data-auth-when] blocks (see
   checkAuthState() in js/nav.js). The desktop profile menu keeps its
   original id-based/inline-style toggling from Task 1, untouched. */
.nav-auth-hidden {
    display: none !important;
}

/* ── Mobile More menu (Sprint 9 Task 3) ──────────────────────────────
   Bottom sheet opened by #mobileMoreBtn. z-index 400/401 sits above
   the bottom nav (50) and the Workout Hub / Progress drawers (100-301),
   but below the auth-guard popup (500) -- see the investigation note
   in the Task 3 report for the full ordering rationale. */
.mobile-more-dim {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
    z-index: 400;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s;
}

.mobile-more-dim.open {
    opacity: 1;
    pointer-events: auto;
}

.mobile-more-panel {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 401;
    background: var(--bg-card);
    border-top: 1px solid var(--border);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    max-height: 80vh;
    overflow-y: auto;
    transform: translateY(100%);
    transition: transform 0.2s ease-out;
    padding-bottom: calc(var(--mobile-bottom-nav-safe-area) + 12px);
}

.mobile-more-panel.open {
    transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
    .mobile-more-dim,
    .mobile-more-panel {
        transition: none;
    }
}

.mobile-more-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 12px 12px 20px;
    border-bottom: 1px solid var(--border);
}

.mobile-more-header h2 {
    font-size: 16px;
    margin: 0;
    color: var(--text);
}

.mobile-more-close {
    width: 44px;
    height: 44px;
    border: none;
    background: none;
    color: var(--text-muted);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    font-size: 18px;
}

.mobile-more-close:hover { color: var(--text); }

.mobile-more-body {
    padding: 8px 0;
}

.mobile-more-user-block {
    padding-top: 4px;
}

.mobile-more-item {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    min-height: 44px;
    border: none;
    background: none;
    color: var(--text-muted);
    text-decoration: none;
    font-size: 14px;
    font-family: 'Inter', sans-serif;
    padding: 12px 20px;
    cursor: pointer;
    text-align: left;
}

.mobile-more-item i {
    font-size: 18px;
    width: 20px;
    text-align: center;
    flex-shrink: 0;
}

.mobile-more-item:hover {
    background: var(--bg-hover);
    color: var(--text);
}

/* Bottom sheet is mobile-only -- guard against a stray open state
   surviving a viewport resize past the breakpoint. */
@media (min-width: 901px) {
    .mobile-more-dim,
    .mobile-more-panel {
        display: none !important;
    }
}

/* ── Body scroll lock while the mobile More sheet is open ───────────── */
body.gf-scroll-locked {
    overflow: hidden;
}

/* ── Footer ──────────────────────────────────────── */
footer {
    background: var(--bg-card);
    color: var(--text-muted);
    padding: 20px 32px;
    text-align: center;
    border-top: 1px solid var(--border);
    font-size: 14px;
}

footer a {
    color: var(--gold);
    text-decoration: none;
}

footer a:hover { text-decoration: underline; }

/* ── Responsive ──────────────────────────────────── */
@media (max-width: 900px) {
    h1 { font-size: 32px; }
    h2 { font-size: 24px; }
    .page-content { padding: 24px 20px; }

    /* Hide the entire desktop nav row except the logo (first child).
       Sprint 9 Task 2 kept the profile/avatar trigger (.nav-profile)
       visible here; Task 3 moves Profile/Theme/Sign out into the
       mobile More panel, so the avatar trigger is now redundant and
       hidden too -- only the brand stays in the compact top bar. */
    nav > ul > li:not(:first-child) {
        display: none;
    }

    .mobile-bottom-nav {
        display: flex;
    }

    /* Reserve room at the bottom of every page so the fixed bottom nav
       never covers the last bit of content. Skipped on auth.html, which
       never shows the bottom nav (see .gf-no-mobile-nav in js/nav.js). */
    body:not(.gf-no-mobile-nav) {
        padding-bottom: calc(var(--mobile-bottom-nav-height) + var(--mobile-bottom-nav-safe-area));
    }
}

/* ── Shared page-level loading/error states (Sprint 3) ──────────────
   Used by assessment.html and profile.html while their Supabase data
   fetch is in flight, and if it fails. Deliberately minimal — no fake
   data is ever shown on failure (CLAUDE.md product rules). */
.page-loading, .page-error {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    padding: 60px 20px;
    color: var(--text-muted);
    font-size: 0.9rem;
    text-align: center;
}

.page-loading-spinner {
    width: 28px;
    height: 28px;
    border: 3px solid var(--border);
    border-top-color: var(--gold);
    border-radius: 50%;
    animation: page-spin 0.8s linear infinite;
}

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

.page-error-msg { color: var(--red); }

.page-error-retry {
    background: var(--gold);
    color: #1a1200;
    border: none;
    border-radius: var(--radius-sm);
    padding: 8px 18px;
    font-weight: 600;
    font-size: 0.85rem;
    cursor: pointer;
}

.page-error-retry:hover { filter: brightness(1.1); }

/* ── Shared branded loading system (Sprint 9 §2) ─────────────────────
   One shared set of primitives backing every loading state in the app:
   Draw-on (cold start / auth restore), Breathe (in-card/section wait),
   Form & fade (branded entrance + assessment-completion celebration),
   a small button spinner, and skeleton blocks. All driven by the GetFit
   monogram path already used in nav.html's logo -- reused here as a
   plain <svg> markup string (js/nav.js's GF_MARK_SVG) rather than
   duplicated inline in every page. See js/nav.js for the JS side
   (setButtonBusy/restoreButton, gfShowPageLoading/gfHidePageLoading,
   beginAppEntranceIfNeeded/completeAppEntrance, showBreatheLoader/
   hideBreatheLoader). Timings/behavior: see CLAUDE.md Sprint 9 §2. */

@keyframes gf-draw   { 0% { stroke-dashoffset: 1; } 42% { stroke-dashoffset: 0; } 70% { stroke-dashoffset: 0; } 100% { stroke-dashoffset: -1; } }
@keyframes gf-pulse  { 0%, 100% { transform: scale(1); opacity: .82; } 50% { transform: scale(1.13); opacity: 1; } }
@keyframes gf-spin   { to { transform: rotate(360deg); } }
@keyframes gf-shimmer{ 0%, 100% { opacity: .32; } 50% { opacity: .8; } }
@keyframes gf-c-draw { 0% { stroke-dashoffset: 1; } 39% { stroke-dashoffset: 0; } 100% { stroke-dashoffset: 0; } }
@keyframes gf-bar-draw{ 0%, 39% { stroke-dashoffset: 1; } 61% { stroke-dashoffset: 0; } 100% { stroke-dashoffset: 0; } }

/* Draw-on: cold app start / auth-session-restore mark, drawn via
   pathLength="1" + stroke-dasharray:1 (resolution-independent, no
   hardcoded path-length math). */
.gf-loader--draw svg {
    stroke: var(--gold);
}
.gf-loader--draw path {
    stroke-dasharray: 1;
    animation: gf-draw 2.7s ease-in-out infinite;
}

/* Breathe: standard in-card/section loader. */
.gf-loader--breathe svg {
    stroke: var(--gold);
    transform-box: fill-box;
    transform-origin: center;
    animation: gf-pulse 1.7s ease-in-out infinite;
}

/* Form & fade: circle then bar draw in, then HOLD fully drawn (fill-mode:
   forwards) until JS removes the whole overlay. The mark itself no longer
   runs its own fade -- the outer .gf-app-entry container (opacity,
   JS-driven via completeAppEntrance()) is the single source of truth for
   "fade away," since only it is synced to real, variable-duration page-load
   completion. A fixed-duration CSS fade on the mark would finish before or
   after that real completion almost every time, which was the missing-logo
   bug: without fill-mode:forwards the one-shot draw animations reverted to
   their pre-animation stroke-dashoffset:1 (fully undrawn) the moment each
   fixed 2.4s/3.6s duration elapsed, which real page loads routinely
   outlast. Two variants -- "entry" (fast, restrained) and "celebrate"
   (slightly slower, may pair with a success message) -- controlled by a
   modifier class, not a second copy of the keyframes. */
.gf-loader--form-fade svg {
    stroke: var(--gold);
}
.gf-loader--form-fade path.gf-mark-circle {
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
    animation: gf-c-draw 2.4s cubic-bezier(.65,0,.35,1) 1 forwards;
}
.gf-loader--form-fade path.gf-mark-bar {
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
    animation: gf-bar-draw 2.4s cubic-bezier(.65,0,.35,1) 1 forwards;
}
.gf-loader--form-fade.gf-loader--celebrate path.gf-mark-circle,
.gf-loader--form-fade.gf-loader--celebrate path.gf-mark-bar {
    animation-duration: 3.6s;
}

/* Base wrapper every .gf-loader--* variant sits inside of. */
.gf-loader {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.gf-loader svg {
    display: block;
    width: 64px;
    height: 64px;
    fill: none;
    stroke-width: 9;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.gf-loading-message {
    color: var(--text-muted);
    font-size: 0.9rem;
    text-align: center;
}

/* Small shared button spinner -- injected by setButtonBusy() (js/nav.js)
   next to the busy label, never replacing it (buttons keep an accessible
   name while busy). */
.gf-spinner {
    display: inline-block;
    width: 14px;
    height: 14px;
    border: 2px solid rgba(255, 255, 255, 0.35);
    border-top-color: currentColor;
    border-radius: 50%;
    animation: gf-spin 0.8s linear infinite;
    margin-right: 8px;
    vertical-align: -2px;
    flex-shrink: 0;
}

/* Skeleton blocks -- shared variables so every future skeleton usage
   matches without re-declaring background/radius/spacing per page.
   Not currently wired into any real page (none of GetFit's existing
   "Loading…" states use skeleton blocks today) -- primitive only,
   available for a future data-heavy layout. */
.gf-skeleton-line {
    height: 12px;
    border-radius: 6px;
    background: var(--bg-hover);
    animation: gf-shimmer 1.5s ease-in-out infinite;
}
.gf-skeleton {
    display: flex;
    flex-direction: column;
    gap: 9px;
}

/* Breathe in-card overlay -- used over a section (e.g. the Workout Hub
   calendar grid) while it re-fetches, without hiding the rest of the
   already-visible page shell. */
.gf-breathe-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    background: var(--bg-card);
    z-index: 5;
}

/* App entrance overlay (Draw-on -> Form & fade, Sprint 9 §2). Fixed,
   full-viewport, theme-aware background -- shown at most once per
   browser/app session (see beginAppEntranceIfNeeded(), js/nav.js). */
.gf-app-entry {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    background: var(--bg);
    opacity: 1;
    transition: opacity 0.4s ease-out;
}
.gf-app-entry.gf-app-entry-out {
    opacity: 0;
    pointer-events: none;
}

/* ── Shared status-state visual language (Sprint 9 §4) ────────────────
   One small shared family backing every empty/error/warning/offline/
   partial-data notice across the app -- distinct from (and layered on top
   of) the existing full-page .page-loading/.page-error pair and the
   Sprint 9 §2 branded loaders above, which continue to gate WHOLE-PAGE
   visibility unchanged. .gf-state is for what a page/card shows INSIDE its
   already-visible content area: a real empty result, a section-level
   error that must not blank out the rest of a working page, a partial-
   data notice, or an honest offline/queued state. See gfRenderState()
   (js/nav.js) for the shared JS helper that builds this markup -- Task 1
   ships the language itself; wiring it into individual cards is later
   (Sprint 9 §4 Tasks 2-4). */
.gf-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 8px;
    padding: 28px 16px;
    color: var(--text-muted);
}

/* Compact variant for a small inline/card-level notice (e.g. a partial-data
   note or an in-card retryable error) rather than a whole empty section --
   left-aligned, smaller icon, minimal padding. */
.gf-state--compact {
    flex-direction: row;
    align-items: flex-start;
    text-align: left;
    padding: 10px 12px;
    gap: 10px;
}

.gf-state__icon {
    font-size: 28px;
    line-height: 1;
    color: var(--text-hint);
}

.gf-state--compact .gf-state__icon {
    font-size: 18px;
    margin-top: 1px;
}

.gf-state__title {
    font-family: 'Space Grotesk', sans-serif;
    font-size: 1rem;
    font-weight: 600;
    color: var(--text);
    margin: 0;
}

.gf-state__message {
    font-size: 0.85rem;
    color: var(--text-muted);
    line-height: 1.5;
    margin: 0;
    max-width: 360px;
}

.gf-state--compact .gf-state__message {
    max-width: none;
}

.gf-state__actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
    margin-top: 4px;
}

.gf-state--compact .gf-state__actions {
    justify-content: flex-start;
}

.gf-state__action {
    min-height: 44px;
    min-width: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 8px 18px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--bg-input);
    color: var(--text);
    font-family: 'Inter', sans-serif;
    font-size: 0.85rem;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s;
}

.gf-state__action:hover {
    background: var(--bg-hover);
    border-color: var(--border-hover);
}

.gf-state__action:disabled {
    opacity: 0.6;
    cursor: default;
}

/* Variant accents -- icon/title color only; the message paragraph always
   stays --text-muted so longer error/offline sentences stay legible,
   matching the existing .page-error-msg convention (color used sparingly,
   never across a whole paragraph). No dedicated "offline" token exists in
   the theme yet -- warning/offline/partial all reuse --gold, the same
   informational tone .wf-banner-info already uses for the real "saved on
   this device" queued-write message. */
.gf-state--empty .gf-state__icon    { color: var(--text-hint); }
.gf-state--error .gf-state__icon,
.gf-state--error .gf-state__title   { color: var(--red); }
.gf-state--error .gf-state__action  { border-color: var(--red-border); }
.gf-state--warning .gf-state__icon,
.gf-state--warning .gf-state__title { color: var(--gold); }
.gf-state--offline .gf-state__icon,
.gf-state--offline .gf-state__title { color: var(--gold); }
.gf-state--partial .gf-state__icon,
.gf-state--partial .gf-state__title { color: var(--gold); }

@media (max-width: 900px) {
    .gf-state {
        padding: 20px 12px;
    }
    .gf-state--compact {
        flex-direction: column;
        align-items: flex-start;
    }
}

@media (prefers-reduced-motion: reduce) {
    .gf-loader--draw path,
    .gf-loader--breathe svg,
    .gf-loader--form-fade path.gf-mark-circle,
    .gf-loader--form-fade path.gf-mark-bar,
    .gf-spinner,
    .gf-skeleton-line {
        animation: none !important;
    }
    /* Draw-on/Breathe/Form & fade: static mark, fully drawn, no pulse --
       still visible, never invisible, motion is never the only signal
       since .gf-loading-message text stays untouched either way.
       .gf-loader--form-fade svg has no animation of its own even outside
       reduced motion (see above), so no override is needed for the SVG
       itself here -- only the two paths' draw-in animations need disabling. */
    .gf-loader--draw path,
    .gf-loader--form-fade path.gf-mark-circle,
    .gf-loader--form-fade path.gf-mark-bar {
        stroke-dashoffset: 0;
    }
    .gf-app-entry {
        transition: opacity 0.15s ease-out;
    }
}