/* =========================================================================
   web.css — Desktop page chrome (groups: public, mylibrary, admin)
   iDev Digital Library Portal — Wireframe Pack v2 ("refined institutional")

   Two shells share one design system at two densities:
     .portal   — public portal / My Library. Roomier, editorial.
     .console  — admin console. Compact, information-dense, a tool
                 someone uses all day.

   v2 craft rules honoured here (see AUTHORING-CONTRACT.md §5, §6 and the
   headers of tokens.css / components.css):
   - Logical properties ONLY. RTL mirrors purely from <html dir="rtl">.
   - letter-spacing only ever via --tracking-* (Arabic resets them).
   - Weights via --fw-*; heights via --ctl-h-*; layering via the one
     --z-* scale; elevation via --shadow-*; overlays via --scrim.
   - No hard-coded colours / radii / font sizes / spacing / motion.
     The only literals left are structural hardware-ish measurements
     that are not spacing at all (sidebar width, chrome bar heights,
     brand-mark size, off-canvas travel) and the section rhythms above
     the top of the --sp ramp, expressed as calc() over it.
   - Brand colour as TEXT is always --primary-ink (tokens.css §3);
     --primary itself is a fill role and is never used as a foreground.
   - The wireframe toolbar (guide.css) publishes --wf-bar-h on :root;
     every sticky offset below accounts for it so product chrome never
     hides under the tool bar. Fallback 0px keeps screens working if a
     page is ever loaded without guide.css.
   ========================================================================= */

/* =========================================================================
   1. PUBLIC PORTAL SHELL
   ========================================================================= */

.portal {
  display: flex;
  flex-direction: column;
  min-height: calc(100vh - var(--wf-bar-h, 0px));
  background: var(--bg);
  color: var(--text);
  font-size: var(--fs-md);

  /* The page column, declared once. Every section of the portal — hero,
     main, footer and the top bar — aligns to these two numbers, so they
     live here rather than being repeated per component. --portal-max is a
     structural page measurement, not spacing; --portal-gutter IS spacing
     and stays on the --sp ramp (§3 narrows it on small viewports). */
  --portal-max: 1200px;
  --portal-gutter: var(--sp-6);
}

/* --- 1.1 Container ------------------------------------------------------ */

.portal-container {
  width: 100%;
  max-width: var(--portal-max, 1200px);
  margin-inline: auto;
  padding-inline: var(--portal-gutter, var(--sp-6));
}

/* --- 1.2 Top bar (sticky) ----------------------------------------------- */
/* A crisp instrument, not a plain white slab: a slightly translucent
   surface with a backdrop blur (falls back to solid --surface where
   unsupported) closed by a single precise hairline. No shadow — the
   hairline IS the edge; a shadow here would read as floating chrome. */

/* The bar is full-bleed (surface, blur and hairline run edge to edge) while
   its CONTENT sits in the page column. The bar therefore hosts the column
   itself, via a centring padding, instead of delegating it to a wrapper
   element: every screen writes .portal-brand / .portal-nav / .portal-actions
   as direct children of <header class="portal-topbar">, so any geometry that
   lives on an inner wrapper is geometry that never applies. Padding beats
   max-width here because max-width on the bar would clip the background. */

.portal-topbar {
  position: sticky;
  inset-block-start: var(--wf-bar-h, 0px);
  z-index: var(--z-sticky, 100);
  display: flex;
  align-items: center;
  gap: var(--portal-gutter, var(--sp-6));
  min-block-size: 60px;          /* chrome bar height, not spacing */
  /* = the page column: gutter, plus whatever slack is left once the column
     has taken its --portal-max. Matches .portal-container to the pixel. */
  padding-inline: calc(
    max(0px, (100% - var(--portal-max, 1200px)) / 2)
    + var(--portal-gutter, var(--sp-6))
  );
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  -webkit-backdrop-filter: blur(12px) saturate(1.4);
  backdrop-filter: blur(12px) saturate(1.4);
  border-block-end: var(--border-w) solid var(--border);
}

@supports not (backdrop-filter: blur(1px)) {
  .portal-topbar { background: var(--surface); }
}

/* Tolerated variant: a screen that DOES wrap its bar contents in a
   .portal-container. The bar already supplies the column, so the wrapper
   must not apply it a second time — it just passes the flex row through. */
.portal-topbar > .portal-container {
  display: flex;
  align-items: center;
  gap: var(--portal-gutter, var(--sp-6));
  flex: 1 1 auto;
  min-width: 0;
  max-width: none;
  padding-inline: 0;
}

