/* ⚠️ `visibility` is deliberately NOT transitioned anywhere in this file.
   A transitioned visibility only flips when the transition actually runs, and
   a transition needs the page to be painting. In a throttled or embedded view
   the class lands but visibility stays `hidden`, leaving the control present,
   interactive and invisible. Switching it with the class is instant and
   cannot stall; only opacity is animated. */
/* =========================================================================
   Public shell - the US Fonts boxed layout, rebuilt on tokens.
   Spec: docs/UI-SPEC.md. Measurements transcribed from the original
   (usfonts-core.css / header.php / index.php), execution rebuilt here.

   Naming note: `.site-footer` is already taken by the app chrome in
   layout.css, so the public footer is `.pub-footer`. Everything else keeps
   the original's class names so the two are easy to compare.
   ========================================================================= */

/* -------------------------------------------------------------------------
   The box
   A tinted page behind a 1200px sheet with a wide soft shadow. That shadow is
   the whole "boxed" effect - without a tinted body there is nothing for it to
   fall on, so the two rules belong together.
   ------------------------------------------------------------------------- */
body.pub {
  background: var(--bg-sunken);
}

.site-wrapper {
  max-width: var(--container);
  margin-inline: auto;
  min-height: 100vh;
  background: var(--surface);
  box-shadow: 0 0 60px rgba(23, 23, 42, 0.08);
}

/* The box shadow needs more weight on a dark page to stay visible. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .site-wrapper { box-shadow: 0 0 60px rgba(0, 0, 0, 0.35); }
}
:root[data-theme="dark"] .site-wrapper { box-shadow: 0 0 60px rgba(0, 0, 0, 0.35); }

/* The inner rail every band aligns to. 1160 + 20px gutters inside the 1200 box. */
.rail {
  max-width: 1160px;
  margin-inline: auto;
  padding-inline: var(--space-5);
}

/* -------------------------------------------------------------------------
   Top bar
   ------------------------------------------------------------------------- */
.top-bar {
  background: linear-gradient(135deg, var(--ink-900) 0%, var(--brand-800) 100%);
  color: var(--ink-200);
  font-size: var(--text-xs);
}
.top-bar-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  min-height: 36px;
}
.top-bar a { color: var(--ink-300); transition: color var(--duration-fast) var(--ease-out); }
.top-bar a:hover { color: var(--ink-0); }

.top-bar-social { display: flex; align-items: center; gap: var(--space-3); }
.top-bar-right  { display: flex; align-items: center; gap: var(--space-4); }
.top-bar-right a { display: inline-flex; align-items: center; gap: var(--space-1); }

@media (max-width: 768px) {
  .top-bar-right { display: none; }
  .top-bar-inner { justify-content: center; }
}

/* -------------------------------------------------------------------------
   Main header
   ------------------------------------------------------------------------- */
.main-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  transition: box-shadow var(--duration-base) var(--ease-out);
}
.main-header.is-stuck { box-shadow: var(--shadow-md); }

.header-inner {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  height: var(--header-height);
}

.site-logo {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex: none;
}
.site-logo img { height: 36px; width: auto; border-radius: var(--radius-xs); display: block; }
.logo-text { display: flex; flex-direction: column; line-height: 1.15; }
.logo-name {
  font-size: var(--text-xl);
  font-weight: var(--weight-black);
  letter-spacing: var(--tracking-tight);
  color: var(--text);
}
.logo-tagline {
  font-size: 0.625rem;
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-subtle);
}

/* ---- primary nav ---- */
.main-nav {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  height: 100%;
}
.main-nav > a,
.main-nav .has-mega > a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 100%;
  padding-inline: var(--space-3);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  color: var(--text-muted);
  white-space: nowrap;
  border-bottom: 2px solid transparent;
  transition: color var(--duration-fast) var(--ease-out),
              border-color var(--duration-fast) var(--ease-out);
}
.main-nav > a:hover,
.main-nav .has-mega > a:hover,
.main-nav > a[aria-current="page"],
.main-nav .has-mega > a[aria-current="page"] {
  color: var(--text-brand);
  border-bottom-color: var(--brand-500);
}
.main-nav .chev { display: inline-flex; transition: transform var(--duration-fast) var(--ease-out); }

/* ---- mega menus ----
   Opened by hover on pointer devices and by click/focus everywhere (the
   .is-open class), so the menu is reachable by keyboard and on touch. */
/* The wrapper must be a full-height flex item, or the trigger inside it loses
   the nav height and the label and chevron wrap onto two lines.

   `position: static` is deliberate: the panel is positioned against the header
   rail, not against this item. Anchored to the trigger, a 44rem panel opening
   under a nav item near the left edge hangs outside the boxed 1200px page. */
.has-mega { position: static; display: flex; align-items: center; height: 100%; }

/* Positioned against .header-inner. --mega-left is set by site.js to centre
   the panel under its trigger, clamped to the rail; with no JS it falls back
   to 0, which is the left edge of the rail - still inside the page. */
.mega-menu {
  position: absolute;
  top: 100%;
  left: var(--mega-left, var(--space-5));
  width: min(44rem, calc(100% - (var(--space-5) * 2)));
  padding: var(--space-6);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-xl);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity var(--duration-base) var(--ease-out),
              transform var(--duration-base) var(--ease-out);
  z-index: var(--z-drawer);
}
.has-mega:hover > .mega-menu,
.has-mega:focus-within > .mega-menu,
.has-mega.is-open > .mega-menu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.has-mega:hover > a .chev,
.has-mega.is-open > a .chev { transform: rotate(180deg); }

