@import url("skeleton.css");

/* ==========================================================
   base.css — shared design tokens + universal reset (DRY)
   Loaded first on every styled page, before the page's group
   stylesheet and inline <style>. Pages may override :root
   tokens inline when they need extra tokens or different values.
   ========================================================== */

:root {
  --navy: #0f2044;
  --navy-light: #1d3a7a;
  /* Semantic alias for the primary page header/title colour. Every page title in
     the app resolves through this one token — never hardcode navy on a heading.
     Aliased (rather than a second literal) so the brand navy stays defined once. */
  --page-title-color: var(--navy);
  /* Primary button (see button.css — the one definition of the primary action).
     Every state is navy so a primary button never changes hue on interaction;
     it only gets lighter on hover and darker on press. */
  --btn-primary-bg: var(--navy);
  --btn-primary-bg-hover: var(--navy-light);
  --btn-primary-bg-active: #0a1730;
  --btn-primary-fg: #ffffff;
  --btn-primary-bg-disabled: #94a3b8;
  --btn-focus-ring: var(--blue-accent);
  --blue-accent: #2563eb;
  --blue-light: #dbeafe;
  --bg: #f1f5fb;
  --bg-card: #ffffff;
  --text-main: #0f2044;
  --text-muted: #64748b;
  --border: #e2e8f0;
  --amber: #f59e0b;
  --green: #16a34a;
  --green-light: #dcfce7;
  --red: #dc2626;
  --radius: 16px;
  --radius-sm: 10px;
  --shadow: 0 4px 24px rgba(15,32,68,0.06);
  --sidebar-w: 240px;
  /* header icon-button hit area: 22px icon + 6px padding all round */
  --icon-btn-size: 34px;
}

/* ── universal reset (mirrors the reset already shared by the
   per-role group stylesheets) ── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html {
  max-width: 100%;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}
body {
  min-width: 0;
  max-width: 100%;
  overflow-x: clip;
}
img, svg, video, canvas { max-width: 100%; }
/* responsive images scale without distortion; class rules that set an
   explicit height (avatars, fixed thumbnails) keep their size via higher
   specificity. svg is excluded so icon width/height attributes are respected. */
img { height: auto; }
/* long unbreakable strings (emails, URLs, transaction IDs) wrap instead of
   forcing horizontal overflow / being clipped by the overflow-x guard. */
body { overflow-wrap: break-word; }

/* Native controls otherwise keep an intrinsic minimum width in several
   browsers. This is particularly visible at text zoom, where a select or
   date input can become wider than its grid/flex track. */
input,
select,
textarea,
button { max-width: 100%; }
input,
select,
textarea { min-width: 0; }
textarea { resize: vertical; }

/* ── small-screen form baseline (responsive) ──
   iOS Safari zooms the viewport when a focused field has font-size < 16px.
   Force ≥16px on phones so focusing inputs never triggers an unwanted zoom.
   !important is required to win over component class rules; the larger text
   only applies at phone widths and improves legibility. */
@media (max-width: 480px) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]),
  select,
  textarea { font-size: 16px !important; }
}

/* ==========================================================
   sticky app header (DRY) — keeps the main header/navigation bar
   visible while scrolling on every styled app page.

   Applies to both the desktop header (.topbar) and the mobile
   header (.mobile-topbar). These bars are authored per page inside
   an inline <style>, but every one is a <header> element, so the
   `header.` qualifier here raises specificity to (0,1,1) and wins
   over each page's inline `.topbar{position:static}` /
   `.mobile-topbar{position:relative}` (0,1,0) — no !important and
   no per-page edits needed. Pages that already opted into a sticky
   header keep working unchanged.

   `position: sticky` leaves the bar in normal flow, so it never
   overlaps or hides page content (no extra offset/padding required).
   z-index 50 sits above page content but below the bottom nav,
   drawers, dropdowns and modals (60–9999), matching the value
   already used by sticky headers elsewhere in the app, so overlays
   are never covered.
   ========================================================== */
