/* ==========================================================================
   SupDucks — home page
   Palette + shapes pulled from the SupDucks Public Figma board:
   the boardwalk scene, the wordmark, and the 16 background traits.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */
:root {
  /* Core brand colors, sampled off the boardwalk illustration */
  --sup-sky:        #6FE0F5;
  --sup-sky-deep:   #3FC6E8;
  /* Sampled from the top row of boardwalk-strip.webp. The hero sky must end
     on exactly this value or a seam shows where the CSS sky meets the art. */
  --sky-art:        #78E0FF;
  --sup-sea:        #2E5FD9;
  --sup-sand:       #EBD7A6;
  --sup-road:       #8A8A8A;

  --sup-pink:       #FF5FA2;
  --sup-bubblegum:  #FF95D6;
  --sup-yellow:     #FFD23F;
  --sup-orange:     #FF8A3D;
  --sup-toxic:      #B6F24B;
  /* Only for destructive states -- currently the connected wallet pill on
     hover/press. Deliberately not one of the boardwalk colours: it has to
     read as "this undoes something" against a palette that is otherwise all
     candy brights. */
  --sup-danger:     #E5484D;
  --sup-green:      #5BD46B;
  --sup-purple:     #7B4BE8;
  --sup-deep:       #241748;

  --sup-ink:        #0D0D0D;
  --sup-cream:      #FFF7E9;
  --sup-paper:      #FFFFFF;

  /* The wordmark is a heavy black outline — that outline is the whole system */
  --line:           4px;
  --line-thick:     6px;
  --radius:         18px;
  /* The rack's section headers and the bar above them ask for this; it was
     never declared, so all three call sites fell back to square corners in a
     design where the cards are 16px and the buttons are pills. */
  --radius-md:      12px;
  --radius-lg:      28px;
  --radius-pill:    999px;

  /* Hard offset shadow, no blur — sticker look */
  --pop:            6px 6px 0 var(--sup-ink);
  --pop-sm:         4px 4px 0 var(--sup-ink);
  --pop-lg:         10px 10px 0 var(--sup-ink);

  --font-display:   "Sigmar", "Impact", system-ui, sans-serif;
  --font-body:      "Nunito", ui-rounded, "Segoe UI", system-ui, sans-serif;

  --shell:          1180px;
  --shell-narrow:   780px;
  --header-h:       76px;

  --ease-out:       cubic-bezier(0.16, 1, 0.3, 1);
}

/* --------------------------------------------------------------------------
   2. Reset / base
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

/* The `hidden` attribute has to win. Components below set their own display
   -- .btn is inline-flex, .col-tabs is flex -- and a class beats the user
   agent's [hidden] rule, so scripts that hide by attribute were leaving the
   element on screen (the rescan icon sat in the picker while signed out).
   One rule, applied once, instead of a :not([hidden]) guard on every
   component that ever gets toggled. */
[hidden] { display: none !important; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 16px);
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: clamp(1rem, 0.96rem + 0.2vw, 1.075rem);
  line-height: 1.65;
  color: var(--sup-ink);
  background: var(--sup-cream);
  overflow-x: hidden;
}

img { max-width: 100%; display: block; height: auto; }
ul, ol { margin: 0; padding: 0; list-style: none; }
p { margin: 0 0 1.1em; }
a { color: inherit; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: 1.02;
  letter-spacing: 0.01em;
  margin: 0 0 0.4em;
}

.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;
}

.skip-link {
  position: absolute; left: 12px; top: -100px; z-index: 200;
  background: var(--sup-yellow); color: var(--sup-ink);
  padding: 12px 20px; border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius); font-weight: 900; text-decoration: none;
  transition: top 0.2s var(--ease-out);
}
.skip-link:focus { top: 12px; }

:focus-visible {
  outline: 4px solid var(--sup-purple);
  outline-offset: 3px;
  border-radius: 4px;
}

/* --------------------------------------------------------------------------
   3. Layout primitives
   -------------------------------------------------------------------------- */
.shell {
  width: min(100% - 2.5rem, var(--shell));
  margin-inline: auto;
}
.shell--narrow { width: min(100% - 2.5rem, var(--shell-narrow)); }

.section { padding: clamp(4.5rem, 9vw, 7.5rem) 0; position: relative; }
.section--tight { padding-top: 0; }
.section--cream { background: var(--sup-cream); }
.section--sky {
  background: linear-gradient(180deg, var(--sup-sky) 0%, var(--sup-sky-deep) 100%);
  border-top: var(--line-thick) solid var(--sup-ink);
  border-bottom: var(--line-thick) solid var(--sup-ink);
}
.section--dark {
  background:
    radial-gradient(120% 90% at 80% 0%, rgba(123, 75, 232, 0.55), transparent 60%),
    radial-gradient(90% 80% at 10% 100%, rgba(255, 95, 162, 0.35), transparent 60%),
    var(--sup-deep);
  border-bottom: var(--line-thick) solid var(--sup-ink);
}

.section__head {
  max-width: 760px;
  margin: 0 auto clamp(2.5rem, 5vw, 4rem);
  text-align: center;
}
.section__head--left { margin-inline: 0; text-align: left; }
.section__sub { font-size: 1.1rem; opacity: 0.92; margin-bottom: 0; }
/* Dark sections set a deep background but no text colour, so the sub was
   inheriting the body ink and disappearing into it. */
.section--dark .section__sub { color: var(--sup-paper); }

.h2 { font-size: clamp(2.1rem, 1.3rem + 3.4vw, 3.9rem); }
.h2--light { color: var(--sup-paper); text-shadow: 4px 4px 0 var(--sup-ink); }

.eyebrow {
  font-family: var(--font-display);
  font-size: 0.86rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sup-paper);
  -webkit-text-stroke: 0.6px var(--sup-ink);
  margin-bottom: 0.9rem;
}
.eyebrow--dark { color: var(--sup-purple); -webkit-text-stroke: 0; }

/* On the pale hero sky the white-on-stroke treatment washes out, so the
   hero eyebrow runs dark with a light halo instead. */
.hero .eyebrow {
  color: var(--sup-deep);
  -webkit-text-stroke: 0;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.65);
}

/* --------------------------------------------------------------------------
   4. Buttons
   -------------------------------------------------------------------------- */
.btn {
  --btn-bg: var(--sup-yellow);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  font-family: var(--font-display);
  font-size: 1.05rem;
  letter-spacing: 0.03em;
  text-decoration: none;
  padding: 0.85rem 1.75rem;
  background: var(--btn-bg);
  color: var(--sup-ink);
  border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius-pill);
  box-shadow: var(--pop);
  cursor: pointer;
  transition: transform 0.14s var(--ease-out), box-shadow 0.14s var(--ease-out);
}
.btn:hover { transform: translate(3px, 3px); box-shadow: var(--pop-sm); }
.btn:active { transform: translate(6px, 6px); box-shadow: 0 0 0 var(--sup-ink); }

.btn--sm { font-size: 0.9rem; padding: 0.55rem 1.15rem; box-shadow: var(--pop-sm); }
.btn--pink   { --btn-bg: var(--sup-pink); color: var(--sup-paper); }
.btn--yellow { --btn-bg: var(--sup-yellow); }
.btn--ghost  { --btn-bg: var(--sup-paper); }
.btn--ghost-light { --btn-bg: var(--sup-sky); }

/* --------------------------------------------------------------------------
   5. Header
   -------------------------------------------------------------------------- */
.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  height: var(--header-h);
  display: flex;
  align-items: center;
  transition: background 0.25s var(--ease-out), box-shadow 0.25s var(--ease-out);
}
.site-header.is-stuck {
  background: var(--sup-cream);
  border-bottom: var(--line) solid var(--sup-ink);
  box-shadow: 0 6px 0 rgba(13, 13, 13, 0.08);
}

.header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  width: min(100% - 2.5rem, 1320px);
}

.brand { display: block; flex: none; }
.brand__mark {
  height: 30px;
  width: auto;
  transition: transform 0.2s var(--ease-out);
}
.brand:hover .brand__mark { transform: rotate(-3deg) scale(1.04); }

.nav {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2vw, 1.9rem);
}
/* The pills read as broken if their labels ever wrap ("Connect / Wallet") */
.nav > .btn { white-space: nowrap; }
.nav > a {
  font-family: var(--font-display);
  font-size: 1rem;
  letter-spacing: 0.03em;
  text-decoration: none;
  color: var(--sup-ink);
  position: relative;
  padding: 4px 0;
}
.nav > a:not(.btn)::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -2px;
  height: 4px;
  border-radius: 4px;
  background: var(--sup-pink);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.22s var(--ease-out);
}
.nav > a:not(.btn):hover::after { transform: scaleX(1); }

/* Hero sits on sky blue, so the un-stuck header needs light text */
.site-header:not(.is-stuck) .nav > a:not(.btn) {
  color: var(--sup-ink);
  text-shadow: 0 2px 0 rgba(255, 255, 255, 0.5);
}