@media (prefers-reduced-motion: reduce) {
  .mega-menu { transition: none; }
}

.mega-grid {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
}

.mega-item {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  transition: background var(--duration-fast) var(--ease-out);
}
.mega-item:hover { background: var(--bg-sunken); }
.mega-icon {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  flex: none;
  border-radius: var(--radius-xs);
  background: var(--brand-50);
  color: var(--brand-600);
}
.mega-title { font-size: var(--text-base); font-weight: var(--weight-semibold); color: var(--text); }
.mega-desc  { font-size: var(--text-xs); line-height: var(--leading-normal); color: var(--text-muted); }

/* Post-flavoured mega (the Blog menu) */
.mega-post { display: grid; grid-template-columns: 6.5rem minmax(0, 1fr); gap: var(--space-3); padding: var(--space-2); border-radius: var(--radius-sm); }
.mega-post:hover { background: var(--bg-sunken); }
.mega-post img { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; border-radius: var(--radius-xs); background: var(--bg-sunken); }
.mega-post-title {
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
  color: var(--text);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; line-clamp: 2; overflow: hidden;
}
.mega-post-excerpt {
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  line-height: var(--leading-normal);
  color: var(--text-muted);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; line-clamp: 2; overflow: hidden;
}
.mega-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}

/* ---- header actions ---- */
.header-actions { display: flex; align-items: center; gap: var(--space-2); flex: none; }

.head-icon-btn {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text-muted);
  transition: all var(--duration-fast) var(--ease-out);
}
.head-icon-btn:hover { color: var(--text-brand); border-color: var(--brand-300); background: var(--brand-50); }

.burger { display: none; }

/* Below 1024 the nav collapses to the drawer; the two CTAs stay visible as
   long as there is room for them, because "Get Started" is the whole point of
   the header on a content site. */
@media (max-width: 1023px) {
  .main-nav { display: none; }
  .burger { display: grid; }
}
/* Below 640 the secondary CTA moves into the drawer; below 460 the theme
   toggle goes too. Get Started is the last thing to drop, not the first - it
   is the primary conversion on every page. */
@media (max-width: 640px) {
  .header-actions .btn-signin { display: none; }
}
@media (max-width: 460px) {
  .header-actions [data-theme-toggle] { display: none; }
}
@media (max-width: 360px) {
  .header-actions .btn-getstarted { display: none; }
}
@media (max-width: 768px) {
  .header-inner { height: 60px; }
  .site-logo img { height: 30px; }
  .logo-name { font-size: var(--text-md); }
  .logo-tagline { display: none; }
}

/* -------------------------------------------------------------------------
   Mobile drawer
   ------------------------------------------------------------------------- */
.nav-scrim {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  background: rgba(13, 13, 26, 0.5);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--duration-base) var(--ease-out);
}
.nav-scrim.is-open { opacity: 1; visibility: visible; }

.nav-drawer {
  position: fixed;
  inset-block: 0;
  inset-inline-start: 0;
  z-index: calc(var(--z-overlay) + 1);
  width: min(20rem, 86vw);
  display: flex;
  flex-direction: column;
  background: var(--surface);
  box-shadow: var(--shadow-xl);
  transform: translateX(-100%);
  transition: transform var(--duration-base) var(--ease-out);
  overscroll-behavior: contain;
}
.nav-drawer.is-open { transform: translateX(0); }
@media (prefers-reduced-motion: reduce) {
  .nav-drawer, .nav-scrim { transition: none; }
}

.nav-drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border);
}
.nav-drawer-body { flex: 1; overflow-y: auto; padding: var(--space-4) var(--space-3); }
.nav-drawer-foot {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--border);
  padding-bottom: max(var(--space-4), env(safe-area-inset-bottom));
}

.drawer-link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  font-size: var(--text-md);
  font-weight: var(--weight-medium);
  color: var(--text);
}
.drawer-link:hover { background: var(--bg-sunken); color: var(--text-brand); }
.drawer-group-title {
  padding: var(--space-4) var(--space-4) var(--space-2);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-subtle);
}

/* -------------------------------------------------------------------------
   Search overlay
   ------------------------------------------------------------------------- */
.search-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: grid;
  place-items: start center;
  padding: clamp(var(--space-12), 14vh, var(--space-24)) var(--space-5) var(--space-5);
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--duration-base) var(--ease-out);
}
.search-overlay.is-open { opacity: 1; visibility: visible; }
.search-box {
  width: min(40rem, 100%);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-xl);
}
.search-box input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--text-lg);
  color: var(--text);
  outline: none;
}
.search-hint { width: min(40rem, 100%); margin-top: var(--space-3); font-size: var(--text-sm); color: var(--text-muted); }

/* -------------------------------------------------------------------------
   Content area - main + sidebar
   ------------------------------------------------------------------------- */
.content-area {
  max-width: 1160px;
  margin-inline: auto;
  padding: var(--space-10) var(--space-5) var(--space-16);
  display: flex;
  gap: var(--space-8);
  align-items: flex-start;
}

