/* Bananai — shell stylesheet
 *
 * Only the shell lives here. Games style themselves inside their own folder
 * and never inherit from this file: the iframe boundary keeps the two apart,
 * which is exactly what we want.
 */

:root {
  --bg-color: #0f172a;
  --card-bg: #1e293b;
  --accent: #6366f1;
  --accent-hover: #4f46e5;
  --text-main: #f8fafc;
  --text-muted: #94a3b8;
  --radius: 16px;
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  font-family: 'Outfit', sans-serif;
}

body {
  background-color: var(--bg-color);
  color: var(--text-main);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  /* Stops pull-to-refresh and scroll chaining, which otherwise fire while
     the player is dragging inside a game. */
  overscroll-behavior: none;
}

/* Applied to <html> and <body> while a game is open.

   Both are needed. iOS Safari treats documentElement as the scrolling element
   in most configurations, so locking only <body> leaves the page free to
   scroll under the game — which is exactly the bug this class exists to
   prevent.

   Pinned at top:0 deliberately — see lockScroll() for why no negative offset
   is used. */
html.game-open,
body.game-open {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  overscroll-behavior: none;
  /* Suppresses iOS momentum/rubber-band scrolling on the locked document. */
  -webkit-overflow-scrolling: auto;
}

/* --- NAVBAR --- */
header {
  background: rgba(30, 41, 59, 0.8);
  backdrop-filter: blur(12px);
  position: sticky;
  top: 0;
  z-index: 100;
  border-bottom: 1px solid rgba(255,255,255,0.05);
  padding: 1rem 2rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
}

.logo {
  font-size: 1.5rem;
  font-weight: 800;
  color: var(--text-main);
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.logo span {
  color: var(--accent);
}

.search-bar {
  flex: 1;
  max-width: 400px;
  position: relative;
}

.search-bar input {
  width: 100%;
  padding: 0.6rem 1rem;
  border-radius: 99px;
  border: 1px solid rgba(255,255,255,0.1);
  background: var(--bg-color);
  color: var(--text-main);
  outline: none;
  transition: all 0.2s ease;
}

.search-bar input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2);
}

/* --- LANGUAGE SWITCHER --- */
.lang-switch {
  display: flex;
  gap: 0.25rem;
  flex-shrink: 0;
}

.lang-btn {
  background: transparent;
  border: 1px solid rgba(255,255,255,0.1);
  color: var(--text-muted);
  border-radius: 8px;
  padding: 0.35rem 0.6rem;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.5px;
  cursor: pointer;
  transition: all 0.2s ease;
}

.lang-btn:hover {
  color: var(--text-main);
  border-color: var(--accent);
}

.lang-btn.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

/* --- CATEGORY FILTERS --- */
.categories {
  display: flex;
  gap: 0.5rem;
  padding: 1rem 2rem;
  overflow-x: auto;
  white-space: nowrap;
  scrollbar-width: none;
}

.categories::-webkit-scrollbar {
  display: none;
}

.chip {
  padding: 0.5rem 1.2rem;
  background: var(--card-bg);
  border: 1px solid rgba(255,255,255,0.05);
  border-radius: 99px;
  color: var(--text-muted);
  cursor: pointer;
  font-weight: 600;
  font-size: 0.9rem;
  transition: all 0.2s ease;
}

.chip:hover, .chip.active {
  background: var(--accent);
  color: white;
  border-color: var(--accent);
}

/* --- MAIN CONTENT & GRID --- */
main {
  flex: 1;
  padding: 1rem 2rem 3rem 2rem;
  max-width: 1400px;
  margin: 0 auto;
  width: 100%;
}

.games-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 1.25rem;
}

/* Game card */
.game-card {
  background: var(--card-bg);
  border-radius: var(--radius);
  overflow: hidden;
  cursor: pointer;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
  position: relative;
  aspect-ratio: 1 / 1;
  display: flex;
  flex-direction: column;
  border: 1px solid rgba(255,255,255,0.05);
}

.game-card:hover,
.game-card:focus-visible {
  transform: translateY(-5px);
  box-shadow: 0 12px 24px rgba(0,0,0,0.4);
  border-color: var(--accent);
  outline: none;
}

.game-card:focus-visible {
  box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.6);
}

/* Games not yet published */
.game-card.coming-soon {
  cursor: default;
  opacity: 0.55;
}

.game-card.coming-soon:hover {
  transform: none;
  box-shadow: none;
  border-color: rgba(255,255,255,0.05);
}

