/* ==========================================================================
   HabSociety site styles.

   Native CSS rather than a framework: this is a Cloudflare Workers Assets
   site serving static files, with the map as a standalone PixiJS bundle.
   Adding a React/Tailwind build would mean restructuring the Worker, the
   deploy pipeline and the map app for no user-facing gain.

   The palette descends from the map app (public/css/style.css) so the site
   and the map read as one product, pushed deeper and colder here.

   Shape system, applied everywhere:
     - interactive controls (buttons, chips, nav items): full pill
     - surfaces (panels, cards, media): --radius
   One accent: teal. It is the only hue in the interface; the ambient glow
   behind everything is the same hue at low opacity, never a second colour.
   ========================================================================== */

@font-face {
  font-family: "Geist";
  src: url("/fonts/Geist-Regular.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Geist";
  src: url("/fonts/Geist-Medium.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Geist";
  src: url("/fonts/Geist-SemiBold.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Geist";
  src: url("/fonts/Geist-Bold.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("/fonts/GeistMono-Regular.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}

:root {
  --bg: #080b0f;
  --bg-raise: #0d1219;
  --surface: rgba(255, 255, 255, 0.028);
  --surface-solid: #10161e;
  --hair: rgba(255, 255, 255, 0.075);
  --hair-strong: rgba(255, 255, 255, 0.15);
  --text: #eef3f6;
  --text-dim: #93a2b0;
  /* Was #64727f, which measured 4.00:1 on --bg — under the 4.5:1 floor, and
     it carries the footer disclaimer and the footer column headings. Nudged
     up the same hue to 4.80:1. Still clearly the quietest of the three. */
  --text-faint: #707f8d;

  --accent: #2dd9c6;
  --accent-soft: rgba(45, 217, 198, 0.13);
  --accent-line: rgba(45, 217, 198, 0.42);
  --accent-ink: #04211d; /* foreground on an accent fill */

  --radius: 16px;
  --maxw: 1180px;
  --gutter: clamp(20px, 5vw, 44px);
  --section-y: clamp(84px, 11vw, 148px);

  --font: "Geist", ui-sans-serif, system-ui, "Segoe UI", Roboto, sans-serif;
  --mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --glow: 0 0 0 1px var(--accent-line), 0 12px 44px rgba(45, 217, 198, 0.18);
}

@media (prefers-color-scheme: light) {
  :root {
    --bg: #f2f5f8;
    --bg-raise: #ffffff;
    --surface: rgba(9, 20, 30, 0.026);
    --surface-solid: #ffffff;
    --hair: rgba(9, 20, 30, 0.1);
    --hair-strong: rgba(9, 20, 30, 0.2);
    --text: #0c141b;
    --text-dim: #4d5c6a;
    --text-faint: #7a8794;
    --accent: #0d7d70;
    --accent-soft: rgba(13, 125, 112, 0.1);
    --accent-line: rgba(13, 125, 112, 0.34);
    --accent-ink: #ffffff;
    --glow: 0 0 0 1px var(--accent-line), 0 12px 36px rgba(13, 125, 112, 0.14);
  }
}

*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  position: relative;
}

/* Ambient light. Fixed and behind everything, so it never repaints on scroll.
   One hue only: this is the accent at low opacity, not a second colour. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  background:
    radial-gradient(58% 44% at 78% -6%, rgba(45, 217, 198, 0.15), transparent 62%),
    radial-gradient(48% 40% at 6% 12%, rgba(45, 217, 198, 0.075), transparent 66%),
    radial-gradient(70% 50% at 50% 108%, rgba(45, 217, 198, 0.075), transparent 68%);
}
@media (prefers-color-scheme: light) {
  body::before {
    background:
      radial-gradient(58% 44% at 78% -6%, rgba(13, 125, 112, 0.11), transparent 62%),
      radial-gradient(70% 50% at 50% 108%, rgba(13, 125, 112, 0.07), transparent 68%);
  }
}

/* Grain. Fixed and pointer-events:none so it costs one composited layer and
   never triggers a repaint while scrolling. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  opacity: 0.035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}
@media (prefers-color-scheme: light) {
  body::after { opacity: 0.02; }
}

h1, h2, h3 {
  margin: 0;
  letter-spacing: -0.035em;
  line-height: 1.04;
  font-weight: 600;
  text-wrap: balance;
}
p { margin: 0; }
a { color: inherit; }
/* `height: auto` is load-bearing, not tidiness. Every screenshot carries
   `width="1200" height="750"` so the browser can reserve the right space
   before it loads. Without `height: auto` those attributes stay in force as a
   fixed 750px height while the CSS stretches the width to 100%, which
   distorts the picture on every viewport that is not exactly 1200px wide. */
img { max-width: 100%; height: auto; display: block; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 6px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  padding: 12px 18px;
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
}
.skip-link:focus { left: 0; }

.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* ------------------------------------------------------------------- nav */

.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  height: 66px;
  display: flex;
  align-items: center;
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  backdrop-filter: blur(18px) saturate(150%);
  -webkit-backdrop-filter: blur(18px) saturate(150%);
  border-bottom: 1px solid var(--hair);
}
@supports not (backdrop-filter: blur(4px)) {
  .nav { background: var(--bg); }
}
@media (prefers-reduced-transparency: reduce) {
  .nav { background: var(--bg); backdrop-filter: none; -webkit-backdrop-filter: none; }
}

.nav-inner {
  display: flex;
  align-items: center;
  gap: 26px;
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.brand {
  display: flex;
  align-items: center;
  gap: 9px;
  font-weight: 600;
  font-size: 15.5px;
  letter-spacing: -0.02em;
  text-decoration: none;
  flex-shrink: 0;
}
.brand-mark {
  color: var(--accent);
  font-size: 14px;
  line-height: 1;
  filter: drop-shadow(0 0 8px var(--accent-line));
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 2px;
  margin-left: auto;
}

/* `:not(.btn)` matters. Without it this rule out-specifies .btn-primary
   (0,1,1 against 0,1,0) and paints the call-to-action's label dim grey on a
   bright teal fill, then strips the fill entirely on hover. */
.nav-links a:not(.btn) {
  position: relative;
  padding: 8px 14px;
  border-radius: 999px;
  font-size: 14.5px;
  color: var(--text-dim);
  text-decoration: none;
  transition: color 0.2s var(--ease);
}
.nav-links a:not(.btn)::after {
  content: "";
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 2px;
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.28s var(--ease);
}
.nav-links a:not(.btn):hover { color: var(--text); }
.nav-links a:not(.btn):hover::after,
.nav-links a:not(.btn)[aria-current="page"]::after { transform: scaleX(1); }
.nav-links a:not(.btn)[aria-current="page"] { color: var(--text); }

.nav-cta { margin-left: 10px; }

.nav-toggle {
  display: none;
  margin-left: auto;
  width: 42px;
  height: 42px;
  align-items: center;
  justify-content: center;
  gap: 4px;
  flex-direction: column;
  border: 1px solid var(--hair);
  border-radius: 999px;
  background: transparent;
  cursor: pointer;
  padding: 0;
}
.nav-toggle span {
  display: block;
  width: 15px;
  height: 1.5px;
  background: var(--text);
  border-radius: 2px;
}

@media (max-width: 880px) {
  .nav-links {
    position: fixed;
    inset: 66px 0 auto 0;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    margin: 0;
    padding: 14px var(--gutter) 22px;
    background: var(--bg-raise);
    border-bottom: 1px solid var(--hair);
    clip-path: inset(0 0 100% 0);
    opacity: 0;
    pointer-events: none;
    /* `visibility` is what takes the six links out of the tab order. Clipped
       and transparent, they were still focusable: a keyboard user on a narrow
       viewport tabbed through the whole closed menu, invisibly, before
       reaching the page. It is transitioned so it flips at the end of the
       close rather than cutting the animation off at the start. */
    visibility: hidden;
    transition: clip-path 0.34s var(--ease), opacity 0.2s var(--ease),
      visibility 0.34s var(--ease);
  }
  .nav-links.open {
    clip-path: inset(0 0 0 0);
    opacity: 1;
    pointer-events: auto;
    visibility: visible;
  }
  .nav-links a:not(.btn) { padding: 13px 14px; font-size: 16px; }
  .nav-cta { margin: 10px 0 0; justify-content: center; }
  .nav-toggle { display: flex; }
}

/* --------------------------------------------------------------- buttons */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 24px;
  border-radius: 999px;
  border: 1px solid transparent;
  font-family: inherit;
  font-size: 15px;
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  overflow: hidden;
  transition: transform 0.2s var(--ease), box-shadow 0.28s var(--ease),
    border-color 0.2s var(--ease), color 0.2s var(--ease);
}
.btn:active { transform: translateY(1px) scale(0.985); }

.btn-primary {
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
  box-shadow: 0 8px 26px rgba(45, 217, 198, 0.24);
}
.btn-primary:hover {
  transform: translateY(-2px);
  box-shadow: 0 14px 40px rgba(45, 217, 198, 0.36);
}
/* Light sweeps across on hover. Feedback for a pointer, nothing on its own. */
.btn-primary::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 20%, rgba(255, 255, 255, 0.45) 50%, transparent 80%);
  transform: translateX(-120%);
}
.btn-primary:hover::before { transform: translateX(120%); transition: transform 0.7s var(--ease); }