header.topbar,
header.mobile-topbar {
  position: sticky;
  top: 0;
  z-index: 50;
}

/* Opaque background for the sticky desktop header so page content never shows
   through it while scrolling. Every app topbar already sets `background: var(--bg)`
   in its per-page/group CSS, so this is visually identical to the current design;
   consolidating it here (DRY) guarantees the sticky bar stays opaque even if a
   page omits its own background. Scoped to the desktop header only — the mobile
   header keeps its per-page background (some pages add a bottom border there). */
header.topbar {
  background: var(--bg);
}

/* Subtle shadow that fades in only once the page is scrolled, for
   visual separation without altering the flat resting design.
   Pure CSS via a scroll-driven animation — browsers without support
   keep the sticky header with no shadow (graceful degradation).
   animation-timeline/animation-range are declared AFTER the
   `animation` shorthand because the shorthand resets the timeline. */
@supports (animation-timeline: scroll()) {
  header.topbar,
  header.mobile-topbar {
    animation: app-header-scroll-shadow linear both;
    animation-timeline: scroll(root block);
    animation-range: 8px 64px;
  }

  @keyframes app-header-scroll-shadow {
    from { box-shadow: 0 6px 16px -10px rgba(15, 32, 68, 0); }
    to   { box-shadow: 0 6px 16px -10px rgba(15, 32, 68, 0.35); }
  }
}

/* ==========================================================
   sticky desktop sidebar (DRY) — keeps the left navigation
   column (logo + nav links) pinned while the page scrolls, so
   the persistent top bar in the reference (logo alongside the
   sticky header) stays whole instead of the logo/links scrolling
   away with the content.

   The sidebar is always `<aside class="sidebar">` (rendered by the
   shared x-app.sidebar component and the few inline outliers), so
   the `aside.` qualifier raises specificity to (0,1,1) and wins
   over each page's inline `.sidebar` (0,1,0) app-wide — no
   !important and no per-page edits, matching the header approach.

   `height: 100vh` keeps the column exactly viewport-tall so it
   pins cleanly and its right border spans the full height; a grid
   item's default `stretch` would otherwise make it as tall as the
   (scrolling) main content and leave nothing for `sticky` to pin.
   `overflow-y: auto` lets a nav taller than the viewport scroll
   internally so no link becomes unreachable. Hidden on mobile by
   each page's `@media (max-width: 900px) .sidebar { display: none }`
   (untouched — different property, so this rule doesn't resurrect it).
   ========================================================== */
@media (min-width: 901px) {
  aside.sidebar {
    position: sticky;
    top: 0;
    height: 100vh;
    overflow-y: auto;
  }
}

/* ==========================================================
   Account-actions header cluster (notification bell + profile
   dropdown). Externalized from the self-contained
   components/account-actions.blade.php @once <style> so the
   reusable component no longer ships its own inline CSS.
   base.css is loaded first on every page that uses the
   component, so the cascade is unchanged.
   ========================================================== */
.acct-actions { margin-left:auto; display:flex; align-items:center; gap:18px; }
/* Notification bell (partials/phase2/notification_button). Single source of
   truth — do NOT redefine .icon-btn in a page or group stylesheet. The bell is
   a flex item of .acct-actions, so it is blockified: without the explicit box
   and centring below it stretches to whatever height the row's touch-target
   rules impose and the unread badge loses its positioning context. */