.badge {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  background: rgba(15, 23, 42, 0.85);
  border: 1px solid var(--accent);
  color: var(--text-main);
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  padding: 0.25rem 0.6rem;
  border-radius: 99px;
  z-index: 2;
}

/* Featured game: larger card */
.game-card.featured {
  grid-column: span 2;
  grid-row: span 2;
}

.game-card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.game-info {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 1rem;
  background: linear-gradient(to top, rgba(15,23,42,0.95), transparent);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

.game-title {
  font-weight: 800;
  font-size: 1rem;
}

.game-category {
  font-size: 0.75rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

/* --- GAME MODAL (sandboxed iframe) --- */
.modal {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.95);
  z-index: 1000;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  /* The backdrop must not bounce or chain its scroll to the page.

     `touch-action` is deliberately absent. It is not an inherited property,
     but the browser intersects it down the ancestor chain, so setting `none`
     here would also apply to the iframe nested inside — which is exactly the
     surface the game needs. Page scrolling is already fully prevented by the
     body lock, so this would buy nothing. */
  overscroll-behavior: contain;
  /* Nothing inside may overflow the backdrop. If the play area is ever
     mis-sized it gets clipped, rather than producing a scrollable area. */
  overflow: hidden;
}

.modal.active {
  display: flex;
}

.modal-content {
  width: 100%;
  max-width: 960px;
  height: 80vh;
  background: #000;
  border-radius: var(--radius);
  overflow: hidden;
  position: relative;
  box-shadow: 0 20px 50px rgba(0,0,0,0.8);
  /* NOTE: no `touch-action` here. It looks like the right place for it, but
     the iframe is a separate document that handles its own touches, and on
     iOS Safari setting touch-action on an iframe's container can stop touch
     events reaching the embedded document entirely. Scroll suppression
     belongs on the backdrop, not on the play area. */
}

.modal-header {
  width: 100%;
  max-width: 960px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
  /* Must keep its natural size so flex gives the rest to the play area. */
  flex-shrink: 0;
}

.modal-actions {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.icon-btn {
  background: var(--card-bg);
  border: none;
  color: white;
  padding: 0.5rem 0.75rem;
  border-radius: 8px;
  cursor: pointer;
  font-weight: 600;
  font-size: 1rem;
  line-height: 1;
  display: none; /* revealed by JS only where fullscreen is supported */
}

.icon-btn:hover {
  background: var(--accent);
}

/* When the modal is the fullscreen element the backdrop covers the screen,
   so its padding should shrink to give the game everything available. */
.modal:fullscreen,
.modal:-webkit-full-screen {
  padding: 0.5rem;
  background: #000;
}

.close-btn {
  background: var(--card-bg);
  border: none;
  color: white;
  padding: 0.5rem 1rem;
  border-radius: 8px;
  cursor: pointer;
  font-weight: 600;
}

.close-btn:hover {
  background: var(--accent);
}

iframe {
  width: 100%;
  height: 100%;
  border: none;
}

/* The play area takes focus so the game receives the keyboard. That is not a
   navigation stop, and the browser's focus ring around the whole frame reads
   as an error rather than as a hint. */
#gameFrame:focus,
#gameFrame:focus-visible {
  outline: none;
}

/* --- CLOSE CONFIRMATION --- */

/* Positioned against .modal-content, which is inside the fullscreen element.
   Anything outside that subtree is not painted while a game is fullscreen.

   Visibility is driven by the `hidden` attribute rather than by a class, so
   that the dialog stays hidden even when this stylesheet does not arrive. With
   a class the default state lives here, and a stale or missing main.css leaves
   an unstyled dialog permanently across the bottom of every game. The attribute
   puts the default in the markup, where the user agent enforces it for free. */
.confirm-overlay {
  display: flex;
  position: absolute;
  inset: 0;
  z-index: 2;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background: rgba(8, 12, 26, 0.85);
}

/* Class + attribute, so this outweighs the rule above. */
.confirm-overlay[hidden] {
  display: none;
}

.confirm-box {
  width: 100%;
  max-width: 22rem;
  padding: 1.5rem;
  border-radius: var(--radius);
  background: var(--card-bg);
  text-align: center;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6);
}

.confirm-box h3 {
  margin: 0 0 0.5rem;
  font-size: 1.15rem;
}

.confirm-box p {
  margin: 0 0 1.25rem;
  color: var(--text-muted);
  font-size: 0.95rem;
}

.confirm-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: center;
}