.btn-ghost {
  background: var(--surface);
  border-color: var(--hair-strong);
  color: var(--text);
}
.btn-ghost:hover {
  border-color: var(--accent-line);
  transform: translateY(-2px);
}

.btn-sm { padding: 9px 17px; font-size: 14px; }

/* ---------------------------------------------------------------- layout */

section { padding-block: var(--section-y); position: relative; }

.section-head { max-width: 42ch; margin-bottom: clamp(40px, 5.5vw, 64px); }
.section-head h2 { font-size: clamp(30px, 4.6vw, 50px); }
.section-head p {
  margin-top: 18px;
  color: var(--text-dim);
  font-size: 17px;
  max-width: 56ch;
}

.lead {
  font-size: clamp(17px, 2vw, 19.5px);
  color: var(--text-dim);
  max-width: 58ch;
}

.rule { border: 0; border-top: 1px solid var(--hair); margin: 0; }

/* ------------------------------------------------------------------ hero */

.hero {
  padding-top: clamp(40px, 6vw, 72px);
  padding-bottom: clamp(64px, 8vw, 108px);
  position: relative;
  overflow: hidden;
}
.hero-grid {
  display: grid;
  /* Weighted towards the words. At an even split the headline broke across
     four lines at its clamped 76px, stranding "run by the" on a line of its
     own; the art beside it is decoration and loses nothing by giving way. */
  grid-template-columns: minmax(0, 1.16fr) minmax(0, 0.84fr);
  gap: clamp(32px, 6vw, 76px);
  align-items: center;
}
.hero h1 {
  font-size: clamp(40px, 7vw, 76px);
  font-weight: 600;
}
.hero .lead { margin-top: 24px; }
.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 34px;
}

