/* ==========================================================================
   Digitization Portal — design tokens ported 1:1 from the PSMS marketing
   site (C:\PSMS_WEBSITE\style.css) so this product visually matches the
   company's other public-facing work. Semantic status colors (success/
   danger/warning/info) are new additions — the marketing site had no forms
   or live data states, so nothing to port for those.
   ========================================================================== */
:root {
  --navy-950: #050a17;
  --navy-900: #0a1128;
  --navy-800: #101a3a;
  --navy-700: #16224d;
  --navy-600: #1d2c63;
  /* New: a lighter tier than PSMS's own darkest-to-navy-600 range — for
     spots (table headers, the navy secondary button) that need to read as
     clearly/unmistakably blue at a glance, not just "dark navy that isn't
     black". */
  --navy-500: #2c4090;
  --accent: #ff8c3a;
  --accent-2: #e6650f;
  --white: #ffffff;
  --off-white: #f5f7fb;
  --gray-100: #eef1f8;
  --gray-300: #c7cee0;
  --gray-500: #7c86a3;
  --gray-700: #4a5170;
  --text-dark: #10152a;

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

  --shadow-sm: 0 4px 16px rgba(10, 17, 40, 0.08);
  --shadow-md: 0 12px 32px rgba(10, 17, 40, 0.12);
  --shadow-lg: 0 24px 60px rgba(10, 17, 40, 0.18);

  --ease: cubic-bezier(0.4, 0, 0.2, 1);

  /* New: semantic status colors, tuned to sit alongside the navy/orange palette */
  --success: #1f9d63;
  --success-bg: #e7f7ee;
  --danger: #d64545;
  --danger-bg: #fdecec;
  --warning: #c98a12;
  --warning-bg: #fdf3df;
  --info: var(--navy-700);
  --info-bg: #eef1f8;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html { overflow-x: hidden; }

body {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  color: var(--text-dark);
  background: var(--off-white);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
/* Home page is a single full-viewport hero (entry/landing page to Login/
   Register only, no other sections) + a compact footer, sized to fit in
   exactly one screen with no scroll. body.page-home becomes its own fixed-
   height flex column (height:100vh + overflow:hidden, not min-height) so
   the hero (.top-section, via .page-main > .container, see below) grows
   to fill precisely "100vh minus the footer's actual rendered height" —
   no hardcoded pixel math, stays correct even if the footer wraps to 2
   lines on a narrow screen. Live-verified (browser devtools, real page):
   documentElement.scrollHeight === window.innerHeight, zero scroll. */
body.page-home { display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
body.page-home .page-main { flex: 1; padding: 0; min-height: 0; display: flex; flex-direction: column; }
/* .top-section is nested inside base.html's auto <div class="container">
   (not a flex container itself), so the flex:1 sizing has to be routed
   through it explicitly too, then .top-section fills that with height:100%. */
body.page-home .page-main > .container { flex: 1; display: flex; flex-direction: column; min-height: 0; }

h1, h2, h3, h4 { font-family: 'Poppins', 'Inter', sans-serif; line-height: 1.25; color: var(--navy-900); }
a { text-decoration: none; color: inherit; }
ul { list-style: none; }
img { max-width: 100%; display: block; }
i { line-height: 1; }

.container { width: 100%; max-width: 1240px; margin: 0 auto; padding: 0 24px; }

/* ==========================================================================
   Navbar
   ========================================================================== */
.navbar { background: var(--navy-900); box-shadow: 0 8px 30px rgba(0,0,0,0.18); position: sticky; top: 0; z-index: 1000; }
.navbar-inner { display: flex; align-items: center; gap: 16px; padding: 10px 0; }

.logo { display: flex; align-items: center; gap: 2px; }
.logo-img { height: 92px; width: auto; object-fit: contain; display: block; flex-shrink: 0; margin-right: -14px; }
.logo-text { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.logo-name { font-family: 'Poppins', sans-serif; font-weight: 700; font-size: 1.02rem; color: var(--white); letter-spacing: 0.3px; white-space: nowrap; }
.logo-tagline { font-size: 0.72rem; color: var(--gray-300); letter-spacing: 0.3px; }

.navbar-actions { display: flex; align-items: center; gap: 14px; margin-left: auto; }
/* Hidden by default; shown only on admin pages (body.page-admin) so it
   always renders in this one consistent spot instead of repeating (and
   sometimes clipping) as a page-level heading on every admin template. */
.navbar-admin-label { display: none; color: var(--white); font-weight: 700; font-size: 0.92rem; white-space: nowrap; transition: color 0.2s var(--ease); }
body.page-admin .navbar-admin-label, body.page-team .navbar-admin-label, body.page-supervisor .navbar-admin-label, body.page-operator .navbar-admin-label { display: inline-block; }
@media (hover: hover) { .navbar-admin-label:hover { color: var(--accent); } }
/* Team Head / Supervisor navbar only: dashboard title + logged-in name
   stacked in one column instead of sitting on one line — the role badge
   stays separate, still rendered via .navbar-user below. Higher specificity
   than the plain .navbar-admin-label rule above (two classes vs one) so it
   wins regardless of source order; Admin's own label is untouched. */
.navbar-admin-label--stacked { flex-direction: column; align-items: center; text-align: center; line-height: 1.3; gap: 1px; }
body.page-team .navbar-admin-label.navbar-admin-label--stacked, body.page-supervisor .navbar-admin-label.navbar-admin-label--stacked, body.page-operator .navbar-admin-label.navbar-admin-label--stacked { display: flex; }
.navbar-admin-label-title { color: var(--white); font-weight: 700; font-size: 0.92rem; }
.navbar-admin-label-sub { color: var(--gray-300); font-weight: 500; font-size: 0.74rem; }
.navbar-user { display: flex; flex-direction: row; align-items: center; gap: 8px; margin-right: 4px; }
.navbar-user-name { color: var(--white); font-weight: 600; font-size: 0.88rem; white-space: nowrap; }
.navbar-user-role {
  color: var(--navy-950); background: linear-gradient(135deg, var(--accent), var(--accent-2));
  font-size: 0.66rem; font-weight: 700; letter-spacing: 0.5px; text-transform: uppercase;
  padding: 4px 13px; line-height: 1.4; border-radius: 999px; display: inline-block; width: fit-content;
}

/* Admin pages only (home already has its own finalized hero treatment,
   untouched here): a light dot-texture + warm-edge gradient over the navy
   bar, reusing the exact PSMS "what-we-do" section texture recipe, kept
   low-opacity so it reads as premium texture, not decoration competing
   with the logo/nav text. .navbar itself is already position:sticky, which
   is enough to anchor the inset:0 pseudo-element without extra layout risk. */
body.page-admin .navbar, body.page-team .navbar, body.page-supervisor .navbar, body.page-operator .navbar { background: linear-gradient(120deg, var(--navy-900) 55%, var(--navy-800) 100%); }
body.page-admin .navbar::before, body.page-team .navbar::before, body.page-supervisor .navbar::before, body.page-operator .navbar::before {
  content: ""; position: absolute; inset: 0;
  background-image: radial-gradient(circle, var(--accent-2) 1.6px, transparent 1.8px);
  background-size: 24px 24px; opacity: 0.14;
  -webkit-mask-image: linear-gradient(110deg, white 0%, white 45%, transparent 90%);
  mask-image: linear-gradient(110deg, white 0%, white 45%, transparent 90%);
  pointer-events: none; z-index: 0;
}
body.page-admin .navbar-inner, body.page-team .navbar-inner, body.page-supervisor .navbar-inner, body.page-operator .navbar-inner { position: relative; z-index: 1; }

/* ==========================================================================
   Buttons
   ========================================================================== */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-family: 'Inter', sans-serif; font-weight: 600; font-size: 0.92rem;
  padding: 12px 24px; border-radius: 999px; border: 2px solid transparent; cursor: pointer;
  transition: transform 0.2s var(--ease), box-shadow 0.2s var(--ease), background 0.2s var(--ease), color 0.2s var(--ease);
  white-space: nowrap;
}
.btn i { font-size: 0.85em; }
.btn-primary { background: linear-gradient(135deg, var(--accent-2), var(--accent)); color: var(--white); box-shadow: 0 8px 22px rgba(255, 140, 58, 0.28); }
/* Portal-wide secondary-action style: a light orange tint + orange border
   instead of the old plain/transparent+navy look, so every secondary
   button (Back, Deactivate, etc.) reads as on-brand and "defined" rather
   than flat, without the visual weight of .btn-primary's solid fill — that
   weight is reserved for the one primary action per form (e.g. Apply
   Filters). Applies everywhere a .field/.btn lives, since this is the
   single shared button-family rule, not a per-page override. */
.btn-outline { background: rgba(255, 140, 58, 0.1); border-color: var(--accent); color: var(--accent-2); }
/* Dark-background contexts (navbar Logout, the locked home-page hero's
   Register button) keep their original transparent/white-bordered look —
   explicit here since they'd otherwise inherit the new orange tint above. */
.btn-outline.on-dark { background: transparent; border-color: rgba(255,255,255,0.35); color: var(--white); }
/* Solid navy fill — for a notable-but-not-primary action (Change Team
   Head) that needs real visual weight, distinct from both .btn-primary's
   orange and .btn-outline's light tint. Uses --navy-500, the same
   "clearly blue, not black" shade as the table header. */
.btn-navy { background: var(--navy-500); border-color: var(--navy-500); color: var(--white); }
@media (hover: hover) {
  .btn-navy:hover { background: var(--navy-600); border-color: var(--navy-600); transform: translateY(-2px); }
}
/* Solid muted blue-gray fill — for a low-emphasis navigate-away action
   (Back) that still needs a real fill (not just a border) but shouldn't
   compete with .btn-navy's more vivid blue or .btn-primary's orange.
   --gray-700 already carries a blue undertone, so it reads as "part of
   the same blue family, just quieter" rather than an unrelated gray. */
.btn-muted { background: var(--gray-700); border-color: var(--gray-700); color: var(--white); }
@media (hover: hover) {
  .btn-muted:hover { background: var(--navy-900); border-color: var(--navy-900); transform: translateY(-2px); }
}
.btn-danger { background: var(--danger); color: var(--white); box-shadow: 0 8px 20px rgba(214, 69, 69, 0.25); }
/* Hover-only effects gated behind (hover: hover) — touch devices report
   hover:none, so this fully prevents the classic "hover state gets stuck
   after a tap" bug (e.g. .btn-outline.on-dark:hover's white fill sticking
   on mobile after tapping "New User Registration", making it look bigger/
   bolder than the borderless primary button next to it — live-reproduced
   and confirmed as the actual cause). Desktop mouse users are completely
   unaffected: a mouse always reports hover:hover. */
@media (hover: hover) {
  .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 12px 28px rgba(255, 140, 58, 0.38); }
  .btn-outline:hover { background: linear-gradient(135deg, var(--accent-2), var(--accent)); color: var(--white); border-color: transparent; transform: translateY(-2px); }
  .btn-outline.on-dark:hover { background: var(--white); color: var(--navy-900); }
  .btn-danger:hover { transform: translateY(-2px); background: #b93838; }
}
.btn-sm { padding: 8px 16px; font-size: 0.82rem; }
.btn-block { width: 100%; }
.btn[disabled] { opacity: 0.55; cursor: not-allowed; transform: none !important; }

/* Hero buttons only: subtle 3D lift (dark shadow underneath, not just the
   accent glow), deepening further on hover — scoped to .hero-actions so
   other buttons sitewide (admin Save/Add/etc.) are untouched. */
.hero-actions .btn-primary { box-shadow: 0 4px 14px rgba(5, 10, 23, 0.35), 0 10px 26px rgba(255, 140, 58, 0.3); }
.hero-actions .btn-outline { box-shadow: 0 4px 14px rgba(5, 10, 23, 0.3); }
@media (hover: hover) {
  .hero-actions .btn-primary:hover { box-shadow: 0 8px 20px rgba(5, 10, 23, 0.45), 0 14px 32px rgba(255, 140, 58, 0.4); transform: translateY(-3px); }
  .hero-actions .btn-outline:hover { box-shadow: 0 8px 20px rgba(5, 10, 23, 0.4); }
}

/* ==========================================================================
   Layout shell
   ========================================================================== */
.page-main { flex: 1; padding: 40px 0 64px; }

/* ==========================================================================
   Cards / panels
   ========================================================================== */
.card {
  background: var(--white); border: 1px solid var(--gray-100); border-radius: var(--radius-lg);
  padding: 28px; box-shadow: var(--shadow-sm); transition: box-shadow 0.2s var(--ease), transform 0.2s var(--ease), padding 0.2s var(--ease);
}
.card + .card { margin-top: 24px; }
/* Team Hub's head-select card: once a head is picked, this card's only
   job is letting you switch heads — it doesn't need full card padding
   pushing "Master Report for: X" further down. */
.card--compact { padding: 14px 20px; }
/* A single field inside .filters-bar's auto-fit grid otherwise stretches
   to the entire row width (nothing else to share the row with), which is
   why the Team Head dropdown looked oversized. */
.head-select-field { max-width: 320px; }

/* minmax widened 210->235: with the sidebar now taking horizontal space,
   4 columns at 210px were squeezing the large rupee figures into
   clipping — live-reproduced and fixed; now wraps to fewer per row
   instead of cramming, same as it always should have for narrow cards. */
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 20px; margin-bottom: 28px; }
/* Two-layer shadow (soft wide + tight close), same recipe as the Welcome
   page's cards, just tuned for this light off-white background instead of
   that page's dark navy one — noticeably "raised" by default, not just on
   hover, for a consistent elevated look portal-wide. */
.stat-card {
  background: var(--white); border: 1px solid var(--gray-100); border-radius: var(--radius-lg);
  border-left: 4px solid var(--accent);
  padding: 12px 16px;
  box-shadow: 0 8px 20px rgba(10, 17, 40, 0.1), 0 2px 6px rgba(10, 17, 40, 0.06);
  transition: transform 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
.stat-card:hover { transform: translateY(-4px); box-shadow: 0 14px 32px rgba(10, 17, 40, 0.16), 0 4px 10px rgba(10, 17, 40, 0.08); }
/* Quantity cards (page counts) keep the orange accent; fund/money cards
   switch to the existing --success green so the color itself carries
   meaning (quantity vs. money) instead of being decoration for its own
   sake. Same tokens used everywhere else, no new hues introduced. */
.stat-card--fund { border-left-color: var(--success); }
.stat-icon {
  width: 26px; height: 26px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  background: rgba(255, 140, 58, 0.12); color: var(--accent-2); font-size: 0.75rem; margin-bottom: 6px;
}
.stat-card--fund .stat-icon { background: var(--success-bg); color: var(--success); }
.stat-label { color: var(--gray-700); font-size: 0.78rem; font-weight: 600; letter-spacing: 0.4px; text-transform: uppercase; margin-bottom: 4px; }
.stat-value {
  font-family: 'Poppins', sans-serif; font-weight: 700; font-size: 1.4rem;
  background: linear-gradient(90deg, var(--accent-2), var(--accent)); -webkit-background-clip: text; background-clip: text; color: transparent;
}
.stat-sub { margin-top: 3px; font-size: 0.78rem; color: var(--gray-500); }
.stat-sub.is-inverse { color: var(--danger); }

/* Fixed to the empty bottom-left of the viewport (below the sidebar) on
   Team Hub's head-selected views, instead of sitting inline above the
   Team Head selector — frees that vertical space so the selector (and
   everything below it) shifts up. */
.back-corner { position: fixed; left: 24px; bottom: 24px; z-index: 40; }
/* A fixed bottom-left Back button works on the wide layout, where the left
   gutter beside the content is empty. On a phone the page collapses to one
   column and it lands on top of whatever is at that height — measured on the
   Master Log at 375px, it covered both the "To" date field and the Apply
   Filters button and took their taps. Put it back in the flow there. */
@media (max-width: 640px) {
  .back-corner { position: static; display: inline-flex; margin: 20px 0 4px; }
}

/* ==========================================================================
   Tabs (top-level dashboard sections + sub-tabs) — real <a> navigation,
   not client-side JS state, so URLs stay bookmarkable/refresh-safe.
   ========================================================================== */
/* Admin dashboard: left sidebar nav (replaces the old horizontal .tabs
   row) — dark navy to match the PSMS theme, active tab highlighted with
   the accent gradient. Stacks above the content as a horizontal wrapping
   pill row on narrow screens, since it can't sit beside content there.
   Team Hub and Allotment are expandable .sidebar-group's whose sub-items
   (subtabs) live here now instead of a pill-row in the content area. */
/* margin-left:-24px: .navbar-inner's own padding shorthand (10px 0) zeroes
   out .container's usual 24px horizontal padding, so the navbar logo sits
   flush at the container's outer edge while normal page content (this
   shell included, via .page-main .container) sits 24px further right —
   a pre-existing gap between navbar and page content that a centered
   layout (the home hero) never exposed, but a left-aligned sidebar does.
   Pulling just the admin shell left realigns it with the logo without
   touching the shared navbar/container rules other pages (home) rely on.
   This -24px value is the mobile/base state (unchanged from before).
   On desktop (see the min-width:901px block below) the sidebar instead
   goes fully flush with the true viewport edge, using the
   calc(-1 * (100vw - 100%) / 2) breakout — this negates exactly the
   .container's own centering + padding gap regardless of screen width.
   Verified via getBoundingClientRect on all 4 admin tabs. */
.admin-shell { display: flex; gap: 24px; align-items: flex-start; margin-left: -24px; }
.admin-sidebar {
  width: 220px; flex-shrink: 0; border-radius: var(--radius-lg);
  padding: 16px; box-shadow: var(--shadow-md);
  background: linear-gradient(165deg, var(--navy-800) 0%, var(--navy-900) 55%, var(--navy-950) 100%);
  position: relative; overflow: hidden;
  animation: sidebar-slide-in 0.45s var(--ease) both;
}
/* Plays whenever a sidebar page loads — including arriving from a Welcome
   Dashboard card, which is a real page navigation (not a JS route), so a
   load-time animation is what actually reads as "the sidebar slides in"
   for that transition. Animating transform/opacity only (not width/margin)
   means .admin-content never shifts during it. Skipped for users who've
   asked the OS for reduced motion. */
@keyframes sidebar-slide-in {
  from { transform: translateX(-24px); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .admin-sidebar { animation: none; }
}
/* Same low-opacity dot texture as the navbar, masked to the top-right
   corner only so it reads as subtle depth behind the nav links, not noise
   competing with the (already low-contrast) inactive link text below it. */
.admin-sidebar::before {
  content: ""; position: absolute; inset: 0;
  background-image: radial-gradient(circle, var(--accent-2) 1.4px, transparent 1.6px);
  background-size: 22px 22px; opacity: 0.07;
  -webkit-mask-image: radial-gradient(circle at top right, white 0%, transparent 65%);
  mask-image: radial-gradient(circle at top right, white 0%, transparent 65%);
  pointer-events: none; z-index: 0;
}
.admin-sidebar > * { position: relative; z-index: 1; }
/* Thin separator between each top-level nav item (Team Hub / All Teams
   Report / Allotment / Announcement), so they read as distinct entries
   even when a group is open with sub-links under it. Same treatment on
   the sub-list items (Assign Projects / Update Project Database / ...). */
.admin-sidebar > * + * { border-top: 1px solid rgba(255,255,255,0.16); padding-top: 6px; }
.sidebar-sublist > * + * { border-top: 1px solid rgba(255,255,255,0.1); padding-top: 4px; }
.admin-sidebar a {
  display: flex; align-items: center; gap: 10px; padding: 11px 14px; border-radius: var(--radius-sm);
  color: var(--gray-300); font-weight: 600; font-size: 0.9rem; margin-bottom: 4px;
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
}
.admin-sidebar a:hover { background: rgba(255,255,255,0.08); color: var(--white); }
.admin-sidebar a.active { background: linear-gradient(135deg, var(--accent-2), var(--accent)); color: var(--white); box-shadow: 0 4px 14px rgba(255,140,58,0.3); }
.admin-content { flex: 1; min-width: 0; }

/* Sidebar items locked until a project is selected on the dashboard —
   rendered as <span> (not <a>) so they're structurally non-clickable,
   not just visually dimmed. */
.admin-sidebar .is-disabled {
  display: flex; align-items: center; gap: 10px; padding: 11px 14px; border-radius: var(--radius-sm);
  color: var(--gray-500); font-weight: 600; font-size: 0.9rem; margin-bottom: 4px;
  cursor: not-allowed; opacity: 0.55;
}
.sidebar-locked-hint {
  font-size: 0.74rem; color: var(--gray-400); background: rgba(255,255,255,0.05);
  border-radius: var(--radius-sm); padding: 8px 10px; margin: 0 0 8px;
  display: flex; align-items: center; gap: 6px;
}

/* Unread-notifications count on the sidebar's Notifications link — reuses
   the same margin-left:auto right-alignment trick as .sidebar-caret, since
   .admin-sidebar a is already a flex row. */
.notif-badge {
  margin-left: auto; background: linear-gradient(135deg, var(--accent-2), var(--accent)); color: var(--white);
  font-size: 0.68rem; font-weight: 700; line-height: 1.6; min-width: 18px; text-align: center;
  padding: 0 6px; border-radius: 999px; box-shadow: 0 2px 6px rgba(255, 140, 58, 0.35);
}
.sidebar-parent-link .sidebar-caret { margin-left: auto; font-size: 0.72rem; transition: transform 0.2s var(--ease); }
.sidebar-group.is-open .sidebar-caret { transform: rotate(180deg); }
.sidebar-sublist { display: flex; flex-direction: column; margin: 2px 0 6px 22px; padding-left: 14px; border-left: 2px solid rgba(255,255,255,0.12); }
.sidebar-sublist a {
  padding: 8px 12px; font-size: 0.83rem; font-weight: 600; color: var(--gray-300);
  border-radius: var(--radius-sm); margin-bottom: 2px;
}
.sidebar-sublist a:hover { background: rgba(255,255,255,0.08); color: var(--white); }
.sidebar-sublist a.active { background: rgba(255,140,58,0.18); color: var(--accent); box-shadow: none; }

@media (max-width: 900px) {
  .admin-shell { flex-direction: column; }
  /* Column flex + align-items:flex-start sizes items to fit-content, so a
     table wider than the screen stretched this box and took the whole page
     with it. Pinning the width hands the overflow back to .table-wrap, which
     already knows how to scroll it. */
  .admin-content { width: 100%; min-width: 0; }
  .admin-sidebar { width: 100%; display: flex; flex-wrap: wrap; gap: 6px; padding: 10px; }
  .admin-sidebar a { margin-bottom: 0; padding: 8px 14px; font-size: 0.82rem; }
  .sidebar-group { width: 100%; }
  .sidebar-sublist { flex-direction: row; flex-wrap: wrap; margin-left: 0; padding-left: 0; border-left: none; }
}
/* Desktop-only: sidebar pulled flush to the true page edge, per explicit
   request. Mobile keeps its existing -24px value above. */
@media (min-width: 901px) {
  .admin-shell { margin-left: calc(-1 * (100vw - 100%) / 2); }
}

/* Small breadcrumb heading (e.g. "Allotment › Assign Projects") that
   replaces the old page-level heading now that section titles live in
   the sidebar and the navbar (.navbar-admin-label) instead. */
.page-crumb { font-size: 0.95rem; font-weight: 700; color: var(--navy-900); margin-bottom: 20px; display: flex; align-items: center; gap: 8px; }
.page-crumb .crumb-sep { color: var(--gray-500); font-weight: 400; }
.page-crumb .crumb-current { color: var(--accent-2); }

/* ==========================================================================
   Forms
   ========================================================================== */
.field { margin-bottom: 18px; }
.field label { display: block; font-size: 0.82rem; font-weight: 600; color: var(--navy-900); margin-bottom: 6px; letter-spacing: 0.2px; }
/* border/box-shadow made a touch more visible portal-wide (was gray-100 —
   nearly invisible against the white field background, reading as flat/
   borderless) — still deliberately subtle, just enough to look "defined". */
.field input[type="text"], .field input[type="password"], .field input[type="number"],
.field input[type="date"], .field select, .field textarea {
  width: 100%; font-family: 'Inter', sans-serif; font-size: 0.94rem; color: var(--text-dark);
  background: var(--white); border: 1px solid var(--gray-300); border-radius: var(--radius-sm);
  padding: 11px 14px; box-shadow: 0 1px 3px rgba(10, 17, 40, 0.05);
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 4px rgba(255, 140, 58, 0.14);
}
.field-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; }
.field-hint { font-size: 0.78rem; color: var(--gray-500); margin-top: 5px; }
.form-actions { margin-top: 22px; display: flex; gap: 12px; }

/* ==========================================================================
   Multi-select dropdown (chips-in-a-box, click to open a checkbox panel) —
   a plain <select multiple> can't show selections as chips, so this is a
   small vanilla-JS component (see team_management.html) built from real
   checkboxes underneath, styled to read as one control. */
.multiselect { position: relative; }
.multiselect-trigger {
  width: 100%; min-height: 42px; display: flex; align-items: center; justify-content: space-between;
  gap: 8px; padding: 10px 14px; border: 1px solid var(--gray-300); border-radius: var(--radius-sm);
  background: var(--white); cursor: pointer; font-family: inherit; font-size: 0.85rem; text-align: left;
  box-shadow: 0 1px 3px rgba(10, 17, 40, 0.05); transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
.multiselect-trigger:hover, .multiselect.is-open .multiselect-trigger { border-color: var(--accent); }
.multiselect.is-open .multiselect-trigger { box-shadow: 0 0 0 4px rgba(255, 140, 58, 0.14); }
.multiselect-chips { display: flex; flex-wrap: wrap; gap: 6px; flex: 1; }
.multiselect-placeholder { color: var(--gray-500); }
.multiselect-chip {
  display: inline-flex; align-items: center; gap: 6px; background: var(--info-bg); color: var(--info);
  padding: 3px 8px; border-radius: 999px; font-size: 0.76rem; font-weight: 600; max-width: 220px;
}
.multiselect-chip span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.multiselect-chip-remove { cursor: pointer; font-weight: 700; flex-shrink: 0; }
.multiselect-chip-remove:hover { color: var(--navy-900); }
.multiselect-trigger .fa-chevron-down { color: var(--gray-500); flex-shrink: 0; transition: transform 0.2s var(--ease); }
.multiselect.is-open .fa-chevron-down { transform: rotate(180deg); }
.multiselect-panel {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 50;
  background: var(--white); border: 1px solid var(--gray-300); border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg); max-height: 260px; overflow-y: auto; padding: 6px;
}
.multiselect-option { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: var(--radius-sm); cursor: pointer; font-size: 0.85rem; }
.multiselect-option:hover { background: var(--off-white); }

.filters-bar { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; margin-bottom: 24px; align-items: end; }

/* ==========================================================================
   Tables
   ========================================================================== */
.table-wrap { overflow-x: auto; border-radius: var(--radius-md); border: 1px solid var(--gray-100); box-shadow: var(--shadow-sm); }
table.data-table { width: 100%; border-collapse: collapse; font-size: 0.86rem; white-space: nowrap; }
table.data-table thead th {
  background: linear-gradient(120deg, var(--navy-600), var(--navy-500));
  color: var(--white); text-align: left; padding: 12px 16px;
  font-weight: 600; font-size: 0.75rem; letter-spacing: 0.4px; text-transform: uppercase;
  border-right: 1px solid rgba(255, 255, 255, 0.14);
}
table.data-table thead th:last-child { border-right: none; }
table.data-table tbody td { padding: 11px 16px; border-bottom: 1px solid var(--gray-100); color: var(--text-dark); }
table.data-table tbody tr:hover { background: var(--off-white); }
table.data-table tbody tr:last-child td { border-bottom: none; }
.table-empty { padding: 18px 16px; text-align: center; color: var(--gray-500); font-size: 0.85rem; background: var(--off-white); }

/* "Nothing selected yet" placeholder (e.g. Team Hub before a head is
   chosen) — scoped separately from .table-empty on purpose: this sits in
   a .card on plain off-white page background, so a colored icon here adds
   life without ever touching an actual data table's background/readability. */
.empty-state { text-align: center; padding: 44px 20px; }
.empty-state-icon {
  width: 60px; height: 60px; margin: 0 auto 16px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(135deg, rgba(255, 140, 58, 0.16), rgba(230, 101, 15, 0.08));
  color: var(--accent-2); font-size: 1.5rem;
}
.empty-state-title { font-family: 'Poppins', sans-serif; font-weight: 700; font-size: 1rem; color: var(--navy-900); margin-bottom: 6px; }
.empty-state-text { font-size: 0.85rem; color: var(--gray-500); max-width: 360px; margin: 0 auto; }

.section-title-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }

/* Remove-an-assignment "x" in the assigned-colleges table. Was an inline style
   with padding:0, which measured 13x8px on a phone — far too small for a
   thumb, and this deletes an assignment. Given a real hit area, and a full
   44px one on touch-sized screens. */
.assign-remove-btn {
  background: none; border: none; color: inherit; cursor: pointer;
  font-weight: 700; line-height: 1; font-size: 1rem;
  padding: 6px 9px; border-radius: var(--radius-sm);
  transition: color 0.2s var(--ease), background 0.2s var(--ease);
}
@media (hover: hover) {
  .assign-remove-btn:hover { color: var(--danger); background: var(--danger-bg); }
}
@media (max-width: 640px) {
  .assign-remove-btn { min-width: 44px; min-height: 44px; }
}

/* ==========================================================================
   Modal — a centered popup with a dimmed backdrop (Team Hub's "choose/
   change Team Head" dialog first, reusable for future modals via the same
   data-modal-open/data-modal-close attributes, wired up in app.js).
   ========================================================================== */
.modal-overlay {
  position: fixed; inset: 0; background: rgba(5, 10, 23, 0.55);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  display: none; align-items: center; justify-content: center; z-index: 2000; padding: 24px;
}
.modal-overlay.is-open { display: flex; }
.modal-box {
  background: var(--white); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  width: 100%; max-width: 400px; padding: 28px; position: relative;
  animation: modal-pop 0.2s var(--ease) both;
}
@keyframes modal-pop { from { transform: scale(0.94); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.modal-close-btn {
  position: absolute; top: 14px; right: 14px; width: 30px; height: 30px; border-radius: 50%;
  background: var(--off-white); border: none; color: var(--gray-700); font-size: 0.9rem; cursor: pointer;
  display: flex; align-items: center; justify-content: center; transition: background 0.2s var(--ease), color 0.2s var(--ease);
}
.modal-close-btn:hover { background: var(--gray-100); color: var(--navy-900); }

/* ==========================================================================
   Admin welcome/landing page — shown instead of the sidebar+content shell,
   a single full-height panel so it reads as its own "choose a section"
   screen rather than a regular admin page with content missing.
   Full-bleed edge-to-edge like home.html's .top-section (same breakout
   technique — width:100vw + margin-left:50% + translateX(-50%) — cancels
   out the .container/.page-main padding this is nested inside), instead of
   a bounded rounded card floating in the off-white page background. */
body.page-admin-welcome .page-main, body.page-team-welcome .page-main, body.page-supervisor-welcome .page-main, body.page-operator-welcome .page-main { padding: 0; }
/* Navbar is rendered INSIDE .welcome-hero here (same structural pattern as
   home.html's .top-section), so .hero-bg's curve — a sibling positioned
   absolute/inset:0 within this same box — paints behind the navbar too,
   reaching all the way to its top-right corner as one continuous shape,
   instead of stopping at the (separately-rendered, opaque) navbar's edge. */
.welcome-hero {
  position: relative; overflow: hidden; background: var(--navy-900);
  width: 100vw; margin-left: 50%; transform: translateX(-50%);
  min-height: 100vh; display: flex; flex-direction: column;
}
/* Overrides the dot-textured .navbar background every other admin page
   gets (body.page-admin .navbar) — this page's navbar instead sits
   transparent over .hero-bg's curve, so no dot pattern shows here and the
   curve reads as one unbroken flow from the navbar down through the page. */
body.page-admin.page-admin-welcome .navbar, body.page-team.page-team-welcome .navbar, body.page-supervisor.page-supervisor-welcome .navbar, body.page-operator.page-operator-welcome .navbar { background: transparent; box-shadow: none; border-bottom: 1px solid rgba(255, 255, 255, 0.16); }
body.page-admin.page-admin-welcome .navbar::before, body.page-team.page-team-welcome .navbar::before, body.page-supervisor.page-supervisor-welcome .navbar::before, body.page-operator.page-operator-welcome .navbar::before { display: none; }
/* .hero-bg (the curved orange/yellow swoosh + rings, defined above in the
   home-page-hero section) is reused here verbatim via the same markup/CSS
   — no separate copy, so any future tweak to that shape stays in sync. */
.welcome-hero-body { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; padding: 16px 24px; position: relative; z-index: 1; }
/* margin-bottom (not margin: 0 auto) nudges the centered block up within
   .welcome-hero-body's flex centering — same bias home.html's own
   .hero-content uses, since pure centering reads slightly bottom-heavy.
   Kept modest (not the 100px this briefly was) so the whole dashboard —
   toprow through the 4 cards — fits one 1366x768-ish viewport with no
   scroll; a much bigger value re-introduces the overflow that caused. */
.welcome-hero-inner { text-align: center; max-width: 780px; margin: 0 auto 48px; }
.welcome-icon {
  width: 54px; height: 54px; margin: 0 auto 10px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(135deg, var(--accent-2), var(--accent)); color: var(--white); font-size: 1.4rem;
  box-shadow: 0 10px 26px rgba(255, 140, 58, 0.35);
}
.welcome-hero h1 { color: var(--white); font-size: clamp(1.5rem, 3vw, 2.1rem); margin-bottom: 14px; }
/* Fixed 2 columns (not auto-fit) so the 4 tool cards always read as a
   balanced 2x2 grid instead of reflowing to 3+1 at in-between widths. */
.welcome-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; animation: welcome-grid-in 0.35s var(--ease) both; }
@keyframes welcome-grid-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
/* Keeps the heading's vertical position identical whether a project is
   selected or not — the badge and cards still take up their layout space,
   just invisible, instead of collapsing and shifting everything above them. */
.is-space-reserved { visibility: hidden; pointer-events: none; animation: none; }
@media (max-width: 480px) {
  .welcome-grid { grid-template-columns: 1fr; }
}
/* var(--shadow-md)/--shadow-lg are tuned for cards sitting on the
   off-white page background (a dark, low-opacity navy shadow) — against
   this hero's own dark navy background that shadow all but disappears, so
   these cards get their own darker/more-opaque values instead, tuned to
   actually read as "raised" here. Default state already has real depth;
   hover just deepens it further rather than adding depth from nothing. */
.welcome-card {
  position: relative; background: var(--white); border-radius: var(--radius-lg); padding: 16px 20px;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.35), 0 2px 6px rgba(0, 0, 0, 0.2);
  transition: transform 0.2s var(--ease), box-shadow 0.2s var(--ease);
  color: var(--navy-900); text-decoration: none;
}
/* Same unread-count badge as the sidebar's .notif-badge, pinned to the card's
   corner instead of flowed inline — this card has no sidebar row to sit in. */
.welcome-card-badge {
  position: absolute; top: -8px; right: -8px; background: linear-gradient(135deg, var(--accent-2), var(--accent));
  color: var(--white); font-size: 0.72rem; font-weight: 700; line-height: 1.8; min-width: 22px; text-align: center;
  padding: 0 6px; border-radius: 999px; box-shadow: 0 2px 8px rgba(255, 140, 58, 0.45); border: 2px solid var(--navy-900);
}
.welcome-card:hover { transform: translateY(-6px); box-shadow: 0 20px 40px rgba(0, 0, 0, 0.45), 0 4px 10px rgba(0, 0, 0, 0.25); }
.welcome-card-icon {
  width: 46px; height: 46px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  background: linear-gradient(135deg, rgba(255, 140, 58, 0.14), rgba(230, 101, 15, 0.08));
  color: var(--accent-2); font-size: 1.25rem; transition: background 0.2s var(--ease), color 0.2s var(--ease);
}
@media (hover: hover) {
  .welcome-card:hover .welcome-card-icon { background: linear-gradient(135deg, var(--accent-2), var(--accent)); color: var(--white); }
}
.welcome-card-label { font-family: 'Poppins', sans-serif; font-weight: 700; font-size: 1rem; color: var(--navy-900); }
/* Team Head shell (Phase 1): the 4 cards are visual placeholders only —
   their views don't exist yet, so no href, and styled inert (not hoverable,
   not clickable-looking) rather than a link that would 404. */
.welcome-card.is-disabled { opacity: 0.55; cursor: default; }
.welcome-card.is-disabled:hover { transform: none; box-shadow: 0 10px 24px rgba(0, 0, 0, 0.35), 0 2px 6px rgba(0, 0, 0, 0.2); }

/* Three fully independent pieces, deliberately NOT sharing a row/card:
   1) dropdown top-left, 2) Add Project top-right — a real full-bleed row
   right under the navbar (not just centered within the 780px hero column,
   which .welcome-hero-inner constrains everything else to), 3) "Working on"
   heading on its own line above the 4 cards (still inside the hero column). */
.project-toprow {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 16px; position: relative;
  /* Was z-index:1 — the same as .welcome-hero-body, a LATER sibling, which
     therefore won and sat invisibly (it has no background) over anything this
     row overflows downward. That swallowed every click on the Manage menu's
     options: the panel rendered fine, but hit-testing at its coordinates
     returned .welcome-hero-body. This row must out-rank the hero body for the
     dropdown to be clickable, and z-index:1 here trapped the panel's own
     z-index:60 inside this element's stacking context. */
  z-index: 2;
  padding-top: 14px; padding-bottom: 14px;
  /* .navbar-inner (this row uses the same .container) zeroes out .container's
     24px horizontal padding itself (padding: 10px 0), so the logo/Logout
     already sit flush at .container's true edge — match that here (0, not
     .container's own 24px) so the two rows are edge-aligned by default. */
  padding-left: 0; padding-right: 0;
}
.project-picker-form { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.project-picker-form label { color: var(--gray-300); font-weight: 600; font-size: 0.85rem; }
.project-picker-form select {
  min-width: 260px; padding: 10px 12px; border-radius: var(--radius-sm);
  border: 1px solid rgba(255,255,255,0.2); background: var(--white); color: var(--navy-900);
}
.add-project-big-btn {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 14px 28px; font-size: 1rem; font-weight: 700; white-space: nowrap;
}
.project-toprow-actions { display: flex; flex-direction: row; align-items: center; flex-wrap: wrap; gap: 14px; }
.toprow-action-link {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 0.8rem; font-weight: 700; color: var(--navy-900);
  padding: 10px 18px; border-radius: 999px;
  border: 1px solid var(--white); background: var(--white);
  /* Also used on the Manage <button>, which unlike an <a> inherits neither
     the page font nor a pointer cursor from the reset. */
  font-family: inherit; line-height: 1.4; cursor: pointer;
}
.toprow-action-link:hover { color: var(--accent); background: var(--white); box-shadow: 0 4px 14px rgba(0,0,0,0.25); }

/* Dropdown menu — groups the secondary system-level actions (Manage Team
   Heads / Manage Projects) behind one trigger, so only the primary action
   (Add Project) stays a standalone button on the landing row. The trigger
   reuses .toprow-action-link's pill; the panel deliberately matches
   .multiselect-panel (same border, radius, shadow and option hover) so it
   reads as the same family of control as Select Project sitting beside it.
   Behaviour (toggle, outside-click, Escape) is app.js's shared
   [data-menu-toggle] wiring, same approach as the modal helpers. */
.menu-dropdown { position: relative; }
.menu-dropdown .menu-caret { font-size: 0.62rem; transition: transform 0.2s var(--ease); }
.menu-dropdown.is-open .menu-caret { transform: rotate(180deg); }
.menu-dropdown.is-open > .toprow-action-link { color: var(--accent); box-shadow: 0 4px 14px rgba(0,0,0,0.25); }
.menu-dropdown-panel {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 60; min-width: 220px;
  background: var(--white); border: 1px solid var(--gray-300); border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg); padding: 6px;
}
.menu-dropdown:not(.is-open) .menu-dropdown-panel { display: none; }
.menu-dropdown-panel a {
  display: flex; align-items: center; gap: 10px; padding: 9px 11px; border-radius: var(--radius-sm);
  font-size: 0.85rem; font-weight: 600; color: var(--navy-900); white-space: nowrap;
}
.menu-dropdown-panel a:hover, .menu-dropdown-panel a:focus-visible { background: var(--off-white); color: var(--accent-2); }
.menu-dropdown-panel a i { width: 16px; text-align: center; color: var(--gray-500); }
.menu-dropdown-panel a:hover i, .menu-dropdown-panel a:focus-visible i { color: var(--accent-2); }
.working-on-heading {
  display: inline-flex; align-items: center; gap: 7px;
  background: rgba(255,140,58,0.16); color: var(--accent);
  border-radius: 999px; padding: 7px 14px; font-size: 0.82rem; font-weight: 600;
  margin: 0 0 14px;
}
.working-on-heading strong { color: var(--white); font-weight: 700; }

/* Mobile card layout for tables that carry a per-row action button (project
   delete, announcement deactivate) — horizontal scroll is an acceptable,
   standard pattern for wide read-only data tables, but stranding an
   interactive button off-screen past a long text column is a real
   usability problem, live-verified via mobile screenshot. Opt-in via
   .data-table--stack; each <td> needs a data-label attribute (empty for
   the action cell, which needs no label). */
@media (max-width: 768px) {
  .data-table--stack thead { display: none; }
  .data-table--stack, .data-table--stack tbody, .data-table--stack tr, .data-table--stack td { display: block; width: 100%; }
  .data-table--stack tr { margin-bottom: 12px; border: 1px solid var(--gray-100); border-radius: var(--radius-md); overflow: hidden; }
  .data-table--stack td { border-bottom: 1px solid var(--gray-100); padding: 10px 16px; white-space: normal; }
  .data-table--stack td:last-child { border-bottom: none; }
  .data-table--stack td[data-label]:not([data-label=""])::before {
    content: attr(data-label); display: block; font-size: 0.68rem; font-weight: 700;
    text-transform: uppercase; color: var(--gray-500); margin-bottom: 2px;
  }
}

/* Assign Project's "Assigned College(s)" cell holds several badges (each
   with its own Remove button) that must wrap onto more lines instead of
   forcing the whole table wider — overrides table.data-table's default
   white-space:nowrap via a class (not inline) so .data-table--stack's own
   display:block on mobile still applies untouched; the actual flex-wrap
   lives on a plain inner div, so there's no display conflict either way. */
.assigned-colleges-cell { white-space: normal; }

/* ==========================================================================
   Badges
   ========================================================================== */
.badge { display: inline-flex; align-items: center; gap: 5px; padding: 4px 12px; border-radius: 999px; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.3px; text-transform: uppercase; }
.badge-success { background: var(--success-bg); color: var(--success); }
.badge-danger { background: var(--danger-bg); color: var(--danger); }
.badge-warning { background: var(--warning-bg); color: var(--warning); }
.badge-info { background: var(--info-bg); color: var(--info); }

/* ==========================================================================
   Toasts (replaces browser alert())
   ========================================================================== */
.toast-container { position: fixed; top: 20px; right: 20px; z-index: 3000; display: flex; flex-direction: column; gap: 10px; max-width: 360px; }
.toast {
  background: var(--white); border-radius: var(--radius-md); box-shadow: var(--shadow-lg);
  padding: 14px 18px; display: flex; align-items: flex-start; gap: 10px; font-size: 0.87rem; font-weight: 500;
  border-left: 4px solid var(--info); animation: toast-in 0.25s var(--ease);
}
.toast.is-leaving { animation: toast-out 0.2s var(--ease) forwards; }
.toast i { margin-top: 2px; }
.toast-success { border-left-color: var(--success); }
.toast-success i { color: var(--success); }
.toast-error { border-left-color: var(--danger); }
.toast-error i { color: var(--danger); }
.toast-warning { border-left-color: var(--warning); }
.toast-warning i { color: var(--warning); }
.toast-info i { color: var(--info); }
@keyframes toast-in { from { opacity: 0; transform: translateX(24px); } to { opacity: 1; transform: translateX(0); } }
@keyframes toast-out { from { opacity: 1; } to { opacity: 0; transform: translateX(24px); } }

/* ==========================================================================
   Loading spinner overlay (shown briefly on full-page navigations so a
   click never looks like a blank/frozen screen while the next page loads)
   ========================================================================== */
.page-loading-overlay {
  position: fixed; inset: 0; background: rgba(245, 247, 251, 0.75); backdrop-filter: blur(2px);
  z-index: 4000; display: flex; align-items: center; justify-content: center;
  opacity: 0; pointer-events: none; transition: opacity 0.15s var(--ease);
}
.page-loading-overlay.is-active { opacity: 1; pointer-events: all; }
.spinner { width: 42px; height: 42px; border-radius: 50%; border: 4px solid var(--gray-100); border-top-color: var(--accent); animation: spin 0.7s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

.inline-spinner { width: 16px; height: 16px; border-width: 2px; display: inline-block; vertical-align: middle; }

/* ==========================================================================
   Home page hero — ported verbatim from PSMS_WEBSITE's .hero/.hero-bg/
   .product-hero (software-development.html) and .news-ticker (index.html),
   approved via preview on 2026-09-01. .top-section breaks out of the
   .container/.page-main ancestors added by base.html so the curved
   background can run full-bleed edge to edge like PSMS's own hero does.
   ========================================================================== */
.top-section {
  position: relative; overflow: hidden; background: var(--navy-900);
  width: 100vw; margin-left: 50%; transform: translateX(-50%);
  /* Fills exactly page-main's flexed height (see body.page-home rules
     above) — height:100%, not its own min-height:100vh, since its parent
     (.container, not itself a flex container) is what actually got
     stretched to the available space. */
  height: 100%; display: flex; flex-direction: column; min-height: 0;
}
/* Navbar becomes part of this shared background layer only when nested
   inside .top-section (i.e. only on the home page, via _navbar.html
   included here) — everywhere else .navbar keeps its normal solid
   navy-900 + shadow from the base rule above. */
.top-section .navbar { background: transparent; box-shadow: none; border-bottom: 1px solid rgba(255,255,255,0.16); }

.hero-bg {
  position: absolute; inset: 0; overflow: hidden; z-index: 0; pointer-events: none;
  /* Wider tonal range (700→950, vs the original 800→600) so the depth
     actually reads instead of looking flat next to the bright curve. */
  background: linear-gradient(165deg, var(--navy-700) 0%, var(--navy-950) 100%);
}
.hero-bg-curve { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.hero-bg-curve--tall { display: none; }
@media (max-width: 768px) {
  .hero-bg-curve--wide { display: none; }
  .hero-bg-curve--tall { display: block; }
}
.hero-rings { position: absolute; right: -3%; bottom: -3%; width: 0; height: 0; pointer-events: none; }
.hero-ring { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); border-radius: 50%; aspect-ratio: 1; border: 1.5px solid rgba(230, 101, 15, 0.45); }
.hero-ring--1 { width: clamp(40px, 6vw, 70px); }
.hero-ring--2 { width: clamp(74px, 10vw, 120px); border-color: rgba(230, 101, 15, 0.32); }
.hero-ring--3 { width: clamp(108px, 14vw, 170px); border-color: rgba(230, 101, 15, 0.2); }

.hero { position: relative; z-index: 1; padding-top: 0; background: transparent; flex: 1; display: flex; flex-direction: column; min-height: 0; }
.product-hero { padding-bottom: 0; }
.product-hero-inner { position: relative; z-index: 2; text-align: center; flex: 1; display: flex; align-items: center; justify-content: center; min-height: 0; }
/* margin-bottom nudges the centered content up within .product-hero-inner's
   flex centering (was reading bottom-heavy: 142px space above vs 190px
   below once measured live). */
.hero-content { position: relative; z-index: 4; max-width: 640px; margin: 0 auto 48px; padding-bottom: 0; }
.hero h1 { font-size: clamp(1.9rem, 3.6vw, 2.7rem); color: var(--white); font-weight: 700; margin-bottom: 14px; }
.hero-tagline {
  font-size: 0.82rem; font-weight: 600; color: var(--gray-300); letter-spacing: 1.5px;
  text-transform: uppercase; max-width: 560px; margin: 0 auto 32px;
}
.hero-actions { display: flex; flex-wrap: wrap; gap: 16px; justify-content: center; }

/* News ticker — PSMS's exact component (index.html + style.css ~L730-833):
   flag-shaped badge on the left, auto-scrolling marquee in the middle,
   pause/play toggle on the right. Sits at the bottom of the hero section. */
.news-ticker {
  position: relative; z-index: 2; display: flex; align-items: stretch; min-height: 38px; background: var(--navy-800);
  /* Clear separation from the orange curve above — desktop and mobile alike. */
  border-top: 3px solid var(--navy-950); box-shadow: 0 -2px 8px rgba(0,0,0,0.25);
}
.news-ticker-badge {
  flex-shrink: 0; display: flex; align-items: center; gap: 6px;
  background: var(--accent); color: var(--white); font-weight: 700; font-size: 0.72rem;
  letter-spacing: 0.5px; text-transform: uppercase; padding: 0 22px 0 16px;
  clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 100%, 0 100%); white-space: nowrap;
}
.news-ticker-badge i { font-size: 0.7rem; }
.news-ticker-viewport {
  flex: 1; min-width: 0; overflow: hidden; display: flex; align-items: center; padding: 0 16px;
  -webkit-mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 20px), transparent);
  mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 20px), transparent);
}
.news-ticker-track { display: flex; align-items: center; width: max-content; animation: news-ticker-scroll 11s linear infinite; }
@keyframes news-ticker-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.news-ticker.is-paused .news-ticker-track { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) { .news-ticker-track { animation-play-state: paused; } }
.news-ticker-item { color: rgba(255,255,255,0.85); font-size: 0.82rem; font-weight: 500; white-space: nowrap; padding: 0 18px; }
.news-ticker-sep { color: var(--accent); opacity: 0.65; font-size: 0.8rem; }
.news-ticker-toggle {
  flex-shrink: 0; width: 42px; -webkit-appearance: none; appearance: none;
  display: flex; align-items: center; justify-content: center; background: none; border: none;
  box-shadow: none; outline: none; color: var(--white); font-size: 0.72rem; cursor: pointer;
  transition: color 0.2s var(--ease);
}
.news-ticker-toggle:hover { color: var(--accent); }
.news-ticker-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
@media (max-width: 768px) {
  .news-ticker {
    min-height: 34px;
    /* Desktop keeps the plain border+shadow from the base rule above —
       this is stronger/richer (inset + outer shadow combined) since the
       flat border alone read as too subtle against the orange curve at
       mobile sizes. Mobile-only, live-verified via screenshot. */
    box-shadow: inset 0 3px 6px rgba(0,0,0,0.25), 0 -3px 8px rgba(0,0,0,0.3);
  }
  .news-ticker-badge { font-size: 0.66rem; padding: 0 18px 0 12px; }
  .news-ticker-item { font-size: 0.76rem; padding: 0 14px; }
  .news-ticker-viewport { padding: 0 10px; }
  .news-ticker-toggle { width: 36px; font-size: 0.64rem; }
}

