/* ============================================================
   shahjada.pro — design system
   Hand-authored. No build step. No CDN.
   Layer order: reset -> tokens -> base -> layout -> components -> utilities
   ============================================================ */

/* ---------- 1. Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
img, picture, svg, video, canvas { display: block; max-width: 100%; height: auto; }
input, button, textarea, select { font: inherit; color: inherit; }
p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }
ul[class], ol[class] { list-style: none; padding: 0; }
a { color: inherit; }
table { border-collapse: collapse; }

/* ---------- 1b. Metric-matched fallback ----------
   Inter arrives from a CDN a beat after first paint. Swapping it in over a
   plain Arial fallback reflowed every line of text, which is where the article
   pages' layout shift came from. These overrides make the fallback occupy the
   same space as Inter, so the swap is invisible. */
@font-face {
  font-family: "Inter Fallback";
  src: local("Arial");
  ascent-override: 90.20%;
  descent-override: 22.48%;
  line-gap-override: 0%;
  size-adjust: 107.40%;
}

/* ---------- 2. Tokens ---------- */
:root {
  /* palette */
  --bg:            #08080b;
  --bg-alt:        #0e0e15;
  --surface:       #101016;
  --surface-2:     #16161f;
  --surface-3:     #1d1d28;
  --border:        #24242f;
  --border-strong: #33334a;

  --text:          #e9e9f2;
  --text-muted:    #a0a0b4;
  --text-faint:    #8a8aa0;   /* 5.8:1 on --bg-alt */

  --accent:        #6366f1;
  --accent-strong: #4f46e5;   /* button fill: 6.3:1 on white text */
  --accent-press:  #5850ec;   /* hover, still 5.5:1 */
  --accent-hi:     #818cf8;
  --accent-2:      #a855f7;
  --accent-soft:   rgba(99, 102, 241, .14);
  --accent-ring:   rgba(99, 102, 241, .45);

  --success:       #34d399;
  --success-soft:  rgba(52, 211, 153, .12);
  --warning:       #fbbf24;
  --warning-soft:  rgba(251, 191, 36, .12);
  --danger:        #f87171;
  --danger-soft:   rgba(248, 113, 113, .12);
  --info:          #60a5fa;
  --info-soft:     rgba(96, 165, 250, .12);

  --grad: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%);

  /* type */
  --font: "Inter", "Inter Fallback", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
          "Liberation Mono", monospace;

  /* fluid type scale — clamp(min, preferred, max) */
  --fs-xs:   .75rem;
  --fs-sm:   .875rem;
  --fs-base: 1rem;
  --fs-md:   1.0625rem;
  --fs-lg:   clamp(1.125rem, .3vw + 1.05rem, 1.25rem);
  --fs-xl:   clamp(1.25rem, .6vw + 1.1rem, 1.5rem);
  --fs-2xl:  clamp(1.5rem, 1.1vw + 1.25rem, 2rem);
  --fs-3xl:  clamp(1.875rem, 2vw + 1.35rem, 2.75rem);
  --fs-4xl:  clamp(2rem, 2.6vw + 1.3rem, 3.25rem);
  --fs-5xl:  clamp(2.5rem, 4vw + 1.4rem, 4.25rem);

  --lh-tight: 1.12;
  --lh-snug:  1.32;
  --lh-body:  1.68;

  /* space */
  --sp-1: .25rem;  --sp-2: .5rem;   --sp-3: .75rem;  --sp-4: 1rem;
  --sp-5: 1.25rem; --sp-6: 1.5rem;  --sp-8: 2rem;    --sp-10: 2.5rem;
  --sp-12: 3rem;   --sp-16: 4rem;   --sp-20: 5rem;   --sp-24: 6rem;

  /* radius */
  --r-sm: 8px; --r-md: 12px; --r-lg: 16px; --r-xl: 22px; --r-full: 999px;

  /* elevation */
  --sh-sm: 0 1px 2px rgba(0,0,0,.4);
  --sh-md: 0 4px 16px rgba(0,0,0,.35);
  --sh-lg: 0 18px 48px -12px rgba(0,0,0,.65);
  --sh-glow: 0 0 0 1px rgba(99,102,241,.2), 0 12px 40px -12px rgba(99,102,241,.45);

  /* motion */
  --ease: cubic-bezier(.22, .61, .36, 1);
  --t-fast: 140ms var(--ease);
  --t: 220ms var(--ease);
  --t-slow: 420ms var(--ease);

  --shell: 1200px;
  --shell-narrow: 760px;
  --nav-h: 68px;
}

/* ---------- 3. Base ---------- */
html { scroll-behavior: smooth; scroll-padding-top: calc(var(--nav-h) + 1rem); }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}

body {
  font-family: var(--font);
  font-size: var(--fs-md);
  line-height: var(--lh-body);
  color: var(--text);
  background: var(--bg);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  /* subtle depth without an image request */
  background-image:
    radial-gradient(60rem 40rem at 12% -8%, rgba(99,102,241,.10), transparent 60%),
    radial-gradient(50rem 34rem at 90% 4%, rgba(168,85,247,.08), transparent 62%);
  background-attachment: fixed;
}

h1, h2, h3, h4 { line-height: var(--lh-tight); font-weight: 700; letter-spacing: -.02em; }
h1 { font-size: var(--fs-4xl); }
h2 { font-size: var(--fs-3xl); }
h3 { font-size: var(--fs-xl); letter-spacing: -.01em; }
h4 { font-size: var(--fs-lg); letter-spacing: -.005em; }

a { text-decoration: none; transition: color var(--t-fast); }
a:hover { color: var(--accent-hi); }

/* visible keyboard focus everywhere, never on mouse click */
:focus-visible {
  outline: 2px solid var(--accent-hi);
  outline-offset: 2px;
  border-radius: 4px;
}

/* real scrollbar, just quieter — hiding it entirely breaks discoverability */
* { scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: var(--r-full); border: 3px solid var(--bg); }
*::-webkit-scrollbar-thumb:hover { background: #45455f; }

::selection { background: var(--accent); color: #fff; }

/* Reserve an icon's width before the Font Awesome webfont resolves. Without
   this, every icon is a zero-width empty element on first paint and the line
   jumps when the font lands. */
.fa-solid, .fa-regular, .fa-brands, .fas, .far, .fab {
  display: inline-block;
  min-width: 1em;
  text-align: center;
  font-style: normal;
}

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  padding: var(--sp-3) var(--sp-5);
  background: var(--accent); color: #fff; border-radius: 0 0 var(--r-md) 0;
  font-weight: 600;
}
.skip-link:focus { left: 0; }

/* ---------- 4. Layout ---------- */
.shell { width: 100%; max-width: var(--shell); margin-inline: auto; padding-inline: var(--sp-6); }
.shell-narrow { max-width: var(--shell-narrow); }
@media (max-width: 640px) { .shell { padding-inline: var(--sp-4); } }

.section { padding-block: clamp(3rem, 5vw, 4.75rem); }
.section-tight { padding-block: clamp(2.5rem, 4vw, 3.5rem); }
/* Page intro followed by a filter bar: collapse the doubled gap between them. */
.section-tight + .section-tight { padding-top: 0; }
.section-alt { background: var(--bg-alt); border-block: 1px solid var(--border); }

.page-main { flex: 1 0 auto; padding-top: var(--nav-h); }

/* Editorial split head. The old flex version pushed the heading to the far
   left and the CTA to the far right, leaving ~550px of dead space between two
   related things. A 7/5 grid puts the supporting copy and the CTA in the right
   column where they belong, and the rule line gives the section a top edge. */
.section-head {
  display: grid; grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-6); align-items: end;
  margin-bottom: var(--sp-10); padding-bottom: var(--sp-6);
  border-bottom: 1px solid var(--border);
}
.section-head > div { min-width: 0; }
.section-head h1 { font-size: var(--fs-4xl); }
/* When the head is a page header with nothing after it in its own
   section, its bottom margin just doubles the section padding. */
