@import url('../omni-ui.css');

/* OmniStream shell — ported from mockups/omni-mock.css.
   Only shell/nav/topbar/cmdk/universal-component blocks are included here.
   Feature-section blocks (.sd*, .qa*, .preview, .connect-row, etc.) live
   in their own section stylesheets (later tickets). */

/* ---- Fonts (graceful fallback if offline) ---- */
@import url('https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap');

*{box-sizing:border-box}
html,body{margin:0;height:100%}
body{
  background:var(--bg); color:var(--text); font-family:var(--font-display);
  font-size:13px; line-height:1.45; -webkit-font-smoothing:antialiased;
}
a{color:inherit;text-decoration:none}

/* ============ ACCESSIBILITY ============ */
.skip-link{position:absolute;left:-999px;top:0;background:var(--accent);color:#fff;padding:8px 14px;z-index:200;border-radius:0 0 var(--r) 0}
.skip-link:focus{left:0}
:focus-visible{outline:2px solid var(--accent-soft); outline-offset:2px; border-radius:var(--r-sm)}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}

/* ============ APP SHELL ============ */
/* Auth-gate flash protection (P1-T2). auth-gate.js sets html[data-auth]
   ('ok' once /api/account confirms a session, 'error' on a transient 5xx /
   network blip). Until it resolves, the shell must NOT paint — otherwise an
   unauthenticated visitor sees the full dashboard chrome (nav, channel label,
   Go Live) flash before being redirected to /login. The default-hidden state
   below is in <head>-linked CSS so it is in force from first paint; auth-gate
   is `defer` (runs after parse) but the attribute is absent until then, so the
   shell stays hidden by default and is revealed only on a resolved state. On
   'error' we reveal (with the section's own quiet banner) rather than trap a
   logged-in user behind a blank screen on a transient failure. */
html[data-js]:not([data-auth='ok']):not([data-auth='error']) .app-shell{visibility:hidden}
html[data-auth='ok'] .app-shell,
html[data-auth='error'] .app-shell{visibility:visible}
.app-shell{--sidebar-w:248px; display:grid; grid-template-columns:var(--sidebar-w) 1fr; grid-template-rows:auto 1fr; grid-template-areas:"sidebar topbar" "sidebar content"; height:100vh; overflow:hidden}
.app-shell.collapsed{--sidebar-w:64px}