/* Brand lockup — mark + name (+ optional sub-line). The tight leading
   and the small negative tracking make the two text lines read as one
   optically-set unit rather than two stacked paragraphs. */
.portal-brand {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  color: var(--text);
  text-decoration: none;
  flex-shrink: 0;
}

.portal-brand:hover { text-decoration: none; }

/* Mark: navy tile with the same machined inner highlight as
   .btn--primary, so the two read as one family. */
.portal-brand-mark {
  inline-size: 34px;             /* fixed mark size, not spacing */
  block-size: 34px;
  border-radius: var(--r-md);
  background: var(--primary);
  color: var(--primary-fg);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), var(--shadow-xs);
}

.portal-brand-name {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-tight-2);
  line-height: var(--lh-tight);
  color: var(--text);
}

.portal-brand-sub {
  display: block;
  margin-block-start: 2px;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-regular);
  color: var(--text-muted);
}

/* Primary navigation — quiet at rest, a real state when active:
   brand-soft fill + brand ink + a half-step up in weight. Hover stays
   neutral (surface, not brand) so colour keeps its meaning. */
.portal-nav {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  min-width: 0;
}

.portal-nav a {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-block-size: var(--ctl-h-md);
  padding-block: 0;
  padding-inline: var(--sp-3);
  border-radius: var(--r-sm);
  color: var(--text-muted);
  text-decoration: none;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  white-space: nowrap;
  transition: background-color var(--t-fast), color var(--t-fast);
}

.portal-nav a:hover {
  color: var(--text);
  background: var(--surface-2);
  text-decoration: none;
}

.portal-nav a:active {
  background: var(--bg-sunken);
}

.portal-nav a:focus-visible {
  outline: 2px solid var(--focus-ring, var(--text));
  outline-offset: -2px;
}

.portal-nav a.is-active,
.portal-nav a[aria-current="page"] {
  color: var(--primary-ink);
  background: var(--primary-soft);
  font-weight: var(--fw-semibold);
}

/* Trailing actions (search shortcut, language, sign-in). */
.portal-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-inline-start: auto;
  flex-shrink: 0;
}

/* Collapsed-nav menu button. Hidden on wide viewports; shown under 860px. */
.portal-menu-btn { display: none; }

/* --- 1.3 Hero ------------------------------------------------------------ */
/* Compact and confident: a whisper of brand tint (vertical gradient +
   centred radial only — no horizontal direction, so LTR and RTL are
   pixel-identical) with the search field as the focal instrument.
   The old hero burned ~120px of padding on a weak headline; this one
   spends its height on the search control instead. */

/* The hero is the first thing a client looks at, and v2's was a flat wash
   with a headline floating in it — competent and forgettable. It now
   carries the ruling of a page: faint horizontal rules on the body
   leading, fading out before they reach the content. Same idea as the
   keyline signature — the ruled margin of a manuscript, which is the
   material this product is actually about — and it costs one pseudo
   element and no images.

   The rules sit near the threshold of visibility on purpose. They should
   register as texture; if you notice them as lines, they are too strong. */
.portal-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background:
    radial-gradient(120% 140% at 50% 0%,
      color-mix(in srgb, var(--primary) 9%, transparent) 0%,
      transparent 62%),
    linear-gradient(to bottom,
      color-mix(in srgb, var(--primary) 4%, var(--surface)),
      var(--bg));
  border-block-end: var(--border-w) solid var(--border);
  padding-block: var(--sp-12) var(--sp-12);
}

.portal-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: repeating-linear-gradient(
    to bottom,
    transparent 0 27px,
    color-mix(in srgb, var(--primary) 15%, transparent) 27px 28px
  );
  -webkit-mask-image: linear-gradient(to bottom,
    rgba(0, 0, 0, 0.42) 0%, rgba(0, 0, 0, 0.13) 46%, transparent 76%);
  mask-image: linear-gradient(to bottom,
    rgba(0, 0, 0, 0.42) 0%, rgba(0, 0, 0, 0.13) 46%, transparent 76%);
}

/* A tenant-coloured rule down the leading edge, continuing the signature
   the primary cards carry. Logical, so it mirrors without a second rule. */
.portal-hero::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  inline-size: 3px;
  background: linear-gradient(to bottom,
    var(--primary), color-mix(in srgb, var(--primary) 18%, transparent));
}

/* Display-set title: the one place the portal uses the display step.
   Tracking comes from the token, which Arabic resets to normal. */
.portal-hero h1 {
  font-size: var(--fs-display);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-display);
  line-height: var(--lh-heading);
  color: var(--text);
}