/* The city plan is a flat top-down render; the tilt gives it depth without
   pretending to be a 3D asset. Masked so it reads as a fragment of a larger
   city rather than a cropped rectangle. */
.hero-art { position: relative; perspective: 1500px; }

@media (max-width: 920px) {
  .hero-grid { grid-template-columns: 1fr; }
  .hero-art { order: -1; }
}

/* ------------------------------------------------------------- stat band */

.stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  border-top: 1px solid var(--hair);
  border-bottom: 1px solid var(--hair);
}
.stat { padding: clamp(28px, 4vw, 44px) 24px clamp(28px, 4vw, 44px) 0; }
.stat + .stat {
  border-left: 1px solid var(--hair);
  padding-left: clamp(22px, 4vw, 44px);
}
.stat-n {
  font-family: var(--mono);
  font-size: clamp(34px, 5.4vw, 54px);
  line-height: 1;
  letter-spacing: -0.045em;
  color: var(--accent);
  text-shadow: 0 0 30px rgba(45, 217, 198, 0.35);
}
.stat-l { margin-top: 12px; color: var(--text-dim); font-size: 14.5px; }
@media (max-width: 720px) {
  .stats { grid-template-columns: 1fr; }
  .stat { padding-right: 0; }
  .stat + .stat { border-left: 0; border-top: 1px solid var(--hair); padding-left: 0; }
}

/* ------------------------------------------------------------------ orgs */

.org-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: clamp(16px, 2.2vw, 24px);
  /* Cards size to their own content. Stretched to the row height, the narrow
     card in each pair inherited the wide one's height and `margin-top: auto`
     on the chips opened a dead band of empty card between the blurb and the
     chips — worst on the ones with a one-line blurb. */
  align-items: start;
}
/* Asymmetric on purpose: four equal cards in a row is the stock layout. */
.org-card:nth-child(4n + 1),
.org-card:nth-child(4n + 4) { grid-column: span 7; }
.org-card:nth-child(4n + 2),
.org-card:nth-child(4n + 3) { grid-column: span 5; }

.org-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--hair);
  border-radius: var(--radius);
  overflow: hidden;
  text-decoration: none;
  isolation: isolate;
  transition: transform 0.4s var(--ease), border-color 0.4s var(--ease),
    box-shadow 0.4s var(--ease);
}
.org-card:hover {
  transform: translateY(-5px);
  border-color: var(--accent-line);
  box-shadow: 0 22px 60px rgba(0, 0, 0, 0.4);
}
.org-media {
  position: relative;
  background: var(--bg-raise);
  aspect-ratio: 16 / 10;
  overflow: hidden;
}
.org-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.7s var(--ease);
}
.org-card:hover .org-media img { transform: scale(1.045); }
/* Ties the photograph into the surface instead of ending on a hard edge. */
.org-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, transparent 52%, color-mix(in srgb, var(--bg) 76%, transparent));
  pointer-events: none;
}

.org-body {
  padding: clamp(20px, 2.4vw, 28px);
  display: flex;
  flex-direction: column;
  gap: 11px;
  flex: 1;
}
.org-body h3 { font-size: clamp(20px, 2.4vw, 26px); }
.org-body p { color: var(--text-dim); font-size: 15px; }
.org-meta {
  margin-top: auto;
  padding-top: 16px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.chip {
  display: inline-flex;
  align-items: center;
  padding: 5px 12px;
  border-radius: 999px;
  border: 1px solid var(--hair-strong);
  font-size: 12.5px;
  font-family: var(--mono);
  color: var(--text-dim);
  white-space: nowrap;
}
.chip-open {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent);
}

@media (max-width: 880px) {
  .org-card,
  .org-card:nth-child(4n + 1),
  .org-card:nth-child(4n + 2),
  .org-card:nth-child(4n + 3),
  .org-card:nth-child(4n + 4) { grid-column: 1 / -1; }
}

