/* ============================================================
 * MEGA BUDDIES — component layer
 * Requires css/mb-tokens.css to be loaded first.
 *
 * Rules that are never broken here:
 *   no border-radius · no gradient · no box-shadow · no blur
 *   no emoji · nav + headings + labels UPPERCASE · hover = invert
 * ============================================================ */

/* -------------------- RESET ------------------------------- */

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

html {
  scroll-behavior: smooth;
  /* Plus the archive bar, which is 0px tall in every other theme. */
  scroll-padding-top: calc(var(--mb-nav-height) + var(--mb-legacy-h));
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--mb-bg);
  color: var(--mb-fg);
  font-family: var(--mb-font-mono);
  font-size: var(--mb-fs-m);
  line-height: var(--mb-lh-body);
  /* `clip`, not `hidden`: `hidden` makes body a scroll container, which
     silently breaks every `position: sticky` descendant. */
  overflow-x: clip;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img { max-width: 100%; display: block; }

a { color: inherit; text-decoration: none; }

button {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  cursor: pointer;
}

ul { list-style: none; margin: 0; padding: 0; }

/* The browser's own `[hidden]` rule is written at element specificity, so
   any class that declares `display` outranks it and the attribute stops
   meaning anything. `.mb-btn` is `inline-flex`, which is why the quest
   card's [SUBMIT] stayed on screen beside the proof form it had just
   opened — `submit.hidden = true` was running and doing nothing. Stated
   once here rather than per component: the attribute has to win wherever
   it is set, and JavaScript sets it in three places on this site. */
[hidden] { display: none !important; }

h1, h2, h3, h4, h5, h6 { margin: 0; }
p { margin: 0; }

::selection { background: var(--mb-fg); color: var(--mb-bg); }

:focus-visible {
  outline: 1px solid var(--mb-fg);
  outline-offset: 2px;
}

/* -------------------- TYPE -------------------------------- */

.mb-h1, .mb-h2, .mb-h3, .mb-h4 {
  font-family: var(--mb-font-display);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  line-height: var(--mb-lh-heading);
  letter-spacing: -0.005em;
}

.mb-h1 { font-size: var(--mb-fs-6xl); }
.mb-h2 { font-size: var(--mb-fs-5xl); }
.mb-h3 { font-size: var(--mb-fs-2xl); }
.mb-h4 { font-size: var(--mb-fs-l); }

.mb-label {
  font-family: var(--mb-font-mono);
  font-size: var(--mb-fs-xs);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  color: var(--mb-text-2);
}

.mb-micro {
  font-family: var(--mb-font-mono);
  font-size: var(--mb-fs-2xs);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.mb-muted { color: var(--mb-text-2); }
.mb-pink { color: var(--mb-pink); }
.mb-green { color: var(--mb-green); }

/* -------------------- LAYOUT ------------------------------ */

.mb-container {
  width: 100%;
  max-width: var(--mb-content-width);
  margin: 0 auto;
  padding: 0 var(--mb-pad-x);
}

@media (min-width: 769px) {
  .mb-container { padding: 0 var(--mb-pad-x-desktop); }
}

@media (min-width: 1440px) {
  .mb-container { padding: 0 var(--mb-pad-x-wide); }
}

.mb-section {
  padding: var(--mb-section-y) 0;
  border-top: 1px solid var(--mb-border-soft);
  position: relative;
}

/* The single contrast device: flip the band to light. */
.mb-section--invert {
  background: var(--mb-light-bg);
  color: var(--mb-light-fg);
  border-top-color: var(--mb-light-border);
  /* A module mask inside this band has to be painted in the band's ground,
     not the page's. See --mb-px-mask in css/mb-tokens.css. */
  --mb-px-mask: var(--mb-light-bg);
}

.mb-section--invert .mb-label,
.mb-section--invert .mb-muted { color: var(--mb-light-text-2); }

.mb-section--invert .mb-section-rule { background: var(--mb-light-border); }

.mb-section--invert .mb-card {
  background: transparent;
  border-color: var(--mb-light-border);
}

/* Anything whose default colour is the dark-theme foreground would render
   light-on-light here and vanish. These are the components that actually
   appear inside an inverted band; add to the list, never leave one out. */
.mb-section--invert .mb-card-index,
.mb-section--invert .mb-section-note,
.mb-section--invert .mb-step::before,
.mb-section--invert .mb-milestone-when,
.mb-section--invert .mb-scroll-cue { color: var(--mb-light-text-2); }

.mb-section--invert .mb-bracket { color: var(--mb-light-fg); }

.mb-section--invert .mb-bracket::before,
.mb-section--invert .mb-bracket::after { color: var(--mb-light-text-2); }

/* The counter-ground's own green. `--mb-green` is tuned against the page
   ground and measured 1.92:1 on the light band — see --mb-light-green in
   css/mb-tokens.css. Every accent that lands inside `.mb-section--invert`
   needs this swap; there are four of them and they are all here, in the
   `--invert` block, next to the rest of the band's overrides. */
.mb-section--invert .mb-bracket:hover { color: var(--mb-light-green); }

.mb-section--invert .mb-status--done { color: var(--mb-light-green); }
.mb-section--invert .mb-status--live { color: var(--mb-light-pink); }

.mb-section--invert a.mb-card:hover::after,
.mb-section--invert .mb-card--interactive:hover::after {
  border-bottom-color: var(--mb-light-green);
}

.mb-section--invert .mb-card-foot,
.mb-section--invert .mb-step,
.mb-section--invert .mb-rail-list li { border-color: var(--mb-light-border); }

.mb-section--invert a.mb-card::after,
.mb-section--invert .mb-card--interactive::after {
  border-bottom-color: var(--mb-light-border);
}

/* -------------------- SECTION HEAD ------------------------ */

.mb-section-head {
  display: flex;
  align-items: baseline;
  gap: 16px;
  margin-bottom: 40px;
  flex-wrap: wrap;
}

.mb-section-index {
  font-family: var(--mb-font-mono);
  font-size: var(--mb-fs-xs);
  font-weight: var(--mb-fw-bold);
  color: var(--mb-text-3);
  letter-spacing: 0.05em;
}

.mb-section--invert .mb-section-index { color: var(--mb-light-text-2); }

.mb-section-title {
  font-family: var(--mb-font-display);
  font-weight: var(--mb-fw-bold);
  font-size: var(--mb-fs-5xl);
  line-height: var(--mb-lh-heading);
  text-transform: uppercase;
  letter-spacing: -0.01em;
}

.mb-section-rule {
  height: 1px;
  background: var(--mb-border-soft);
  flex: 1 1 80px;
  min-width: 40px;
  align-self: center;
}

.mb-section-note {
  font-size: var(--mb-fs-xs);
  text-transform: uppercase;
  color: var(--mb-text-2);
  letter-spacing: 0.05em;
}

/* The same note, standing on its own under a block rather than sitting
   on a section head's baseline. Only the spacing is new: the type stays
   --mb-text-2 rather than dropping to --mb-text-3, which is 3.72:1 on
   paper and below AA for anything anyone is meant to read. */
.mb-section-note--afterthought { margin-top: 24px; }

/* -------------------- NAVBAR ------------------------------ */

.mb-nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  height: var(--mb-nav-height);
}

/* The 24px bottom-left chamfer is painted by a pseudo-element rather than
   clipping the header itself — clip-path on the header would also clip the
   fixed-position mobile menu that lives inside it. */
.mb-nav::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--mb-surface);
  border-bottom: 1px solid var(--mb-border);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 24px 100%, 0 calc(100% - 24px));
}

/* Full-bleed, unlike every other block on the site. The bar is chrome
   rather than content: capped at --mb-content-width and centred, the
   wordmark drifted inwards on a wide screen and read as an indent rather
   than as a corner. 12px is the gutter the burger and the theme menu need
   on the other side, not a margin. */
.mb-nav-inner {
  height: 100%;
  padding: 0 12px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}

.mb-logo {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--mb-font-display);
  font-weight: var(--mb-fw-bold);
  font-size: var(--mb-fs-m);
  text-transform: uppercase;
  letter-spacing: -0.01em;
  white-space: nowrap;
}

.mb-logo img {
  width: 20px;
  height: 20px;
  object-fit: contain;
}

/* Wordmark inverts against whatever sits behind it — the one "effect"
   this system allows. Kept off the bitmap mark, which would invert badly.

   Only ever seen when js/mb-pixel.js did not arrive, or when the bar is
   too narrow for the smaller of the two module pitches. Once the script
   has drawn the mark out of modules the word takes `.mb-sr` and this
   rule is decorating something clipped — which is the right way round:
   a blocked script leaves the logo set in the display face, never a
   hole. */
.mb-logo-text { mix-blend-mode: difference; }

/* The bar's mark, drawn on the same 5x7 grid as the hero's. Built by
   js/mb-pixel.js — see `logoMark` for why the pitch is measured rather
   than declared here.

   No blend mode. The hero's mark has none either, and `difference`
   against `--mb-fg` is the trap that empties a module wherever it lands
   on text of the same token. Over the bar's opaque surface there is
   nothing to invert against in any case. */
.mb-logo-grid { color: var(--mb-fg); }

/* The sweep. Ten seconds apart and on hover, both armed from JavaScript
   and both refused under reduced motion there — this rule is the second
   lock, not the first. It animates modules that are already lit and
   already on screen: nothing is withheld while it runs, and the mark is
   complete at every frame of it.

   `steps`, like everything else in this layer: the modules blink out and
   back rather than fading, so the mark reads as a grid switching rather
   than as type dissolving. The per-column delay is set inline. */
.mb-logo-grid.is-playing i.is-on {
  animation: mb-logo-sweep 640ms steps(2, end) both;
}

@keyframes mb-logo-sweep {
  0%   { opacity: 1; }
  35%  { opacity: 0.2; }
  100% { opacity: 1; }
}

.mb-nav-list {
  display: flex;
  align-items: center;
  gap: 2px;
}

.mb-nav-link {
  position: relative;
  isolation: isolate; /* keeps the z-index:-1 wipe inside the link */
  display: inline-block;
  padding: 6px 8px;
  font-family: var(--mb-font-mono);
  font-size: var(--mb-fs-xs);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  color: var(--mb-fg);
  white-space: nowrap;
  overflow: hidden;
  transition: color 150ms ease;
}

/* Skewed wipe on hover — AGEM's nav signature. */
.mb-nav-link::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--mb-fg);
  transform: scaleX(0) skewX(-45deg);
  transform-origin: left center;
  transition: transform 180ms ease;
  z-index: -1;
}

.mb-nav-link:hover { color: var(--mb-surface); }
.mb-nav-link:hover::before { transform: scaleX(1.2) skewX(-45deg); }