.section-head:last-child { margin-bottom: 0; }
.section-head-aside {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: var(--sp-5); min-width: 0;
}
@media (min-width: 900px) {
  .section-head { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); column-gap: var(--sp-12); }
  /* A lone CTA hugs the far edge; a CTA that follows a paragraph stays under
     it so the two read as one block. */
  .section-head-aside > .btn:only-child { align-self: flex-end; }
}
.eyebrow {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-xs); font-weight: 600; letter-spacing: .14em;
  text-transform: uppercase; color: var(--accent-hi); margin-bottom: var(--sp-3);
}
.eyebrow::before { content: ""; width: 22px; height: 2px; background: var(--grad); border-radius: 2px; }
.section-sub { color: var(--text-muted); font-size: var(--fs-base); margin-top: var(--sp-3); max-width: 52ch; }
.section-head-aside .section-sub { margin-top: 0; }

/* auto-fit grids: never a lonely stretched card, never a 3-col squeeze on mobile */
.grid { display: grid; gap: var(--sp-6); }
.grid-cards { grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); }
.grid-cards-wide { grid-template-columns: repeat(auto-fit, minmax(min(400px, 100%), 1fr)); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); }
.grid-split { grid-template-columns: 1fr; gap: var(--sp-12); align-items: start; }
@media (min-width: 900px) { .grid-split { grid-template-columns: 1.05fr .95fr; } }

.stack { display: flex; flex-direction: column; gap: var(--sp-4); }
.stack-lg { gap: var(--sp-6); }
.row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); }
.row-between { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--sp-4); }

/* ---------- 5. Navigation ---------- */
.nav {
  position: fixed; inset: 0 0 auto 0; z-index: 100;
  height: var(--nav-h);
  background: rgba(8, 8, 11, .72);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid transparent;
  transition: border-color var(--t), background var(--t), box-shadow var(--t);
}
.nav.is-stuck { border-bottom-color: var(--border); box-shadow: var(--sh-md); background: rgba(8,8,11,.88); }
.nav-inner { height: 100%; display: flex; align-items: center; justify-content: space-between; gap: var(--sp-6); }

/* The name is the logo. There is no monogram tile -- it only repeated in
   initials what the wordmark says in full, half a centimetre away. */
.brand { display: inline-flex; align-items: center; font-weight: 700; letter-spacing: -.02em; }
.brand:hover { color: var(--text); }
.brand-text { font-size: var(--fs-base); white-space: nowrap; }
/* The wordmark is now the only thing in the link, so it can never be hidden
   the way it used to be when the mark stood in for it. It steps down a size
   instead, to stay clear of the menu button on the narrowest screens. */
@media (max-width: 420px) {
  .brand-text { font-size: var(--fs-sm); }
}

.nav-links { display: none; align-items: center; gap: var(--sp-1); }
@media (min-width: 860px) { .nav-links { display: flex; } }
.nav-link {
  position: relative; padding: var(--sp-2) var(--sp-4);
  font-size: var(--fs-sm); font-weight: 500; color: var(--text-muted);
  border-radius: var(--r-md); transition: color var(--t-fast), background var(--t-fast);
}
.nav-link:hover { color: var(--text); background: var(--surface-2); }
.nav-link[aria-current="page"] { color: var(--text); }
.nav-link[aria-current="page"]::after {
  content: ""; position: absolute; left: 50%; bottom: 2px; translate: -50% 0;
  width: 16px; height: 2px; border-radius: 2px; background: var(--grad);
}

.nav-toggle {
  display: inline-grid; place-items: center; width: 42px; height: 42px;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--r-md); cursor: pointer; transition: border-color var(--t-fast);
}
.nav-toggle:hover { border-color: var(--border-strong); }
@media (min-width: 860px) { .nav-toggle { display: none; } }
.nav-toggle-bars { display: block; position: relative; width: 18px; height: 12px; }
.nav-toggle-bars span {
  position: absolute; left: 0; width: 100%; height: 2px; border-radius: 2px;
  background: var(--text); transition: transform var(--t), opacity var(--t-fast);
}
.nav-toggle-bars span:nth-child(1) { top: 0; }
.nav-toggle-bars span:nth-child(2) { top: 5px; }
.nav-toggle-bars span:nth-child(3) { top: 10px; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars span:nth-child(1) { transform: translateY(5px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars span:nth-child(3) { transform: translateY(-5px) rotate(-45deg); }

.nav-drawer {
  position: fixed; inset: var(--nav-h) 0 auto 0; z-index: 99;
  background: rgba(10,10,14,.97);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--border);
  display: grid; grid-template-rows: 0fr; opacity: 0; visibility: hidden;
  transition: grid-template-rows var(--t), opacity var(--t), visibility var(--t);
}
.nav-drawer.is-open { grid-template-rows: 1fr; opacity: 1; visibility: visible; }
.nav-drawer-inner { overflow: hidden; }
.nav-drawer-inner > div { padding: var(--sp-2) 0 var(--sp-5); }
@media (min-width: 860px) { .nav-drawer { display: none; } }
.nav-drawer a {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-4) var(--sp-2); font-weight: 500; color: var(--text-muted);
  border-bottom: 1px solid var(--border);
}
.nav-drawer a:last-child { border-bottom: 0; }
.nav-drawer a:hover { color: var(--text); }

/* ---------- 6. Buttons ---------- */
.btn {
  --btn-bg: var(--surface-2);
  --btn-fg: var(--text);
  --btn-bd: var(--border);
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  padding: .7rem 1.25rem; min-height: 44px;
  font-size: var(--fs-sm); font-weight: 600; line-height: 1;
  color: var(--btn-fg); background: var(--btn-bg);
  border: 1px solid var(--btn-bd); border-radius: var(--r-md);
  cursor: pointer; white-space: nowrap; text-align: center;
  transition: transform var(--t-fast), background var(--t-fast),
              border-color var(--t-fast), box-shadow var(--t-fast), opacity var(--t-fast);
}
.btn:hover { color: var(--btn-fg); border-color: var(--border-strong); transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn[disabled], .btn.is-busy { opacity: .55; pointer-events: none; }

.btn-primary { --btn-bg: var(--accent-strong); --btn-fg: #fff; --btn-bd: transparent; box-shadow: var(--sh-glow); }
.btn-primary:hover { --btn-bg: var(--accent-press); box-shadow: 0 0 0 1px rgba(129,140,248,.3), 0 16px 44px -14px rgba(99,102,241,.7); }
.btn-ghost { --btn-bg: transparent; }
.btn-ghost:hover { --btn-bg: var(--surface-2); }
.btn-danger { --btn-bg: var(--danger-soft); --btn-fg: var(--danger); --btn-bd: rgba(248,113,113,.3); }
.btn-danger:hover { --btn-bg: rgba(248,113,113,.2); --btn-bd: var(--danger); }
.btn-sm { padding: .4rem .75rem; min-height: 34px; font-size: var(--fs-xs); }
.btn-icon { padding: 0; width: 36px; min-height: 36px; }
.btn-block { width: 100%; }
.btn-full-mobile { width: 100%; }
@media (min-width: 560px) { .btn-full-mobile { width: auto; } }

.btn .ico-arrow, .link-arrow .ico-arrow { transition: transform var(--t-fast); }
.btn:hover .ico-arrow, .link-arrow:hover .ico-arrow { transform: translateX(3px); }

.link-arrow {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-sm); font-weight: 600; color: var(--accent-hi);
}

/* ---------- 7. Cards ---------- */
/* card is a flex column so .card-actions can be pinned with margin-top:auto.
   That is what keeps every button in a grid row on the same baseline. */
.card {
  display: flex; flex-direction: column; height: 100%;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); overflow: hidden;
  transition: border-color var(--t), transform var(--t), box-shadow var(--t);
}
.card-interactive:hover { border-color: var(--border-strong); transform: translateY(-3px); box-shadow: var(--sh-lg); }
.card-body { display: flex; flex-direction: column; flex: 1 1 auto; gap: var(--sp-3); padding: var(--sp-6); min-width: 0; }
.card-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); margin-top: auto; padding-top: var(--sp-5); }
.card-title { font-size: var(--fs-lg); }
/* Contact rows are labels, not headings — fs-lg made them compete with the
   section h2 right above them. */