/* ----------------------------------------------------------------- staff */

/* auto-fit rather than a fixed column count: the team is two people today and
   the page should not need touching when it is five. */
.staff-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: clamp(16px, 2.2vw, 24px);
}

.staff-card {
  position: relative;
  display: flex;
  gap: clamp(16px, 2vw, 24px);
  align-items: flex-start;
  padding: clamp(20px, 2.4vw, 28px);
  background: var(--surface);
  border: 1px solid var(--hair);
  border-radius: var(--radius);
  transition: transform 0.4s var(--ease), border-color 0.4s var(--ease),
    box-shadow 0.4s var(--ease);
}
.staff-card:hover {
  transform: translateY(-5px);
  border-color: var(--accent-line);
  box-shadow: 0 22px 60px rgba(0, 0, 0, 0.4);
}

/* The avatar is a transparent PNG of a character standing on nothing, so it
   needs a surface of its own or it floats in the card. */
.staff-figure {
  position: relative;
  flex: 0 0 auto;
  width: 116px;
  display: grid;
  place-items: end center;
  padding-top: 10px;
  border-radius: calc(var(--radius) - 4px);
  background: radial-gradient(
    120% 70% at 50% 92%,
    var(--accent-soft),
    transparent 70%
  );
}
/* The shadow the avatar does not come with. */
.staff-figure::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 6px;
  width: 66px;
  height: 12px;
  transform: translateX(-50%);
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.36);
  filter: blur(6px);
}
.staff-figure img {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: auto;
  /* Habbo's imaging is pixel art; smoothing it turns it to mush. */
  image-rendering: pixelated;
  transition: transform 0.5s var(--ease);
}
.staff-card:hover .staff-figure img { transform: translateY(-4px); }

.staff-body {
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding-top: 4px;
  min-width: 0;
}
.staff-body h3 {
  font-size: clamp(21px, 2.4vw, 26px);
  overflow-wrap: anywhere;
}
.staff-title {
  align-self: flex-start;
  padding: 5px 12px;
  border-radius: 999px;
  border: 1px solid var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent);
  font-family: var(--mono);
  font-size: 12.5px;
  white-space: nowrap;
}
.staff-role {
  font-size: 15.5px;
  color: var(--text);
}
.staff-bio {
  font-size: 15px;
  color: var(--text-dim);
}

@media (max-width: 460px) {
  .staff-card { flex-direction: column; align-items: center; text-align: center; }
  .staff-body { align-items: center; }
  .staff-title { align-self: center; }
}

/* -------------------------------------------------------- unequal columns */

.cols {
  display: grid;
  grid-template-columns: 1.15fr 0.85fr 1fr;
  gap: clamp(24px, 4vw, 52px);
}
.col { padding-top: 24px; border-top: 1px solid var(--hair-strong); }
.col h3 { font-size: clamp(20px, 2.2vw, 24px); margin-bottom: 12px; }
.col p { color: var(--text-dim); font-size: 15.5px; }
.col-n {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--accent);
  margin-bottom: 14px;
  display: block;
}
@media (max-width: 840px) { .cols { grid-template-columns: 1fr; } }

/* ------------------------------------------------------------ split prose */

.split {
  display: grid;
  grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
  gap: clamp(32px, 6vw, 76px);
  align-items: center;
}
/* Direct child only. These images sit inside a .shot-frame, which already
   carries the border and the radius, so an unscoped rule drew a second one
   inside the first. */
.split-media > img {
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  border: 1px solid var(--hair);
}
.prose p + p { margin-top: 18px; }
.prose p { color: var(--text-dim); font-size: 16.5px; max-width: 62ch; }
@media (max-width: 900px) { .split { grid-template-columns: 1fr; } }

/* ----------------------------------------------------------- numbered list */

.steps { display: grid; gap: 0; }
.step {
  display: grid;
  grid-template-columns: 68px 1fr;
  gap: clamp(16px, 3vw, 36px);
  padding: clamp(26px, 3.6vw, 38px) 0;
  border-top: 1px solid var(--hair);
  align-items: start;
  transition: border-color 0.3s var(--ease);
}
.step:last-child { border-bottom: 1px solid var(--hair); }
.step:hover { border-top-color: var(--accent-line); }
.step-n { font-family: var(--mono); font-size: 14px; color: var(--accent); padding-top: 6px; }
.step h3 { font-size: clamp(21px, 2.6vw, 28px); margin-bottom: 12px; }
.step p { color: var(--text-dim); max-width: 62ch; }
@media (max-width: 620px) { .step { grid-template-columns: 1fr; gap: 8px; } }

/* ------------------------------------------------------------- fact list */

.facts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(22px, 3vw, 40px) clamp(28px, 5vw, 68px);
}
.fact { padding-left: 18px; border-left: 1px solid var(--hair); }
.fact h3 { font-size: 17.5px; margin-bottom: 9px; }
.fact p { color: var(--text-dim); font-size: 15.5px; }
@media (max-width: 760px) { .facts { grid-template-columns: 1fr; } }