.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 48px; height: 44px;
  padding: 0 9px;
  background: var(--sup-yellow);
  border: var(--line) solid var(--sup-ink);
  border-radius: 12px;
  box-shadow: var(--pop-sm);
  cursor: pointer;
}
.nav-toggle__bar {
  display: block; height: 4px; width: 100%;
  background: var(--sup-ink); border-radius: 3px;
  transition: transform 0.22s var(--ease-out), opacity 0.15s linear;
}
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(1) { transform: translateY(9px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(3) { transform: translateY(-9px) rotate(-45deg); }

/* --------------------------------------------------------------------------
   6. Hero
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: min(100svh, 860px);
  padding-top: calc(var(--header-h) + clamp(1.25rem, 3.5vw, 2.75rem));
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  overflow: hidden;
  /* The flat --sky-art band has to reach the artwork's top edge, and where
     that edge lands is driven by WIDTH, not height: boardwalk-strip.webp is
     3000x991, always rendered at 100% width, so it is 33.03vw tall no matter
     how tall the hero is. Pinning the stop to a percentage of hero height
     (it was 55%) therefore only worked at some window sizes -- the scene top
     measures 79% of the hero at 375px wide but 37% at 2560px, so on wide
     screens the art butted into sky that had not finished fading and a hard
     line showed (#84E4FF against #78E0FF at 2560).

     Measured from the bottom instead, the flat band tracks the art exactly:
     33.03vw for the strip, rounded up to 34vw for slack, plus 3rem to clear
     .hero__scene's padding-top (max 2rem). vw counts the scrollbar the hero
     does not, which only over-covers -- the safe direction. */
  background: linear-gradient(
    to top,
    var(--sky-art) 0,
    var(--sky-art) calc(34vw + 3rem),
    #9DEDFF 100%
  );
  border-bottom: var(--line-thick) solid var(--sup-ink);
}

.hero__sky { position: absolute; inset: 0; pointer-events: none; }

/* The real cloud, lifted straight out of the boardwalk illustration */
.cloud {
  position: absolute;
  height: auto;
  pointer-events: none;
  filter: drop-shadow(0 4px 0 rgba(13, 13, 13, 0.07));
}
.cloud--1 { width: clamp(180px, 22vw, 340px); top: 15%; left: 4%;   animation: drift 34s ease-in-out infinite; }
.cloud--2 { width: clamp(130px, 16vw, 250px); top: 31%; right: 10%; animation: drift 46s ease-in-out infinite reverse; }
/* mirrored so the three do not read as the same stamp repeated */
.cloud--3 { width: clamp(100px, 12vw, 190px); top: 8%;  right: 31%; animation: drift 58s ease-in-out infinite; transform: scaleX(-1); }

/* Uses the standalone `translate` property, not `transform`, so the mirrored
   cloud keeps its scaleX(-1) while drifting. */
@keyframes drift {
  from { translate: -5vw 0; }
  to   { translate: 5vw 0; }
}

.sun {
  position: absolute;
  top: 8%; right: 8%;
  width: 120px; height: 120px;
  border-radius: 50%;
  background: var(--sup-yellow);
  border: var(--line) solid var(--sup-ink);
  box-shadow: 0 0 0 14px rgba(255, 210, 63, 0.28);
}

.hero__content {
  position: relative;
  z-index: 2;
  text-align: center;
  max-width: 860px;
}

.hero__title { margin: 0 0 1rem; }
.hero__logo {
  /* Scales with the viewport instead of sitting at a fixed 720px, which made
     the hero overrun a 720px-tall screen by ~40%. */
  width: min(100%, clamp(280px, 44vw, 600px));
  margin-inline: auto;
  filter: drop-shadow(8px 8px 0 rgba(13, 13, 13, 0.22));
  animation: bob 5s ease-in-out infinite;
}

@keyframes bob {
  0%, 100% { transform: translateY(0) rotate(-0.6deg); }
  50%      { transform: translateY(-12px) rotate(0.6deg); }
}

.hero__lede {
  font-size: clamp(1.05rem, 0.95rem + 0.55vw, 1.35rem);
  font-weight: 700;
  max-width: 620px;
  margin: 0 auto 1.6rem;
  color: var(--sup-ink);
  text-shadow: 0 2px 0 rgba(255, 255, 255, 0.45);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  justify-content: center;
}

.hero__scene {
  position: relative;
  z-index: 1;
  margin-top: auto;
  width: 100%;
  padding-top: clamp(1rem, 2.5vw, 2rem);
  /* Same colour as the strip's own sky, so any sub-pixel gap is invisible */
  background: var(--sky-art);
}
.hero__boardwalk {
  /* Exactly full width. Any overscale pushed the end buildings off-canvas,
     and the crop now runs from clean sky down to the true artwork edge, so
     nothing needs hiding. */
  width: 100%;
  margin-left: 0;
  will-change: transform;
}

/* --------------------------------------------------------------------------
   7. Marquee
   -------------------------------------------------------------------------- */
.marquee {
  background: var(--sup-pink);
  border-bottom: var(--line-thick) solid var(--sup-ink);
  overflow: hidden;
  padding: 0.75rem 0;
}
.marquee__track {
  display: flex;
  gap: 2rem;
  width: max-content;
  font-family: var(--font-display);
  font-size: 1.35rem;
  letter-spacing: 0.08em;
  color: var(--sup-paper);
  -webkit-text-stroke: 1.5px var(--sup-ink);
  animation: scroll-x 26s linear infinite;
}
.marquee__track .dot { color: var(--sup-yellow); -webkit-text-stroke: 1.5px var(--sup-ink); }

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

/* --------------------------------------------------------------------------
   8. Stats
   -------------------------------------------------------------------------- */
.stats {
  background: var(--sup-cream);
  padding: clamp(3rem, 6vw, 4.5rem) 0;
}
.stats__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 1.25rem;
}
.stat {
  background: var(--sup-paper);
  border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius-lg);
  box-shadow: var(--pop);
  padding: 1.6rem 1.25rem;
  text-align: center;
  transition: transform 0.18s var(--ease-out);
}
.stat:nth-child(1) { background: var(--sup-yellow); }
.stat:nth-child(2) { background: var(--sup-bubblegum); }
.stat:nth-child(3) { background: var(--sup-toxic); }
.stat:nth-child(4) { background: var(--sup-sky); }
.stat:hover { transform: translateY(-6px) rotate(-1.5deg); }

.stat__num {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(2.1rem, 1.5rem + 2.2vw, 3rem);
  line-height: 1;
  color: var(--sup-paper);
  -webkit-text-stroke: 2.5px var(--sup-ink);
  paint-order: stroke fill;
}
.stat__label {
  display: block;
  margin-top: 0.5rem;
  font-weight: 900;
  font-size: 0.9rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* --------------------------------------------------------------------------
   9. Split (World)
   -------------------------------------------------------------------------- */
.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: center;
}
.split__text .h2 { margin-bottom: 0.6em; }

.ticks { margin-top: 1.75rem; display: grid; gap: 0.75rem; }
.ticks li {
  position: relative;
  padding-left: 2.4rem;
  font-weight: 700;
}
.ticks li::before {
  content: "";
  position: absolute;
  left: 0; top: 2px;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--sup-toxic);
  border: 3px solid var(--sup-ink);
}
.ticks li::after {
  content: "";
  position: absolute;
  left: 9px; top: 9px;
  width: 6px; height: 11px;
  border: solid var(--sup-ink);
  border-width: 0 3px 3px 0;
  transform: rotate(42deg);
}

.polaroid {
  margin: 0;
  background: var(--sup-paper);
  border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius);
  box-shadow: var(--pop-lg);
  padding: 14px 14px 0;
  transform: rotate(-1.6deg);
  transition: transform 0.25s var(--ease-out);
}
.polaroid:hover { transform: rotate(0.8deg) scale(1.015); }
.polaroid img {
  border: 3px solid var(--sup-ink);
  border-radius: 10px;
}
.polaroid figcaption {
  font-family: var(--font-display);
  font-size: 1.05rem;
  text-align: center;
  padding: 0.7rem 0 0.9rem;
}

/* --------------------------------------------------------------------------
   10. Featured duck
   -------------------------------------------------------------------------- */
.duck-feature {
  display: grid;
  grid-template-columns: minmax(0, 420px) 1fr;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
}

.duck-feature__card {
  margin: 0;
  background: var(--sup-paper);
  border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius-lg);
  box-shadow: var(--pop-lg);
  overflow: hidden;
}

.duck-feature__art {
  position: relative;      /* positioning context for the loading cover */
  aspect-ratio: 1;
  border-bottom: var(--line) solid var(--sup-ink);
  display: grid;
  place-items: end center;
  background-size: cover;
  background-position: center;
  transition: background-image 0.2s linear;
}
.duck-feature__art img {
  width: 88%;
  align-self: end;
}

/* Until the duck of the day resolves, cover the card.
   The markup ships the bundled sample duck -- small, bottom-aligned, on a
   trait tile -- and the live token replaces it full-bleed. Left uncovered,
   every visitor watched that swap happen, which looked like a bug. This is
   an ::after cover rather than a rule on the element because buildDuckFeature
   paints the trait tile as an INLINE style, and no stylesheet rule outranks
   that without !important.

   Gated on .js: with scripting off nothing will ever replace the sample duck,
   so it must stay visible. */
.js .duck-feature__art.is-loading::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(100deg, var(--sup-sky) 30%, #CFF6FF 50%, var(--sup-sky) 70%)
    0 0 / 300% 100%;
  animation: duck-shimmer 1.4s ease-in-out infinite;
}
@keyframes duck-shimmer {
  from { background-position: 150% 0; }
  to   { background-position: -50% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .js .duck-feature__art.is-loading::after { animation: none; background: var(--sup-sky); }
}
/* The picker only applies to the bundled duck, so it must not appear and then
   vanish when the live one arrives. */
.js #duckPicker.is-pending { display: none; }

/* A live collection token is a finished square with its own background, so
   it fills the card instead of sitting on a trait tile. */
.duck-feature__art.is-live { background: none; place-items: stretch; }
.duck-feature__art.is-live img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  align-self: stretch;
}

