/* ════════════════════════════════════════════════════════════════════
   DASHBOARD SHELL — the single shared application frame for every
   /dashboard/* route (overview, applications, builder, settings).
   The sidebar + mobile drawer markup is injected once by
   /assets/js/dashboard-shell.js; this file is its only styling source.
   Loaded by every dashboard page so the frame is byte-identical and
   never re-authored per page. Page-specific content styles stay inline
   on each page.
   ════════════════════════════════════════════════════════════════════ */

html, body { overflow-x: clip; }

/* ════════════════════════════════════════════════════════════════════
   PRE-PAINT AUTH GATE
   Hide the protected shell until /assets/js/auth-gate.js confirms a valid
   session, so an unauthenticated visitor (or a bfcache restore after logout)
   never sees a flash of protected content before the redirect. The static
   `bh-auth-pending` class ships in each protected page's <html>, so the hide
   is in effect from the very first paint; the gate removes it on success or
   redirects while still hidden. An opaque cover + spinner give a clean loading
   state instead of a blank screen for the (fast) authed path.
   ════════════════════════════════════════════════════════════════════ */
html.bh-auth-pending body { visibility: hidden; }
html.bh-auth-pending::before {
  content: ""; position: fixed; inset: 0;
  /* Match the real page background (#0a0908) so revealing the shell never
     jumps from a lighter loading shade to the darker page. */
  background: #0a0908; z-index: 2147483646;
}
html.bh-auth-pending::after {
  content: ""; position: fixed; inset: 0; margin: auto;
  width: 34px; height: 34px; border-radius: 50%;
  border: 3px solid rgba(255, 255, 255, .16);
  border-top-color: #e8a85e;
  animation: bhAuthSpin .7s linear infinite;
  z-index: 2147483647;
}
@keyframes bhAuthSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  html.bh-auth-pending::after { animation: none; border-top-color: rgba(255, 255, 255, .16); }
}

/* App grid: persistent 272px sidebar + fluid content */
/* 100dvh = dynamic viewport height (excludes iOS browser chrome like the address bar).
   Falls back to 100vh on older browsers. Without dvh, the app grid overflows the visible
   area on iOS Safari when the address bar is showing, causing layout issues. */
.app { display: grid; grid-template-columns: 272px 1fr; gap: 18px; padding: 18px; min-height: 100vh; min-height: 100dvh; align-items: start; }

/* ── Sidebar ── */
/* height: 100dvh (dynamic viewport height) so the sidebar fills only the truly visible
   area on iOS Safari, not the area behind the browser toolbar.
   will-change:transform and contain:paint were previously here for backdrop-filter
   resampling but on iOS Safari they break position:sticky — the element is composited
   to its own layer, and WebKit's sticky implementation doesn't correctly apply the
   sticky constraint to independently-composited layers on some iOS versions, causing
   the sidebar to scroll instead of staying fixed. Removed; the backdrop-filter
   resampling cost on desktop is acceptable without them. */
.side { position: sticky; top: 18px; display: flex; flex-direction: column; padding: 16px; height: calc(100vh - 36px); height: calc(100dvh - 36px); }