/* Supporting line under the title stays quiet and measured. */
.portal-hero h1 + p,
.portal-hero .section-sub {
  font-size: var(--fs-md);
  color: var(--text-muted);
  max-inline-size: 62ch;
}

/* The search field is the page's centre of gravity: one size up from
   every other control, a hairline-stronger border, and the only
   at-rest --shadow-md in the portal. */
.portal-hero .searchbar {
  min-block-size: calc(var(--ctl-h-lg) + var(--sp-3));   /* 52px */
  padding-inline: var(--sp-4);
  border-radius: var(--r-lg);
  border-color: var(--border-strong);
  box-shadow: var(--shadow-md);
}

.portal-hero .searchbar .btn {
  min-block-size: var(--ctl-h-lg);
}

/* --- 1.4 Main + footer --------------------------------------------------- */

.portal-main {
  flex: 1;
  padding-block: var(--sp-10) calc(var(--sp-12) + var(--sp-4));
}

.portal-footer {
  background: var(--surface);
  border-block-start: var(--border-w) solid var(--border);
  padding-block: var(--sp-10) var(--sp-6);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.portal-footer-grid {
  display: grid;
  grid-template-columns: 1.4fr repeat(3, 1fr);
  gap: var(--sp-10);
}

.portal-footer-col { min-width: 0; }

/* Column headers as caps eyebrows — the same label voice as table
   headers and stat labels, so the footer belongs to the system. */
.portal-footer-h {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--text);
  margin-block-end: var(--sp-3);
}

.portal-footer-links {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--sp-2);
}

.portal-footer-links a {
  color: var(--text-muted);
  text-decoration: none;
  transition: color var(--t-fast);
}

.portal-footer-links a:hover {
  color: var(--primary-ink);
  text-decoration: underline;
}

/* Tenant social channels (offer: "Client Social Media Links"). */
.portal-social {
  display: flex;
  gap: var(--sp-2);
  margin-block-start: var(--sp-4);
}

.portal-social a {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--ctl-h-md);
  block-size: var(--ctl-h-md);
  border-radius: var(--r-full);
  border: var(--border-w) solid var(--border);
  color: var(--text-muted);
  background: var(--surface);
  transition: color var(--t-fast), border-color var(--t-fast),
              background var(--t-fast);
}

.portal-social a:hover {
  color: var(--primary-ink);
  border-color: var(--border-strong);
  background: var(--surface-hover);
}

.portal-social a:focus-visible {
  outline: 2px solid var(--focus-ring, var(--text));
  outline-offset: 2px;
}

/* Legal strip under the columns. */
.portal-footer-legal {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  flex-wrap: wrap;
  justify-content: space-between;
  margin-block-start: var(--sp-10);
  padding-block-start: var(--sp-5);
  border-block-start: var(--border-w) solid var(--border);
  font-size: var(--fs-xs);
  color: var(--text-faint);
}

/* =========================================================================
   2. ADMIN CONSOLE SHELL
   -------------------------------------------------------------------------
   Grid areas keep the markup flat and direction-agnostic: in RTL the
   "sidebar" column resolves to the right edge automatically because grid
   columns follow the writing direction. Expected markup:

     <div class="console">
       <aside class="console-sidebar"> .console-brand? .console-nav </aside>
       <header class="console-topbar"> breadcrumbs · search · user </header>
       <main class="console-main">
         <div class="console-header"> .console-title  .console-actions </div>
         <div class="console-body"> … </div>
       </main>
     </div>

   Nav items MUST be authored as:  <a class="console-nav-item">
     <i class="icon" data-icon="…"></i><span>Label</span> [<span class="badge">]
   </a>  — the rail variant hides everything except the icon.

   Density: the console runs one type step down (--fs-sm), 28px nav rows,
   a 52px topbar, and the raised sidebar sits on the page ground so the
   three layers (ground → chrome → cards) separate by luminance.
   ========================================================================= */

.console {
  --sidebar-w: 248px;            /* shell column width, not spacing */
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  grid-template-rows: auto 1fr;
  grid-template-areas:
    "sidebar topbar"
    "sidebar main";
  min-height: calc(100vh - var(--wf-bar-h, 0px));
  background: var(--bg);
  color: var(--text);
  font-size: var(--fs-sm);          /* console density: one step down */
}

/* --- 2.1 Sidebar --------------------------------------------------------- */

.console-sidebar {
  grid-area: sidebar;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-inline-end: var(--border-w) solid var(--border);
  position: sticky;
  inset-block-start: var(--wf-bar-h, 0px);
  block-size: calc(100vh - var(--wf-bar-h, 0px));
  overflow-y: auto;
  overflow-x: hidden;
}