/* Set statically in each page's markup — there is no scroll-spy to go stale. */
.mb-nav-link[aria-current='page'] { color: var(--mb-green); }

/* The one nav item that is a call to action rather than a destination.
   Pink for exactly as long as you are not on it: once you are, the green
   `aria-current` rule below wins and the bar is back to one accent doing
   one job. The two are never lit at the same time, which is what keeps
   this inside the ration — a nav that shouted in two colours at once
   would be the third accent the system refuses. */
.mb-nav-link--cta { color: var(--mb-pink); }
.mb-nav-link--cta[aria-current='page'] { color: var(--mb-green); }

/* -------------------- DROPDOWN ---------------------------- */

/* A flex box rather than a block, and that is an alignment fix, not a
   preference. The list centres its items; a block <li> holding an
   inline-block link is as tall as the link's *line box*, which carries
   the strut's descender under it, so the anchors were sitting 4px above
   the two <button> triggers — whose flex parents give them no such
   leading. [COMMUNITY] and [THEME] were the ones that looked wrong; they
   were the two that were right. */
.mb-nav-item {
  position: relative;
  display: flex;
  align-items: center;
}

.mb-dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 200px;
  background: var(--mb-surface);
  border: 1px solid var(--mb-border);
  padding: 4px;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity 150ms ease, transform 150ms ease, visibility 150ms;
  clip-path: polygon(
    4px 0, calc(100% - 4px) 0,
    100% 4px, 100% calc(100% - 4px),
    calc(100% - 4px) 100%, 4px 100%,
    0 calc(100% - 4px), 0 4px
  );
}

/* Hover for a pointer, `.is-open` for everything else. js/mb-site.js owns
   the class and sets `data-menus-ready` once it does. */
[data-mb-menu]:hover > .mb-dropdown,
[data-mb-menu].is-open > .mb-dropdown {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

/* The keyboard's way in before that script arrives — and only before it.
   :focus-within and Escape cannot both be right: the trigger lives inside
   the menu, so returning focus to it after a close would immediately
   match this selector and reopen what was just dismissed. Once the
   handlers are attached the button is operable with Enter and Space like
   any other, so the fallback stands down. Note which way round it is:
   what it guards is a menu that can be opened at all, never a menu that
   cannot be closed. */
html:not([data-menus-ready]) [data-mb-menu]:focus-within > .mb-dropdown {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

/* The theme menu is the last thing in the bar, so its panel hangs from
   the right edge — anchored left it would run off the viewport. */
.mb-dropdown--end { left: auto; right: 0; }

/* A trigger that opens a menu rather than going anywhere. It is a
   <button> so a keyboard and a screen reader both get the right thing;
   these four lines only undo what a button brings with it. */
.mb-nav-menu {
  border: none;
  background: none;
  cursor: pointer;
  letter-spacing: inherit;
}

.mb-dropdown-link {
  display: block;
  width: 100%;
  text-align: left;
  border: none;
  background: none;
  cursor: pointer;
  padding: 8px 10px;
  font-family: var(--mb-font-mono);
  font-size: var(--mb-fs-xs);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  color: var(--mb-fg);
  transition: background 150ms ease, color 150ms ease;
}

.mb-dropdown-link:hover { background: var(--mb-fg); color: var(--mb-surface); }

/* Which theme is on. Same green the current nav item uses, for the same
   reason — it marks where you already are, and it is never on screen at
   the same time as the other one. */
.mb-dropdown-link[aria-pressed='true'] { color: var(--mb-green); }
.mb-dropdown-link[aria-pressed='true']:hover { color: var(--mb-surface); }

.mb-dropdown-link.is-planned {
  color: var(--mb-text-3);
  cursor: not-allowed;
}

.mb-dropdown-link.is-planned:hover { background: none; color: var(--mb-text-3); }

/* -------------------- BURGER ------------------------------ */

.mb-burger {
  display: none;
  width: 32px;
  height: 32px;
  border: 1px solid var(--mb-border);
  padding: 8px 6px;
  flex-direction: column;
  justify-content: space-between;
}

.mb-burger span {
  display: block;
  height: 1px;
  width: 100%;
  background: var(--mb-fg);
  transition: transform 180ms ease, opacity 180ms ease;
}

.mb-burger[aria-expanded='true'] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.mb-burger[aria-expanded='true'] span:nth-child(2) { opacity: 0; }
.mb-burger[aria-expanded='true'] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

@media (max-width: 1180px) {
  .mb-burger { display: flex; }

  .mb-nav-list {
    position: fixed;
    top: calc(var(--mb-nav-height) + var(--mb-legacy-h));
    left: 0;
    right: 0;
    bottom: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: var(--mb-bg);
    border-top: 1px solid var(--mb-border);
    padding: 8px var(--mb-pad-x) 32px;
    overflow-y: auto;
    transform: translateX(100%);
    transition: transform 220ms ease;
  }

  .mb-nav-list.is-open { transform: translateX(0); }

  /* Back to a block. Up in the bar the item is a flex box so the link and
     the menu triggers share one centre line; down here the dropdown is
     pinned open underneath its trigger, and a flex row would stand the
     two of them side by side. */
  .mb-nav-list .mb-nav-item { display: block; }

  .mb-nav-link {
    display: block;
    padding: 14px 0;
    font-size: var(--mb-fs-m);
    border-bottom: 1px solid var(--mb-border-faint);
  }

  .mb-nav-link::before { display: none; }
  .mb-nav-link:hover { color: var(--mb-green); }

  /* Scoped to the primary list. The theme menu lives outside it, stays a
     real dropdown at every width, and would otherwise be flattened into
     the bar itself — three theme names strung across a 44px strip. */
  .mb-nav-list .mb-dropdown {
    position: static;
    opacity: 1;
    visibility: visible;
    transform: none;
    border: none;
    border-bottom: 1px solid var(--mb-border-faint);
    background: transparent;
    padding: 0 0 8px 16px;
    clip-path: none;
  }

  .mb-nav-list .mb-dropdown-link { padding: 10px 0; color: var(--mb-text-2); }
  .mb-nav-list .mb-dropdown-link:hover { background: none; color: var(--mb-fg); }

  /* The COMMUNITY trigger has nothing to open down here — its panel is
     already pinned open below it — so it reads as the group's heading. */
  .mb-nav-list .mb-nav-menu { pointer-events: none; }
}

/* -------------------- THEME MENU -------------------------- */
/* Three swatches became a named menu: ROBINHOOD DADDY / MEGAETH CHAD /
   BUDDY ORIGIN. A 14px square could not say which theme it selected, and on
   a phone three of them sat where the burger wanted to be. */

/* `.mb-nav-inner` is `justify-content: space-between`, but an auto margin
   claims every pixel of free space BEFORE justify-content is consulted. So
   `margin-left: auto` on the menu itself would pack the logo and the nav
   list together on the left and leave a hole in the middle of the bar.
   The auto margin goes on the nav instead: logo left, nav list and theme
   menu flush right, exactly as the bar read before the menu existed. On
   `404.html` there is no `<nav>` and space-between alone puts the menu on
   the right. Below 1180px the list is fixed-positioned and the `<nav>` box
   collapses to zero width, so the auto margin still lands the menu and the
   burger on the right. */
.mb-nav-inner > nav { margin-left: auto; }

.mb-theme {
  position: relative;
  display: flex;
  align-items: center;
  margin-left: auto;
}

/* Two auto margins would split the free space between them and re-centre
   the nav list, so the menu keeps its auto margin only when it is the
   thing that has to be pushed — i.e. when no `<nav>` precedes it. */
.mb-nav-inner > nav ~ .mb-theme { margin-left: 0; }

/* Below the burger breakpoint the bar holds the wordmark, this menu and
   the burger. Three theme names are long; the trigger keeps its brackets
   and drops to the shortest thing that still labels it. */
@media (max-width: 600px) {
  .mb-theme .mb-nav-menu { padding: 6px 4px; font-size: var(--mb-fs-2xs); }
}

/* -------------------- TICKER ------------------------------ */

.mb-ticker {
  position: fixed;
  top: calc(var(--mb-nav-height) + var(--mb-legacy-h));
  left: 0;
  right: 0;
  z-index: 900;
  height: 28px;
  background: var(--mb-bg);
  border-bottom: 1px solid var(--mb-border-soft);
  overflow: hidden;
  display: flex;
  align-items: center;
}

.mb-ticker-track {
  display: flex;
  width: max-content;
  animation: mb-ticker-scroll 40s linear infinite;
}

/* Two identical groups: the -50% keyframe is only seamless when both halves
   measure the same, so the spacing lives on the items, never as a flex gap. */
.mb-ticker-group { display: flex; }

.mb-ticker-item {
  font-size: var(--mb-fs-2xs);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--mb-text-2);
  white-space: nowrap;
  margin-right: 32px;
}

.mb-ticker-item b { color: var(--mb-fg); font-weight: var(--mb-fw-bold); }

@keyframes mb-ticker-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

.mb-ticker:hover .mb-ticker-track { animation-play-state: paused; }

/* JS takes the wheel when it boots, so the CSS animation must stand down. */
.mb-ticker-track.is-driven { animation: none; }

/* -------------------- MAIN -------------------------------- */

/* `--mb-legacy-h` is 0px unless the archive warning bar is on the page, so
   this reads exactly as it always did in every other theme. See the
   ARCHIVE WARNING section below. */
.mb-main { padding-top: calc(var(--mb-nav-height) + var(--mb-legacy-h) + 28px); }

/* Pages without the ticker strip. */
.mb-main--flush { padding-top: calc(var(--mb-nav-height) + var(--mb-legacy-h)); }

/* -------------------- TWO COLUMNS ------------------------- */

.mb-cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 48px;
}

@media (max-width: 900px) { .mb-cols { grid-template-columns: minmax(0, 1fr); gap: 32px; } }

/* -------------------- KEY / VALUE ROWS -------------------- */

.mb-kv { border-top: 1px solid var(--mb-border-soft); }

.mb-section--invert .mb-kv { border-top-color: var(--mb-light-border); }

.mb-kv-row {
  display: flex;
  align-items: baseline;
  gap: 16px;
  padding: 14px 0;
  border-bottom: 1px solid var(--mb-border-soft);
  font-size: var(--mb-fs-s);
  text-transform: uppercase;
}

.mb-section--invert .mb-kv-row { border-bottom-color: var(--mb-light-border); }

.mb-kv-key {
  flex: 0 0 120px;
  font-weight: var(--mb-fw-bold);
  letter-spacing: 0.05em;
}