/* ==========================================================================
   Auth pages (login / register)
   ========================================================================== */
.auth-shell { max-width: 440px; margin: 56px auto; position: relative; z-index: 1; }
.auth-shell h1 { text-align: center; margin-bottom: 24px; font-size: 1.5rem; }

/* Login/Register drop the off-white page background for a navy base with a
   subtle 45-degree orange stripe texture, so the white form card reads as
   floating over branded backdrop instead of sitting on a flat page.
   Deliberately near-threshold: a 1.5px hairline every 24px at 6% alpha, so
   at a glance the page reads as plain navy and the texture only resolves on
   a second look. An earlier 3px/16px/18% pass read as bold stripes and
   competed with the form — if this ever gets retuned, alpha is the knob that
   goes loud fastest.
   Uses the shared --navy-900 / --accent tokens (not one-off hexes) so these
   two pages stay in lockstep with the dashboards if the palette ever moves. */
body.page-auth { background: var(--navy-900); }
/* Painted by a position:fixed pseudo-element rather than a background on
   body itself: the register form is tall enough to scroll, and a fixed
   layer keeps the stripe rhythm locked to the viewport (no scroll-drift,
   no seam where the body box ends). z-index:-1 puts it above the canvas
   but below every in-flow element, so nothing needs re-stacking. */