/* Tenant identity block at the top of the sidebar. Its height matches
   the topbar band exactly, so the hairline under both runs level. */
.console-brand {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding-block: var(--sp-2);
  padding-inline: var(--sp-4);
  border-block-end: var(--border-w) solid var(--border);
  min-block-size: 52px;          /* aligns with the topbar band */
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-tight-2);
  color: var(--text);
}

.console-nav {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: var(--sp-2);
}

/* Group label — the pack's caps-eyebrow voice (uppercase + --tracking-caps;
   the token collapses to `normal` for Arabic, so the joined baseline is
   safe). Inline padding matches .console-nav-item so labels and items
   share one text edge (--sp-2 rail padding + --sp-2 item padding =
   --sp-4, the same inset as .console-brand). */
.console-nav-group {
  padding-block: var(--sp-5) var(--sp-1);
  padding-inline: var(--sp-2);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--text-faint);
}

.console-nav-group:first-child { padding-block-start: var(--sp-2); }

/* Nav rows — genuinely dense (28px), quiet at rest, a real active state:
   brand-soft fill, brand ink, semibold, and the icon takes the ink too. */
.console-nav-item {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-block-size: 28px;
  padding-block: var(--sp-1);
  padding-inline: var(--sp-2);
  border-radius: var(--r-sm);
  color: var(--text-muted);
  text-decoration: none;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  line-height: 1.3;
  transition: background-color var(--t-fast), color var(--t-fast);
}

.console-nav-item .icon {
  flex-shrink: 0;
  font-size: 1.1em;
  color: var(--text-faint);
  transition: color var(--t-fast);
}

.console-nav-item .badge { margin-inline-start: auto; }

.console-nav-item:hover {
  background: var(--surface-hover);
  color: var(--text);
  text-decoration: none;
}

.console-nav-item:hover .icon { color: var(--text-muted); }

.console-nav-item:active {
  background: var(--surface-2);
}

.console-nav-item:focus-visible {
  outline: 2px solid var(--focus-ring, var(--text));
  outline-offset: -2px;
}

.console-nav-item.is-active,
.console-nav-item[aria-current="page"] {
  background: var(--primary-soft);
  color: var(--primary-ink);
  font-weight: var(--fw-semibold);
}

.console-nav-item.is-active .icon,
.console-nav-item[aria-current="page"] .icon {
  color: inherit;
}

/* Bottom-of-sidebar slot (signed-in user, version, help). */
.console-sidebar-foot {
  padding-block: var(--sp-3);
  padding-inline: var(--sp-4);
  border-block-start: var(--border-w) solid var(--border);
  font-size: var(--fs-xs);
  color: var(--text-faint);
}

/* --- 2.2 Rail variant (explicit) ----------------------------------------- */
/* NOTE: rules 2.2 and the ≤1100px query below are intentionally the same
   declarations — one is opt-in (.console--rail), one is responsive. Keep
   them in sync when editing. */

.console--rail { --sidebar-w: 64px; }
.console--rail .console-nav-item {
  justify-content: center;
  min-block-size: 32px;
  padding-inline: 0;
}
.console--rail .console-nav-item > span,
.console--rail .console-nav-item > .badge,
.console--rail .console-brand > :not(.icon):not(.portal-brand-mark),
.console--rail .console-sidebar-foot { display: none; }
.console--rail .console-brand { justify-content: center; padding-inline: var(--sp-2); }
.console--rail .console-nav-group {
  /* Collapse group labels to a hairline divider. */
  padding: 0;
  margin-block: var(--sp-3);
  block-size: var(--border-w);
  background: var(--border);
  overflow: hidden;
  color: transparent;
}

/* --- 2.3 Topbar ----------------------------------------------------------- */
/* Same surface + hairline treatment as the sidebar, and the same 52px
   band as .console-brand, so the chrome reads as one continuous frame
   around the content instead of a floating strip. */

.console-topbar {
  grid-area: topbar;
  position: sticky;
  inset-block-start: var(--wf-bar-h, 0px);
  z-index: var(--z-sticky, 100);
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-block-size: 52px;          /* chrome bar height, not spacing */
  padding-inline: var(--sp-5);
  background: var(--surface);
  border-block-end: var(--border-w) solid var(--border);
}

.console-topbar .breadcrumbs { min-width: 0; }

/* Topbar search sits on the control rhythm (32px), not the portal's
   hero scale — this is a tool, not a landing page. */