/* `min-width` and `overflow-wrap` are not decoration — without them the
   admin console pushes the page wider than the phone it is read on.

   A value used to be a phrase written by us. The moderation queue made it
   a proof link pasted by a visitor: one unbreakable ~50-character token.
   A flex item's `min-width` defaults to `auto`, which refuses to shrink
   below the longest word, and `anywhere` is what gives that word a break
   opportunity — `break-word` would not, because it is ignored when the
   intrinsic minimum is computed and the item would still demand its full
   width. Measured on admin.html at 390px against a real status URL: the
   span asked for 412px inside a 390px viewport and the document scrolled
   sideways.

   tools/audit.js cannot catch this one. It runs with no API behind the
   site, so the queue it would have to measure is never populated. */
.mb-kv-val {
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--mb-text-2);
}

.mb-section--invert .mb-kv-val { color: var(--mb-light-text-2); }

.mb-kv-row .mb-status { flex: 0 0 auto; align-self: center; }

/* A wider key column for tables whose labels are phrases rather than
   single words. 120px breaks WHITELIST SPOTS across two lines and leaves
   the value floating beside a two-line label. */
.mb-kv--wide .mb-kv-key { flex-basis: 200px; }

/* The manifesto, set as a table like everything else on the site rather
   than in the terminal frame it used to wear. The key is an index, so it
   only needs room for two digits; the value is the statement and carries
   the weight the terminal's monospace body used to. */
.mb-kv--creed .mb-kv-row { align-items: baseline; padding: 20px 0; }
.mb-kv--creed .mb-kv-key { flex-basis: 56px; color: var(--mb-text-3); }
.mb-section--invert .mb-kv--creed .mb-kv-key { color: var(--mb-light-text-2); }

.mb-kv--creed .mb-kv-val {
  font-size: var(--mb-fs-l);
  line-height: 1.55;
  color: var(--mb-fg);
}

.mb-section--invert .mb-kv--creed .mb-kv-val { color: var(--mb-light-fg); }

/* One line of accent per section — the closing line, and nothing else. */
.mb-kv-val--accent,
.mb-section--invert .mb-kv--creed .mb-kv-val--accent { color: var(--mb-light-pink); }

@media (max-width: 600px) {
  .mb-kv--creed .mb-kv-val { font-size: var(--mb-fs-m); }
}

@media (max-width: 600px) {
  .mb-kv-row { flex-wrap: wrap; gap: 6px 12px; }
  .mb-kv-key { flex-basis: 100%; }
}

/* -------------------- ALERT ------------------------------- */
/* The one thing on this site that has to be believed before it is read:
   there is no $BUDDY contract yet, so anything calling itself one is
   somebody taking money. It is a bordered block rather than a line of
   copy because a scam warning that reads like the paragraph above it is
   a scam warning nobody sees.

   Painted in `--mb-pink`, which this palette already defines as the
   alert role — not in `--mb-error`. That token is deliberately outside
   the palette and is tuned for the 404's display figure: #DC2626 on the
   MEGAETH CHAD ground measures 2.87:1, under the floor for anything, let
   alone for a paragraph. The alert accent measures 5.76:1 on that theme,
   5.79:1 on the archive's blue and 15:1 on ROBINHOOD DADDY, where the role
   is carried by the brand lime — one hue per theme, as the system has it
   everywhere else.

   This block is a section's whole accent ration. Nothing else in the
   band it sits in may be pink. */
.mb-alert {
  margin-top: 32px;
  padding: 16px;
  border: 1px solid var(--mb-pink);
  background: var(--mb-status-live-bg);
  color: var(--mb-pink);
  font-size: var(--mb-fs-s);
  line-height: 1.6;
  text-transform: uppercase;
  clip-path: polygon(
    4px 0, calc(100% - 4px) 0,
    100% 4px, 100% calc(100% - 4px),
    calc(100% - 4px) 100%, 4px 100%,
    0 calc(100% - 4px), 0 4px
  );
}

/* Bracketed, like every other label here, and set on its own line so the
   sentence starts flush rather than being indented by its own tag. */
.mb-alert-tag {
  display: block;
  margin-bottom: 8px;
  font-weight: var(--mb-fw-bold);
  letter-spacing: 0.1em;
}

/* Inside a light band the ordinary accent measures 2.29:1, so the alert
   would be the least legible thing in the section it is shouting in. */
.mb-section--invert .mb-alert {
  border-color: var(--mb-light-pink);
  color: var(--mb-light-pink);
  background: none;
}

/* -------------------- METER ------------------------------- */

.mb-meter {
  height: 12px;
  border: 1px solid var(--mb-border);
  background: var(--mb-surface);
  display: block;
}

.mb-meter-fill {
  display: block;
  height: 100%;
  background: var(--mb-green);
}

/* -------------------- HERO -------------------------------- */

.mb-hero {
  position: relative;
  min-height: calc(100vh - var(--mb-nav-height) - var(--mb-legacy-h) - 28px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Longhand only — the shorthand would wipe out .mb-container's
     horizontal padding, since .mb-hero is applied to the same element. */
  padding-top: 64px;
  padding-bottom: 48px;
  overflow: hidden;
}

/* On a phone the hero gains a whole object — the buddy stops being a
   layer behind the copy and stands in the flow above the wordmark (see
   .mb-buddy-bg below). The desktop padding was written for a hero whose
   tallest thing was the wordmark; keeping it on top of the buddy is what
   pushed the mark most of a screen down. The band is still a screen
   tall, so `justify-content: center` still has slack to hand out; this
   only stops the padding from spending it twice. */
@media (max-width: 768px) {
  .mb-hero {
    padding-top: 24px;
    padding-bottom: 32px;
  }
}

/* One column since the buddy left it. The hero copy has to stay off the
   middle of the hero, which is where the buddy now is, so it is capped
   rather than allowed to run the full container width. */
.mb-hero-grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  max-width: 720px;
}

/* The cap has to move with the type it is capping. --mb-fs-10xl steps up
   to 200px at this width (see css/mb-tokens.css), where BUDDIES measures
   about 872px and would spill out of a 720px column. Measured at 2560. */
@media (min-width: 1921px) {
  .mb-hero-grid { max-width: 980px; }
}

.mb-hero-wordmark {
  font-family: var(--mb-font-display);
  font-weight: var(--mb-fw-bold);
  font-size: var(--mb-fs-10xl);
  line-height: 0.85;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  margin: 0;
}

.mb-hero-wordmark > span { display: block; }
.mb-hero-wordmark .mb-hero-line-2 { color: var(--mb-text-2); }

/* -------------------- MODULE WORDMARK --------------------- */
/* js/mb-pixel.js draws each line as a 5x7-per-letter grid of modules and
   only then adds `.is-wm-drawn`, which is what takes the type out of
   view. Direction matters, as everywhere else here: a script that never
   arrives leaves the wordmark set in the display face, not a hole.

   The words are taken out of view, not out of the document: they stay in
   the accessibility tree and the grids beside them are aria-hidden, so
   the mark is announced exactly once on both paths.

   `clip`, deliberately, and not the `clip-path` that would be the modern
   spelling: tools/audit.js reads a computed `clip-path: inset(100%)` as
   content a stuck effect has hidden, and it cannot tell this apart from
   the real thing. `.mb-sr` is the same technique the skip link already
   uses, which is why this borrows it rather than restating it. */