.duck-feature__cap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.9rem 1.2rem 1rem;
}
.duck-feature__name { font-family: var(--font-display); font-size: 1.25rem; }
.duck-feature__bg {
  font-size: 0.78rem;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0.28rem 0.7rem;
  border: 2.5px solid var(--sup-ink);
  border-radius: var(--radius-pill);
  background: var(--sup-yellow);
  white-space: nowrap;
}

.duck-feature__info { color: var(--sup-ink); }
.duck-feature__h3 {
  font-size: clamp(1.6rem, 1.2rem + 1.6vw, 2.4rem);
  color: var(--sup-paper);
  text-shadow: 3px 3px 0 var(--sup-ink);
}
.duck-feature__info p { font-weight: 700; }
.duck-feature__hint { margin-bottom: 0.85rem; }

/* Real trait layer names off the board */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.55rem;
  margin: 0 0 1.6rem;
}
.chips li {
  font-family: var(--font-display);
  font-size: 0.92rem;
  letter-spacing: 0.02em;
  padding: 0.35rem 0.85rem;
  background: var(--sup-paper);
  border: 3px solid var(--sup-ink);
  border-radius: var(--radius-pill);
  box-shadow: 3px 3px 0 var(--sup-ink);
}

.swatch-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(46px, 1fr));
  gap: 0.55rem;
  max-width: 520px;
}
.swatch {
  aspect-ratio: 1;
  padding: 0;
  border: 3px solid var(--sup-ink);
  border-radius: 12px;
  background-size: cover;
  background-position: center;
  cursor: pointer;
  transition: transform 0.15s var(--ease-out), box-shadow 0.15s var(--ease-out);
}
.swatch:hover { transform: translateY(-4px) scale(1.06); }
.swatch.is-active {
  box-shadow: 0 0 0 4px var(--sup-yellow), 4px 4px 0 var(--sup-ink);
  transform: translateY(-4px) scale(1.06);
}

/* --------------------------------------------------------------------------
   11. Background trait grid
   -------------------------------------------------------------------------- */
.bg-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 1.15rem;
}

.bg-card {
  border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius);
  box-shadow: var(--pop-sm);
  overflow: hidden;
  background: var(--sup-paper);
  transition: transform 0.18s var(--ease-out), box-shadow 0.18s var(--ease-out);
}
.bg-card:hover { transform: translateY(-6px) scale(1.03); box-shadow: var(--pop); }

.bg-card__swatch {
  aspect-ratio: 1;
  border-bottom: 3px solid var(--sup-ink);
  background-size: cover;
  background-position: center;
}
.bg-card__name {
  display: block;
  font-family: var(--font-display);
  font-size: 0.95rem;
  text-align: center;
  padding: 0.6rem 0.5rem 0.7rem;
  letter-spacing: 0.02em;
}

/* --------------------------------------------------------------------------
   12. FAQ
   -------------------------------------------------------------------------- */
.faq { display: grid; gap: 1rem; }

.faq__item {
  background: var(--sup-paper);
  border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius);
  box-shadow: var(--pop-sm);
  overflow: hidden;
}
.faq__item[open] { background: var(--sup-yellow); }

.faq__item summary {
  list-style: none;
  cursor: pointer;
  padding: 1.15rem 3.5rem 1.15rem 1.4rem;
  font-family: var(--font-display);
  font-size: 1.2rem;
  position: relative;
}
.faq__item summary::-webkit-details-marker { display: none; }
.faq__item summary::after {
  content: "";
  position: absolute;
  right: 1.4rem; top: 50%;
  width: 14px; height: 14px;
  border: solid var(--sup-ink);
  border-width: 0 4px 4px 0;
  transform: translateY(-70%) rotate(45deg);
  transition: transform 0.22s var(--ease-out);
}
.faq__item[open] summary::after { transform: translateY(-25%) rotate(-135deg); }

.faq__answer { padding: 0 1.4rem 1.3rem; }
.faq__answer p { margin: 0; font-weight: 700; }

/* --------------------------------------------------------------------------
   13. Join CTA
   -------------------------------------------------------------------------- */
.join {
  background:
    radial-gradient(80% 120% at 50% 0%, rgba(255, 210, 63, 0.35), transparent 65%),
    linear-gradient(160deg, var(--sup-pink) 0%, var(--sup-purple) 100%);
  border-top: var(--line-thick) solid var(--sup-ink);
  border-bottom: var(--line-thick) solid var(--sup-ink);
  padding: clamp(4rem, 9vw, 7rem) 0;
}
.join__inner { text-align: center; }
.join__title { margin-bottom: 0.5em; }
.join__sub {
  color: var(--sup-paper);
  font-weight: 900;
  font-size: 1.15rem;
  text-shadow: 0 2px 0 rgba(13, 13, 13, 0.35);
  margin-bottom: 2rem;
}
.join__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  justify-content: center;
}

/* --------------------------------------------------------------------------
   14. Footer
   -------------------------------------------------------------------------- */
.site-footer {
  background: var(--sup-deep);
  color: var(--sup-cream);
  padding: clamp(3rem, 6vw, 4.5rem) 0 1.75rem;
}
.footer__inner {
  display: grid;
  grid-template-columns: minmax(220px, 1fr) 2fr;
  gap: clamp(2rem, 5vw, 4rem);
  padding-bottom: 2.5rem;
  border-bottom: 3px solid rgba(255, 247, 233, 0.18);
}
.footer__logo { height: 34px; width: auto; filter: invert(1); }
.footer__tag {
  margin: 1rem 0 0;
  font-family: var(--font-display);
  font-size: 1.1rem;
  color: var(--sup-sky);
}

.footer__nav {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 1.5rem;
}
.footer__col h4 {
  font-size: 1.05rem;
  color: var(--sup-yellow);
  margin-bottom: 0.8em;
}
.footer__col a {
  display: block;
  text-decoration: none;
  font-weight: 700;
  padding: 0.28rem 0;
  opacity: 0.85;
  transition: opacity 0.15s, transform 0.15s var(--ease-out);
}
.footer__col a:hover { opacity: 1; transform: translateX(4px); color: var(--sup-sky); }

.footer__base {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.5rem;
  padding-top: 1.75rem;
  font-size: 0.87rem;
  opacity: 0.7;
}
.footer__base p { margin: 0; }

/* --------------------------------------------------------------------------
   15. Scroll reveal
   -------------------------------------------------------------------------- */
/* Gated on .js so the page never ends up blank if the script fails to run */
.js .reveal {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.6s var(--ease-out), transform 0.6s var(--ease-out);
}
.js .reveal.is-visible { opacity: 1; transform: none; }

/* --------------------------------------------------------------------------
   16. Responsive
   -------------------------------------------------------------------------- */
@media (max-width: 900px) {
  .split { grid-template-columns: 1fr; }
  .split__media { order: -1; }
  .footer__inner { grid-template-columns: 1fr; }
  .duck-feature { grid-template-columns: 1fr; }
  .duck-feature__card { max-width: 420px; margin-inline: auto; }
}

/* The inline nav needs ~1280px once every link and pill is on one row —
   any tighter and the wallet pill wraps or slides off the right edge, so
   the drawer takes over well before the phone breakpoint. */
@media (max-width: 1279px) {
  .nav-toggle { display: flex; }

  .nav {
    position: fixed;
    inset: var(--header-h) 0 auto 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: var(--sup-cream);
    border-bottom: var(--line-thick) solid var(--sup-ink);
    padding: 0.5rem 1.25rem 1.5rem;
    transform: translateY(-130%);
    transition: transform 0.3s var(--ease-out);
    box-shadow: 0 14px 0 rgba(13, 13, 13, 0.1);
  }
  .nav.is-open { transform: translateY(0); }

  /* With the drawer open the header must match it. On the hero the header is
     transparent, which left a band of sky between the logo row and the cream
     drawer. */
  .site-header.is-nav-open {
    background: var(--sup-cream);
    border-bottom: none;
    box-shadow: none;
  }
  .nav > a {
    padding: 0.85rem 0;
    font-size: 1.3rem;
    border-bottom: 3px solid rgba(13, 13, 13, 0.1);
  }
  .nav > a:not(.btn)::after { display: none; }
  .nav__cta { margin-top: 1rem; border-bottom: var(--line) solid var(--sup-ink); }
}

@media (max-width: 780px) {
  .sun { width: 80px; height: 80px; top: 6%; right: 6%; }
  /* The strip is cropped tight to the artwork, so it is full-width here too.
     The old 165%/-32% overscale dated from the uncropped image and shoved the
     boardwalk off to the left on phones. */
  .hero__boardwalk { width: 100%; margin-left: 0; }
}

@media (max-width: 480px) {
  .btn { width: 100%; }
  /* ...except the icon-only rescan, which must stay a small circle */
  .btn--rescan-icon { width: 48px; }
  .hero__actions, .join__actions { flex-direction: column; }
  .bg-grid { grid-template-columns: repeat(2, 1fr); }
}

/* --------------------------------------------------------------------------
   17. Motion / print
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .js .reveal { opacity: 1; transform: none; }
}

/* --------------------------------------------------------------------------
   18. Resources page
   -------------------------------------------------------------------------- */
.page { padding-top: var(--header-h); }

.page__head {
  background: var(--sup-cream);
  padding: clamp(2.5rem, 6vw, 4.5rem) 0 clamp(1.5rem, 3vw, 2.5rem);
}
.page__lede {
  max-width: 620px;
  font-weight: 700;
  margin-top: 0.75rem;
}
/* The lede heads the dark Build section now, sitting between the title and
   the picker card */
.page__lede--light {
  color: var(--sup-paper);
  opacity: 0.92;
  margin: -0.9rem 0 1.75rem;
}

.section--tight2 { padding: clamp(2.5rem, 5vw, 4rem) 0; }
/* The build section opens the Resources page now — it doesn't need a full
   section gap between the header and the title. */
