/* Digital School Portal - Mobile Layer
   ============================================================================
   Loaded AFTER portal.css. Everything here is scoped to the mobile breakpoints
   (<=767px unless stated) so desktop rendering is untouched.

   Breakpoints
     <= 480px   phone (portrait)
     481-767px  large phone / phone landscape / small tablet
     768-1023px tablet
     >= 1024px  desktop

  Addresses the shared mobile failure modes:
    1. Shell navigation and sidebar previously changed at different widths,
      leaving a fixed sidebar offset beside mobile navigation.
    2. `min-height: 100vh` is taller than the visible viewport on mobile
        browsers with dynamic toolbars, pushing content under the chrome.
     3. The fixed 60px topbar wrapped its action group onto two lines on narrow
        phones and clipped it.
     4. No overflow guard let one oversized child scroll the whole page, which
        detaches fixed chrome from the content.
   ============================================================================ */

/* ==========================================================================
   1. SCROLL / VIEWPORT GUARD
   ========================================================================== */
html {
  /* dvh tracks the dynamic browser toolbars; vh does not. */
  min-height: 100vh;
  min-height: 100dvh;
}

@media (max-width: 767px) {
  html,
  body {
    max-width: 100%;
    overflow-x: clip; /* keeps fixed chrome attached to the content */
  }

  body {
    /* The page itself must never pan horizontally; wide content opts into a
       scroll container (.portal-table-wrapper / .portal-scroll-x).

       break-word, never `anywhere`, anywhere in this file: `anywhere` is the
       only value that participates in min-content sizing, so it drops every
       flex item's minimum to ONE CHARACTER. A tab bar or button row then
       shrinks to a stack of per-character columns (measured at 390px).
       break-word still breaks an over-long token as a last resort, which is
       all the guard was ever meant to do. */
    overscroll-behavior-x: none;
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
    overflow-wrap: break-word;
  }

  body :where(p, span, a, button, label, li, th, td, h1, h2, h3, h4) {
    min-width: 0;
    overflow-wrap: break-word;
  }

  .portal-main-column {
    min-height: 100vh;
    min-height: 100dvh;
  }

  .portal-main-area {
    /* Replaces the 640px rule in portal.css so landscape phones and 6.7"
       devices get the same treatment. 5.75rem clears the bottom nav. */
    margin-top: 3.75rem;
    padding: 0.875rem !important;
    padding-bottom: calc(5.75rem + env(safe-area-inset-bottom, 0px)) !important;
  }

  /* Keep shell behavior consistent with the 767px navigation breakpoint.
     The drawer is always the full labelled column: --sb-rail collapsing is a
     desktop-only idea (sidebar.css gates it behind min-width:768px) and the
     !important width beats the rail width portal.css would otherwise apply. */
  .portal-sidebar {
    transform: translateX(-100%);
    transition: transform 0.2s ease;
    position: fixed;
    z-index: 50;
    width: var(--sb-wide, 250px) !important;
    height: 100dvh;
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }

  .portal-sidebar.open {
    transform: translateX(0);
  }

  .portal-sidebar .sb-panel {
    border-right: 0;
    box-shadow: 4px 0 24px rgba(0, 0, 0, 0.35);
  }

  /* The drawer is opened with a thumb, so its rows get the same 44px floor this
     file enforces for buttons (sidebar.css sizes them at 40px for a mouse). */
  .portal-sidebar .sb-link {
    min-height: 2.75rem;
  }

  .portal-layout-root .portal-main-column,
  .portal-layout-root.sidebar-collapsed .portal-main-column {
    margin-left: 0 !important;
    min-width: 0;
  }

  .portal-topbar,
  .portal-layout-root.sidebar-collapsed .portal-topbar {
    left: 0 !important;
  }

  .portal-main-area,
  .portal-main-area > *,
  .portal-card,
  .portal-modal {
    min-width: 0;
    max-width: 100%;
    box-sizing: border-box;
  }

  .portal-main-area :where(p, span, a, button, label, li, th, td, h1, h2, h3, h4) {
    overflow-wrap: break-word;
  }

  body .grid.grid-cols-2 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  body .grid.grid-cols-3,
  body .grid.grid-cols-4,
  body .grid.grid-cols-5,
  body .grid.grid-cols-6 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  form .grid,
  form .flex {
    min-width: 0;
  }

  form .flex.gap-3,
  form .flex.gap-4 {
    flex-wrap: wrap;
  }

  /* Zero specificity on purpose. `.portal-main-area button` (0,1,1) out-specifies
     any single Tailwind utility, so it silently overrode the whitespace-nowrap
     on tab bars and action rows and squeezed each label onto its own line;
     :where() keeps this as the default a phone applies when the template has
     not stated otherwise, and lets the authored utility win. */
  :where(.portal-btn-primary,
         .portal-btn-secondary,
         .portal-btn-accent,
         .portal-btn-danger,
         .portal-btn-success,
         .portal-btn-warning,
         .portal-main-area button,
         .portal-main-area input[type="submit"],
         .portal-main-area a[role="button"]) {
    min-height: 2.75rem;
    max-width: 100%;
    white-space: normal;
    overflow-wrap: break-word;
  }

  body button,
  body input[type="submit"],
  body a[role="button"] {
    min-height: 2.75rem;
    max-width: 100%;
    white-space: normal;
    overflow-wrap: break-word;
  }

  .theme-toggle-btn,
  .portal-icon-btn,
  .tb-btn,
  .portal-sidebar-group-toggle,
  .portal-sidebar-collapse-btn,
  .portal-sidebar-mobile-close,
  .portal-sidebar-toggle {
    min-width: 2.75rem;
    min-height: 2.75rem;
  }

  .portal-toast-container {
    top: calc(4.25rem + env(safe-area-inset-top, 0px));
    right: 0.75rem;
    left: 0.75rem;
    width: auto;
    max-width: none;
  }

  .portal-modal {
    max-height: calc(100dvh - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px));
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  .portal-scroll-x,
  .portal-table-wrapper,
  .table-responsive,
  .portal-main-area [class*="overflow-x-auto"] {
    min-width: 0;
    max-width: 100%;
    overscroll-behavior-x: contain;
    scrollbar-width: thin;
  }

  .portal-scroll-x > table,
  .portal-table-wrapper > table,
  .table-responsive > table,
  .portal-main-area [class*="overflow-x-auto"] > table {
    width: max-content;
    min-width: 100%;
    max-width: none;
  }
}