.is-wm-drawn .mb-wm-word {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.mb-hero-wordmark > span { position: relative; }

.is-wm-drawn > span {
  /* The grid is the line's whole height once it is drawn; the display
     face's leading underneath it would add a second, larger box. */
  line-height: 0;
}

/* inline-grid, not grid: a block-level grid box stretches to the column
   even when its tracks are fixed, so MEGA's shorter row of modules would
   still occupy BUDDIES' full width and the two lines would look the same
   size. */
.mb-wm-grid {
  display: inline-grid;
  justify-content: start;
  line-height: 0;
}

/* Unlit modules are absent, not dimmed. A ghosted full rectangle behind
   the letters reads as a panel the mark is printed on, which is the one
   thing borders are for here. */
.mb-wm-grid i { background: none; }
.mb-wm-grid i.is-on { background: currentColor; }

/* The two lines are one mark, so the gap between them is a module row
   rather than the type's leading. */
.mb-hero-wordmark > span + span { margin-top: 10px; }

.mb-hero-sub {
  margin-top: 24px;
  max-width: 52ch;
  font-size: var(--mb-fs-s);
  text-transform: uppercase;
  color: var(--mb-text-2);
  line-height: 1.6;
}

.mb-hero-actions {
  margin-top: 32px;
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}

/* Content-width buttons stack into a ragged left column on a phone. */
@media (max-width: 600px) {
  .mb-hero-actions { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .mb-hero-actions .mb-btn { justify-content: center; }
}

/* 3D buddy — a background layer, centred behind the whole hero rather
   than a framed panel beside the copy. No border, no chamfer and no
   corner labels: it is the ground the hero stands on, and a frame around
   it would read as a second card competing with the wordmark.

   Empty until js/mb-buddy.js puts a canvas in it. There is no static
   mark underneath any more: a pixel rabbit that gets replaced by a
   wireframe one a second later is a preloader for something that is not
   loading, and the layer is decoration — a hero with nothing behind the
   type is the correct fallback, not a degraded one.

   Square, because the camera in js/mb-buddy.js frames the stage box and
   a hero-shaped one would leave the buddy small and lost in the middle.
   Sized off the viewport so it scales with the type beside it. */
.mb-buddy-bg {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(64vw, 660px);
  aspect-ratio: 1 / 1;
  z-index: 0;
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Copy is read over this, so the wireframe is held well below the
     foreground's own weight. Any lower and the buddy stops reading as a
     shape at all on the light themes. */
  opacity: 0.38;
}

/* Once the hero is wide enough to hold both, the buddy moves out from
   behind the copy and takes the empty right half. `.mb-hero-grid` is
   capped at 720px on the left, so above this width there is a column
   nothing is standing in — centred, the buddy spent it sitting under
   BUDDIES and the sub-paragraph instead.

   The transform loses its horizontal half, which is what `right` is now
   doing; vertical centring stays. Anchored to the right edge rather than
   given a left offset so the stage cannot be pushed past `.mb-hero`'s
   `overflow: hidden` by a viewport it did not expect, and held off the
   edge by the container's own gutter so it lines up with everything
   else on the page. It also comes out from under the type, so it no
   longer has to hide as far behind it. */
/* Sized to the column rather than to the viewport. `46vw` was a guess at
   how much room the copy leaves, and it guessed high: at 1899 the stage
   measured 620px against a 438px gap, so 182px of rabbit sat under the
   right-hand end of BUDDIES and the two read as one crowded object.

   The gap is exact and does not need guessing at — it is the container's
   content width less `.mb-hero-grid`'s own cap — so that is what the
   width is now written as, with 32px held back so the buddy clears the
   type instead of touching it. `100%` resolves against `.mb-hero`'s
   padding box, which is why the two gutters are subtracted here as well.

   `.mb-hero-grid`'s cap steps to 980px at 1921 (see above), so the third
   query is not a repeat: the same sentence with the number that is true
   up there. */
@media (min-width: 1200px) {
  .mb-buddy-bg {
    left: auto;
    right: var(--mb-pad-x-desktop);
    transform: translate(0, -50%);
    width: min(100% - 720px - 2 * var(--mb-pad-x-desktop) - 32px, 620px);
    opacity: 0.5;
  }
}

@media (min-width: 1440px) {
  .mb-buddy-bg {
    right: var(--mb-pad-x-wide);
    width: min(100% - 720px - 2 * var(--mb-pad-x-wide) - 32px, 620px);
  }
}

@media (min-width: 1921px) {
  .mb-buddy-bg {
    width: min(100% - 980px - 2 * var(--mb-pad-x-wide) - 32px, 620px);
  }
}

.mb-buddy-bg canvas {
  width: 100% !important;
  height: 100% !important;
}

/* A tablet has no room to keep the buddy clear of the copy, so it is
   smaller, fainter, and lifted to sit behind the wordmark rather than
   behind the paragraph — bold display type carries a texture behind it
   that 15px body copy does not. */
@media (max-width: 1024px) {
  .mb-buddy-bg {
    width: min(62vw, 300px);
    top: 38%;
    opacity: 0.22;
  }
}

/* On a phone it stops being a background layer altogether and becomes
   the first thing in the hero.

   Behind the wordmark it had to be held at 0.22 to stay readable
   through, which is most of the way to not being there. The room above
   the wordmark is empty at every phone size the harness walks, so the
   buddy is put in it: `position: relative` makes the stage an ordinary
   flex item of `.mb-hero`, and it is already the first child in the
   markup, so it lands above the copy with no reordering. Nothing is read
   over it any more, which is what pays for the opacity.

   768px, not the 1024px above, because that is `BREAKPOINT` in
   js/device-detector.js: at or below it the visitor is on the mobile
   half of the twin, which is the page this was asked for. The band
   between the two keeps the treatment it had.

   `flex: none` because the stage has no height of its own — it is an
   `aspect-ratio` box, and a flex item is free to shrink one to nothing.

   Every inset and the transform are reset, not overridden piecemeal:
   they are what the absolute layer needs and all of them are wrong
   here. */
@media (max-width: 768px) {
  .mb-buddy-bg {
    position: relative;
    flex: none;
    top: auto;
    left: auto;
    right: auto;
    transform: none;
    opacity: 0.55;
    /* Nothing until there is something. In the absolute layer above, a
       buddy that never arrives costs no space; in the flow it would be a
       240px hole above the wordmark on a blocked CDN, a refused WebGL
       context, or with scripts off entirely. js/mb-buddy.js writes
       `live` only once a canvas is actually in the box, which is the
       same signal the static mark underneath already waits for, so the
       room is taken when the buddy is there to fill it and not before.

       The rule runs both ways, and js/mb-buddy.js is written around it:
       the stage cannot be measured until the attribute below opens it,
       so the script writes the attribute first and sizes the renderer
       once there is a box, rather than measuring a box CSS is holding
       shut. Measuring first is how this deadlocked. */
    width: 0;
    margin: 0;
  }

  /* 200, not 240, and 16 under it rather than 24: js/mb-buddy.js now
     frames the rabbit for a box this size instead of for the desktop
     layer, so it fills more of a smaller box and reads larger than it
     did while taking less of the hero. */
  .mb-buddy-bg[data-buddy='live'] {
    width: min(52vw, 200px);
    margin: 0 auto 16px;
  }
}

/* Scroll cue */
/* Positioned, like .mb-hero-grid, because the buddy layer behind them is:
   a static element paints below a positioned z-index:0 sibling, so the
   cue would be under the canvas without this. */
.mb-scroll-cue {
  position: relative;
  z-index: 1;
  margin-top: 48px;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: var(--mb-fs-2xs);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--mb-text-3);
}

.mb-scroll-cue span:last-child { animation: mb-cue 2s ease-in-out infinite; }

@keyframes mb-cue {
  0%, 100% { transform: translateY(0); opacity: 0.4; }
  50%      { transform: translateY(4px); opacity: 1; }
}

/* -------------------- BUTTONS ----------------------------- */

.mb-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 12px 18px;
  font-family: var(--mb-font-mono);
  font-size: var(--mb-fs-s);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  border: 1px solid var(--mb-fg);
  background: transparent;
  color: var(--mb-fg);
  cursor: pointer;
  transition: background 150ms ease, color 150ms ease, border-color 150ms ease;
  clip-path: polygon(
    4px 0, calc(100% - 4px) 0,
    100% 4px, 100% calc(100% - 4px),
    calc(100% - 4px) 100%, 4px 100%,
    0 calc(100% - 4px), 0 4px
  );
}

.mb-btn:hover { background: var(--mb-fg); color: var(--mb-bg); }

/* The one filled button on a page, and the only place a theme is allowed
   to put its brand colour on something that is not a status. It reads the
   solid pair rather than fg/bg directly: NIGHT and PLUM define that pair
   AS fg/bg, so they are unchanged, and ROBINHOOD DADDY sets it to the
   lime — which is what makes that theme look like Robinhood instead of
   merely dark and warm.

   Still one per viewport. A second filled button is two accent blocks on
   one screen, which is the ration this system has always had; the tab
   switch and the selected tab stay on `--mb-fg` for exactly that reason. */
.mb-btn--solid {
  background: var(--mb-solid-bg);
  color: var(--mb-solid-fg);
  border-color: var(--mb-solid-bg);
}

.mb-btn--solid:hover {
  background: transparent;
  color: var(--mb-solid-bg);
  border-color: var(--mb-solid-bg);
}

.mb-btn--pink { border-color: var(--mb-pink); color: var(--mb-pink); }
.mb-btn--pink:hover { background: var(--mb-pink); color: var(--mb-bg); }

.mb-btn--sm { padding: 8px 12px; font-size: var(--mb-fs-xs); }

.mb-btn[aria-disabled='true'],
.mb-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: none;
}

.mb-section--invert .mb-btn { border-color: var(--mb-light-fg); color: var(--mb-light-fg); }
.mb-section--invert .mb-btn:hover { background: var(--mb-light-fg); color: var(--mb-light-bg); }
.mb-section--invert .mb-btn--solid { background: var(--mb-light-fg); color: var(--mb-light-bg); }
.mb-section--invert .mb-btn--solid:hover { background: transparent; color: var(--mb-light-fg); }

/* ============================================================
 * FORMS
 * The design system has always specified a 3px chamfer for
 * inputs; the site simply never had a form until the quest
 * board. Corner treatment is clip-path — there is no
 * border-radius in this codebase and tools/audit.js fails the
 * run if one appears.
 *
 * The polygons below are spelled out rather than reusing the
 * .mb-clip-3 / .mb-clip-4 utilities, matching how .mb-card
 * already does it: a component owns its own geometry so the
 * markup does not have to carry two classes to look right.
 *
 * Field labels reuse the `.mb-label` defined near the top of
 * this file. Redeclaring it here would have applied to every
 * `.mb-label` on the site, not just the ones in a form — the
 * selector is global and the cascade would have carried the
 * change onto /ai-companions, which uses it twice.
 * ============================================================ */

.mb-field { display: flex; flex-direction: column; gap: 6px; }

/* `.mb-page-sub` carries a top margin and no bottom one — everywhere else on
   the site it is the last thing in a page hero, so it never needed one. Put
   the air back when a form follows it, rather than giving every `.mb-page-sub`
   a bottom margin it would only want here. */
.mb-page-sub + .mb-field { margin-top: 32px; }

.mb-input {
  clip-path: polygon(
    3px 0, calc(100% - 3px) 0,
    100% 3px, 100% calc(100% - 3px),
    calc(100% - 3px) 100%, 3px 100%,
    0 calc(100% - 3px), 0 3px
  );
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--mb-border);
  background: var(--mb-surface-2);
  color: var(--mb-fg);
  font-family: var(--mb-font-mono);
  font-size: var(--mb-fs-s);
  text-transform: uppercase;
}

.mb-input::placeholder { color: var(--mb-text-3); text-transform: uppercase; }
.mb-input:focus { border-color: var(--mb-fg); }

/* The ring is inset by exactly the chamfer, and that is not a style choice:
   `clip-path` clips an element's outline along with everything else it
   paints, so at the reset's `outline-offset: 2px` the ring lands outside the
   clip region and never appears at all. Verified in the browser — the same
   is true of every chamfered control here, `.mb-btn` included, which is why
   a focused [SUBMIT] shows no ring today. Offsetting inward by the 3px
   chamfer clears the corner cut on all four corners.

   The border change stays. It is the state; the ring is what makes the
   state legible without hunting for it. */
.mb-input:focus-visible { outline: 1px solid var(--mb-fg); outline-offset: -3px; }

.mb-input[aria-invalid='true'] { border-color: var(--mb-pink); }

/* Addresses and proof links are not brand voice — they are the visitor's
   own text and must not be shouted back at them in a different case. */
.mb-input--raw { text-transform: none; }
.mb-input--raw::placeholder { text-transform: none; }

.mb-form-row { display: flex; gap: 8px; align-items: flex-start; }
.mb-form-row .mb-input { flex: 1 1 auto; min-width: 0; }
.mb-form-row .mb-btn { flex: 0 0 auto; }

.mb-form-note {
  margin: 6px 0 0;
  font-size: var(--mb-fs-xs);
  color: var(--mb-text-2);
}

.mb-form-note--error { color: var(--mb-pink); }
.mb-form-note--ok { color: var(--mb-green); }

@media (max-width: 600px) {
  .mb-form-row { flex-direction: column; }
  .mb-form-row .mb-btn { width: 100%; justify-content: center; }
}

/* ============================================================
 * QUEST BOARD
 * ============================================================ */

.mb-quests { display: flex; flex-direction: column; gap: 12px; }

.mb-quest {
  clip-path: polygon(
    4px 0, calc(100% - 4px) 0,
    100% 4px, 100% calc(100% - 4px),
    calc(100% - 4px) 100%, 4px 100%,
    0 calc(100% - 4px), 0 4px
  );
  padding: 16px;
  border: 1px solid var(--mb-border);
  background: var(--mb-surface);
}

.mb-quest--pinned { border-color: var(--mb-pink); }

