/* ==========================================================================
   griefbot.net front page
   Same brand as the rest of the site (assets/app.css): the eye mark's azure
   (#4fa2ff) on near-black. Display face is Chakra Petch, body is Inter.
   The feature catalogue is rendered by site.js from features.json.
   ========================================================================== */

/* The two typefaces, self-hosted (SIL OFL 1.1). The same three faces as
   /assets/fonts/fonts.css, which the other pages link; written here so the
   front page's first paint waits on one stylesheet, not a chain of two.
   index.html preloads all three files. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/fonts/inter-latin-var.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Chakra Petch";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/chakra-petch-latin-600.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Chakra Petch";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/fonts/chakra-petch-latin-700.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  color-scheme: dark;

  --bg: #050507;
  --bg-2: #0a0b10;
  --surface: rgba(18, 19, 26, 0.72);
  --surface-2: rgba(28, 29, 39, 0.66);
  --solid: #0d0e14;

  --line: rgba(255, 255, 255, 0.075);
  --line-strong: rgba(255, 255, 255, 0.14);

  --text: #ececf3;
  --muted: #9294a3;
  --faint: #62646f;

  /* --accent-2 is the logo's own #4FA2FF and carries text and highlights;
     --accent is the same hue darkened so white reads on a filled button. */
  --accent: #176dcf;
  --accent-rgb: 23, 109, 207;
  --accent-2: #4fa2ff;
  --accent-2-rgb: 79, 162, 255;
  --good: #54d07d;

  --font-display: "Chakra Petch", "Inter", system-ui, sans-serif;
  --font-body: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-mono: ui-monospace, "Cascadia Code", "JetBrains Mono", Consolas, monospace;

  --radius: 18px;
  --radius-sm: 11px;
  /* Same nav height and content width as the internal pages (assets/app.css,
     --nav-h, .topnav-in max-width): the brand mark lands on the same pixel
     x as you move between the front page, the forum and the dashboard. */
  --nav-h: 60px;
  --bar-h: 64px;
  --wrap: 1280px;
  --gutter: clamp(16px, 3vw, 20px);

  --ease: cubic-bezier(0.2, 0.8, 0.2, 1);
}

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

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 12px);
  -webkit-text-size-adjust: 100%;
  /* Reserve the scrollbar gutter whether the bar is drawn or not: a short
     page and a long one share the same vertical axis, with no sideways
     shift when the catalog loads and the page starts to scroll. */
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  min-height: 100vh;
  overflow-x: hidden;
  color: var(--text);
  background: var(--bg);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }
h1, h2, h3 { margin: 0; font-family: var(--font-display); font-weight: 700; letter-spacing: -0.01em; line-height: 1.05; }
p { margin: 0; }

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

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

.skip {
  position: absolute;
  left: 12px;
  top: -60px;
  z-index: 100;
  padding: 10px 14px;
  border-radius: 10px;
  background: var(--accent);
  color: #fff;
  font-weight: 600;
}
.skip:focus { top: 12px; }

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

.ic {
  flex: none;
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ---------- the eye mark (same technique as the dashboard) ---------- */
/* The PNG's alpha carries the whole design, halo included, so masking the
   accent gradient with it paints the mark in the brand colour. The canvas is
   square with transparent margins; negative margins pull it back so the eye,
   not its canvas, lines up with the text beside it. */
.mark {
  --mark: 52px;
  position: relative;
  flex: none;
  width: var(--mark);
  height: var(--mark);
  margin: calc(var(--mark) * -0.208) calc(var(--mark) * -0.107);
  background: linear-gradient(140deg, var(--accent-2), var(--accent));
  -webkit-mask: url("/dashboard/griefbot-eye.png") center / contain no-repeat;
          mask: url("/dashboard/griefbot-eye.png") center / contain no-repeat;
  overflow: hidden;
}
/* The in-game menu's logo shine: one leaning white band every 3.4 s. */
.mark::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg,
    rgba(255, 255, 255, 0) 38.2%,
    rgba(255, 255, 255, 0.32) 50%,
    rgba(255, 255, 255, 0) 61.8%);
  transform: translateX(-84.9%) skewX(-24deg);
  opacity: 0;
  animation: mark-sweep 3.4s linear infinite;
  pointer-events: none;
}
@keyframes mark-sweep {
  0%    { transform: translateX(-84.9%) skewX(-24deg); opacity: 0; }
  6.9%  { opacity: 0; }
  20.8% { opacity: 1; }
  79.2% { opacity: 1; }
  93.1% { opacity: 0; }
  100%  { transform: translateX(84.9%) skewX(-24deg); opacity: 0; }
}