@media (max-width: 400px) {
  body .grid.grid-cols-2,
  body .grid.grid-cols-3,
  body .grid.grid-cols-4,
  body .grid.grid-cols-5,
  body .grid.grid-cols-6 {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* A single horizontal scroll container for anything genuinely wide. */
.portal-scroll-x {
  width: 100%;
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  padding-bottom: 0.25rem;
  scrollbar-width: thin;
  scrollbar-color: var(--color-primary) var(--color-surface-2);
}

/* ==========================================================================
   2. TOPBAR - one row, truncated title, overflow menu
   ========================================================================== */
@media (max-width: 767px) {
  .portal-topbar {
    left: 0;
    right: 0;
    height: 3.75rem;
    min-height: 3.75rem;
    /* 3.75rem clears the fixed 44px hamburger (12px inset + 44px = 56px). */
    padding: 0 0.5rem 0 3.75rem;
    gap: 0.5rem;
    flex-wrap: nowrap;
    overflow: hidden;
  }

  .portal-topbar > .flex-1 {
    min-width: 0;
  }

  .portal-topbar h1 {
    font-size: 1rem;
    line-height: 1.25rem;
  }

  /* Only the essentials stay in the bar; everything else lives in the sheet. */
  .portal-topbar .portal-topbar-overflow-hide {
    display: none !important;
  }

  /* Both lines live in the same wrapper, so one rule hides name + role. */
  .tb-user-text {
    display: none;
  }

  .portal-user-name {
    display: none;
  }

  .portal-user-avatar {
    width: 1.9rem;
    height: 1.9rem;
    flex: 0 0 1.9rem;
  }
}

/* 44x44 tap target for the hamburger (was ~24px). */
.portal-sidebar-toggle {
  min-width: 2.75rem;
  min-height: 2.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.25rem;
  line-height: 1;
}

/* The icon macro emits a viewBox-only <svg>; unsized it defaults to 300x150. */
.portal-sidebar-toggle svg {
  width: 1.25rem;
  height: 1.25rem;
}

/* The drawer only exists below 768px (see §1), so the hamburger and its
   backdrop must disappear at the same breakpoint, not Tailwind's 640px. */
@media (min-width: 768px) {
  .portal-sidebar-toggle,
  .portal-sidebar-backdrop {
    display: none;
  }
}

/* ==========================================================================
   3. BOTTOM NAVIGATION
   ========================================================================== */
@media (max-width: 767px) {
  .portal-bottom-nav {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    /* auto height + safe-area padding: a fixed height squeezed the labels on
       notched phones. */
    height: auto;
    min-height: 4.25rem;
    padding: 0.25rem 0.25rem calc(0.25rem + env(safe-area-inset-bottom, 0px));
    z-index: 55;
    background-color: var(--color-surface);
    border-top: 1px solid var(--color-border-strong, var(--color-border));
    box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.25);
    align-items: stretch;
    justify-content: space-around;
    gap: 0;
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
  }

  .portal-bottom-nav-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    flex: 1 1 0;
    gap: 0.15rem;
    min-width: 0;
    min-height: 2.75rem;
    max-width: 6rem;
    padding: 0.35rem 0.15rem;
    border-radius: 0.75rem;
    color: var(--color-text-faint);
    text-decoration: none;
    font-size: 0.625rem;
    font-weight: 500;
    letter-spacing: 0.01em;
    position: relative;
    transition: color 0.15s ease, background-color 0.15s ease;
  }

  .portal-bottom-nav-item:hover,
  .portal-bottom-nav-item:focus-visible {
    color: var(--color-primary);
    background-color: var(--color-surface-tint);
    text-decoration: none;
  }

  .portal-bottom-nav-item.active {
    color: var(--color-primary);
  }

  .portal-bottom-nav-item.active::before {
    content: '';
    position: absolute;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 1.75rem;
    height: 0.2rem;
    background-color: var(--color-primary);
    border-radius: 0 0 0.25rem 0.25rem;
  }

  .portal-bottom-nav-item:active {
    transform: scale(0.94);
  }

  /* SVG icons inherit text colour, so the active tab is brand-coloured
     automatically. Emoji could not do this and rendered at different widths
     per operating system. */
  .portal-bottom-nav-icon {
    display: block;
    width: 1.4rem;
    height: 1.4rem;
    flex: 0 0 1.4rem;
  }

  .portal-bottom-nav-icon svg {
    width: 100%;
    height: 100%;
    display: block;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.9;
    stroke-linecap: round;
    stroke-linejoin: round;
  }

  .portal-bottom-nav-label {
    display: block;
    max-width: 4.5rem;
    font-size: 0.625rem;
    line-height: 1.05;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .portal-bottom-nav-badge {
    position: absolute;
    top: 0.1rem;
    right: 50%;
    transform: translateX(1.05rem);
    min-width: 1.05rem;
    height: 1.05rem;
    padding: 0 0.2rem;
    border-radius: 999px;
    background: var(--color-danger);
    color: #fff;
    font-size: 0.5625rem;
    font-weight: 700;
    line-height: 1.05rem;
    text-align: center;
  }

  .portal-bottom-nav-badge[hidden],
  .portal-bottom-nav-badge:empty {
    display: none;
  }
}

/* ==========================================================================
   4. SHEETS (bottom sheet: "More" nav, filters, confirmations)
   ========================================================================== */
.portal-sheet-backdrop,
.portal-nav-sheet-backdrop {
  position: fixed;
  inset: 0;
  z-index: 80;
  background: rgba(0, 0, 0, 0.5);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}

.portal-sheet,
.portal-nav-sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 81;
  max-height: 88vh;
  max-height: 88dvh;
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border-top-left-radius: 1rem;
  border-top-right-radius: 1rem;
  box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.4);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  animation: portalSheetUp 0.18s ease-out;
}

