/* main — the shell's own rules, moved out of its template.
 *
 * Every declaration is byte-for-byte what `client_code/main/form_template.yaml`
 * held. The only change is the prelude: each rule is scoped
 * `:where(.spoke-shell)`, which adds zero specificity, so no rule can change
 * which one wins.
 *
 * The ROOT gets no rule at all. `main`'s template has three top-level nodes
 * (`.spoke-app`, `#spoke-toast`, `#spoke-reset-overlay`), so the conversion
 * wraps them in one `.spoke-shell` div to keep Anvil from adopting a wrapper of
 * its own; `.spoke-app` keeps its own rule as an inner div, exactly as today.
 *
 * NOT here, and deliberately: `*`, `body`, `.loading-spinner`, its `::before`
 * and `@keyframes spin`. All five are app-global rather than shell-scoped and
 * live in `theme/assets/spoke/spoke_shared.css`; scoping the first two to
 * `.spoke-shell` would NARROW what they reach, which is a behaviour change, and
 * `body` cannot be scoped at all.
 *
 * ORDER AUDIT. `main.css` sorts FIRST among the spoke links, so on a
 * specificity tie a leaf's rule beats the shell's — which is today's order,
 * because `main`'s inline block was injected when `main` mounted and each
 * child's when the child mounted, later. No class declared here is declared by
 * any other spoke form, no `.px-*` class selector appears, and
 * `px-critical-tokens` declares custom properties only, so nothing ties.
 *
 * No designer gate is needed: nothing here hides content until JavaScript runs
 * that the markup does not already hide today.
 */