/* ---------- buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  min-height: 46px;
  padding: 0 20px;
  border-radius: 12px;
  border: 1px solid transparent;
  font: 600 15px/1 var(--font-body);
  letter-spacing: 0.005em;
  cursor: pointer;
  white-space: nowrap;
  transition: transform 0.18s var(--ease), background-color 0.18s, border-color 0.18s, box-shadow 0.18s, color 0.18s;
}
.btn .ic { width: 17px; height: 17px; }
.btn:active { transform: translateY(1px) scale(0.99); }

.btn-primary {
  color: #fff;
  background: linear-gradient(180deg, #2a7fe0, var(--accent));
  border-color: rgba(var(--accent-2-rgb), 0.55);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    0 14px 34px -14px rgba(var(--accent-rgb), 0.9);
}
.btn-primary:hover {
  background: linear-gradient(180deg, #3a8cec, #1d77dc);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.26),
    0 18px 40px -14px rgba(var(--accent-2-rgb), 0.85);
}

.btn-ghost {
  color: var(--text);
  background: rgba(255, 255, 255, 0.035);
  border-color: var(--line-strong);
}
.btn-ghost:hover {
  background: rgba(255, 255, 255, 0.07);
  border-color: rgba(255, 255, 255, 0.24);
}

.btn-sm { min-height: 38px; padding: 0 14px; font-size: 14px; border-radius: 10px; }

/* ---------- nav ---------- */
/* Same chrome as the internal .topnav (assets/app.css): solid-blurred bar
   from the first pixel, same height, same max-width, same side padding.
   The front page used to fade its nav in on scroll; moving between the
   home page and the forum then felt like two sites. */
.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  height: var(--nav-h);
  background: rgba(7, 8, 11, 0.82);
  border-bottom: 1px solid var(--line);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
          backdrop-filter: blur(16px) saturate(140%);
}
.nav .wrap {
  height: 100%;
  display: flex;
  align-items: center;
  gap: 18px;
  min-width: 0;
}
.brand {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 12px;
  font: 700 19px/1 var(--font-display);
  letter-spacing: 0.01em;
}
.nav-links {
  display: flex;
  gap: 4px;
  margin-left: 8px;
}
.nav-links a {
  padding: 9px 12px;
  border-radius: 9px;
  color: var(--muted);
  font-size: 14.5px;
  font-weight: 500;
  transition: color 0.15s, background-color 0.15s;
}
.nav-links a:hover { color: var(--text); background: rgba(255, 255, 255, 0.05); }
.nav-links a[aria-current="page"] {
  color: #fff;
  background: rgba(var(--accent-2-rgb), 0.14);
}
/* The first visible item of status / Dashboard / account pushes the group to
   the right; the ones after it sit tight. */
.nav-cta, .nav-dash { margin-left: auto; }
.nav-status:not([hidden]) ~ .nav-dash, .nav-status:not([hidden]) ~ .nav-cta, .nav-dash:not([hidden]) ~ .nav-cta { margin-left: 0; }
.nav-cta { position: relative; }
.nav-signup[hidden], .nav-dash[hidden] { display: none; }
/* Unread notifications, on the account button once signed in. */
.nav-unread {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 5px; border-radius: 999px;
  background: var(--accent-2); color: #fff; font-size: 11px; font-weight: 700; line-height: 1;
}
.nav-unread[hidden] { display: none; }
/* The site-wide notice (Settings › Site), same look as the internal pages. */
.announce {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 9px 20px; text-align: center;
  background: rgba(229, 160, 60, 0.1); border-bottom: 1px solid rgba(229, 160, 60, 0.28);
  color: #e5a03c; font-weight: 500; font-size: 14px;
}
.announce[hidden] { display: none; }
.announce .ic { width: 16px; height: 16px; flex: none; }
/* Build status (the same dot and word as the forum's top bar). */
.nav-status {
  margin-left: auto; display: inline-flex; align-items: center; gap: 7px; flex: none;
  min-height: 30px; padding: 3px 10px; border-radius: 999px;
  border: 1px solid var(--line); background: rgba(255, 255, 255, 0.03);
  color: var(--muted); font-size: 12.5px; font-weight: 600; white-space: nowrap;
}
/* [hidden] must win over the display above, or an unknown build status
   shows an empty grey pill and pushes the phone menu off the right edge. */