body.page-auth::before {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background-color: var(--navy-900);
  /* Literal-rgba first, color-mix second: the fallback keeps the texture
     rendering on anything pre-Chrome-111, while the color-mix line (which
     wins wherever it parses) derives the stripe straight from --accent, so
     a future palette change carries through here automatically instead of
     silently leaving a stale hardcoded orange behind. */
  background-image: repeating-linear-gradient(
    45deg,
    rgba(255, 140, 58, 0.06) 0px, rgba(255, 140, 58, 0.06) 1.5px,
    transparent 1.5px, transparent 24px
  );
  background-image: repeating-linear-gradient(
    45deg,
    color-mix(in srgb, var(--accent) 6%, transparent) 0px,
    color-mix(in srgb, var(--accent) 6%, transparent) 1.5px,
    transparent 1.5px, transparent 24px
  );
}
/* Heading/back-link sit directly on the navy now, so they invert; the card
   itself stays white and gets the deep shadow (--shadow-* are all tuned for
   light backgrounds and read as nothing against navy). */
body.page-auth .auth-shell h1 { color: var(--white); }
body.page-auth .auth-shell h1 i { color: var(--accent); }
body.page-auth .auth-shell .card {
  border-color: rgba(255, 255, 255, 0.14);
  box-shadow: 0 28px 64px rgba(3, 8, 22, 0.55), 0 6px 16px rgba(3, 8, 22, 0.35);
}
.auth-back { text-align: center; margin-top: 16px; }
body.page-auth .auth-back { color: rgba(255, 255, 255, 0.72); font-size: 0.88rem; }
body.page-auth .auth-back a { color: rgba(255, 255, 255, 0.82); font-weight: 600; transition: color 0.2s var(--ease); }
body.page-auth .auth-back a:hover { color: var(--accent); }