/* -------------------------------------------------------------- cta band */

.band {
  position: relative;
  overflow: hidden;
  border-top: 1px solid var(--hair);
  background: var(--bg-raise);
}
/* A drawn grid rather than a washed-out photo. The flat city render at low
   opacity read as a smudge behind the text; this gives depth and stays out
   of the way of the copy. */
.band-bg {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(var(--hair) 1px, transparent 1px),
    linear-gradient(90deg, var(--hair) 1px, transparent 1px);
  background-size: 62px 62px;
  -webkit-mask-image: radial-gradient(70% 62% at 50% 50%, #000 8%, transparent 78%);
  mask-image: radial-gradient(70% 62% at 50% 50%, #000 8%, transparent 78%);
}
.band-inner {
  position: relative;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
}
.band h2 { font-size: clamp(30px, 5vw, 52px); max-width: 17ch; }
.band p { color: var(--text-dim); max-width: 48ch; }
.band-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
  margin-top: 8px;
}

/* ----------------------------------------------------------- page header */

.page-head {
  padding-top: clamp(48px, 7vw, 92px);
  padding-bottom: clamp(38px, 5vw, 60px);
}
.page-head h1 { font-size: clamp(36px, 6vw, 66px); max-width: 17ch; }
.page-head .lead { margin-top: 22px; }

/* ---------------------------------------------------------------- footer */

.foot {
  border-top: 1px solid var(--hair);
  padding-block: clamp(44px, 5vw, 64px);
}
.foot-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 28px;
  align-items: flex-start;
  justify-content: space-between;
}
.foot-brand { max-width: 34ch; display: flex; flex-direction: column; gap: 14px; }
.foot-brand p { color: var(--text-faint); font-size: 14px; }

/* Columns, not one row of seven.
   Flex rather than grid, deliberately. `repeat(auto-fit, minmax(140px,
   max-content))` needs a definite container width to work out how many
   tracks fit, and this is a flex item sized by its own content — so auto-fit
   resolved to a single track and the three columns stacked into one tall
   strip down the right. Flex wrapping needs no such measurement. */
.foot-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 28px clamp(32px, 4vw, 64px);
}
.foot-col { display: flex; flex-direction: column; gap: 10px; min-width: 118px; }
.foot-col h2 {
  font-family: var(--mono);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.foot-nav a {
  color: var(--text-dim);
  text-decoration: none;
  font-size: 14.5px;
  transition: color 0.2s var(--ease);
}
.foot-nav a:hover,
.foot-nav a[aria-current="page"] { color: var(--accent); }

.foot-base {
  margin-top: clamp(32px, 4vw, 46px);
  padding-top: 24px;
  border-top: 1px solid var(--hair);
  display: flex;
  flex-wrap: wrap;
  gap: 12px 32px;
  align-items: baseline;
  justify-content: space-between;
}
.foot-note { color: var(--text-faint); font-size: 13.5px; max-width: 62ch; }

@media (max-width: 560px) {
  .foot-nav { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 24px; }
}

/* Small label above a heading. */
.eyebrow {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 16px;
}

/* ------------------------------------------------------------- dropdown */

.has-menu { position: relative; display: flex; }

.menu-trigger {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  border: 0;
  border-radius: 999px;
  background: none;
  font: inherit;
  font-size: 14.5px;
  color: var(--text-dim);
  cursor: pointer;
  transition: color 0.2s var(--ease);
}
.menu-trigger:hover,
.menu-trigger[aria-current="page"],
.has-menu.open .menu-trigger { color: var(--text); }
.caret { transition: transform 0.28s var(--ease); }
.has-menu.open .caret { transform: rotate(180deg); }

.menu {
  position: absolute;
  top: calc(100% + 12px);
  left: 50%;
  min-width: 218px;
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  border-radius: 15px;
  background: var(--bg-raise);
  border: 1px solid var(--hair);
  box-shadow: 0 26px 64px rgba(0, 0, 0, 0.55);
  opacity: 0;
  visibility: hidden;
  transform: translateX(-50%) translateY(-8px);
  transition: opacity 0.22s var(--ease), transform 0.22s var(--ease), visibility 0.22s;
}
.has-menu.open .menu {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
}

/* `.nav-links a:not(.btn)` is 0,2,1 because :not() counts its argument, so a
   plain `.menu a` would lose to it and the items would inherit the top-level
   link's underline animation. Matching the specificity and coming later is
   what puts these back in control of their own appearance. */
.nav-links .menu a {
  padding: 10px 12px;
  border-radius: 10px;
  font-size: 14.5px;
  color: var(--text-dim);
  text-decoration: none;
  transition: background 0.18s var(--ease), color 0.18s var(--ease);
}
.nav-links .menu a::after { display: none; }
.nav-links .menu a:hover { background: var(--surface); color: var(--text); }

@media (max-width: 880px) {
  .has-menu { flex-direction: column; }
  .menu-trigger { justify-content: space-between; padding: 13px 14px; font-size: 16px; }
  /* In the drawer there is nowhere to pop out to, so the submenu is simply
     part of the list. */
  .menu {
    position: static;
    transform: none;
    opacity: 1;
    visibility: visible;
    min-width: 0;
    padding: 0 0 6px 12px;
    border: 0;
    background: none;
    box-shadow: none;
    border-left: 1px solid var(--hair);
    margin-left: 14px;
  }
  .caret { display: none; }
}

/* --------------------------------------------------------- screenshots */

/* The map's own pixel art, framed. No mask: an isometric screenshot faded at
   the edges reads as a mistake, where the old flat plan needed it. */
.shot-frame {
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--hair-strong);
  background: var(--bg-raise);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.07);
}
/* Height stays automatic so the capture keeps the proportions it was taken
   in; the frame sizes itself to the picture rather than the reverse. */