.nav-status[hidden] { display: none; }
.nav-status:hover { border-color: var(--line-strong); }
.nav-status .st-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--faint); }
.nav-status.st-ok { color: #54d07d; }
.nav-status.st-ok .st-dot { background: #54d07d; box-shadow: 0 0 0 3px rgba(84, 208, 125, 0.18); }
.nav-status.st-updating { color: #e5a03c; }
.nav-status.st-updating .st-dot { background: #e5a03c; box-shadow: 0 0 0 3px rgba(229, 160, 60, 0.18); }
.nav-status.st-down { color: #ff6b71; }
.nav-status.st-down .st-dot { background: #ff6b71; box-shadow: 0 0 0 3px rgba(255, 107, 113, 0.18); }

/* Phones: the section links in a <details>, shown below 820px. */
.nav-menu { display: none; position: relative; }
.nav-menu > summary {
  list-style: none; cursor: pointer;
  display: grid; place-items: center;
  width: 44px; height: 44px; border-radius: 10px; color: var(--muted);
}
.nav-menu > summary::-webkit-details-marker { display: none; }
.nav-menu > summary .ic { width: 22px; height: 22px; }
.nav-menu[open] > summary, .nav-menu > summary:hover { color: var(--text); background: rgba(255, 255, 255, 0.06); }
.nav-menu-pop {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 60;
  min-width: 220px; padding: 6px;
  display: flex; flex-direction: column; gap: 2px;
  border: 1px solid var(--line-strong); border-radius: 12px;
  background: var(--solid); box-shadow: 0 20px 55px -25px rgba(0, 0, 0, 0.85);
}
.nav-menu-pop a { display: flex; align-items: center; min-height: 44px; padding: 8px 12px; border-radius: 9px; color: var(--muted); font-weight: 500; }
.nav-menu-pop a:hover { color: var(--text); background: rgba(255, 255, 255, 0.06); }
.nav-menu-pop a[aria-current="page"] { color: #fff; background: rgba(var(--accent-2-rgb), 0.14); }

/* ---------- hero ---------- */
.hero {
  position: relative;
  padding: clamp(56px, 9vw, 112px) 0 0;
  margin-top: calc(var(--nav-h) * -1);
  padding-top: calc(var(--nav-h) + clamp(48px, 8vw, 104px));
  isolation: isolate;
}
/* One light source above the headline, and a hairline grid that fades out
   from it: structure, not decoration with its own meaning. */
.hero::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1100px;
  z-index: -1;
  background:
    radial-gradient(720px 420px at 50% -60px, rgba(var(--accent-2-rgb), 0.30), transparent 70%),
    radial-gradient(1100px 620px at 50% 0, rgba(var(--accent-rgb), 0.16), transparent 72%);
}
.hero::after {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 900px;
  z-index: -1;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.045) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.045) 1px, transparent 1px);
  background-size: 64px 64px;
  background-position: center -1px;
  -webkit-mask-image: radial-gradient(640px 460px at 50% 80px, #000 10%, transparent 75%);
          mask-image: radial-gradient(640px 460px at 50% 80px, #000 10%, transparent 75%);
}
.hero-copy {
  max-width: 860px;
  margin: 0 auto;
  text-align: center;
}
.pill {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 7px 14px 7px 8px;
  border-radius: 999px;
  border: 1px solid rgba(var(--accent-2-rgb), 0.28);
  background: rgba(var(--accent-rgb), 0.12);
  color: #cfe3ff;
  font-size: 13.5px;
  font-weight: 500;
}
.pill b {
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font: 700 11.5px/1.3 var(--font-display);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.hero h1 {
  margin-top: 26px;
  font-size: clamp(44px, 8.4vw, 96px);
  line-height: 0.98;
  letter-spacing: -0.025em;
}
.grad {
  background: linear-gradient(100deg, #ffffff 0%, #bcdcff 30%, var(--accent-2) 62%, #2f7fe0 100%);
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
}
.hero .lede {
  max-width: 640px;
  margin: 26px auto 0;
  color: var(--muted);
  font-size: clamp(16.5px, 1.9vw, 19px);
  line-height: 1.6;
}
.hero .lede strong { color: var(--text); font-weight: 600; }
.hero-ctas {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-top: 34px;
}
.hero-ctas .btn { min-height: 52px; padding: 0 24px; font-size: 15.5px; }

/* The real menu's tabs, each with how many features it holds; a click lands
   on that part of the catalogue. Filled by site.js. */
.tabstrip {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin: 44px auto 0;
  max-width: 900px;
  padding: 0;
  list-style: none;
}
.tabstrip a {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  padding: 9px 14px;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.025);
  font-size: 14px;
  font-weight: 500;
  color: var(--muted);
  transition: color 0.15s, border-color 0.15s, background-color 0.15s;
}
.tabstrip a:hover { color: var(--text); border-color: rgba(var(--accent-2-rgb), 0.4); background: rgba(var(--accent-rgb), 0.1); }
.tabstrip .n {
  font: 600 13px/1 var(--font-mono);
  color: var(--accent-2);
  font-variant-numeric: tabular-nums;
}

.hero-shot {
  position: relative;
  margin: clamp(44px, 6vw, 72px) auto 0;
  max-width: 1120px;
  perspective: 1800px;
}
.hero-shot::before {
  content: "";
  position: absolute;
  inset: 8% 6% -6%;
  z-index: -1;
  background: radial-gradient(closest-side, rgba(var(--accent-rgb), 0.55), transparent);
  filter: blur(40px);
  opacity: 0.7;
}
.hero-shot .shot {
  transform: rotateX(10deg) scale(0.97);
  transform-origin: 50% 100%;
  transition: transform 1.1s var(--ease);
}
.hero-shot.flat .shot { transform: none; }

/* ---------- screenshot frames ---------- */
/* A frame holds a gallery of in-game screenshots (.gal, written into
   index.html by tools/site_images.php from shots/gallery.json; see
   site/shots/README.md) over a placeholder that says what belongs there.
   While the frame has a picture the placeholder is not drawn; site.js removes
   a picture that fails to load, so a frame left with none shows it again
   (browsers without :has() hide it once a picture loads, via .has-img). */
.shot {
  position: relative;
  margin: 0;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius);
  border: 1px solid var(--line-strong);
  background: linear-gradient(180deg, rgba(20, 22, 30, 0.95), rgba(9, 10, 14, 0.95));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    0 40px 90px -40px rgba(0, 0, 0, 0.9);
  overflow: hidden;
}
.shot img {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.shot-ph {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 20px;
  text-align: center;
  background-image: repeating-linear-gradient(135deg,
    rgba(255, 255, 255, 0.022) 0 1px, transparent 1px 14px);
}
.shot-ph .ic { width: 26px; height: 26px; color: var(--accent-2); opacity: 0.85; }
.shot-ph-k {
  font: 600 11.5px/1 var(--font-display);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--faint);
}
.shot-ph-l {
  max-width: 34ch;
  font-size: 14px;
  color: var(--muted);
}
.shot.has-img .shot-ph, .shot:has(.gal img) .shot-ph { display: none; }

/* The gallery. These rules lay the slides out before Swiper runs (site.js
   loads site/vendor/swiper/ once a frame nears the screen, the hero's after
   the page has loaded), so nothing moves when it arrives: the first picture
   fills the frame, the rest wait clipped to its right. Selectors are two
   classes deep so Swiper's own stylesheet, loaded later, cannot undo them.
   overflow: clip, not hidden: a hidden box is a scroll container, and
   Chrome lazy-loads pictures within ~1250px of a scroll container's edge,
   so every gallery would fetch its next few pictures unasked (site.js
   fetches a neighbour once the reader shows interest instead). A clip box
   is not scrollable either, so focus can never scroll the slides askew. */
.shot .gal {
  position: absolute;
  inset: 0;
  z-index: 1;
  margin: 0;
  overflow: hidden;
  overflow: clip;
}
.gal .swiper-wrapper {
  display: flex;
  width: 100%;
  height: 100%;
  transition-property: transform;
}
.gal .swiper-slide {
  position: relative;
  flex-shrink: 0;
  width: 100%;
  height: 100%;
}
/* Until Swiper runs only the first slide exists for layout. The hero's 3D
   tilt makes Chrome treat even clipped pictures as on screen, and a slide
   with no box can neither be lazy-loaded early nor read out of order. Swiper
   adds .swiper-initialized before it measures, so it sees every slide. */
.gal:not(.swiper-initialized) .swiper-slide + .swiper-slide { display: none; }
/* Once Swiper runs, a sideways drag is its swipe and an upright one still
   scrolls the page. */
.gal.swiper-horizontal { touch-action: pan-y; }
/* Swiper's screen-reader announcer, in case its CSS has not arrived. */
.gal .swiper-notification { position: absolute; top: 0; left: 0; opacity: 0; pointer-events: none; z-index: -1; }
/* Which option the picture shows, from the manifest's caption. */
.gal-cap {
  position: absolute;
  left: 12px;
  bottom: 12px;
  z-index: 2;
  max-width: calc(100% - 24px);
  margin: 0;
  padding: 6px 10px;
  border-radius: 8px;
  border: 1px solid var(--line-strong);
  background: rgba(7, 8, 11, 0.84);
  color: var(--text);
  font: 500 13px/1.3 var(--font-body);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
}
/* "2 / 7", top right; the slides themselves carry "2 of 7" for screen readers. */
.gal-count {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 3;
  margin: 0;
  padding: 6px 9px;
  border-radius: 8px;
  border: 1px solid var(--line-strong);
  background: rgba(7, 8, 11, 0.84);
  color: var(--muted);
  font: 600 12.5px/1 var(--font-mono);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}
.gal-count-cur { color: var(--text); }
/* Previous / next: shown while the pointer is on the frame or a key has
   focused them. A touch screen swipes instead; the buttons stay in the
   accessibility tree there, and appear if a keyboard reaches them. */
.gal-btn {
  position: absolute;
  top: 50%;
  z-index: 3;
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  margin-top: -21px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  background: rgba(7, 8, 11, 0.8);
  color: var(--text);
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.2s, background-color 0.15s, border-color 0.15s;
}
.gal-prev { left: 12px; }
.gal-next { right: 12px; }
.gal-btn .ic { width: 20px; height: 20px; pointer-events: none; }
.gal-btn:hover { background: var(--accent); border-color: rgba(var(--accent-2-rgb), 0.6); }
.gal-btn:focus-visible { border-radius: 50%; outline-offset: 2px; }
.shot:hover .gal-btn, .gal-btn:focus-visible, .gal:has(.gal-btn:focus-visible) .gal-btn { opacity: 1; }
@media (hover: none) {
  .shot:hover .gal-btn { opacity: 0; }
  .gal-btn { pointer-events: none; }
  .gal-btn:focus-visible, .gal:has(.gal-btn:focus-visible) .gal-btn { opacity: 1; pointer-events: auto; }
}
/* Nothing to press before Swiper runs, or once a frame is down to one picture. */
.gal:not(.swiper-initialized) .gal-btn, .gal:not(.swiper-initialized) .gal-count, .gal .gal-locked { display: none; }
@media (max-width: 680px) {
  .gal-cap { left: 10px; bottom: 10px; max-width: calc(100% - 20px); font-size: 12.5px; padding: 5px 9px; }
  .gal-count { top: 10px; right: 10px; }
}

/* ---------- sections ---------- */
.section { padding: clamp(80px, 11vw, 140px) 0 0; }
.eyebrow {
  display: inline-block;
  font: 600 12.5px/1 var(--font-display);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent-2);
}
.section-head { max-width: 720px; }
.section-head h2 {
  margin-top: 14px;
  font-size: clamp(32px, 5vw, 54px);
  line-height: 1.02;
  letter-spacing: -0.02em;
}
.section-head p {
  margin-top: 16px;
  color: var(--muted);
  font-size: 17px;
  max-width: 600px;
}

/* ---------- highlights (bento) ---------- */
.bento {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
  margin-top: 48px;
}
.card {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: 26px 26px 0;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0) 40%),
    var(--bg-2);
  overflow: hidden;
  transition: border-color 0.25s, transform 0.35s var(--ease);
}
.card:hover { border-color: rgba(var(--accent-2-rgb), 0.32); }
/* A light that follows the cursor across the card (site.js sets --mx/--my). */
.card::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(420px circle at var(--mx, 50%) var(--my, -20%), rgba(var(--accent-2-rgb), 0.10), transparent 60%);
  opacity: 0;
  transition: opacity 0.3s;
  pointer-events: none;
}
.card:hover::before { opacity: 1; }
.card.wide { grid-column: span 2; }
.card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.card-cat {
  font: 600 12px/1 var(--font-display);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent-2);
}
.card-count {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  font-weight: 500;
  color: var(--muted);
  transition: color 0.15s;
}
.card-count:hover { color: var(--text); }
.card-count .ic { width: 15px; height: 15px; }
.card h3 {
  margin-top: 16px;
  font-size: clamp(24px, 2.6vw, 30px);
  line-height: 1.08;
}
.card-body > p {
  margin-top: 12px;
  color: var(--muted);
  font-size: 15.5px;
}
.facts {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin: 18px 0 0;
  padding: 0;
  list-style: none;
}
.facts li {
  padding: 6px 10px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.03);
  font-size: 13px;
  color: #c9cad6;
}
.card .shot {
  margin: 24px -1px 0;
  border-radius: 12px 12px 0 0;
  border-bottom: 0;
  aspect-ratio: 16 / 9;
  margin-top: auto;
  transform: translateY(0);
}
.card .card-body { padding-bottom: 24px; }
/* A wide card puts its screenshot beside the words, bled to the card's
   right and bottom edges. */
