/**
 * BEACON DESIGN SYSTEM -- marketing site, public course directory/course
 * pages, admin dashboard, auth pages, and every shared base component.
 * Admin-only rules live in the separate beacon-admin.css, loaded only on
 * admin-context pages. Hand-written CSS custom properties + classes, no
 * build step (no npm/PostCSS/Vite).
 */

@import url('https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@500;600&display=swap');

:root {
  /* ---- Core tokens (see guidelines/Guidelines.md's "Core tokens" table
     in the Figma export -- exact same hex values, just as CSS custom
     properties instead of Tailwind's bg-background/text-foreground etc). */
  --beacon-bg: #f8f9fc;
  --beacon-fg: #0f1117;
  --beacon-card: #ffffff;
  --beacon-border: rgba(15, 17, 23, 0.08);
  --beacon-primary: #2563eb;
  --beacon-primary-hover: #1d4ed8;
  --beacon-primary-foreground: #ffffff;
  --beacon-muted: #f1f4f9;
  --beacon-muted-foreground: #64748b;
  /* Body-copy paragraph color -- darker than --beacon-slate-500 (still right
     for short labels/meta text) since long-form paragraphs read too light at that value. */
  --beacon-text-secondary: #424a56;

  /* ---- Blue scale (primary brand) ---- */
  --beacon-blue-50: #eff6ff;
  --beacon-blue-100: #dbeafe;
  --beacon-blue-200: #bfdbfe;
  --beacon-blue-300: #93c5fd;
  --beacon-blue-600: #2563eb;
  --beacon-blue-700: #1d4ed8;

  /* ---- Indigo (gradient partner to blue) ---- */
  --beacon-indigo-500: #6366f1;
  --beacon-indigo-600: #4f46e5;

  /* ---- Slate scale (neutrals) ---- */
  --beacon-slate-50: #f8fafc;
  --beacon-slate-100: #f1f5f9;
  --beacon-slate-200: #e2e8f0;
  --beacon-slate-400: #94a3b8;
  --beacon-slate-500: #64748b;
  --beacon-slate-700: #334155;
  --beacon-slate-900: #0f172a;

  /* ---- Semantic / status ---- */
  --beacon-emerald-400: #34d399;
  --beacon-amber-400: #fbbf24;
  --beacon-red-400: #f87171;
  --beacon-red-500: #ef4444;

  /* ---- Radius scale ---- */
  --beacon-radius-lg: 8px;
  --beacon-radius-xl: 12px;
  --beacon-radius-2xl: 16px;

  /* ---- Shadow scale ---- */
  --beacon-shadow-sm: 0 1px 2px rgba(15, 17, 23, 0.05);
  /* Same color/opacity as -sm, wider/softer spread -- its own token so editing
     it doesn't also shift .beacon-page-card/.beacon-toggle-thumb's tighter lift. */
  --beacon-shadow-soft: 0 1px 8px 2px rgba(15, 17, 23, 0.05);
  --beacon-shadow-md: 0 4px 6px -1px rgba(15, 17, 23, 0.1), 0 2px 4px -2px rgba(15, 17, 23, 0.06);
  --beacon-shadow-lg: 0 10px 15px -3px rgba(15, 17, 23, 0.08), 0 4px 6px -4px rgba(15, 17, 23, 0.05);
  --beacon-shadow-lg-slate: 0 10px 25px -5px rgba(226, 232, 240, 0.6), 0 4px 6px -4px rgba(226, 232, 240, 0.5);
  --beacon-shadow-xl-blue: 0 20px 25px -5px rgba(191, 219, 254, 0.5), 0 8px 10px -6px rgba(191, 219, 254, 0.4);
  --beacon-shadow-2xl: 0 25px 50px -12px rgba(15, 17, 23, 0.25);
}

/* ---- Base ---- */
body.beacon-body {
  margin: 0;
  background: var(--beacon-bg);
  color: var(--beacon-fg);
  /* --beacon-font-body is only set by renderTenantFontFaceTags() on the
     public directory/course pages; every other page falls back to Geist. */
  font-family: var(--beacon-font-body, 'Geist', 'Inter', sans-serif);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* Only the 5 marketing-context pages carry this class. Ensures the mobile
   offcanvas backdrop has real page background behind it on short pages.
   min-height, not height -- height: 100vh made terms.php's long content unscrollable. */
body.beacon-body.beacon-body-marketing {
  min-height: 100vh;
}

.beacon-body h1,
.beacon-body h2,
.beacon-body h3 {
  margin: 0;
  font-weight: 700;
  letter-spacing: -0.02em;
}

.beacon-body p {
  margin: 0;
}

/**
 * GOTCHA: `.beacon-body h1/h2/h3` and `.beacon-body p` above are both
 * specificity 0-1-1. A lone class selector (0-1-0) always loses to them
 * regardless of source order -- give new p/heading rules at least 0-1-1
 * (e.g. `p.your-class-name` or `.parent-scope .your-class-name`).
 */

/* Deliberately no blanket ".beacon-body a { color: inherit; }" rule --
   its higher specificity silently beat .beacon-btn-on-blue's own color,
   producing invisible white-on-white buttons in the CTA/pricing sections. */

/* Shared page container, used on every section. */
.beacon-container {
  max-width: 1152px;
  margin: 0 auto;
  padding: 0 1.5rem;
}

/* ---- Logo ---- */
.beacon-logo {
  display: inline-flex;
  align-items: center;
  gap: 0.65rem;
  text-decoration: none;
  color: var(--beacon-fg);
  /* Prevents the logo from shrinking/overlapping in a narrow nav row -- let
     a flex:1 sibling (nav links/buttons) give up space first instead. */
  flex-shrink: 0;
}

.beacon-logo-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--beacon-blue-600);
  border-radius: 9999px;
  flex-shrink: 0;
}

.beacon-logo-mark svg {
  display: block;
}

.beacon-logo-word {
  font-weight: 600;
  font-size: 1.125rem;
  letter-spacing: -0.025em;
}

/* A tenant's own uploaded logo -- caps height only, width scales
   proportionally, since real logos are rarely square. */
.beacon-directory-logo-image {
  display: block;
  height: 36px;
  width: auto;
  max-width: 220px;
  object-fit: contain;
}

/* ---- Nav ---- */
.beacon-nav {
  position: sticky;
  top: 0;
  z-index: 50;
  height: 64px;
  /* Without this, border-bottom adds on top of 64px, throwing off every
     calc(100vh - 64px) elsewhere that assumes an exact nav height. */
  box-sizing: border-box;
  background: rgba(255, 255, 255, 0.8);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border-bottom: 1px solid var(--beacon-border);
}

.beacon-nav-inner {
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
}

/* Used instead of .beacon-container on the admin nav -- the dashboard's
   full-bleed content below has no centered max-width, so the nav matches. */

/* Used instead of .beacon-container on the directory nav, matching the
   directory content's own 1400px max-width. */
.beacon-nav-inner-directory {
  max-width: 1400px;
  margin: 0 auto;
  padding: 0 2rem;
}

.beacon-nav-links {
  display: flex;
  align-items: center;
  gap: 2rem;
  flex: 1;
  justify-content: center;
}

.beacon-nav-link {
  font-size: 0.875rem;
  color: var(--beacon-slate-500);
  text-decoration: none;
  transition: color 0.15s ease;
  /* Border reserved (transparent) on every link, not just .active ones,
     so switching a link to active doesn't shift layout by suddenly
     adding border height that wasn't accounted for before. */
  padding-bottom: 4px;
  border-bottom: 2px solid transparent;
}

.beacon-nav-link.active {
  color: var(--beacon-blue-600);
  font-weight: 600;
  border-bottom-color: var(--beacon-blue-600);
}

.beacon-nav-link:hover {
  color: var(--beacon-fg);
}

.beacon-nav-right {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  flex-shrink: 0;
}

/* ---- Buttons ---- */
.beacon-btn {
  /* Without this, a width: 100% button (e.g. .beacon-category-nav-footer's)
     overflows its container by its own left/right padding. */
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.7rem 1.4rem;
  /* --beacon-button-radius is only set by renderTenantThemeTags() on the
     public directory/course pages; other pages fall back to -radius-xl. */
  border-radius: var(--beacon-button-radius, var(--beacon-radius-xl));
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
  border: 1px solid transparent;
}

.beacon-btn-sm {
  padding: 0.5rem 1rem;
}

/* --beacon-primary-button-bg/-fg are separate tokens (not overrides of
   --beacon-blue-600), set only by renderTenantThemeTags() on public
   directory/course pages, scoped to buttons only -- links/badges/focus
   rings keep reading --beacon-blue-* directly. */
.beacon-btn-primary {
  background: var(--beacon-primary-button-bg, var(--beacon-blue-600));
  color: var(--beacon-primary-button-fg, var(--beacon-primary-foreground));
  box-shadow: var(--beacon-shadow-sm);
}

.beacon-btn-primary:hover {
  background: var(--beacon-primary-button-bg-hover, var(--beacon-blue-700));
}

.beacon-btn-secondary {
  background: var(--beacon-card);
  border-color: var(--beacon-border);
  color: var(--beacon-slate-700);
  box-shadow: var(--beacon-shadow-sm);
}

.beacon-btn-secondary:hover {
  border-color: var(--beacon-slate-400);
}

/* Used on a blue background (the CTA section) -- white fill instead of
   blue, so it still reads as the primary action against that backdrop. */
.beacon-btn-on-blue {
  background: #ffffff;
  color: var(--beacon-blue-600);
}

.beacon-btn-on-blue:hover {
  background: var(--beacon-blue-50);
}

/* For genuinely irreversible destructive actions only (e.g. "Delete
   account"). #b91c1c gives white text 6.66:1 contrast, past WCAG AA's 4.5:1. */
.beacon-btn-danger {
  background: #b91c1c;
  color: #ffffff;
  box-shadow: var(--beacon-shadow-sm);
}

.beacon-btn-danger:hover {
  background: #991b1b;
}

.beacon-btn-ghost-on-blue {
  background: transparent;
  border-color: rgba(255, 255, 255, 0.35);
  color: #ffffff;
}

.beacon-btn-ghost-on-blue:hover {
  border-color: rgba(255, 255, 255, 0.6);
}

.beacon-btn-link {
  /* Button-chrome reset -- required for the <button type="submit"> uses
     in the import wizard that need to look like a plain link. */
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  cursor: pointer;
  font-size: 0.875rem;
  color: var(--beacon-slate-500);
  text-decoration: none;
  transition: color 0.15s ease;
}

.beacon-btn-link:hover {
  color: var(--beacon-fg);
}

/* ---- Badges / tags ---- */
.beacon-badge {
  display: inline-flex;
  align-items: center;
  font-size: 0.75rem;
  padding: 0.15rem 0.65rem;
  border-radius: 9999px;
  border: 1px solid;
  font-weight: 500;
}

.beacon-badge-blue {
  background: var(--beacon-blue-50);
  color: var(--beacon-blue-700);
  border-color: var(--beacon-blue-100);
}

/* A neutral "not live, but not an error" status (e.g. a Draft directory).
   #334155-on-#f1f5f9 measures 9.45:1, past the 4.5:1 WCAG AA minimum. */
.beacon-badge-slate {
  background: var(--beacon-slate-100);
  color: var(--beacon-slate-700);
  border-color: var(--beacon-slate-200);
}

/* "Set up, but needs a look" -- amber, not red, since a stale sync isn't
   necessarily broken. #92400e-on-#fffbeb measures 6.84:1, past WCAG AA. */
.beacon-badge-amber {
  background: #fffbeb;
  color: #92400e;
  border-color: #fde68a;
}