/* min-width:0 is load-bearing: without it a wide table or a long URL inside
   this flex child makes the whole page scroll sideways. */
/* Both constraints are load-bearing, for DIFFERENT axes.
 *
 * `min-width: 0` overrides the flex default of `min-width: auto` and lets this
 * item shrink below its content on the MAIN axis. That is enough on desktop,
 * where .content-area is a row.
 *
 * At <=768px .content-area becomes `flex-direction: column`, so width is the
 * CROSS axis and neither `flex` nor `min-width` governs it any more. Without
 * `max-width: 100%` the intrinsic width of the widest descendant - the
 * carousel track, a nowrap flex row as wide as the sum of its slides -
 * propagates up through the display:block ancestors and stretches this item.
 *
 * Measured live before the fix: .main-content was 1532px inside a 375px
 * viewport, document scrollWidth 1548, and every homepage section below the
 * hero scrolled sideways. */
.main-content { flex: 1; min-width: 0; max-width: 100%; }

.sidebar { width: 320px; flex: none; display: grid; gap: var(--space-6); }
@media (max-width: 1023px) { .sidebar { width: 280px; } }
@media (max-width: 768px) {
  .sidebar { display: none; }
  .content-area { flex-direction: column; padding: var(--space-4) var(--space-3) var(--space-10); gap: var(--space-6); }
}

/* ---- sidebar widgets ---- */
.sidebar-widget {
  padding: var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.sidebar-widget-title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border);
  font-size: var(--text-base);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-snug);
  color: var(--text);
}
.sidebar-widget-title i, .sidebar-widget-title svg { color: var(--text-brand); }

.sidebar-cats { display: grid; gap: var(--space-1); }
.sidebar-cats a {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-xs);
  font-size: var(--text-base);
  color: var(--text-muted);
}
.sidebar-cats a:hover { background: var(--bg-sunken); color: var(--text-brand); }
.cat-count {
  margin-left: auto;
  padding: 0.1rem var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--bg-sunken);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  color: var(--text-subtle);
}

.sidebar-social-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-2); }
.sidebar-social-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-muted);
}
.sidebar-social-item:hover { border-color: var(--brand-300); color: var(--text-brand); background: var(--brand-50); }

.sidebar-tags { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.sidebar-tag {
  padding: 0.25rem var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--bg-sunken);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--text-muted);
}
.sidebar-tag:hover { background: var(--brand-50); color: var(--text-brand); }

.widget-newsletter {
  border: 0;
  background: var(--gradient-brand);
  color: var(--ink-0);
  box-shadow: var(--shadow-brand);
}
.widget-newsletter .sidebar-widget-title { color: var(--ink-0); border-bottom-color: rgba(255, 255, 255, 0.25); }
.widget-newsletter .sidebar-widget-title i, .widget-newsletter .sidebar-widget-title svg { color: var(--ink-0); }
.widget-newsletter p { font-size: var(--text-base); line-height: var(--leading-normal); color: rgba(255, 255, 255, 0.9); }

/* -------------------------------------------------------------------------
   Footer  (.pub-footer - `.site-footer` belongs to the app chrome)
   ------------------------------------------------------------------------- */
.pub-footer {
  background: var(--ink-900);
  color: var(--ink-300);
}
.pub-footer-main {
  display: grid;
  gap: var(--space-8);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
  padding-block: var(--space-12);
}
.pub-footer h4 {
  margin-bottom: var(--space-4);
  font-size: var(--text-base);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-snug);
  color: var(--ink-0);
}
.pub-footer p { font-size: var(--text-base); line-height: var(--leading-normal); color: var(--ink-400); }
.pub-footer a { color: var(--ink-300); }
.pub-footer a:hover { color: var(--ink-0); }

.footer-links { display: grid; gap: var(--space-2); font-size: var(--text-base); }
.footer-links a { display: inline-flex; align-items: center; gap: var(--space-2); }

/* Sits ABOVE the about paragraph now, so the top margin goes and a bottom one
   takes its place. Wraps rather than overflowing: six networks do not fit on
   one line in a footer column. */
.footer-social {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-5);
}
.footer-social-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 6px 10px 6px 8px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
}
.footer-social-link:hover {
  background: var(--brand-600);
  border-color: var(--brand-600);
  color: var(--ink-0);
}
.footer-social a:not(.footer-social-link) {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius-sm);
  transition: all var(--duration-fast) var(--ease-out);
}
.footer-social a:hover { background: var(--brand-600); border-color: var(--brand-600); color: var(--ink-0); }

.app-download { display: grid; gap: var(--space-2); margin-top: var(--space-3); }
.app-link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
}
.app-link:hover { background: rgba(255, 255, 255, 0.06); }

.footer-bottom { border-top: 1px solid rgba(255, 255, 255, 0.1); }
.footer-bottom-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding-block: var(--space-5);
  font-size: var(--text-sm);
  color: var(--ink-400);
}
/* Seven legal links with icons. `flex-wrap` and a smaller gap keep them on
   two tidy lines at 375px instead of one overflowing row; `align-items:center`
   stops the icons sitting on the text baseline. */
.footer-bottom-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
}
.footer-bottom-links a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