/* --- Auth card: premium/compact treatment ---------------------------------
   All of this is scoped to body.page-auth on purpose. .card / .field / .btn
   are the portal's shared families used by every admin/team/operator screen,
   and those were signed off as final — so nothing here leaks past the two
   auth pages. */

/* Compact pass: the register form is the longest form in the portal, and at
   the shared 18px field rhythm it ran taller than the viewport. Everything
   below tightens by ~25% while keeping label->input and field->field
   proportions intact, so it reads compact rather than cramped. */
body.page-auth .auth-shell .card { padding: 26px 26px 24px; }
body.page-auth .auth-shell .field { margin-bottom: 13px; }
body.page-auth .auth-shell .field label { margin-bottom: 5px; font-size: 0.8rem; }
body.page-auth .auth-shell .field-row { gap: 13px; }
body.page-auth .auth-shell .form-actions { margin-top: 20px; }
body.page-auth .auth-shell .field input[type="text"],
body.page-auth .auth-shell .field input[type="password"],
body.page-auth .auth-shell .field select {
  padding: 9px 13px;
  /* Two-layer: a tight 1px contact shadow plus a slightly wider one, so the
     field reads as sitting ON the card rather than being cut out of it. */
  box-shadow: 0 1px 2px rgba(10, 17, 40, 0.04), 0 1px 3px rgba(10, 17, 40, 0.09);
}
/* Ring tightened 4px->3px and warmed slightly: at the smaller field padding
   above, the wider ring visually collided with the neighbouring field in the
   Mobile/Adhar row. */