@media (min-width: 1081px) {
  .card.wide {
    display: grid;
    grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
    column-gap: 32px;
    padding: 28px 0 0 28px;
  }
  .card.wide .card-body { padding-bottom: 28px; }
  .card.wide .card-top { justify-content: flex-start; gap: 18px; }
  .card.wide .shot {
    margin: 0;
    align-self: end;
    border-radius: 12px 0 0 0;
    border-right: 0;
  }
}

/* ---------- shipped lately ---------- */
.latest { margin-top: 24px; }
/* A heading wherever the timeline crosses into an earlier month. */
.latest-month {
  padding: 30px 4px 12px;
  border-bottom: 1px solid var(--line-strong);
  font: 600 13px/1 var(--font-display);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
.latest-more {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  margin-top: 30px;
  text-align: center;
}
.latest-status {
  color: var(--muted);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
}
.latest-more .btn[hidden] { display: none; }
/* A build entry: its feature lines stacked, the version on the right. */
.latest-items {
  display: grid;
  gap: 14px;
  margin: 0;
  padding: 0;
  list-style: none;
  min-width: 0;
}
a.latest-name {
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 4px;
  transition: color 0.15s, text-decoration-color 0.15s;
}
a.latest-name:hover { color: var(--accent-2); text-decoration-color: rgba(var(--accent-2-rgb), 0.5); }
.latest-text { color: #c9cad6; font-size: 15px; }
.latest-aside { color: var(--muted); font-size: 14px; }
.latest-ver {
  justify-self: end;
  padding: 5px 9px;
  border: 1px solid var(--line);
  border-radius: 8px;
  font: 500 12.5px/1.2 var(--font-mono);
  color: var(--muted);
  white-space: nowrap;
  transition: color 0.15s, border-color 0.15s;
}
a.latest-ver:hover { color: var(--text); border-color: var(--line-strong); }
/* The catalogue entry a changelog link landed on. */
.feat.flash { animation: feat-flash 1.8s var(--ease); }
@keyframes feat-flash {
  0%, 30% { background: rgba(var(--accent-2-rgb), 0.14); box-shadow: -12px 0 0 rgba(var(--accent-2-rgb), 0.14), 12px 0 0 rgba(var(--accent-2-rgb), 0.14); }
  100% { background: transparent; box-shadow: none; }
}
.latest-row {
  display: grid;
  grid-template-columns: 120px minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 8px 28px;
  padding: 20px 4px;
  border-bottom: 1px solid var(--line);
  transition: background-color 0.2s;
}
.latest-row:hover { background: rgba(255, 255, 255, 0.02); }
.latest-date {
  font: 500 13.5px/1.4 var(--font-mono);
  color: var(--accent-2);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.latest-name {
  font: 600 19px/1.25 var(--font-display);
  letter-spacing: 0;
}
.latest-sep { color: var(--accent-2); }
.latest-desc {
  margin-top: 4px;
  color: var(--muted);
  font-size: 15px;
}
.latest-cat {
  justify-self: end;
  font-size: 13px;
  color: var(--muted);
  white-space: nowrap;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 5px 9px;
  transition: color 0.15s, border-color 0.15s;
}
.latest-cat:hover { color: var(--text); border-color: var(--line-strong); }

/* ---------- the catalogue ---------- */
.toolbar {
  position: sticky;
  top: var(--nav-h);
  z-index: 40;
  margin-top: 40px;
  padding: 12px 0;
  background: rgba(5, 5, 7, 0.82);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
          backdrop-filter: blur(16px) saturate(140%);
  border-bottom: 1px solid var(--line);
}
.toolbar-row {
  display: flex;
  align-items: center;
  gap: 14px;
}
.search {
  position: relative;
  flex: 0 1 380px;
  min-width: 0;
}
.search .ic {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--muted);
  pointer-events: none;
}
.search input {
  width: 100%;
  height: 46px;
  padding: 0 44px 0 42px;
  border-radius: 12px;
  border: 1px solid var(--line-strong);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text);
  font: 500 16px/1 var(--font-body);
  transition: border-color 0.15s, background-color 0.15s, box-shadow 0.15s;
}
.search input::placeholder { color: var(--faint); }
.search input:focus {
  outline: none;
  border-color: rgba(var(--accent-2-rgb), 0.7);
  background: rgba(var(--accent-rgb), 0.08);
  box-shadow: 0 0 0 4px rgba(var(--accent-2-rgb), 0.14);
}
.search input::-webkit-search-cancel-button { display: none; }
.kbd {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  min-width: 24px;
  padding: 3px 7px;
  border-radius: 6px;
  border: 1px solid var(--line-strong);
  font: 600 12px/1.2 var(--font-mono);
  color: var(--muted);
  text-align: center;
  pointer-events: none;
}
.search input:focus ~ .kbd, .search input:not(:placeholder-shown) ~ .kbd { display: none; }
.result-count {
  margin-left: auto;
  font-size: 14px;
  color: var(--muted);
  white-space: nowrap;
}
.result-count b { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }

.chips {
  display: flex;
  gap: 6px;
  margin-top: 12px;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 40px), transparent);
          mask-image: linear-gradient(90deg, #000 calc(100% - 40px), transparent);
  padding-right: 40px;
}
.chips::-webkit-scrollbar { display: none; }
.chip {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 36px;
  padding: 0 12px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.025);
  color: var(--muted);
  font: 500 13.5px/1 var(--font-body);
  cursor: pointer;
  transition: color 0.15s, background-color 0.15s, border-color 0.15s;
}
.chip:hover { color: var(--text); border-color: var(--line-strong); }
/* The counts are 12px text, so AA asks 4.5:1: #868896 is 5.6:1 on the
   toolbar, and white on the lit chip's --accent is 5.1:1. */