.contact-label { font-size: var(--fs-base); font-weight: 600; }
.card-title a:hover { color: var(--accent-hi); }
.card-text { color: var(--text-muted); font-size: var(--fs-sm); line-height: 1.65; }

/* fixed-ratio media box keeps every card in a row identical up top */
.card-media { position: relative; aspect-ratio: 16 / 9; overflow: hidden; background: var(--surface-2); flex: 0 0 auto; }
.card-media img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--t-slow); }
.card-interactive:hover .card-media img { transform: scale(1.04); }
.card-media-fallback {
  display: grid; place-items: center;
  background:
    radial-gradient(circle at 1px 1px, rgba(255, 255, 255, .05) 1px, transparent 0) 0 0 / 16px 16px,
    linear-gradient(135deg, rgba(99, 102, 241, .16), rgba(168, 85, 247, .09));
}
.card-media-fallback i { font-size: 3.25rem; color: var(--accent-hi); opacity: .55; }

/* clamp text so descriptions of different lengths cannot skew card height */
.clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.clamp-3 { display: -webkit-box; -webkit-line-clamp: 3; line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }

.panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); padding: var(--sp-6); }
.panel-flush { padding: 0; overflow: hidden; }

/* ---------- 8. Badges and chips ---------- */
.badge {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: .25rem .65rem; border-radius: var(--r-full);
  font-size: var(--fs-xs); font-weight: 600; line-height: 1.6; white-space: nowrap;
  background: var(--surface-3); color: var(--text-muted); border: 1px solid var(--border);
}
.badge-accent  { background: var(--accent-soft);  color: var(--accent-hi); border-color: rgba(99,102,241,.28); }
.badge-success { background: var(--success-soft); color: var(--success);   border-color: rgba(52,211,153,.28); }
.badge-warning { background: var(--warning-soft); color: var(--warning);   border-color: rgba(251,191,36,.28); }
.badge-danger  { background: var(--danger-soft);  color: var(--danger);    border-color: rgba(248,113,113,.28); }
.badge-info    { background: var(--info-soft);    color: var(--info);      border-color: rgba(96,165,250,.28); }

.chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.chip {
  padding: .25rem .6rem; border-radius: var(--r-sm); font-size: var(--fs-xs);
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text-muted);
  transition: border-color var(--t-fast), color var(--t-fast);
}
a.chip:hover { border-color: var(--accent); color: var(--accent-hi); }

.meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); font-size: var(--fs-xs); color: var(--text-faint); }
.meta > * { display: inline-flex; align-items: center; gap: var(--sp-1); }

