/* 3ds.tw visual theme: soft patterned backdrop and tactile controls. */

:root {
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
  --mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
  --dot: ui-monospace, "SFMono-Regular", Consolas, monospace;
  --hand: "Comic Sans MS", cursive;

  --bg: #e7edf2;
  --panel: #fdfefe;
  --rim: #c3ced8;
  --rim-lo: #cfd8e1;
  --well: #f2f5f8;
  --line-soft: #dde4ea;
  --ink: #22303c;
  --mute: #4b5a67;
  --dim: #677683;

  --cyan: #34beed;
  --cyan-ink: #0a6f9f;
  --link: #1660a8;

  /* The New 3DS face buttons. */
  --x: #3d6fe0;
  --y: #2fa552;
  --b: #f2b42a;
  --a: #e5484d;
  --x-ink: #2856c2;
  --y-ink: #1d7a3a;
  --b-ink: #7f5900;
  --a-ink: #c22f35;

  /* Console inspired accent palette. */
  --app-bg: #08131f;
  --app-bg-2: #0d2031;
  --app-surface: #102638;
  --app-raised: #173449;
  --app-primary: #34d6e8;
  --app-text: #f4f8fb;
  --app-muted: #9db3c3;
  --app-green: #56e39f;
  --app-yellow: #ffc857;
  --app-red: #ff5d73;

  --pad: 34px;
  --head-h: 58px;
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  /* Overshoots a little and settles, for things that pop. */
  --spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --dur-press: 80ms;
  --dur-ui: 200ms;

  color-scheme: light;
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--head-h) + 16px);
}

body {
  margin: 0;
  overflow-x: clip;
  color: var(--ink);
  font: 500 15px/1.55 var(--font);
  -webkit-font-smoothing: antialiased;
  background: var(--bg);
}

/* The HOME Menu backdrop: soft diagonal stripes and dots that drift slowly
   up and to the left. The layer moves by exactly one tile per loop, so the
   loop is seamless, and it is a transform, so it costs no repaints. */
body::before {
  content: "";
  position: fixed;
  inset: -48px;
  z-index: -2;
  pointer-events: none;
  background-image:
    radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.95) 0 2.3px, transparent 2.9px),
    linear-gradient(135deg, rgba(255, 255, 255, 0.55) 25%, transparent 25% 50%, rgba(255, 255, 255, 0.55) 50% 75%, transparent 75%);
  background-size: 48px 48px, 24px 24px;
  animation: drift 9s linear infinite;
  will-change: transform;
}

body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(ellipse 90% 60% at 50% 0%, rgba(255, 255, 255, 0.8), transparent 70%);
}

@keyframes drift {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(-48px, -48px, 0);
  }
}

a {
  color: var(--link);
  text-underline-offset: 3px;
}

h1,
h2,
h3 {
  margin: 0;
  line-height: 1.15;
  font-weight: 800;
  letter-spacing: -0.01em;
  text-wrap: balance;
}

code,
kbd {
  font-family: var(--mono);
  font-size: 0.88em;
  font-weight: 500;
  background: var(--well);
  border: 1px solid var(--line-soft);
  border-radius: 5px;
  padding: 0.04em 0.35em;
  overflow-wrap: anywhere;
}

kbd {
  background: linear-gradient(#fff, #e9eef2);
  border-color: #b4c0ca;
  border-bottom-width: 2px;
}

/* Elements given their own display (the release sticker, the sound
   toggle) must still disappear when the script hides them. */
[hidden] {
  display: none !important;
}

.sprite {
  display: none;
}

.icon {
  width: 1.1em;
  height: 1.1em;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: -0.18em;
}

symbol .fill {
  fill: currentColor;
  stroke: none;
}

.skip {
  position: absolute;
  left: -999px;
  top: 8px;
  z-index: 20;
  padding: 6px 12px;
  border-radius: 999px;
  background: var(--x-ink);
  color: #fff;
}

.skip:focus {
  left: 8px;
}

:focus-visible {
  outline: 3px solid #4aa8ff;
  outline-offset: 2px;
}

/* -------------------------------------------------------------- Status bar */

.head {
  position: sticky;
  top: 0;
  z-index: 10;
  height: var(--head-h);
  background: linear-gradient(rgba(255, 255, 255, 0.94), rgba(236, 241, 245, 0.9));
  border-bottom: 1px solid #b7c3ce;
  box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.7), 0 6px 18px rgba(30, 50, 70, 0.08);
  -webkit-backdrop-filter: saturate(1.4) blur(10px);
  backdrop-filter: saturate(1.4) blur(10px);
}

.head-in {
  display: flex;
  align-items: center;
  gap: 14px;
  max-width: 1040px;
  height: 100%;
  margin: 0 auto;
  padding: 0 20px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 9px;
  color: var(--ink);
  text-decoration: none;
  font-weight: 800;
  font-size: 1.15rem;
}

.brand img {
  border-radius: 8px;
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.18);
  transition: transform 360ms var(--spring);
}

.brand:hover img {
  transform: rotate(-8deg) scale(1.08);
}

.brand-3ds {
  color: var(--cyan-ink);
}

.brand-tw {
  color: var(--ink);
}

.brand-caption {
  margin-left: 8px;
  color: var(--dim);
  font-size: 14px;
  font-weight: 500;
}

.tabs {
  display: flex;
  gap: 6px;
  margin: 0 auto;
}

.tabs a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 14px;
  border-radius: 999px;
  border: 1px solid #b9c5d0;
  background: linear-gradient(#fff, #eaf0f4);
  box-shadow: inset 0 1px 0 #fff, 0 2px 0 #c6d0d9;
  color: var(--ink);
  font: 700 0.84rem var(--font);
  text-decoration: none;
  white-space: nowrap;
  transition: transform 260ms var(--spring), box-shadow var(--dur-press) var(--ease-out);
}

.tabs a:hover {
  transform: translateY(-1px);
}

.tabs a:active {
  transform: translateY(2px);
  box-shadow: inset 0 1px 0 #fff, 0 0 0 #c6d0d9;
  transition-duration: var(--dur-press);
}