body.page-auth .auth-shell .field input:focus,
body.page-auth .auth-shell .field select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(255, 140, 58, 0.18), 0 1px 3px rgba(10, 17, 40, 0.09);
}

/* The card itself: radius pulled to 16px (the shared --radius-lg is 22px —
   see note below), a three-stop shadow, and the SaaS-standard accent rule
   along the top edge. */
body.page-auth .auth-shell .card {
  position: relative;
  overflow: hidden;               /* clips the ::before bar to the radius */
  border-radius: 16px;
  border-color: rgba(255, 255, 255, 0.14);
  /* Tuned for a DARK backdrop. The usual light-page recipe
     (0 8px 24px rgba(0,0,0,.12)) is invisible on navy — a shadow can't
     darken something that's already near-black — so the separation here
     comes from a much deeper, wider falloff plus the 1px top highlight in
     the last stop, which is what actually makes the edge read. */
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.55) inset,
    0 2px 6px rgba(3, 8, 22, 0.30),
    0 10px 24px rgba(3, 8, 22, 0.40),
    0 28px 64px rgba(3, 8, 22, 0.55);
}
body.page-auth .auth-shell .card::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px;
  background: linear-gradient(90deg, var(--accent-2), var(--accent));
}

/* Button depth: .btn-primary already carries the orange gradient + glow
   portal-wide; this adds the inset top highlight and a dark ground shadow
   underneath, which is what separates a "flat gradient" from a button that
   looks pressable. Same recipe the home-page hero buttons use. */