/* Sidebar */
.sidebar{grid-area:sidebar; background:linear-gradient(180deg,#1b1820,var(--surface-inset)); border-right:1px solid var(--border); display:flex; flex-direction:column; overflow-y:auto; overflow-x:hidden}
.brand{display:flex; align-items:center; gap:var(--space-3); padding:var(--space-4); font-weight:700; font-size:15px; letter-spacing:.3px; position:sticky; top:0; background:#1b1820; z-index:2}
.brand .logo{width:26px;height:26px;border-radius:7px;object-fit:contain;flex:none;display:block}
.brand .logo svg{width:15px;height:15px}
/* Collapsed rail hides the LABEL span only — NOT the icon span (.ico), which must
   stay visible as the icon-only rail. `.nav-item span` would match BOTH spans
   (icon + label); :not(.ico) keeps the icon. */
.collapsed .brand-name,.collapsed .nav-item span:not(.ico){display:none}
/* Group-label: keep its vertical BOX (hide only the text) when collapsed, so the
   icons below DON'T shift up. display:none removes the header's space and reflows
   every icon upward — the reported "icons change position when collapsed". */
.collapsed .nav-group-label{visibility:hidden}
.nav{padding:var(--space-2) var(--space-2) var(--space-6)}
.nav-group{margin-top:var(--space-4)}
.nav-group-label{display:flex;align-items:center;gap:6px;padding:6px var(--space-3);font-family:var(--font-mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--text-faint);cursor:pointer;user-select:none}
.nav-group-label .chev{margin-left:auto;transition:transform var(--dur) var(--ease);font-size:9px}
.nav-group.closed .chev{transform:rotate(-90deg)}
.nav-group.closed .nav-item{display:none}
.nav-item{display:flex;align-items:center;gap:var(--space-3);padding:9px var(--space-3);border-radius:var(--r-sm);color:var(--text-dim);cursor:pointer;font-size:13px;font-weight:500;position:relative;transition:background var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)}
.nav-item .ico{width:18px;text-align:center;flex:none;font-size:15px;filter:grayscale(.3)}
/* :hover is decorative-only and lives in @media(hover:hover) (S2-shell) so it
   never sticks on touch — the unconditional rule was removed to avoid sticky hover. */
.nav-item.is-active{background:var(--accent-wash);color:var(--text)}
.nav-item.is-active::before{content:"";position:absolute;left:-2px;top:8px;bottom:8px;width:3px;border-radius:3px;background:var(--accent)} /* shell rail indicator (allowed: nav affordance, not a card stripe) */
.nav-item .tag{margin-left:auto;font-family:var(--font-mono);font-size:9px;padding:1px 6px;border-radius:var(--r-pill);background:#ffffff14;color:var(--text-dim)}
.nav-item .lock{margin-left:auto;opacity:.5;font-size:11px}
.nav-upsell{margin:var(--space-2) var(--space-2) 0;padding:10px var(--space-3);border:1px dashed var(--border-accent);border-radius:var(--r);color:var(--text-dim);font-size:11.5px;cursor:pointer;background:var(--accent-wash)}
.sidebar-foot{margin-top:auto;padding:var(--space-3);border-top:1px solid var(--border);display:flex;gap:var(--space-2)}

/* Topbar */
/* NOTE: no backdrop-filter. A blurred sticky bar is one of the most expensive
   per-frame compositor ops, and it's ALWAYS in the viewport — when a preview video
   drives the compositor (20fps) it re-blurs every frame, which showed up as scroll/
   input sluggishness (worst on the dense Stream Manager page). Solid bar instead. */
.topbar{grid-area:topbar;display:flex;align-items:center;gap:var(--space-3);padding:0 var(--space-4);height:56px;border-bottom:1px solid var(--border);background:#1c1c20;position:sticky;top:0;z-index:10}
.icon-btn{width:34px;height:34px;display:grid;place-items:center;border-radius:var(--r-sm);background:transparent;border:1px solid transparent;color:var(--text-dim);cursor:pointer;font-size:16px}
/* :hover decorative-only → @media(hover:hover) (S2-shell), no sticky hover on touch. */
/* (.chan-switch retired in desktop-overhaul §02 — the channel dropdown folded into
   the destination cards; see sec-destinations.js.) */
.cmdk-trigger{display:flex;align-items:center;gap:var(--space-2);min-width:220px;padding:7px var(--space-3);border-radius:var(--r-sm);background:var(--surface-inset);border:1px solid var(--border);color:var(--text-faint);cursor:text;font-size:12px}
.cmdk-trigger .kbd{margin-left:auto}
.kbd{font-family:var(--font-mono);font-size:10px;padding:1px 6px;border-radius:4px;background:#ffffff14;border:1px solid var(--border);color:var(--text-dim)}
.topbar-spacer{flex:1}
/* P4 low-balance nudge: a polite status message paired with a warn action link
   (NOT a .pill — §3.3 reserves .pill for transient LIVE/ERROR). Inline group with
   topbar rhythm; the link itself uses the .btn system (.btn--warn .btn--sm). */
.billing-nudge{display:inline-flex;align-items:center;gap:var(--space-2)}
.billing-nudge-msg{font-family:var(--font-display);font-weight:600;font-size:12px}

/* Content */
.content{grid-area:content;overflow-y:auto;padding:var(--space-6)}
.page-header{display:flex;align-items:flex-end;gap:var(--space-4);margin-bottom:var(--space-5);flex-wrap:wrap}
.page-header h1{margin:0;font-size:24px;font-weight:700;letter-spacing:.2px}
.page-header .sub{color:var(--text-dim);font-size:13px;margin-top:2px}
.page-header .ph-actions{margin-left:auto;display:flex;gap:var(--space-2)}
.section{display:none}
.section.is-active{display:block;animation:rise var(--dur-slow) var(--ease)}
/* Uniform vertical rhythm across ALL sections: every top-level block (page header,
   cards, grids, stat bars) is separated by exactly one space-4 gap. Without this,
   stacked blocks relied on ad-hoc per-block margins and frequently touched at 0 —
   the inconsistent card/grid spacing seen on every page. Scoped to DIRECT children
   so each block's own inner layout is untouched; the last block has no trailing gap. */
.section > *{margin-bottom:var(--space-4)}
.section > :last-child{margin-bottom:0}
@keyframes rise{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}

/* Live banner (persistent when live) */
.live-banner{display:none;align-items:center;gap:var(--space-3);padding:8px var(--space-4);background:linear-gradient(90deg,rgba(255,64,64,.16),transparent);border-bottom:1px solid var(--border)}
body.is-live .live-banner{display:flex}
.live-banner .rec{display:flex;align-items:center;gap:7px;font-family:var(--font-mono);font-size:11px;color:var(--text)}
.live-banner .rec .dot{background:var(--danger);animation:live-glow-d 2.8s ease-in-out infinite}
@keyframes pulse{50%{opacity:.4}}
/* Soft, slow glow for the LIVE indicators — a calm breathing halo rather than a
   hard opacity blink (intentionally low-key, not distracting). Two colour variants:
   red-on-dark for the banner dot, white-on-red for the Go-Live button dot. */
@keyframes live-glow-d{0%,100%{box-shadow:0 0 4px 0 rgba(255,64,64,.5)}50%{box-shadow:0 0 11px 1px rgba(255,64,64,.95)}}
@keyframes live-glow-w{0%,100%{box-shadow:0 0 4px 0 rgba(255,255,255,.4)}50%{box-shadow:0 0 10px 1px rgba(255,255,255,.9)}}
@media (prefers-reduced-motion: reduce){.live-banner .rec .dot,.btn--live .live-dot{animation:none}}

/* ============ COMPONENTS ============ */

/* Card */
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);box-shadow:var(--shadow-card);padding:var(--space-4)}
.card--raised{background:var(--surface-raised);border-color:var(--border-accent)}

/* Buttons — one system */
.btn{font-family:var(--font-display);font-weight:600;font-size:12px;display:inline-flex;align-items:center;gap:7px;padding:8px 14px;border-radius:var(--r-sm);border:1px solid var(--border);background:var(--surface-inset);color:var(--text);cursor:pointer;transition:transform var(--dur-fast) var(--ease),background var(--dur-fast) var(--ease),border-color var(--dur-fast)}
/* .btn / .btn--primary :hover decorative-only → @media(hover:hover) (S2-shell). */
.btn:active{transform:translateY(1px)}
.btn--primary{background:linear-gradient(135deg,#8a3df0,var(--accent));border-color:transparent;color:#fff;box-shadow:0 6px 18px -8px var(--accent)}
/* Live Go-Live button (topbar + Home): solid red with a soft-glowing dot. Shared
   so both entry points reflect the broadcast state identically. */
.btn--live{background:var(--danger);border-color:transparent;color:#fff;box-shadow:0 6px 18px -8px var(--danger)}
.btn--live .live-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:#fff;animation:live-glow-w 2.8s ease-in-out infinite}
.btn--ghost{background:transparent}
.btn--warn{border-color:var(--warn);color:var(--warn)} .btn--danger{border-color:var(--danger);color:var(--danger)}
.btn--sm{padding:5px 10px;font-size:11px} .btn--lg{padding:12px 20px;font-size:13px}
.btn[disabled]{opacity:.45;cursor:not-allowed}

/* Status: pill (transient) vs badge (persistent) */
.pill{font-family:var(--font-mono);font-size:10px;letter-spacing:.06em;text-transform:uppercase;padding:3px 9px;border-radius:var(--r-pill);background:#ffffff12;color:var(--text-dim);display:inline-flex;align-items:center;gap:6px}
.pill--ok{background:rgba(0,179,0,.14);color:#5fd35f} .pill--warn{background:rgba(255,184,77,.15);color:var(--warn)} .pill--danger{background:rgba(255,64,64,.15);color:#ff6b6b}
.badge{font-family:var(--font-mono);font-size:9px;letter-spacing:.08em;text-transform:uppercase;font-weight:700;padding:2px 7px;border-radius:var(--r-sm);background:var(--accent-wash);color:var(--accent-2);border:1px solid var(--border-accent)}
.badge--beta{background:rgba(255,184,77,.12);color:var(--warn);border-color:#5a4520}
.dot{width:8px;height:8px;border-radius:50%;background:var(--text-faint);flex:none}
.dot--ok{background:var(--ok);box-shadow:0 0 8px rgba(0,179,0,.6)} .dot--warn{background:var(--warn)} .dot--danger{background:var(--danger);box-shadow:0 0 8px rgba(255,64,64,.6)}

/* Command palette */
.cmdk{position:fixed;inset:0;background:#000b;display:none;justify-content:center;align-items:flex-start;padding-top:12vh;z-index:130;backdrop-filter:blur(3px)}
.cmdk.open{display:flex}
.cmdk-box{width:min(560px,92vw);background:var(--surface);border:1px solid var(--border-accent);border-radius:var(--r-md);box-shadow:var(--shadow-modal);overflow:hidden}
.cmdk-box input{width:100%;border:0;background:transparent;color:var(--text);font-family:var(--font-display);font-size:16px;padding:16px 18px;border-bottom:1px solid var(--border)}
.cmdk-box input:focus{outline:none}
.cmdk-list{max-height:340px;overflow-y:auto;padding:6px}
.cmdk-item{display:flex;align-items:center;gap:var(--space-3);padding:10px 12px;border-radius:var(--r-sm);cursor:pointer;color:var(--text-dim);width:100%;text-align:left;background:transparent;border:0;font:inherit}
/* .sel (keyboard/programmatic selection) stays unconditional; :hover is
   decorative-only and lives in @media(hover:hover) (S2-shell) — no sticky hover on touch. */
.cmdk-item.sel{background:var(--accent-wash);color:var(--text)}
.cmdk-item .grp{margin-left:auto;font-family:var(--font-mono);font-size:9px;text-transform:uppercase;color:var(--text-faint)}

/* Empty / loading states */
.empty{display:grid;place-items:center;text-align:center;padding:var(--space-10) var(--space-4);color:var(--text-dim)}
.empty .big{font-size:30px;margin-bottom:8px;opacity:.6}

/* Utility */
.mono{font-family:var(--font-mono)}
.dim{color:var(--text-dim)} .faint{color:var(--text-faint)}
/* Semantic text-color utilities (token-only). Pair with role="alert" so an
   error state is both ANNOUNCED and visibly colored (DESIGN-GUIDELINES §5:
   error = cause + retry, visually signalled). Used by inline form errors, list
   error states and the script lastError row in the bot/loyalty sections. */
.danger{color:var(--danger)} .ok{color:var(--ok)} .warn{color:var(--warn)}
.hide{display:none!important}

/* ============ DROPDOWN MENU (account / channel switcher) ============ */
/* Token-only floating menu. `--shadow-2` from the ticket has no token in
   omni-ui.css; nearest existing elevation token is --shadow-modal (used here)
   and --shadow-drawer. */
.menu{
  position:absolute; top:calc(100% + var(--space-2)); right:0; z-index:120;
  min-width:200px;
  background:var(--surface); border:1px solid var(--border);
  border-radius:var(--r-md); box-shadow:var(--shadow-modal);
  padding:var(--space-1); overflow:hidden;
}
.menu[hidden]{display:none}
/* The account menu's trigger is the right-most topbar child, so the default
   right:0 anchors it correctly. (The left-anchored channel-switcher popover —
   .chan-switch-anchor/.menu--channels — was retired in desktop-overhaul §02.) */
.menu-head{
  padding:var(--space-2) var(--space-3);
  display:flex; flex-direction:column; gap:2px;
  border-bottom:1px solid var(--border);
}
.menu-name{font-weight:600;color:var(--text)}
.menu-list{padding-top:var(--space-1)}
.menu-item{
  display:flex; align-items:center; gap:var(--space-2);
  width:100%; text-align:left;
  padding:var(--space-2) var(--space-3);
  border:0; background:transparent;
  border-radius:var(--r-sm);
  color:var(--text-dim); cursor:pointer;
  font:inherit; font-size:13px;
}
/* :focus stays unconditional (keyboard); :hover is decorative-only and lives in
   @media(hover:hover) (S2-shell) — no sticky hover on touch. */
.menu-item:focus{background:var(--surface-inset);color:var(--text)}

/* ============================================================================
   S2-shell — MOBILE LAYOUT (additive, scoped). Everything below is either
   inside @media(max-width:860px), @media(hover:*) or body.is-embedded so the
   desktop grid/topbar/sidebar render byte-identical to before. Breakpoint 860px.
   ============================================================================ */

/* dvh: keep the shell pinned to the *visible* viewport on mobile browsers whose
   URL bar collapses (100vh overshoots; 100dvh tracks the live height). Desktops
   that don't support dvh keep the 100vh value. */
.app-shell{height:100vh;height:100dvh}

/* Drawer scrim — hidden by default everywhere; only painted on mobile when the
   nav is open. The [hidden] attr (toggled by shell.js) is the inert default. */
.nav-scrim{display:none}

/* Body scroll-lock while the drawer is open (set by shell.js with the drawer).
   NOTE: body is not the scroll container (.app-shell is overflow:hidden and the
   real scroller is .content{overflow-y:auto}), so this body rule is belt-and-
   braces only. The actual lock is `.app-shell.nav-open .content{overflow:hidden}`
   in the @media(max-width:860px) block below, which pins the real scroller so
   keyboard/trackpad/programmatic scroll can't reach the content behind the drawer. */
body.nav-locked{overflow:hidden}

/* Drawer close (X) affordance lives in the brand row; desktop never shows it. */
.nav-close{display:none}

/* Bottom tab-bar — built by shell.js, hidden on desktop; shown only <=860px. */
.tabbar{display:none}

/* nav-group-label is now a real <button> (was a div) — strip native button
   chrome so the desktop look is byte-identical to the old div. Additive +
   unconditional; the .nav-group-label class rule above supplies layout/color. */
button.nav-group-label{width:100%;margin:0;border:0;background:transparent;text-align:left;font-family:var(--font-mono);font-size:10px;line-height:1.45;color:var(--text-faint)}

@media (hover:hover){
  /* Pure-decoration hovers — only on devices that truly hover (not touch). These
     are the SOLE home for these :hover declarations (the unconditional duplicates
     were removed) so a tap on touch falls through to :active with no sticky hover.
     Desktop always matches (hover:hover) so its look is byte-identical. */
  .nav-item:hover{background:#ffffff0a;color:var(--text)}
  .icon-btn:hover{background:#ffffff0d;color:var(--text)}
  .btn:hover{border-color:var(--border-accent);background:#ffffff0d}
  .btn--primary:hover{background:linear-gradient(135deg,#9550ff,#a06bff)}
  .cmdk-item:hover{background:var(--accent-wash);color:var(--text)}
  .menu-item:hover{background:var(--surface-inset);color:var(--text)}
}
/* :active feedback for touch — no hover to lean on, so press-state matters. */
.nav-item:active{background:var(--accent-wash)}
.icon-btn:active{background:#ffffff14;transform:translateY(1px)}

@media (max-width:860px){
  html,body{overflow-x:hidden}

  /* Single-column: topbar over content; sidebar leaves the grid (it goes fixed). */
  .app-shell{grid-template-columns:1fr;grid-template-rows:auto 1fr;grid-template-areas:"topbar" "content"}
  /* Neutralize the desktop collapsed rail — on mobile the drawer is the nav. */
  .app-shell.collapsed{--sidebar-w:248px}

  /* Off-canvas drawer */
  .sidebar{
    position:fixed; inset:0 auto 0 0; width:min(86vw,320px); z-index:140;
    transform:translateX(-100%); transition:transform var(--dur) var(--ease);
    box-shadow:var(--shadow-drawer);
    padding-top:var(--safe-top); padding-bottom:var(--safe-bottom);
    padding-left:max(0px,var(--safe-left));
  }
  .app-shell.nav-open .sidebar{transform:none}
  /* Lock the REAL scroller while the drawer is open (body overflow is a no-op
     here — see the .nav-locked note above). Stops keyboard/trackpad/programmatic
     scroll of the content behind the open drawer, not just scrim interception. */
  .app-shell.nav-open .content{overflow:hidden}
  /* The .collapsed rule above hides labels via .collapsed selectors elsewhere;
     ensure the drawer always shows full labels even if .collapsed lingers. */
  .app-shell.collapsed .brand-name,
  .app-shell.collapsed .nav-item span,
  .app-shell.collapsed .nav-group-label{display:flex;visibility:visible}
  .app-shell.collapsed .nav-item span{display:inline}

  .nav-scrim{
    display:block; position:fixed; inset:0; z-index:130;
    background:rgba(0,0,0,.5); border:0; opacity:0; pointer-events:none;
    transition:opacity var(--dur) var(--ease);
  }
  .app-shell.nav-open .nav-scrim{opacity:1;pointer-events:auto}
  .nav-scrim[hidden]{display:none}

  /* Drawer close (X) in the brand row */
  .nav-close{
    display:grid; place-items:center; margin-left:auto;
    width:var(--touch-min); height:var(--touch-min);
    border:1px solid transparent; border-radius:var(--r-sm);
    background:transparent; color:var(--text-dim); cursor:pointer; font-size:18px;
  }
  .nav-close:active{background:#ffffff14}

  /* ---- Topbar reflow ---- */
  .topbar{
    gap:var(--space-2);
    padding-left:max(var(--space-4),var(--safe-left));
    padding-right:max(var(--space-4),var(--safe-right));
    padding-top:var(--safe-top);
    height:calc(56px + var(--safe-top));
  }
  /* cmdk-trigger collapses to a compact search affordance (still opens cmdk). */
  .cmdk-trigger{min-width:0;flex:none;width:var(--touch-min);justify-content:center;padding:0;height:var(--touch-min)}
  .cmdk-trigger .kbd,.cmdk-trigger .cmdk-label{display:none}

  /* ---- Touch targets ---- */
  .icon-btn{width:var(--touch-min);height:var(--touch-min);font-size:18px}
  .btn{min-height:var(--touch-min)}
  .nav-item{min-height:48px;padding:12px var(--space-3)}
  .nav-group-label{min-height:var(--touch-min)}

  /* ---- Content: pad for safe-area + leave room for the bottom tab-bar ---- */
  .content{
    padding-left:max(var(--space-6),var(--safe-left));
    padding-right:max(var(--space-6),var(--safe-right));
    padding-bottom:calc(56px + var(--safe-bottom) + var(--space-4));
  }
  .live-banner{
    padding-left:max(var(--space-4),var(--safe-left));
    padding-right:max(var(--space-4),var(--safe-right));
  }

  /* ---- Bottom tab-bar (primary mobile nav ergonomics) ---- */
  .tabbar{
    display:flex; position:fixed; left:0; right:0; bottom:0; z-index:120;
    background:#1c1c20; /* solid (no backdrop-filter — same per-frame compositor cost as the topbar) */
    border-top:1px solid var(--border);
    padding-bottom:var(--safe-bottom);
  }
  body.is-embedded .tabbar{display:none}
  .tabbar-item{
    flex:1; min-width:0; height:56px;
    display:grid; place-items:center; gap:2px;
    border:0; background:transparent; color:var(--text-dim);
    cursor:pointer; font:inherit; font-family:var(--font-display);
  }
  .tabbar-item .tb-ico{font-size:18px;line-height:1}
  .tabbar-item .tb-label{font-size:10px;letter-spacing:.02em;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .tabbar-item.is-active{color:var(--accent-2)}
  .tabbar-item:active{background:#ffffff10}
}

/* ============ EMBEDDED MODE (T7) — chrome-less single column ============ */
/* When ?embedded is present shell.js sets body.is-embedded: strip the topbar,
   sidebar and tab-bar so the content fills an iframe/webview surface. */
body.is-embedded .topbar,
body.is-embedded .sidebar,
body.is-embedded .tabbar{display:none}
body.is-embedded .app-shell{grid-template-columns:1fr;grid-template-rows:1fr;grid-template-areas:"content"}
body.is-embedded .content{padding-bottom:var(--space-6)}