.page > .section--dark:first-child { padding-top: clamp(2rem, 4vw, 3rem); }

.step__title {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 1.1rem + 1.6vw, 2.2rem);
  margin-bottom: 1.75rem;
}
.step__title--light { color: var(--sup-paper); text-shadow: 3px 3px 0 var(--sup-ink); }
.step__num {
  flex: none;
  width: 46px; height: 46px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--sup-yellow);
  border: var(--line) solid var(--sup-ink);
  box-shadow: var(--pop-sm);
  color: var(--sup-ink);
  text-shadow: none;
  font-size: 1.35rem;
}

/* ---- picker (lives at the top of the Build It section) ---- */
.picker {
  background: var(--sup-paper);
  border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius-lg);
  box-shadow: var(--pop);
  padding: clamp(1.25rem, 3vw, 2rem);
  margin-bottom: 1.75rem;
}
.picker__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 1.25rem;
}
/* Inline in the actions row, the tabs shed the standalone top margin */
.picker__actions .col-tabs { margin: 0; }
.picker__or {
  font-family: var(--font-display);
  padding-bottom: 0.6rem;
  opacity: 0.5;
}
.field { display: grid; gap: 0.35rem; }
.field__label {
  font-size: 0.78rem;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.field__label small {
  display: block;
  font-weight: 700;
  text-transform: none;
  letter-spacing: 0;
  opacity: 0.6;
}
.field__row { display: flex; gap: 0.5rem; }
.field input[type="number"] {
  width: 140px;
  font: inherit;
  font-weight: 700;
  padding: 0.6rem 0.8rem;
  border: 3px solid var(--sup-ink);
  border-radius: 12px;
  background: var(--sup-cream);
}

.picker__status {
  margin: 1rem 0 0;
  font-weight: 700;
  font-size: 0.92rem;
  min-height: 1.4em;
}
.picker__status.is-ok   { color: #1E7A34; }
.picker__status.is-warn { color: #B4232B; }

.duck-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 1rem;
  max-height: 190px;
  overflow-y: auto;
  /* The active chip casts a 3px offset shadow to the right and below, and
     overflow-y:auto forces overflow-x to clip too -- so without this the
     shadow is sliced off the selected duck on both edges. Pulled back out
     with margin so nothing shifts. */
  padding: 0 4px 5px 0;
  margin-right: -4px;
  margin-bottom: -5px;
}
.duck-chip {
  font-family: var(--font-display);
  font-size: 0.9rem;
  padding: 0.4rem 0.8rem;
  background: var(--sup-cream);
  border: 3px solid var(--sup-ink);
  border-radius: var(--radius-pill);
  cursor: pointer;
}
.duck-chip.is-active { background: var(--sup-yellow); box-shadow: 3px 3px 0 var(--sup-ink); }

/* ---- tabs ---- */
.tabs { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-bottom: 1.5rem; }

.tab {
  font-family: var(--font-display);
  font-size: 1rem;
  padding: 0.6rem 1.3rem;
  background: var(--sup-paper);
  border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius-pill);
  box-shadow: var(--pop-sm);
  cursor: pointer;
}
.tab.is-active { background: var(--sup-yellow); }

/* ---- studio ---- */
.studio {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: start;
}
/* Grid and flex children default to min-width:auto, which the 1200px canvas
   would otherwise use as a floor. */
.studio > * { min-width: 0; }

.stage {
  position: relative;
  background:
    repeating-conic-gradient(rgba(255,255,255,0.14) 0% 25%, transparent 0% 50%) 50%/22px 22px;
  border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius-lg);
  box-shadow: var(--pop);
  overflow: hidden;
  display: grid;
  place-items: center;
  min-height: 260px;
}
.stage canvas { display: block; width: 100%; max-width: 100%; height: auto; }
/* Once there is art the canvas sets the height. Keeping the empty-state
   minimum would letterbox the wide membership card on a narrow screen. */
.stage.has-art { min-height: 0; }
.stage__empty {
  position: absolute;
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.1rem;
  color: var(--sup-paper);
  text-shadow: 2px 2px 0 var(--sup-ink);
}
.stage__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  margin-top: 1.1rem;
}
.stage__meta {
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--sup-paper);
  opacity: 0.8;
}
.btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; box-shadow: var(--pop); }

.studio__controls {
  --panel-pad: clamp(1.1rem, 2.5vw, 1.75rem);
  background: rgba(255, 255, 255, 0.07);
  border: 3px solid rgba(255, 255, 255, 0.22);
  border-radius: var(--radius-lg);
  padding: var(--panel-pad);
}
.panel { display: none; }
.panel.is-active { display: block; }

.control { margin-bottom: 1.4rem; }
.control:last-child { margin-bottom: 0; }
.control__label {
  display: block;
  font-size: 0.78rem;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--sup-paper);
  margin-bottom: 0.6rem;
}
.control__label small {
  display: block;
  font-weight: 700;
  text-transform: none;
  letter-spacing: 0;
  opacity: 0.65;
}

/* The row scrolls edge to edge of the panel, not edge to edge of the panel's
   text column. Left inside the padding, a chip disappeared a whole gutter
   early and read as clipped by nothing at all. Negative margins pull the
   scroll port out to the border; the matching padding keeps the first and
   last chips off it when you reach either end. */
.chip-row {
  display: flex;
  gap: 0.5rem;
  overflow-x: auto;
  padding-bottom: 0.25rem;
  margin-inline: calc(-1 * var(--panel-pad, 0px));
  padding-inline: var(--panel-pad, 0px);
  scroll-padding-inline: var(--panel-pad, 0px);
  /* Same clipping trap vertically: :hover lifts a chip 2px and the active one
     drops a 3px shadow, both of which a scroll container cuts. */
  padding-top: 3px;
  margin-top: -3px;
  /* The chips have 3px outlines and a drop shadow; a scrollbar underneath
     them fights the artwork. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.chip-row::-webkit-scrollbar { display: none; }
.chip-row--wrap {
  flex-wrap: wrap;
  overflow: visible;
  margin-inline: 0;
  padding-inline: 0;
}
.chip {
  flex: none;
  font-family: var(--font-display);
  font-size: 0.85rem;
  letter-spacing: 0.02em;
  padding: 0.42rem 0.9rem;
  background: var(--sup-paper);
  border: 3px solid var(--sup-ink);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: transform 0.14s var(--ease-out);
}
.chip:hover { transform: translateY(-2px); }
.chip.is-active { background: var(--sup-yellow); box-shadow: 3px 3px 0 var(--sup-ink); }

.control input[type="range"] { width: 100%; accent-color: var(--sup-pink); }


/* ---- membership card tier strip ---- */
.card-tier {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1rem;
  align-items: center;
}
.card-tier__item {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  opacity: 0.42;                 /* tiers you have not reached */
  transition: opacity 0.14s var(--ease-out);
}
/* The one your holdings actually earn. */
.card-tier__item.is-active {
  opacity: 1;
  transform: scale(1.08);
}
.card-tier__item img { width: 34px; height: 34px; }
.card-tier__item em {
  font-family: var(--font-display);
  font-style: normal;
  font-size: 0.85rem;
  color: var(--sup-paper);
}

/* ---- rescan ---- */
.btn--rescan {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}
/* Icon-only variant: a compact round refresh control. The text label is
   sr-only in the markup, so aria-label/title carry the name visually. */
.btn--rescan-icon {
  width: 48px;
  height: 48px;
  min-height: 48px;
  padding: 0;
  justify-content: center;
  border-radius: 50%;
}
.rescan__icon { flex: none; }
/* Spun only while a scan is running, so the button reads as busy rather than
   unresponsive -- an owner-scan sweeps the whole supply and takes seconds. */
.btn--rescan.is-busy .rescan__icon { animation: rescan-spin 0.9s linear infinite; }
@keyframes rescan-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .btn--rescan.is-busy .rescan__icon { animation: none; opacity: 0.6; }
}

/* ---- wallet button ---- */
[data-wallet-button] { font-family: var(--font-display); }
/* Drive --btn-bg, not `background`. Every .btn colour in this file works that
   way (.btn--pink, .btn--boardwalk:hover), because .btn itself paints
   `background: var(--btn-bg)`. Setting `background` here instead meant two
   declarations competing for the same pixel, which is fragile and was the
   wrong idiom regardless of who won. */
[data-wallet-button].is-connected { --btn-bg: var(--sup-toxic); color: var(--sup-ink); }

/* Connected, the pill shows the address until you go to press it, then says
   what pressing it will do. Both labels occupy the same grid cell so the
   button is sized to the wider of the two and never resizes mid-hover --
   a pill that jumps as the cursor lands on it is easy to mis-click. */
[data-wallet-button].is-connected {
  display: inline-grid;
  grid-template-areas: "label";
  place-items: center;
  transition: background-color 0.14s var(--ease-out), color 0.14s var(--ease-out),
              transform 0.14s var(--ease-out), box-shadow 0.14s var(--ease-out);
}
[data-wallet-button].is-connected .wallet__label { grid-area: label; }
[data-wallet-button].is-connected .wallet__label--off { visibility: hidden; }

/* Desktop: on hover. Guarded so a touch device, where :hover can stick after
   a tap, does not leave the pill reading "Disconnect" indefinitely. */
@media (hover: hover) {
  [data-wallet-button].is-connected:hover,
  [data-wallet-button].is-connected:focus-visible {
    --btn-bg: var(--sup-danger);
    color: #fff;
  }
  [data-wallet-button].is-connected:hover .wallet__label--addr,
  [data-wallet-button].is-connected:focus-visible .wallet__label--addr { visibility: hidden; }
  [data-wallet-button].is-connected:hover .wallet__label--off,
  [data-wallet-button].is-connected:focus-visible .wallet__label--off { visibility: visible; }
}