.icon-btn { position:relative; flex-shrink:0; width:var(--icon-btn-size,34px); height:var(--icon-btn-size,34px); display:inline-flex; align-items:center; justify-content:center; background:none; border:none; border-radius:50%; color:var(--navy,#0f2044); cursor:pointer; transition:background .2s; }
.icon-btn:hover { background:var(--blue-light,#dbeafe); }
.acct-actions .profile-wrapper { position:relative; }
.acct-actions .profile-trigger-avatar { width:38px; height:38px; border-radius:50%; background:var(--blue-light,#dbeafe); color:var(--blue-accent,#2563eb); display:flex; align-items:center; justify-content:center; border:none; cursor:pointer; flex-shrink:0; padding:0; overflow:hidden; transition:box-shadow .2s; }
.acct-actions .profile-trigger-avatar:hover { box-shadow:0 0 0 3px var(--blue-light,#dbeafe); }
.acct-actions .profile-menu { position:absolute; top:calc(100% + 12px); right:0; background:#fff; border-radius:var(--radius-sm,10px); box-shadow:var(--shadow,0 4px 24px rgba(15,32,68,.12)); width:260px; max-width:calc(100vw - 32px); padding:18px; display:none; flex-direction:column; gap:14px; z-index:120; border:1px solid var(--border,#e2e8f0); }
.acct-actions .profile-menu.open { display:flex; }
.acct-actions .profile-menu-user { display:flex; align-items:center; gap:14px; padding-bottom:14px; border-bottom:1px solid var(--border,#e2e8f0); }
.acct-actions .profile-menu-user > span, .acct-actions .user-name-row > span { font-weight:700; font-size:1rem; color:var(--navy,#0f2044); }
.acct-actions .profile-avatar { width:48px; height:48px; border-radius:12px; background:var(--blue-light,#dbeafe); display:flex; align-items:center; justify-content:center; color:var(--blue-accent,#2563eb); flex-shrink:0; overflow:hidden; }
.acct-actions .user-name-row { display:flex; align-items:center; gap:6px; flex-wrap:wrap; }
.acct-actions .user-role { font-size:.78rem; font-weight:600; color:var(--text-muted,#64748b); width:100%; }
.acct-actions .verified-badge { flex-shrink:0; color:var(--blue-accent,#2563eb); display:flex; }
.acct-actions .profile-menu-link { display:flex; align-items:center; gap:12px; font-size:.92rem; font-weight:600; color:var(--navy,#0f2044); padding:6px 2px; border-radius:var(--radius-sm,10px); transition:background .2s; text-decoration:none; }
.acct-actions .profile-menu-link:hover { background:var(--bg,#f1f5fb); }
.acct-actions .profile-menu-link.logout { color:var(--red,#dc2626); }
@media (max-width:900px) {
  .acct-actions { gap:14px; }
  .acct-actions .profile-menu { right:-8px; }
}

/* ==========================================================
   Sign-out <button> reset (DRY) — neutralizes the native button
   chrome so a `<button class="profile-menu-link logout">` renders
   exactly like the `<a class="profile-menu-link">` menu links.
   Consolidates the button-reset half of ~62 identical inline
   styles that previously lived on every student logout button.
   The compound `button.profile-menu-link.logout` specificity
   (0,2,1) intentionally beats `.profile-menu-link:hover` (0,2,0)
   so these buttons keep their flat (no hover-background) look, as
   the old inline `background:none` did. Only targets <button>, so
   the booth/staff logout <a> links are untouched. Layout that
   varies per button (width / padding) stays inline.
   ========================================================== */
button.profile-menu-link.logout { background:none; border:none; cursor:pointer; font-family:inherit; }

button.profile-menu-link.logout.tutorial-logout-button {
  width: 100%;
  appearance: none;
  margin: 0;
  padding: 6px 2px;
  border: none;
  background: transparent;
  box-shadow: none;
  font-family: inherit;
  text-align: left;
}

button.profile-menu-link.tutorial-restart-button {
  width: 100%;
  appearance: none;
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
}

/* Shared submit state. skeleton-loading.js applies this after a form has
   passed validation and has not been cancelled by another submit handler. */
button.is-submitting,
input[type="submit"].is-submitting {
  cursor: wait !important;
  pointer-events: none;
  opacity: .78;
}

.button-loading-spinner {
  display: inline-block;
  flex: 0 0 auto;
  width: 1em;
  height: 1em;
  margin-right: .5em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: button-loading-spin .65s linear infinite;
}

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

@media (prefers-reduced-motion: reduce) {
  .button-loading-spinner { animation-duration: 1.4s; }
}