.chip .n { font: 600 12px/1 var(--font-mono); color: #868896; font-variant-numeric: tabular-nums; }
.chip[aria-pressed="true"] {
  color: #fff;
  background: var(--accent);
  border-color: rgba(var(--accent-2-rgb), 0.6);
}
.chip[aria-pressed="true"] .n { color: #fff; }
.chip[disabled] { opacity: 0.38; cursor: default; }

.catalog { padding-bottom: 20px; }
.cat {
  padding-top: 56px;
  scroll-margin-top: calc(var(--nav-h) + var(--bar-h) + 40px);
  /* The catalogue is most of the page's markup. Each section is laid out
     only once it nears the screen, so the first layout (on the way to the
     first paint) skips ~200 features nobody can see yet; until then it
     holds its last measured height, or this guess. Search, find-in-page,
     scrollIntoView and #links all still reach inside it. */
  content-visibility: auto;
  contain-intrinsic-size: auto 720px;
  /* ...and paints past its edge as far as a .flash highlight reaches. */
  overflow-clip-margin: 16px;
}
.cat[hidden] { display: none; }
.cat-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--line-strong);
}
.cat-path {
  font: 500 12.5px/1 var(--font-mono);
  color: var(--accent-2);
  letter-spacing: 0.02em;
}
.cat-head h3 {
  margin-top: 10px;
  font-size: clamp(24px, 3vw, 32px);
}
.cat-head p {
  margin-top: 8px;
  color: var(--muted);
  font-size: 15px;
  max-width: 620px;
}
.cat-n {
  flex: none;
  font: 600 13px/1 var(--font-mono);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  padding-bottom: 4px;
}
.feats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: 32px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.feat {
  padding: 18px 0 20px;
  border-bottom: 1px solid var(--line);
  min-width: 0;
}
.feat[hidden] { display: none; }
.feat-name {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 9px;
  font-weight: 600;
  font-size: 16px;
  line-height: 1.35;
  color: var(--text);
}
.feat-desc {
  margin-top: 6px;
  color: var(--muted);
  font-size: 14.5px;
  line-height: 1.55;
}
.tag {
  display: inline-flex;
  align-items: center;
  padding: 3px 7px;
  border-radius: 6px;
  font: 600 10.5px/1.25 var(--font-display);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}