.shot-frame img { width: 100%; height: auto; display: block; }

.hero-art .shot-frame {
  transform: perspective(1800px) rotateY(-6deg) rotateX(3deg);
  transform-origin: 100% 50%;
  transition: transform 0.7s var(--ease);
}
.hero-art:hover .shot-frame { transform: perspective(1800px) rotateY(-2deg) rotateX(1deg); }
@media (max-width: 920px) {
  .hero-art .shot-frame,
  .hero-art:hover .shot-frame { transform: none; }
}

/* ------------------------------------------------------ organization page */

.crumb { margin-bottom: 18px; font-size: 14px; }
.crumb a { color: var(--text-dim); text-decoration: none; }
.crumb a:hover { color: var(--accent); }

.org-hero { padding-block: 0 clamp(48px, 6vw, 76px); }
.org-hero-meta {
  margin-top: 20px;
  display: flex;
  flex-wrap: wrap;
  gap: 9px;
}
.chip-link {
  text-decoration: none;
  border-color: var(--accent-line);
  color: var(--accent);
  transition: background 0.2s var(--ease);
}
.chip-link:hover { background: var(--accent-soft); }

.split-h2 { font-size: clamp(24px, 3.4vw, 34px); margin-bottom: 20px; }

.roles {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0;
}
.roles li {
  padding: 15px 2px;
  border-top: 1px solid var(--hair);
  font-size: 16.5px;
  display: flex;
  align-items: center;
  gap: 12px;
}
.roles li:last-child { border-bottom: 1px solid var(--hair); }
.roles li::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
  flex-shrink: 0;
}
.roles-note { margin-top: 20px; color: var(--text-dim); font-size: 15px; }

/* ------------------------------------------------------------ leaderboard */

.soon {
  position: relative;
  overflow: hidden;
  padding: clamp(40px, 6vw, 72px);
  border: 1px solid var(--hair);
  border-radius: var(--radius);
  background: var(--surface);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;
}
.soon::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(var(--hair) 1px, transparent 1px),
    linear-gradient(90deg, var(--hair) 1px, transparent 1px);
  background-size: 54px 54px;
  -webkit-mask-image: radial-gradient(60% 60% at 80% 20%, #000, transparent 76%);
  mask-image: radial-gradient(60% 60% at 80% 20%, #000, transparent 76%);
}
.soon > * { position: relative; }
.soon-tag {
  display: inline-flex;
  padding: 6px 14px;
  border-radius: 999px;
  border: 1px solid var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent);
  font-family: var(--mono);
  font-size: 12.5px;
}
.soon h2 { font-size: clamp(28px, 4.4vw, 44px); }
.soon p { color: var(--text-dim); max-width: 52ch; }

/* ------------------------------------------------------------ the city

   The hero visual, drawn rather than photographed. Three by three, matching
   the real street grid: the four corners are the organizations, the middle is
   the Town Center. Costs nothing to load and never goes out of date. */

.city {
  position: relative;
  aspect-ratio: 1 / 1;
  display: grid;
  place-items: center;
  perspective: 1300px;
}
.city::after {
  content: "";
  position: absolute;
  inset: 12%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(45, 217, 198, 0.26), transparent 72%);
  filter: blur(56px);
  z-index: -1;
}

.city-plane {
  width: 72%;
  aspect-ratio: 1;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 11px;
  position: relative;
  transform: rotateX(56deg) rotateZ(45deg);
  transform-style: preserve-3d;
  transition: transform 0.9s var(--ease);
}
.hero-art:hover .city-plane { transform: rotateX(50deg) rotateZ(41deg) scale(1.04); }