.beacon-badge-full {
  background: #fef2f2;
  /* red-500 measures only 3.44:1 here, below WCAG AA's 4.5:1; red-700 clears it at 5.91:1. */
  color: #b91c1c;
  border-color: #fecaca;
}

/* ---- Feature card ---- */
.beacon-feature-card {
  background: var(--beacon-card);
  border: 1px solid var(--beacon-border);
  border-radius: var(--beacon-radius-2xl);
  padding: 1.5rem;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.beacon-feature-card:hover {
  border-color: var(--beacon-blue-200);
  box-shadow: var(--beacon-shadow-md);
}

.beacon-feature-icon {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--beacon-radius-xl);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 1rem;
}

.beacon-feature-card h3 {
  font-size: 0.95rem;
  font-weight: 600;
  margin-bottom: 0.5rem;
}

.beacon-feature-card p {
  font-size: 0.85rem;
  color: var(--beacon-text-secondary);
  line-height: 1.6;
}

/* ---- Pricing card ---- */
.beacon-pricing-card {
  border-radius: var(--beacon-radius-2xl);
  border: 1px solid var(--beacon-border);
  padding: 1.75rem;
  display: flex;
  flex-direction: column;
  background: var(--beacon-card);
  box-shadow: var(--beacon-shadow-sm);
}

.beacon-pricing-card.highlighted {
  position: relative;
  border-color: var(--beacon-blue-600);
  background: var(--beacon-blue-600);
  color: #ffffff;
  box-shadow: var(--beacon-shadow-xl-blue);
}

/* "Recommended" -- our own stated opinion, not an unverifiable popularity claim. */
.beacon-pricing-badge {
  position: absolute;
  top: -0.75rem;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.7rem;
  font-weight: 500;
  background: var(--beacon-indigo-500);
  color: #ffffff;
  padding: 0.25rem 0.8rem;
  border-radius: 9999px;
}

.beacon-pricing-plan {
  font-size: 0.875rem;
  color: var(--beacon-slate-500);
  margin-bottom: 0.5rem;
}

.beacon-pricing-card.highlighted .beacon-pricing-plan {
  /* blue-100 measures 4.24:1 here, just under WCAG AA's 4.5:1; blue-50 clears it at 4.75:1. */
  color: var(--beacon-blue-50);
}

.beacon-pricing-price {
  font-size: 2.25rem;
  font-weight: 700;
  letter-spacing: -0.02em;
}

.beacon-pricing-price span {
  font-size: 0.9rem;
  font-weight: 400;
  opacity: 0.7;
}

.beacon-pricing-limit {
  font-size: 0.8rem;
  color: var(--beacon-slate-500);
  margin-bottom: 1.25rem;
}

.beacon-pricing-card.highlighted .beacon-pricing-limit {
  /* blue-100 measures 4.24:1 here, just under WCAG AA's 4.5:1; blue-50 clears it at 4.75:1. */
  color: var(--beacon-blue-50);
}

.beacon-pricing-features {
  list-style: none;
  margin: 0 0 1.5rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  flex: 1;
}

.beacon-pricing-features li {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  font-size: 0.85rem;
}

.beacon-pricing-features svg {
  flex-shrink: 0;
  margin-top: 0.15rem;
  color: var(--beacon-blue-600);
}

.beacon-pricing-card.highlighted .beacon-pricing-features svg {
  color: #ffffff;
}

/* ---- Live/status indicators ---- */
.beacon-live-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 9999px;
  background: var(--beacon-emerald-400);
  animation: beacon-pulse 2s ease-in-out infinite;
  flex-shrink: 0;
}

@keyframes beacon-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}

.beacon-floating-badge {
  position: absolute;
  background: #ffffff;
  border: 1px solid var(--beacon-border);
  border-radius: var(--beacon-radius-xl);
  box-shadow: var(--beacon-shadow-lg);
  padding: 0.6rem 1rem;
  font-size: 0.85rem;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.beacon-promo-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  background: var(--beacon-blue-600);
  color: #ffffff;
  font-size: 0.8rem;
  font-weight: 500;
  padding: 0.4rem 0.9rem;
  border-radius: 9999px;
  box-shadow: var(--beacon-shadow-md);
}

/* ---- Section eyebrow + heading pattern -- Guidelines.md's "every major
   section uses this three-line opener" rule. */
.beacon-eyebrow {
  display: block;
  font-family: 'Geist Mono', monospace;
  font-size: 0.75rem;
  color: var(--beacon-blue-600);
  text-transform: uppercase;
  letter-spacing: 0.15em;
  font-weight: 500;
}

/* h2.beacon-section-heading, not just .beacon-section-heading -- the
   specificity trap this file's top gotcha comment warns about. */
h2.beacon-section-heading {
  font-size: clamp(22.4px, 16px + 2vw, 40px);
  line-height: 1.2;
  margin-top: 0.5rem;
  margin-bottom: 0.4rem;
}

.beacon-section-sub {
  color: var(--beacon-text-secondary);
  max-width: 32rem;
  margin-top: 0.75rem;
  font-size: 0.95rem;
}

.beacon-section-header {
  text-align: center;
  margin: 0 auto 3rem;
}

.beacon-section-header .beacon-section-sub {
  margin-left: auto;
  margin-right: auto;
}

/* ---- Sections ---- */
.beacon-section {
  padding: 5rem 0;
}

.beacon-section-tight {
  padding: 2.5rem 0;
}

.beacon-section-bordered {
  border-top: 1px solid var(--beacon-border);
  border-bottom: 1px solid var(--beacon-border);
}

.beacon-section-border-bottom {
  border-bottom: 1px solid var(--beacon-border);
}

.beacon-section-muted {
  background: rgba(241, 245, 249, 0.6);
}

/* Explicit #ffffff, not left to fall through to body's slightly-tinted
   --beacon-bg -- the two read as visibly different against white cards. */
.beacon-section-white {
  background: #ffffff;
}

/* ---- Hero ---- */
.beacon-hero {
  background: linear-gradient(180deg, var(--beacon-blue-50) 0%, var(--beacon-bg) 65%);
  position: relative;
  overflow: hidden;
  padding: 4.5rem 0 5rem;
}

/* Less top padding once .beacon-hero-grid stacks below 850px. */
@media (max-width: 849px) {
  .beacon-hero {
    padding-top: 2.5rem;
  }

  /* Hidden once stacked -- "View live demo" already scrolls to the real, interactive section. */
  .beacon-mockup-wrap {
    display: none;
  }
}

.beacon-hero-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 3rem;
  align-items: center;
  position: relative;
  z-index: 1;
}

.beacon-hero-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  background: var(--beacon-blue-50);
  color: var(--beacon-blue-700);
  border: 1px solid var(--beacon-blue-100);
  font-size: 0.8rem;
  font-weight: 500;
  padding: 0.4rem 0.9rem;
  border-radius: 9999px;
  margin-bottom: 1.5rem;
}

.beacon-hero h1 {
  /* Fluid clamp() scales continuously with viewport width instead of
     snapping at fixed breakpoints. */
  font-size: clamp(34px, 27.4545px + 2.0455vw, 52px);
  line-height: 1.08;
  letter-spacing: -0.03em;
}

.beacon-hero-gradient-text {
  background: linear-gradient(90deg, var(--beacon-blue-600), var(--beacon-indigo-500));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.beacon-hero-body {
  margin-top: 1.25rem;
  font-size: 1.05rem;
  color: var(--beacon-text-secondary);
  max-width: 30rem;
  line-height: 1.6;
}

.beacon-hero-actions {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  margin-top: 2rem;
  flex-wrap: wrap;
}

.beacon-hero-proof {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1.75rem;
  font-size: 0.85rem;
  color: var(--beacon-slate-500);
}

/* ---- Hero browser mockup ---- */
.beacon-mockup-wrap {
  position: relative;
}

.beacon-mockup {
  background: #ffffff;
  border-radius: var(--beacon-radius-2xl);
  box-shadow: var(--beacon-shadow-2xl);
  overflow: hidden;
  border: 1px solid var(--beacon-border);
}

.beacon-mockup-chrome {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.65rem 0.9rem;
  background: var(--beacon-slate-50);
  border-bottom: 1px solid var(--beacon-border);
}

.beacon-mockup-dots {
  display: flex;
  gap: 0.35rem;
}

.beacon-mockup-dots span {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 9999px;
  background: var(--beacon-slate-200);
}

.beacon-mockup-url {
  flex: 1;
  background: #ffffff;
  border: 1px solid var(--beacon-border);
  border-radius: var(--beacon-radius-lg);
  padding: 0.3rem 0.8rem;
  font-size: 0.75rem;
  color: var(--beacon-slate-500);
  text-align: center;
}

.beacon-mockup-body {
  padding: 1.1rem;
}

/* Compact overrides for the real directory classes this hero mockup reuses,
   scoped under .beacon-mockup-body so live tenant directories are untouched. */
.beacon-mockup-body .beacon-directory-filters-card {
  padding: 0.85rem 1rem;
}

.beacon-mockup-body .beacon-directory-search {
  margin-bottom: 0.6rem;
}

.beacon-mockup-body .beacon-directory-filter-row {
  gap: 0.6rem;
}

.beacon-mockup-body .beacon-input {
  padding: 0.4rem 0.65rem;
  font-size: 0.8rem;
}

.beacon-mockup-body .beacon-checkbox-row {
  padding-bottom: 0.4rem;
}

.beacon-mockup-body .beacon-checkbox-row label {
  font-size: 0.8rem;
}

.beacon-mockup-body .beacon-directory-card {
  padding: 1rem;
}

.beacon-mockup-body .beacon-directory-card-badges {
  margin-bottom: 0.5rem;
}

/* Truncates visually after 2 lines beyond the server-side char cutoff.
   -webkit-line-clamp has full evergreen support despite the prefix. */
.beacon-mockup-body .beacon-directory-card-desc {
  margin-bottom: 0.5rem;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.beacon-mockup-live {
  position: absolute;
  top: -0.9rem;
  right: 1rem;
}

.beacon-course-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 1rem;
}

/* #live-demo sits on a muted background nearly indistinguishable from the
   real directory card background, so it needs white cards -- scoped here,
   not on the shared classes, so real tenant directories are untouched. */
#live-demo .beacon-directory-card,
#live-demo .beacon-directory-filters-card {
  background: #ffffff;
}

/* ---- Feature / testimonial / pricing grids ---- */
.beacon-grid-3 {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
}

.beacon-grid-2 {
  display: grid;
  grid-template-columns: 1fr;
  gap: 3rem;
}

/* ---- CTA section ---- */
.beacon-cta {
  padding: 6rem 0;
  background: linear-gradient(135deg, var(--beacon-blue-600), var(--beacon-indigo-600));
  background-image:
    radial-gradient(rgba(255, 255, 255, 0.18) 1.5px, transparent 1.5px),
    linear-gradient(135deg, var(--beacon-blue-600), var(--beacon-indigo-600));
  background-size: 32px 32px, cover;
  color: #ffffff;
  text-align: center;
}

.beacon-cta h2 {
  font-size: clamp(22.4px, 16px + 2vw, 40px);
  letter-spacing: -0.02em;
  line-height: 1.15;
}

.beacon-cta p {
  margin-top: 1.25rem;
  color: var(--beacon-blue-100);
  font-size: 1rem;
  max-width: 34rem;
  margin-left: auto;
  margin-right: auto;
}

.beacon-cta-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  margin-top: 2rem;
  flex-wrap: wrap;
}

.beacon-trust-trio {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.75rem;
  margin-top: 1.75rem;
  flex-wrap: wrap;
  font-size: 0.85rem;
  /* blue-100 measures 5.15:1 against this gradient, past WCAG AA's 4.5:1. */
  color: var(--beacon-blue-100);
}