.mb-quest-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
  flex-wrap: wrap;
}

/* Foreground, not green, and the reason is the ration rather than taste.
   This class carries the value on every card, so on a five-task board it
   painted green six times against pink twice — and the pinned card said
   PINNED in green beside a border already saying it in pink. An accent
   repeated on every row is not an accent; it is the body colour with
   extra steps, and it leaves nothing for the states that must stand out.

   What green is left to do: the DONE badge and the leaderboard's
   self-marker. What pink is left to do: pending state and pinning. One
   of each in view, each doing one job. */
.mb-quest-points {
  font-size: var(--mb-fs-xs);
  letter-spacing: 0.08em;
  color: var(--mb-fg);
}

.mb-quest-title {
  margin: 0;
  font-size: var(--mb-fs-m);
  text-transform: uppercase;
}

.mb-quest-body {
  margin: 4px 0 0;
  font-size: var(--mb-fs-s);
  color: var(--mb-text-2);
  text-transform: uppercase;
}

.mb-quest-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
  flex-wrap: wrap;
}

/* Pushes what follows to the right edge of a flex row. Used by the quest
   card and the account panel, both of which build their rows in JS. */
.mb-spacer { flex: 1 1 auto; }

.mb-quest-proof {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--mb-border-soft);
}

/* The referral card stacks three things in here — the link, the share
   button and the roll of who joined — and they were built butted against
   each other, each one's label reading as a caption for the row above it.

   Keyed off the two things only that card puts in here, not off every
   sibling: an ordinary task's proof form fills the same box with a label,
   an input row and a form note, and those three are one field whose parts
   belong close together. `.mb-form-note` keeps its 6px. */
.mb-quest-proof > .mb-field + *,
.mb-quest-proof > .mb-quest-actions + * { margin-top: 16px; }

/* -------------------- TABS -------------------------------- */
/* /get-buddy has two faces — the board people come for and the account
   behind it — and stacking both down one page buried the tasks. The tab
   list is chrome that does nothing at all without JavaScript, so it stays
   hidden until js/mb-quests.js has wired it and set `data-tabs-ready` on
   <html>. Note which way round that is: nothing here hides a *panel*, so
   a blocked script leaves both of them stacked and readable rather than
   leaving the page behind a dead switch. That is the same contract as the
   `.mb-motion` failsafe, arrived at from the other direction. */

.mb-tabs { display: none; }

/* The three faces of the page are one choice, so they are drawn as one
   control across the whole column rather than as chips floated left.
   Same 1px-gutter bed as `.mb-grid`: the gap between the thirds is the
   border they share.

   `grid-auto-flow: column` rather than a fixed track count: the tab list
   is markup, and a page that ships two tabs or four should not need this
   rule edited to keep them equal. `1fr` on the implicit columns is what
   makes them equal — auto tracks would size to the longest label and
   leave [TASKS] a stub beside [LEADERBOARD]. */
[data-tabs-ready] .mb-tabs {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 1px;
  background: var(--mb-border);
  border: 1px solid var(--mb-border);
  margin-bottom: 48px;
}

.mb-tab {
  padding: 16px 18px;
  border: none;
  background: var(--mb-bg);
  color: var(--mb-fg);
  font-family: var(--mb-font-mono);
  font-size: var(--mb-fs-s);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  text-align: center;
  cursor: pointer;
  transition: background 150ms ease, color 150ms ease;
}

.mb-tab:hover,
.mb-tab:focus-visible,
.mb-tab[aria-selected='true'] {
  background: var(--mb-fg);
  color: var(--mb-bg);
}

/* Three labels across a phone, and [LEADERBOARD] is thirteen characters of
   a monospace face. The control stays one row — stacked, it stops reading
   as a switch and becomes a menu — so the type comes down to the badge
   size instead and the padding with it. Measured at 360px, the narrowest
   width the site claims to serve. */
@media (max-width: 600px) {
  .mb-tab {
    padding: 12px 4px;
    font-size: var(--mb-fs-2xs);
    letter-spacing: 0;
  }
}

/* Only ever spaces panels that are on screen together, which is the
   no-JS case: once the switcher is running exactly one is visible and a
   margin between them would be a gap under the tab list. */
.mb-tabpanel + .mb-tabpanel { margin-top: 72px; }
[data-tabs-ready] .mb-tabpanel + .mb-tabpanel { margin-top: 0; }

/* -------------------- INVITATION -------------------------- */
/* Shown once, at the top of the board, to somebody who arrived on another
   buddy's link. It is a banner and not a modal on purpose: a scrim over
   the tasks would be the first thing a new visitor has to dismiss before
   they can see what they were invited to, and an `aria-modal` dialog owes
   the keyboard a focus trap that this — a greeting nobody has to answer —
   does not earn.

   Built entirely by js/mb-quests.js. There is nothing to say to a visitor
   who did not come in on a link, so the markup ships an empty slot rather
   than a banner this stylesheet would then have to hide — the same
   direction as everything else on this page.

   No accent. The pinned referral card below it is the board's pink, and
   two pink boxes in one column is the ration spent twice on the same
   idea. This one carries its weight with the panel's 8px chamfer and a
   full border instead. */
/* The line above the tasks that says who is reading them. */
.mb-board-state {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 40px;
  padding-bottom: 24px;
  border-bottom: 1px solid var(--mb-border-soft);
}

/* `.mb-state` is a boxed placeholder everywhere else on this page; here
   it is a sentence, and the band already has a rule under it. */
.mb-board-state .mb-state {
  margin: 0;
  padding: 0;
  border: none;
  text-align: left;
  color: var(--mb-fg);
}

/* Handles the visitor did not write are theirs, not ours, and keep their
   own casing inside a list that is otherwise shouted. */
/* One line that runs across, not a block that wraps down. A referrer
   with thirty names had a paragraph of chips pushing the sign-out button
   off the panel; the roll is a list you scan, so it scrolls sideways and
   the panel keeps its height whatever the count.

   `overflow-x: auto` and not `hidden`: a keyboard tab onto a chip past
   the right edge scrolls it into view either way, but a pointer needs
   somewhere to grab. Trap 8 in CLAUDE.md is about `overflow-x` on the
   BODY breaking sticky descendants — this is a leaf container with no
   sticky inside it, and it is the one place the rule does not apply.

   The scrollbar is left alone rather than hidden: something that scrolls
   and shows no sign of it is a list that looks truncated. */
.mb-roll {
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
  margin-top: 4px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  padding-bottom: 4px;
}

.mb-roll-item {
  /* A flex item's floor is its content, and these must not be squeezed
     into two lines each by a row that is wider than the panel. */
  flex: 0 0 auto;
  padding: 4px 8px;
  font-size: var(--mb-fs-xs);
  border: 1px solid var(--mb-border);
  background: var(--mb-status-idle-bg);
  color: var(--mb-text-2);
  white-space: nowrap;
  text-decoration: none;
  clip-path: polygon(
    4px 0, calc(100% - 4px) 0,
    100% 4px, 100% calc(100% - 4px),
    calc(100% - 4px) 100%, 4px 100%,
    0 calc(100% - 4px), 0 4px
  );
}

/* Every name on the roll is a real account on X, and a handle you cannot
   click is a claim you cannot check. Hover is the site's inversion, the
   same answer `.mb-card` and `.mb-btn` give. */
a.mb-roll-item {
  transition: background 150ms ease, color 150ms ease, border-color 150ms ease;
}

a.mb-roll-item:hover,
a.mb-roll-item:focus-visible {
  background: var(--mb-fg);
  border-color: var(--mb-fg);
  color: var(--mb-bg);
}

/* ============================================================
 * ACCOUNT AND LEADERBOARD
 * ============================================================ */

.mb-account-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
}

/* The account panel is assembled in JS, so the spacing between its parts
   lives here rather than being set on each node as it is built. Sibling
   selectors only — a signed-out visitor gets the sign-in line, a button
   and the wallet field, and nothing else in the list is on the page. */
.mb-panel-body .mb-kv,
.mb-panel-body .mb-account-grid,
.mb-panel-body > .mb-field { margin-top: 20px; }

.mb-panel-body .mb-btn + .mb-field { margin-top: 28px; }

.mb-account-avatar {
  width: 40px;
  height: 40px;
  border: 1px solid var(--mb-border);
  filter: grayscale(1);
}

.mb-account-avatar:hover { filter: none; }

/* An X handle is a name its owner chose, not chrome we wrote, so the
   uppercase rule does not apply to it — the same exception the design
   system already makes for quoted tweets and chat. `@Mega_Buddies` was
   being shouted back as `@MEGA_BUDDIES` by `.mb-quest-title` in the
   account panel and by `.mb-kv-row` in the leaderboard and the admin
   feed, contradicting the comment sitting directly above the line that
   builds it.

   A modifier rather than a relaxation of those three: they are right for
   everything else they hold. It sits below `.mb-quest-title` on purpose —
   both are single class selectors and the tie goes to source order. The
   two `.mb-kv` cases inherit rather than declare, so any declaration on
   the element itself already wins. */
.mb-handle { text-transform: none; }

/* The same opt-out for a brand that is spelled with its own capitals.
   `MegaETH` is a name, not a label, and the chrome around it — ticker
   items, key-value cells, the hero paragraph, the footer link — all
   inherit `text-transform: uppercase`, which would shout it back as
   MEGAETH wherever it appears.

   Always applied to a `<span>` of its own rather than added beside an
   existing component class. Both are single-class selectors, so a class
   sharing an element would be decided by source order in this file; a
   nested element has nothing to tie with and inherits from a declaration
   that is already on itself.

   The theme called MEGAETH CHAD is deliberately NOT this. That is a
   label the design system fixes verbatim, in the same voice as ROBINHOOD
   DADDY and BUDDY ORIGIN, and it names a skin rather than the chain. */
.mb-cased { text-transform: none; }

.mb-lb-rank {
  flex: 0 0 auto;
  min-width: 3ch;
  color: var(--mb-text-3);
  font-variant-numeric: tabular-nums;
}

.mb-lb-points {
  flex: 0 0 auto;
  color: var(--mb-fg);
  font-variant-numeric: tabular-nums;
}

.mb-lb-row--self { border-left: 1px solid var(--mb-green); padding-left: 8px; }

/* The empty and offline states are ordinary content. Nothing on this page
   is hidden by CSS that only JavaScript can reveal — see the failsafe rule
   in CLAUDE.md. */
.mb-state {
  padding: 24px;
  border: 1px solid var(--mb-border-soft);
  text-align: center;
  font-size: var(--mb-fs-s);
  color: var(--mb-text-2);
  text-transform: uppercase;
}