.tag-new { color: #fff; background: var(--accent); }
.tag-on { color: var(--muted); border: 1px solid var(--line-strong); }
mark {
  background: rgba(var(--accent-2-rgb), 0.24);
  color: #fff;
  border-radius: 3px;
  padding: 0 1px;
}

.empty {
  padding: 72px 0 40px;
  text-align: center;
}
.empty[hidden] { display: none; }
.empty h3 { font-size: 26px; }
.empty p { margin-top: 10px; color: var(--muted); }
.empty .tries {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-top: 18px;
}

.loading, .load-failed {
  padding: 60px 0;
  text-align: center;
  color: var(--muted);
}

/* ---------- closing band ---------- */
.closing { padding: clamp(90px, 12vw, 150px) 0 0; }
.band {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 28px;
  padding: clamp(32px, 5vw, 56px);
  border-radius: 24px;
  border: 1px solid rgba(var(--accent-2-rgb), 0.25);
  background:
    radial-gradient(600px 300px at 0% 0%, rgba(var(--accent-2-rgb), 0.20), transparent 70%),
    radial-gradient(500px 300px at 100% 100%, rgba(var(--accent-rgb), 0.18), transparent 70%),
    var(--bg-2);
  overflow: hidden;
}
.band h2 { font-size: clamp(30px, 4.4vw, 48px); line-height: 1.04; }
.band p { margin-top: 12px; color: var(--muted); font-size: 16.5px; max-width: 560px; }
.band .btns { display: flex; flex-wrap: wrap; gap: 12px; }

/* ---------- footer ---------- */
.foot {
  margin-top: clamp(70px, 9vw, 110px);
  border-top: 1px solid var(--line);
  padding: 28px 0 40px;
  color: var(--muted);
  font-size: 14px;
}
.foot .wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px 28px;
}
.foot .brand { font-size: 16px; color: var(--text); }
.foot .brand .mark { --mark: 42px; }
/* Wraps: ten links in one row are ~650px, which on a phone widened the page
   and made the browser zoom the whole of it out to fit. */