.beacon-trust-trio span {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

/* ---- Footer ---- */
.beacon-footer {
  background: #ffffff;
  padding: 2.5rem 0;
  border-top: 1px solid var(--beacon-border);
}

.beacon-footer-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1.5rem;
}

.beacon-footer-links {
  display: flex;
  gap: 1.75rem;
  flex-wrap: wrap;
}

.beacon-footer-links a {
  font-size: 0.85rem;
  color: var(--beacon-slate-500);
  text-decoration: none;
}

.beacon-footer-links a:hover {
  color: var(--beacon-fg);
}

/* The one icon-only link in this row -- centers it on the adjacent text
   links' baseline and bumps the icon up from its own default 16px. */
.beacon-footer-social-link {
  display: inline-flex;
  align-items: center;
}

.beacon-footer-social-link svg {
  width: 20px;
  height: 20px;
}

.beacon-footer-copyright {
  font-family: 'Geist Mono', monospace;
  font-size: 0.78rem;
  color: var(--beacon-slate-500);
}

/* ---- Responsive ---- */
/* .beacon-hero-grid gets its own 850px breakpoint, separate from the
   shared 768px block below, since 768-850px left the hero cramped. */
@media (min-width: 850px) {
  .beacon-hero-grid {
    grid-template-columns: 1fr 1fr;
    gap: 3.5rem;
  }
}

@media (min-width: 768px) {
  .beacon-grid-3 {
    grid-template-columns: repeat(2, 1fr);
  }

  .beacon-grid-2 {
    grid-template-columns: 1fr 1fr;
  }

  .beacon-pricing-card.highlighted {
    transform: scale(1.03);
  }
}


@media (min-width: 1024px) {
  .beacon-grid-3 {
    grid-template-columns: repeat(3, 1fr);
  }
}


@media (max-width: 767px) {
  .beacon-nav-links {
    display: none;
  }

}


/* ---- Accessibility utilities ----
   Ported from style.css's identical rules -- login.php/signup.php load
   ONLY this stylesheet now (not style.css), so these need their own
   copy here rather than being missing entirely. Kept byte-for-byte
   equivalent on purpose, not reinvented. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: -100px;
  left: 1em;
  background: var(--beacon-fg);
  color: #ffffff;
  padding: 0.75em 1.25em;
  border-radius: 0 0 8px 8px;
  z-index: 100;
  font-family: 'Geist Mono', monospace;
  font-size: 0.85em;
  text-decoration: none;
}

.skip-link:focus {
  top: 0;
}

/* ---- Auth pages (login.php, signup.php) ---- */
.beacon-auth-page {
  /* Deliberately no min-height/viewport-height math -- the page is exactly
     as tall as its content needs, with no nav-height figure to keep in sync. */
  display: flex;
  justify-content: center;
  padding: 2.5rem 1.5rem 3rem;
}

/* .beacon-content-card is the same card, a neutral name for non-auth uses. */
.beacon-auth-card,
.beacon-content-card {
  width: 100%;
  max-width: 420px;
  background: #ffffff;
  border: 1px solid var(--beacon-border);
  border-radius: var(--beacon-radius-2xl);
  padding: 2.5rem;
  box-shadow: var(--beacon-shadow-sm);
}

.beacon-content-card h1 {
  font-size: 1.4rem;
  margin-bottom: 0.75rem;
}

.beacon-content-card p {
  color: var(--beacon-text-secondary);
  font-size: 0.9rem;
  line-height: 1.6;
  margin-bottom: 1rem;
}

/* h2/ul/li for the long-form prose pages (privacy.php, terms.php).
   h2 matches the global reset's 0-1-1 specificity (see top-of-file gotcha). */
.beacon-content-card h2 {
  font-size: 1.05rem;
  margin: 1.75rem 0 0.5rem;
}

.beacon-content-card ul {
  color: var(--beacon-text-secondary);
  font-size: 0.9rem;
  line-height: 1.6;
  margin: 0 0 1rem;
  padding-left: 1.25rem;
}

.beacon-content-card li + li {
  margin-top: 0.35rem;
}

.beacon-auth-header {
  text-align: center;
  margin-bottom: 2rem;
}

.beacon-auth-header .beacon-logo-mark {
  margin: 0 auto 1.25rem;
}

.beacon-auth-heading {
  font-size: 1.4rem;
}

.beacon-auth-subheading {
  color: var(--beacon-text-secondary);
  font-size: 0.88rem;
  margin-top: 0.4rem;
}

.beacon-form-group {
  margin-bottom: 0.5rem;
}

/* Extra breathing room before the submit button, for pages (signup.php)
   with no inline link element to carry that spacing instead. */
.beacon-form-group:last-of-type {
  margin-bottom: 1.5rem;
}

.beacon-form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.85rem;
}

.beacon-auth-card input[type="text"],
.beacon-auth-card input[type="email"],
.beacon-auth-card input[type="password"] {
  width: 100%;
  box-sizing: border-box;
  padding: 0.65rem 0.9rem;
  border: 1px solid var(--beacon-border);
  border-radius: var(--beacon-radius-xl);
  font-family: inherit;
  font-size: 0.9rem;
  color: var(--beacon-fg);
  background: #ffffff;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.beacon-auth-card input[type="text"]:focus,
.beacon-auth-card input[type="email"]:focus,
.beacon-auth-card input[type="password"]:focus {
  outline: none;
  border-color: var(--beacon-blue-300);
  box-shadow: 0 0 0 4px var(--beacon-blue-50);
}

/* .password-field/.password-toggle: same class names style.css's
   version uses (public/assets/app.js's toggle wiring is pure class-
   name-based, so it works unmodified against either stylesheet) --
   just a different visual treatment here, since these two pages load
   beacon.css only, not style.css. */
.password-field {
  position: relative;
}

.password-field input {
  padding-right: 2.75rem;
}

.password-toggle {
  position: absolute;
  top: 50%;
  right: 0.6rem;
  transform: translateY(-50%);
  background: none;
  border: none;
  padding: 0.3rem;
  cursor: pointer;
  color: var(--beacon-slate-400);
  display: flex;
  transition: color 0.15s ease;
}

.password-toggle:hover {
  color: var(--beacon-slate-700);
}

.password-toggle:focus-visible {
  outline: 2px solid var(--beacon-blue-600);
  outline-offset: 2px;
  border-radius: 4px;
}

.password-toggle[aria-pressed="true"] {
  color: var(--beacon-blue-600);
}

.beacon-auth-inline-link {
  text-align: right;
  /* padding-bottom, not a top-side margin/padding -- this is the LAST
     thing before the submit button, so the space that actually needs
     controlling is BELOW it, not above (the field-to-link gap is
     .beacon-form-group's own margin-bottom, already tightened above). */
  padding-bottom: 1rem;
  font-size: 0.83rem;
}

.beacon-auth-inline-link a,
.beacon-auth-footer-link a {
  color: var(--beacon-blue-600);
  text-decoration: none;
}

.beacon-auth-inline-link a:hover,
.beacon-auth-footer-link a:hover {
  text-decoration: underline;
}

.beacon-btn-block {
  width: 100%;
  justify-content: center;
  /* Was on the general .beacon-btn class -- that broke every OTHER
     button's vertical alignment wherever it sits in a horizontal flex
     row (the nav, the hero's button pair, pricing card CTAs), since
     margin-bottom shifts a flex item's centered position. Scoped here
     instead: .beacon-btn-block is used ONLY by the full-width auth-form
     submit buttons (login.php/signup.php), which are exactly where this
     spacing is actually needed, and nowhere else. */
  margin-bottom: 1.1em;
}

.beacon-auth-footer-link {
  text-align: center;
  margin-top: 1.5rem;
  font-size: 0.88rem;
  color: var(--beacon-slate-500);
}

/* Separates the password form from the "Continue with Google" button;
   the "or" label makes the choice between sign-in paths explicit. */
.beacon-auth-divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1.25rem 0;
  font-size: 0.83rem;
  color: var(--beacon-slate-500);
}

.beacon-auth-divider::before,
.beacon-auth-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--beacon-border);
}

.beacon-btn-google svg {
  margin-right: 0.5rem;
}

.beacon-notification {
  padding: 0.85rem 1rem;
  border-radius: var(--beacon-radius-xl);
  margin-bottom: 1.25rem;
  font-size: 0.85rem;
}

.beacon-notification ul {
  margin: 0.4rem 0 0;
  padding-left: 1.1rem;
}

.beacon-notification.error {
  background: #fef2f2;
  color: #b91c1c;
  border: 1px solid #fecaca;
}

.beacon-notification.success {
  background: #ecfdf5;
  color: #047857;
  border: 1px solid #a7f3d0;
}

.beacon-notification.success a {
  color: #047857;
  font-weight: 600;
  word-break: break-all;
}

/* A long unbroken string (e.g. the one-time sync key) has no natural wrap
   points, so it overflowed the notification box without word-break. */
.beacon-notification code {
  display: block;
  word-break: break-all;
}

/* Used for the draft-preview banner -- "not live yet" is neither error nor
   success. amber-800-on-amber-50 is a standard, verified-AA warning pairing. */
.beacon-notification.warning {
  background: #fffbeb;
  color: #92400e;
  border: 1px solid #fde68a;
}

.beacon-notification.warning a {
  color: #92400e;
  font-weight: 600;
}

@media (max-width: 480px) {
  .beacon-form-row {
    grid-template-columns: 1fr;
  }

  .beacon-auth-card {
    padding: 1.75rem;
  }
}


/* ---- Logged-in admin nav (renderBeaconAdminTopnav()) ----
   Reuses .beacon-nav/.beacon-nav-inner/.beacon-logo/.beacon-nav-link, just
   with its own left/right grouping (two zones instead of the marketing nav's three). */

/* ---- Account menu -- native <details>/<summary>, re-themed from style.css's
   .account-menu-dropdown fade+slide-down technique. ---- */

/* ---- Admin nav: hamburger offcanvas, shown only below 900px. Unlike the
   directory nav's toggle/menu set, this has no desktop role at all -- every
   piece here is display: none and absent from layout above 900px, avoiding
   the position:fixed off-screen-layout-box horizontal-scroll bug. */

/* Larger tap targets, no underline-on-active border -- a left accent reads
   more naturally than a bottom border in a full-height vertical list. */
.beacon-admin-nav-menu-links .beacon-nav-link {
  font-size: 1rem;
  padding-bottom: 0;
  border-bottom: none;
}

/* A real .beacon-btn, not a plain text link -- "Log out" needs to stand
   out from the link-styled nav destinations above it. */
.beacon-admin-nav-menu-logout-form .beacon-btn {
  width: 100%;
  justify-content: center;
}

@media (max-width: 900px) {
  /* Keeps exactly one way to reach these destinations visible at any width. */
  .beacon-admin-nav-links,
  .beacon-admin-nav-right .beacon-nav-link,
  .beacon-admin-nav-right .beacon-account-menu {
    display: none;
  }
}


/* ---- General-purpose content page (profile.php, admin settings panels) --
   a wider, padded card with visible labels, unlike the auth pages' sr-only pattern. ---- */

/* .beacon-input is its own class (not scoped to .beacon-auth-card) -- a
   plain input style usable anywhere, including pages with visible labels. */
.beacon-input {
  width: 100%;
  box-sizing: border-box;
  padding: 0.65rem 0.9rem;
  /* Darker than the shared --beacon-border, since a form field's edge needs
     to read as an interactive boundary, not just a content divider. */
  border: 1px solid rgb(15 17 23 / 20%);
  border-radius: var(--beacon-radius-xl);
  font-family: inherit;
  font-size: 0.9rem;
  color: var(--beacon-fg);
  background: #ffffff;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
  /* Form controls don't reliably inherit the page's line-height across
     browsers, falling back to a UA default tied to font metrics otherwise. */
  line-height: 1.6;
}