/* The leaderboard sits in an inverted band, so every colour used inside it
   needs its light-ground counterpart. Without these the rank column and
   the empty state render near-invisible dark-on-dark values on #DFD9D9. */
.mb-section--invert .mb-state {
  border-color: var(--mb-light-border);
  color: var(--mb-light-text-2);
}

.mb-section--invert .mb-lb-rank { color: var(--mb-light-text-2); }
.mb-section--invert .mb-lb-points { color: var(--mb-light-fg); }
.mb-section--invert .mb-lb-row--self { border-left-color: var(--mb-light-green); }

/* Bracket action — brackets open on hover. */
.mb-bracket {
  display: inline-flex;
  align-items: center;
  font-family: var(--mb-font-mono);
  font-size: var(--mb-fs-s);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  color: var(--mb-fg);
  cursor: pointer;
}

.mb-bracket::before,
.mb-bracket::after {
  display: inline-block;
  transition: transform 180ms ease;
  color: var(--mb-text-2);
}

.mb-bracket::before { content: '['; margin-right: 2px; }
.mb-bracket::after  { content: ']'; margin-left: 2px; }

.mb-bracket:hover::before { transform: translateX(-4px); }
.mb-bracket:hover::after  { transform: translateX(4px); }
.mb-bracket:hover { color: var(--mb-green); }

/* -------------------- CARDS ------------------------------- */

.mb-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--mb-surface);
  border: 1px solid var(--mb-border);
  padding: 24px;
  transition: background 150ms ease, border-color 150ms ease;
  clip-path: polygon(
    4px 0, calc(100% - 4px) 0,
    100% 4px, 100% calc(100% - 4px),
    calc(100% - 4px) 100%, 4px 100%,
    0 calc(100% - 4px), 0 4px
  );
}

a.mb-card:hover,
.mb-card--interactive:hover { border-color: var(--mb-fg); }

.mb-card-index {
  font-size: var(--mb-fs-2xs);
  font-weight: var(--mb-fw-bold);
  color: var(--mb-text-3);
  letter-spacing: 0.08em;
  margin-bottom: 12px;
}

.mb-card-title {
  font-family: var(--mb-font-display);
  font-weight: var(--mb-fw-bold);
  font-size: var(--mb-fs-2xl);
  text-transform: uppercase;
  line-height: var(--mb-lh-heading);
  margin-bottom: 12px;
}

.mb-card-body {
  font-size: var(--mb-fs-s);
  color: var(--mb-text-2);
  text-transform: uppercase;
  line-height: 1.6;
}

.mb-section--invert .mb-card-body { color: var(--mb-light-text-2); }

/* `margin-top: auto` is what lands every card's action row on one baseline
   across a grid, however unequal the copy above it. */
.mb-card-foot {
  margin-top: auto;
  padding-top: 16px;
  border-top: 1px solid var(--mb-border-faint);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

/* That auto margin collapses to zero in a card whose copy already fills it,
   so the air above the rule has to come from the element before it. */
.mb-card-body,
.mb-tags { margin-bottom: 20px; }

.mb-card > .mb-card-body:last-child,
.mb-card > .mb-tags:last-child { margin-bottom: 0; }

/* Decorative bottom-right corner triangle on link cards. */
a.mb-card::after,
.mb-card--interactive::after {
  content: '';
  position: absolute;
  right: 0;
  bottom: 0;
  border-style: solid;
  border-width: 0 0 10px 10px;
  border-color: transparent transparent var(--mb-border) transparent;
  transition: border-color 150ms ease;
}

a.mb-card:hover::after,
.mb-card--interactive:hover::after {
  border-bottom-color: var(--mb-green);
}

/* -------------------- 1PX-GUTTER GRID --------------------- */
/* Cells sit on a border-coloured bed; the 1px gaps are the borders. */

.mb-grid {
  display: grid;
  gap: 1px;
  background: var(--mb-border);
  border: 1px solid var(--mb-border);
}

.mb-grid > * {
  background: var(--mb-bg);
  border: none;
  clip-path: none;
}

.mb-grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.mb-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.mb-grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

@media (max-width: 1024px) {
  .mb-grid-3, .mb-grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 700px) {
  .mb-grid-2, .mb-grid-3, .mb-grid-4 { grid-template-columns: minmax(0, 1fr); }
}

/* Free-standing card grid (bordered cards, real gaps).
   auto-fit, not auto-fill: with two or three cards the unused tracks
   collapse so the row fills the container instead of huddling left. */
.mb-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 16px;
}

/* -------------------- STAT TILES -------------------------- */

.mb-stats { margin-top: 40px; }

.mb-stat { padding: 24px; }

.mb-stat-value {
  font-family: var(--mb-font-display);
  font-weight: var(--mb-fw-bold);
  font-size: var(--mb-fs-4xl);
  line-height: 1;
  text-transform: uppercase;
}

a.mb-stat { transition: background 150ms ease, color 150ms ease; }
a.mb-stat:hover { background: var(--mb-fg); color: var(--mb-bg); }
a.mb-stat:hover .mb-stat-label { color: var(--mb-bg); }

.mb-stat-label {
  margin-top: 8px;
  font-size: var(--mb-fs-2xs);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--mb-text-2);
}

/* -------------------- BADGES / TAGS ----------------------- */

.mb-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 16px;
}

.mb-tag {
  display: inline-block;
  padding: 4px 8px;
  font-size: var(--mb-fs-2xs);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--mb-text-2);
  border: 1px solid var(--mb-border);
  background: var(--mb-status-idle-bg);
  clip-path: polygon(
    4px 0, calc(100% - 4px) 0,
    100% 4px, 100% calc(100% - 4px),
    calc(100% - 4px) 100%, 4px 100%,
    0 calc(100% - 4px), 0 4px
  );
}

.mb-section--invert .mb-tag { border-color: var(--mb-light-border); color: var(--mb-light-text-2); }

.mb-status {
  display: inline-block;
  padding: 4px 8px;
  font-size: var(--mb-fs-2xs);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  border: 1px solid;
  clip-path: polygon(
    4px 0, calc(100% - 4px) 0,
    100% 4px, 100% calc(100% - 4px),
    calc(100% - 4px) 100%, 4px 100%,
    0 calc(100% - 4px), 0 4px
  );
}

.mb-status--done {
  color: var(--mb-green);
  background: var(--mb-status-done-bg);
  border-color: var(--mb-status-done-border);
}

.mb-status--live {
  color: var(--mb-pink);
  background: var(--mb-status-live-bg);
  border-color: var(--mb-status-live-border);
}

.mb-status--idle {
  color: var(--mb-text-2);
  background: var(--mb-status-idle-bg);
  border-color: var(--mb-status-idle-border);
}

/* -------------------- TERMINAL ---------------------------- */

/* -------------------- PANEL ------------------------------- */
/* What the terminal frame became. The chrome that made it a terminal —
   the `user@host:~$` bar and the green `$` in front of every line — was
   a costume, and the page underneath is a form. What is left is the
   8px-chamfered surface the system already uses for anything that holds
   controls. */

.mb-panel {
  border: 1px solid var(--mb-border);
  background: var(--mb-surface);
  clip-path: polygon(
    8px 0, calc(100% - 8px) 0,
    100% 8px, 100% calc(100% - 8px),
    calc(100% - 8px) 100%, 8px 100%,
    0 calc(100% - 8px), 0 8px
  );
}

.mb-panel-body { padding: 24px 16px; }

@media (min-width: 769px) {
  .mb-panel-body { padding: 32px; }
}

/* Tweet embeds — no height cap: it cut copy mid-sentence. `overflow: hidden`
   stays to contain a widget wider than its column. */
.mb-embed {
  border-top: 1px solid var(--mb-border-faint);
  margin-top: 16px;
  padding-top: 12px;
  overflow: hidden;
}

.mb-embed .twitter-tweet { margin: 0 !important; }

/* -------------------- ROADMAP ----------------------------- */

.mb-roadmap {
  border-top: 1px solid var(--mb-border-soft);
}

.mb-phase {
  display: grid;
  grid-template-columns: 180px minmax(0, 1fr) auto;
  gap: 24px;
  align-items: start;
  padding: 28px 0;
  border-bottom: 1px solid var(--mb-border-soft);
  transition: background 150ms ease;
}

.mb-phase:hover { background: var(--mb-surface); }

.mb-phase-when {
  font-size: var(--mb-fs-xs);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--mb-text-2);
  padding-top: 4px;
}

.mb-phase-title {
  font-family: var(--mb-font-display);
  font-weight: var(--mb-fw-bold);
  font-size: var(--mb-fs-2xl);
  text-transform: uppercase;
  line-height: var(--mb-lh-heading);
  margin-bottom: 10px;
}

.mb-phase-body {
  font-size: var(--mb-fs-s);
  text-transform: uppercase;
  color: var(--mb-text-2);
  line-height: 1.6;
  max-width: 70ch;
}

@media (max-width: 900px) {
  .mb-phase { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .mb-phase-status { justify-self: start; }
}

/* -------------------- PAGE HERO (subpages) ---------------- */

.mb-page-hero {
  padding: 72px 0 56px;
  border-bottom: 1px solid var(--mb-border-soft);
}

.mb-crumbs {
  font-size: var(--mb-fs-2xs);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--mb-text-3);
  margin-bottom: 20px;
}

.mb-crumbs a:hover { color: var(--mb-green); }

.mb-page-title {
  font-family: var(--mb-font-display);
  font-weight: var(--mb-fw-bold);
  font-size: var(--mb-fs-9xl);
  line-height: 0.9;
  letter-spacing: -0.02em;
  text-transform: uppercase;
}

.mb-page-sub {
  margin-top: 20px;
  max-width: 60ch;
  font-size: var(--mb-fs-s);
  text-transform: uppercase;
  color: var(--mb-text-2);
  line-height: 1.6;
}

/* -------------------- STEPS ------------------------------- */

.mb-steps { counter-reset: mb-step; }

.mb-step {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr);
  gap: 20px;
  padding: 24px 0;
  border-bottom: 1px solid var(--mb-border-soft);
}

.mb-step::before {
  counter-increment: mb-step;
  content: '0' counter(mb-step);
  font-family: var(--mb-font-display);
  font-weight: var(--mb-fw-bold);
  font-size: var(--mb-fs-4xl);
  line-height: 1;
  color: var(--mb-text-3);
}

/* -------------------- CHAT PREVIEW ------------------------ */

.mb-chat { display: flex; flex-direction: column; gap: 10px; }