.foot nav { display: flex; flex-wrap: wrap; gap: 8px 20px; margin-left: auto; }
.foot nav a:hover { color: var(--text); }

/* ---------- scroll reveal ---------- */
/* Below the fold only (the hero has no .reveal: it paints on the first
   frame). site.js's observer marks what it finds below the window .later,
   hidden at once and out of sight, and fades it up (.in) as it scrolls in.
   Nothing on screen is ever hidden, and without the script (or where the
   observer never reports) the page is simply whole. */
.reveal.later {
  opacity: 0;
  transform: translateY(18px);
}
.reveal.in {
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
  transition-delay: var(--d, 0s);
}

/* ---------- responsive ---------- */
@media (max-width: 1080px) {
  .feats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .bento { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .card.wide { grid-column: span 2; }
}

@media (max-width: 820px) {
  .nav-links { display: none; }
  .nav-menu { display: block; }
  .nav .wrap { gap: 12px; }
  .latest-row { grid-template-columns: minmax(0, 1fr) auto; }
  .latest-date { grid-column: 1 / -1; }
  .band { grid-template-columns: 1fr; }
  .result-count { display: none; }
  .search { flex: 1 1 auto; }
}

@media (max-width: 680px) {
  :root { --nav-h: 58px; --bar-h: 108px; }
  /* Phones: the account button keeps its icon and count, the sign-up link
     moves into the menu, and the Dashboard link is icon-only. Their words
     are hidden from sight only, so each link keeps its name ("Sign in",
     "Your account", "Dashboard") for screen readers. */
  .nav-signup { display: none; }
  .nav-cta > span:not(.nav-unread), .nav-dash > span {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
  .nav-cta, .nav-dash { padding: 0 10px; }
  .bento { grid-template-columns: 1fr; }
  .card.wide { grid-column: auto; }
  .card { padding: 22px 20px 0; }
  .feats { grid-template-columns: 1fr; }
  .hero-ctas .btn { flex: 1 1 100%; }
  .tabstrip { gap: 6px; }
  .tabstrip a { padding: 8px 11px; font-size: 13.5px; }
  .hero-shot .shot { transform: none; }
  .latest-cat { display: none; }
  .latest-row { grid-template-columns: 1fr; }
  /* A build entry keeps its version on the date's line. */
  .latest-row:has(.latest-items) { grid-template-columns: minmax(0, 1fr) auto; }
  .latest-items { grid-column: 1 / -1; grid-row: 2; }
  .latest-ver { grid-column: 2; grid-row: 1; }
  .foot nav { margin-left: 0; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { transition-duration: 0.01ms !important; animation: none !important; }
  .reveal.later { opacity: 1; transform: none; }
  .hero-shot .shot { transform: none; }
}