.beacon-input:focus {
  outline: none;
  border-color: var(--beacon-blue-300);
  box-shadow: 0 0 0 4px var(--beacon-blue-50);
}

/* One consistent dropdown arrow for every <select>, replacing each
   browser's own native arrow. Same chevron path as the mobile category
   dropdown's summary arrow, in --beacon-slate-400 (inlined as a hex value
   since a CSS custom property can't be read inside an SVG data URI). */
/* input.beacon-input[list] (the search-to-add datalist boxes) gets the
   same arrow as a real <select>, since it's presented the same way. */
select.beacon-input,
input.beacon-input[list] {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none'%3E%3Cpath d='M6 9l6 6 6-6' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.75rem center;
  background-size: 1rem;
  padding-right: 2.25rem;
  cursor: pointer;
}

/* `appearance: none` above doesn't suppress Chromium/Safari's own
   datalist "record finder" triangle -- it needs its own pseudo-selector. */
input.beacon-input[list]::-webkit-calendar-picker-indicator,
input.beacon-input[list]::-webkit-list-button {
  display: none !important;
}

/* select only: wraps a long selected option's text instead of clipping it.
   overflow: hidden stays as a safety net for a single unbreakable word. */
select.beacon-input {
  white-space: normal;
  overflow: hidden;
}

/* Custom file-picker control (filename on the left, solid blue button on
   the right). Not ::file-selector-button -- browsers always render that
   button first, no CSS way to get a right-aligned button from it. Instead
   the real <input type="file"> stays keyboard-operable but visually
   hidden, and its <label> natively opens the picker on click/Enter/Space. */

/* Focus lands on the real (hidden) input, so the visible ring is drawn on
   its sibling label via :focus-visible + the adjacent-sibling selector. */

/* Drag-and-drop highlight, toggled by file-upload-filename-display.js.
   Reuses the focus ring's blue -- both mean "about to receive input." */

/* Once a file's selected, the button switches to "Remove" (muted, with a
   left border so it doesn't blend into the white filename background). */

/* Needed as its own rule: .beacon-file-upload:hover's higher specificity
   would otherwise flip a hovered "Remove" button back to solid blue. */

/* A native <input type="color"> renders its own small color-swatch
   button internally -- .beacon-input's normal text-input padding just
   adds awkward empty space around that swatch rather than framing real
   text, so this trims it down to a compact, still-clearly-bordered
   swatch instead. */
.beacon-input[type="color"] {
  width: 3.5rem;
  height: 2.4rem;
  padding: 0.25rem;
  cursor: pointer;
}

/* ---- Admin dashboard: category rail + live preview + settings panel ----
   Full-bleed three-column layout, only one settings panel visible at a
   time via real ARIA tabs, progressively enhanced over plain anchor links. */
/* body.beacon-body-dashboard turns <body> into a full-viewport flex column
   (.beacon-nav auto-height, .beacon-admin-layout filling the rest) -- no
   hardcoded pixel height has to match the nav's real rendered size.
   position: fixed, not height: 100vh + overflow: hidden -- that combo
   doesn't reset an already-nonzero scroll position when widening back
   past the mobile breakpoint, leaving the layout visibly shifted up.
   position: fixed sidesteps this structurally: it paints relative to the
   viewport regardless of the document's scroll position. */
body.beacon-body-dashboard {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
}

body.beacon-body-dashboard .beacon-nav {
  flex-shrink: 0;
  /* No longer needs to be sticky -- the body itself is already non-scrolling. */
  position: static;
}

/* Desktop only -- the laptop-tier @media block below hides this once the
   sidebar collapses to an icon rail; the Overview panel covers narrower tiers. */

/* A real .beacon-btn, not a plain text link. Full width + centered
   content makes it read as a card-footer action. */
.beacon-category-nav-footer .beacon-btn {
  width: 100%;
  justify-content: center;
}

/* ---- Mobile category dropdown (900px and narrower only) ----
   A real, separate <details>/<summary> element, not a re-styled
   .beacon-category-nav -- same native disclosure pattern as the desktop
   account menu. Hidden outside its own breakpoint; exactly one of this
   and .beacon-category-nav is ever in the rendered layout at a time. */
/* margin, not padding on a full-bleed bar, makes this read as a card
   sitting in the page, edges aligned with the settings content. */

/* A real <svg>, not a CSS content: '▾' pseudo-element, so the open/closed
   flip is a plain CSS transform instead of swapping glyphs. */

/* details[open] is native browser state -- flips for free whether opened
   by a click or closed programmatically by wireCategoryDropdown(). */

/* Flush with the button's edges -- the button already carries its own
   margin, so insetting the menu again would misalign it. */

/* Floating card, same treatment as the marketing homepage's preview card. */

.beacon-admin-preview-frame {
  flex: 1;
  width: 100%;
  border: none;
}

/* Holds the toolbar and .beacon-admin-panels as one flex column -- owns
   the fixed width/height so the toolbar stays visible even in preview-only
   mode, when .beacon-admin-panels' own content is display: none. */

/* Toggled by wireAdminContentMode() whenever a button's target state is
   active, so the rotated arrow always points toward what it'll do next. */

/* ---- Admin dashboard content modes (Overview/settings <-> preview) ----
   Three states, one attribute, driven by wireAdminContentMode(): "split"
   (default, both visible), "panel-only" (settings fill the row), and
   "preview-only" (settings collapse to a slim toolbar rail). */

/* Side-by-side reads fine at 480px, but stacked once this column shrinks
   to just the toolbar's width -- two buttons wouldn't fit on one row. */

/* Renders outside any .beacon-admin-panel so it stays visible no matter
   which category is active -- used only for the category-less expired-session error. */

/* Default (no JavaScript): every panel renders in normal document flow,
   stacked and fully visible -- a long but functional settings page. */

/* Once admin-dashboard.js adds "js" to <html>, panels switch to
   single-panel tabs: every panel occupies the same CSS Grid cell, only
   .active is visible. grid-template-rows: 100% ties row height to the
   grid container instead of the tallest panel, so a short panel doesn't
   inherit a long one's empty scroll space; each panel scrolls its own overflow. */

/* Padding lives one level in from the element that actually scrolls, not
   on the scrolling element directly -- keeps the scrollbar flush with the
   column's true edge and gives real end-of-scroll spacing below content. */

/* Each subsection is its own bordered card, not a heading + divider --
   see .beacon-settings-box below. */

/* ---- Settings box: one bordered, rounded card per subsection ---- */
/* Same recipe as .beacon-page-card, but its own --beacon-shadow-soft
   token -- wants a wider, softer spread than -sm's tighter lift. */

/* ---- Hidden Courses: the "currently hidden" rule list ----
   One row per saved exclusion rule -- a type badge, the rule's label,
   and a small Remove action flush right. */

/* margin-left: auto on the Remove form (not the label, which grows via
   flex: 1) keeps it flush right even if a row's markup changes later. */

/* A lighter grouping within a settings box (e.g. each CTA button's own
   field cluster), for a subsection of one form, not a separate saveable concern. */

/* Pairs a settings box's h3 with its section-level on/off switch on one
   row, title left / switch right. */

/* Section on/off switch, built on the same "real control, visually hidden
   + styled label" pattern as file upload, for full native keyboard/screen-reader
   support. The visible "On"/"Off" text satisfies "never convey status via
   color/position alone" for everyone, not just sighted users. */

/* Not the "shrink to 1px + clip" hidden-input technique used elsewhere --
   that made a focused checkbox trigger the browser's scroll-into-view
   behavior, which reliably painted the entire settings column blank given
   this page's CSS-Grid-overlapping-panel layout. Positioned on its own
   visible track instead, which needs no scroll-into-view at all. */

/* Checked/focus state reads off the real input via the sibling combinator,
   same "focus lands on the real control, styling reacts" pattern used throughout. */

/* Driven by a JS-toggled class, not a CSS sibling combinator -- the state
   text sits outside .beacon-toggle, past where a plain combinator can reach. */

/* The rest of a toggleable box's content -- dimmed and disabled (not
   deleted) when its switch is off, so an admin can still see what they'd
   filled in. Deliberately not opacity: it promotes a new compositing
   layer, which combined with this page's grid-overlap layout reliably
   painted the whole settings column blank when scrolling then toggling. */

.beacon-toggle-dimmable .beacon-input:disabled,
.beacon-toggle-dimmable select:disabled {
  background: var(--beacon-slate-50);
  color: var(--beacon-slate-400);
  cursor: not-allowed;
}

/* Scoped to boxes, not the shared .beacon-btn class -- margin-bottom
   there once broke every other button's flex-row alignment site-wide. */
.beacon-settings-box .beacon-btn {
  margin-bottom: 1rem;
}

/* Un-zeroes .beacon-body p's global margin reset, scoped to the box --
   settings-box label/note/status copy needs real breathing room. */

/* ---- Admin dashboard, laptop tier (1280px and narrower) ----
   A 260px nav plus 480px settings column squeeze the live preview
   uncomfortably narrow below this width. Two changes: the category rail
   shrinks to an icon-plus-label column, and "split" mode is overridden
   to render like "panel-only" -- no room for a three-way split, so the
   settings panel takes the row with a single "show preview" toggle. */
@media (max-width: 1280px) {

  /* No room for this text at an 84px icon rail -- the Overview panel covers it instead. */

  /* The label span is the one without .beacon-category-icon. */

  /* display: flex + justify-content: center centers this icon-only button --
     .beacon-btn is inline-flex, and auto margins don't center inline boxes. */

  /* Icon-only at this width -- the full label had nowhere to go in an
     84px column. Visually hidden, not removed, so the accessible name stays. */
  .beacon-category-nav-footer .beacon-btn {
    width: auto;
    padding: 0.6rem;
  }

  /* Same visually-hidden-but-announced recipe as .sr-only, duplicated since
     the label needs to stay visible above this breakpoint. */

  /* Redundant at this width -- "split" already renders like "panel-only". */
  #admin-panels-expand {
    display: none;
  }
}


/* ---- Desktop toolbar: one arrow, not two, outside "split" ----
   Both buttons stay visible only while data-content-mode is "split" --
   once either is clicked and the mode moves to an extreme, this hides
   the other button, leaving only the one that leads back to split. */
@media (min-width: 1281px) {

  /* Exact complement of .beacon-greeting's own display: none above --
     exactly one of the two greeting copies is ever visible at a time. */
}


/* ---- Admin dashboard, mobile tier (900px and narrower) ----
   Even an 84px icon rail stops being workable below this width -- the
   category nav becomes a dropdown, and the content area becomes a single
   stacked pane: settings by default, or the preview taking the full pane. */
@media (max-width: 900px) {
  /* position: static puts the body back in normal, scrolling document
     flow -- a fixed element stays clipped to one viewport-height regardless
     of height/overflow, so leaving it fixed would still cut off content. */
  body.beacon-body-dashboard {
    position: static;
    height: auto;
    overflow: visible;
  }

  /* The desktop CSS Grid crossfade needs a definite height to clip
     against; this tier is height: auto by design, so grid-template-rows
     falls back to sizing on the tallest panel even while invisible --
     display: none/block excludes a panel from layout entirely instead. */

  /* The persistent column is fully removed, not shrunk -- .beacon-category-dropdown
     below is its replacement at this width. */

  /* No column-toggle toolbar here -- .beacon-admin-mobile-preview-toggle
     below is the one fixed button that replaces it. */

  /* Settings is the only thing in normal document flow; the preview,
     when shown, is a position: fixed overlay on top instead of sharing the row. */

  /* A real modal (margin + dimmed backdrop), not a full-bleed overlay, so
     it reads as a floating card over the dashboard. Top inset clears
     .beacon-nav's 64px rather than 0 like the other sides -- otherwise
     this modal's z-index also covered the nav's own hamburger toggle,
     making it unreachable while the preview was open. */
}