.mb-msg {
  max-width: 72%;
  padding: 12px 14px;
  font-size: var(--mb-fs-s);
  border: 1px solid var(--mb-border);
  background: var(--mb-surface-2);
  clip-path: polygon(
    4px 0, calc(100% - 4px) 0,
    100% 4px, 100% calc(100% - 4px),
    calc(100% - 4px) 100%, 4px 100%,
    0 calc(100% - 4px), 0 4px
  );
}

.mb-msg-who {
  display: block;
  font-size: var(--mb-fs-2xs);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--mb-text-3);
  margin-bottom: 6px;
}

.mb-msg--out { align-self: flex-end; background: var(--mb-fg); color: var(--mb-bg); }
.mb-msg--out .mb-msg-who { color: rgba(15, 15, 16, 0.55); }

@media (max-width: 768px) { .mb-msg { max-width: 90%; } }

/* -------------------- COMMUNITY --------------------------- */

.mb-links { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 20px; }

/* -------------------- FOOTER ------------------------------ */

.mb-footer {
  border-top: 1px solid var(--mb-border);
  padding: 56px 0 24px;
  background: var(--mb-surface);
}

.mb-footer-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) repeat(2, minmax(0, 1fr));
  gap: 40px;
}

@media (max-width: 1024px) { .mb-footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 32px; } }
@media (max-width: 600px)  { .mb-footer-grid { grid-template-columns: minmax(0, 1fr); gap: 28px; } }

.mb-footer-mark {
  font-family: var(--mb-font-display);
  font-weight: var(--mb-fw-bold);
  font-size: var(--mb-fs-3xl);
  text-transform: uppercase;
  letter-spacing: -0.01em;
  margin-bottom: 14px;
}

.mb-footer-title {
  font-size: var(--mb-fs-2xs);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--mb-text-3);
  margin-bottom: 14px;
}

.mb-footer-links li { margin-bottom: 8px; }

.mb-footer-links a {
  font-size: var(--mb-fs-xs);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  color: var(--mb-text-2);
  transition: color 150ms ease;
}

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

.mb-footer-bottom {
  margin-top: 48px;
  padding-top: 20px;
  border-top: 1px solid var(--mb-border-faint);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  font-size: var(--mb-fs-2xs);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--mb-text-3);
}

/* -------------------- ERROR PAGE -------------------------- */

.mb-error {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
}

.mb-error-code {
  font-family: var(--mb-font-display);
  font-weight: var(--mb-fw-bold);
  font-size: var(--mb-fs-10xl);
  line-height: 0.85;
  letter-spacing: -0.02em;
}

/* -------------------- TO TOP ------------------------------ */

.mb-top {
  position: fixed;
  right: 16px;
  bottom: 16px;
  z-index: 950;
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--mb-surface);
  border: 1px solid var(--mb-border);
  color: var(--mb-fg);
  font-weight: var(--mb-fw-bold);
  opacity: 0;
  visibility: hidden;
  transition: opacity 150ms ease, visibility 150ms, background 150ms ease, color 150ms ease;
  clip-path: polygon(
    4px 0, calc(100% - 4px) 0,
    100% 4px, 100% calc(100% - 4px),
    calc(100% - 4px) 100%, 4px 100%,
    0 calc(100% - 4px), 0 4px
  );
}

.mb-top.is-visible { opacity: 1; visibility: visible; }
.mb-top:hover { background: var(--mb-fg); color: var(--mb-bg); }

/* -------------------- ARCHIVE WARNING --------------------- */
/* The two things that front BUDDY ORIGIN: a dialog at the moment somebody
   turns it on, and a bar that stays for as long as it is on.
 *
 * Both are built by js/mb-site.js and neither has any markup on any page.
 * That direction is the point: a warning is exactly the kind of element
 * that must not be able to survive the script that owns it, and a scrim
 * that ships in the HTML is one blocked request away from being a black
 * sheet nobody can dismiss.
 *
 * The cost of that direction, stated rather than glossed: the inline
 * <head> snippet applies a stored theme on its own, so a visitor whose
 * js/*.js is blocked gets the archive skin with no bar over it. Accepted.
 * `origin` can only be in their storage because they chose it, and
 * choosing it is what raised the dialog in the first place. The failure
 * that direction rules out is the one that cannot be recovered from.
 *
 * Deliberately styled here, in the system's own language, and deliberately
 * NOT restyled by css/mb-origin.css — which is why that file names neither
 * of these class stems. This is the site speaking about the exhibit, not
 * part of the exhibit. Do not "fix" the inconsistency by skinning them.
 * ============================================================ */

/* The bar's own height, measured by the script and written back here so
   the fixed strip can push the page down by exactly what it occupies.
   Zero — and therefore inert in every calc below — in every other theme
   and whenever the script has not run. */
:root { --mb-legacy-h: 0px; }

.mb-legacy-bar {
  position: fixed;
  top: var(--mb-nav-height);
  left: 0;
  right: 0;
  /* Under the navbar's 1000 so the mobile menu still covers it, over
     everything else including the back-to-top button. */
  z-index: 990;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px 16px;
  padding: 6px 12px;
  background: var(--mb-surface-2);
  border-bottom: 1px solid var(--mb-border);
  font-family: var(--mb-font-mono);
  font-size: var(--mb-fs-2xs);
  font-weight: var(--mb-fw-bold);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--mb-fg);
}

/* Wraps rather than truncates. An ellipsised warning is not a warning, and
   at 390px the full sentence does not fit beside the button on one line —
   which is the whole reason the height is measured instead of declared. */
.mb-legacy-bar-text { min-width: 0; }

.mb-legacy-bar-exit {
  flex: none;
  padding: 3px 8px;
  border: 1px solid var(--mb-border);
  background: none;
  color: var(--mb-fg);
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
  transition: background 150ms ease, color 150ms ease;
}

.mb-legacy-bar-exit:hover { background: var(--mb-fg); color: var(--mb-bg); }

.mb-modal {
  position: fixed;
  inset: 0;
  z-index: 1100;
  display: grid;
  place-items: center;
  padding: 16px;
  background: var(--mb-overlay);
}

.mb-modal-box {
  width: 100%;
  max-width: 460px;
  padding: 28px;
  background: var(--mb-surface);
  border: 1px solid var(--mb-border);
  clip-path: polygon(
    8px 0, calc(100% - 8px) 0,
    100% 8px, 100% calc(100% - 8px),
    calc(100% - 8px) 100%, 8px 100%,
    0 calc(100% - 8px), 0 8px
  );
}

.mb-modal-kicker {
  font-family: var(--mb-font-mono);
  font-size: var(--mb-fs-2xs);
  font-weight: var(--mb-fw-bold);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--mb-text-3);
}

.mb-modal-title {
  margin-top: 8px;
  font-family: var(--mb-font-display);
  font-weight: var(--mb-fw-bold);
  font-size: var(--mb-fs-2xl);
  line-height: var(--mb-lh-heading);
  text-transform: uppercase;
}

.mb-modal-body {
  margin-top: 16px;
  font-family: var(--mb-font-mono);
  font-size: var(--mb-fs-s);
  line-height: 1.6;
  text-transform: uppercase;
  color: var(--mb-text-2);
}

.mb-modal-body + .mb-modal-body { margin-top: 12px; }