/* Touch: while pressed. :active fires on touchstart, so the label confirms
   the action under the finger before it lifts. */
[data-wallet-button].is-connected:active {
  --btn-bg: var(--sup-danger);
  color: #fff;
}
[data-wallet-button].is-connected:active .wallet__label--addr { visibility: hidden; }
[data-wallet-button].is-connected:active .wallet__label--off { visibility: visible; }

@media (max-width: 900px) {
  /* minmax(0,…) not 1fr: a bare 1fr floors at the canvas's intrinsic width
     (1200px) and pushed the stage past the viewport on phones. */
  .studio { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 560px) {
  .picker__or { display: none; }
  /* The actions row used to stack its two full-width buttons here. It holds
     the collection tabs and a 48px icon now, so it stays a wrapping row —
     the rescan sits beside the tabs unless space genuinely runs out. */
  .field input[type="number"] { width: 100%; }
}

.picker__gate {
  margin: 0 0 1.25rem;
  font-weight: 700;
  font-size: 0.95rem;
  padding-left: 0.9rem;
  border-left: 4px solid var(--sup-yellow);
}

/* ---- legal pages (terms.html / privacy.html) ---- */
.legal h3 { margin-top: 2rem; }
.legal ul {
  list-style: disc;
  padding-left: 1.4rem;
  margin-bottom: 1.1em;
}
.legal ul li { margin-bottom: 0.35em; }
.legal-pending { padding: clamp(2rem, 6vw, 4rem) 0; }

/* ---- downloads grid ---- */
.dl-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.25rem;
  margin-top: 1.75rem;
}
.dl-card {
  margin: 0;
  background: var(--sup-paper);
  border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius-lg);
  box-shadow: var(--pop-sm);
  overflow: hidden;
  transition: transform 0.18s var(--ease-out), box-shadow 0.18s var(--ease-out);
}
.dl-card:hover { transform: translateY(-5px); box-shadow: var(--pop); }
.dl-card img { border-bottom: 3px solid var(--sup-ink); width: 100%; }
.dl-card figcaption {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.8rem 1rem 0.9rem;
}
.dl-card__name { font-family: var(--font-display); font-size: 1rem; }

/* ---- collection tabs in the picker ---- */
.col-tabs { display: flex; flex-wrap: wrap; gap: 0.55rem; margin: 1.1rem 0 0; }
.col-tab {
  font-family: var(--font-display);
  font-size: 0.92rem;
  padding: 0.45rem 1rem;
  background: var(--sup-cream);
  border: 3px solid var(--sup-ink);
  border-radius: var(--radius-pill);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}
.col-tab.is-active { background: var(--sup-yellow); box-shadow: 3px 3px 0 var(--sup-ink); }
.col-tab__n {
  font-family: var(--font-body);
  font-weight: 900;
  font-size: 0.72rem;
  background: var(--sup-ink);
  color: var(--sup-paper);
  border-radius: var(--radius-pill);
  padding: 0.05rem 0.45rem;
}

.control__note {
  margin: 0.25rem 0 0;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--sup-paper);
  opacity: 0.75;
}

/* --------------------------------------------------------------------------
   18b. Collections carousel (home page)
   -------------------------------------------------------------------------- */
.col-carousel { position: relative; }

.col-carousel { --col-gap: 1.5rem; }

/* The ring rotates whole cards through a fixed window — nothing is ever cut
   off at the shell edge, so no bleed or edge fade is needed. */
.col-carousel__viewport {
  overflow: hidden;
  /* Room for the pop shadows and the centre card's lift */
  padding: 1rem 10px 1.25rem;
  touch-action: pan-y;
}

.col-carousel__ring {
  display: flex;
  gap: var(--col-gap);
  will-change: transform;
}

.col-card {
  /* Three whole cards fill the window; the ring's extra cards wait offstage */
  flex: 0 0 calc((100% - 2 * var(--col-gap)) / 3);
  display: flex;
  flex-direction: column;
  background: var(--sup-paper);
  border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius-lg);
  box-shadow: var(--pop);
  padding: 1rem;
  transition: transform 0.25s var(--ease-out);
}
.col-card:hover { transform: translateY(-6px) rotate(-1deg); }
/* The card in the middle of the window sits proud of its neighbours */
.col-card.is-center { transform: translateY(-8px); box-shadow: var(--pop-lg); }
.col-card.is-center:hover { transform: translateY(-10px) rotate(-1deg); }

.col-card__img {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius);
  background: var(--sup-sky);
}

.col-card__name {
  font-size: 1.3rem;
  margin: 0.85rem 0 0.25rem;
}

.col-card__desc {
  font-size: 0.9rem;
  font-weight: 700;
  line-height: 1.45;
  opacity: 0.85;
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: calc(3 * 1.45 * 0.9rem);
}

.col-card__stats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0.85rem 0;
}
.col-card__stat {
  background: var(--sup-cream);
  border: 3px solid var(--sup-ink);
  border-radius: var(--radius-pill);
  padding: 0.1rem 0.65rem;
  font-size: 0.78rem;
  font-weight: 700;
  white-space: nowrap;
}
.col-card__stat b { font-weight: 900; }

.col-card__btn {
  margin-top: auto;
  text-align: center;
  justify-content: center;
}

.col-carousel__controls {
  display: flex;
  justify-content: center;
  gap: 1rem;
  margin-top: 0.5rem;
}

.col-carousel__nav {
  width: 52px;
  height: 52px;
  display: grid;
  place-items: center;
  border: var(--line) solid var(--sup-ink);
  border-radius: 50%;
  background: var(--sup-yellow);
  box-shadow: var(--pop-sm);
  font-size: 1.35rem;
  font-weight: 900;
  color: var(--sup-ink);
  cursor: pointer;
  transition: transform 0.15s var(--ease-out);
}
.col-carousel__nav:hover { transform: translateY(-3px); }
.col-carousel__nav:active { transform: translateY(1px); }

@media (max-width: 780px) {
  /* One whole card per view; swipe or the arrows below rotate the ring */
  .col-card { flex-basis: 100%; }
}

/* --------------------------------------------------------------------------
   19. Mobile / PWA
   -------------------------------------------------------------------------- */
:root { --safe-t: env(safe-area-inset-top, 0px); --safe-b: env(safe-area-inset-bottom, 0px); }

/* Respect the notch when installed to the home screen */
.site-header { padding-top: var(--safe-t); height: calc(var(--header-h) + var(--safe-t)); }
.page { padding-top: calc(var(--header-h) + var(--safe-t)); }
.site-footer { padding-bottom: calc(1.75rem + var(--safe-b)); }

/* The drawer breakpoint, matching the nav rules above */
@media (max-width: 1279px) {
  .nav { top: calc(var(--header-h) + var(--safe-t)); }
}