/* ---- Mobile preview toggle + backdrop (900px and narrower only) ----
   The toggle is fixed to the bottom of the viewport, always reachable and
   (via a higher z-index) still clickable to close the preview while open.
   The backdrop is a real, separate element, always in the DOM, shown only
   via the sibling-combinator rule whenever preview-only mode is active. */

@media (max-width: 900px) {

  /* pointer-events: none -- purely a visual dimming layer; the one real
     close control is the fixed bottom button. Without this, the backdrop's
     z-index would also block clicks to the nav bar's hamburger toggle underneath it. */
}


/* ---- Public course directory (public/index.php) ----
   Inverted from every other beacon page (white body, off-white cards
   instead of the reverse) -- a deliberate design call, not an inconsistency. */
body.beacon-body-directory {
  background: #ffffff;
}

/* ---- Directory nav: custom links + CTA buttons ---- */

.beacon-directory-nav-toggle {
  /* Hidden on desktop/tablet -- only shown inside the mobile @media blocks below. */
  display: none;
}

/* A plain <div> -- an earlier <dialog>-based version caused nearly every
   bug this feature went through. Desktop/tablet: an always-visible flex
   row; mobile @media blocks below turn it into an offcanvas panel. */
.beacon-directory-nav-menu {
  display: flex;
  align-items: center;
  flex: 1;
  min-width: 0;
  margin-left: 1.5rem;
  /* Fallback for buttons-with-no-links, so they sit flush right. */
  justify-content: flex-end;
}

.beacon-directory-nav-links {
  display: flex;
  align-items: center;
  gap: 1.75rem;
  flex: 1;
  min-width: 0;
}

/* No CTA buttons: links right-align within the FULL remaining nav width
   (there's nothing else to share it with). */
.beacon-directory-nav-menu:not(.has-buttons) .beacon-directory-nav-links {
  justify-content: flex-end;
}

/* WITH CTA buttons: links center within their own flex:1 box, up to
   where the fixed-width buttons sibling starts. */
.beacon-directory-nav-menu.has-buttons .beacon-directory-nav-links {
  justify-content: center;
}

.beacon-directory-nav-buttons {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  flex-shrink: 0;
  margin-left: 1.5rem;
}

/* Colors are all set inline per-button (admin-chosen values) -- this
   class only owns layout, never a color, so it can't override branding. */
.beacon-directory-cta-button {
  display: inline-flex;
  align-items: center;
  padding: 0.55rem 1.1rem;
  font-size: 0.85rem;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  transition: opacity 0.15s ease;
}

.beacon-directory-cta-button:hover {
  opacity: 0.85;
}

.beacon-directory-cta-button:focus-visible {
  outline: 3px solid var(--beacon-primary);
  outline-offset: 2px;
}

/* Only meaningful once the nav menu is an offcanvas panel -- hidden here
   since a close button makes no sense on the desktop flex row. */
.beacon-directory-nav-close {
  display: none;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--beacon-radius-lg);
  border: none;
  background: transparent;
  color: var(--beacon-slate-500);
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
}

.beacon-directory-nav-close:hover {
  background: var(--beacon-muted);
  color: var(--beacon-fg);
}

.beacon-directory-nav-close:focus-visible {
  outline: 3px solid var(--beacon-primary);
  outline-offset: 2px;
}

/* Never shown on desktop/tablet -- only relevant once the panel is an offcanvas. */
.beacon-directory-nav-backdrop {
  display: none;
}

/* ---- Mobile offcanvas: two breakpoint tiers, same rules ----
   A single fixed breakpoint can't fit every tenant -- $navMenuIsWide
   picks .beacon-directory-nav-menu--wide server-side from the actual
   link/button count. Two genuinely duplicated blocks (@media can't read
   a custom property, so there's no way to parameterize one block's
   trigger width). Tuned empirically (900px/1150px) against real CCRI data. */
@media (max-width: 900px) {
  /* The offcanvas panel is position: fixed + translateX(100%) even while
     closed, which doesn't remove its layout box from the page's
     scrollable width -- both html and body need overflow-x: hidden,
     since html is the actual root scrolling box browsers measure. */
  html, body.beacon-body-directory {
    overflow-x: hidden;
  }

  .beacon-directory-nav-toggle:not(.beacon-directory-nav-toggle--wide) {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    flex-shrink: 0;
    border: 1px solid var(--beacon-border);
    border-radius: var(--beacon-radius-lg);
    background: #ffffff;
    color: var(--beacon-fg);
    cursor: pointer;
    margin-left: auto;
  }

  /* A fixed-position panel pinned to the right edge, independent of the nav
     bar's flow. 100dvh, not 100vh, so a mobile address bar doesn't clip it.
     visibility: hidden (not display: none) removes the closed state from
     the tab order while still letting transform transition smoothly. */
  .beacon-directory-nav-menu:not(.beacon-directory-nav-menu--wide) {
    /* Without this, padding adds on top of width/height (content-box
       default), rendering 368x924 instead of the intended 320x844. */
    box-sizing: border-box;
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    height: 100dvh;
    z-index: 40;
    width: min(320px, 85vw);
    flex-direction: column;
    align-items: stretch;
    /* Resets the desktop rule's justify-content: flex-end, which in this
       column direction pushed all content toward the panel's bottom. */
    justify-content: flex-start;
    gap: 1.25rem;
    margin: 0;
    padding: 3.5rem 1.5rem 1.5rem;
    background: #ffffff;
    box-shadow: var(--beacon-shadow-2xl);
    overflow-y: auto;
    transform: translateX(100%);
    visibility: hidden;
    transition: transform 0.25s ease, visibility 0.25s ease;
  }

  .beacon-directory-nav-menu:not(.beacon-directory-nav-menu--wide).is-open {
    transform: translateX(0);
    visibility: visible;
  }

  .beacon-directory-nav-menu:not(.beacon-directory-nav-menu--wide) .beacon-directory-nav-close {
    display: inline-flex;
    position: absolute;
    top: 1rem;
    right: 1rem;
  }

  /* Resets desktop's justify-content/flex, which otherwise stretched this
     box to fill the panel's height and centered links vertically. */
  .beacon-directory-nav-menu:not(.beacon-directory-nav-menu--wide) .beacon-directory-nav-links {
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    flex: none;
    gap: 0.85rem;
  }

  .beacon-directory-nav-menu:not(.beacon-directory-nav-menu--wide) .beacon-directory-nav-buttons {
    flex-direction: column;
    align-items: stretch;
    margin-left: 0;
  }

  .beacon-directory-nav-menu:not(.beacon-directory-nav-menu--wide) .beacon-directory-cta-button {
    justify-content: center;
  }

  /* A real, separate element, not a native <dialog> ::backdrop -- always
     in the DOM, shown/hidden by the same .is-open class as the panel. */
  /* top/left/right: 0 + explicit height, not inset: 0's bottom: 0 --
     .beacon-nav's backdrop-filter establishes a new containing block for
     fixed descendants, so inset: 0 only dimmed the nav's own 64px strip.
     100dvh sidesteps that wrong containing block entirely. */
  .beacon-directory-nav-backdrop {
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 100dvh;
    z-index: 39;
    background: rgba(15, 17, 23, 0.5);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.25s ease, visibility 0.25s ease;
  }

  .beacon-directory-nav-backdrop.is-open {
    opacity: 1;
    visibility: visible;
  }
}


/* Identical rules to the 900px block above, just triggered earlier for
   tenants with enough nav items (.beacon-directory-nav-menu--wide) that
   they'd otherwise start squishing before reaching 900px. */
@media (max-width: 1150px) {
  /* Same scrollWidth-overflow fix as the 900px tier's own body rule above. */
  html, body.beacon-body-directory {
    overflow-x: hidden;
  }

  .beacon-directory-nav-toggle--wide {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    flex-shrink: 0;
    border: 1px solid var(--beacon-border);
    border-radius: var(--beacon-radius-lg);
    background: #ffffff;
    color: var(--beacon-fg);
    cursor: pointer;
    margin-left: auto;
  }

  .beacon-directory-nav-menu--wide {
    /* Same box-sizing fix as the 900px tier's own non-wide rule above. */
    box-sizing: border-box;
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    height: 100dvh;
    z-index: 40;
    width: min(320px, 85vw);
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 1.25rem;
    margin: 0;
    padding: 3.5rem 1.5rem 1.5rem;
    background: #ffffff;
    box-shadow: var(--beacon-shadow-2xl);
    overflow-y: auto;
    transform: translateX(100%);
    visibility: hidden;
    transition: transform 0.25s ease, visibility 0.25s ease;
  }

  .beacon-directory-nav-menu--wide.is-open {
    transform: translateX(0);
    visibility: visible;
  }

  .beacon-directory-nav-menu--wide .beacon-directory-nav-close {
    display: inline-flex;
    position: absolute;
    top: 1rem;
    right: 1rem;
  }

  .beacon-directory-nav-menu--wide .beacon-directory-nav-links {
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    flex: none;
    gap: 0.85rem;
  }

  .beacon-directory-nav-menu--wide .beacon-directory-nav-buttons {
    flex-direction: column;
    align-items: stretch;
    margin-left: 0;
  }

  .beacon-directory-nav-menu--wide .beacon-directory-cta-button {
    justify-content: center;
  }

  /* top/left/right: 0 + explicit height, not inset: 0's bottom: 0 --
     .beacon-nav's backdrop-filter establishes a new containing block for
     fixed descendants, so inset: 0 only dimmed the nav's own 64px strip. */
  .beacon-directory-nav-backdrop {
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 100dvh;
    z-index: 39;
    background: rgba(15, 17, 23, 0.5);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.25s ease, visibility 0.25s ease;
  }

  .beacon-directory-nav-backdrop.is-open {
    opacity: 1;
    visibility: visible;
  }
}


/* Marketing homepage's own breakpoint, deliberately not the same 1150px
   value as the tenant directory's --wide tier -- --wide's threshold is
   tuned against real customer nav data, so this stays a separate class
   suffix rather than risk changing real tenant sites' collapse point. */