:where(.spoke-shell) .spoke-app { display: flex; flex-direction: column; min-height: 100vh; background: #f5f7fa; }

/* Top bar */
:where(.spoke-shell) .spoke-topbar {
  background: var(--px-accent); color: #fff; display: flex; align-items: center;
  justify-content: space-between; padding: 0 20px; height: 56px; flex-shrink: 0;
  box-shadow: 0 2px 4px rgba(0,0,0,0.15);
}
:where(.spoke-shell) .spoke-topbar-title { font-size: 18px; font-weight: 600; letter-spacing: 0.3px; }
:where(.spoke-shell) .spoke-topbar-group { display: flex; align-items: center; gap: 12px; }
:where(.spoke-shell) .spoke-topbar-user { font-size: 13px; opacity: 0.85; }
/* Active-spoke switcher — hidden unless the user belongs to 2+ spokes */
:where(.spoke-shell) .spoke-switcher {
  display: none; background: rgba(255,255,255,0.12); color: #fff;
  border: 1px solid rgba(255,255,255,0.4); border-radius: 4px;
  padding: 6px 10px; font-size: 13px; font-family: inherit; cursor: pointer;
  max-width: 260px;
}
:where(.spoke-shell) .spoke-switcher.visible { display: inline-block; }
:where(.spoke-shell) .spoke-switcher option { color: var(--px-accent); background: #fff; }
/* Single-spoke label (switcher hidden): show which spoke is active */
:where(.spoke-shell) .spoke-active-name { display: none; font-size: 13px; opacity: 0.85; }
:where(.spoke-shell) .spoke-active-name.visible { display: inline; }
:where(.spoke-shell) .spoke-topbar-logout {
  background: none; border: 1px solid rgba(255,255,255,0.4); color: #fff;
  padding: 6px 14px; border-radius: 4px; cursor: pointer; font-size: 13px;
}
:where(.spoke-shell) .spoke-topbar-logout:hover { background: rgba(255,255,255,0.15); }

/* Nav tabs */
:where(.spoke-shell) .spoke-nav {
  display: flex; background: #fff; border-bottom: 2px solid #e0e0e0;
  padding: 0 20px; flex-shrink: 0;
}
:where(.spoke-shell) .spoke-nav-tab {
  padding: 12px 24px; cursor: pointer; font-size: 14px; font-weight: 500;
  color: #555; border-bottom: 3px solid transparent; transition: all 0.2s;
  user-select: none; background: none; border: none; border-bottom: 3px solid transparent;
  font-family: inherit;
}
:where(.spoke-shell) .spoke-nav-tab:focus-visible { outline: 2px solid var(--px-accent); outline-offset: -2px; }
:where(.spoke-shell) .spoke-nav-tab:hover { color: var(--px-accent); background: #f5f5f5; }
:where(.spoke-shell) .spoke-nav-tab.active { color: var(--px-accent); border-bottom-color: var(--px-accent); font-weight: 600; }

/* Content area — applies to both static content and child form slot */
:where(.spoke-shell) .spoke-content, :where(.spoke-shell) #spoke-child-forms { flex: 1; padding: 20px; overflow-y: auto; }

/* Toast — informational only, never captures clicks. The toast uses
   opacity (not display:none) to fade in/out, so its DOM box persists
   and would otherwise block clicks on anything underneath (e.g. the
   Save Pattern button in the Custom Times modal whose footer sits at
   bottom-right, exactly where this toast lives). */
:where(.spoke-shell) .spoke-toast {
  position: fixed; bottom: 20px; right: 20px; padding: 12px 24px;
  border-radius: 6px; color: #fff; font-size: 14px; z-index: 10000;
  box-shadow: 0 4px 12px rgba(0,0,0,0.25); opacity: 0; transition: opacity 0.3s;
  max-width: 400px; pointer-events: none;
}
:where(.spoke-shell) .spoke-toast.visible { opacity: 1; }
:where(.spoke-shell) .spoke-toast.success { background: #2e7d32; }
:where(.spoke-shell) .spoke-toast.error { background: #c62828; }
/* `danger` is the shared toolkit's spelling of the same severity
   (PX_TOAST_TYPES: success/error/danger/warning/info). window.pxGlobalToast
   points at showToast in main.mjs, so a toolkit call using `danger` renders
   through this node and would otherwise land with no background at all. */
:where(.spoke-shell) .spoke-toast.danger { background: #c62828; }
:where(.spoke-shell) .spoke-toast.warning { background: #ef6c00; }
:where(.spoke-shell) .spoke-toast.info { background: #1565c0; }
/* Size variant the shared toolkit asks for (PX_TOAST_SIZES.large). */
:where(.spoke-shell) .spoke-toast.spoke-toast-large {
  max-width: 560px; font-size: 15px; padding: 16px 28px;
}

/* Training environment banner — shown only in the "Spoke Training"
   env via setTrainingMode(). High-visibility amber, persists on every
   tab because it lives on the main shell above the child form slot. */
:where(.spoke-shell) .spoke-training-banner {
  display: none; background: var(--px-warning); color: #fff; text-align: center;
  font-size: 13px; font-weight: 700; letter-spacing: 0.5px;
  padding: 7px 16px; flex-shrink: 0;
  box-shadow: inset 0 -1px 0 rgba(0,0,0,0.2);
}
:where(.spoke-shell) .spoke-training-banner.visible { display: block; }

/* Last-reset-failed notice — dismissible inline banner (NOT the
   blocking overlay); shown on fresh loads when the most recent reset
   task terminated unsuccessfully. */
:where(.spoke-shell) .spoke-reset-failure-notice {
  display: none; background: #fdecea; color: #a12626;
  border-bottom: 1px solid #f2b8b5; font-size: 13px; font-weight: 600;
  padding: 7px 40px 7px 16px; text-align: center; position: relative;
  flex-shrink: 0;
}
:where(.spoke-shell) .spoke-reset-failure-notice.visible { display: block; }
:where(.spoke-shell) .spoke-reset-failure-dismiss {
  position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
  background: none; border: none; color: #a12626; font-size: 16px;
  font-weight: 700; cursor: pointer; line-height: 1; padding: 2px 6px;
}

/* Reset training data button — topbar, Spoke Training env only. */
:where(.spoke-shell) .spoke-reset-btn {
  display: none; background: var(--px-warning); border: 1px solid rgba(255,255,255,0.6);
  color: #fff; padding: 6px 14px; border-radius: 4px; cursor: pointer;
  font-size: 13px; font-weight: 600; font-family: inherit;
}
:where(.spoke-shell) .spoke-reset-btn:hover:not(:disabled) {
  /* Derived from the token, never a second literal: --px-warning is
     band-dependent, so one hex would darken the light band and change
     hue in the dark one. Same 80%-toward-black derivation the shared
     theme uses for --px-info-hover (R3-94). */
  background: color-mix(in srgb, var(--px-warning) 80%, #000);
}
:where(.spoke-shell) .spoke-reset-btn:disabled { opacity: 0.6; cursor: not-allowed; }
:where(.spoke-shell) .spoke-reset-btn.visible { display: inline-flex; }

/* Dark band: --px-warning is a light amber (#fbbf24), so the white ink
   both amber surfaces above use in the light band drops to roughly
   1.7:1 and the "high-visibility" banner becomes the least readable
   thing on the page. Ink and border switch to dark in this band only —
   the light band is byte-unchanged.

   `[data-theme]` sits on <html>, an ANCESTOR of the root, so the scope goes on
   the SUBJECT rather than in front of the whole selector. Written
   `:where(.spoke-shell) [data-theme="dark"] ...` the dark band would silently
   lose its ink fix. Specificity is the same either way. */
[data-theme="dark"] :where(.spoke-shell) .spoke-training-banner,
[data-theme="dark"] :where(.spoke-shell) .spoke-reset-btn { color: #0f172a; }
[data-theme="dark"] :where(.spoke-shell) .spoke-reset-btn { border-color: rgba(0,0,0,0.35); }

/* Reset overlay — client-owned countdown + 1Hz poll state machine. */
:where(.spoke-shell) .spoke-reset-overlay {
  position: fixed; inset: 0; background: rgba(15,20,45,0.92);
  display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 16px; z-index: 11000;
  opacity: 0; visibility: hidden; transition: opacity 0.25s, visibility 0.25s;
}
:where(.spoke-shell) .spoke-reset-overlay.visible { opacity: 1; visibility: visible; }
:where(.spoke-shell) .spoke-reset-countdown {
  font-size: 64px; font-weight: 600; color: #fff; font-variant-numeric: tabular-nums;
}
:where(.spoke-shell) .spoke-reset-status {
  color: #fff; font-size: 16px; font-weight: 500; text-align: center;
  max-width: 480px; padding: 0 16px;
}
:where(.spoke-shell) .spoke-reset-actions { display: none; gap: 16px; margin-top: 16px; }
:where(.spoke-shell) .spoke-reset-actions.visible { display: flex; }
:where(.spoke-shell) .spoke-reset-retry, :where(.spoke-shell) .spoke-reset-dismiss {
  padding: 8px 24px; font-size: 14px; font-weight: 500; border-radius: 4px;
  cursor: pointer; border: 1px solid #fff; font-family: inherit;
}
:where(.spoke-shell) .spoke-reset-retry { background: #fff; color: var(--px-accent); }
:where(.spoke-shell) .spoke-reset-retry:hover { opacity: 0.9; }
:where(.spoke-shell) .spoke-reset-dismiss { background: transparent; color: #fff; }
:where(.spoke-shell) .spoke-reset-dismiss:hover { background: rgba(255,255,255,0.1); }

@media (max-width: 768px) {
  :where(.spoke-shell) .spoke-topbar {
    padding: 8px 12px; height: auto; min-height: 56px; flex-wrap: wrap; row-gap: 8px;
  }
  :where(.spoke-shell) .spoke-topbar-group { flex-wrap: wrap; row-gap: 8px; width: 100%; justify-content: flex-end; }
  :where(.spoke-shell) .spoke-topbar-user { display: none; }
  :where(.spoke-shell) .spoke-switcher.visible {
    display: block; flex: 1 0 100%; max-width: 100%;
  }
  :where(.spoke-shell) .spoke-topbar select { font-size: 16px; }
  :where(.spoke-shell) .spoke-topbar-logout, :where(.spoke-shell) .spoke-reset-btn {
    min-height: 44px; min-width: 44px; align-items: center; justify-content: center;
  }
  :where(.spoke-shell) .spoke-topbar-logout { display: inline-flex; }
  :where(.spoke-shell) .spoke-nav-tab { padding: 10px 16px; font-size: 13px; min-height: 44px; }
  :where(.spoke-shell) .spoke-content, :where(.spoke-shell) #spoke-child-forms { padding: 12px; }
  :where(.spoke-shell) .spoke-toast { max-width: calc(100vw - 40px); }
  :where(.spoke-shell) .spoke-toast.spoke-toast-large { max-width: calc(100vw - 40px); }
  :where(.spoke-shell) .spoke-reset-failure-notice { padding-right: 56px; }
  :where(.spoke-shell) .spoke-reset-failure-notice.visible { display: flex; align-items: center;
    justify-content: center; min-height: 44px; }
  :where(.spoke-shell) .spoke-reset-failure-dismiss {
    min-height: 44px; min-width: 44px; display: flex; align-items: center;
    justify-content: center; right: 6px;
  }
}