.mb-modal-actions {
  margin-top: 24px;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

/* The box is the positioning context for it, which is why the box gets
   `position: relative` here rather than at its own declaration: nothing
   else inside a modal is positioned. Held inside the chamfer's own inset
   so the corner cannot clip it. */
.mb-modal-box { position: relative; }

.mb-modal-close {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 28px;
  height: 28px;
  border: 1px solid var(--mb-border);
  background: none;
  color: var(--mb-text-2);
  font-family: var(--mb-font-mono);
  font-size: var(--mb-fs-s);
  line-height: 1;
  cursor: pointer;
  transition: background 150ms ease, color 150ms ease;
}

.mb-modal-close:hover { background: var(--mb-fg); color: var(--mb-bg); }

/* Clear of the close button rather than under it. */
.mb-modal-kicker { padding-right: 32px; }

/* A tick set on the same grid as the wordmark, because the system has
   neither emoji nor an icon font and a success that says so in words
   alone is a paragraph nobody reads. Nine modules by seven, two strokes
   two modules thick; js/mb-quests.js holds the bitmap.

   Static. It is a mark, not an effect — nothing in the pixel layer is
   involved, so nothing here can leave an overlay behind and nothing runs
   on load. */
.mb-check {
  display: inline-grid;
  grid-template-columns: repeat(9, 6px);
  grid-auto-rows: 6px;
  gap: 1px;
  margin-top: 16px;
}

.mb-check i.is-on { background: var(--mb-green); }

@media (max-width: 600px) {
  .mb-modal-box { padding: 20px 16px; }
  .mb-modal-actions { display: grid; grid-template-columns: minmax(0, 1fr); }
  .mb-modal-actions .mb-btn { justify-content: center; }
}

/* -------------------- MOTION ------------------------------ */

/* Everything below hides content, so all of it is gated on `.mb-motion`.
   An inline <head> script sets that class and a 2s failsafe strips it again
   if js/mb-motion.js never boots — a script that fails to load must not be
   able to leave the page blank. */

.mb-motion .mb-reveal { opacity: 0; transform: translateY(20px); }

.mb-motion .mb-reveal.is-in {
  animation: mb-fade-in-up 500ms ease-out forwards;
  animation-delay: calc(var(--mb-i, 0) * 60ms);
}

@keyframes mb-fade-in-up {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* wipe — a left-to-right sweep, the way a terminal prints a line.
   `steps(14)` makes it advance module by module instead of gliding.

   Reserved for a display figure that stands on its own — today only
   `.mb-error-code`. It used to run on every `.mb-section-title` as well,
   where it stacked on top of the `.mb-section-head` reveal the same
   heading was already riding: two entrances on one element, one of them
   stepping and one of them easing, which read as a glitch rather than as
   a mechanic. A title now arrives with its head and nothing else. */
.mb-motion [data-motion='wipe'] { clip-path: inset(0 100% 0 0); }

.mb-motion [data-motion='wipe'].is-in {
  animation: mb-wipe 420ms steps(14, end) forwards;
  animation-delay: calc(var(--mb-i, 0) * 60ms);
}

@keyframes mb-wipe {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}

/* shutter — an inverted band rises into place from its own bottom edge.
   Its trigger is a sentinel injected just above it, never the band itself:
   a clipped element reports an empty intersection rect and could never
   observe its own entrance. See initShutters in js/mb-motion.js. */
.mb-shutter-cue {
  display: block;
  height: 1px;
  margin-bottom: -1px;
  pointer-events: none;
}

.mb-motion [data-motion='shutter'] { clip-path: inset(100% 0 0 0); }

.mb-motion [data-motion='shutter'].is-in {
  animation: mb-shutter 620ms ease-out forwards;
}

@keyframes mb-shutter {
  from { clip-path: inset(100% 0 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}

/* count — numerals roll over. Tabular figures, so the box never reflows. */
[data-motion='count'] { font-variant-numeric: tabular-nums; }

/* progress — page scroll as a 1px rule along the bottom of the ticker.
   `bottom: 0`, not `-1px`: .mb-ticker clips its overflow. */
.mb-progress {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 1px;
  width: 0;
  background: var(--mb-green);
  z-index: 1;
}

/* -------------------- PIXEL LAYER ------------------------- */
/* Overlays created by js/mb-pixel.js. None of these rules hide page
   content: an overlay that is never created simply does not exist,
   which is why this block is NOT scoped to `.mb-motion`. */

.mb-px-host { position: relative; }

.mb-px-cells {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  /* The overlay is a child of whatever it decorates, so a host's child
     rule can fill it. `.mb-grid > *` paints every child in --mb-bg, which
     turned the panel overlay into an opaque sheet over the tiles for the
     full 1.5s. Stated here so no host can hide what this layer decorates. */
  background: transparent;
}

.mb-px-cells i {
  position: absolute;
  background: var(--mb-fg);
  opacity: 0;
}

/* glyph — modules snap into place, then hand off to the real text. */
.mb-px-cells.is-in i {
  animation: mb-px-flash 440ms steps(2, end) forwards;
}

/* panel — modules flash over the box and clear. */
.mb-px-cells--flash.is-in i {
  animation: mb-px-flash 440ms steps(2, end) forwards;
}

@keyframes mb-px-flash {
  0%   { opacity: 0; }
  40%  { opacity: 1; }
  100% { opacity: 0; }
}

/* band — a mask in the host band's ground colour that clears in Bayer
   order. --mb-px-mask is the page ground by default and the light ground
   inside `.mb-section--invert`; painting the page ground everywhere made
   an inverted band flash dark before the dither even started. */
.mb-px-cells--mask i {
  background: var(--mb-px-mask);
  opacity: 1;
}

.mb-px-cells--mask.is-in i {
  animation: mb-px-clear 130ms linear forwards;
}

@keyframes mb-px-clear {
  to { opacity: 0; }
}

/* art / scrub — a 16x16 module grid. The static fallback underneath is
   only hidden once the grid actually exists. */
/* -------------------- SCRUBBED STRIP ---------------------- */
/* A rule made of modules that fills with the scroll. Built and driven by
   js/mb-pixel.js; empty in the markup, so a blocked script leaves a
   28px band of nothing rather than a half-drawn mark. */

.mb-px-strip {
  margin-top: 48px;
  display: flex;
  gap: 2px;
  height: 28px;
  width: 100%;
}

.mb-px-strip i {
  flex: 1 1 0;
  min-width: 0;
  background: var(--mb-border-soft);
  transition: background 200ms linear;
}

.mb-px-strip i.is-on { background: var(--mb-fg); }

.mb-section--invert .mb-px-strip i { background: var(--mb-light-border); }
.mb-section--invert .mb-px-strip i.is-on { background: var(--mb-light-fg); }

@media (prefers-reduced-motion: reduce) {
  .mb-px-strip i { transition: none; }
}

/* -------------------- PIXEL COMPONENTS -------------------- */
/* Static decoration. Nothing here animates and nothing here hides
   content — these are the module's flat forms. */

/* Two modules on opposite corners. The chamfer stays: this is a marker,
   not a replacement frame. */
.mb-px-corners { position: relative; }

.mb-px-corners::before,
.mb-px-corners::after {
  content: '';
  position: absolute;
  width: var(--mb-px-half);
  height: var(--mb-px-half);
  background: var(--mb-fg);
  pointer-events: none;
}

.mb-px-corners::before { top: -1px; left: -1px; }
.mb-px-corners::after { bottom: -1px; right: -1px; }

/* A rule made of modules. Real elements, not a repeating gradient:
   tools/audit.js fails any computed background-image containing the
   substring "gradient", and a hard-stop repeating gradient contains it. */
.mb-px-div {
  display: flex;
  gap: var(--mb-px-half);
  margin: 22px 0;
}

.mb-px-div i {
  flex: 0 0 auto;
  width: var(--mb-px-half);
  height: var(--mb-px-half);
  background: var(--mb-border);
}

.mb-px-div i:nth-child(3n) { background: var(--mb-text-3); }

/* A dot bed. SVG data URI for the same reason. The dot is painted in a
   mid grey at low opacity so it reads on every theme's ground without
   the pattern needing a per-theme copy. */
.mb-px-bed {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Crect width='1' height='1' fill='%23808080' fill-opacity='.55'/%3E%3C/svg%3E");
  background-size: var(--mb-px-bed) var(--mb-px-bed);
}

/* A meter that steps instead of sliding. */
.mb-px-meter { display: flex; gap: 3px; }

.mb-px-meter i {
  flex: 0 0 auto;
  width: 10px;
  height: 18px;
  background: var(--mb-border);
}

.mb-px-meter i.is-on { background: var(--mb-green); }

/* Button hover: the inversion arrives as modules instead of a flat fill.
   The cells sit behind the label on a negative z-index (which is why the
   button isolates), so the text still paints over them and only the
   background changes. They are a permanent, idle layer — deliberately NOT
   `.mb-px-cells`, which the audit expects to find gone once effects have
   settled. For outline buttons only: on a solid button the flip runs the
   other way and there is nothing for the modules to reveal. */
.mb-btn.mb-px-fill {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

.mb-btn.mb-px-fill:hover,
.mb-btn.mb-px-fill:focus-visible { background: transparent; }

.mb-px-fill-cells {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

.mb-px-fill-cells i {
  position: absolute;
  background: var(--mb-fg);
  opacity: 0;
  transition: opacity 90ms steps(1, end);
}

.mb-btn.mb-px-fill:hover .mb-px-fill-cells i,
.mb-btn.mb-px-fill:focus-visible .mb-px-fill-cells i { opacity: 1; }

.mb-btn--pink.mb-px-fill .mb-px-fill-cells i { background: var(--mb-pink); }

@media (prefers-reduced-motion: reduce) {
  /* No stagger, but the fill still has to happen or the label would sit
     on the page ground with nothing behind it. */
  .mb-px-fill-cells i { transition: none; transition-delay: 0s !important; }

  .mb-px-cells { display: none; }
}

/* -------------------- RAIL -------------------------------- */
/* Base state is a vertical stack. The sticky/horizontal treatment only
   applies once JS has measured the track and added `--active`, so a failed
   script, a narrow viewport and reduced motion all fall back to the stack. */

.mb-rail-track {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 16px;
}

.mb-rail--active .mb-rail-sticky {
  position: sticky;
  top: calc(var(--mb-nav-height) + var(--mb-legacy-h) + 28px);
  height: calc(100vh - var(--mb-nav-height) - var(--mb-legacy-h) - 28px);
  display: flex;
  align-items: center;
  overflow: hidden;
}

.mb-rail--active .mb-rail-track {
  display: flex;
  grid-template-columns: none;
  padding: 0 var(--mb-pad-x-wide);
  will-change: transform;
}

.mb-rail--active .mb-rail-panel {
  flex: 0 0 min(460px, 72vw);
  min-height: min(560px, 68vh);
}

.mb-rail-panel {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 320px;
  padding: 32px;
  /* stacked fallback keeps the shorter height; the rail wants presence */
  border: 1px solid var(--mb-border);
  background: var(--mb-surface);
  transition: border-color 150ms ease;
  clip-path: polygon(
    4px 0, calc(100% - 4px) 0,
    100% 4px, 100% calc(100% - 4px),
    calc(100% - 4px) 100%, 4px 100%,
    0 calc(100% - 4px), 0 4px
  );
}

.mb-rail-panel:hover { border-color: var(--mb-fg); }

.mb-rail-index {
  font-size: var(--mb-fs-2xs);
  font-weight: var(--mb-fw-bold);
  letter-spacing: 0.08em;
  color: var(--mb-text-3);
  margin-bottom: 16px;
}

.mb-rail-title {
  font-family: var(--mb-font-display);
  font-weight: var(--mb-fw-bold);
  font-size: var(--mb-fs-6xl);
  line-height: var(--mb-lh-heading);
  text-transform: uppercase;
  letter-spacing: -0.01em;
  margin-bottom: 16px;
}

.mb-rail-body {
  font-size: var(--mb-fs-s);
  text-transform: uppercase;
  color: var(--mb-text-2);
  line-height: 1.6;
  margin-bottom: 20px;
}

.mb-rail-list { margin-bottom: 24px; }

.mb-rail-list li {
  font-size: var(--mb-fs-xs);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--mb-text-2);
  padding: 6px 0;
  border-bottom: 1px solid var(--mb-border-faint);
}

/* -------------------- MILESTONES -------------------------- */

.mb-milestones {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  border-top: 1px solid var(--mb-border-soft);
}

.mb-milestone {
  position: relative;
  padding: 24px 16px 0 0;
}

/* A square, not a dot — there is no border-radius in this system. */
.mb-milestone::before {
  content: '';
  position: absolute;
  top: -4px;
  left: 0;
  width: 7px;
  height: 7px;
  background: var(--mb-border);
}

.mb-milestone--done::before { background: var(--mb-green); }
.mb-milestone--live::before { background: var(--mb-pink); }

.mb-milestone-when {
  font-size: var(--mb-fs-2xs);
  font-weight: var(--mb-fw-bold);
  letter-spacing: 0.08em;
  color: var(--mb-text-3);
  margin-bottom: 8px;
}

.mb-milestone-title {
  font-size: var(--mb-fs-xs);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
  line-height: 1.4;
  margin-bottom: 10px;
}

@media (max-width: 900px) {
  .mb-milestones { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 16px; }
}

@media (max-width: 520px) {
  .mb-milestones { grid-template-columns: minmax(0, 1fr); }
}

/* -------------------- A11Y / MOTION ----------------------- */

.mb-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.mb-skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 1100;
  padding: 10px 14px;
  background: var(--mb-fg);
  color: var(--mb-bg);
  font-size: var(--mb-fs-xs);
  font-weight: var(--mb-fw-bold);
  text-transform: uppercase;
}

.mb-skip:focus { left: 8px; top: 8px; }

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

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  /* Not "faster" — off, in the end state. */
  .mb-motion .mb-reveal { opacity: 1; transform: none; }
  .mb-motion [data-motion='wipe'],
  .mb-motion [data-motion='shutter'] { clip-path: none; }
  .mb-ticker-track { animation: none; }
  .mb-progress { display: none; }
}