.confirm-actions button {
  border: none;
  border-radius: 8px;
  padding: 0.6rem 1.1rem;
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 600;
  color: white;
  cursor: pointer;
}

/* Staying is the safe answer, so it carries the accent. */
.confirm-stay {
  background: var(--accent);
}

.confirm-stay:hover {
  background: var(--accent-hover);
}

.confirm-leave {
  background: rgba(255, 255, 255, 0.12);
}

.confirm-leave:hover {
  background: rgba(255, 255, 255, 0.2);
}

/* --- VIRTUAL CONTROLS (touch fallback for keyboard-only games) --- */
.vc-root {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  padding: 0.75rem 1rem calc(0.75rem + env(safe-area-inset-bottom, 0px));
  gap: 1rem;
  z-index: 5;
  /* The overlay must not steal touches meant for the canvas: only the buttons
     themselves are interactive. */
  pointer-events: none;
}

.vc-cluster {
  display: flex;
  gap: 0.6rem;
  align-items: flex-end;
}

.vc-btn {
  pointer-events: auto;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;

  min-width: 62px;
  height: 62px;
  padding: 0 0.9rem;
  border-radius: 50%;
  border: 1px solid rgba(255,255,255,0.22);
  background: rgba(15, 23, 42, 0.55);
  backdrop-filter: blur(6px);
  color: var(--text-main);
  font-size: 1.35rem;
  font-weight: 700;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.08s ease, transform 0.08s ease;
}

.vc-btn-wide {
  border-radius: 31px;
  min-width: 96px;
  font-size: 0.95rem;
  letter-spacing: 0.5px;
}

.vc-btn.vc-active {
  background: rgba(99, 102, 241, 0.75);
  transform: scale(0.94);
}

/* --- INSTALL INVITATION --- */
.install-bar {
  position: fixed;
  left: 0.75rem;
  right: 0.75rem;
  bottom: calc(0.75rem + env(safe-area-inset-bottom, 0px));
  z-index: 900; /* below the game modal, which is 1000 */
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.85rem 1rem;
  background: rgba(30, 41, 59, 0.97);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 14px;
  box-shadow: 0 10px 30px rgba(0,0,0,0.45);
  max-width: 640px;
  margin: 0 auto;
}

.install-bar p {
  flex: 1;
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--text-muted);
}

.install-bar strong { color: var(--text-main); }

.install-actions {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-shrink: 0;
}

.install-cta {
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: 99px;
  padding: 0.5rem 1rem;
  font-weight: 700;
  font-size: 0.85rem;
  cursor: pointer;
}

.install-close {
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-size: 1rem;
  padding: 0.35rem 0.5rem;
  cursor: pointer;
}

/* --- ADVERTISING ---
 *
 * Every rule here answers the same question: how does an ad appear without
 * taking anything from the game or from the page around it.
 *
 * Both containers ship `hidden` in the markup and are revealed only once
 * something has actually rendered inside them. Reserving space for an ad that
 * may never fill is the layout shift the reserved space was meant to prevent,
 * paid for twice.
 */
.ad-slot {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 50px;
  margin-bottom: 1.25rem;
  overflow: hidden;
}

.ad-slot[hidden] {
  display: none;
}

/* The house cross-promotion: an internal placeholder, and the only thing that
   fills a slot when no network does. Styled as a card so it reads as part of
   the catalogue rather than as a failed advert. */
.ad-house {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  width: 100%;
  max-width: 728px;
  padding: 0.6rem 0.9rem;
  background: var(--card-bg);
  border: 1px solid rgba(255,255,255,0.05);
  border-radius: var(--radius);
  color: var(--text-main);
  text-align: left;
  cursor: pointer;
  transition: border-color 0.2s ease;
}

.ad-house:hover,
.ad-house:focus-visible {
  border-color: var(--accent);
  outline: none;
}

.ad-house img {
  width: 56px;
  height: 56px;
  border-radius: 10px;
  object-fit: cover;
  flex-shrink: 0;
}

.ad-house-copy {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.ad-house-kicker {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--text-muted);
}

.ad-house-title {
  font-size: 1rem;
  font-weight: 800;
}

/* Desktop rail beside the play area.
 *
 * Absolutely positioned inside the modal rather than laid out as a flex child,
 * and that is the entire point. The play-area geometry — freezing to the small
 * viewport, re-measuring on rotation, propagating resize into the game — was
 * hard-won and is documented at length in index.html. An ad participating in
 * that layout would be one more thing able to change the box a canvas was
 * sized against. Positioned, it cannot: the game's dimensions are computed as
 * though the rail did not exist, because as far as layout is concerned it
 * does not.
 *
 * The consequence is that it appears only where it costs nothing. The play
 * area is capped at 960px, so a 1600px viewport leaves roughly 300px of unused
 * backdrop on either side; below that width the rail stays hidden rather than
 * overlapping anything.
 */