/* Profile card */
.side-profile { padding: 14px 14px 12px; border-radius: var(--r-sm); background: rgba(255,255,255,.03); border: 1px solid var(--glass-border); margin-bottom: 6px; }
.sp-top { display: flex; align-items: center; gap: 11px; margin-bottom: 10px; }
.ava { width: 36px; height: 36px; border-radius: 10px; display: grid; place-items: center; font-weight: 800; color: var(--amber-ink); background: linear-gradient(180deg,var(--amber-bright),var(--amber-deep)); box-shadow: inset 0 1px 0 rgba(255,255,255,.5); flex: none; font-size: .9rem; }
.sp-text { flex: 1; min-width: 0; }
.sp-name { font-weight: 700; font-size: .94rem; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.2; }
.sp-plan-badge { display: inline-flex; align-items: center; margin-top: 5px; padding: 2px 8px; border-radius: 99px; font-family: 'JetBrains Mono',monospace; font-size: .60rem; letter-spacing: .08em; text-transform: uppercase; background: rgba(232,168,94,.10); border: 1px solid rgba(232,168,94,.24); color: var(--amber); white-space: nowrap; line-height: 1.6; }
.sp-email-row { display: flex; align-items: center; gap: 8px; padding: 7px 9px; border-radius: 7px; background: rgba(0,0,0,.18); border: 1px solid rgba(255,255,255,.07); }
.sp-email-icon { flex: none; color: var(--ink-faint); }
.sp-email { font-size: .75rem; color: var(--ink-mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; cursor: default; letter-spacing: .01em; line-height: 1; }
.side-sep { border: none; border-top: 1px solid var(--glass-border); margin: 4px 0; }
.side-nav { display: flex; flex-direction: column; gap: 3px; margin-top: 8px; }
.side-nav a { display: flex; align-items: center; gap: 11px; padding: 11px 13px; border-radius: var(--r-sm); color: var(--ink-soft); text-decoration: none; font-size: .92rem; font-weight: 500; transition: background .2s, color .2s; }
.side-nav a svg { width: 17px; height: 17px; opacity: .8; }
.side-nav a:hover { color: var(--ink); background: rgba(255,255,255,.04); }
.side-nav a.active { color: var(--ink); background: linear-gradient(180deg,rgba(232,168,94,.2),rgba(232,168,94,.06)); border: 1px solid rgba(232,168,94,.28); box-shadow: inset 0 1px 0 rgba(255,255,255,.12); }
.side-nav a.active svg { color: var(--amber); opacity: 1; }
/* Locked (premium) nav item — discoverable upgrade prompt; still navigable */
.side-nav a .nav-lock { margin-left: auto; display: inline-flex; align-items: center; color: var(--amber); opacity: .85; }
.side-nav a .nav-lock svg { width: 13px; height: 13px; opacity: 1; }
.side-nav a.nav-locked .t-en, .side-nav a.nav-locked .t-de { opacity: .82; }
.side-bottom { margin-top: auto; }

/* Credits widget — whole card links to /dashboard/billing */
.plan-widget { display: block; text-decoration: none; color: inherit; cursor: pointer; padding: 12px 14px; border-radius: var(--r-sm); background: rgba(232,168,94,.04); border: 1px solid rgba(232,168,94,.22); transition: background .18s, border-color .18s; }
.plan-widget:hover { background: rgba(232,168,94,.08); border-color: rgba(232,168,94,.45); }
.plan-widget:focus-visible { outline: 2px solid rgba(232,168,94,.7); outline-offset: 2px; }
.plan-widget-label { font-family: 'JetBrains Mono',monospace; font-size: .63rem; letter-spacing: .10em; text-transform: uppercase; color: var(--ink-mute); margin-bottom: 8px; display: block; }
.bar { height: 5px; border-radius: 99px; background: rgba(255,255,255,.08); margin-bottom: 10px; overflow: hidden; }
.bar-fill { display: block; height: 100%; border-radius: 99px; background: linear-gradient(90deg,var(--amber-deep),var(--amber-bright)); transition: width .5s ease; }
.bar-caption { font-size: .76rem; color: var(--ink-mute); margin-bottom: 10px; display: block; }
.pw-upgrade-btn { display: block; text-align: center; font-size: .81rem; font-weight: 500; padding: 8px 12px; border-radius: var(--r-sm); background: rgba(232,168,94,.10); border: 1px solid rgba(232,168,94,.32); color: var(--amber); text-decoration: none; transition: background .18s, border-color .18s; }
.pw-upgrade-btn:hover { background: rgba(232,168,94,.18); border-color: rgba(232,168,94,.52); }
/* Logout — canonical soft-red sign-out language (matches the public .m-menu
   logout: #f0a89f text + red-tint hover), so it reads as a distinct,
   deliberate action instead of disappearing into the muted grays. */
.pw-logout-btn { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; margin-top: 6px; padding: 9px 12px; font-size: .8rem; font-weight: 500; color: #f0a89f; background: none; border: none; cursor: pointer; font-family: inherit; border-radius: var(--r-sm); transition: color .18s, background .18s; touch-action: manipulation; }
.pw-logout-btn svg { width: 15px; height: 15px; flex: none; opacity: .9; }
.pw-logout-btn:hover { color: #f6beb6; background: rgba(232,137,126,.10); }

/* ── Content area ── */
.main { padding-block: 6px; min-width: 0; }
.topbar { display: flex; align-items: center; padding: 4px 6px 0; }
.topbar .crumbs { font-size: .86rem; color: var(--ink-mute); }
.topbar .crumbs a { color: inherit; text-decoration: none; }
.topbar .crumbs a:hover { color: var(--ink-soft); }
.topbar .crumbs b { color: var(--ink-soft); font-weight: 500; }

/* ── Mobile drawer chrome — hidden on desktop, revealed ≤900 ── */
.m-top { display: none; }
.m-burger { display: none; align-items: center; justify-content: center; width: 44px; height: 44px; flex: none; border-radius: var(--r-sm); background: rgba(255,255,255,.05); border: 1px solid var(--glass-border); color: var(--ink); cursor: pointer; padding: 0; }
.m-burger:hover { background: rgba(255,255,255,.09); }
.m-burger svg { width: 20px; height: 20px; }
/* Drawer header (brand · close) + nav section label — exist in the shared
   markup but render only inside the ≤900 drawer. The close button is a
   normal flex child of .drawer-head (NOT absolutely positioned), so it can
   never overlap or truncate the profile card. */
.drawer-head { display: none; }
.side-nav-label { display: none; }
.drawer-close { display: none; width: 40px; height: 40px; flex: none; align-items: center; justify-content: center; border-radius: 999px; background: rgba(255,255,255,.045); border: 1px solid var(--glass-border); color: var(--ink-soft); cursor: pointer; padding: 0; transition: color .2s, border-color .2s, background .2s; }
.drawer-close:hover { color: var(--ink); border-color: var(--glass-border-2); background: rgba(255,255,255,.09); }
.drawer-close svg { width: 17px; height: 17px; }
/* Scrim: strong enough to isolate the drawer from page content (was .6 + 3px
   blur — too weak to read as a modal layer). */
.drawer-overlay { position: fixed; inset: 0; z-index: 55; background: rgba(8,7,6,.72); backdrop-filter: blur(8px) saturate(120%); -webkit-backdrop-filter: blur(8px) saturate(120%); opacity: 0; pointer-events: none; transition: opacity .28s ease; }
body.drawer-open .drawer-overlay { opacity: 1; pointer-events: auto; }

/* Consistent keyboard focus ring across shell controls */
.side-nav a:focus-visible, .pw-upgrade-btn:focus-visible,
.m-burger:focus-visible, .drawer-close:focus-visible {
  outline: 2px solid rgba(232,168,94,.7); outline-offset: 2px;
}
/* Logout gets the matching red ring (same as the public drawer's logout) */
.pw-logout-btn:focus-visible { outline: 2px solid rgba(232,137,126,.6); outline-offset: 2px; }

@media (max-width: 900px) {
  .app { grid-template-columns: 1fr; padding: 14px; }
  .m-top { display: flex; align-items: center; gap: 14px; padding: 10px 14px; margin: 14px 14px 4px; }
  .m-burger { display: inline-flex; }
  .m-top .brand { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .m-top .btn { flex: none; --pad-x: 13px; --pad-y: 9px; font-size: .82rem; }

  /* Sidebar reused as a slide-out drawer — same markup, no duplicate component.
     The drawer OWNS its surface: the desktop .glass card fill (~5-7% white) is
     far too translucent to sit over live page content, and glass.css strips
     backdrop-filter from .glass at ≤768 (iOS GPU budget) — which left the
     drawer an unblurred, near-transparent sheet on phones. Near-opaque
     liquid-glass surface per the canonical public .m-menu (rgba .97 + blur 28).
     Selector is aside.side (0,1,1) so it outranks both .glass rules (0,1,0)
     regardless of file order. */
  aside.side {
    display: flex; position: fixed; top: 0; left: 0; bottom: 0; height: 100%;
    width: 296px; max-width: 88vw; z-index: 60; border-radius: 0 20px 20px 0;
    transform: translateX(-100%); transition: transform .28s cubic-bezier(.4,0,.2,1);
    overflow-y: auto; overscroll-behavior: contain;
    padding: max(14px, env(safe-area-inset-top)) 14px calc(16px + env(safe-area-inset-bottom));
    /* Near-opaque bg — 14px reads identically to the old 28px at a fraction
       of the GPU cost on this full-height, mobile-only drawer (§12 budget). */
    background: rgba(10,9,8,.97);
    backdrop-filter: blur(14px) saturate(150%);
    -webkit-backdrop-filter: blur(14px) saturate(150%);
    border: 0; border-right: 1px solid rgba(255,255,255,.09);
    box-shadow: none;
  }
  /* Suppress the .glass hairline-rim pseudo (the drawer draws its own edge)
     and paint ambient amber light wells instead — same language as .m-menu. */
  aside.side::before { content: none; }
  aside.side::after {
    content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 0;
    border-radius: inherit;
    background:
      radial-gradient(300px 240px at 92% -4%, rgba(232,168,94,.12), transparent 70%),
      radial-gradient(260px 220px at -14% 104%, rgba(232,168,94,.05), transparent 70%);
  }
  body.drawer-open .side { transform: translateX(0); box-shadow: 24px 0 80px rgba(0,0,0,.5), 0 32px 80px rgba(0,0,0,.55); }

  /* Drawer header: brand · close — a real layout row above the profile card */
  .drawer-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-shrink: 0; padding: 2px 2px 13px; margin-bottom: 14px; border-bottom: 1px solid rgba(255,255,255,.07); }
  .drawer-head .brand { font-size: .98rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .drawer-close { display: inline-flex; }

  /* Section rhythm: the mono "Menu" eyebrow (canonical drawer language)
     replaces the bare hairline between profile and nav */
  .side .side-sep { display: none; }
  .side-nav-label { display: flex; align-items: center; gap: 10px; margin: 16px 4px 8px; font-family: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace; font-size: 10px; font-weight: 500; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-mute); }
  .side-nav-label::after { content: ""; flex: 1; height: 1px; background: linear-gradient(90deg, rgba(255,255,255,.10), transparent); }
  .side-nav { margin-top: 0; gap: 4px; }

  /* Comfortable touch targets + press feedback inside the drawer */
  .side-nav a { padding: 12px 13px; min-height: 48px; font-size: .95rem; border-radius: 12px; }
  .side-nav a svg { width: 18px; height: 18px; }
  .side-nav a:active { background: rgba(255,255,255,.07); }
  .pw-upgrade-btn { padding: 12px; min-height: 44px; }
  .pw-logout-btn { padding: 12px; min-height: 48px; margin-top: 8px; }

  /* Bottom block (credits + logout) separated from the nav with its own rule */
  .side-bottom { padding-top: 14px; border-top: 1px solid rgba(255,255,255,.07); }
}

/* Fallback when backdrop-filter is unsupported: fully opaque drawer */
@supports not (backdrop-filter: blur(1px)) {
  @media (max-width: 900px) { aside.side { background: rgba(10,9,8,.99); } }
}

/* Staggered content entrance — premium reveal, motion-safe (skipped entirely
   under prefers-reduced-motion; the base state stays fully visible there) */
@media (max-width: 900px) and (prefers-reduced-motion: no-preference) {
  .side > * { opacity: 0; transform: translateX(-10px); transition: opacity .16s ease, transform .16s ease; }
  body.drawer-open .side > * { opacity: 1; transform: none; transition: opacity .34s ease, transform .34s cubic-bezier(.22,1,.36,1); }
  body.drawer-open .side > :nth-child(2) { transition-delay: 40ms; }
  body.drawer-open .side > :nth-child(4) { transition-delay: 70ms; }
  body.drawer-open .side > :nth-child(5) { transition-delay: 100ms; }
  body.drawer-open .side > :nth-child(6) { transition-delay: 130ms; }
}


/* ═══════════════════════════════════════════════════════════════════
   INPUT-MODALITY FOCUS SYSTEM — product-wide interaction contract.
   Focus rings are for keyboard users. iOS Safari matches :focus-visible
   on PROGRAMMATIC focus (drawer/modal focus traps + focus-restore), so
   tap-driven flows previously stranded rings (UA blue where uncustomized,
   amber where customized). dashboard-shell.js stamps
   <html data-input="pointer|kbd"> from the LAST input type; while it is
   "pointer", non-form focus outlines are suppressed. Form fields
   (input/textarea/select) ALWAYS keep their focus styles — they signal
   active text entry. Tab/arrow keys restore every ring instantly.
   Every ring in the product is outline-based (audited 2026-07-06), so
   suppressing outline alone is complete. Keep in sync with the copy in
   public-header.css. ═════════════════════════════════════════════════ */
html[data-input="pointer"] :focus:not(input):not(textarea):not(select) {
  outline: none !important;
}

/* Touch polish: kill the iOS gray tap-flash (components provide their own
   pressed states) and the double-tap-zoom delay on interactive elements. */
a, button, [role="button"], label, select, summary, [tabindex] {
  -webkit-tap-highlight-color: transparent;
}
a, button, input, select, textarea, label, summary {
  touch-action: manipulation;
}