@media (max-width: 1000px) {
  html, body.beacon-body-marketing {
    overflow-x: hidden;
  }

  .beacon-directory-nav-toggle--marketing {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    flex-shrink: 0;
    border: 1px solid var(--beacon-border);
    border-radius: var(--beacon-radius-lg);
    background: #ffffff;
    color: var(--beacon-fg);
    cursor: pointer;
    margin-left: auto;
  }

  .beacon-directory-nav-menu--marketing {
    box-sizing: border-box;
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    height: 100dvh;
    z-index: 40;
    width: min(320px, 85vw);
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 1.25rem;
    margin: 0;
    padding: 3.5rem 1.5rem 1.5rem;
    background: #ffffff;
    box-shadow: var(--beacon-shadow-2xl);
    overflow-y: auto;
    transform: translateX(100%);
    visibility: hidden;
    transition: transform 0.25s ease, visibility 0.25s ease;
  }

  .beacon-directory-nav-menu--marketing.is-open {
    transform: translateX(0);
    visibility: visible;
  }

  .beacon-directory-nav-menu--marketing .beacon-directory-nav-close {
    display: inline-flex;
    position: absolute;
    top: 1rem;
    right: 1rem;
  }

  .beacon-directory-nav-menu--marketing .beacon-directory-nav-links {
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    flex: none;
    gap: 0.85rem;
  }

  .beacon-directory-nav-menu--marketing .beacon-directory-nav-buttons {
    flex-direction: column;
    align-items: stretch;
    margin-left: 0;
  }

  /* .beacon-btn's own base rule never sets justify-content -- every
     other use sits inline, sized to its own content, so it never
     needed one. This panel's align-items: stretch above (the rule
     immediately before this one) stretches "Get started" to the full
     panel width, which left its label sitting flush left in all that
     newly-added empty space instead of centered in it. */
  .beacon-directory-nav-menu--marketing .beacon-directory-nav-buttons .beacon-btn {
    justify-content: center;
  }

  /* "Log in" restyled to a real secondary button here only. Values copied
     from .beacon-btn-secondary rather than applying that class directly,
     since it carries admin-page tenant-branding assumptions this nav doesn't share. */
  .beacon-directory-nav-menu--marketing .beacon-directory-nav-buttons .beacon-btn-link {
    display: block;
    text-align: center;
    padding: 0.7rem 1.4rem;
    border: 1px solid var(--beacon-border);
    border-radius: var(--beacon-radius-xl);
    background: var(--beacon-card);
    color: var(--beacon-slate-700);
    box-shadow: var(--beacon-shadow-sm);
    font-size: 0.875rem;
    font-weight: 500;
  }

  .beacon-directory-nav-menu--marketing .beacon-directory-nav-buttons .beacon-btn-link:hover {
    border-color: var(--beacon-slate-400);
    color: var(--beacon-slate-700);
  }

  /* Same top/left/right: 0 + explicit height reasoning as the --wide
     tier's identical backdrop rule -- duplicated since media queries
     can't share rules across different breakpoint numbers. */
  .beacon-directory-nav-backdrop {
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 100dvh;
    z-index: 39;
    background: rgba(15, 17, 23, 0.5);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.25s ease, visibility 0.25s ease;
  }

  .beacon-directory-nav-backdrop.is-open {
    opacity: 1;
    visibility: visible;
  }
}


/* Full-bleed background band -- .beacon-directory-header keeps its own
   max-width/centering, so the background spans edge-to-edge while
   heading text stays aligned. --beacon-bg is the default of three
   tenant-configurable hero styles: flat color, background photo, or split layout. */
.beacon-directory-hero {
  background: var(--beacon-bg);
  /* Containing block/clip for .beacon-directory-hero-image/-scrim, only
     populated when the "Background image" style is active. */
  position: relative;
  overflow: hidden;
  /* Lives here, not on .beacon-directory-header -- overflow: hidden above
     traps a child's margin-bottom inside this box otherwise. */
  margin-bottom: 2.5rem;
}

/* Darkens the photo uniformly so white heading text stays legible.
   brightness(0.5) leaves worst-case (pure-white) photos at ~3.98:1 --
   the large H1 clears WCAG's 3:1 large-text floor, but the smaller
   breadcrumb links need 4.5:1; growing that text past the large-text
   threshold would close the gap. A deliberately accepted tradeoff for now. */
.beacon-directory-hero-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: brightness(0.5) saturate(140%);
  z-index: 0;
}

.beacon-directory-hero--image {
  /* A dark fallback while the photo loads, avoiding a flash of white
     text on white. */
  background: #14161c;
  min-height: 360px;
  display: flex;
  align-items: flex-end;
}

.beacon-directory-hero--image .beacon-directory-header {
  position: relative;
  z-index: 2;
  width: 100%;
  /* Base rule's 2.5rem margin-bottom still applies; doesn't collapse
     here since this is a flex child. */
}

/* Breadcrumb + heading text switch to white for legibility against the
   darkened photo -- icons already use stroke="currentColor", so this
   carries them along for free. */
.beacon-directory-hero--image .beacon-breadcrumbs a,
.beacon-directory-hero--image .beacon-breadcrumbs li,
.beacon-directory-hero--image .beacon-directory-header h1,
.beacon-directory-hero--image .beacon-directory-intro {
  /* Solid white, not translucent -- the brightness() calibration above
     was proven against solid white specifically. */
  color: #ffffff;
}

/* .beacon-breadcrumbs a:hover's blue hover color matched the white
   override's specificity, winning on hover via source order alone.
   Two extra ancestor classes here beat it regardless of source order.
   Underline gives hover its own visible feedback despite the same color. */
.beacon-directory-hero--image .beacon-breadcrumbs a:hover {
  color: #ffffff;
  text-decoration: underline;
}

/* At least as specific as the base li[aria-current="page"] rule, so it wins regardless of source order. */
.beacon-directory-hero--image .beacon-breadcrumbs li[aria-current="page"] {
  color: #ffffff;
}

/* The chevron separator is a masked shape, not text -- background-color
   controls it, so the plain "li" color override above doesn't reach it. */
.beacon-directory-hero--image .beacon-breadcrumbs li + li::before {
  background-color: #ffffff;
}

/* ---- "Side by side" hero style -- borrowed from the marketing
   homepage's .beacon-hero-grid. No white-text overrides needed here,
   unlike --image above: the photo sits in its own bounded panel. ---- */
/* Plain white, not --beacon-bg like the other styles -- deliberately
   pinned to white, not an oversight. */
.beacon-directory-hero--split {
  background: var(--beacon-card);
}

.beacon-directory-hero-split-grid {
  max-width: 1400px;
  margin: 0 auto;
  padding: 3rem 2rem;
  display: grid;
  grid-template-columns: 1fr;
  gap: 2.5rem;
  align-items: center;
}

@media (min-width: 768px) {
  .beacon-directory-hero-split-grid {
    grid-template-columns: 1fr 1fr;
    gap: 3.5rem;
  }
}


/* Neutralizes .beacon-directory-header's own max-width/padding here,
   since inside the split grid it'd double up with the grid's own --
   keeps the breadcrumb/h1/intro markup one shared copy for all three styles. */
.beacon-directory-hero--split .beacon-directory-header {
  max-width: none;
  margin: 0;
  padding: 0;
}

/* Matches the recommended 3:2 upload ratio -- a portrait panel would crop
   left/right, but hero_image_position only controls vertical placement. */
.beacon-directory-hero-split-image {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  display: block;
  border-radius: var(--beacon-radius-2xl);
  /* Visible only before the photo paints in, like the dark fallback above. */
  background: var(--beacon-slate-100);
}

/* Hidden below desktop, not stacked underneath the text -- a photo panel
   here would push real functionality further down on a small screen. */
@media (max-width: 767px) {
  .beacon-directory-hero-split-image {
    display: none;
  }
}


.beacon-directory-header {
  max-width: 1400px;
  margin: 0 auto;
  padding: 2.5rem 2rem;
}

.beacon-breadcrumbs {
  margin-bottom: 1rem;
}

.beacon-breadcrumbs ol {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.beacon-breadcrumbs li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.85rem;
  color: var(--beacon-slate-500);
}

/* A chevron between crumbs. background-color + mask, not <img>/inline
   <svg> -- lets a single background-color declaration control its color
   the same way `color` controls text, and stays silent to screen readers. */
.beacon-breadcrumbs li + li::before {
  content: "";
  display: inline-block;
  width: 12px;
  height: 12px;
  /* Matches the breadcrumb text color, not --beacon-border (too faint to read as a glyph). */
  background-color: var(--beacon-slate-500);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E") center / contain no-repeat;
}

.beacon-breadcrumbs a {
  display: inline-flex;
  align-items: center;
  color: var(--beacon-slate-500);
  text-decoration: none;
}

.beacon-breadcrumbs a:hover {
  color: var(--beacon-primary);
}

.beacon-breadcrumbs a:focus-visible {
  outline: 3px solid var(--beacon-primary);
  outline-offset: 2px;
  border-radius: var(--beacon-radius-lg);
}

/* Current page's crumb is plain text, darker than the rest so it reads as "you are here". */
.beacon-breadcrumbs li[aria-current="page"] {
  color: var(--beacon-fg);
  font-weight: 500;
}

.beacon-directory-header h1 {
  /* Covers both the directory page heading and a course page's title heading. */
  font-family: var(--beacon-font-heading, 'Geist', 'Inter', sans-serif);
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  margin: 0 0 0.4rem;
}

/* white-space: pre-line preserves an admin's typed line breaks without
   nl2br(). max-width caps line length in this full-bleed band. */
.beacon-directory-intro {
  margin: 0;
  max-width: 65ch;
  font-size: 1.05rem;
  line-height: 1.5;
  color: var(--beacon-text-secondary);
  white-space: pre-line;
}

.beacon-directory-filters {
  max-width: 1400px;
  margin: 0 auto;
  padding: 0 2rem 1.5rem;
}

.beacon-directory-filters-card {
  background: var(--beacon-bg);
  border: 1px solid var(--beacon-border);
  border-radius: var(--beacon-radius-2xl);
  padding: 1.25rem 1.5rem;
}

.beacon-directory-search {
  margin-bottom: 1rem;
}

.beacon-directory-filter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 1rem;
}

.beacon-directory-filter-group {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  flex: 1 1 160px;
  min-width: 140px;
}

.beacon-directory-filter-group .beacon-label {
  margin: 0;
}

/* Generic checkbox+label row, used by both the directory filters and the
   admin dashboard's longer-label toggles. Not flex: 1 -- shouldn't stretch
   like a dropdown. align-items: flex-start keeps the checkbox pinned to
   the label's first line when it wraps; white-space: normal lets it wrap
   instead of overflowing the settings column. */
.beacon-checkbox-row {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  flex: 0 0 auto;
  white-space: normal;
  /* Lines up with the adjacent <select> boxes' own ~2.6rem rendered height. */
  padding-bottom: 0.5rem;
}

.beacon-checkbox-row input[type="checkbox"] {
  width: 1.05rem;
  height: 1.05rem;
  accent-color: var(--beacon-primary);
}

.beacon-checkbox-row label {
  font-size: 0.88rem;
  color: var(--beacon-fg);
}

/* Background swatch picker -- real colored preview squares, since a
   label alone can't convey subtle differences like "Cool Gray" vs
   "Neutral Gray". Built like .beacon-toggle: a real, visually-hidden
   native radio paired with a styled label, not a custom JS widget. */
.beacon-swatch-group {
  display: flex;
  flex-wrap: wrap;
  gap: 0.85rem;
}

.beacon-swatch {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  cursor: pointer;
  width: 4.5rem;
}

.beacon-swatch-input {
  /* Visually hidden, not display: none -- stays in the tab order and
     keyboard-operable; :focus-visible below still needs to be able to
     target it. Same clip-based hiding technique as .sr-only. */
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.beacon-swatch-preview {
  display: block;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: var(--beacon-radius-lg);
  border: 1px solid var(--beacon-border);
  transition: box-shadow 0.12s ease, border-color 0.12s ease;
}

.beacon-swatch-input:checked ~ .beacon-swatch-preview {
  border-color: var(--beacon-primary);
  box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px var(--beacon-primary);
}

.beacon-swatch-input:focus-visible ~ .beacon-swatch-preview {
  outline: 2px solid var(--beacon-primary);
  outline-offset: 2px;
}

.beacon-swatch-name {
  font-size: 0.78rem;
  color: var(--beacon-slate-700);
  text-align: center;
}

/* One removable "chip" per currently-active filter, so filtering has a
   visible summary. This app's own blue accent, not the reference
   screenshot's green -- that's a different site's brand color. */
.beacon-directory-active-filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.85rem;
}

.beacon-directory-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0.4rem 0.35rem 0.75rem;
  border-radius: 999px;
  background: var(--beacon-blue-50);
  color: var(--beacon-blue-700);
  font-size: 0.82rem;
  font-weight: 500;
  border: 1px solid transparent;
}