@media (max-width: 780px) {
  /* Touch targets: 44px minimum */
  .chip, .duck-chip, .col-tab { min-height: 44px; padding-block: 0.6rem; }
  .tab { min-height: 46px; }
  .btn { min-height: 48px; }

  /* Horizontal chip rows should scroll cleanly under a thumb */
  .chip-row {
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .chip-row::-webkit-scrollbar { display: none; }
  .chip-row > .chip { scroll-snap-align: start; }

  /* The holdings strip can get long; cap it and let it scroll */
  .duck-strip { max-height: 168px; }

  .step__title { font-size: 1.4rem; }
  .step__num { width: 38px; height: 38px; font-size: 1.1rem; }
  .studio__controls { padding: 1rem; }
  .stage__actions { flex-direction: column; align-items: stretch; }
  .stage__meta { text-align: center; }
}

/* Installed standalone: the browser chrome is gone, so trim the top gap */
@media (display-mode: standalone) {
  .page__head { padding-top: 1.5rem; }
}

/* Stop iOS Safari inflating text in landscape */
@media screen and (max-device-width: 780px) {
  html { -webkit-text-size-adjust: 100%; }
}

/* ---- grouped downloads ---- */
/* The wrapper must not be a grid itself — each group carries its own, and
   nesting them turned the two groups into side-by-side columns. */
.dl-groups { display: block; }
.dl-group { margin-top: 2.25rem; }
.dl-group:first-child { margin-top: 1.5rem; }
.dl-group__title {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 1.25rem;
  margin-bottom: 1rem;
}
.dl-group__n {
  font-family: var(--font-body);
  font-weight: 900;
  font-size: 0.72rem;
  background: var(--sup-ink);
  color: var(--sup-paper);
  border-radius: var(--radius-pill);
  padding: 0.1rem 0.5rem;
}
/* Wallpapers are tall phone art; headers are wide. Different grids. */
.dl-grid--wide { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.dl-grid--tall { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
.dl-grid--tall .dl-card img { aspect-ratio: 853 / 1844; object-fit: cover; }

@media (max-width: 560px) {
  .dl-grid--tall { grid-template-columns: repeat(2, 1fr); }
}

/* ---- Boardwalk entry point ---- */
.btn--boardwalk {
  --btn-bg: var(--sup-purple);
  color: var(--sup-paper);
  text-decoration: none;
  white-space: nowrap;
  letter-spacing: 0.02em;
}
/* Scoped to .nav on purpose: `.nav > a` sets `padding: 4px 0`, and being a
   class-plus-element selector it outranks a bare `.btn--boardwalk`. The pill
   was rendering with 4px of end padding, which is what made the label look
   cramped against the edges. */
.nav > .btn--boardwalk {
  /* `.nav > a` also overrides font-size (1rem vs the pill's 0.9rem), which is
     what left this button 10px taller than the wallet pill beside it. */
  font-size: 0.9rem;
  padding: 0.55rem 1.6rem;
  /* Match the <button> beside it. A <button> resolves `normal` from the font
     -- Sigmar works out around 1.67 -- and an <a> would have inherited the
     body's 1.65, which was near enough by accident. The 1.25 that used to sit
     here to correct that overshot instead, leaving the pill 6px short of the
     wallet button next to it. Asking for the same `normal` matches by
     construction rather than by a number that happens to land. */
  line-height: normal;
}
.btn--boardwalk:hover { --btn-bg: #8d5cff; }

@media (max-width: 1279px) {
  /* In the drawer it reads as a nav item, so let it fill the width */
  .nav__boardwalk { margin-top: 1rem; border-bottom: var(--line) solid var(--sup-ink); }
}

/* ---- sign-in chooser ---- */
.auth-menu {
  position: absolute;
  z-index: 200;
  min-width: 208px;
  background: var(--sup-paper);
  border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius);
  box-shadow: var(--pop);
  padding: 0.4rem;
  display: grid;
  gap: 0.3rem;
}
.auth-menu__item {
  font-family: var(--font-display);
  font-size: 0.95rem;
  text-align: left;
  padding: 0.7rem 0.9rem;
  min-height: 44px;
  background: var(--sup-cream);
  border: 3px solid var(--sup-ink);
  border-radius: 12px;
  cursor: pointer;
}
.auth-menu__item:hover { background: var(--sup-yellow); }

/* --------------------------------------------------------------------------
   20. Bungalow page
   The React app is mounted under the shared header rather than owning the
   viewport, so it gets the remaining height and the header keeps its safe-area
   inset. --sup-header-h is what App.tsx subtracts from 100dvh.
   -------------------------------------------------------------------------- */
.bungalow-page {
  --sup-header-h: calc(var(--header-h) + var(--safe-t));
  margin: 0;
  background: var(--sup-deep);
}
/* The app itself does not scroll — it scales to fit — but the page does, so
   the shared footer is reachable below it like on every other page. */
.bungalow-page #root > div { overscroll-behavior: contain; }
.bungalow-page #root {
  margin-top: var(--sup-header-h);
}
/* The app draws its own dark scene; the site header stays cream above it. */
.bungalow-page .site-header { background: var(--sup-cream); }

/* The hook and the sign-off are their own lines so the middle sentence reads
   as the collection blurb rather than one long run-on. */
.hero__lede-hook,
.hero__lede-sign {
  display: block;
}
.hero__lede-hook { margin-bottom: 0.35em; }
.hero__lede-sign { margin-top: 0.35em; }

/* ---- Gypsy's Tailor Shop (tailor.html) ---- */
/* The painting is the layout. Every position below is a percentage of it,
   measured off the artwork itself, so the scene holds together at any width
   without a media query per element:
     mirror panel  x 0.510-0.936, y 0.319-1
     counter top   y 0.797, running from the left edge to x 0.44 */
.shop-stage {
  position: relative;
  border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius-lg);
  box-shadow: var(--pop);
  overflow: hidden;
  margin-bottom: 1.25rem;
  background: var(--sup-ink);
}
/* Everything pinned to the painting is pinned to THIS, not to the stage: the
   stage also holds the rack once the rack is not a drawer, and anchoring to it
   dropped the arrows and the mirror's caption onto the rack below. */
.shop-stage__scene { position: relative; }
.shop-stage__bg { display: block; width: 100%; height: auto; }

/* The duck stands in the mirror.
   Sized off the body tile rather than the finished composite: a duck occupies
   y 0.276-1 of its square frame and is centred on x 0.484, and those hold for
   every duck, while the composite's box moves with whatever hat is on (Goku
   hair alone is twice the width of a beanie). Working from the body means the
   duck lands in the same place no matter what it is wearing, and a tall hat
   overflows upward the way it should. 46.8% wide puts the duck at 86% of the
   mirror's height, which clears the shop sign; the frame's bottom edge is the
   artwork's, so the duck stands on the floor. */
.shop-stage__duck {
  position: absolute;
  left: 49.7%;
  bottom: 0;
  width: 46.8%;
  height: auto;
  opacity: 0;
  transition: opacity 0.25s var(--ease-out);
  pointer-events: none;
}
.shop-stage.has-duck .shop-stage__duck { opacity: 1; }

/* Gypsy behind the counter. The counter is painted into the background, so
   she cannot be drawn beneath it -- this box ends exactly at the counter's top
   edge (y 0.797 of the artwork) and clips her, which reads as standing behind
   it and needs no second copy of the counter drawn over her. */
.shop-stage__counter {
  position: absolute;
  left: 6%;
  bottom: 21.07%;     /* the counter's top edge, measured at y 0.7893 */
  width: 21%;
  height: 41%;        /* her own proportions at that width, so nothing crops */
  overflow: hidden;
  pointer-events: none;
}
.shop-stage__keeper {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: auto;
}

/* Says what the mirror is for until there is something in it. */
.shop-stage__empty {
  position: absolute;
  left: 51.0%;
  top: 55%;
  width: 42.6%;
  margin: 0;
  text-align: center;
  font-family: var(--font-display);
  font-size: clamp(0.85rem, 1.7vw, 1.4rem);
  color: #fff;
  text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.65);
  pointer-events: none;
}
.shop-stage.has-duck .shop-stage__empty { display: none; }

/* The rack, dressed as the Bungalow stash: a pager, a count, a dot per
   section, and a grid under it. A wrapping grid rather than the scrolling row
   it started as -- Clothes runs to forty-odd once every jersey is in, and a
   row that long hides most of itself off the right-hand edge. */
.stash {
  background: rgba(24, 10, 46, 0.92);
  border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius-lg);
  box-shadow: var(--pop);
  padding: 0.9rem 1rem 1rem;
  margin-bottom: 1.25rem;
}
.stash__tab { display: none; }

/* Under the drawer breakpoint the rack sits below the scene inside the same
   frame, so it needs the stage's rounded corners taken off its top edge. */
.shop-stage .stash {
  border: 0;
  border-top: var(--line) solid var(--sup-ink);
  border-radius: 0;
  box-shadow: none;
  margin: 0;
}

/* Wide enough to hold the shop at a usable size: the drawer slides out of the
   left wall, the way the Bungalow's slides out of the right. Below this it
   goes back to being a panel under the shop -- a drawer across a phone-width
   scene would cover the very duck it is dressing. */
@media (min-width: 900px) {
  .shop-stage { overflow: hidden; }

  .shop-stage .stash {
    position: absolute;
    left: 0;
    top: 0;
    height: 100%;
    width: min(21rem, 62%);
    margin: 0;
    border: var(--line) solid var(--sup-ink);
    border-left: 0;
    border-radius: 0 var(--radius-lg) var(--radius-lg) 0;
    display: flex;
    flex-direction: column;
    transform: translateX(-100%);
    transition: transform 0.3s var(--ease-out);
    z-index: 20;
  }
  .shop-stage .stash.is-open { transform: translateX(0); }

  /* The handle rides on the drawer's outer edge, so it travels with it and is
     the one thing still on screen when the drawer is shut. */
  /* The same handle the Bungalow's stash wears, mirrored: that drawer comes
     out of the right wall so its tab is on the left, this one is the other way
     round. The word reads down the edge a letter at a time rather than being
     a rotated line -- upright letters stay legible at a glance. */
  .stash__tab {
    display: flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    left: 100%;
    top: 50%;
    transform: translateY(-50%);
    width: 3rem;
    height: 10rem;
    padding: 0;
    background: linear-gradient(180deg, #facc15, #f97316);
    border: 3px solid var(--sup-ink);
    border-left: 0;
    border-radius: 0 1rem 1rem 0;
    box-shadow: 3px 3px 0 0 rgba(0, 0, 0, 0.6);
    cursor: pointer;
  }
  .stash__tab-text {
    display: flex;
    flex-direction: column;
    align-items: center;
    font-family: var(--font-display);
    font-size: 0.875rem;
    line-height: 1.25;
    letter-spacing: 0.025em;
    color: var(--sup-paper);
    filter: drop-shadow(1px 1px 0 rgba(0, 0, 0, 0.5));
  }

  .stash .shop-rack {
    flex: 1;
    min-height: 0;
    max-height: none;
  }
}
.stash__pager {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.stash__title {
  flex: 1;
  min-width: 0;
  margin: 0;
  text-align: center;
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 2.4vw, 1.6rem);
  line-height: 1.1;
  color: #fff;
  text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.6);
}
.stash__cat {
  display: block;
  font-size: 0.72em;
  color: var(--sup-yellow);
}
.stash__arrow {
  flex: none;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: 1.5rem;
  line-height: 1;
  color: var(--sup-ink);
  background: var(--sup-paper);
  border: 3px solid var(--sup-ink);
  border-radius: var(--radius-pill);
  box-shadow: var(--pop-sm);
  cursor: pointer;
}
.stash__arrow:hover { background: var(--sup-yellow); }
.stash__arrow:active { transform: translate(2px, 2px); box-shadow: 0 0 0 var(--sup-ink); }

.stash__meta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  margin: 0.5rem 0 0.85rem;
}
.stash__count {
  font-weight: 900;
  font-size: 0.72rem;
  line-height: 1;
  padding: 0.3rem 0.6rem;
  color: #fff;
  background: linear-gradient(90deg, var(--sup-yellow), var(--sup-orange));
  border: 2px solid var(--sup-ink);
  border-radius: var(--radius-pill);
}
.stash__dots { display: flex; gap: 0.35rem; }
.stash__dot {
  width: 9px;
  height: 9px;
  border: 2px solid var(--sup-ink);
  border-radius: 50%;
  background: var(--sup-paper);
}
.stash__dot.is-on { background: var(--sup-pink); }