/* ---------- 9. Forms ---------- */
.field { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
.label { font-size: var(--fs-sm); font-weight: 600; }
.label .req { color: var(--accent-hi); }
.hint { font-size: var(--fs-xs); color: var(--text-faint); }
.error-text { font-size: var(--fs-xs); color: var(--danger); }

.input, .textarea, .select {
  width: 100%; padding: .7rem .9rem; min-height: 46px;
  font-size: var(--fs-sm); color: var(--text);
  background: var(--bg-alt); border: 1px solid var(--border); border-radius: var(--r-md);
  transition: border-color var(--t-fast), box-shadow var(--t-fast), background var(--t-fast);
}
.textarea { min-height: 120px; resize: vertical; line-height: 1.6; }
.textarea-code { font-family: var(--mono); font-size: var(--fs-sm); line-height: 1.6; }
.input::placeholder, .textarea::placeholder { color: var(--text-faint); }
.input:focus, .textarea:focus, .select:focus {
  outline: none; border-color: var(--accent); background: var(--surface);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
.input:focus-visible, .textarea:focus-visible, .select:focus-visible { outline: none; }
.input[aria-invalid="true"], .textarea[aria-invalid="true"] { border-color: var(--danger); }
.select {
  appearance: none; -webkit-appearance: none; padding-right: 2.4rem; cursor: pointer;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' fill='none' stroke='%23a0a0b4' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M3 5l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right .9rem center;
}

.search { position: relative; flex: 1 1 260px; min-width: 0; }
/* The right padding has to clear the submit button that sits on top of the
   field. It was 3rem against a 67px button, so the placeholder ran underneath
   it and got clipped. */
.search .input { padding-left: 2.6rem; padding-right: 6.5rem; text-overflow: ellipsis; }
.search-icon { position: absolute; left: .95rem; top: 50%; translate: 0 -50%; color: var(--text-faint); pointer-events: none; font-size: var(--fs-sm); }
.search-submit { position: absolute; right: .4rem; top: 50%; translate: 0 -50%; }

/* On narrow screens the word "Search" costs more room than the placeholder can
   spare, and the magnifier on the left already names the control. */
@media (max-width: 620px) {
  .search .input { padding-left: 2.4rem; padding-right: 3.2rem; }
  .search-icon { left: .8rem; }
  /* Hidden visually but kept in the accessibility tree — display:none would
     leave the button with an aria-hidden icon and no accessible name. */
  .search-submit-label {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  .search-submit { padding: 0; width: 34px; min-height: 34px; }

  /* iOS Safari zooms the whole page when a focused field is under 16px.
     Bumping the mobile font size is the standard way to stop that. */
  .input, .textarea, .select { font-size: 1rem; }
}

.checkbox { display: inline-flex; align-items: flex-start; gap: var(--sp-3); cursor: pointer; user-select: none; }
.checkbox input { margin-top: 2px; }
.checkbox input { width: 20px; height: 20px; accent-color: var(--accent); cursor: pointer; flex: 0 0 auto; }
/* The whole label is the target, so it is the label that has to clear 24px. */
.checkbox { min-height: 24px; }

.form-grid { display: grid; gap: var(--sp-5); grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); }
.form-actions {
  display: flex; flex-wrap: wrap; gap: var(--sp-3);
  padding-top: var(--sp-6); margin-top: var(--sp-2); border-top: 1px solid var(--border);
}

/* Honeypot: moved off-screen rather than display:none, because a number of
   spam bots skip inputs that are display:none but still fill positioned ones. */
.hp-field {
  position: absolute !important; left: -9999px !important; top: auto !important;
  width: 1px; height: 1px; overflow: hidden;
}

/* ---------- 10. Hero ---------- */
.hero { position: relative; overflow: hidden; padding-block: clamp(3rem, 8vw, 6rem) clamp(3.5rem, 8vw, 6rem); }
.hero-bg { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.hero-bg img { width: 100%; height: 100%; object-fit: cover; opacity: .13; }
.hero-bg::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(8,8,11,.45) 0%, rgba(8,8,11,.82) 55%, var(--bg) 100%);
}
.hero > .shell { position: relative; z-index: 1; }
.hero-inner { display: grid; gap: var(--sp-8); justify-items: center; text-align: center; }
@media (min-width: 900px) {
  .hero-inner { grid-template-columns: auto 1fr; justify-items: start; text-align: left; gap: var(--sp-12); align-items: center; }
}

.avatar-ring { position: relative; flex: 0 0 auto; padding: 4px; border-radius: var(--r-full); background: var(--grad); box-shadow: var(--sh-glow); }
.avatar-ring img {
  width: clamp(140px, 26vw, 208px); aspect-ratio: 1; border-radius: var(--r-full);
  object-fit: cover; border: 4px solid var(--bg);
}

.hero-title { font-size: var(--fs-5xl); margin-bottom: var(--sp-4); }
.hero-title .grad {
  background: linear-gradient(100deg, #fff 0%, var(--accent-hi) 45%, var(--accent-2) 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.hero-roles { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3); justify-content: center; margin-bottom: var(--sp-5); }
@media (min-width: 900px) { .hero-roles { justify-content: flex-start; } }
.hero-lead { color: var(--text-muted); font-size: var(--fs-lg); max-width: 56ch; margin-bottom: var(--sp-8); }
.hero-cta { display: flex; flex-wrap: wrap; gap: var(--sp-3); justify-content: center; }
@media (min-width: 900px) { .hero-cta { justify-content: flex-start; } }

.stat-strip {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(150px, 45%), 1fr));
  gap: var(--sp-4); margin-top: var(--sp-10);
}
.stat { padding: var(--sp-5); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); text-align: center; }
.stat-value { font-size: var(--fs-2xl); font-weight: 800; letter-spacing: -.03em; background: var(--grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
.stat-label { font-size: var(--fs-xs); color: var(--text-muted); text-transform: uppercase; letter-spacing: .1em; margin-top: var(--sp-1); }

/* ---------- 11. Tables ---------- */
/* Wrapper scrolls; the page body never does. */
.table-wrap { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.table { width: 100%; min-width: 760px; font-size: var(--fs-sm); text-align: left; }
.table thead th {
  padding: var(--sp-4) var(--sp-5); font-size: var(--fs-xs); font-weight: 600;
  text-transform: uppercase; letter-spacing: .08em; color: var(--text-faint);
  background: var(--bg-alt); border-bottom: 1px solid var(--border); white-space: nowrap;
}
.table tbody td { padding: var(--sp-4) var(--sp-5); border-bottom: 1px solid var(--border); vertical-align: middle; }
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr { transition: background var(--t-fast); }
.table tbody tr:hover { background: var(--surface-2); }
.table tbody tr.is-unread { background: rgba(99,102,241,.05); }
.table tbody tr.is-unread:hover { background: rgba(99,102,241,.09); }
.table .cell-actions { white-space: nowrap; }
.table .cell-actions .row { flex-wrap: nowrap; gap: var(--sp-2); }
.cell-title { font-weight: 600; color: var(--text); }
.cell-sub { font-size: var(--fs-xs); color: var(--text-faint); font-family: var(--mono); }
/* Give identity and date columns room so names and timestamps stop wrapping
   mid-word, and let long addresses truncate instead of stacking a letter deep. */
.cell-who { min-width: 210px; }
.cell-who-body { min-width: 0; }
.cell-who .cell-title { white-space: nowrap; }
/* An auto-layout table treats a long cell as very wide and starves its
   neighbours. Capping the preview inside a block element caps the column. */
.cell-preview { max-width: 52ch; }
.cell-nowrap { white-space: nowrap; }
.cell-truncate { display: block; max-width: 26ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 700px) {
  .table-stack .cell-who { min-width: 0; }
  /* The name may wrap here; nowrap is a desktop-column concern only. */
  .table-stack .cell-who .cell-title { white-space: normal; overflow-wrap: anywhere; }
  .table-stack .cell-truncate { max-width: 100%; white-space: normal; overflow-wrap: anywhere; }
  .table-stack .cell-nowrap { white-space: normal; }
}

/* Below 700px the admin tables reflow into stacked rows with labels.
   Nothing gets cut off and no horizontal scrolling is required. */
@media (max-width: 700px) {
  .table-stack .table { min-width: 0; }
  .table-stack thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .table-stack tbody tr {
    display: block; padding: var(--sp-4) var(--sp-5);
    border-bottom: 1px solid var(--border);
  }
  .table-stack tbody td { display: flex; gap: var(--sp-4); padding: var(--sp-2) 0; border: 0; align-items: flex-start; }
  .table-stack tbody td::before {
    content: attr(data-label); flex: 0 0 88px;
    font-size: var(--fs-xs); font-weight: 600; text-transform: uppercase;
    letter-spacing: .06em; color: var(--text-faint); padding-top: 2px;
  }
  .table-stack tbody td:empty { display: none; }
  .table-stack .cell-actions { white-space: normal; }
  .table-stack .cell-actions .row { flex-wrap: wrap; }
}

/* ---------- 12. Pagination ---------- */
.pagination { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: var(--sp-2); margin-top: var(--sp-12); }
.page-btn {
  display: inline-grid; place-items: center; min-width: 42px; height: 42px; padding: 0 var(--sp-3);
  font-size: var(--fs-sm); font-weight: 600;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md);
  color: var(--text-muted); transition: all var(--t-fast);
}
.page-btn:hover { color: var(--text); border-color: var(--border-strong); }
.page-btn[aria-current="page"] { background: var(--accent-strong); border-color: transparent; color: #fff; }
.page-btn.is-disabled { opacity: .35; pointer-events: none; }
.page-gap { color: var(--text-faint); padding-inline: var(--sp-1); }

/* ---------- 13. Alerts and toasts ---------- */
.toast-region {
  position: fixed; z-index: 150; top: calc(var(--nav-h) + var(--sp-4));
  right: var(--sp-4); left: var(--sp-4);
  display: flex; flex-direction: column; align-items: flex-end; gap: var(--sp-3);
  pointer-events: none;
}
@media (min-width: 560px) { .toast-region { left: auto; max-width: 400px; } }
.toast {
  pointer-events: auto; width: 100%;
  display: flex; align-items: flex-start; gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  background: var(--surface-2); border: 1px solid var(--border);
  border-left: 3px solid var(--text-faint);
  border-radius: var(--r-md); box-shadow: var(--sh-lg);
  font-size: var(--fs-sm);
  animation: toast-in 280ms var(--ease) both;
}
.toast.is-leaving { animation: toast-out 240ms var(--ease) both; }
.toast-success { border-left-color: var(--success); }
.toast-success .toast-icon { color: var(--success); }
.toast-error   { border-left-color: var(--danger); }
.toast-error .toast-icon { color: var(--danger); }
.toast-info    { border-left-color: var(--info); }
.toast-info .toast-icon { color: var(--info); }
.toast-warning { border-left-color: var(--warning); }
.toast-warning .toast-icon { color: var(--warning); }
.toast-icon { flex: 0 0 auto; margin-top: 2px; }
.toast p { flex: 1 1 auto; min-width: 0; }
.toast-close { flex: 0 0 auto; background: none; border: 0; cursor: pointer; color: var(--text-faint); padding: 0 2px; }
.toast-close:hover { color: var(--text); }

@keyframes toast-in  { from { opacity: 0; transform: translateY(-10px) scale(.97); } to { opacity: 1; transform: none; } }
@keyframes toast-out { to { opacity: 0; transform: translateX(16px); } }

.alert {
  display: flex; gap: var(--sp-3); align-items: flex-start;
  padding: var(--sp-4) var(--sp-5); border-radius: var(--r-md);
  border: 1px solid var(--border); background: var(--surface-2); font-size: var(--fs-sm);
}
.alert-error   { border-color: rgba(248,113,113,.35); background: var(--danger-soft);  color: var(--danger); }
.alert-success { border-color: rgba(52,211,153,.35);  background: var(--success-soft); color: var(--success); }
.alert-info    { border-color: rgba(96,165,250,.35);  background: var(--info-soft);    color: var(--info); }
.alert-warning { border-color: rgba(251,191,36,.35);  background: var(--warning-soft); color: var(--warning); }

/* ---------- 14. Empty states ---------- */
.empty { display: grid; justify-items: center; gap: var(--sp-3); padding: clamp(3rem, 8vw, 5rem) var(--sp-6); text-align: center; }
.empty-icon {
  display: grid; place-items: center; width: 64px; height: 64px; margin-bottom: var(--sp-2);
  border-radius: var(--r-full); background: var(--surface-2); border: 1px solid var(--border);
  color: var(--text-faint); font-size: 1.5rem;
}
.empty h3 { color: var(--text-muted); }
.empty p { color: var(--text-faint); font-size: var(--fs-sm); max-width: 46ch; }

/* ---------- 15. Article prose ---------- */
.prose { font-size: var(--fs-md); line-height: var(--lh-body); color: #d3d3e0; }
.prose > * + * { margin-top: 1.15em; }
.prose h2 { font-size: var(--fs-2xl); margin-top: 2.2em; margin-bottom: .6em; color: var(--text); scroll-margin-top: calc(var(--nav-h) + 1rem); }
.prose h3 { font-size: var(--fs-xl); margin-top: 1.8em; margin-bottom: .5em; color: var(--text); scroll-margin-top: calc(var(--nav-h) + 1rem); }
.prose h4 { font-size: var(--fs-lg); margin-top: 1.5em; margin-bottom: .5em; color: var(--text); }
.prose a { color: var(--accent-hi); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
.prose a:hover { text-decoration-thickness: 2px; }
.prose strong { color: var(--text); font-weight: 650; }
.prose ul, .prose ol { padding-left: 1.4em; }
.prose ul { list-style: disc; }
.prose ol { list-style: decimal; }
.prose li { margin-top: .45em; }
.prose li::marker { color: var(--accent-hi); }
.prose img, .prose video { border-radius: var(--r-md); border: 1px solid var(--border); margin-block: 1.6em; }
.prose figure figcaption { font-size: var(--fs-xs); color: var(--text-faint); text-align: center; margin-top: .6em; }
.prose hr { border: 0; border-top: 1px solid var(--border); margin-block: 2.4em; }
.prose blockquote {
  border-left: 3px solid var(--accent); padding: .3em 0 .3em 1.15em;
  color: var(--text-muted); font-style: italic;
}
.prose code {
  font-family: var(--mono); font-size: .875em;
  background: var(--surface-3); border: 1px solid var(--border);
  padding: .12em .38em; border-radius: 5px; color: #d8b4fe;
}
.prose pre {
  background: #0b0b12; border: 1px solid var(--border); border-radius: var(--r-md);
  padding: var(--sp-5); overflow-x: auto; font-size: var(--fs-sm); line-height: 1.62;
}
.prose pre code { background: none; border: 0; padding: 0; color: #d8d8e8; font-size: inherit; }
.prose table { width: 100%; font-size: var(--fs-sm); display: block; overflow-x: auto; }
.prose th, .prose td { padding: .6em .9em; border: 1px solid var(--border); text-align: left; }
.prose th { background: var(--surface-2); font-weight: 600; }

.article-header { margin-bottom: var(--sp-10); }
.article-cover { border-radius: var(--r-xl); overflow: hidden; border: 1px solid var(--border); margin-bottom: var(--sp-10); aspect-ratio: 21 / 9; }
.article-cover img { width: 100%; height: 100%; object-fit: cover; }
.article-footer { margin-top: var(--sp-16); padding-top: var(--sp-8); border-top: 1px solid var(--border); }

.share { display: flex; align-items: center; gap: var(--sp-3); }
.share-btn {
  display: grid; place-items: center; width: 40px; height: 40px; border-radius: var(--r-md);
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text-muted);
  cursor: pointer; transition: all var(--t-fast);
}
.share-btn:hover { color: #fff; border-color: transparent; background: var(--accent-strong); transform: translateY(-2px); }

/* ---------- 16. Footer ---------- */
.footer { flex: 0 0 auto; margin-top: auto; background: var(--bg-alt); border-top: 1px solid var(--border); padding-block: var(--sp-16) var(--sp-8); }
.footer-grid { display: grid; gap: var(--sp-10); grid-template-columns: 1fr; }
.footer-heading { font-size: var(--fs-sm); font-weight: 600; text-transform: uppercase; letter-spacing: .1em; color: var(--text-faint); margin-bottom: var(--sp-4); }
.footer-links { display: grid; gap: var(--sp-3); }
.footer-links a { color: var(--text-muted); font-size: var(--fs-sm); width: fit-content; max-width: 100%; overflow-wrap: anywhere; }
.footer-bottom {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--sp-4); margin-top: var(--sp-12); padding-top: var(--sp-6);
  border-top: 1px solid var(--border); font-size: var(--fs-xs); color: var(--text-faint);
}
.social-row { display: flex; gap: var(--sp-3); }

/* Two narrow link columns sit side by side from the smallest screens up; the
   brand block spans the full width above them. Stacking all of it in a single
   column made the footer taller than a phone screen on its own. */
@media (min-width: 340px) {
  .footer-grid { grid-template-columns: 1fr 1fr; }
  .footer-brand { grid-column: 1 / -1; }
}
@media (min-width: 760px) {
  .footer-grid { grid-template-columns: 1.6fr 1fr 1fr; gap: var(--sp-12); }
  .footer-brand { grid-column: auto; }
}

@media (max-width: 759px) {
  .footer { padding-block: var(--sp-12) var(--sp-6); }
  .footer-grid { gap: var(--sp-8) var(--sp-6); }
  .footer-links { gap: var(--sp-2); }
  .footer-links a { padding-block: 2px; }
  .footer-bottom {
    margin-top: var(--sp-8); padding-top: var(--sp-5);
    flex-direction: column; justify-content: center; text-align: center;
    gap: var(--sp-5);
  }
  .footer-bottom .social-row { order: -1; }
  .footer-blurb { max-width: none; }
}
.social-btn {
  display: grid; place-items: center; width: 40px; height: 40px; border-radius: var(--r-md);
  background: var(--surface); border: 1px solid var(--border); color: var(--text-muted);
  transition: all var(--t-fast);
}
.social-btn:hover { color: #fff; background: var(--accent-strong); border-color: transparent; transform: translateY(-2px); }

/* ---------- 17. Admin shell ---------- */
.admin { display: grid; min-height: 100vh; grid-template-columns: 1fr; }
@media (min-width: 1024px) {
  .admin { grid-template-columns: 264px 1fr; }
  /* The sidebar is sticky and only 100vh tall, so on a long page its column
     used to fall away to bare page background halfway down. Painting the
     surface on the grid itself keeps the column continuous to the footer. */
  .admin { background: var(--surface); }
  .admin-main { background: var(--bg); border-left: 1px solid var(--border); }
  .admin-side { border-right: 0; }
}

.admin-side {
  position: fixed; inset: 0 auto 0 0; z-index: 120; width: 264px;
  display: flex; flex-direction: column;
  background: var(--surface); border-right: 1px solid var(--border);
  transform: translateX(-100%); transition: transform var(--t);
  overflow-y: auto;
}
.admin-side.is-open { transform: none; }
/* A closed drawer must leave the tab order. transform alone parks it off-screen
   while keeping every link focusable, so keyboard focus vanishes into a menu
   nobody can see. visibility does remove it, and it still animates. */
@media (max-width: 1023.98px) {
  .admin-side { visibility: hidden; transition: transform var(--t), visibility var(--t); }
  .admin-side.is-open { visibility: visible; }
}
@media (min-width: 1024px) { .admin-side { position: sticky; top: 0; height: 100vh; width: auto; transform: none; } }

.admin-side-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); padding: var(--sp-5) var(--sp-5) var(--sp-4); border-bottom: 1px solid var(--border); }
.admin-nav { display: grid; gap: 2px; padding: var(--sp-4) var(--sp-3); }
.admin-nav-label { padding: var(--sp-3) var(--sp-3) var(--sp-2); font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .1em; color: var(--text-faint); }
.admin-nav a {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4); border-radius: var(--r-md);
  font-size: var(--fs-sm); font-weight: 500; color: var(--text-muted);
  transition: background var(--t-fast), color var(--t-fast);
}
.admin-nav a:hover { background: var(--surface-2); color: var(--text); }
.admin-nav a[aria-current="page"] { background: var(--accent-soft); color: var(--accent-hi); font-weight: 600; }
.admin-nav a i { width: 18px; text-align: center; flex: 0 0 auto; }
.admin-nav a .badge { margin-left: auto; }
.admin-nav-danger { color: var(--danger) !important; }
.admin-nav-danger:hover { background: var(--danger-soft) !important; }
.admin-side-foot { margin-top: auto; padding: var(--sp-4); border-top: 1px solid var(--border); }

.admin-main { min-width: 0; display: flex; flex-direction: column; }
.admin-topbar {
  position: sticky; top: 0; z-index: 90;
  display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-6);
  background: rgba(8,8,11,.85); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border);
}
@media (min-width: 1024px) { .admin-topbar { padding-inline: var(--sp-8); } }
.admin-content { padding: var(--sp-6); }
@media (min-width: 1024px) { .admin-content { padding: var(--sp-8); } }
.admin-form { max-width: 920px; }
.page-head h1 { font-size: var(--fs-2xl); }
.admin-scrim {
  position: fixed; inset: 0; z-index: 110; background: rgba(0,0,0,.6);
  opacity: 0; visibility: hidden; transition: opacity var(--t), visibility var(--t);
}
.admin-scrim.is-open { opacity: 1; visibility: visible; }
@media (min-width: 1024px) { .admin-scrim { display: none; } }

.page-head { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: var(--sp-4); margin-bottom: var(--sp-8); }

.page-head p { color: var(--text-muted); font-size: var(--fs-sm); margin-top: var(--sp-2); }

.stat-card { display: flex; flex-direction: column; gap: var(--sp-4); padding: var(--sp-5); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); transition: border-color var(--t); }
.stat-card:hover { border-color: var(--border-strong); }
.stat-card-top { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4); }
.stat-icon { display: grid; place-items: center; width: 44px; height: 44px; border-radius: var(--r-md); background: var(--accent-soft); color: var(--accent-hi); font-size: 1.1rem; }
.stat-num { font-size: var(--fs-3xl); font-weight: 800; letter-spacing: -.03em; line-height: 1; }