.tabs a.is-active {
  color: #fff;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
  border-color: color-mix(in srgb, var(--tone) 72%, #000);
  background: linear-gradient(color-mix(in srgb, var(--tone) 62%, #fff), var(--tone));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.45), 0 2px 0 color-mix(in srgb, var(--tone) 62%, #000);
}

.tabs a[href="#faq"].is-active {
  color: #4a3300;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.45);
}

/* 管理后台入口 */
.gh-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 13px;
  margin-left: auto;
  border-radius: 999px;
  border: 1px solid #1b1f24;
  background: linear-gradient(#3b434b, #24292f);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 2px 0 #11151a;
  color: #fff;
  font: 700 0.84rem var(--font);
  text-decoration: none;
  white-space: nowrap;
  transition: transform var(--dur-press, 120ms) ease;
}

.gh-link:hover {
  transform: translateY(-1px);
  background: linear-gradient(#454e57, #2c3238);
}

.gh-link:active {
  transform: translateY(2px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 0 0 #11151a;
}

.gh-link:focus-visible {
  outline: 2px solid var(--a);
  outline-offset: 2px;
}

.gh-mark {
  width: 16px;
  height: 16px;
  fill: currentColor;
  flex: none;
}

.tray {
  display: flex;
  align-items: center;
  gap: 10px;
  color: #33414d;
  font: 400 16px/1 var(--dot);
}

.wifi {
  width: 20px;
  height: 15px;
  fill: currentColor;
}

.battery {
  width: 30px;
  height: 16px;
}

.battery .shell {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
}

.battery .nub {
  fill: currentColor;
}

.battery .cell {
  fill: #2fa8e0;
}

.clock .colon {
  display: inline-block;
  margin: 0 1px;
  animation: blink 1s steps(1) infinite;
}

@keyframes blink {
  50% {
    opacity: 0;
  }
}

@media (max-width: 860px) {
  .tray {
    display: none;
  }
  .tabs {
    margin-right: 0;
  }
}

@media (max-width: 600px) {
  .head-in {
    gap: 8px;
    padding: 0 10px;
  }
  .tab-more { display: none; }
  .tabs {
    gap: 4px;
  }
  .tabs a {
    padding: 4px 10px;
    font-size: 0.8rem;
  }
  .gh-link { padding: 5px 10px; }
}

/* -------------------------------------------------------------------- Page */

.page {
  max-width: 1040px;
  margin: 0 auto;
  padding: 0 20px 34px;
}

@media (max-width: 640px) {
  .page {
    padding: 0 10px 22px;
  }
}

/* ----------------------------------------------------------------- Buttons
   Chunky and glossy, sitting on a ledge they press down into. */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 7px 18px 8px;
  border-radius: 999px;
  border: 1px solid #9fb0be;
  background: linear-gradient(#fff, #e6edf2);
  box-shadow: inset 0 1px 0 #fff, 0 3px 0 #b9c6d1, 0 5px 10px rgba(30, 50, 70, 0.1);
  color: var(--ink);
  font: 700 13px var(--font);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: transform 260ms var(--spring), box-shadow var(--dur-press) var(--ease-out);
}

.btn:hover:not(:disabled) {
  transform: translateY(-2px);
}

.btn:active:not(:disabled) {
  transform: translateY(3px);
  box-shadow: inset 0 1px 0 #fff, 0 0 0 #b9c6d1, 0 1px 2px rgba(30, 50, 70, 0.1);
  transition-duration: var(--dur-press);
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* The big glossy cyan button. */
.btn-go {
  padding: 10px 28px 11px;
  font-size: 16px;
  color: #fff;
  border-color: #0a6a94;
  background: linear-gradient(#8ae3fb 0%, #34beed 48%, #0f98cf 52%, #0b86bd 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 4px 0 #086a96, 0 10px 20px rgba(11, 134, 189, 0.3);
  text-shadow: 0 1px 1px rgba(0, 45, 70, 0.45);
}

.btn-go:active:not(:disabled) {
  transform: translateY(4px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 0 0 #086a96, 0 2px 6px rgba(11, 134, 189, 0.3);
}

/* -------------------------------------------------------------------- Hero */

.hero {
  padding: 44px 14px 0;
  text-align: center;
}

/* "New!" with the release, and the download count beside it, stuck on at
   an angle. */
.sticker {
  --tilt: -4deg;
  display: inline-block;
  margin: 0 0 16px;
  padding: 5px 15px 4px;
  border-radius: 999px;
  border: 2px solid #fff;
  font: 400 15px/1.2 var(--dot);
  letter-spacing: 0.05em;
  color: #fff;
  background: linear-gradient(#ff7b7f, var(--a));
  box-shadow: 0 3px 0 #b8343a, 0 8px 16px rgba(200, 50, 60, 0.22);
  transform: rotate(var(--tilt));
}

.sticker-count {
  --tilt: 3deg;
  background: linear-gradient(#5fcff5, #0f98cf);
  box-shadow: 0 3px 0 #086a96, 0 8px 16px rgba(11, 134, 189, 0.22);
}

.sticker:not([hidden]) + .sticker {
  margin-left: 10px;
}

/* Small enough on the narrowest phones that both still share a line. */
@media (max-width: 360px) {
  .sticker {
    padding: 4px 11px 3px;
    font-size: 13px;
  }

  .sticker:not([hidden]) + .sticker {
    margin-left: 6px;
  }
}

.hero h1 {
  max-width: 12.5em;
  margin: 0 auto 18px;
  font-size: clamp(2.2rem, 5.6vw, 3.6rem);
  line-height: 1.08;
  letter-spacing: -0.015em;
}

/* A sticker-style outline, like DS box art. */
.accent {
  color: #17a3da;
  text-shadow:
    3px 0 #fff, -3px 0 #fff, 0 3px #fff, 0 -3px #fff,
    2px 2px #fff, -2px 2px #fff, 2px -2px #fff, -2px -2px #fff,
    0 7px 0 rgba(15, 95, 140, 0.2);
}

.sub {
  max-width: 44em;
  margin: 0 auto 24px;
  font-size: 1.05rem;
  color: var(--mute);
}

@media (max-width: 560px) {
  .sub br {
    display: none;
  }
}

.cta {
  margin-bottom: 26px;
}

/* ------------------------------------------------------------- The device
   A real photo. The two screens are HTML drawn at native 3DS size (400 x 240
   and 320 x 240 px) and projected onto the photographed screens by each
   photo's matrices, which scripts/screen-map computed for the photo at
   --photo-w pixels wide. --k rescales them to the rendered width. */

.device {
  position: relative;
  margin: 0 auto;
  perspective: 1600px;
}

/* The hero: seen from the front at an angle (1356 x 1177). */
.device-angled {
  width: min(100%, 860px);
  padding-bottom: 12px;
}

.device-angled .device-stage {
  aspect-ratio: 1356 / 1177;
  --photo-w: 1356;
  --m-top: matrix3d(1.025661, 0.317498, 0, -0.000237, -0.417270, 1.468451, 0, 0.000048, 0, 0, 1, 0, 611.231497, 133.366913, 0, 1);
  --m-bottom: matrix3d(1.063699, 0.348492, 0, -0.000208, -1.145845, 0.471260, 0, -0.000304, 0, 0, 1, 0, 493.838504, 590.464957, 0, 1);
}

/* The install section: a dead-front New 3DS render (736 x 736), whose
   screens are axis-aligned, so the matrices are plain scales. */
.device-front {
  width: min(100%, 760px);
}

.device-front .device-stage {
  aspect-ratio: 1 / 1;
  --photo-w: 736;
  --m-top: matrix3d(0.8875, 0, 0, 0, 0, 0.883333, 0, 0, 0, 0, 1, 0, 191.5, 114.5, 0, 1);
  --m-bottom: matrix3d(0.882813, 0, 0, 0, 0, 0.882917, 0, 0, 0, 0, 1, 0, 227.5, 412.6, 0, 1);
}

/* --k is (rendered width / --photo-w), set by app.js: CSS cannot divide a
   length by a length to get the plain number scale() needs. Until it is set
   the screens stay hidden, so the photo's own blank screens show instead. */
.device-stage {
  position: relative;
  transform-style: preserve-3d;
  transition: transform 600ms var(--ease-out);
  will-change: transform;
}

.screen {
  display: none;
}

.device-stage.is-mapped .screen {
  display: block;
}

.device-photo {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 26px 30px rgba(20, 40, 60, 0.28));
}

.screen {
  position: absolute;
  left: 0;
  top: 0;
  overflow: hidden;
  color: var(--app-text);
  font-family: var(--font);
  transform-origin: 0 0;
  backface-visibility: hidden;
}

.screen-top {
  width: 400px;
  height: 240px;
  background: linear-gradient(90deg, var(--app-bg), var(--app-bg-2));
  transform: scale(var(--k)) var(--m-top);
}

.screen-bottom {
  width: 320px;
  height: 240px;
  background: var(--app-bg);
  transform: scale(var(--k)) var(--m-bottom);
}

/* The screenshots on the hero's screens take turns, cross-fading. */
.screen .shot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 450ms var(--ease-out);
}

.screen .shot.is-on {
  opacity: 1;
}

/* Glass: a faint highlight across each screen so they sit in the photo. */
.screen::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(105deg, rgba(255, 255, 255, 0.13) 0%, rgba(255, 255, 255, 0) 45%);
}

/* Power-on: the screens light up, the site logo pops in, and the boot
   screen fades away to the app. Only with the script (the screens need it
   anyway) and only when motion is welcome. */
.boot {
  display: none;
}

@media (prefers-reduced-motion: no-preference) {
  .js .boot {
    position: absolute;
    inset: 0;
    z-index: 1;
    display: grid;
    place-content: center;
    justify-items: center;
    gap: 10px;
    background: #000;
    pointer-events: none;
    animation: boot-screen 2.6s var(--ease-out) forwards;
  }

  .boot-top img {
    width: 64px;
    height: 64px;
    border-radius: 14px;
    animation: boot-logo 2.6s var(--ease-out) both;
  }

  .boot-top span {
    font: 800 30px/1 var(--font);
    color: #fff;
    animation: boot-logo 2.6s var(--ease-out) 90ms both;
  }

  .js .boot-bottom {
    grid-auto-flow: column;
    gap: 10px;
  }

  .boot-bottom i {
    width: 11px;
    height: 11px;
    border-radius: 50%;
    background: var(--app-primary);
    animation: boot-dot 0.7s ease-in-out infinite alternate;
  }

  .boot-bottom i:nth-child(2) { animation-delay: 0.15s; }
  .boot-bottom i:nth-child(3) { animation-delay: 0.3s; }
}

@keyframes boot-screen {
  0%, 14% {
    background: #000;
  }
  26% {
    background: var(--app-bg);
  }
  80% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    visibility: hidden;
  }
}

@keyframes boot-logo {
  0%, 24% {
    opacity: 0;
    transform: scale(0.5);
  }
  38% {
    opacity: 1;
    transform: scale(1.12);
  }
  48%, 100% {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes boot-dot {
  from {
    opacity: 0.25;
    transform: translateY(3px);
  }
  to {
    opacity: 1;
    transform: translateY(-3px);
  }
}

/* Doodles drawn over the hero photo, in its own pixel coordinates, like a
   Swapnote scribbled on a picture. They draw themselves in after boot. */
.doodles {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}

/* One set of notes per hero screenshot; only the current one shows. */
.doodle-set {
  transition: opacity 250ms ease;
}

.doodle-set:not(.is-on) {
  opacity: 0;
}

.doodle path {
  fill: none;
  stroke: var(--pen);
  stroke-width: 6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.doodle text {
  font: 700 54px var(--hand);
  fill: var(--pen);
}

.pen-blue { --pen: #2b6de0; }
.pen-red { --pen: #e5484d; }
.pen-green { --pen: #1f9a52; }

.doodle-stars path {
  fill: #ffd34d;
  stroke: #e0a21a;
  stroke-width: 4;
  transform-box: fill-box;
  transform-origin: center;
}

/* They point at the live screens, so they go when those can't run, and
   when the photo is too small to write on. */
html:not(.js) .doodles,
html:not(.js) .tray {
  display: none;
}

@media (max-width: 640px) {
  .doodles {
    display: none;
  }
}

@media (prefers-reduced-motion: no-preference) {
  .reveal-ready .doodle path {
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
  }

  .reveal-ready .doodle text {
    opacity: 0;
  }

  .reveal-ready .device.is-in .doodle text {
    opacity: 1;
    transition: opacity 450ms ease calc(1500ms + var(--d) * 380ms);
  }

  .reveal-ready .device.is-in .doodle path {
    stroke-dashoffset: 0;
    transition: stroke-dashoffset 650ms var(--ease-out) calc(1700ms + var(--d) * 380ms);
  }

  .reveal-ready .device.is-in .doodle path + path {
    transition: stroke-dashoffset 220ms var(--ease-out) calc(2250ms + var(--d) * 380ms);
  }

  /* When the screens change, the old notes fade and reset out of sight
     and the new ones draw themselves in, without the boot's wait. */
  .reveal-ready .device.is-cycling .doodle-set:not(.is-on) .doodle path {
    stroke-dashoffset: 1;
    transition: stroke-dashoffset 0s linear 300ms;
  }

  .reveal-ready .device.is-cycling .doodle-set:not(.is-on) .doodle text {
    opacity: 0;
    transition: opacity 0s linear 300ms;
  }

  .reveal-ready .device.is-cycling .doodle-set.is-on .doodle text {
    transition-delay: calc(250ms + var(--d) * 260ms);
  }

  .reveal-ready .device.is-cycling .doodle-set.is-on .doodle path {
    transition-delay: calc(400ms + var(--d) * 260ms);
  }

  .reveal-ready .device.is-cycling .doodle-set.is-on .doodle path + path {
    transition-delay: calc(950ms + var(--d) * 260ms);
  }

  .reveal-ready .doodle-stars path {
    stroke-dashoffset: 0;
    transform: scale(0);
  }

  .reveal-ready .device.is-in .doodle-stars path {
    transform: scale(1);
    transition: transform 520ms var(--spring) 2.7s;
    animation: twinkle 2.4s ease-in-out 3.3s infinite;
  }

  .reveal-ready .device.is-in .doodle-stars path + path {
    transition-delay: 2.85s;
    animation-delay: 4.1s;
  }
}

@keyframes twinkle {
  0%, 100% {
    transform: scale(1) rotate(0deg);
  }
  50% {
    transform: scale(0.72) rotate(18deg);
  }
}

/* ---------------------------------------------------------------- Sections
   Each section is a rounded panel on the backdrop. */

.panel {
  position: relative;
  margin-top: 46px;
  padding: 46px var(--pad);
  background: var(--panel);
  border: 1px solid var(--rim);
  border-radius: 30px;
  box-shadow: inset 0 2px 0 #fff, 0 5px 0 var(--rim-lo), 0 20px 40px rgba(36, 56, 78, 0.1);
}


.section-head {
  max-width: 40rem;
  margin: 0 auto 30px;
  text-align: center;
}

.section-head h2 {
  font-size: clamp(1.6rem, 3.4vw, 2.25rem);
}

.section-sub {
  margin: 12px auto 0;
  color: var(--mute);
}

@media (max-width: 640px) {
  .panel {
    --pad: 16px;
    margin-top: 40px;
    padding-top: 36px;
    padding-bottom: 36px;
    border-radius: 24px;
  }
}

.fine {
  margin: 14px 0 0;
  font-size: 0.82rem;
  color: var(--dim);
}

.fine-center {
  max-width: 46em;
  margin: 22px auto 0;
  text-align: center;
}

/* --------------------------------------------------------------- The video
   A 16:9 screen in the HOME Menu's dark glass bezel. The play button sits in
   the thumbnail's empty bottom-left corner, clear of what it shows. */

.tv {
  padding: 10px;
  border-radius: 24px;
  background: linear-gradient(#46525e, #262f38);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    inset 0 -2px 0 rgba(0, 0, 0, 0.25),
    0 4px 0 #1b222a,
    0 14px 28px rgba(20, 32, 44, 0.22);
}

.tv-poster,
.tv iframe {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
  border-radius: 14px;
  background: #0b1520;
}

.tv-poster {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  outline-offset: 4px;
}

.tv-poster img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.tv-play {
  position: absolute;
  left: 4%;
  bottom: 6%;
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  padding: 0.5em 1.15em 0.55em 0.9em;
  border-radius: 999px;
  border: 1px solid #0a6a94;
  background: linear-gradient(#8ae3fb 0%, #34beed 48%, #0f98cf 52%, #0b86bd 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 4px 0 #086a96, 0 10px 20px rgba(0, 0, 0, 0.35);
  color: #fff;
  font: 800 clamp(13px, 2.2vw, 17px)/1 var(--font);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 1px rgba(0, 45, 70, 0.45);
}

.tv-play .icon {
  width: 1.25em;
  height: 1.25em;
  filter: drop-shadow(0 1px 1px rgba(0, 45, 70, 0.45));
}

@media (prefers-reduced-motion: no-preference) {
  .tv-poster img {
    transition: transform 700ms var(--ease-out);
  }

  .tv-play {
    transition: transform 260ms var(--spring), box-shadow var(--dur-press) var(--ease-out);
  }

  .tv-poster:hover img {
    transform: scale(1.025);
  }

  .tv-poster:hover .tv-play {
    transform: translateY(-2px);
  }

  .tv-poster:active .tv-play {
    transform: translateY(4px);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 0 0 #086a96, 0 2px 6px rgba(0, 0, 0, 0.35);
    transition-duration: var(--dur-press);
  }
}

@media (max-width: 640px) {
  .tv {
    padding: 6px;
    border-radius: 18px;
  }

  .tv-poster,
  .tv iframe {
    border-radius: 12px;
  }
}

/* ------------------------------------------------- Features: the HOME Menu
   Without the script this is a grid of cards. With it, the icons sit on a
   touch screen and the selected one's demo shows on the top screen above. */

/* The features use a warm palette so screenshots sit on a quiet background. */
.home {
  --app-bg: #f2ebb2;
  --app-bg-2: #f2e9a3;
  --app-text: #615a4b;
  --app-muted: #8c8466;
  --app-primary: #1c93c2;
}

.home-screen {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 14px;
}

.feature {
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: #fff;
  border: 1px solid var(--line-soft);
  border-radius: 16px;
}

.feature-text {
  padding: 10px 14px 14px;
}

.feature-text h3 {
  font-size: 1rem;
}

.feature-text p {
  margin: 4px 0 0;
  font-size: 0.86rem;
  color: var(--mute);
}

.home-bottom {
  display: none;
}

/* The demos are sized in em off one font size, which follows the card or
   screen width (cqi), so the same markup works at any size. */
.feature-demo {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.45em;
  aspect-ratio: 16 / 8;
  padding: 0.95em 1.15em;
  overflow: hidden;
  font-size: 5.2cqi;
  line-height: 1.25;
  color: var(--app-text);
  background: linear-gradient(90deg, var(--app-bg), var(--app-bg-2));
}

/* A feature shown by the real app: its top and bottom screens side by
   side at the same height, as captured at native size. */
.feature-demo.shot-pair,
.js .home .feature-demo.shot-pair {
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 3%;
  aspect-ratio: auto;
  padding: 4% 4% 3%;
  background: linear-gradient(90deg, var(--app-bg), var(--app-bg-2));
}

.js .home .feature-demo.shot-pair {
  padding: 2% 3% 3%;
  background: none;
}

.shot-pair img {
  display: block;
  min-width: 0;
  height: auto;
  border-radius: 4px;
  box-shadow: 0 0 0 1px rgba(97, 90, 75, 0.25), 0 2px 6px rgba(64, 56, 16, 0.22);
}

.shot-pair img:first-child {
  flex: 400 1 0;
}

.shot-pair img:last-child {
  flex: 320 1 0;
}

/* The console look. The two screens sit in dark glass bezels. */
.js .home {
  display: grid;
  justify-items: center;
  gap: 16px;
}

.js .home-top,
.js .home-bottom {
  display: block;
  padding: 10px;
  border-radius: 24px;
  background: linear-gradient(#46525e, #262f38);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    inset 0 -2px 0 rgba(0, 0, 0, 0.25),
    0 4px 0 #1b222a,
    0 14px 28px rgba(20, 32, 44, 0.22);
}

.js .home-top {
  width: min(100%, 640px);
}

.js .home-bottom {
  width: min(100%, 520px);
}

.js .home-screen {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
  overflow: hidden;
  border-radius: 14px;
  background: linear-gradient(90deg, var(--app-bg), var(--app-bg-2));
}

/* Application title bar. */
.home-app-bar {
  display: none;
}

.js .home-app-bar {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 12px 18px 12px;
  font-size: 19px;
  color: var(--app-text);
}

.js .home-app-bar::after {
  content: "";
  position: absolute;
  left: 18px;
  bottom: 4px;
  width: 44px;
  height: 3px;
  background: var(--app-primary);
}

/* They all share one grid cell, so the screen is as tall as the tallest and
   never jumps; only the selected one is shown. */
.js .home-screen {
  grid-template-rows: auto minmax(0, 1fr);
}

.js .home-screen > .feature {
  grid-area: 2 / 1;
  border: 0;
  border-radius: 0;
  background: none;
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px) scale(0.98);
  transition: opacity 200ms var(--ease-out), transform 420ms var(--spring), visibility 0s linear 200ms;
}

.js .home-screen > .feature.is-on {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity 260ms var(--ease-out) 60ms, transform 480ms var(--spring) 60ms, visibility 0s;
}

.js .home .feature-demo {
  flex: 1;
  aspect-ratio: 16 / 6;
  padding: 1em 1.3em 0.7em;
  font-size: clamp(12px, 3.4cqi, 22px);
  background: none;
}

/* The banner label along the bottom of the top screen. */
.js .home .feature-text {
  margin: 0 12px 12px;
  padding: 10px 16px 12px;
  border-radius: 14px;
  background: linear-gradient(#fff, #eef3f6);
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.3);
}

.js .home-grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 6px;
  row-gap: 14px;
  padding: 16px 10px 12px;
  border-radius: 14px;
  background:
    radial-gradient(circle, #cdd8e1 1.3px, transparent 1.8px) 0 0 / 14px 14px,
    linear-gradient(#f6f9fb, #e8eef3);
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.08);
}

.app-icon {
  position: relative;
  z-index: 1;
  display: grid;
  justify-items: center;
  gap: 6px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.app-icon:focus-visible {
  outline: none;
}

.app-glyph {
  position: relative;
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  overflow: hidden;
  border-radius: 15px;
  color: #fff;
  background: linear-gradient(color-mix(in srgb, var(--tint) 62%, #fff), var(--tint) 55%, color-mix(in srgb, var(--tint) 84%, #000));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.6),
    inset 0 -2px 0 rgba(0, 0, 0, 0.15),
    0 3px 0 color-mix(in srgb, var(--tint) 58%, #000),
    0 6px 10px rgba(0, 0, 0, 0.12);
  transition: transform 380ms var(--spring);
}

/* The gloss across the top half of each icon. */
.app-glyph::before {
  content: "";
  position: absolute;
  left: 7%;
  right: 7%;
  top: 4%;
  height: 46%;
  border-radius: 12px 12px 45% 45% / 12px 12px 16px 16px;
  background: linear-gradient(rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0.06));
}

.app-glyph .icon {
  position: relative;
  width: 30px;
  height: 30px;
  stroke-width: 2.3;
  filter: drop-shadow(0 1px 0 rgba(0, 0, 0, 0.25));
}

.app-label {
  font: 400 12px/1.2 var(--dot);
  color: var(--dim);
  white-space: nowrap;
}

.app-icon:hover .app-glyph {
  transform: translateY(-2px);
}

.app-icon:active .app-glyph {
  transform: scale(0.9);
  transition-duration: 90ms;
}

.app-icon[aria-selected="true"] .app-glyph {
  transform: translateY(-3px) scale(1.07);
}

.app-icon[aria-selected="true"] .app-label {
  color: var(--ink);
}

.app-icon:focus-visible .app-label {
  border-radius: 4px;
  outline: 2px solid #4aa8ff;
  outline-offset: 2px;
}

/* The HOME Menu's selection frame: glides to the selected icon and pulses. */
.home-cursor {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 0;
  border: 3px solid #34c8f0;
  border-radius: 20px;
  background: rgba(52, 200, 240, 0.12);
  pointer-events: none;
  opacity: 0;
}

.home-cursor.is-ready {
  opacity: 1;
  transition: transform 460ms var(--spring), width 300ms var(--ease-out), height 300ms var(--ease-out);
  animation: cursor-pulse 1.4s ease-in-out infinite;
}

@keyframes cursor-pulse {
  50% {
    border-color: #9eeaff;
    box-shadow: 0 0 0 5px rgba(52, 200, 240, 0.18);
  }
}

@media (prefers-reduced-motion: no-preference) {
}

@keyframes bar-grow {
  from {
    width: 8%;
  }
}

@media (max-width: 560px) {
  .js .home-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    row-gap: 14px;
  }
  .js .home .feature-demo {
    aspect-ratio: 16 / 8;
    font-size: clamp(14px, 4.8cqi, 22px);
  }
  .js .home-app-bar {
    font-size: 16px;
    padding: 10px 14px;
  }
  .js .home-app-bar::after {
    left: 14px;
  }
  .js .home-top,
  .js .home-bottom {
    padding: 8px;
    border-radius: 20px;
  }
}

/* --------------------------------------------- Loved on Reddit: a plaza
   What people said, as speech bubbles over their avatars in the lime of
   Nintendo's old community plaza. app.js makes each row drift; without it
   the rows scroll sideways. */

.plaza {
  --plaza-bg: #f4faec;
  --fade: 24px;
  overflow: hidden;
  border: 2px solid #b6dc8c;
  border-radius: 20px;
  background: var(--plaza-bg);
  box-shadow: 0 4px 0 #d6ecbd;
}

.plaza-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
  padding: 7px 12px;
  border-bottom: 2px solid #b6dc8c;
  background: linear-gradient(#f7fdee, #dff2c5);
  font: 400 15px/1.2 var(--dot);
  color: #3f6d12;
}

.plaza-bar > .icon {
  font-size: 18px;
}

/* A thread: its subreddit, then its upvotes and comments. */
.plaza-thread {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px 12px;
  padding: 4px 10px 3px;
  border-radius: 999px;
  color: inherit;
  text-decoration: none;
  transition: background var(--dur-ui) var(--ease-out);
}

.plaza-thread:hover {
  background: rgba(255, 255, 255, 0.75);
}

.plaza-stat {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font: 800 13px/1.2 var(--font);
  color: #2f5f07;
}

.plaza-stat .icon {
  vertical-align: 0;
}

@media (max-width: 640px) {
  .plaza-bar > .icon {
    display: none;
  }
}

/* Each row is contained, so a hovered bubble repaints only its own row. The
   padding is as wide as the edge fades, so a post brought into view sits
   clear of them. */
.plaza-row {
  position: relative;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
  contain: content;
  padding: 18px var(--fade) 14px;
}

/* The edges fade into the plaza. Two still overlays cost nothing per frame,
   where a mask on the moving row can make Safari repaint it. */
.plaza.is-drifting .plaza-row::before,
.plaza.is-drifting .plaza-row::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 1;
  width: var(--fade);
  pointer-events: none;
}

.plaza.is-drifting .plaza-row::before {
  left: 0;
  background: linear-gradient(90deg, var(--plaza-bg), rgba(244, 250, 236, 0));
}

.plaza.is-drifting .plaza-row::after {
  right: 0;
  background: linear-gradient(270deg, var(--plaza-bg), rgba(244, 250, 236, 0));
}

.plaza-row + .plaza-row {
  padding-top: 8px;
}

.plaza-row:last-child {
  padding-bottom: 22px;
}

/* Bubbles stand on one line of names, so they rise to their own heights. */
.plaza-track {
  display: flex;
  align-items: flex-end;
  gap: 22px;
  width: max-content;
}

/* The script takes over the scrolling: rows drift, and drag or swipe
   sideways. Vertical swipes still scroll the page. */
.plaza.is-drifting .plaza-row {
  overflow: clip;
  touch-action: pan-y;
  cursor: grab;
  -webkit-user-select: none;
  user-select: none;
}

.plaza.is-drifting .plaza-row.is-dragging {
  cursor: grabbing;
}

.post {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: max-content;
  max-width: 18.5rem;
  color: var(--ink);
  text-decoration: none;
  outline: none;
}

.post-text {
  position: relative;
  margin: 0;
  padding: 12px 16px 13px;
  border: 2px solid #b6dc8c;
  border-radius: 16px;
  background: #fff;
  box-shadow: 0 3px 0 #d6ecbd;
  font-size: 0.95rem;
  line-height: 1.45;
  overflow-wrap: anywhere;
  transition: border-color var(--dur-ui) var(--ease-out);
}

/* The tail, pointing down at the avatar. */
.post-text::after {
  content: "";
  position: absolute;
  left: 20px;
  bottom: -8px;
  width: 12px;
  height: 12px;
  background: #fff;
  border: solid #b6dc8c;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
  transition: border-color var(--dur-ui) var(--ease-out);
}

.post-long {
  max-width: 24rem;
}

.post-short .post-text {
  font-size: 1.2rem;
  font-weight: 800;
  line-height: 1.3;
}

.post:hover .post-text,
.post:hover .post-text::after {
  border-color: #6abf1f;
}

.post:focus-visible .post-text {
  outline: 3px solid #4aa8ff;
  outline-offset: 2px;
}

.post-by {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  padding-left: 8px;
}

.post-pfp {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 11px;
  border: 2px solid #fff;
  background: #e3eaf0;
  box-shadow: 0 0 0 1px #c3ced8, 0 2px 0 1px #cfd8e1;
}

.post-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 400 13px/1.2 var(--dot);
  letter-spacing: 0.02em;
  color: var(--mute);
}

/* The upvotes, as a "Yeah!" pill. */
.post-score {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  flex: none;
  margin-left: auto;
  padding: 2px 9px 2px 7px;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 800;
  color: #2f5f07;
  background: #e2f4cc;
}

.post-score .icon {
  width: 0.95em;
  height: 0.95em;
  vertical-align: 0;
}

.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

@media (max-width: 640px) {
  .plaza {
    --fade: 16px;
  }

  .post {
    max-width: 14.5rem;
  }

  .post-long {
    max-width: 17.5rem;
  }
}

/* ----------------------------------------------- Send a link: PictoChat
   Ruled paper, and messages framed in their sender's colour with the name in
   the corner. */

.chat {
  max-width: 880px;
  margin: 0 auto;
  overflow: hidden;
  border: 2px solid #a9bccb;
  border-radius: 20px;
  background: #fff;
  box-shadow: 0 4px 0 #cfdbe5;
}

.chat-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  border-bottom: 2px solid #a9bccb;
  background: linear-gradient(#f9fbfd, #e2eaf1);
  font: 400 15px/1.2 var(--dot);
  color: #3a4a58;
}

.chat-room {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 7px;
  color: #fff;
  background: var(--y);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 2px 0 #1d7a3a;
}

.chat-log {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 22px 26px;
  align-items: start;
  padding: 26px 22px 28px;
  background: #fff repeating-linear-gradient(transparent 0 27px, #dbe8f3 27px 28px);
}

@media (max-width: 720px) {
  .chat-log {
    grid-template-columns: minmax(0, 1fr);
    padding: 22px 14px 24px;
  }
}

.msg {
  position: relative;
  margin: 0;
  padding: 36px 16px 16px;
  border: 2px solid var(--who);
  border-radius: 12px;
  background: #fff;
  box-shadow: 0 3px 0 color-mix(in srgb, var(--who) 30%, #fff);
}

.msg-you {
  --who: #2fa552;
}

.msg-app {
  --who: #1aa6dc;
  justify-self: end;
  margin-top: 56px;
}

@media (max-width: 720px) {
  .msg-app {
    justify-self: center;
    margin-top: 0;
  }
}

.msg-name {
  position: absolute;
  left: -2px;
  top: -2px;
  padding: 4px 12px 3px;
  border-radius: 12px 0 10px 0;
  font: 400 14px/1.2 var(--dot);
  letter-spacing: 0.04em;
  color: #fff;
  background: var(--who);
}

.send-form label {
  display: block;
  margin-bottom: 6px;
  font-weight: 700;
  font-size: 0.9rem;
}

.send-form input {
  width: 100%;
  padding: 10px 14px;
  border-radius: 10px;
  border: 1px solid #9fb0be;
  background: #fff;
  box-shadow: inset 0 2px 3px rgba(0, 0, 0, 0.1);
  color: var(--ink);
  font: 500 0.92rem var(--mono);
}

.send-form input:focus {
  outline: none;
  border-color: var(--y);
  box-shadow: inset 0 2px 3px rgba(0, 0, 0, 0.1), 0 0 0 3px rgba(47, 165, 82, 0.28);
}

.status {
  min-height: 1.5em;
  margin: 8px 2px 12px;
  font-size: 0.86rem;
  color: var(--dim);
}

.status.ok {
  color: var(--y-ink);
  font-weight: 700;
}

.status.error {
  color: #9a4a00;
  font-weight: 700;
}

.row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.qr-frame {
  flex: none;
  margin: 0;
  padding: 8px;
  line-height: 0;
  border-radius: 8px;
  background: #fff;
}

.qr-frame img,
.qr-frame svg {
  display: block;
  width: 150px;
  height: 150px;
}

.qr-large svg,
.qr-large .qr-placeholder {
  width: 220px;
  height: 220px;
}

.qr-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  line-height: 1.4;
  font: 400 14px var(--dot);
  color: #8a96a1;
  border-radius: 6px;
  background: repeating-linear-gradient(45deg, #f6f8fa 0 8px, #ebf0f4 8px 16px);
}

.qr-placeholder .icon {
  width: 44px;
  height: 44px;
  stroke-width: 1.6;
}

/* ------------------------------------------- FAQ: the Notifications app */

.faq {
  max-width: 720px;
  margin: 0 auto;
  overflow: hidden;
  border: 2px solid #e2cf8f;
  border-radius: 20px;
  background: #fff;
  box-shadow: 0 4px 0 #efe2b5;
}

.faq-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  border-bottom: 2px solid #e2cf8f;
  background: linear-gradient(#fff9e2, #fbebb4);
  font: 400 15px/1.2 var(--dot);
  color: #6b4f00;
}

.faq-count {
  margin-left: auto;
  padding: 2px 10px 1px;
  border-radius: 999px;
  font-size: 13px;
  color: #fff;
  background: var(--y);
}

.faq-count:empty {
  display: none;
}

.faq-count.is-done {
  color: var(--mute);
  background: #dfe5ea;
}

.faq details + details {
  border-top: 1px solid #efe7cc;
}

.faq summary {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 16px;
  cursor: pointer;
  list-style: none;
  font-weight: 700;
  font-size: 0.95rem;
  transition: background var(--dur-ui) var(--ease-out);
}

.faq summary::-webkit-details-marker {
  display: none;
}

.faq summary:hover {
  background: #eef9ff;
}

/* A chevron that turns as the row opens. */
.faq summary::after {
  content: "";
  flex: none;
  width: 9px;
  height: 9px;
  margin: 0 4px 3px 2px;
  border-right: 2.5px solid #9aa8b4;
  border-bottom: 2.5px solid #9aa8b4;
  transform: rotate(-45deg);
  transition: transform 300ms var(--spring);
}

.faq details[open]:not(.is-closing) summary::after {
  transform: translateY(-2px) rotate(45deg);
}

.note-icon {
  position: relative;
  display: grid;
  place-items: center;
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  color: #6b4a00;
  background: linear-gradient(#ffe08a, var(--b));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6), 0 2px 0 #c99415;
  transition: background var(--dur-ui) var(--ease-out);
}

.note-icon .icon {
  position: absolute;
  width: 18px;
  height: 18px;
  transition: opacity 180ms var(--ease-out), transform 320ms var(--spring);
}

.note-icon .i-open,
.faq details[open]:not(.is-closing) .note-icon .i-closed {
  opacity: 0;
  transform: scale(0.6);
}

.faq details[open]:not(.is-closing) .note-icon .i-open {
  opacity: 1;
  transform: none;
}

.faq .is-read .note-icon {
  color: #5d6b77;
  background: linear-gradient(#f6f8fa, #dfe5ea);
  box-shadow: inset 0 1px 0 #fff, 0 2px 0 #b9c4ce;
}

.note-title {
  flex: 1;
  min-width: 0;
}

.note-new {
  display: none;
}

.js .note-new {
  display: inline-block;
  flex: none;
  padding: 2px 8px 1px;
  border-radius: 999px;
  font: 400 12px/1.3 var(--dot);
  letter-spacing: 0.06em;
  color: #fff;
  background: var(--y);
  box-shadow: 0 0 0 3px rgba(47, 165, 82, 0.18);
}

.js .is-read .note-new {
  opacity: 0;
  transform: scale(0);
}

.faq.is-ready .note-new {
  transition: transform 340ms var(--spring), opacity 200ms var(--ease-out);
}

.faq details p {
  margin: 0;
  padding: 0 20px 16px 62px;
  font-size: 0.9rem;
  color: var(--mute);
}

@media (max-width: 560px) {
  .faq details p {
    padding-left: 18px;
  }
}

/* ----------------------------------------------------------------- Install
   Two tabs pick the method; the 3DS shows its QR code on the top screen and
   the details on the touch screen. Everything inside a screen is laid out in
   3DS pixels (400 x 240 and 320 x 240) and scaled with the screen. */

.installer {
  display: grid;
  justify-items: center;
}

/* A segmented control whose glossy highlight springs to the selected tab. */
.picker {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  width: min(100%, 560px);
  margin-bottom: 18px;
  overflow: hidden;
  border: 1px solid #9fb0be;
  border-radius: 16px;
  background: linear-gradient(#fff, #e9eff3);
  box-shadow: inset 0 1px 0 #fff, 0 3px 0 #c3ced8;
}

.picker::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 50%;
  background: linear-gradient(#8ae3fb 0%, #34beed 48%, #0f98cf 52%, #0b86bd 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.55);
  transition: transform 460ms var(--spring);
}

.picker[data-active="3dsx"]::before {
  transform: translateX(100%);
}

.pick {
  position: relative;
  display: grid;
  gap: 1px;
  padding: 9px 14px 10px;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  text-align: center;
  cursor: pointer;
  transition: color var(--dur-ui) var(--ease-out);
}

.pick + .pick {
  border-left: 1px solid #c4cfd8;
}

.pick:hover:not([aria-selected="true"]) {
  background: rgba(255, 255, 255, 0.6);
}

.pick:active {
  transform: translateY(1px);
}

.pick[aria-selected="true"] {
  color: #fff;
  text-shadow: 0 1px 1px rgba(0, 45, 70, 0.45);
}

.pick:focus-visible {
  outline-offset: -4px;
  border-radius: 12px;
}

.pick-title {
  font-weight: 800;
  font-size: 0.98rem;
}

@media (max-width: 480px) {
  .pick {
    padding: 8px 8px 9px;
  }
  .pick-title {
    font-size: 0.9rem;
  }
}

.install-panel {
  display: grid;
  justify-items: center;
  width: 100%;
}

/* Without the script the tabs can't switch and the screens aren't mapped,
   so the <noscript> list stands in for both. */
html:not(.js) .picker,
html:not(.js) .install-panel {
  display: none;
}

/* The notification LED on the hinge, blinking blue as if a SpotPass
   download were waiting. Placed in the render's own pixels. */
.led {
  position: absolute;
  left: calc(637 * 100% / 736);
  top: calc(367 * 100% / 736);
  width: calc(11 * 100% / 736);
  aspect-ratio: 1;
  border-radius: 30%;
  background: #6ccfff;
  box-shadow: 0 0 4px 1px #5ec8ff, 0 0 14px 5px rgba(94, 200, 255, 0.5);
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, -50%);
}

.device-stage.is-mapped .led {
  opacity: 0.9;
  animation: led 2.8s ease-in-out infinite;
}

@keyframes led {
  0%, 100% { opacity: 0.12; }
  8% { opacity: 1; }
  18% { opacity: 0.15; }
  28% { opacity: 1; }
  42% { opacity: 0.12; }
}

.view {
  position: absolute;
  inset: 0;
}

.view[hidden] {
  display: none;
}

/* Top screen: the QR code and the file it installs, or the file and the
   folder it goes in. */
.qr-view {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 0 20px 0 22px;
}

.qr-tile {
  flex: none;
  margin: 0;
  padding: 8px;
  line-height: 0;
  border-radius: 10px;
  background: #fff;
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.35), 0 0 0 1px rgba(255, 255, 255, 0.25);
}

.qr-tile img {
  display: block;
  width: 184px;
  height: 184px;
  image-rendering: pixelated;
}

.qr-side {
  display: grid;
  gap: 6px;
  min-width: 0;
}

.qr-app {
  font-size: 30px;
  line-height: 1;
  font-weight: 800;
  color: var(--app-primary);
}

.qr-size {
  font-size: 16px;
  color: var(--app-muted);
}

.path-view {
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 8px;
}

.path {
  margin-top: 10px;
  padding: 8px 14px;
  border-radius: 6px;
  font: 500 20px var(--mono);
  color: var(--app-text);
  background: var(--app-raised);
  box-shadow: inset 4px 0 0 var(--app-primary);
}

/* Touch screen: the steps and two buttons. */
.info-view {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 13px 15px 14px;
}

.info-steps {
  display: grid;
  gap: 8px;
  margin: auto 0;
  padding: 0;
  list-style: none;
  counter-reset: step;
}

.info-steps li {
  position: relative;
  padding-left: 25px;
  font-size: 14px;
  line-height: 1.3;
  color: var(--app-text);
  counter-increment: step;
}

.info-steps li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0;
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  font-size: 11px;
  font-weight: 700;
  color: var(--app-bg);
  background: var(--app-primary);
}

.info-steps b {
  color: #fff;
}

.info-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

/* Compact buttons with a firm drop shadow. */
.sbtn {
  display: grid;
  place-items: center;
  height: 36px;
  border: 0;
  border-radius: 3px;
  font: 700 14px var(--font);
  letter-spacing: 0.02em;
  color: var(--app-text);
  text-decoration: none;
  background: var(--app-raised);
  box-shadow: 2px 3px 0 rgba(0, 0, 0, 0.4);
  cursor: pointer;
  transition: transform var(--dur-press) var(--ease-out), box-shadow var(--dur-press) var(--ease-out), filter var(--dur-ui) var(--ease-out);
}

.sbtn:hover {
  filter: brightness(1.15);
}

.sbtn:active {
  transform: translate(2px, 3px);
  box-shadow: 0 0 0 rgba(0, 0, 0, 0.4);
}

.sbtn-go {
  color: var(--app-bg);
  background: var(--app-primary);
  box-shadow: inset 5px 0 0 #c5f8fc, 2px 3px 0 rgba(0, 0, 0, 0.4);
}

.sbtn-go:active {
  box-shadow: inset 5px 0 0 #c5f8fc;
}

.sbtn:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

/* Phones: the console is too small to read its touch screen, so app.js
   moves the steps out into .install-steps, and the touch screen points
   down at them. */
.split-note {
  display: none;
}

.is-split .split-note {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 6px;
  color: var(--app-primary);
}

.split-note b {
  font-size: 64px;
  line-height: 1;
}

.split-note span {
  font: 800 34px/1 var(--font);
  color: var(--app-text);
}

@media (prefers-reduced-motion: no-preference) {
  .is-split .split-note b {
    animation: nudge 1.4s ease-in-out infinite;
  }
}

@keyframes nudge {
  50% {
    transform: translateY(10px);
  }
}

.install-steps {
  width: min(100%, 440px);
}

.install-steps:empty {
  display: none;
}

.install-steps .view {
  position: relative;
  inset: auto;
  border-radius: 18px;
  background: var(--app-bg);
  box-shadow: 0 4px 0 #050c14, 0 12px 22px rgba(8, 19, 31, 0.2);
}

.install-steps .info-view {
  gap: 10px;
  padding: 16px;
}

.install-steps .info-actions {
  margin-top: 6px;
}

.nojs-downloads {
  display: grid;
  gap: 14px;
  max-width: 640px;
  margin: 18px auto 0;
  padding: 0;
  list-style: none;
}

.nojs-downloads img {
  display: block;
  margin-top: 6px;
}

/* --------------------------------------------- Footer: the back of the box */

.foot {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 26px;
  align-items: center;
  padding: 26px 30px;
}

.rating {
  display: flex;
  align-items: stretch;
  border: 3px solid #111;
  background: #fff;
  color: #111;
}

.rating-box {
  display: grid;
  grid-template-rows: 1fr auto;
  justify-items: center;
  width: 76px;
  border-right: 3px solid #111;
}

.rating-box b {
  align-self: center;
  padding-top: 4px;
  font: 800 46px/1 var(--font);
}

.rating-box span {
  width: 100%;
  padding: 4px 0;
  text-align: center;
  font: 800 9px/1 var(--font);
  letter-spacing: 0.06em;
  color: #fff;
  background: #111;
}

.rating-notes {
  align-self: center;
  margin: 0;
  padding: 8px 12px;
  list-style: none;
  font: 700 11px/1.55 var(--font);
}

.foot-main {
  display: grid;
  gap: 10px;
}

.foot-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 18px;
  font-size: 0.88rem;
  font-weight: 700;
}

.foot-links a {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.sound-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  border-radius: 999px;
  border: 1px solid #9fb0be;
  background: linear-gradient(#fff, #e9eef2);
  box-shadow: 0 2px 0 #c3ced8;
  color: var(--ink);
  font: 700 0.8rem var(--font);
  cursor: pointer;
}

.sound-toggle[aria-pressed="true"] {
  color: var(--cyan-ink);
  border-color: var(--cyan);
}

.legal {
  margin: 0;
  font-size: 0.76rem;
  line-height: 1.6;
  color: var(--dim);
}

@media (max-width: 720px) {
  .foot {
    grid-template-columns: minmax(0, 1fr);
    justify-items: center;
    padding: 24px 18px;
    text-align: center;
  }
  .foot-links {
    justify-content: center;
  }
}

/* ----------------------------------------------------------- QR dialog */

dialog {
  max-width: min(92vw, 620px);
  padding: 22px;
  border: 1px solid var(--rim);
  border-radius: 26px;
  background: #fff;
  color: var(--ink);
  text-align: center;
  box-shadow: inset 0 2px 0 #fff, 0 6px 0 var(--rim-lo), 0 24px 60px rgba(0, 0, 0, 0.35);
}

dialog::backdrop {
  background: rgba(30, 40, 52, 0.55);
  backdrop-filter: blur(3px);
}

.qr-dialog svg {
  display: block;
  width: min(78vw, 68vh, 500px);
  height: auto;
  aspect-ratio: 1;
  margin: 0 auto;
}

.dialog-url {
  margin: 12px 0 16px;
  font: 500 0.82rem var(--mono);
  color: var(--dim);
  overflow-wrap: anywhere;
}

/* ------------------------------------------------------------- Reveals
   Hiding is gated on .reveal-ready, which app.js sets, so nothing is ever
   hidden when the script does not run. Panels pop up with a little bounce. */

@media (prefers-reduced-motion: no-preference) {
  .reveal-ready .reveal {
    opacity: 0;
    transform: translateY(22px) scale(0.98);
    transition: opacity 450ms var(--ease-out), transform 700ms var(--spring);
  }

  .reveal-ready .reveal.is-in {
    opacity: 1;
    transform: none;
  }

  .sticker:not([hidden]) {
    animation: sticker-in 650ms var(--spring) both;
  }
}

@keyframes sticker-in {
  from {
    transform: rotate(calc(var(--tilt) * 4)) scale(0);
  }
  to {
    transform: rotate(var(--tilt)) scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  body::before,
  .clock .colon,
  .home-cursor.is-ready {
    animation: none;
  }
  .device-stage.is-mapped .led {
    animation: none;
    opacity: 0.9;
  }
  .btn,
  .tabs a,
  .brand img,
  .device-stage,
  .app-glyph,
  .home-cursor.is-ready,
  .js .home-screen > .feature,
  .picker::before,
  .faq summary::after,
  .post-text,
  .note-icon .icon,
  .faq.is-ready .note-new {
    transition: none;
  }
}

/* ---- The buddy card: the other app, peeking in from the right edge like
   a game card left in the slot. Hover or tap slides it out. ---- */
.buddy {
  position: fixed;
  right: 0;
  top: calc(var(--head-h) + 26px);
  z-index: 12;
  transform: translateX(calc(100% - 34px));
  transition: transform 420ms var(--spring);
  filter: drop-shadow(0 10px 18px rgba(36, 56, 78, 0.18));
}
.buddy.is-open,
.buddy:hover,
.buddy:focus-within {
  transform: translateX(0);
}
.buddy-card {
  display: flex;
  align-items: stretch;
  max-width: calc(100vw - 6px);
  text-decoration: none;
  color: var(--ink);
  background: var(--panel);
  /* The rim is a shadow ring rather than a border, so it follows the
     rounded tab as one outline and nothing shows through its corners. */
  border: 0;
  border-radius: 19px 0 0 19px;
  box-shadow: 0 0 0 1px var(--rim), inset 0 2px 0 #fff, 0 4px 0 var(--rim-lo);
  overflow: hidden;
}
.buddy-tab {
  display: grid;
  place-items: center;
  width: 34px;
  padding: 14px 0;
  border: 0;
  /* The card's own rounded left edge shapes the tab, so the tab itself
     is square: flat against the card, curved only where the card is. */
  border-radius: 0;
  background: var(--buddy-tone);
  color: #fff;
  font: 400 13px/1 var(--dot);
  letter-spacing: 0.1em;
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  cursor: pointer;
  box-shadow: inset -3px 0 0 rgba(0, 0, 0, 0.12), inset 2px 0 0 rgba(255, 255, 255, 0.35);
}
.buddy-body {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 18px 12px 12px;
}
.buddy-icon {
  flex: none;
  width: 64px;
  height: 64px;
  border-radius: 14px;
  box-shadow: 0 3px 0 var(--rim-lo), 0 10px 16px rgba(36, 56, 78, 0.16);
  transform-origin: 50% 60%;
  transition: transform 360ms var(--spring);
}
.buddy:hover .buddy-icon,
.buddy:focus-within .buddy-icon,
.buddy.is-open .buddy-icon {
  transform: rotate(-7deg) scale(1.08);
}
.buddy-card:active .buddy-icon {
  transform: rotate(4deg) scale(0.96);
  transition-duration: var(--dur-press);
}
.buddy-text {
  display: grid;
  gap: 3px;
  width: 168px;
  min-width: 0;
}
.buddy-title {
  font: 800 18px/1.1 var(--font);
  color: var(--ink);
}
.buddy-sub {
  font: 500 12.5px/1.35 var(--font);
  color: var(--mute);
}
@media (max-width: 640px) {
  .buddy {
    top: calc(var(--head-h) + 14px);
  }
  .buddy-text {
    width: 150px;
  }
}

/* ---- Support: GitHub Sponsors, Buy Me a Coffee and Ko-fi, pinned to the
   left edge opposite the 3dsFM card. Where the gutter is too narrow for
   them they wait behind a Support tab that slides them out, as the 3dsFM
   card does. ---- */
.funds {
  position: fixed;
  left: 0;
  top: calc(var(--head-h) + 26px);
  z-index: 12;
  transform: translateX(calc(-100% + 34px));
  transition: transform 420ms var(--spring);
  filter: drop-shadow(0 10px 18px rgba(36, 56, 78, 0.18));
}
/* Hover only where there is a pointer, and focus only from the keyboard, so
   a tap on the tab can close it again. */
.funds.is-open,
.funds:has(:focus-visible) {
  transform: translateX(0);
}
@media (hover: hover) {
  .funds:hover {
    transform: translateX(0);
  }
}
.funds-card {
  display: flex;
  align-items: stretch;
  background: var(--panel);
  border-radius: 0 19px 19px 0;
  box-shadow: 0 0 0 1px var(--rim), inset 0 2px 0 #fff, 0 4px 0 var(--rim-lo);
  overflow: hidden;
}
.funds-list {
  display: grid;
  gap: 12px;
  padding: 14px 14px 18px;
}
.funds-tab {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 34px;
  padding: 14px 0;
  border: 0;
  border-radius: 0;
  background: #e2449f;
  color: #fff;
  font: 400 13px/1 var(--dot);
  letter-spacing: 0.1em;
  writing-mode: vertical-rl;
  cursor: pointer;
  box-shadow: inset 3px 0 0 rgba(0, 0, 0, 0.12), inset -2px 0 0 rgba(255, 255, 255, 0.35);
}
.funds-tab .icon {
  width: 15px;
  height: 15px;
}

/* A pill: the site's glossy button in its platform's colours. */
.fund {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 10px 20px 11px 16px;
  border-radius: 999px;
  border: 1px solid var(--ledge);
  background: linear-gradient(var(--hi) 0%, var(--mid) 48%, var(--lo) 52%, color-mix(in srgb, var(--lo) 88%, #000) 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 4px 0 var(--ledge), 0 8px 16px color-mix(in srgb, var(--mid) 30%, transparent);
  color: #fff;
  font: 800 15px/1.1 var(--font);
  text-decoration: none;
  white-space: nowrap;
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.3);
  transition: transform 260ms var(--spring), box-shadow var(--dur-press) var(--ease-out);
}
.fund .icon {
  width: 20px;
  height: 20px;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.25));
}
.fund:hover {
  transform: translateY(-2px);
}
.fund:active {
  transform: translateY(4px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 0 0 var(--ledge), 0 2px 6px color-mix(in srgb, var(--mid) 30%, transparent);
  transition-duration: var(--dur-press);
}
.fund-gh {
  --hi: #f6acd6;
  --mid: #e2449f;
  --lo: #d0338c;
  --ledge: #9c2268;
}
.fund-kofi {
  --hi: #ffb5b3;
  --mid: #ff5e5b;
  --lo: #f0433f;
  --ledge: #b02b28;
}
.fund-bmc {
  --hi: #fff4a6;
  --mid: #ffdd00;
  --lo: #f4cb00;
  --ledge: #b28f00;
  color: #0d0c22;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.45);
}
.fund-bmc .icon {
  filter: none;
}
@media (prefers-reduced-motion: no-preference) {
  .fund-gh:hover .icon,
  .funds-tab:hover .icon {
    animation: heartbeat 1s var(--ease-out) infinite;
  }
}
@keyframes heartbeat {
  0%, 30%, 60%, 100% {
    transform: scale(1);
  }
  15% {
    transform: scale(1.28);
  }
  45% {
    transform: scale(1.16);
  }
}
/* Where the gutter beside the page fits them, the pills stand there, always
   in view, with no tab. */
@media (min-width: 1420px) {
  .funds,
  .funds:hover {
    left: 16px;
    transform: none;
    filter: none;
  }
  .funds-card {
    overflow: visible;
    background: none;
    border-radius: 0;
    box-shadow: none;
  }
  .funds-tab {
    display: none;
  }
  .funds-list {
    gap: 10px;
    padding: 0;
  }
  .fund {
    gap: 8px;
    padding: 9px 16px 10px 12px;
    font-size: 14px;
  }
  .fund .icon {
    width: 18px;
    height: 18px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .funds,
  .fund {
    transition: none;
  }
}