.shop-rack {
  display: grid;
  /* auto-fill rather than auto-fit. This was the other way round, so that a
     nearly empty rack shared the row out between the few cards in it rather
     than leaving a gap -- but the cost only shows when a row holds one item,
     and then it is the whole point: the card stretches the full width of the
     drawer and stands about twice the size of every other card on the page.
     A card should be the same square whether it has neighbours or not, so
     the empty tracks stay. */
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  /* Rows take the tile's own square height. Without this the grid stretches
     its rows to fill the drawer, and a section with forty-odd items in it
     squashed every tile into a bar. */
  grid-auto-rows: max-content;
  align-content: start;
  /* Same spacing as the grouped grids: the cards are tilted and cast a 5px
     shadow, and at the old gap their corners clipped their neighbours. Hats
     and the other ungrouped pages lay out here rather than in .rack__grid, so
     both have to carry it. */
  gap: 0.9rem 0.8rem;
  max-height: 22rem;
  overflow-y: auto;
  padding: 0.35rem;
  /* The bar sat over the right-hand column of tiles. The rack still scrolls;
     it just does it without drawing one. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.shop-rack::-webkit-scrollbar { width: 0; height: 0; }
/* One square, every tile, every section -- a rack whose rows change height as
   you page from Hats to Clothes reads as a different component each time. The
   label sits over the foot of the art rather than under it, which is what
   keeps the square honest when the name runs to two lines. */
/* Grouped, the rack is a stack of collapsible sections rather than one grid,
   so each section carries its own. */
.shop-rack--grouped { display: block; }
.rack__group { margin-bottom: 0.6rem; }
.rack__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.45rem 0.7rem;
  font-family: var(--font-display);
  font-size: 0.9rem;
  color: var(--sup-ink);
  background: var(--sup-yellow);
  border: 3px solid var(--sup-ink);
  border-radius: var(--radius-md);
  box-shadow: var(--pop-sm);
  cursor: pointer;
  list-style: none;
}
.rack__summary::-webkit-details-marker { display: none; }
.rack__summary::after {
  content: '▸';
  transition: transform 0.15s var(--ease-out);
}
.rack__group[open] > .rack__summary::after { transform: rotate(90deg); }
.rack__group[open] > .rack__summary { border-radius: var(--radius-md) var(--radius-md) 0 0; }
.rack__n {
  margin-left: auto;
  margin-right: 0.4rem;
  font-family: var(--font-body);
  font-size: 0.7rem;
  font-weight: 900;
  padding: 0.1rem 0.45rem;
  color: var(--sup-ink);
  background: rgba(255, 255, 255, 0.75);
  border: 2px solid var(--sup-ink);
  border-radius: var(--radius-pill);
}
.rack__grid {
  display: grid;
  /* auto-fill, not auto-fit. auto-fit collapses the tracks nothing landed in,
     so the single item in a one-item row -- Basketball holds one jersey --
     stretched across the full width of the drawer and stood about twice the
     size of every other card. auto-fill keeps the empty tracks, so a card is
     the same square whether it has neighbours or not. */
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  grid-auto-rows: max-content;
  /* Roomier than flat tiles needed: these are tilted and cast a 5px shadow,
     and at the old spacing the corners clipped their neighbours. */
  gap: 0.9rem 0.8rem;
  padding: 0.7rem 0.35rem 0.35rem;
}
/* Cut to the same pattern as the Bungalow's stash cards: a white card with a
   chunky outline and an offset block shadow, the art in its own square well
   on warm tie-dye, and the name underneath rather than laid over the picture.
   They sit at a slight alternating tilt like stickers and straighten under
   the pointer. */
.rack__item {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 0.4rem 0.4rem 0.5rem;
  background: var(--sup-paper);
  border: 4px solid var(--sup-ink);
  border-radius: 1rem;
  box-shadow: 5px 5px 0 0 rgba(0, 0, 0, 0.6);
  cursor: pointer;
  transition: transform 0.2s var(--ease-out), box-shadow 0.2s var(--ease-out);
}
.rack__item:nth-child(odd)  { transform: rotate(1deg); }
.rack__item:nth-child(even) { transform: rotate(-1deg); }
.rack__item:hover,
.rack__item:focus-visible { transform: rotate(0deg) scale(1.05); }
/* A ring rather than a fill, so the chosen item still shows its own colours. */
.rack__item.is-active {
  box-shadow: 5px 5px 0 0 rgba(0, 0, 0, 0.6), 0 0 0 4px var(--sup-yellow);
}

/* The thumbnails are built cropped to the art and padded square
   (tools/build-shop-assets.py), so they only have to be contained and
   centred -- no per-category offset, nothing to drift out of frame. */
.rack__art {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  aspect-ratio: 1 / 1;
  margin-bottom: 0.3rem;
  overflow: hidden;
  background: linear-gradient(135deg, #FEF9C3, #FED7AA 55%, #FBCFE8);
  border: 2px solid var(--sup-ink);
}
/* The same spray-paint grain the Bungalow lays over its tie-dye surfaces. */
.rack__art::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.5 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
  opacity: 0.12;
  mix-blend-mode: overlay;
}
.rack__art img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition: transform 0.3s var(--ease-out);
}
.rack__item:hover .rack__art img { transform: scale(1.1); }

.rack__label {
  font-family: var(--font-display);
  font-size: 0.6rem;
  line-height: 1.2;
  text-align: center;
  color: #6B21A8;
  /* Two lines then an ellipsis, and the space for both reserved either way,
     so a one-word name does not sit a row higher than its neighbours. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: 1.44rem;
}
.rack__item--none { justify-content: center; min-height: 6.5rem; }

/* Grouped, the way-back tile is a bar like the section headers above it
   rather than a card. It is a control, not something to wear, and a tall
   square sitting on top of a stack of bars reads as the first item in the
   rack. Double class so it outranks the sticker tilt, which is keyed on
   :nth-child and would otherwise leave the bar sitting at an angle. */
.rack__item.rack__item--bar {
  flex-direction: row;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 0;
  margin-bottom: 0.6rem;
  padding: 0.5rem 0.7rem;
  border-width: 3px;
  border-radius: var(--radius-md);
  box-shadow: var(--pop-sm);
  transform: none;
}
.rack__item.rack__item--bar:hover,
.rack__item.rack__item--bar:focus-visible {
  transform: none;
  background: var(--sup-cream);
}
.rack__item.rack__item--bar.is-active {
  background: var(--sup-yellow);
  box-shadow: var(--pop-sm);
}
.rack__none {
  font-family: var(--font-display);
  font-size: 0.85rem;
  text-align: center;
  color: var(--sup-ink);
}

.shop-actions { display: flex; gap: 0.75rem; flex-wrap: wrap; }

@media (max-width: 640px) {
  .shop-rack { grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); }
}

/* The shop, shut. Bungalow's treatment: the room stays visible behind a tint
   so it is obvious what is on the other side, and the whole panel is the
   control rather than a button to hunt for. */
.shop-lock {
  position: absolute;
  inset: 0;
  z-index: 25;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  border: 0;
  background: rgba(26, 12, 56, 0.6);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  cursor: pointer;
  transition: background 0.2s var(--ease-out);
}
.shop-lock:hover,
.shop-lock:focus-visible { background: rgba(26, 12, 56, 0.45); outline: none; }
.shop-lock[hidden] { display: none !important; }

.shop-lock__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(0.9rem, 2.2vw, 1.6rem);
}
.shop-lock__line {
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 3vw, 2.4rem);
  line-height: 1.1;
  text-align: center;
  color: #fff;
  text-shadow: 3px 3px 0 rgba(0, 0, 0, 0.65);
}
.shop-lock__cta {
  display: inline-flex;
  align-items: center;
  padding: clamp(0.5rem, 1.2vw, 0.9rem) clamp(1.2rem, 3vw, 2.4rem);
  font-family: var(--font-display);
  font-size: clamp(0.85rem, 1.7vw, 1.3rem);
  color: var(--sup-purple);
  background: var(--sup-paper);
  border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius-pill);
  box-shadow: var(--pop-sm);
}
.shop-lock:hover .shop-lock__cta { background: var(--sup-cream); }

/* With the wallet unread there is nothing to dress and nothing to page. */
.shop-stage.is-locked .stash,
.shop-stage.is-locked .shop-stage__empty { display: none; }

/* The rescan sits with the count, and what the shop is doing is said inside
   the drawer rather than in a card above it. */
.stash__rescan {
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--sup-ink);
  background: var(--sup-paper);
  border: 2px solid var(--sup-ink);
  border-radius: 50%;
  box-shadow: var(--pop-sm);
  cursor: pointer;
}
.stash__rescan:hover { background: var(--sup-yellow); }
.stash__rescan.is-busy .rescan__icon { animation: rescan-spin 0.9s linear infinite; }
.stash__status {
  margin: 0 0 0.7rem;
  text-align: center;
  font-size: 0.75rem;
  color: rgba(255, 255, 255, 0.9);
}
.stash__status:empty { display: none; }

/* --------------------------------------------------------------------------
   The boardwalk map

   A plan of the town that doubles as navigation. The artwork is a stack of
   layers positioned in its own coordinates inside .map__world, and one
   transform on that wrapper does all the panning and zooming -- so the
   landmarks ride along without any of them being repositioned in JS.
   -------------------------------------------------------------------------- */