/* ---------- 18. Modal ---------- */
.modal { position: fixed; inset: 0; z-index: 200; display: grid; place-items: center; padding: var(--sp-4); opacity: 0; visibility: hidden; transition: opacity var(--t), visibility var(--t); }
.modal.is-open { opacity: 1; visibility: visible; }
.modal-scrim { position: absolute; inset: 0; background: rgba(0,0,0,.72); backdrop-filter: blur(3px); }
.modal-box {
  position: relative; z-index: 1; width: min(680px, 100%); max-height: min(86vh, 760px);
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--r-lg); box-shadow: var(--sh-lg);
  transform: translateY(12px) scale(.98); transition: transform var(--t);
}
.modal.is-open .modal-box { transform: none; }
.modal-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-4); padding: var(--sp-5) var(--sp-6); border-bottom: 1px solid var(--border); }
.modal-body { padding: var(--sp-6); overflow-y: auto; flex: 1 1 auto; }
.modal-foot { display: flex; flex-wrap: wrap; gap: var(--sp-3); justify-content: flex-end; padding: var(--sp-5) var(--sp-6); border-top: 1px solid var(--border); }
.message-body { white-space: pre-wrap; overflow-wrap: break-word; line-height: 1.72; color: #d3d3e0; font-size: var(--fs-sm); }

.dl { display: grid; gap: var(--sp-3); }
.dl-row { display: grid; gap: var(--sp-1); }
@media (min-width: 520px) { .dl-row { grid-template-columns: 110px 1fr; gap: var(--sp-4); align-items: baseline; } }
.dl-key { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .07em; color: var(--text-faint); }
.dl-val { font-size: var(--fs-sm); min-width: 0; overflow-wrap: break-word; }

/* ---------- 19. Utilities ---------- */
.visually-hidden { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
.text-muted { color: var(--text-muted); }
.text-faint { color: var(--text-faint); }
.text-accent { color: var(--accent-hi); }
.text-danger { color: var(--danger); }
.text-center { text-align: center; }
.text-sm { font-size: var(--fs-sm); }
.text-xs { font-size: var(--fs-xs); }
.font-mono { font-family: var(--mono); }
.mt-auto { margin-top: auto; }
.mt-4 { margin-top: var(--sp-4); }
.mt-6 { margin-top: var(--sp-6); }
.mt-8 { margin-top: var(--sp-8); }
.mt-12 { margin-top: var(--sp-12); }
.mb-4 { margin-bottom: var(--sp-4); }
.mb-6 { margin-bottom: var(--sp-6); }
.mb-8 { margin-bottom: var(--sp-8); }
.w-full { width: 100%; }
.flex-1 { flex: 1 1 auto; min-width: 0; }
/* Responsive show/hide that only ever ADDS `display:none` inside a media
   query. Setting `display:revert` on the other side clobbered whatever layout
   the element's own class had set (a .btn stopped being inline-flex, a
   hamburger's bars collapsed to zero width). */
@media (max-width: 639px)  { .hide-sm { display: none !important; } }
@media (min-width: 1024px) { .only-sm { display: none !important; } }
.break-all { overflow-wrap: anywhere; }

/* Scroll reveal lives in the Motion section below. Elements are visible by
   default and JS opts them in, so the page is never blank without JS. */
@media (prefers-reduced-motion: reduce) { .reveal-ready .reveal { opacity: 1 !important; transform: none !important; } }

.spinner {
  width: 15px; height: 15px; border: 2px solid rgba(255,255,255,.28);
  border-top-color: currentColor; border-radius: 50%;
  animation: spin .7s linear infinite; flex: 0 0 auto;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ============================================================
   Motion
   Rules this follows: transform/opacity only (never layout), 150-300ms for
   micro-interactions and 400-600ms for entrances, ease-out on enter and
   ease-in on exit, nothing infinite that is merely decorative, and one focal
   animation per view rather than everything moving at once.
   The reduced-motion block in section 3 disables all of it.
   ============================================================ */

/* Cross-document transitions. One declaration; browsers without support just
   ignore it and navigate normally. */
@view-transition { navigation: auto; }
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}
::view-transition-old(root) { animation: vt-out 90ms ease-in both; }
::view-transition-new(root) { animation: vt-in 220ms var(--ease) both; }
@keyframes vt-out { to { opacity: 0; } }
@keyframes vt-in  { from { opacity: 0; transform: translateY(6px); } }

/* ---- Aurora --------------------------------------------------------------
   Ambient drift behind the hero only, so reading pages stay still. Built from
   radial gradients rather than filter: blur() — blurring elements this large
   costs real time on every composited frame, whereas a gradient's soft falloff
   is free. Only transform animates, so it never touches layout or paint.
   The reduced-motion guard below stops it dead rather than shortening it. */
.aurora {
  position: absolute; inset: -15% -10%; z-index: 0;
  pointer-events: none; overflow: hidden;
}
.aurora-blob {
  position: absolute; display: block;
  width: 46vw; min-width: 300px; aspect-ratio: 1;
  border-radius: 50%;
  will-change: transform;
}
.aurora-1 {
  top: -14%; left: -6%;
  background: radial-gradient(circle at 50% 50%, rgba(99, 102, 241, .30), rgba(99, 102, 241, 0) 68%);
  animation: aurora-a 26s ease-in-out infinite alternate;
}
.aurora-2 {
  top: 12%; right: -10%;
  background: radial-gradient(circle at 50% 50%, rgba(168, 85, 247, .26), rgba(168, 85, 247, 0) 68%);
  animation: aurora-b 34s ease-in-out infinite alternate;
}
.aurora-3 {
  bottom: -26%; left: 32%;
  background: radial-gradient(circle at 50% 50%, rgba(129, 140, 248, .22), rgba(129, 140, 248, 0) 70%);
  animation: aurora-c 30s ease-in-out infinite alternate;
}

@keyframes aurora-a {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(7%, 9%, 0) scale(1.14); }
}
@keyframes aurora-b {
  from { transform: translate3d(0, 0, 0) scale(1.1); }
  to   { transform: translate3d(-9%, -7%, 0) scale(.94); }
}
@keyframes aurora-c {
  from { transform: translate3d(0, 0, 0) scale(.94); }
  to   { transform: translate3d(6%, -8%, 0) scale(1.12); }
}

/* Fewer, smaller blobs on phones: these are large translucent fills and
   fill-rate is what costs battery on a low-end GPU. */
@media (max-width: 620px) {
  .aurora-blob { width: 78vw; }
  .aurora-3 { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .aurora-blob { animation: none !important; }
}

/* ---- Hero entrance ------------------------------------------------------
   Runs from CSS on load, so it works with JS blocked. `--enter` orders the
   pieces; the headline goes first and unblocked so the largest paint is not
   held back behind a queue of delays. */
@keyframes enter-up {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}
@keyframes enter-scale {
  from { opacity: 0; transform: scale(.94); }
  to   { opacity: 1; transform: none; }
}

.enter { animation: enter-up 420ms var(--ease) both; animation-delay: calc(var(--enter, 0) * 70ms); }
.enter-pop { animation: enter-scale 460ms var(--ease) both; animation-delay: calc(var(--enter, 0) * 70ms); }
.hero-title.enter { animation-duration: 260ms; animation-delay: 0ms; }

/* The global reduced-motion block shortens durations but not delays, which
   would leave a staggered element invisible for up to 420ms. Drop the
   animation entirely instead. */
@media (prefers-reduced-motion: reduce) {
  .enter, .enter-pop { animation: none !important; opacity: 1; transform: none; }
}

/* ---- Scroll reveal ------------------------------------------------------ */
.reveal-ready .reveal {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 520ms var(--ease), transform 520ms var(--ease);
  transition-delay: calc(var(--i, 0) * 70ms);
}
.reveal-ready .reveal.is-visible { opacity: 1; transform: none; }

/* ---- Reading progress --------------------------------------------------- */
.progress {
  position: fixed; inset: var(--nav-h) 0 auto 0; z-index: 95;
  height: 2px; pointer-events: none; background: transparent;
}
.progress > span {
  display: block; height: 100%; width: 100%;
  transform: scaleX(var(--p, 0)); transform-origin: 0 50%;
  background: var(--grad);
  transition: transform 90ms linear;
}

/* ---- Link underline ----------------------------------------------------- */
/* The underline grows from the leading edge instead of blinking on. */
.footer-links a, .link-underline {
  background-image: linear-gradient(var(--accent-hi), var(--accent-hi));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 1px;
  transition: background-size 220ms var(--ease), color var(--t-fast);
  padding-bottom: 1px;
}
.footer-links a:hover, .link-underline:hover { background-size: 100% 1px; }

/* ---- Press feedback ----------------------------------------------------- */
.btn:active, .share-btn:active, .social-btn:active, .page-btn:active { transform: scale(.97); }
.card-interactive:active { transform: translateY(-1px); }

/* ---- Counters ----------------------------------------------------------- */
/* Tabular figures stop the tile from twitching while a number counts up. */
.stat-value, .stat-num { font-variant-numeric: tabular-nums; }


/* ============================================================
   Requested effects
   Custom cursor, floating 3D object, typewriter, light streaks
   and border tracers. Everything below is transform/opacity only
   and every piece is disabled under prefers-reduced-motion.
   ============================================================ */

/* ---- Custom cursor -------------------------------------------------------
   The native cursor is only hidden once JS has successfully created the
   replacement (it adds .custom-cursor). If the script never runs, the ordinary
   pointer stays — a page with no visible cursor at all is unusable. Text
   fields keep the real caret cursor so typing still reads as typing. */
.cursor-dot,
.cursor-ring {
  position: fixed; top: 0; left: 0; z-index: 300;
  border-radius: 50%; pointer-events: none;
  opacity: 0; transition: opacity 200ms var(--ease);
}
.cursor-dot {
  width: 6px; height: 6px; margin: -3px 0 0 -3px;
  background: var(--accent-hi);
}
.cursor-ring {
  width: 34px; height: 34px; margin: -17px 0 0 -17px;
  border: 1.5px solid rgba(129, 140, 248, .5);
  transition: width 220ms var(--ease), height 220ms var(--ease),
              margin 220ms var(--ease), background 220ms var(--ease),
              border-color 220ms var(--ease), opacity 200ms var(--ease);
}
.cursor-dot.is-live, .cursor-ring.is-live { opacity: 1; }

html.custom-cursor,
html.custom-cursor a,
html.custom-cursor button,
html.custom-cursor label,
html.custom-cursor summary { cursor: none; }
html.custom-cursor input,
html.custom-cursor textarea,
html.custom-cursor select,
html.custom-cursor [contenteditable] { cursor: auto; }

/* Over something clickable the ring opens up and fills faintly. */
.cursor-ring.is-hover {
  width: 54px; height: 54px; margin: -27px 0 0 -27px;
  border-color: var(--accent-hi); background: rgba(99, 102, 241, .10);
}
.cursor-ring.is-down { width: 26px; height: 26px; margin: -13px 0 0 -13px; }
.cursor-ring.is-text {
  width: 3px; height: 26px; margin: -13px 0 0 -1.5px;
  border-radius: 2px; background: var(--accent-hi); border-color: transparent;
}

/* A pointing device that is not fine (touch) has no cursor to replace. */
@media (pointer: coarse) {
  .cursor-dot, .cursor-ring { display: none !important; }
}

/* ---- Globe ---------------------------------------------------------------
   A canvas projects the land mask onto a sphere (see site.js); everything here
   is the shell around it plus the no-JS fallback, which is why the layers below
   are hidden the moment the canvas goes live. Sits in the empty right-hand
   column of the hero. */
.scene3d {
  /* Anchored to the content, not the viewport edge. At 3% of a 3840px screen
     the globe sat nearly a thousand pixels clear of the 1200px column and read
     as an unrelated object floating in the margin. */
  position: absolute; right: max(24px, calc(50% - 760px)); top: 46%;
  width: 230px; height: 230px;
  translate: 0 -50%;
  perspective: 900px; z-index: 1; pointer-events: none;
}
.globe3d {
  position: relative; width: 100%; height: 100%;
  border-radius: 50%;
  /* Ocean, lit from the top-left and falling to near-black at the far limb.
     Deliberately not pure black: flat black kills the sense of volume. */
  background:
    radial-gradient(circle at 32% 26%, #2f2a72 0%, #221e56 34%, #14113a 62%, #0a0820 84%, #06050f 100%);
  box-shadow:
    inset -26px -30px 60px rgba(0, 0, 0, .80),
    inset 14px 16px 44px rgba(129, 140, 248, .14),
    0 0 60px rgba(99, 102, 241, .26);
  overflow: hidden;
  isolation: isolate;
}

/* No-JS fallback surface. The continents live on the canvas below, which is
   the only thing that loads the map; without scripting this stays a plainly
   shaded planet rather than pulling 47 KB for a decoration nothing animates. */
.globe3d-land {
  position: absolute; inset: 0; border-radius: 50%;
  background:
    radial-gradient(ellipse 62% 40% at 38% 34%, rgba(196, 188, 246, .28), transparent 70%),
    radial-gradient(ellipse 48% 30% at 68% 66%, rgba(168, 85, 247, .16), transparent 72%);
  pointer-events: none;
}

/* Terminator for the fallback. The canvas computes its own per-pixel lighting,
   so this comes off as soon as the projection is running. */
.globe3d-shade {
  position: absolute; inset: 0; border-radius: 50%;
  background:
    radial-gradient(circle at 28% 22%, rgba(255, 255, 255, .22), transparent 42%),
    radial-gradient(circle at 76% 80%, rgba(0, 0, 0, .72), transparent 62%);
  pointer-events: none;
}

/* Thin lit limb along the top-left. This is what reads as curvature. */
.globe3d-rim {
  position: absolute; inset: 0; border-radius: 50%;
  background: linear-gradient(145deg, rgba(203, 194, 250, .68), transparent 38%) border-box;
  border: 1.5px solid transparent;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask-composite: exclude;
  pointer-events: none;
}

/* The projected globe. Fades in only once the map has decoded and the first
   frame is on screen, so there is never a flash of empty canvas. Once it is
   live the flat fallback layers come out of the paint entirely. */
.globe3d-canvas {
  position: absolute; inset: 0; display: block;
  width: 100%; height: 100%;
  border-radius: 50%;
  opacity: 0; transition: opacity .7s ease;
}
.globe3d.is-live .globe3d-canvas { opacity: 1; }
.globe3d.is-live .globe3d-land,
.globe3d.is-live .globe3d-shade { display: none; }

/* Atmosphere. Two stops so it reads as air rather than a drop shadow. */
.globe3d-halo {
  position: absolute; inset: -16%; border-radius: 50%;
  /* Two hues so the air echoes --grad rather than sitting flat indigo. */
  background:
    radial-gradient(circle, rgba(129, 140, 248, .24) 48%, rgba(168, 85, 247, .11) 63%, transparent 76%);
  pointer-events: none; z-index: -1;
}

/* Hidden until there is genuinely spare margin beside the copy. Measured:
   at 1150px the object's left edge crossed the hero paragraph. */
@media (max-width: 1365px) { .scene3d { display: none; } }

/* ---- Typewriter ----------------------------------------------------------
   The full role list is in the markup as real text for crawlers and for
   JS-off visitors; the script only takes over if it can. min-height holds the
   line open so nothing below it moves while characters are added. */
.typewriter {
  display: block; min-height: 2.1em;
  font-size: var(--fs-lg); color: var(--text-muted);
}
.typewriter-line {
  display: inline-flex; align-items: baseline;
  /* Wide enough for the longest phrase, so the centred box never re-centres. */
  min-width: 21ch;
}
.typewriter-text { color: var(--accent-hi); font-weight: 600; }
.typewriter-caret {
  display: inline-block; width: 2px; height: 1.05em; margin-left: 4px;
  background: var(--accent-hi); translate: 0 .12em;
  animation: caret-blink 1.05s steps(1) infinite;
}
@keyframes caret-blink { 50% { opacity: 0; } }

/* ---- Light streak on the primary button ---------------------------------- */
.btn-primary { position: relative; overflow: hidden; }
/* The search submit is absolutely positioned inside its field. The rule above
   matches it too and would otherwise drop it out of the input. */
.btn.search-submit { position: absolute; }
.btn-primary > * { position: relative; z-index: 1; }
.btn-primary::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 45%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .30), transparent);
  transform: translateX(-160%) skewX(-18deg);
  pointer-events: none;
}
.btn-primary:hover::after { animation: streak 720ms var(--ease); }
@keyframes streak { to { transform: translateX(340%) skewX(-18deg); } }