@keyframes portalSheetUp {
  from { transform: translateY(12px); opacity: 0.6; }
  to { transform: translateY(0); opacity: 1; }
}

.portal-sheet-handle {
  width: 2.5rem;
  height: 0.25rem;
  border-radius: 999px;
  background: var(--color-border-strong, var(--color-text-faint));
  opacity: 0.5;
  margin: 0.5rem auto 0.25rem;
  flex: 0 0 auto;
}

/* ── Swipe down to close ─────────────────────────────────────────────────────
   portal.js translates the sheet with the finger and closes it past a
   threshold. pan-y lets the sheet body scroll normally while a drag that
   starts on the handle is delivered to JS as a gesture, not a scroll, and
   overscroll-behavior keeps a pull at the top of the list from chaining to
   the page underneath (which would look like the whole app rubber-banding). */
.portal-sheet,
.portal-nav-sheet {
  overscroll-behavior: contain;
  touch-action: pan-y;
}

.portal-sheet-handle {
  position: relative;
  touch-action: none;
}

/* The bar is 40x4 px; this invisible pad is what makes it grabbable. */
.portal-sheet-handle::before {
  content: "";
  position: absolute;
  inset: -14px -48px;
  border-radius: 999px;
}

.portal-sheet-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.5rem 1rem 0.75rem;
  border-bottom: 1px solid var(--color-border-inner, var(--color-border));
  flex: 0 0 auto;
}