.map-section { padding-bottom: 3rem; }
.map-section__head { margin-bottom: 1.4rem; }

.map {
  position: relative;
  margin: 0 auto;
  width: min(100% - 2rem, 1500px);
}

.map__frame {
  position: relative;
  overflow: hidden;
  /* Definite, and it has to be: with the width left auto, aspect-ratio reads
     min-height as the constrained side and grows the width to satisfy the
     ratio instead. On a 375px phone that made the frame 512px wide -- a 137px
     overflow that widened the layout viewport, which is what left the header,
     the sections and the footer sitting narrow with a band of page behind
     them. */
  width: 100%;
  /* The map covers this frame rather than fitting inside it, so the frame is
     free to be whatever shape suits the screen. Tall enough to stand in for a
     view down the street, capped so the page below stays in reach. */
  aspect-ratio: 16 / 10;
  max-height: 78vh;
  min-height: 320px;
  /* Shows only while the art loads or past its edges, so it is the drawing's
     own sky -- the Halloween night, sampled off the top row of the map. */
  background: #1D0A45;
  border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius-lg);
  box-shadow: var(--pop);
  -webkit-tap-highlight-color: transparent;
}

.map__world {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  will-change: transform;
}


.map__layer {
  position: absolute;
  display: block;
  user-select: none;
  -webkit-user-drag: none;
  /* The art is flat-shaded line work; smoothing it on the way up turns the
     outlines to mush, so let the browser keep the edges. */
  image-rendering: auto;
}

/* ---- landmarks ---- */
.spot {
  position: absolute;
  display: block;
  border-radius: 14px;
  /* No background of its own: the building underneath is the picture, and a
     tint over it would flatten the art. */
  z-index: 5;
}
/* Sizes divided by --map-k are constant on screen: the ring still traces the
   building at every zoom, but its stroke does not thin to a hair. */
.spot__ring {
  position: absolute;
  inset: calc(-10px / var(--map-k, 1));
  border: calc(5px / var(--map-k, 1)) solid transparent;
  border-radius: calc(18px / var(--map-k, 1));
  transition: border-color 140ms ease, background-color 140ms ease;
}
/* `is-lit` is set from the list above the map: pointing at a name there is
   the same as pointing at the building here. */
.spot:hover .spot__ring,
.spot:focus-visible .spot__ring,
.spot.is-lit .spot__ring {
  border-color: var(--sup-ink);
  background: rgba(255, 210, 63, 0.28);
}
.spot:focus-visible { outline: none; }
.spot:focus-visible .spot__ring { border-color: var(--sup-yellow); }

/* The label hangs above the landmark and is scaled back down by the inverse
   of nothing -- it lives in artwork coordinates, so it grows with the map.
   That is deliberate: zoomed out the whole town is legible at a glance, and
   zoomed in the labels stay in proportion to what they point at. */
.spot__tag {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 18px);
  transform: translate(-50%, 6px) scale(calc(1 / var(--map-k, 1)));
  transform-origin: 50% 100%;
  display: grid;
  gap: 2px;
  padding: 10px 16px;
  min-width: max-content;
  background: var(--sup-cream);
  border: 4px solid var(--sup-ink);
  border-radius: var(--radius);
  box-shadow: var(--pop-sm);
  opacity: 0;
  pointer-events: none;
  transition: opacity 140ms ease, transform 140ms ease;
}
.spot:hover .spot__tag,
.spot:focus-visible .spot__tag,
.spot.is-lit .spot__tag {
  opacity: 1;
  transform: translate(-50%, 0) scale(calc(1 / var(--map-k, 1)));
}

/* A pointer down to the building, so a label that sits clear of a crowded
   block still reads as belonging to one. */
.spot__tag::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  width: 14px;
  height: 14px;
  margin-left: -7px;
  margin-top: -8px;
  background: var(--sup-cream);
  border-right: 4px solid var(--sup-ink);
  border-bottom: 4px solid var(--sup-ink);
  transform: rotate(45deg);
}
/* Landmarks at the top of the town hang their name below instead, or the
   frame cuts it off. */
.spot--below .spot__tag {
  bottom: auto;
  top: calc(100% + 18px);
  transform-origin: 50% 0;
}
.spot--below .spot__tag::after {
  top: auto;
  bottom: 100%;
  margin-top: 0;
  margin-bottom: -8px;
  border-right: 0;
  border-bottom: 0;
  border-left: 4px solid var(--sup-ink);
  border-top: 4px solid var(--sup-ink);
}

.spot__name {
  font-family: var(--font-display);
  font-size: 1rem;
  line-height: 1.1;
  color: var(--sup-ink);
}
.spot__blurb { font-size: 0.8rem; line-height: 1.2; color: rgba(13, 13, 13, 0.72); }

/* ---- chrome ---- */


.map__status {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  margin: 0;
  padding: 1rem 1.4rem;
  max-width: 80%;
  text-align: center;
  color: var(--sup-ink);
  background: var(--sup-cream);
  border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius);
  box-shadow: var(--pop-sm);
  z-index: 12;
}

.map__loading {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  gap: 10px;
  z-index: 11;
}
.map[data-state="ready"] .map__loading,
.map[data-state="error"] .map__loading { display: none; }
.map__loading-dot {
  width: 14px;
  height: 14px;
  background: var(--sup-cream);
  border: 3px solid var(--sup-ink);
  border-radius: 50%;
  animation: map-bob 1s ease-in-out infinite;
}
.map__loading-dot:nth-child(2) { animation-delay: 0.15s; }
.map__loading-dot:nth-child(3) { animation-delay: 0.3s; }
@keyframes map-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-9px); }
}
@media (prefers-reduced-motion: reduce) {
  .map__loading-dot { animation: none; }
}

/* ---- the same places, as text, above the map ---- */
.map-section__index { margin-bottom: 1.8rem; }
.map-index__title {
  margin: 0 0 1rem;
  font-family: var(--font-display);
  font-size: clamp(1.2rem, 3vw, 1.7rem);
  color: var(--sup-cream);
}
.map-index {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 0.6rem 1.6rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.map-index li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.5rem 0;
  border-bottom: 2px solid rgba(255, 255, 255, 0.14);
}
.map-index a { font-weight: 900; color: var(--sup-yellow); text-decoration: none; }
.map-index a:hover { text-decoration: underline; }
.map-index span { font-size: 0.85rem; color: rgba(255, 255, 255, 0.66); }

@media (max-width: 700px) {
  .map { width: 100%; }
  .map__frame { border-radius: 0; border-left: 0; border-right: 0; box-shadow: none; max-height: 62vh; }
}

/* --------------------------------------------------------------------------
   The loading card, and the brand row a room closes on

   Both are the Bungalow's, brought across so the shop and the room open and
   close the same way. The card stands where the thing it is waiting for will
   appear, at the same size, so nothing jumps when it arrives.
   -------------------------------------------------------------------------- */
.app-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  /* The shop painting's own aspect, so the card is exactly the hole it is
     filling and nothing shifts when the shop replaces it. */
  aspect-ratio: 1649 / 954;
  min-height: 260px;
  padding: 1.5rem;
  position: relative;
  overflow: hidden;
  background: linear-gradient(90deg, #FDE047, #FB923C 55%, #EC4899);
  border: var(--line) solid var(--sup-ink);
  border-radius: var(--radius-lg);
  box-shadow: var(--pop);
  margin-bottom: 1.25rem;
}
/* The same spray-paint grain the Bungalow lays over its tie-dye surfaces. */
.app-loading::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.5 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
  opacity: 0.12;
  mix-blend-mode: overlay;
}
.app-loading__bolt {
  width: auto;
  height: clamp(3rem, 8vw, 6rem);
  margin-bottom: 1rem;
  filter: drop-shadow(3px 3px 0 rgba(0, 0, 0, 0.5));
  animation: app-bolt-bob 1s ease-in-out infinite;
}
@keyframes app-bolt-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-22%); }
}
.app-loading__title {
  margin: 0 0 0.6rem;
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 1rem + 2.4vw, 3rem);
  line-height: 1.1;
  color: var(--sup-paper);
  text-shadow: 3px 3px 0 rgba(0, 0, 0, 0.6);
}
.app-loading__note {
  margin: 0 0 1.4rem;
  font-weight: 700;
  color: rgba(255, 255, 255, 0.95);
  text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.5);
}
.app-loading__bar {
  width: min(16rem, 70%);
  height: 12px;
  overflow: hidden;
  background: var(--sup-paper);
  border: 2px solid var(--sup-ink);
  border-radius: var(--radius-pill);
}
.app-loading__bar span {
  display: block;
  width: 70%;
  height: 100%;
  background: linear-gradient(90deg, #FACC15, #F97316 50%, #EC4899);
  animation: app-bar-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes app-bar-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.5; }
}
@media (prefers-reduced-motion: reduce) {
  .app-loading__bolt,
  .app-loading__bar span { animation: none; }
}

/* No background of its own: it stands on whatever it is placed in, the way
   the Bungalow's does on the app's own ground. Giving it a fill made a band
   with a rule above it, which is a footer -- and this is not one. */
.brand-footer {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.25rem 1rem 0.25rem;
}
.brand-footer__logo {
  display: block;
  width: auto;
  height: clamp(3rem, 6vw, 4rem);
  filter: drop-shadow(2px 2px 0 rgba(0, 0, 0, 0.6));
}

/* Failed, it is a message rather than a progress card: no bolt, no bar, and
   the shop's own colours instead of the scanner's. */
.app-loading--failed {
  background: var(--sup-deep);
  aspect-ratio: auto;
  min-height: 0;
  padding: clamp(2rem, 6vw, 3.5rem) 1.5rem;
}