/* ---- Hero light beam -----------------------------------------------------
   One sweep on load, then it is finished. Not a loop. */
.hero-beam { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 1; }
.hero-beam::before {
  content: ""; position: absolute; top: -20%; bottom: -20%; left: 0; width: 28%;
  background: linear-gradient(100deg, transparent, rgba(129, 140, 248, .10),
                              rgba(168, 85, 247, .13), transparent);
  /* transform, not `left`: animating a layout property makes the browser count
     the sweep as a layout shift and it alone scored 0.45 CLS. */
  transform: translate3d(-140%, 0, 0) skewX(-14deg);
  animation: hero-beam 1700ms var(--ease) 420ms both;
  will-change: transform;
}
@keyframes hero-beam { to { transform: translate3d(460%, 0, 0) skewX(-14deg); } }

/* ---- Card border tracer --------------------------------------------------
   A light travels the card's edge while it is hovered. Drawn with a masked
   conic gradient so it follows the rounded corners. Browsers without
   @property just show no tracer; the normal hover border still applies. */
@property --tracer {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}
.card-interactive::before {
  content: ""; position: absolute; inset: 0; z-index: 2;
  border-radius: inherit; padding: 1px;
  background: conic-gradient(from var(--tracer),
              transparent 0turn .70turn,
              var(--accent-hi) .84turn,
              var(--accent-2) .92turn,
              transparent 1turn);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask-composite: exclude;
  opacity: 0; transition: opacity 260ms var(--ease); pointer-events: none;
}
.card-interactive:hover::before,
.card-interactive:focus-within::before {
  opacity: 1;
  animation: tracer-run 2.6s linear infinite;
}
@keyframes tracer-run { to { --tracer: 1turn; } }