.beacon-directory-filter-chip button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 999px;
  border: none;
  background: none;
  color: inherit;
  font-size: 0.95rem;
  line-height: 1;
  cursor: pointer;
  flex-shrink: 0;
}

.beacon-directory-filter-chip button:hover {
  background: rgba(29, 78, 216, 0.12);
}

/* Outline, not filled, so this "undo everything" control isn't mistaken
   for just another filter chip. Only rendered when 2+ filters are active. */
.beacon-directory-filter-chip--clear-all {
  background: #ffffff;
  color: var(--beacon-slate-500);
  border-color: var(--beacon-border);
  padding: 0.35rem 0.75rem;
  gap: 0.3rem;
}

.beacon-directory-filter-chip--clear-all:hover {
  color: var(--beacon-fg);
  border-color: var(--beacon-slate-500);
}

.beacon-directory-result-summary {
  max-width: 1400px;
  margin: 0 auto;
  padding: 0.5rem 2rem 1.5rem;
  font-size: 0.88rem;
  color: var(--beacon-text-secondary);
}

.beacon-directory-result-summary strong {
  color: var(--beacon-fg);
}

/* p.beacon-directory-freshness-note, not just the class alone -- see the
   top-of-file specificity gotcha for why a lone class would lose the reset. */
p.beacon-directory-freshness-note {
  margin: 0.3rem 0 0;
  font-size: 0.85rem;
}

/* Generic "?" trigger + native <dialog> pair, shared by the public seat-
   counts popup and admin info dialogs, one style instead of copy-pasting
   per instance. Neutral slate, not a tenant brand color, since this
   renders on every tenant's directory. */
.beacon-info-trigger {
  margin-left: 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.2rem;
  height: 1.2rem;
  border-radius: 999px;
  border: 1.5px solid var(--beacon-slate-500);
  background: #ffffff;
  color: var(--beacon-slate-500);
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  vertical-align: middle;
}

.beacon-info-trigger:hover {
  color: var(--beacon-fg);
  border-color: var(--beacon-fg);
}

.beacon-info-trigger:focus-visible {
  outline: 3px solid var(--beacon-primary);
  outline-offset: 2px;
}

/* Native <dialog> -- showModal()/close() (see the script near the
   filter JS below) give real focus-trapping and Escape-to-close for
   free, so this needs no custom modal library or manual ARIA wiring
   beyond aria-labelledby. */
.beacon-info-dialog {
  width: min(28rem, calc(100vw - 2rem));
  padding: 0;
  border: 1px solid var(--beacon-border);
  border-radius: var(--beacon-radius-2xl);
  box-shadow: var(--beacon-shadow-2xl);
}

.beacon-info-dialog::backdrop {
  background: rgba(15, 17, 23, 0.5);
}

.beacon-info-dialog-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.25rem 1.5rem;
  border-bottom: 1px solid var(--beacon-border);
}

.beacon-info-dialog-header h2 {
  font-size: 1.05rem;
  margin: 0;
}

.beacon-info-dialog-close {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--beacon-radius-lg);
  border: none;
  background: transparent;
  color: var(--beacon-slate-500);
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
}

.beacon-info-dialog-close:hover {
  background: var(--beacon-muted);
  color: var(--beacon-fg);
}

.beacon-info-dialog-close:focus-visible {
  outline: 3px solid var(--beacon-primary);
  outline-offset: 2px;
}

.beacon-info-dialog-body {
  padding: 1.25rem 1.5rem 1.5rem;
}

.beacon-info-dialog-body p {
  font-size: 0.88rem;
  line-height: 1.6;
  color: var(--beacon-slate-700);
  margin: 0 0 0.85rem;
}

.beacon-info-dialog-body p:last-child {
  margin-bottom: 0;
}

/* Deliberately namespaced .beacon-directory-grid/-card, not the shorter
   .beacon-course-grid/-card names -- those already belong to the
   unrelated marketing-homepage demo course card and caused real
   style bleed (a floating code badge, blue-on-hover titles) when shared. */
.beacon-directory-grid {
  max-width: 1400px;
  margin: 0 auto;
  padding: 0 2rem 4rem;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.25rem;
  align-items: stretch;
}

.beacon-directory-card {
  display: flex;
  flex-direction: column;
  background: var(--beacon-bg);
  border: 1px solid var(--beacon-border);
  border-radius: var(--beacon-radius-2xl);
  padding: 1.5rem;
  text-decoration: none;
  color: var(--beacon-fg);
  transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease;
}

.beacon-directory-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--beacon-shadow-lg-slate);
  border-color: var(--beacon-slate-200);
}

.beacon-directory-card:focus-visible {
  outline: 3px solid var(--beacon-primary);
  outline-offset: 2px;
}

.beacon-directory-card-code {
  /* Was hardcoded to 'Geist Mono' -- didn't follow the tenant's Body font choice. */
  font-family: var(--beacon-font-body, 'Geist', 'Inter', sans-serif);
  font-size: 0.78rem;
  letter-spacing: 0.03em;
  color: var(--beacon-slate-500);
  margin-bottom: 0.3rem;
}

/* .beacon-directory-card-title alone loses the global h2 margin reset --
   scoped under .beacon-directory-card to win, not by source order. */
.beacon-directory-card .beacon-directory-card-title {
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 0 0 1rem;
  line-height: 1.3;
  /* Explicit, belt-and-suspenders against cross-component color bleed. */
  color: var(--beacon-fg);
}

.beacon-directory-card-meta {
  font-size: 0.85rem;
  color: var(--beacon-slate-500);
  margin-bottom: 0.4rem;
}

.beacon-directory-card-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.75rem;
}

/* One uniform pill style -- icon + name (not color) is what tells
   Online/Hybrid/In-Person apart, per WCAG 1.4.1. */
.beacon-format-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  background: var(--beacon-muted);
  color: var(--beacon-slate-700);
  border-radius: var(--beacon-radius-lg);
  padding: 0.25rem 0.55rem;
  font-size: 0.75rem;
  font-weight: 500;
}

.beacon-directory-card-desc {
  font-size: 0.85rem;
  color: var(--beacon-text-secondary);
  line-height: 1.5;
  margin-bottom: 1rem;
}

/* Pushes the footer to the bottom no matter how much description text
   sits above it, so every card in a row lines up its footer. */
.beacon-directory-card-footer {
  margin-top: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding-top: 0.25rem;
}

.beacon-seats-open {
  font-size: 0.82rem;
  font-weight: 600;
  /* Follows a tenant's own primary color like .beacon-btn-primary does --
     but as text on a light card background, --beacon-seats-open-color is
     pre-darkened by ensureReadableColorAsText() to clear WCAG's 4.5:1,
     since a pale primary color has no button-background escape hatch here. */
  color: var(--beacon-seats-open-color, var(--beacon-blue-600));
}

/* Reads as a real button (round, clickable, background-colored) even
   though it isn't literally .beacon-btn-primary -- a real, reported gap
   otherwise: it kept the fixed beacon-blue background/white text even
   after a tenant picked their own primary color, since nothing pointed
   it at the same tenant-configurable tokens .beacon-btn-primary already
   uses. Same var()-with-fallback pattern, same reasoning -- see that
   class's own comment above. */
.beacon-directory-card-arrow {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: 999px;
  background: var(--beacon-primary-button-bg, var(--beacon-blue-600));
  color: var(--beacon-primary-button-fg, #ffffff);
}

.beacon-directory-empty-state {
  max-width: 1400px;
  margin: 0 auto;
  padding: 0 2rem 4rem;
  color: var(--beacon-text-secondary);
  font-size: 0.95rem;
}

@media (max-width: 1180px) {
  .beacon-directory-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}


@media (max-width: 860px) {
  .beacon-directory-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}


/* Hidden by default (desktop/tablet never show it -- the filter row
   below is always visible there already); only made visible inside the
   max-width: 560px block below. */
.beacon-directory-filters-toggle {
  display: none;
}

@media (max-width: 560px) {
  .beacon-directory-grid {
    grid-template-columns: 1fr;
  }

  .beacon-directory-filters-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    width: 100%;
    background: #ffffff;
    border: 1px solid var(--beacon-border);
    border-radius: var(--beacon-radius-xl);
    padding: 0.65rem 1rem;
    font-family: inherit;
    font-size: 0.88rem;
    font-weight: 500;
    color: var(--beacon-fg);
    cursor: pointer;
  }

  /* Collapsed by default; .expanded is toggled by the filters toggle button. */
  .beacon-directory-filter-row {
    display: none;
    flex-direction: column;
    align-items: stretch;
    /* Resets the desktop row's flex: 1 1 160px, which applies to a
       column's main axis too and stretched groups with uneven gaps. */
    gap: 1rem;
    margin-top: 1rem;
  }

  .beacon-directory-filter-row.expanded {
    display: flex;
  }

  .beacon-directory-filter-group {
    flex: none;
    min-width: 0;
  }

  .beacon-checkbox-row {
    padding-bottom: 0;
  }
}


/* ---- GUIDED IMPORT WIZARD (public/import.php) ----
   A wider variant of the .beacon-auth-page/.beacon-content-card pattern --
   the wizard's Map Fields step needs more horizontal room than a login card. */

/* The post-"Complete import" confirmation view -- centered and spaced,
   reading as a distinct destination rather than another form step. */

.beacon-wizard-complete .beacon-btn {
  text-decoration: none;
}

/* .beacon-wizard-card .beacon-wizard-subtitle -- two classes needed to
   beat .beacon-wizard-card p's 0-1-1 specificity (see top-of-file gotcha). */

/* Every plain paragraph in this wizard -- without this, the global
   .beacon-body p reset zeroes margin and leaves font-size/color unset. */

/* Help text under a field label, closer in scale to the label than body copy. */
.beacon-form-group p {
  margin: 0.35rem 0 0.85rem;
  font-size: 0.88rem;
}

/* Same specificity reasoning as .beacon-wizard-subtitle above. */

/* This wizard's own larger field-label size, overriding the shared
   .beacon-label used in the denser admin settings panels. */

/* More breathing room between upload fields than the shared
   .beacon-form-group's 0.5rem -- each field here is a bigger unit. */
.beacon-wizard-card .beacon-form-group {
  margin-bottom: 1.25rem;
}

/* ---- Step indicator -- purely visual orientation, not a clickable nav.
   Spans the full card width; the connecting line grows to fill each step's
   leftover space. ---- */

/* The last step has no trailing connector, so it's sized to its own
   content instead of flex:1 leaving dead space after its label. */

/* The connecting line between steps -- a pseudo-element on every step
   except the last, so PHP's step loop needs no "is this the last one" branching. */

/* Same specificity reasoning as .beacon-wizard-subtitle above. */

/* ---- Map Fields step ----
   One row per canonical field Course Beacon uses, not one per raw column
   in the uploaded file -- a small, bounded, predictable list. */

/* A permanent marker, not conditional on match status -- visible whether
   matched or not, like a red asterisk on a required field. Neutral slate,
   not red/amber -- those colors are reserved for the status button. */

/* The one clickable control per row -- status display and the "fix this"
   action are deliberately the same element. Text label always differs
   between states, not just color, per this project's WCAG standard. */
/* inline-flex + gap lays the pencil icon and text side by side -- the
   icon is what makes this read as "click to change" rather than a static badge. */

/* A visible border/shadow shift confirms interactivity once pointed at. */

/* No focus-visible style existed here before -- a real WCAG gap. Reuses
   the same outline treatment as .beacon-file-upload-input elsewhere. */

/* Genuinely trustworthy matches -- same blue this project uses for "safe to trust" elsewhere. */