body.page-auth .auth-shell .btn-primary {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.30),
    0 2px 4px rgba(10, 17, 40, 0.18),
    0 8px 20px rgba(255, 140, 58, 0.32);
}
@media (hover: hover) {
  body.page-auth .auth-shell .btn-primary:hover {
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.30),
      0 4px 8px rgba(10, 17, 40, 0.22),
      0 12px 28px rgba(255, 140, 58, 0.42);
  }
}

/* --- Auth pages: fit one viewport, no scroll -------------------------------
   Measured before this pass (1440x900 window, innerHeight 765): register's
   document was 1005px — 240px of overflow — and even login overflowed by 6px,
   so both pages scrolled. The height was going to fixed chrome, not content:
   .page-main's 40/64 padding + .auth-shell's 56px top AND bottom margin =
   216px of dead space that couldn't shrink.

   Replaced with the same flex-column technique body.page-home already uses:
   body owns the column, .page-main takes the leftover with flex:1, and the
   shell centres inside it. Centring is done with `margin: auto` on the flex
   item rather than align-items:center on purpose — if a viewport is ever too
   short for the form, auto margins collapse and the page scrolls normally,
   whereas align-items:center would push the top of the card out of reach. */
body.page-auth { display: flex; flex-direction: column; }
body.page-auth .page-main { flex: 1; min-height: 0; padding: 20px 0; display: flex; }
body.page-auth .page-main > .container { flex: 1; display: flex; }
body.page-auth .auth-shell { width: 100%; margin: auto; }