@media (max-width: 768px) {
  .pub-footer-main { padding-block: var(--space-10); gap: var(--space-6); }
  .footer-bottom-inner { justify-content: center; text-align: center; }
  .footer-bottom-links { justify-content: center; }
  .footer-social { justify-content: flex-start; }
}

/* -------------------------------------------------------------------------
   Mobile bottom nav
   The bar height and the body padding read from ONE token. The original
   shipped 72px of padding against a 61px bar and left a dead strip under the
   footer on notched phones; a single source of truth prevents that.
   ------------------------------------------------------------------------- */
:root { --bottom-nav-h: calc(53px + max(8px, env(safe-area-inset-bottom))); }

.mobile-bottom-nav {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: var(--z-header);
  box-sizing: border-box;
  height: var(--bottom-nav-h);
  padding-top: var(--space-1);
  padding-bottom: max(8px, env(safe-area-inset-bottom));
  background: var(--surface);
  border-top: 1px solid var(--border);
  box-shadow: 0 -2px 16px rgba(23, 23, 42, 0.06);
}
.mobile-bottom-nav-inner {
  display: flex;
  align-items: center;
  justify-content: space-around;
  max-width: 26rem;
  height: 100%;
  margin-inline: auto;
}
.mobile-nav-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: var(--space-1) var(--space-2);
  font-size: 0.625rem;
  font-weight: var(--weight-medium);
  color: var(--text-subtle);
}
.mobile-nav-item i, .mobile-nav-item svg { font-size: var(--text-md); }
.mobile-nav-item:hover,
.mobile-nav-item[aria-current="page"] { color: var(--text-brand); }

@media (min-width: 769px) {
  .mobile-bottom-nav { display: none; }
}
@media (max-width: 768px) {
  body.pub { padding-bottom: var(--bottom-nav-h); }
}

/* -------------------------------------------------------------------------
   Hero
   ------------------------------------------------------------------------- */
.hero-section {
  position: relative;
  overflow: hidden;
  background: var(--gradient-mesh);
  border-bottom: 1px solid var(--border);
}
.hero-inner {
  display: grid;
  gap: var(--space-10);
  align-items: center;
  grid-template-columns: minmax(0, 1fr);
  padding-block: var(--space-16);
}
@media (min-width: 940px) {
  .hero-inner { grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); }
}
@media (max-width: 768px) {
  .hero-inner { padding-block: var(--space-10); gap: var(--space-8); }
}

.hero-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.3rem var(--space-3);
  margin-bottom: var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  color: var(--text-muted);
}

/* Capability chips replace the original's invented "10K+ Readers" stat row.
   See docs/UI-SPEC.md §6. */
.hero-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-6); }
.hero-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-muted);
}
.hero-chip i, .hero-chip svg { color: var(--text-brand); }

/* -------------------------------------------------------------------------
   Section header used down the homepage and the hubs
   ------------------------------------------------------------------------- */
.section-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-6);
}
.section-title {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--text-2xl);
  font-weight: var(--weight-black);
  letter-spacing: var(--tracking-tight);
  color: var(--text);
}
.section-title i, .section-title svg { color: var(--text-brand); font-size: 0.85em; }
.section-more { font-size: var(--text-sm); font-weight: var(--weight-semibold); color: var(--text-brand); }

.home-block + .home-block { margin-top: var(--space-12); }

@media (max-width: 768px) {
  .section-title { font-size: var(--text-xl); }
  .home-block + .home-block { margin-top: var(--space-10); }
}

/* -------------------------------------------------------------------------
   Featured grid - one lead card plus two stacked, as the original
   ------------------------------------------------------------------------- */
.featured-grid {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 860px) {
  .featured-grid { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); }
  .featured-right { display: grid; gap: var(--space-4); }
}

.featured-card {
  position: relative;
  display: block;
  min-height: 15rem;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--bg-sunken);
}
.featured-card img { width: 100%; height: 100%; position: absolute; inset: 0; object-fit: cover; }
.featured-card-overlay {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: var(--space-2);
  min-height: inherit;
  height: 100%;
  padding: var(--space-5);
  background: linear-gradient(to top, rgba(13, 13, 26, 0.88) 0%, rgba(13, 13, 26, 0.35) 45%, transparent 75%);
  color: var(--ink-0);
}
.featured-card-title {
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
  line-height: var(--leading-snug);
  color: var(--ink-0);
  text-wrap: pretty;
}
.featured-card-meta { display: flex; flex-wrap: wrap; gap: var(--space-3); font-size: var(--text-xs); color: var(--ink-300); }

@media (min-width: 860px) {
  .featured-card.is-lead { min-height: 24rem; }
  .featured-card.is-lead .featured-card-title { font-size: var(--text-2xl); }
}

/* A hero with no featured post to show collapses to one column rather than
   leaving an empty half. This is the normal state before content is seeded. */
.hero-inner.is-solo { grid-template-columns: minmax(0, 1fr); }
.hero-inner.is-solo .home-hero-desc,
.hero-inner.is-solo .home-hero-title { max-width: 24ch; }
.hero-inner.is-solo .home-hero-desc { max-width: 60ch; }

/* =========================================================================
   Brand mark, navigation motion, and the centred hero
   ========================================================================= */

/* ---- logo -------------------------------------------------------------
   The supplied wordmark is 126x31, so it is rendered at its native size
   rather than upscaled. Displaying it larger would only soften it - a 2x
   asset (252x62 or an SVG) would let this grow. */