.portal-sheet-body {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 0.75rem 1rem calc(1rem + env(safe-area-inset-bottom, 0px));
  flex: 1 1 auto;
}

.portal-sheet-close {
  min-width: 2.75rem;
  min-height: 2.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  background: transparent;
  color: var(--color-text);
  font-size: 1.35rem;
  line-height: 1;
}

/* The "More" sheet: role menu groups plus account actions. */
.portal-nav-sheet-group + .portal-nav-sheet-group {
  margin-top: 1rem;
}

.portal-nav-sheet-group-title {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-faint);
  margin-bottom: 0.5rem;
}

.portal-nav-sheet-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.5rem;
}

@media (max-width: 360px) {
  .portal-nav-sheet-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.portal-nav-sheet-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-height: 4.5rem;
  padding: 0.625rem 0.4rem;
  border: 1px solid var(--color-border);
  border-radius: 0.75rem;
  background: var(--color-surface-2);
  color: var(--color-text);
  text-align: center;
  text-decoration: none;
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1.15;
}

.portal-nav-sheet-tile:active {
  transform: scale(0.97);
}

.portal-nav-sheet-tile.active {
  border-color: var(--color-primary);
  color: var(--color-primary);
}

.portal-nav-sheet-tile svg {
  width: 1.5rem;
  height: 1.5rem;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.portal-nav-sheet-lock {
  font-size: 0.6rem;
  font-weight: 700;
  color: var(--color-warning, #d97706);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* ==========================================================================
   5. FORMS - no iOS zoom, comfortable tap targets
   ========================================================================== */
@media (max-width: 767px) {
  /* Safari zooms the whole page when a focused control's font-size is below
     16px; utilities such as .text-xs on inputs are common in this codebase. */
  input,
  select,
  textarea,
  .portal-input,
  .portal-select {
    font-size: max(16px, 1em) !important;
  }

  input[type="checkbox"],
  input[type="radio"] {
    width: 1.15rem;
    height: 1.15rem;
  }

  .portal-btn-primary,
  .portal-btn-secondary,
  .portal-btn-accent,
  .portal-btn-danger {
    min-height: 2.75rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
  }

  /* Stack the common two-up button rows into full-width actions. */
  .portal-action-row {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    width: 100%;
  }

  .portal-action-row > * {
    width: 100%;
  }

  .portal-pagination {
    align-items: stretch;
    flex-direction: column;
    gap: 0.75rem;
    width: 100%;
    min-width: 0;
  }

  .portal-pagination__summary {
    font-size: 0.8125rem;
    line-height: 1.25rem;
  }

  .portal-pagination__controls {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    width: 100%;
    min-width: 0;
  }

  .portal-pagination__controls > a,
  .portal-pagination__controls > button {
    flex: 0 0 auto;
    min-height: 2.75rem;
    white-space: nowrap;
  }

  .portal-pagination__numbers {
    flex: 1 1 auto;
    justify-content: center;
    min-width: 0;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
  }

  .portal-pagination__numbers > button {
    flex: 0 0 2.75rem;
    min-height: 2.75rem;
  }
}

/* Sticky action bar used by report / detail views on mobile. */
.portal-sticky-actions {
  position: sticky;
  bottom: calc(4.5rem + env(safe-area-inset-bottom, 0px));
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  margin: 0 -0.25rem 0.75rem;
  border-radius: 0.75rem;
  background: color-mix(in srgb, var(--color-surface) 92%, transparent);
  border: 1px solid var(--color-border);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

/* ==========================================================================
   6. TABLES - readable rows on phones instead of clipped columns
   ========================================================================== */
@media (max-width: 767px) {
  .broadsheet-subject-heading {
    writing-mode: horizontal-tb !important;
    text-orientation: mixed !important;
    transform: none !important;
    max-height: none !important;
    min-width: 7rem !important;
    white-space: normal;
    overflow-wrap: break-word;
  }

  /* Keep table semantics; wide grids use a bounded horizontal scroll region. */
  table,
  .table,
  .portal-table {
    display: table;
    white-space: normal;
    overflow: visible;
  }

  .portal-table-wrapper,
  .table-responsive {
    width: 100%;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    scrollbar-width: thin;
    scrollbar-color: var(--color-primary) var(--color-surface-2);
  }

  .portal-scroll-x::-webkit-scrollbar,
  .portal-table-wrapper::-webkit-scrollbar,
  .table-responsive::-webkit-scrollbar,
  .portal-main-area [class*="overflow-x-auto"]::-webkit-scrollbar {
    height: 0.5rem;
  }

  .portal-scroll-x::-webkit-scrollbar-thumb,
  .portal-table-wrapper::-webkit-scrollbar-thumb,
  .table-responsive::-webkit-scrollbar-thumb,
  .portal-main-area [class*="overflow-x-auto"]::-webkit-scrollbar-thumb {
    background: var(--color-primary);
    border-radius: 999px;
  }

    /* ── Opt-in card rows ─────────────────────────────────────────────────
      Templates opt in with data-mobile-layout="cards"; wide and bespoke grids
      retain table semantics and use their horizontal scroll region. */
  table.portal-table--stack {
    display: block;
    width: 100% !important;
    border: none;
    box-shadow: none;
    background: transparent;
    margin-bottom: 0.5rem;
  }

  table.portal-table--stack thead {
    display: none;
  }

  table.portal-table--stack tbody,
  table.portal-table--stack tr,
  table.portal-table--stack td {
    display: block;
    width: 100%;
  }

  table.portal-table--stack tr {
    margin-bottom: 0.625rem;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--color-border);
    border-radius: 0.75rem;
    background: var(--color-surface);
    box-shadow: var(--shadow-1);
  }

  table.portal-table--stack td {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.35rem 0 !important;
    border-bottom: 1px solid var(--color-border-inner, var(--color-border));
    font-size: 0.875rem;
    text-align: right;
  }

  /* Responsive utility classes remain authoritative inside card rows. */
  table.portal-table--stack td.hidden {
    display: none !important;
  }

  table.portal-table--stack td:last-child {
    border-bottom: 0;
  }

  table.portal-table--stack td::before {
    content: attr(data-label);
    flex: 0 0 42%;
    max-width: 42%;
    text-align: left;
    font-weight: 600;
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--color-text-faint);
  }

  /* A cell with no label (action buttons, nested cards) spans the full row. */
  table.portal-table--stack td:not([data-label])::before,
  table.portal-table--stack td[data-label=""]::before {
    display: none;
  }

  table.portal-table--stack td:not([data-label]) {
    justify-content: flex-start;
    text-align: left;
  }

  /* Tables without a scroll wrapper get their own scroll container. */
  table.portal-table--scroll {
    display: block;
    width: 100% !important;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
  }

  /* Empty-state rows must not keep the label gutter. */
  table.portal-table--stack td[colspan] {
    display: block;
    text-align: center;
  }
}

/* ==========================================================================
   7. MODALS -> bottom sheets  |  TOASTS clear of the topbar
   ========================================================================== */
@media (max-width: 767px) {
  .portal-modal-overlay {
    align-items: flex-end;
  }

  .portal-modal {
    width: 100% !important;
    max-width: 100% !important;
    max-height: 88vh;
    max-height: 88dvh;
    overflow-y: auto;
    border-radius: 1rem 1rem 0 0;
    padding: 1.25rem 1rem calc(1.25rem + env(safe-area-inset-bottom, 0px));
  }

  .portal-toast-container {
    top: calc(3.75rem + 0.5rem);
    left: 0.75rem;
    right: 0.75rem;
    align-items: stretch;
  }

  .portal-toast {
    width: 100%;
    max-width: 100%;
  }
}

/* ==========================================================================
   8. PAGE-FRAME HELPERS
   ========================================================================== */
@media (max-width: 767px) {
  /* Full-bleed edge-to-edge cards / image bands on phones. */
  .portal-page-narrow-bleed {
    margin-left: -0.875rem;
    margin-right: -0.875rem;
    border-radius: 0;
  }

  /* Horizontal snap scroller for KPI strips and quick-action rows. */
  .portal-snap-x {
    display: flex;
    gap: 0.625rem;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    margin: 0 -0.875rem;
    padding: 0 0.875rem 0.25rem;
  }

  .portal-snap-x > * {
    flex: 0 0 auto;
    scroll-snap-align: start;
    min-width: 9.5rem;
  }
}

/* ==========================================================================
   9. OFFLINE BANNER
   ========================================================================== */
.portal-offline-banner {
  position: fixed;
  left: 0;
  right: 0;
  top: 3.75rem;
  z-index: 54;
  display: none;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.875rem;
  font-size: 0.78rem;
  font-weight: 500;
  line-height: 1.35;
  background: color-mix(in srgb, var(--color-warning) 22%, var(--color-surface));
  border-bottom: 1px solid var(--color-warning);
  color: var(--color-text);
}

body.portal-offline .portal-offline-banner {
  display: flex;
}

@media (max-width: 767px) {
  body.portal-offline .portal-main-area {
    padding-top: 3rem !important;
  }
}

/* ==========================================================================
   10. DEDICATED PRINT VIEW (?print_view=1)
   ========================================================================== */
.portal-print-view {
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--color-bg);
  padding-bottom: 2rem;
}

.portal-print-view-main {
  max-width: 60rem;
  margin: 0 auto;
  padding: 0.75rem;
}

.print-view-bar {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  padding: 0.625rem 0.75rem;
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  box-shadow: var(--shadow-1);
}

.print-view-bar-title {
  flex: 1 1 12rem;
  min-width: 0;
  font-size: 0.8rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.print-view-tip {
  margin: 0.625rem 0 0.875rem;
  padding: 0.625rem 0.75rem;
  border-radius: 0.625rem;
  border: 1px solid var(--color-border);
  background: var(--color-surface-2);
  font-size: 0.78rem;
  line-height: 1.45;
  color: var(--color-text-muted);
}

@media (max-width: 767px) {
  .portal-print-view-main {
    padding: 0.5rem 0.5rem 1rem;
  }

  .print-view-tip {
    font-size: 0.75rem;
  }
}

/* ==========================================================================
   11. SMALL-SCREEN UTILITIES  (+ print resets)
   ========================================================================== */
@media (max-width: 767px) {
  .hide-mobile {
    display: none !important;
  }

  .show-mobile {
    display: initial;
  }

  .stack-mobile {
    display: flex;
    flex-direction: column;
    gap: 0.625rem;
    align-items: stretch;
  }

  .portal-card {
    padding: 0.875rem;
  }

  /* .nt-modal must keep its bars flush: sidebar.css zeroes portal-card's
     padding for the modal surface, and this file loads after it. */
  .portal-card.nt-modal {
    padding: 0;
  }

  /* Never let a long word or URL widen the page beyond the screen. */
  .portal-main-area {
    overflow-wrap: break-word;
  }
}

/* ==========================================================================
   12. WRAPPING AND PADDING - buttons, headers, tabs, tables
   Measured at 360 / 390 / 430px against the real shell. Every rule here fixes
   something the probe could see: a label broken mid-word, a tab bar squeezed
   to one character per column, or a table cell with no room to breathe.
   ========================================================================== */
@media (max-width: 767px) {
  /* A horizontal scroller exists so its children can keep their full label.
     Without this the flex algorithm shrinks each tab to its (now sane, but
     still narrow) min-content and the bar reads as vertical text. */
  .portal-main-area [class*="overflow-x-auto"] > * {
    flex-shrink: 0;
  }

  /* Tab bars: one consistent height, snap to the leading edge, and no
     scrollbar furniture — the bar is a control, not a document. */
  .portal-main-area [class*="overflow-x-auto"] {
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    scroll-snap-type: x proximity;
    padding-bottom: 2px; /* stops the active tab's own shadow being clipped */
  }

  .portal-main-area [class*="overflow-x-auto"]::-webkit-scrollbar {
    display: none;
  }

  .portal-main-area [class*="overflow-x-auto"] > * {
    scroll-snap-align: start;
  }

  /* A row of page buttons wraps instead of squeezing every label to three
     characters; min-width keeps each button on its own full line of text. */
  .portal-main-area .flex:has(> .portal-btn-primary),
  .portal-main-area .flex:has(> .portal-btn-secondary),
  .portal-main-area .flex:has(> .portal-btn-danger),
  .portal-main-area .flex:has(> .portal-btn-success),
  .portal-main-area .flex:has(> .portal-btn-warning) {
    flex-wrap: wrap;
  }

  .portal-main-area .flex > .portal-btn-primary,
  .portal-main-area .flex > .portal-btn-secondary,
  .portal-main-area .flex > .portal-btn-accent,
  .portal-main-area .flex > .portal-btn-danger,
  .portal-main-area .flex > .portal-btn-success,
  .portal-main-area .flex > .portal-btn-warning {
    flex: 0 1 auto;
    min-width: min(max-content, 100%);
    padding: 0.5rem 0.875rem;
    text-align: center;
  }

  /* Page headers: the glyph stays with the words it labels, and a long title
     wraps under itself instead of pushing the actions out of the row. */
  .portal-main-area h1,
  .portal-main-area h2,
  .portal-main-area h3 {
    min-width: 0;
    overflow-wrap: break-word;
  }

  .portal-main-area h1 {
    font-size: 1.5rem;
    line-height: 1.25;
    gap: 0.5rem;
  }

  .portal-main-area h1 .gi,
  .portal-main-area h2 .gi {
    width: 1.15em;
    height: 1.15em;
  }

  /* Table cells: a thumb-sized target and enough padding that two lines of
     text in one cell do not touch the cell above. */
  .portal-main-area th,
  .portal-main-area td {
    padding: 0.5rem 0.625rem;
    overflow-wrap: break-word;
  }

  .portal-main-area th {
    font-size: 0.6875rem;
    line-height: 1.3;
    letter-spacing: 0.02em;
  }

  /* Row actions are the one place where the 44px floor above is wrong: four
     of them at 44px turn a table row into a screen. 2.5rem is still a thumb
     target and keeps the row scannable. On a phone the cluster also wraps —
     the row cannot widen, so the actions stack instead. */
  .row-actions {
    gap: 0.375rem;
    flex-wrap: wrap;
  }

  .row-actions .portal-btn-primary,
  .row-actions .portal-btn-secondary,
  .row-actions .portal-btn-accent,
  .row-actions .portal-btn-danger,
  .row-actions .portal-btn-success,
  .row-actions .portal-btn-warning {
    min-height: 2.5rem;
    padding: 0.4rem 0.7rem;
    flex: 0 1 auto;
    min-width: min(max-content, 100%);
  }

  /* Quick Access tiles keep two columns on a phone, so the chip and the padding
     shrink slightly and the subtitle wraps inside its own column. */
  .qa-card { padding: 0.75rem; gap: 0.55rem; }
  .qa-ico { width: 1.85rem; height: 1.85rem; font-size: 1rem; }

  /* Forms: one control per row. auto-fit already collapses when 14rem does not
     fit twice, and this makes it explicit — a date, select or password field
     half a screen wide is unreadable. */
  .form-grid { grid-template-columns: 1fr; }

  /* A checkbox row's tap target should be the whole line, not the 17px box. */
  .fld-box { min-height: 2.5rem; align-items: center; }

  /* The headshot stacks above its picker instead of squeezing both into a row. */
  .fld-photo-row { flex-direction: column; }

  /* The required-fields note takes a line of its own; beside the buttons it
     squeezes both tap targets below a thumb width at 390px. */
  .portal-sticky-actions { flex-wrap: wrap; }
  .portal-sticky-actions .actions-note { flex: 1 1 100%; }

  /* Cancel and Save split the sticky bar evenly; a right-aligned pair is a
     miss-target for a thumb. */
  .portal-sticky-actions .portal-btn-primary,
  .portal-sticky-actions .portal-btn-secondary { flex: 1 1 auto; }
}

@media (min-width: 768px) {
  .show-mobile {
    display: none !important;
  }
}

/* The print stylesheets must win over every small-screen tweak above. */
@media print {
  .hide-mobile {
    display: revert !important;
  }

  .show-mobile {
    display: revert !important;
  }

  .portal-snap-x,
  .portal-sticky-actions {
    display: block !important;
    position: static !important;
    overflow: visible !important;
    margin: 0 !important;
    padding: 0 !important;
    border: 0 !important;
    background: transparent !important;
  }

  .portal-snap-x > * {
    min-width: 0 !important;
    flex: none !important;
  }

  html,
  body {
    overflow-x: visible !important;
  }

  .portal-main-area {
    margin-top: 0 !important;
    padding: 0 !important;
  }
}