/* Spacing trimmed to buy back the last ~30px: the shell was 584px against
   560px of available height, so the flex change alone wasn't enough. */
body.page-auth .auth-shell h1 { margin-bottom: 14px; }
body.page-auth .auth-shell .field { margin-bottom: 11px; }
body.page-auth .auth-back { margin-top: 10px; }
/* Compact footer, same treatment (and same reason) as body.page-home's:
   36px of vertical padding for one line of copyright is the single largest
   non-content block on the page — 40px back for free. */
body.page-auth .footer { padding: 16px 0; flex-shrink: 0; margin-top: 0; }

/* Short viewports (a 1366x768 laptop leaves ~640px of innerHeight, well under
   what the register form needs even after the trims above). Rather than let
   it scroll there, shrink the two things that are pure chrome at that size —
   the 92px navbar logo and the card's own padding. Scoped by height, so a
   normal desktop window never sees it. */
@media (max-height: 820px) {
  body.page-auth .logo-img { height: 64px; margin-right: -8px; }
  body.page-auth .auth-shell .card { padding: 20px 22px 18px; }
  body.page-auth .auth-shell .field { margin-bottom: 9px; }
  body.page-auth .auth-shell .form-actions { margin-top: 14px; }
  body.page-auth .auth-shell h1 { font-size: 1.35rem; margin-bottom: 10px; }
}