.site-logo img { height: auto; width: 126px; max-width: 100%; border-radius: 0; }
@media (max-width: 768px) { .site-logo img { width: 108px; } }
@media (max-width: 360px) { .site-logo img { width: 96px; } }

/* The wordmark's navy half disappears on a dark ground, so on dark themes it
   sits on a light plate rather than being inverted (inverting would turn the
   red half pink). */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .site-logo img,
  :root:not([data-theme="light"]) .nav-drawer-head img {
    background: var(--ink-0);
    padding: 4px 8px;
    border-radius: var(--radius-xs);
  }
}
:root[data-theme="dark"] .site-logo img,
:root[data-theme="dark"] .nav-drawer-head img {
  background: var(--ink-0);
  padding: 4px 8px;
  border-radius: var(--radius-xs);
}

/* ---- ambient navigation icons -----------------------------------------
   These animate on their own, not only on hover.

   The motion is deliberately periodic rather than continuous: each icon
   moves for a fraction of a long cycle and rests for the remainder, with a
   stagger so they never move in unison. Continuous motion in a fixed header
   is the kind of thing that reads as cheap on the tenth viewing and is
   genuinely hostile to anyone with a vestibular disorder - hence the hard
   stop under prefers-reduced-motion below. */
.nav-ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-subtle);
  transition: color var(--duration-fast) var(--ease-out),
              transform var(--duration-base) var(--ease-spring);
  transform-origin: center;
  will-change: transform;
}
.main-nav a:hover .nav-ico,
.main-nav a[aria-current="page"] .nav-ico { color: var(--brand-600); }
.main-nav a:hover .nav-ico { transform: scale(1.14); }

.nav-ico[data-motion="sway"]  { animation: ico-sway  7s var(--ease-in-out) infinite; }
.nav-ico[data-motion="twist"] { animation: ico-twist 8s var(--ease-in-out) infinite 0.6s; }
.nav-ico[data-motion="bob"]   { animation: ico-bob   6s var(--ease-in-out) infinite 1.2s; }
.nav-ico[data-motion="pulse"] { animation: ico-pulse 5s var(--ease-in-out) infinite 1.8s; }
.nav-ico[data-motion="flip"]  { animation: ico-flip  9s var(--ease-in-out) infinite 2.4s; }

/* Each keyframe set does its work inside the first ~18% and rests after. */
@keyframes ico-sway {
  0%, 82%, 100% { transform: rotate(0); }
  86%           { transform: rotate(-9deg); }
  92%           { transform: rotate(7deg); }
  96%           { transform: rotate(-3deg); }
}
@keyframes ico-twist {
  0%, 80%, 100% { transform: rotate(0) scale(1); }
  88%           { transform: rotate(22deg) scale(1.08); }
}
@keyframes ico-bob {
  0%, 78%, 100% { transform: translateY(0); }
  86%           { transform: translateY(-3.5px); }
  93%           { transform: translateY(1px); }
}
@keyframes ico-pulse {
  0%, 76%, 100% { transform: scale(1); opacity: 1; }
  84%           { transform: scale(1.18); opacity: 0.72; }
}
@keyframes ico-flip {
  0%, 84%, 100% { transform: rotateY(0); }
  92%           { transform: rotateY(180deg); }
}

@media (prefers-reduced-motion: reduce) {
  .nav-ico[data-motion] { animation: none !important; }
  .main-nav a:hover .nav-ico { transform: none; }
}

/* ---- mega menu: two columns ------------------------------------------- */
.mega-grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.mega-menu   { width: min(44rem, calc(100vw - var(--space-8))); }
@media (max-width: 860px) {
  .mega-grid-2 { grid-template-columns: minmax(0, 1fr); }
}

/* ---- centred hero -----------------------------------------------------
   The homepage hero is a single centred column. It reads as a masthead
   rather than a product splash, which is what a publication wants, and it
   needs no second column to look finished before content exists. */
.hero-inner.is-centred {
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  text-align: center;
  padding-block: var(--space-20);
}
.hero-inner.is-centred > * { max-width: 46rem; width: 100%; }
.hero-inner.is-centred .home-hero-title { max-width: 20ch; margin-inline: auto; }
.hero-inner.is-centred .home-hero-desc  { max-width: 58ch; margin-inline: auto; }
.hero-inner.is-centred .home-search     { margin-inline: auto; }
.hero-inner.is-centred .hero-chips      { justify-content: center; }
.hero-inner.is-centred .home-hero-actions { justify-content: center; }

@media (max-width: 768px) {
  .hero-inner.is-centred { padding-block: var(--space-12); }
}

/* =========================================================================
   Footer CTA band, app buttons, and scroll reveal
   ========================================================================= */

.footer-cta {
  background: var(--gradient-brand);
  color: var(--ink-0);
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}
.footer-cta-inner {
  display: grid;
  gap: var(--space-6);
  align-items: center;
  padding-block: var(--space-10);
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 860px) {
  .footer-cta-inner { grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); }
}
.footer-cta-title {
  font-size: var(--text-2xl);
  font-weight: var(--weight-black);
  letter-spacing: var(--tracking-tight);
  color: var(--ink-0);
}
.footer-cta-text {
  margin-top: var(--space-2);
  font-size: var(--text-md);
  line-height: var(--leading-normal);
  color: rgba(255, 255, 255, 0.85);
  max-width: 52ch;
}
.footer-cta-form { display: flex; gap: var(--space-2); }
.footer-cta-form .input { flex: 1; min-width: 0; }
@media (max-width: 520px) {
  .footer-cta-form { flex-direction: column; }
  .footer-cta-form .btn { width: 100%; }
}