.console-topbar .searchbar,
.console-topbar .input {
  margin-inline-start: auto;
  inline-size: min(320px, 36%);
}

.console-topbar .searchbar {
  min-block-size: var(--ctl-h-md);
  padding-inline: var(--sp-2);
  border-radius: var(--r-sm);
  border-color: var(--border);
  box-shadow: none;
}

.console-topbar .searchbar input,
.console-topbar .searchbar .input {
  font-size: var(--fs-sm);
}

/* Signed-in user cluster at the trailing edge. */
.console-user {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-shrink: 0;
}

/* Off-canvas menu button — only shown under 760px. */
.console-menu-btn { display: none; }

/* --- 2.4 Main region ------------------------------------------------------ */

.console-main {
  grid-area: main;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.console-header {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
  flex-wrap: wrap;
  padding-block: var(--sp-5) 0;
  padding-inline: var(--sp-6);
}

.console-title {
  font-size: var(--fs-xl);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-tight-2);
  line-height: var(--lh-heading);
  color: var(--text);
}

/* Optional muted sub-line directly under the title. */
.console-sub {
  margin-block-start: var(--sp-1);
  font-size: var(--fs-sm);
  font-weight: var(--fw-regular);
  color: var(--text-muted);
}

.console-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  margin-inline-start: auto;
}

.console-body {
  flex: 1;
  padding-block: var(--sp-4) var(--sp-10);
  padding-inline: var(--sp-6);
  min-width: 0;
}

/* Scrim behind the off-canvas sidebar drawer (≤760px only). */
.console-scrim { display: none; }

/* =========================================================================
   3. RESPONSIVE
   ========================================================================= */

/* Portal nav collapses to a menu button. */
@media (max-width: 860px) {
  .portal-nav { display: none; }
  .portal-menu-btn { display: inline-flex; }
  .portal-topbar,
  .portal-topbar > .portal-container { gap: var(--sp-4); }
  .portal-hero { padding-block: var(--sp-8) var(--sp-10); }
  .portal-footer-grid { grid-template-columns: 1fr 1fr; gap: var(--sp-7); }
}

/* Console sidebar auto-collapses to a rail (mirror of §2.2). */
@media (max-width: 1100px) and (min-width: 761px) {
  .console { --sidebar-w: 64px; }
  .console .console-nav-item {
    justify-content: center;
    min-block-size: 32px;
    padding-inline: 0;
  }
  .console .console-nav-item > span,
  .console .console-nav-item > .badge,
  .console .console-brand > :not(.icon):not(.portal-brand-mark),
  .console .console-sidebar-foot { display: none; }
  .console .console-brand { justify-content: center; padding-inline: var(--sp-2); }
  .console .console-nav-group {
    padding: 0;
    margin-block: var(--sp-3);
    block-size: var(--border-w);
    background: var(--border);
    overflow: hidden;
    color: transparent;
  }
}

/* Console sidebar becomes an off-canvas drawer, full labels restored.
   Toggle with .is-menu-open on .console (wired by a screen-local handler
   or left as a documented state — wireframes may ship it open or closed). */
@media (max-width: 760px) {
  .console {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "topbar"
      "main";
  }
  .console-sidebar {
    position: fixed;
    inset-block: 0;
    inset-inline-start: -320px;   /* logical: hides on the start edge, both dirs */
    inline-size: 280px;
    block-size: 100vh;
    z-index: var(--z-drawer, 500);
    transition: inset-inline-start var(--t-slow);
    /* Elevation must invert with the theme — a --text-derived shadow
       becomes a white glow in dark mode. --shadow-lg is re-derived
       per theme in tokens.css, same as .drawer in components.css. */
    box-shadow: var(--shadow-lg);
  }
  .console.is-menu-open .console-sidebar { inset-inline-start: 0; }
  .console-menu-btn { display: inline-flex; }
  .console.is-menu-open .console-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: calc(var(--z-drawer, 500) - 10);
    /* One overlay recipe for the whole pack (.modal, .wf-scrim, here) —
       tokens.css derives --scrim per theme; a scrim must always DARKEN
       what it obscures. Fallback = the light-theme --scrim value. */
    background: var(--scrim, rgba(16, 24, 40, 0.55));
  }
  .console-header,
  .console-body { padding-inline: var(--sp-4); }
  /* Narrow the page gutter once, on the column definition, so the top bar
     tightens in step with the hero, main and footer instead of drifting. */
  .portal { --portal-gutter: var(--sp-4); }
  .portal-footer-grid { grid-template-columns: 1fr; }
}