/* Very short viewports (measured: a 1280x720 window leaves innerHeight 627,
   where register still overflowed by 77px even after the 820px tier above).
   The footer goes first — every logged-in dashboard already hides it
   (body.page-admin .footer et al), so dropping a one-line copyright strip to
   keep a form reachable is the established trade here, not a new idea. */
@media (max-height: 700px) {
  body.page-auth .footer { display: none; }
  body.page-auth .page-main { padding: 12px 0; }
  body.page-auth .logo-img { height: 54px; }
  body.page-auth .auth-shell h1 { font-size: 1.25rem; margin-bottom: 8px; }
  body.page-auth .auth-shell .field { margin-bottom: 7px; }
  body.page-auth .auth-shell .field input[type="text"],
  body.page-auth .auth-shell .field input[type="password"],
  body.page-auth .auth-shell .field select { padding: 7px 12px; }
  body.page-auth .auth-back { margin-top: 6px; }
}

/* ==========================================================================
   Footer
   ========================================================================== */
.footer { background: var(--navy-950); padding: 36px 0; margin-top: auto; }
/* Admin dashboard pages don't repeat the full copyright footer — home page
   keeps it (body has no page-home class on any admin page, verified via
   the extension: body.className was "" on /admin/team-hub/, confirming
   this selector is the correct, safe way to scope it there only). */
body.page-admin .footer, body.page-team .footer, body.page-supervisor .footer, body.page-operator .footer { display: none; }
.footer-inner { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; }
.footer p { color: var(--gray-500); font-size: 0.82rem; }
.footer a { color: var(--gray-300); }
.footer a:hover { color: var(--accent); }
/* Home page: compact footer sharing exactly 100vh with the hero (see
   body.page-home rules near the top of this file) — flex-shrink:0 so it
   never gets squeezed, margin-top:0 since .page-main's own flex:1 already
   handles the space distribution (auto margin here would fight it). */
body.page-home .footer { padding: 16px 0; flex-shrink: 0; margin-top: 0; }

@media (max-width: 768px) {
  /* Mobile home page: text wrapping to 2 lines reads bulky at the desktop
     padding/font-size — shrink both so it's a clean, intentional-looking
     compact strip instead. */
  body.page-home .footer { padding: 16px 0; }
  body.page-home .footer p { font-size: 0.72rem; }
  body.page-home .footer-inner { gap: 4px; }
}

/* ==========================================================================
   Phone-width touch sizing. Measured on a real 375px viewport: sidebar links
   rendered 38px tall, their parent rows 37px, the project picker 39px and the
   table download buttons 36px — all under the 44px that reliably takes a
   thumb. These only apply where a pointer is coarse AND the screen is narrow,
   so desktop density is untouched.
   ========================================================================== */
@media (max-width: 640px) {
  .admin-sidebar a,
  .admin-sidebar .is-disabled,
  .sidebar-parent-link { min-height: 44px; padding-top: 10px; padding-bottom: 10px; }
  .sidebar-sublist a { min-height: 44px; }
  .btn-sm { padding: 11px 18px; font-size: 0.84rem; }
  .field input[type="text"], .field input[type="password"], .field input[type="number"],
  .field input[type="date"], .field select, .field textarea,
  .project-picker-form select,
  .multiselect-trigger { min-height: 44px; }
  /* A checkbox row in the college picker is a tap target too. */
  .multiselect-option { min-height: 44px; }
}

@media (max-width: 640px) {
  .navbar-user { display: none; }
  .navbar-admin-label { display: none; }
  /* The stacked Team Head/Supervisor/Operator variant is set to display:flex
     by a `body.page-team .navbar-admin-label.navbar-admin-label--stacked`
     rule, i.e. (0,3,1). An override here has to carry the same body-class
     prefix or it simply loses the cascade — which is exactly what happened
     before: the label kept rendering at phone widths and pushed
     .navbar-actions past the viewport on every inner page. */
  body.page-team .navbar-admin-label.navbar-admin-label--stacked,
  body.page-supervisor .navbar-admin-label.navbar-admin-label--stacked,
  body.page-operator .navbar-admin-label.navbar-admin-label--stacked { display: none; }
  /* Logo text has no width limit and genuinely overlaps navbar-actions
     (Logout button / role badge) at narrow widths — not a @media-gated
     issue, a real overflow bug, live-reproduced. Same truncation pattern
     PSMS's own marketing site already uses at its own narrow breakpoint. */
  .logo-tagline { display: none; }
  .logo-text { min-width: 0; }
  .logo-name { max-width: 44vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.85rem; }
  .logo-img { height: 44px; margin-right: -4px; }
  .navbar-actions { flex-shrink: 0; }
}