.footer-logo {
  height: auto;
  width: 126px;
  margin-bottom: var(--space-4);
  background: var(--ink-0);
  padding: 5px 9px;
  border-radius: var(--radius-xs);
}

/* Store buttons, styled like the app-store chips they stand in for. */
.app-link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--radius-sm);
  transition: background var(--duration-fast) var(--ease-out),
              border-color var(--duration-fast) var(--ease-out);
}
.app-link:hover { background: rgba(255, 255, 255, 0.08); border-color: rgba(255, 255, 255, 0.32); }
.app-link-text { display: flex; flex-direction: column; line-height: 1.2; }
.app-link-sub  { font-size: 0.625rem; letter-spacing: var(--tracking-wide); text-transform: uppercase; color: var(--ink-400); }
.app-link-name { font-size: var(--text-base); font-weight: var(--weight-semibold); color: var(--ink-0); }

/* An app we have not shipped yet: visibly inert, not a link to nowhere. */
.app-link.is-pending { opacity: 0.55; cursor: default; }
.app-link.is-pending:hover { background: none; border-color: rgba(255, 255, 255, 0.16); }

/* =========================================================================
   Scroll reveal
   Sections fade and rise once as they enter the viewport. The default state
   is VISIBLE - the .reveal-on class is added by JS only when IntersectionObserver
   is available, so a page with JS disabled or blocked shows all its content
   rather than a column of invisible sections.
   ========================================================================= */
.reveal-on [data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity var(--duration-slow) var(--ease-out),
              transform var(--duration-slow) var(--ease-out);
}
.reveal-on [data-reveal].is-visible { opacity: 1; transform: none; }

/* Cards inside a revealed block come in as a short stagger rather than all at
   once, which reads as intentional instead of as a page-load stutter.

   ⚠️ Scoped to the actual card grids, NOT to `> * > *`. The broad version hit
   `.carousel-viewport` and left a `transform` on the scroll container, which
   silently froze scrollLeft - the carousels would not move at all. A transform
   on a scroll container is not a cosmetic mistake. */
.reveal-on [data-reveal].is-visible :is(.post-grid, .tool-grid, .service-grid, .scholarship-grid) > *:nth-child(1) { animation: card-in 460ms var(--ease-out) both 40ms; }
.reveal-on [data-reveal].is-visible :is(.post-grid, .tool-grid, .service-grid, .scholarship-grid) > *:nth-child(2) { animation: card-in 460ms var(--ease-out) both 100ms; }
.reveal-on [data-reveal].is-visible :is(.post-grid, .tool-grid, .service-grid, .scholarship-grid) > *:nth-child(3) { animation: card-in 460ms var(--ease-out) both 160ms; }
.reveal-on [data-reveal].is-visible :is(.post-grid, .tool-grid, .service-grid, .scholarship-grid) > *:nth-child(4) { animation: card-in 460ms var(--ease-out) both 220ms; }

/* A scroll container must never carry an animation or a transform. */
.carousel-viewport { animation: none !important; transform: none !important; }

@keyframes card-in {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .reveal-on [data-reveal],
  .reveal-on [data-reveal].is-visible { opacity: 1; transform: none; transition: none; }
  .reveal-on [data-reveal].is-visible :is(.post-grid, .tool-grid, .service-grid, .scholarship-grid) > * { animation: none; }
}

/* =========================================================================
   Section polish
   ========================================================================= */

/* A short accent rule under each section title, in the brand red. */
.section-title { position: relative; }
.home-block .section-header { border-bottom: 1px solid var(--border); padding-bottom: var(--space-3); }
.home-block .section-header::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -1px;
  width: 56px;
  height: 2px;
  background: var(--gold-500);
}
.home-block .section-header { position: relative; }

/* The trending strip's live dot, reused as a section marker. */
.home-block { scroll-margin-top: calc(var(--header-height) + var(--space-6)); }

/* =========================================================================
   Hero and section-heading scale
   ========================================================================= */

/* Two lines, deliberately. Each half is a block so the break is structural
   rather than a <br> that moves as the viewport changes, and the measure is
   capped so line one cannot spill into a third line on a narrow desktop. */
.hero-line-1,
.hero-line-2 { display: block; }

.hero-inner.is-centred .home-hero-title {
  /* The canonical largest heading. Article and hub titles derive from this. */
  font-size: var(--hero-title);
  line-height: 1.14;
  /* Wider than the 46rem the rest of the centred hero is capped to, because
     line one has to fit on one line for the two-line break to hold. */
  max-width: min(52rem, 100%);
  width: 100%;
  margin-inline: auto;
  margin-bottom: var(--space-5);
}
.hero-inner.is-centred .home-hero-desc {
  font-size: clamp(1rem, 0.5vw + 0.9rem, 1.125rem);
  max-width: 54ch;
}
.hero-inner.is-centred { padding-block: var(--space-16); }