/* The two tiers easiest to be confidently wrong: 'fuzzy' (text-similarity
   guess) and 'value-inferred' (guessed from column data, not header) --
   both deserve human confirmation. Amber, not red: still probably right. */

/* Nothing matched, but this field is genuinely optional -- deliberately
   low-emphasis, since leaving it unmatched is a legitimate choice. */
.beacon-wizard-status-btn--unmatched {
  background: #ffffff;
  color: var(--beacon-slate-700);
  border-color: var(--beacon-border);
}

/* Nothing matched AND this field is required -- the one status that
   will actually BLOCK submission (confirm_mapping's hard validation in
   public/import.php), so it gets real visual urgency, not just a
   "Required" tag someone could miss. */
.beacon-wizard-status-btn--unmatched-required {
  background: #fee2e2;
  color: #991b1b;
}

/* ---- Map Fields popup: pick a column for one field ----
   A plain, manually-controlled overlay <div>, not native <dialog> --
   <dialog>'s ::backdrop/showModal() behavior varies enough across
   browsers that a manual overlay + focus trap is the safer choice. */

/* [hidden]'s UA rule ties in specificity with a lone class, and author
   CSS wins ties -- so display: flex above silently overrode "hidden".
   class + attribute (0-2-0) is what actually beats it. */

/* Belt-and-suspenders with the overlay's own scroll lock. */

/* Visually hidden but still announced -- a placeholder alone isn't a
   substitute for a real accessible label. Same clip-to-1px technique as
   .beacon-file-upload-input elsewhere in this file. */

/* ---- Review step ---- */

/* The "not included yet" section -- deliberately calm/neutral, not
   amber .beacon-notification.warning, since nothing here needs fixing. */

/* ---- COURSE DETAIL PAGE (public/course.php) ----
   Shares its nav/hero/format-badge treatment with the directory page's
   rules above; everything below is specific to this page's content. */

/* Narrower than .beacon-directory-filters' 1400px -- a single course's
   content reads better as one focused column. */
/* Widened to match .beacon-directory-grid's 1400px once the section list
   became a 4-column grid; description text caps its own max-width instead. */
.beacon-course-detail {
  max-width: 1400px;
  margin: 0 auto;
  padding: 2rem 2rem 4rem;
}

.beacon-course-badges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1.25rem;
}

/* A stacked-text box -- a small detail line over a bold label line --
   matching the owner's own original CCRI reference screenshot, not the
   single-line pill this replaced. */
/* var(--beacon-bg), not var(--beacon-card) -- see .beacon-directory-hero
   --split's own comment above for why: this is a real product-page
   element (course.php), not one of the marketing-only cards
   --beacon-card still correctly serves. */
.beacon-course-info-badge {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: var(--beacon-bg);
  border: 1px solid var(--beacon-border);
  border-radius: var(--beacon-radius-lg);
  padding: 0.5rem;
  text-align: center;
}

.beacon-course-info-badge-detail {
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--beacon-text-secondary);
}

.beacon-course-info-badge-label {
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--beacon-slate-800);
}

/* p.beacon-course-description, not just .beacon-course-description --
   see this file's own GOTCHA comment (near .beacon-body p) for why a
   lone class selector silently loses its margin to that global reset
   without matching its element+class specificity. Same fix applied to
   every other <p>/<h2>/<h3> rule below. */
/* max-width caps these two paragraphs specifically at a readable line
   length, now that .beacon-course-detail itself is wide enough for a
   4-column section grid (see that rule's own comment) -- a paragraph
   stretched to the full 1400px would be uncomfortably wide to read. */
p.beacon-course-description {
  max-width: 760px;
  font-size: 1rem;
  line-height: 1.65;
  color: var(--beacon-text-secondary);
  margin: 0 0 1rem;
}

p.beacon-course-prereq {
  max-width: 760px;
  font-size: 0.92rem;
  color: var(--beacon-text-secondary);
  margin: 0 0 2rem;
}

p.beacon-course-prereq strong {
  color: var(--beacon-slate-700);
}

/* Centered -- along with the freshness note and filter card below it --
   matching the owner's own CCRI reference layout for this whole block,
   rather than staying left-aligned like the description/prerequisites
   text above it. */
h2.beacon-course-sections-heading {
  /* The other half of the heading-font scope (see .beacon-directory-header
     h1's own comment above) -- a real, named heading, not a repeated
     per-card label. */
  font-family: var(--beacon-font-heading, 'Geist', 'Inter', sans-serif);
  font-size: 1.4rem;
  margin: 0 0 0.25rem;
  text-align: center;
}

.beacon-course-detail .beacon-directory-freshness-note {
  margin-bottom: 1.25rem;
  text-align: center;
}

/* Full width of the section grid below it now (not capped narrower) --
   matches the owner's own CCRI reference, where the filter bar spans
   the same width as the cards underneath rather than sitting as a
   separate, narrower block above them. */
.beacon-course-detail .beacon-directory-filters-card {
  margin-bottom: 2rem;
}

.beacon-course-filter-count {
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--beacon-text-secondary);
  align-self: center;
}

/* Its own class, not a reuse of .beacon-directory-filter-row -- that
   class is coupled to the directory page's mobile toggle button, which
   this page doesn't have. This page's lighter filter set can just wrap/stack. */
.beacon-course-filter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 1rem;
}

@media (max-width: 560px) {
  .beacon-course-filter-row {
    flex-direction: column;
    align-items: stretch;
  }

  .beacon-course-filter-row .beacon-directory-filter-group {
    flex: none;
    min-width: 0;
  }
}


.beacon-term-group + .beacon-term-group {
  margin-top: 2.5rem;
}

h3.beacon-term-group-heading {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  font-size: 1.1rem;
  margin: 0 0 1rem;
}

.beacon-term-group-count {
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--beacon-text-secondary);
  letter-spacing: 0;
}

/* A grid of vertical cards, not a stacked list. Same 4/3/2/1 responsive
   tiers as .beacon-directory-grid above, for consistent breakpoints. */
.beacon-section-list {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.25rem;
  align-items: stretch;
}

@media (max-width: 1180px) {
  .beacon-section-list {
    grid-template-columns: repeat(3, 1fr);
  }
}


@media (max-width: 860px) {
  .beacon-section-list {
    grid-template-columns: repeat(2, 1fr);
  }
}


@media (max-width: 560px) {
  .beacon-section-list {
    grid-template-columns: 1fr;
  }
}


/* Two-tone card: light header band over a white body. flex-direction:
   column lets margin-top: auto push Register to a consistent baseline
   across a row regardless of each card's content length. */
/* var(--beacon-card), specifically not --beacon-bg -- deliberately pinned
   white; the header stays on --beacon-muted. */
.beacon-section-card {
  display: flex;
  flex-direction: column;
  background: var(--beacon-card);
  border: 1px solid var(--beacon-border);
  border-radius: var(--beacon-radius-xl);
  overflow: hidden;
}

.beacon-section-card-header {
  background: var(--beacon-muted);
  border-bottom: 1px solid var(--beacon-border);
  padding: 0.85rem 1rem;
}

.beacon-section-card-meeting {
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--beacon-slate-800);
  margin-bottom: 0.3rem;
}

.beacon-section-card-location {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.8rem;
  font-style: italic;
  color: var(--beacon-text-secondary);
}

.beacon-section-card-body {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  padding: 1rem;
  /* Pushes content below the last row down to the bottom of the card. */
  flex: 1;
}

.beacon-section-card-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
  color: var(--beacon-slate-700);
}

/* Status is conveyed by visible text as much as color, per WCAG 1.4.1.
   Colors reused from already-contrast-checked text elsewhere in this file. */
.beacon-section-status {
  font-weight: 600;
}

.beacon-section-status--open,
.beacon-section-status--few-seats-left {
  color: #047857;
}

.beacon-section-status--full {
  color: #b91c1c;
}

.beacon-section-status--unknown {
  color: var(--beacon-slate-500);
}

.beacon-section-card-term-instructor {
  font-size: 0.85rem;
  color: var(--beacon-slate-700);
  margin-top: 0.15rem;
}

.beacon-section-card-crn {
  font-size: 0.78rem;
  color: var(--beacon-slate-500);
  margin-bottom: 0.5rem;
}

.beacon-section-card-register {
  align-self: flex-start;
  /* Pins to the bottom of the card body regardless of content above it. */
  margin-top: auto;
}

/* ---- DIRECTORY FOOTER (renderBeaconDirectoryFooter()) ----
   Shared by index.php and course.php. Grey, not dark -- a dark footer
   would need a second "logo for dark backgrounds" upload. */

.beacon-directory-footer {
  background: var(--beacon-muted);
  margin-top: 3rem;
}

.beacon-directory-footer-inner {
  max-width: 1400px;
  margin: 0 auto;
  padding: 3rem 2rem;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 2.5rem;
}

.beacon-directory-footer-identity {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.75rem;
  max-width: 360px;
}

.beacon-directory-footer-logo {
  display: block;
  height: 28px;
  width: auto;
  max-width: 200px;
  object-fit: contain;
}

.beacon-directory-footer-name {
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--beacon-fg);
}

/* p.beacon-directory-footer-address, not just .beacon-directory-footer-address
   -- see this file's own GOTCHA comment (near .beacon-body p) for why a
   lone class selector silently loses its margin to that global reset.
   white-space: pre-line preserves an admin's own line breaks from the
   textarea, same convention as .beacon-directory-intro. */
p.beacon-directory-footer-address {
  margin: 0;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--beacon-text-secondary);
  white-space: pre-line;
}

.beacon-directory-footer-social {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.beacon-directory-footer-social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 999px;
  border: 1px solid var(--beacon-border);
  color: var(--beacon-slate-500);
  transition: color 0.15s ease, border-color 0.15s ease;
}

.beacon-directory-footer-social a:hover {
  color: var(--beacon-primary);
  border-color: var(--beacon-primary);
}

.beacon-directory-footer-social a:focus-visible {
  outline: 3px solid var(--beacon-primary);
  outline-offset: 2px;
}

.beacon-directory-footer-links {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

/* h3.beacon-directory-footer-links-heading -- same GOTCHA-driven
   specificity match as p.beacon-directory-footer-address above, this
   time against .beacon-body h1/h2/h3's own margin/letter-spacing reset. */
h3.beacon-directory-footer-links-heading {
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--beacon-slate-500);
  margin: 0;
}

.beacon-directory-footer-links ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.beacon-directory-footer-links a {
  font-size: 0.88rem;
  color: var(--beacon-text-secondary);
  text-decoration: none;
}

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

.beacon-directory-footer-links a:focus-visible {
  outline: 3px solid var(--beacon-primary);
  outline-offset: 2px;
  border-radius: var(--beacon-radius-sm);
}

.beacon-directory-footer-bottom {
  border-top: 1px solid var(--beacon-border);
}

.beacon-directory-footer-bottom-inner {
  max-width: 1400px;
  margin: 0 auto;
  padding: 1.25rem 2rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  font-size: 0.8rem;
  color: var(--beacon-slate-500);
}

.beacon-directory-footer-powered-by {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--beacon-slate-500);
  text-decoration: none;
}

.beacon-directory-footer-powered-by:hover {
  color: var(--beacon-primary);
}

.beacon-directory-footer-powered-by:focus-visible {
  outline: 3px solid var(--beacon-primary);
  outline-offset: 2px;
  border-radius: var(--beacon-radius-sm);
}

@media (max-width: 560px) {
  .beacon-directory-footer-inner {
    padding: 2.5rem 1.5rem;
    flex-direction: column;
    gap: 2rem;
  }

  .beacon-directory-footer-identity {
    max-width: none;
  }

  .beacon-directory-footer-bottom-inner {
    padding: 1.25rem 1.5rem;
    justify-content: flex-start;
  }
}