.city-tile {
  position: relative;
  border-radius: 7px;
  border: 1px solid var(--hair-strong);
  background: linear-gradient(145deg, rgba(255, 255, 255, 0.055), rgba(255, 255, 255, 0.014));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.07);
}
.city-tile.is-org {
  border-color: var(--accent-line);
  background: linear-gradient(145deg, var(--accent-soft), rgba(45, 217, 198, 0.03));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.09), 0 0 24px rgba(45, 217, 198, 0.16);
}
.city-tile.is-core {
  border-color: var(--accent);
  background: linear-gradient(145deg, rgba(45, 217, 198, 0.3), rgba(45, 217, 198, 0.08));
}
/* The pulse lives on a pseudo-element so it never fights the tile's own
   transform animation. */
.city-tile.is-core::after {
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: 9px;
  box-shadow: 0 0 0 0 rgba(45, 217, 198, 0.5);
}

@media (max-width: 920px) {
  .city { max-width: 420px; margin-inline: auto; }
}

/* ------------------------------------------------------------ footer credit */

.foot-credit {
  margin-top: 14px;
  color: var(--text-faint);
  font-size: 13px;
}
.foot-credit a {
  color: var(--text-dim);
  text-decoration: none;
  border-bottom: 1px solid var(--hair-strong);
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.foot-credit a:hover { color: var(--accent); border-color: var(--accent-line); }

/* --------------------------------------------------------------- motion */

/* Two motion systems, both motivated:
     - hero content enters once on load, in reading order
     - everything below enters as it becomes relevant on scroll

   Both collapse to "already visible" under reduced motion.

   The `.js` gate is not decoration. `.reveal` starts at opacity 0 and only
   site.js ever adds `.in`, so without JavaScript every revealed element —
   most of the body of every page — stayed invisible forever. An earlier note
   here claimed reduced-motion covered that; it does not, they are different
   conditions. The class is set by an inline script in <head>, before first
   paint, so hiding never happens unless something is coming to unhide it. */
@media (prefers-reduced-motion: no-preference) {
  .js .reveal {
    opacity: 0;
    transform: translateY(22px);
    filter: blur(6px);
    transition: opacity 0.7s var(--ease), transform 0.7s var(--ease), filter 0.7s var(--ease);
  }
  .js .reveal.in { opacity: 1; transform: none; filter: none; }

  /* `.enter` is pure CSS and finishes on its own, so it is safe without JS —
     but it is gated too, so that a page with JS off has exactly one motion
     story rather than half of one. */
  .js .enter { opacity: 0; animation: enter 0.9s var(--ease) forwards; }
  .enter-1 { animation-delay: 0.05s; }
  .enter-2 { animation-delay: 0.16s; }
  .enter-3 { animation-delay: 0.27s; }
  .enter-4 { animation-delay: 0.2s; }

  @keyframes enter {
    from { opacity: 0; transform: translateY(20px); filter: blur(8px); }
    to { opacity: 1; transform: none; filter: none; }
  }

  /* The city builds itself, corner tiles first, then settles into a drift.
     The float is delayed past the end of the build so the two animations
     never write `transform` in the same frame. */
  .city-tile {
    opacity: 0;
    transform: translateZ(-46px);
    animation:
      tile-in 0.85s var(--ease) forwards,
      tile-float 7s ease-in-out infinite;
    animation-delay:
      calc(var(--d, 0) * 110ms + 250ms),
      calc(var(--d, 0) * 110ms + 1150ms);
  }
  @keyframes tile-in {
    to { opacity: 1; transform: translateZ(0); }
  }
  @keyframes tile-float {
    0%, 100% { transform: translateZ(0); }
    50% { transform: translateZ(11px); }
  }

  .city-tile.is-core::after {
    animation: core-pulse 3.2s ease-out infinite;
    animation-delay: 1.4s;
  }
  @keyframes core-pulse {
    0% { box-shadow: 0 0 0 0 rgba(45, 217, 198, 0.45); }
    70%, 100% { box-shadow: 0 0 0 22px rgba(45, 217, 198, 0); }
  }

  /* A light passes over the plane. One slow sweep, not a strobe. */
  .city-plane::before {
    content: "";
    position: absolute;
    inset: -18%;
    background: linear-gradient(115deg, transparent 42%, rgba(45, 217, 198, 0.16) 50%, transparent 58%);
    animation: sweep 7s linear infinite;
    animation-delay: 2s;
    pointer-events: none;
  }
  @keyframes sweep {
    from { transform: translateX(-60%); opacity: 0; }
    15%, 55% { opacity: 1; }
    to { transform: translateX(60%); opacity: 0; }
  }

  /* Section headings draw their own rule as they arrive. */
  .section-head h2 { position: relative; }
  .section-head h2::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: -14px;
    height: 2px;
    width: 62px;
    background: var(--accent);
    border-radius: 2px;
    transform: scaleX(0);
    transform-origin: left;
    transition: transform 0.8s var(--ease) 0.15s;
  }
  .section-head.in h2::after,
  .in .section-head h2::after { transform: scaleX(1); }

  .chip { transition: transform 0.2s var(--ease), border-color 0.2s var(--ease); }
  .chip:hover { transform: translateY(-1px); }

  .roles li { transition: padding-left 0.25s var(--ease), color 0.25s var(--ease); }
  .roles li:hover { padding-left: 10px; color: var(--text); }

  .foot-nav a { transition: color 0.2s var(--ease), transform 0.2s var(--ease); }
  .foot-nav a:hover { transform: translateY(-1px); }

  .stat { transition: transform 0.4s var(--ease); }
  .stat:hover { transform: translateY(-3px); }
}