.ad-rail {
  display: none;
  position: absolute;
  top: 50%;
  right: 2rem;
  transform: translateY(-50%);
  width: 300px;
  z-index: 3;
}

@media (min-width: 1600px) {
  .ad-rail:not([hidden]) {
    display: block;
  }
}

/* In fullscreen the modal is the whole screen and there is no spare backdrop
   left to place it in. */
.modal:fullscreen .ad-rail,
.modal:-webkit-full-screen .ad-rail {
  display: none;
}

/* Interstitial overlay.
 *
 * Above everything including the game modal, because it is shown after the
 * modal has closed and must not be undercut by anything still on screen. */
.ad-interstitial {
  position: fixed;
  inset: 0;
  z-index: 1100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background: rgba(8, 12, 26, 0.92);
}

.ad-interstitial-box {
  width: 100%;
  max-width: 22rem;
  padding: 1.5rem;
  border-radius: var(--radius);
  background: var(--card-bg);
  text-align: center;
}

.ad-interstitial-kicker {
  display: block;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 0.75rem;
}

.ad-interstitial-note {
  color: var(--text-muted);
  font-size: 0.9rem;
  margin-bottom: 1.25rem;
}

/* Fixed width so the countdown ticking down does not resize the button under
   the finger that is waiting to press it. */
.ad-interstitial-skip {
  min-width: 6rem;
  border: none;
  border-radius: 8px;
  padding: 0.6rem 1.1rem;
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 600;
  color: #fff;
  background: var(--accent);
  cursor: pointer;
}

.ad-interstitial-skip[disabled] {
  background: rgba(255, 255, 255, 0.12);
  color: var(--text-muted);
  cursor: default;
}

/* --- RESPONSIVE --- */
@media (max-width: 640px) {
  header {
    padding: 0.75rem 1rem;
  }
  .categories, main {
    padding-left: 1rem;
    padding-right: 1rem;
  }
  .game-card.featured {
    grid-column: span 1;
    grid-row: span 1;
  }

  /* Full-screen play area on phones. Every pixel spent on chrome is a pixel
     the game does not get.

     Safe-area insets keep the play area clear of the notch and the home
     indicator, which otherwise sit on top of it. */
  .modal {
    padding: 0.5rem;
    padding-top: calc(0.5rem + env(safe-area-inset-top, 0px));
    padding-bottom: calc(0.5rem + env(safe-area-inset-bottom, 0px));
    padding-left: calc(0.5rem + env(safe-area-inset-left, 0px));
    padding-right: calc(0.5rem + env(safe-area-inset-right, 0px));
    justify-content: flex-start;
  }

  .modal-header {
    margin-bottom: 0.5rem;
    padding: 0 0.25rem;
  }

  .modal-header h2 {
    font-size: 1.1rem;
  }

  /* Height comes from flex, never from viewport arithmetic.

     The previous `calc(100svh - 4rem)` had to guess the header's footprint,
     and any mismatch pushed the play area past the backdrop. Letting flex
     distribute the remaining space removes the guess entirely: whatever is
     left after the header is the play area, exactly.

     `min-height: 0` is required — flex items default to `min-height: auto`,
     which refuses to shrink below content size and would reintroduce the
     overflow this is meant to eliminate. */
  .modal-content {
    flex: 1;
    min-height: 0;
    height: auto;
    border-radius: 12px;
  }
}

/* Landscape phones: the header would eat most of the height. */
@media (max-height: 500px) and (orientation: landscape) {
  .modal {
    padding: 0.25rem;
  }
  .modal-header {
    margin-bottom: 0.25rem;
  }
  .modal-content {
    flex: 1;
    min-height: 0;
    height: auto;
  }
}

/* --- SITE FOOTER ---------------------------------------------------------
   Sits below the catalogue and is pushed there by the body's flex column.
   It carries the privacy and cookie links, which is not decoration: an
   accessible privacy policy is an AdSense approval requirement and a GDPR
   one, and a link that exists only on the policy pages themselves is not
   accessible. */
.site-footer {
  margin-top: auto;
  padding: 2rem 1.5rem 2.5rem;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
  color: var(--text-muted);
  font-size: 0.85rem;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
}