/* Below this the line simply cannot hold on one row at a readable size, so it
   is allowed to wrap rather than shrinking the type into illegibility. */
@media (max-width: 820px) {
  .hero-inner.is-centred .home-hero-title { max-width: 22ch; }
}

/* Section headings sit below the hero in the hierarchy, so they step down a
   size. They were competing with the h1 at --text-3xl. */
.section-title { font-size: var(--text-xl); }
@media (max-width: 768px) { .section-title { font-size: var(--text-lg); } }

.home-block .section-header { padding-bottom: var(--space-3); }

/* Carousels need breathing room between sections that a flat grid did not,
   because the arrows overhang the content edge. */
.home-block + .home-block { margin-top: var(--space-16); }
@media (max-width: 768px) { .home-block + .home-block { margin-top: var(--space-12); } }

/* The hero chips are links, so they need to read as pressable. */
a.hero-chip {
  transition: border-color var(--duration-fast) var(--ease-out),
              background var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out),
              transform var(--duration-fast) var(--ease-out);
}
a.hero-chip:hover {
  border-color: var(--brand-300);
  background: var(--brand-50);
  color: var(--text-brand);
  transform: translateY(-2px);
}
a.hero-chip:hover svg { color: var(--brand-600); }
@media (prefers-reduced-motion: reduce) {
  a.hero-chip:hover { transform: none; }
}

/* -------------------------------------------------------------------------
   Cookie consent
   Rendered last in the DOM and pinned with CSS, so it is never the first
   thing a screen reader or a crawler encounters. Fixed rather than sticky so
   it cannot push the page down and shift content while a reader is mid-line.

   `visibility` is deliberately NOT transitioned here either - see the note at
   the top of this file.
   ------------------------------------------------------------------------- */
.consent {
  position: fixed;
  left: 50%;
  bottom: var(--space-4);
  transform: translateX(-50%);
  z-index: 1200;
  width: min(920px, calc(100vw - var(--space-6)));
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: 0 18px 48px rgb(0 0 0 / 0.22);

  /* Visible by DEFAULT, with no animation in the base rule.
     `animation: ... both` plus `from { opacity: 0 }` leaves the element stuck
     at opacity 0 wherever animations do not actually run - a throttled or
     non-painting tab, an engine that skips them. The reveal-on-scroll code in
     this same file already stranded content invisible that way once.
     Here the cost would be worse than cosmetic: an invisible consent banner is
     a legal control the reader cannot reach, and because ads are gated on the
     answer it would also mean the site never serves an ad and nobody could
     work out why. The entrance is an enhancement layered on top, never a
     precondition for the banner being visible. */
}
@media (prefers-reduced-motion: no-preference) {
  .consent { animation: consent-in 260ms ease-out; }
}
@keyframes consent-in {
  from { opacity: 0; transform: translateX(-50%) translateY(12px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}
.consent.is-gone { display: none; }

.consent-inner {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-4) var(--space-5);
}
.consent-title {
  margin: 0 0 2px;
  font-weight: 700;
  font-size: var(--text-sm);
  color: var(--text);
}
.consent-text {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--text-muted);
}
.consent-text a { color: var(--text-brand); text-decoration: underline; }

/* Both buttons keep the same visual weight. A banner whose only obvious
   action is "Accept" does not collect meaningful consent. */
.consent-actions {
  display: flex;
  gap: var(--space-3);
  flex-shrink: 0;
}
.consent-actions .btn { white-space: nowrap; }

@media (max-width: 700px) {
  .consent-inner { flex-direction: column; align-items: stretch; gap: var(--space-4); }
  .consent-actions { flex-direction: row; }
  .consent-actions .btn { flex: 1; }
  /* Clear the mobile bottom nav so the banner never covers it. */
  .consent { bottom: calc(var(--bottom-nav-h) + var(--space-3)); }
}

/* Reduced motion is handled by the no-preference guard on the animation above. */