/* Cross-document view transitions. Supporting browsers cross-fade between
   pages instead of flashing white; everywhere else this is inert. */
@view-transition { navigation: auto; }

@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) { animation: vt-out 0.22s var(--ease) both; }
  ::view-transition-new(root) { animation: vt-in 0.34s var(--ease) both; }
  @keyframes vt-out { to { opacity: 0; } }
  @keyframes vt-in { from { opacity: 0; transform: translateY(8px); } }
}

/* ------------------------------------------------------------------ admin

   Same visual language as the site so it does not feel like a different
   product, but plain forms and one section at a time. Everything on one page
   became unreadable the moment the map had seven organizations on it. */

.admin { min-height: 100dvh; }

.abar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px var(--gutter);
  border-bottom: 1px solid var(--hair);
  background: var(--bg-raise);
  position: sticky;
  top: 0;
  z-index: 20;
  flex-wrap: wrap;
}
.abar .brand { margin-right: auto; }
.auser { font-family: var(--mono); font-size: 13px; color: var(--accent); }
.ainline { display: contents; }

.alayout {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr);
  gap: clamp(24px, 4vw, 48px);
  max-width: 1080px;
  margin-inline: auto;
  padding: 34px var(--gutter) 110px;
  align-items: start;
}

.aside {
  position: sticky;
  top: 84px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.anav-head {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin: 18px 0 8px 12px;
}
.anav-head:first-child { margin-top: 0; }
.anav-item {
  display: block;
  padding: 9px 12px;
  border-radius: 9px;
  font-size: 14.5px;
  color: var(--text-dim);
  text-decoration: none;
  border-left: 2px solid transparent;
  transition: background 0.18s var(--ease), color 0.18s var(--ease), border-color 0.18s var(--ease);
}
.anav-item:hover { background: var(--surface); color: var(--text); }
.anav-item.is-on {
  background: var(--accent-soft);
  border-left-color: var(--accent);
  color: var(--accent);
}
.anav-empty { margin: 4px 12px; }

.amain { min-width: 0; }
.amain h1 { font-size: clamp(24px, 3.4vw, 34px); margin-bottom: 10px; }

.afield { display: block; margin-top: 20px; }
.alabel { display: block; font-size: 13.5px; font-weight: 500; margin-bottom: 6px; }
.ahint {
  display: block;
  font-size: 12.5px;
  color: var(--text-faint);
  margin-bottom: 8px;
  line-height: 1.55;
}
.ahint a { color: var(--text-dim); }
.afoot { margin-top: 44px; padding-top: 20px; border-top: 1px solid var(--hair); }

.afield input,
.afield textarea {
  width: 100%;
  padding: 11px 13px;
  border-radius: 10px;
  border: 1px solid var(--hair-strong);
  background: var(--bg);
  color: var(--text);
  font-family: inherit;
  font-size: 15px;
  line-height: 1.55;
  outline: none;
  resize: vertical;
}
.afield textarea { font-size: 14.5px; }
.afield input:focus,
.afield textarea:focus { border-color: var(--accent-line); }

/* Save stays reachable without scrolling back up a long section. */
.aactions {
  position: sticky;
  bottom: 0;
  display: flex;
  gap: 12px;
  margin-top: 28px;
  padding: 16px 0;
  background: linear-gradient(to top, var(--bg) 62%, transparent);
}

.aerror,
.asaved {
  padding: 11px 15px;
  border-radius: 10px;
  font-size: 14px;
  margin: 16px 0;
}
.aerror { border: 1px solid #7d3b3b; background: rgba(224, 108, 108, 0.1); color: #f0a9a9; }
.asaved { border: 1px solid var(--accent-line); background: var(--accent-soft); color: var(--accent); }

.alogin { min-height: 100dvh; display: grid; place-items: center; padding: 24px; }
.acard {
  width: 100%;
  max-width: 380px;
  padding: 30px;
  border: 1px solid var(--hair);
  border-radius: var(--radius);
  background: var(--bg-raise);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.5);
}
.acard h1 { font-size: 26px; margin: 18px 0 4px; }
.acard .btn { width: 100%; margin-top: 22px; }

@media (max-width: 820px) {
  .alayout { grid-template-columns: 1fr; padding-top: 22px; }
  .aside {
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
    gap: 6px;
    padding-bottom: 18px;
    border-bottom: 1px solid var(--hair);
  }
  /* The headings only make sense against a vertical list. */
  .anav-head { display: none; }
  .anav-item { border-left: 0; border: 1px solid var(--hair); border-radius: 999px; padding: 8px 14px; font-size: 14px; }
  .anav-item.is-on { border-color: var(--accent-line); }
}