.site-footer nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.4rem 1.25rem;
}

.site-footer a {
  color: var(--text-muted);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}

.site-footer a:hover,
.site-footer a:focus-visible {
  color: var(--text-main);
  border-bottom-color: var(--accent);
}

/* --- LEGAL PAGES ---------------------------------------------------------
   privacy.html and cookie.html. Long-form reading rather than a catalogue:
   a measured column, generous line height, and no interference with the
   shell's own layout — these pages share only the header and the palette. */
.legal {
  max-width: 46rem;
  margin: 0 auto;
  padding: 2.5rem 1.5rem 4rem;
  line-height: 1.7;
}

.legal h1 {
  font-size: 2rem;
  margin-bottom: 0.35rem;
}

.legal h2 {
  font-size: 1.25rem;
  margin: 2.25rem 0 0.6rem;
  padding-top: 1.25rem;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}

.legal h3 {
  font-size: 1rem;
  margin: 1.5rem 0 0.4rem;
  color: var(--text-main);
}

.legal p,
.legal ul {
  margin-bottom: 1rem;
  color: #cbd5e1;
}

.legal ul {
  padding-left: 1.25rem;
}

.legal li {
  margin-bottom: 0.4rem;
}

.legal a {
  color: #a5b4fc;
}

.legal code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85em;
  background: rgba(255, 255, 255, 0.06);
  padding: 0.1em 0.35em;
  border-radius: 4px;
  word-break: break-word;
}

.legal-updated {
  color: var(--text-muted);
  font-size: 0.85rem;
  margin-bottom: 1.75rem;
}

/* The one-paragraph answer, for the large majority who will read nothing
   else. Stating it plainly at the top is the honest arrangement, not a
   summary that softens what follows. */
.legal-summary {
  background: var(--card-bg);
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--radius) var(--radius) 0;
  padding: 1.1rem 1.25rem;
  margin-bottom: 1.5rem;
}

/* Unmissable on purpose. A privacy policy without an identified controller
   is not valid, so the gap has to be visible on the page rather than sitting
   quietly in a comment where it can ship. */
.legal-todo {
  display: inline-block;
  background: #7f1d1d;
  color: #fee2e2;
  padding: 0.15em 0.5em;
  border-radius: 6px;
}

/* Narrow screens: the table scrolls inside itself rather than widening the
   document, which on a phone would break the whole page's layout. */
.legal-table-wrap {
  overflow-x: auto;
  margin-bottom: 1.25rem;
  border-radius: var(--radius);
  border: 1px solid rgba(255, 255, 255, 0.08);
}

.legal-table {
  width: 100%;
  min-width: 34rem;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.legal-table th,
.legal-table td {
  text-align: left;
  vertical-align: top;
  padding: 0.7rem 0.9rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}

.legal-table th {
  background: rgba(255, 255, 255, 0.03);
  font-weight: 600;
}

.legal-table tr:last-child td {
  border-bottom: none;
}

.legal-table td {
  color: #cbd5e1;
}

.legal-nav {
  margin-top: 2.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}

@media (max-width: 600px) {
  .legal {
    padding: 1.75rem 1.1rem 3rem;
  }
  .legal h1 {
    font-size: 1.6rem;
  }
}

/* --- DEVELOPER DOCUMENTATION ---------------------------------------------
   /developers/, generated from docs/developers/ by scripts/build-docs.mjs.

   These pages are rendered markdown, so the selectors have to cover whatever
   the source happens to use rather than a known set of hand-written classes:
   headings to h4, tables, fenced code, blockquotes. Anything unstyled here
   falls back to browser defaults on a dark background, which is unreadable —
   so the list is deliberately exhaustive rather than minimal. */
.doc {
  max-width: 52rem;
  margin: 0 auto;
  padding: 2.5rem 1.5rem 4rem;
  line-height: 1.7;
}

.doc-home {
  margin-left: auto;
  color: var(--text-muted);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.9rem;
}

.doc-home:hover {
  color: var(--text-main);
}

.doc h1 {
  font-size: 2.1rem;
  margin-bottom: 1rem;
}

.doc h2 {
  font-size: 1.35rem;
  margin: 2.5rem 0 0.75rem;
  padding-top: 1.25rem;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}

.doc h3 {
  font-size: 1.05rem;
  margin: 1.75rem 0 0.5rem;
}

.doc h4 {
  font-size: 0.95rem;
  margin: 1.25rem 0 0.4rem;
  color: var(--text-muted);
}

.doc p,
.doc ul,
.doc ol {
  margin-bottom: 1rem;
  color: #cbd5e1;
}

.doc ul,
.doc ol {
  padding-left: 1.35rem;
}

.doc li {
  margin-bottom: 0.4rem;
}

.doc a {
  color: #a5b4fc;
}

.doc strong {
  color: var(--text-main);
}

.doc hr {
  border: none;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  margin: 2.5rem 0;
}

.doc blockquote {
  margin: 0 0 1rem;
  padding: 0.75rem 1rem;
  border-left: 3px solid var(--accent);
  background: rgba(99, 102, 241, 0.08);
  border-radius: 0 8px 8px 0;
  color: #cbd5e1;
}

.doc blockquote p:last-child {
  margin-bottom: 0;
}

/* Inline code and code blocks are the substance of these pages, not decoration:
   most of what a developer copies out of here is inside one. */
/* font-family is not optional here. The reset at the top of this file sets
   Outfit on `*`, which beats the browser's monospace default for code and pre —
   so a code sample in a developer document renders in the body font, with
   proportional spacing, unless it is set back explicitly. */
.doc code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  background: rgba(148, 163, 184, 0.16);
  padding: 0.12em 0.4em;
  border-radius: 6px;
  font-size: 0.9em;
  word-break: break-word;
}