/* Sidebar: open scholarships ---------------------------------------------- */
.sidebar-grants { display: grid; gap: 2px; }
.grant-row {
  display: block;
  padding: var(--space-3) var(--space-3);
  border-radius: var(--radius-sm);
  text-decoration: none;
  transition: background var(--duration-fast);
}
.grant-row:hover { background: var(--surface-alt); }
.grant-row-title {
  display: block;
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1.35;
  color: var(--text);
  /* Two lines maximum: a long award name must not push the widget open. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.grant-row-meta {
  display: block;
  margin-top: 3px;
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.sidebar-widget-more {
  display: inline-block;
  margin-top: var(--space-3);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-brand);
  text-decoration: none;
}
.sidebar-widget-more:hover { text-decoration: underline; }

/* -------------------------------------------------------------------------
   Mobile form fields: 16px minimum
   iOS Safari zooms the entire page when a focused field's font-size is below
   16px, and does not zoom back out. Every field on the site computes to 15px,
   so tapping any input on an iPhone threw the layout off — which reads as "the
   mobile site is broken" even though nothing is actually wrong with it.
   16px is the threshold, not a preference.
   ------------------------------------------------------------------------- */
@media (max-width: 768px) {
  input[type="text"],
  input[type="email"],
  input[type="tel"],
  input[type="search"],
  input[type="password"],
  input[type="number"],
  input[type="url"],
  input[type="date"],
  select,
  textarea,
  /* The class selectors are required, not decorative: components.css styles
     these as `.input, .select, .textarea` (specificity 0,1,0), which beats a
     bare `textarea` element selector (0,0,1). Without `.textarea` here the
     message box on the contact form stayed at 15px and kept the zoom bug. */
  .input,
  .select,
  .textarea {
    font-size: 16px;
  }
}

/* -------------------------------------------------------------------------
   Footer icon motion
   Reuses the header's .nav-ico[data-motion] system rather than defining a
   second one, so the keyframes, the stagger and the prefers-reduced-motion
   off-switch are all shared. Two footer-specific corrections:

   1. .nav-ico hard-codes `color: var(--text-subtle)`, which is tuned for the
      light header and goes almost invisible on the footer's dark ground.
      Inheriting the link colour keeps icon and label together in every theme.
   2. `will-change: transform` is dropped. It is worth the compositor memory
      for five icons in a sticky header; the footer carries eighteen, all
      below the fold, and promoting every one of them to its own layer costs
      more than the animation is worth.
   ------------------------------------------------------------------------- */
.pub-footer .nav-ico {
  color: inherit;
  will-change: auto;
  flex-shrink: 0;
}
.pub-footer .footer-links a:hover .nav-ico,
.pub-footer .footer-bottom-links a:hover .nav-ico { color: inherit; }

/* -------------------------------------------------------------------------
   Back to top

   Sits at --z-sticky: above content, deliberately BELOW the header, the
   drawer and the consent banner (z-index 1200). A control that floats over a
   consent dialog is both ugly and, since the banner gates advertising, a
   thing that must never be obscured.

   Hidden by default via opacity + pointer-events rather than display, so the
   entrance can be transitioned. Unlike the consent banner this may safely
   default to invisible: it is a pure enhancement injected by script, so if
   nothing runs there is simply no button, not a stranded invisible one.
   ------------------------------------------------------------------------- */
.to-top {
  position: fixed;
  right: var(--space-5);
  bottom: var(--space-5);
  z-index: var(--z-sticky);

  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  cursor: pointer;

  color: var(--ink-0);
  background: linear-gradient(145deg, var(--brand-500), var(--brand-700));
  box-shadow: 0 8px 22px rgb(0 0 0 / 0.24);

  opacity: 0;
  visibility: hidden;
  transform: translateY(14px) scale(0.86);
  transition: opacity var(--duration-base) var(--ease-out),
              transform var(--duration-base) var(--ease-spring),
              box-shadow var(--duration-fast) var(--ease-out);
}

/* `visibility` IS transitioned here, and only here, on purpose: it is the one
   property that must not be interpolated, so it is stepped at the end of the
   fade-out and immediately on the way in. Without it the invisible button
   would still swallow clicks in the corner of every page. */
.to-top { transition-property: opacity, transform, box-shadow, visibility; }

.to-top.is-on {
  opacity: 1;
  visibility: visible;
  transform: translateY(0) scale(1);
}

.to-top:hover  { box-shadow: 0 12px 28px rgb(0 0 0 / 0.32); }
.to-top:active { transform: translateY(1px) scale(0.96); }
.to-top:focus-visible { outline: 3px solid var(--brand-200); outline-offset: 3px; }

/* The arrow lifts on hover, and nudges once when the button first appears so
   the eye catches it without anything moving permanently. */
.to-top-arrow {
  position: relative;
  transition: transform var(--duration-base) var(--ease-spring);
}
.to-top:hover .to-top-arrow { transform: translateY(-2px); }
.to-top.is-on .to-top-arrow { animation: to-top-nudge 620ms var(--ease-out) 1; }

@keyframes to-top-nudge {
  0%, 100% { transform: translateY(0); }
  40%      { transform: translateY(-4px); }
}

/* Reading-progress ring around the button: it fills as the reader descends,
   so the control reports where they are instead of merely offering an exit. */
.to-top-ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);      /* start the sweep at 12 o'clock */
  pointer-events: none;
}
.to-top-ring circle {
  fill: none;
  stroke-width: 2.5;
}
.to-top-track { stroke: rgb(255 255 255 / 0.22); }
.to-top-bar {
  stroke: var(--ink-0);
  stroke-linecap: round;
  transition: stroke-dashoffset var(--duration-fast) linear;
}

/* Clear the mobile tab bar. Without this the button lands on top of it and
   covers a navigation target. */
@media (max-width: 768px) {
  .to-top {
    right: var(--space-4);
    bottom: calc(var(--bottom-nav-h) + var(--space-3));
    width: 42px;
    height: 42px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .to-top { transition: opacity 1ms linear, visibility 1ms linear; transform: none; }
  .to-top.is-on { transform: none; }
  .to-top.is-on .to-top-arrow { animation: none; }
  .to-top:hover .to-top-arrow { transform: none; }
  .to-top-bar { transition: none; }
}

/* The bottom-nav labels carry the site's real section names, including the
   long one ("Scholarships"). At five tabs on a 320px phone each tab has about
   58px, so the label is allowed to shrink and to be cut with an ellipsis
   rather than pushing the bar wider or forcing a shorter, inconsistent name. */
.mobile-nav-item span {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
@media (max-width: 380px) {
  .mobile-nav-item { padding-inline: 2px; font-size: 0.5625rem; }
}