/* ---- Reduced motion ------------------------------------------------------
   Ambient loops stop entirely rather than being shortened, and the custom
   cursor is not installed at all (JS checks the same query). */
@media (prefers-reduced-motion: reduce) {
  .hero-beam::before { animation: none; opacity: 0; }
  .btn-primary::after { display: none; }
  .card-interactive:hover::before { animation: none; }
  .typewriter-caret { animation: none; }
  .cursor-dot, .cursor-ring { display: none !important; }
}

/* ---------- 19b. Tap targets ---------- */
/* WCAG 2.5.8 AA asks for 24x24 CSS px. These are all inline links, whose box is
   sized by font metrics rather than line-height, so padding is what grows the
   hit area; the matching negative margin keeps the visual layout exactly where
   it was. Sizes verified across a 320-2560px sweep. */
.card-title a,
.contact-label + a,
.cell-sub.cell-truncate {
  padding-block: 5px;
  margin-block: -5px;
}
/* Vertical negative margin only: pulling the sides out too made the link
   overhang its <p>, which shows up as the parent scrolling 6px wider than its
   own content box. The extra side padding just reads as footer spacing. */
.footer-bottom p a {
  padding: 5px;
  margin-block: -5px;
}

/* ---------- 19c. Very wide displays ---------- */
/* A 1200px column on a 2560px+ desktop reads as a narrow strip with the page
   stranded around it. Widen the shell -- the card grids are auto-fit, so they
   take the extra room as wider cards rather than an extra column, and the
   prose blocks keep their own max-width so line length is unaffected. */
@media (min-width: 1800px) {
  :root { --shell: 1440px; }
}
@media (min-width: 2400px) {
  :root { --shell: 1600px; }
}

/* ---------- 20. Print ---------- */
@media print {
  .nav, .nav-drawer, .footer, .toast-region, .share, .admin-side, .admin-topbar, .btn { display: none !important; }
  .reveal { opacity: 1 !important; transform: none !important; }
  body { background: #fff !important; color: #000 !important; background-image: none !important; }
  .page-main { padding-top: 0; }
  .panel, .card { border-color: #ccc; box-shadow: none; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: .8em; color: #555; }
}

/* ---------- 21. Page-specific bits ---------- */
.nav-cta { margin-left: var(--sp-2); }
.footer-blurb { max-width: 34ch; }