.doc pre {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  background: #0b1220;
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 12px;
  padding: 1rem 1.1rem;
  margin-bottom: 1.25rem;
  overflow-x: auto;          /* long lines scroll in the block, not the page */
  line-height: 1.55;
}

.doc pre code {
  background: none;
  padding: 0;
  border-radius: 0;
  font-size: 0.86rem;
  word-break: normal;
}

/* Source documents separate their sections with `---`, and h2 already draws a
   rule above itself. Together they render as two lines with a gap between,
   which reads as a mistake because it is one. */
.doc hr + h2 {
  border-top: none;
  padding-top: 0;
  margin-top: 0;
}

/* Tables are wrapped by nothing — marked emits a bare <table> — so the table
   itself carries the horizontal scroll. Without this a wide comparison table
   makes the whole page scroll sideways on a phone. */
.doc table {
  display: block;
  overflow-x: auto;
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 1.25rem;
  font-size: 0.92rem;
}

.doc th,
.doc td {
  border: 1px solid rgba(255, 255, 255, 0.09);
  padding: 0.55rem 0.7rem;
  text-align: left;
  vertical-align: top;
  color: #cbd5e1;
}

.doc th {
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-main);
  white-space: nowrap;
}

.doc-raw {
  font-size: 0.88rem;
  color: var(--text-muted);
  background: var(--card-bg);
  border-radius: 10px;
  padding: 0.7rem 0.9rem;
  margin-bottom: 2rem;
}

.doc-lede {
  font-size: 1.05rem;
}

/* --- Index --- */

.doc-list {
  list-style: none;
  padding: 0;
  margin: 1.5rem 0 2rem;
}

.doc-list li {
  display: flex;
  align-items: stretch;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

.doc-card {
  flex: 1;
  display: block;
  background: var(--card-bg);
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: 1rem 1.15rem;
  text-decoration: none;
  transition: border-color 0.15s, transform 0.15s;
}

.doc-card:hover {
  border-color: var(--accent);
  transform: translateY(-2px);
}

.doc-card-title {
  display: block;
  font-weight: 700;
  color: var(--text-main);
  margin-bottom: 0.15rem;
}

.doc-card-blurb {
  display: block;
  font-size: 0.9rem;
  color: var(--text-muted);
}

/* The markdown link sits beside the card rather than inside it: nesting an
   anchor in an anchor is invalid and the inner one is unreachable by keyboard. */
.doc-card-raw {
  display: flex;
  align-items: center;
  padding: 0 0.9rem;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: var(--radius);
  font-size: 0.8rem;
  color: var(--text-muted);
  text-decoration: none;
  white-space: nowrap;
}

.doc-card-raw:hover {
  border-color: var(--accent);
  color: var(--text-main);
}

.doc-machine {
  list-style: none;
  padding: 0;
}

@media (max-width: 600px) {
  .doc {
    padding: 1.75rem 1.1rem 3rem;
  }
  .doc h1 {
    font-size: 1.65rem;
  }
  .doc-list li {
    flex-direction: column;
  }
  .doc-card-raw {
    justify-content: center;
    padding: 0.5rem;
  }
}
