@import url("https://fonts.googleapis.com/css2?family=Archivo:wdth,wght@62..125,400..900&display=swap");
/* =============================================
   Jaron Phillips — 2026 site styles
   Plain CSS, mobile-first, no build step.
   System fonts only (no font downloads = fast load).
   Edit the color variables below to re-theme the whole site.
============================================= */

:root {
  --bg: #0A0120;        /* violet-black ground */
  --bg-panel: #1B0A45;  /* raised panel */
  --bg-deep: #0A0120;   /* recessed / stage */
  --text: #EDE7FA;
  --text-dim: #9C8BC9;
  --accent: #C8F53C;    /* acid lime — the voltage. links, primary action */
  --accent-hi: #DCFF6B; /* lime, lifted */
  --accent2: #FF2ED6;   /* magenta — the spark. used sparingly, on action */
  --violet: #5E00E0;    /* brand violet, for fields and glows */
  --border: #34206B;
  --on-accent: #14061F; /* text that sits on lime */
  /* Scales with the viewport instead of a fixed cap. Floor of 960px
     keeps it from feeling cramped on smaller desktop windows; ceiling
     of 1320px matches common convention (Bootstrap's widest
     container, Tailwind's typical max-width tokens) — beyond that,
     more width doesn't help, it just spreads the layout thin.
     Actual paragraph text still gets its own tighter max-width
     wherever it appears (see .hero p.tagline, ~640px), since that's
     a readability rule, not a layout one. */
  --maxw: clamp(960px, 70vw, 1320px);
  --radius: 10px;
  font-size: 17px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  line-height: 1.55;
}

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

a { color: var(--accent); text-decoration: none; }
a:hover, a:focus { text-decoration: underline; }

h1, h2, h3 {
  line-height: 1.2;
  margin: 0 0 0.5em 0;
}

h1 { font-size: clamp(1.9rem, 5vw, 2.8rem); }
h2 { font-size: clamp(1.4rem, 4vw, 1.9rem); }
h3 { font-size: 1.15rem; }

p { margin: 0 0 1em 0; }

:root { --rail-pad: clamp(14px, 4vw, 60px); }

.wrap {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0 20px;
}

/* ---------- Header / Nav ---------- */

header.site-header {
  border-bottom: 1px solid var(--border);
  padding: 16px 0;
  /* The banner has to paint above the page, not just sit before it. The
     billboard is a positioned sibling further down the document, so with
     both on auto the billboard won, and the Art menu opened underneath
     it — present, sized, and completely invisible. */
  position: relative;
  z-index: 50;
}

/* The header runs the full width instead of sitting in the centred column,
   so the logo and the nav line up with the billboard copy and the first
   tile rather than starting a couple of hundred pixels further in. */
.site-header .wrap {
  max-width: none;
  width: 100%;
  padding-inline: var(--rail-pad);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

/* ---------- Header: nav on the left, tools on the right ---------- */

/* One row, always. The open search field is wide enough to push the
   tools onto a second line otherwise, which doubles the header height
   mid-interaction. The nav gives up the space instead. */
.site-header .wrap { flex-wrap: nowrap; }

.hdr-left {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2.4vw, 34px);
  flex: 0 1 auto;
  min-width: 0;
}

.hdr-right {
  display: flex;
  align-items: center;
  gap: clamp(10px, 1.4vw, 20px);
  flex: 0 0 auto;
  margin-left: auto;
}

.hdr-left nav.main-nav ul { flex-wrap: nowrap; }

/* Icons sit bare in the bar — no chips, no borders — until you use one. */
.hdr-icon {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text);
  cursor: pointer;
}

.hdr-icon svg { width: 22px; height: 22px; }
.hdr-icon:hover { color: var(--accent); }

/* The field has no width until it is opened, so the search reads as a
   single icon the rest of the time. Width animates, display cannot. */
.hdr-search {
  display: flex;
  align-items: center;
  border: 1px solid transparent;
  border-radius: 3px;
  background: transparent;
  transition: background .2s ease, border-color .2s ease;
}

.hdr-search.is-open {
  background: rgba(10, 1, 32, .9);
  border-color: rgba(237, 231, 250, .55);
}

.hdr-search input {
  width: 0;
  padding: 0;
  border: 0;
  outline: 0;
  background: none;
  color: var(--text);
  font: inherit;
  font-size: .92rem;
  transition: width .28s ease, padding .28s ease;
}

.hdr-search.is-open input {
  width: clamp(140px, 18vw, 220px);
  padding: 7px 12px 7px 2px;
}

.hdr-search input::placeholder { color: var(--text-dim); }

/* ---- the bear, worn as a profile ---- */

.hdr-profile { position: relative; }

.hdr-profile-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--text);
}

.hdr-avatar-img {
  width: 32px;
  height: 32px;
  border-radius: 4px;
  object-fit: cover;
  background: var(--violet);
  display: block;
}

.hdr-caret {
  width: 11px;
  height: 11px;
  transition: transform .2s ease;
}

.hdr-profile-btn[aria-expanded="true"] .hdr-caret { transform: rotate(180deg); }

.hdr-menu {
  position: absolute;
  top: calc(100% + 12px);
  right: 0;
  min-width: 190px;
  padding: 6px 0;
  background: rgba(10, 1, 32, .96);
  border: 1px solid rgba(237, 231, 250, .22);
  border-radius: 4px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .55);
  z-index: 60;
}

.hdr-menu[hidden] { display: none; }

.hdr-menu a,
.hdr-menu button {
  display: block;
  width: 100%;
  padding: 9px 16px;
  border: 0;
  background: none;
  text-align: left;
  color: var(--text);
  font: inherit;
  font-size: .9rem;
  cursor: pointer;
}

/* display:block on the items above would otherwise defeat [hidden] */
.hdr-menu [hidden] { display: none !important; }

/* ---- the profile panel ---- */

.hdr-menu { min-width: 260px; }

.pf-signin { padding: 14px 16px; }

.pf-note {
  margin: 10px 0 0;
  font-size: .76rem;
  line-height: 1.5;
  color: var(--text-dim);
}

.pf-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px 12px;
  border-bottom: 1px solid rgba(156, 139, 201, .20);
}

.pf-face {
  width: 44px;
  height: 44px;
  border-radius: 4px;
  object-fit: cover;
  background: var(--violet);
  flex: 0 0 auto;
}

.pf-id { min-width: 0; }

.pf-name {
  margin: 0;
  font-weight: 700;
  font-size: .95rem;
  color: var(--text);
}

.pf-mail {
  margin: 2px 0 0;
  font-size: .78rem;
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 200px;
}

.pf-edit {
  display: flex;
  gap: 8px;
  padding: 12px 16px;
  border-bottom: 1px solid rgba(156, 139, 201, .20);
}

.pf-edit input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 7px 10px;
  border-radius: 3px;
  border: 1px solid rgba(237, 231, 250, .35);
  background: rgba(10, 1, 32, .8);
  color: var(--text);
  font: inherit;
  font-size: .88rem;
}

.pf-save {
  padding: 7px 12px;
  border-radius: 3px;
  border: 0;
  background: var(--accent);
  color: var(--on-accent);
  font: inherit;
  font-size: .85rem;
  font-weight: 700;
  cursor: pointer;
}

.pf-msg {
  margin: 0;
  padding: 10px 16px 0;
  font-size: .8rem;
  color: var(--text-dim);
}

.pf-msg.is-bad { color: #ff8f8f; }

.pf-links { padding: 6px 0; }

.pf-links a,
.pf-links button {
  display: block;
  width: 100%;
  padding: 9px 16px;
  border: 0;
  background: none;
  text-align: left;
  color: var(--text);
  font: inherit;
  font-size: .9rem;
  cursor: pointer;
}

.pf-links a:hover,
.pf-links button:hover {
  background: var(--bg-panel);
  color: var(--accent);
  text-decoration: none;
}

.hdr-menu a:hover,
.hdr-menu button:hover {
  background: var(--bg-panel);
  color: var(--accent);
  text-decoration: none;
}

/* Two buttons side by side under the creator. */
.creator-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
}

.btn-ghost {
  background: transparent;
  color: var(--accent);
  border: 1.5px solid var(--accent);
}

.btn-ghost:hover { background: var(--accent); color: var(--on-accent); }
.btn-ghost[disabled] { opacity: .6; cursor: default; }

/* A tile filtered out by search leaves no gap behind it. */
.slide[hidden] { display: none !important; }

.rail-empty {
  padding: clamp(24px, 4vw, 48px) var(--rail-pad, clamp(14px, 4vw, 60px));
  color: var(--text-dim);
}

/* On a phone the banner used to wrap: six links broke onto a second row
   and pushed the search/cart/avatar cluster onto a third, which made the
   header 159px tall and overlapped the logo. Everything stays on one row
   now and the nav scrolls sideways instead of wrapping — the fade on its
   right edge is the hint that there is more of it. */
/* Nothing on this site is meant to scroll sideways except the rail and
   the nav, both of which are their own scroll containers. A stray pixel of
   overflow anywhere else drags the whole page across on a phone and cuts
   the copy off at the left edge, so the page itself is pinned. clip rather
   than hidden: hidden would turn <body> into a scroll container and break
   position:sticky inside it. */
html, body { overflow-x: clip; }

@media (max-width: 900px) {
  .site-header .wrap { flex-wrap: nowrap; gap: 10px; }
  .hdr-left { gap: 10px; min-width: 0; flex: 1 1 auto; }
  .hdr-left nav.main-nav {
    min-width: 0;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-mask-image: linear-gradient(90deg, #000 88%, transparent 100%);
            mask-image: linear-gradient(90deg, #000 88%, transparent 100%);
  }
  .hdr-left nav.main-nav::-webkit-scrollbar { display: none; }
  .hdr-left nav.main-nav ul { flex-wrap: nowrap; width: max-content; gap: 2px; }
  nav.main-nav a { padding: 6px 9px; font-size: 0.88rem; }
  .hdr-search.is-open input { width: 120px; }
}

.logo {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 700;
  font-size: 1.2rem;
  color: var(--text);
}

.logo img {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 2px solid var(--accent);
}

.logo:hover { text-decoration: none; color: var(--accent); }

nav.main-nav ul {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0;
  padding: 0;
}

nav.main-nav a {
  display: inline-block;
  padding: 8px 12px;
  color: var(--text);
  border-radius: 6px;
  font-size: 0.95rem;
}

nav.main-nav a:hover,
nav.main-nav a[aria-current="page"] {
  background: var(--bg-panel);
  color: var(--accent);
  text-decoration: none;
}


/* =============================================================
   One grouped nav item: Art, with Archive under it.

   The button is styled to match the plain links exactly, because
   from the outside it is one of them — the only tell is the
   caret. Nothing about it should read as a different kind of
   control until it opens.
============================================================= */
.nav-group { position: relative; }

.nav-top {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 8px 12px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 0.95rem;
  line-height: inherit;
  cursor: pointer;
}
.site-header .nav-top { color: #fff; }
.nav-top:hover,
.nav-group.is-open .nav-top,
.nav-group.is-current .nav-top {
  background: var(--bg-panel);
  color: var(--accent);
}
.nav-caret {
  width: 11px; height: 11px;
  transition: transform 0.18s ease;
}
.nav-group.is-open .nav-caret { transform: rotate(180deg); }

/* The panel is absolutely positioned so opening it never changes the
   height of the banner — on a phone the banner is one row and has to
   stay one row. */
.nav-sub {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 60;
  min-width: 150px;
  margin: 0;
  padding: 5px;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 1px;
  border: 1.5px solid rgba(200, 245, 60, 0.35);
  border-radius: 5px;
  background: #140537;
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.55);
}
.nav-sub[hidden] { display: none; }
.nav-sub a {
  display: block;
  padding: 8px 12px;
  border-radius: 3px;
  white-space: nowrap;
  color: #fff;
  font-size: 0.92rem;
}
.nav-sub a:hover,
.nav-sub a[aria-current="page"] {
  background: var(--bg-panel);
  color: var(--accent);
  text-decoration: none;
}


/* The editor links in the banner menu. Only Jay ever sees these, so they
   are set apart rather than mixed in with the links everyone gets —
   a different thing, not another item on the same list. */
.pf-admin {
  margin: 0 0 10px;
  padding: 0 0 10px;
  border-bottom: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.pf-admin-h {
  margin: 0 0 4px;
  padding: 0 10px;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}
.pf-admin a {
  display: block;
  padding: 7px 10px;
  border-radius: 3px;
  color: var(--text);
  font-size: 0.9rem;
  text-decoration: none;
}
.pf-admin a:hover {
  background: var(--bg-panel);
  color: var(--accent);
}

/* The nav is a sideways scroller on a phone, and a panel inside a
   scroller gets clipped by it. Letting the overflow show while one is
   open costs the scroll for those few seconds and keeps the menu whole. */
@media (max-width: 900px) {
  /* The nav is a sideways scroller here, with a fade on its right edge.
     Both clip an absolutely positioned panel — and the mask clips it even
     with overflow visible, which is what made the menu open into nothing
     on a phone while working on a desktop. Both stand down while a group
     is open; the scroll comes back when it closes. */
  .hdr-left nav.main-nav:has(.nav-group.is-open) {
    overflow: visible;
    -webkit-mask-image: none;
            mask-image: none;
  }
  .nav-top { padding: 6px 9px; font-size: 0.88rem; }
}

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

.hero {
  padding: 60px 0 40px 0;
  text-align: center;
}

.hero img.mascot {
  width: 140px;
  margin: 0 auto 20px auto;
  border-radius: 50%;
  border: 3px solid var(--accent);
}

.hero p.tagline {
  font-size: 1.15rem;
  color: var(--text-dim);
  max-width: 640px;
  margin: 0 auto 28px auto;
}

/* ---------- Buttons ---------- */

.btn {
  display: inline-block;
  padding: 12px 22px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 700;
  border: none;
  cursor: pointer;
  font-size: 1rem;
}

.btn:hover { background: var(--accent2); color: #fff; text-decoration: none; }

.btn.ghost {
  background: transparent;
  border: 2px solid var(--accent);
  color: var(--accent);
}

.btn.ghost:hover { background: rgba(200,245,60,0.12); }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
}

/* ---------- Door / card grid (home page) ---------- */

.doors {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 18px;
  margin: 40px 0;
}

.door {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 24px;
  text-align: left;
  color: var(--text);
}

.door:hover { border-color: var(--accent); text-decoration: none; }

.door h3 { color: var(--accent); margin-bottom: 8px; }
.door p { color: var(--text-dim); margin-bottom: 0; font-size: 0.95rem; }

/* ---------- Gallery grid (art page) ---------- */

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 18px;
  margin: 30px 0;
}

.gallery figure {
  margin: 0;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.gallery img { width: 100%; }

.gallery figcaption {
  padding: 12px 14px;
}

.gallery figcaption .title {
  font-weight: 700;
  color: var(--accent);
  display: block;
  margin-bottom: 4px;
}

.gallery figcaption p {
  color: var(--text-dim);
  font-size: 0.9rem;
  margin: 0;
}

/* ---------- Sections / panels ---------- */

section { padding: 40px 0; }

section.alt {
  background: var(--bg-panel);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

.panel {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 24px;
  margin-bottom: 20px;
}

/* ---------- Services / pricing list ---------- */

.service {
  border-bottom: 1px solid var(--border);
  padding: 20px 0;
}

.service:last-child { border-bottom: none; }

.service .price {
  color: var(--accent2);
  font-weight: 700;
  font-size: 1.05rem;
}

.service h3 { margin-bottom: 4px; }
.service p { color: var(--text-dim); margin-bottom: 0; }

/* ---------- Client / proof strip ---------- */

.clients {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 20px 0;
}

.clients span {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 8px 16px;
  font-size: 0.9rem;
  color: var(--text-dim);
}

/* ---------- Footer ---------- */

footer.site-footer {
  border-top: 1px solid var(--border);
  padding: 30px 0;
  text-align: center;
  color: var(--text-dim);
  font-size: 0.9rem;
}

footer.site-footer a { color: var(--text-dim); }

/* ---------- Play / character creator page ---------- */

/* Outer column that holds the Randomize button, the colored panel,
   and the name/save controls, all centered. The button/input are
   capped narrow; the panel below gets its own wider cap (see .creator). */
.creator-shell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}

.creator-shell > .btn,
.creator-shell > .field-row {
  width: 100%;
  max-width: 320px;
}

/* Shared "label prefix inside a bordered box" look for Name and
   Instagram, so they read as one consistent pair of fields instead of
   two different styles (used to be one bare <input> plus one
   separately-styled Instagram row — now both are a <label class=
   "field-row"> wrapping a prefix + a reset-to-plain <input>).
   Clicking anywhere in the row focuses the input, same as clicking a
   normal input's own padding would, since the row itself is the
   <label>, not just decoration sitting next to one. */
.field-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  cursor: text;
}

.field-prefix {
  color: var(--accent);
  font-weight: 700;
  flex-shrink: 0;
}

.instagram-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: var(--accent);
}

.field-row input {
  flex: 1;
  min-width: 0;
  border: none;
  background: none;
  padding: 0;
  color: var(--text);
  font-size: 1rem;
}

.field-row input:focus { outline: none; }

/* Background + Fur pickers flank Randomize above the panel — they
   restyle the whole character rather than one part of it, so they
   don't belong in the left/right columns with everything else.

   This is the exact same grid as .creator below (auto | 1fr | auto,
   same gap, same padding, same breakpoint) rather than a separately
   tuned flex row — two independent layouts can be nudged to roughly
   line up by eye, but they drift the moment content changes (that's
   what happened here: this used to be hand-tuned to the old design
   and quietly stopped matching once the panel's own width/padding
   changed). Using the identical grid means bg/fur mathematically
   land in the same column as the picker directly below them, always,
   with no manual re-tuning. .trait-slot's own min-width (see below)
   is what makes that hold even though bg/fur have no label and the
   panel's slots do — without it, this row's "auto" column would size
   to the bare 60/92px icon instead of matching the wider column the
   labeled slots produce underneath it. */
.creator-top {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  justify-items: center;
  gap: 10px 4px;
  width: 100%;
  padding: 18px 6px;
  margin: 0 auto 5px auto;
  position: relative;
  z-index: 2;
}

@media (min-width: 1100px) {
  .creator-top {
    width: 828px;
    max-width: 100%;
    padding: 32px;
    gap: 24px 28px;
  }
}

@media (max-width: 1099px) {
  /* Background/Fur are now compact ~56px icons (see .trait-slot),
     not full label+strip blocks, so Randomize is the only thing wide
     enough to risk overflowing the row — trimming it down keeps
     bg / Randomize / fur comfortably fitting on real phones. */
  #randomize-btn {
    padding: 10px 16px;
    font-size: 0.9rem;
  }
}

/* Outlined in accent yellow with a black fill, instead of the solid
   .btn look — reads as a distinct "action" against the two pickers
   sitting beside it. */
#randomize-btn {
  background: var(--bg-deep);
  border: 2px solid var(--accent);
  color: var(--accent);
}

#randomize-btn:hover { background: var(--violet); }

/* The colored card itself — background-image is set live by
   js/play.js to match the selected "bg" trait, background-color is
   just the pre-JS fallback so there's no flash of nothing.

   Same 3-column CSS grid — auto | 1fr | auto — at every breakpoint
   now: the left/right trait-slot icons (see .trait-slot below) hug
   the edges, the stage fills whatever's left in the middle. Only the
   sizes change at 1100px (bigger icons, more padding, a wider fixed
   panel width) — see the media query below, and slotThumbSize() /
   popoverTierSizes() in js/play.js for the matching pixel values.
   Desktop's width is exact-math'd the same way this page always has:

     width  = padding, both sides                (32 * 2 =  64)
            + 2 slot columns (~label-width wide) (104 * 2 = 208)
            + 2 column gaps                       (28 * 2 =  56)
            + the stage                                   (500)
            = 828px (the columns are "auto" width, sized to their
              actual content by the browser — 104 is just the
              intended target via .trait-slot-label's max-width at
              that breakpoint, not a hard-coded column width)

   Desktop layout doesn't kick in until 1100px (a holdover from when
   this was a much wider flex layout — still a reasonable point to
   switch since a narrower window has no real room for flanking
   columns next to a 500px stage either way). */
.creator {
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-rows: repeat(3, 1fr);
  align-items: center;
  justify-items: center;
  gap: 10px 4px;
  width: 100%;
  border-radius: 20px;
  padding: 18px 6px;
  background-color: var(--bg-panel);
  background-size: cover;
  background-position: center;
}

.creator-preview {
  display: flex;
  justify-content: center;
  width: 100%;
  max-width: 500px;
  grid-column: 2;
  grid-row: 1 / span 3;
}

@media (min-width: 1100px) {
  .creator {
    width: 828px;
    max-width: 100%;
    padding: 32px;
    gap: 24px 28px;
    margin: 0 auto;
  }
}

.creator-preview .stage {
  width: 100%;
  max-width: 500px;
  aspect-ratio: 1 / 1;
  position: relative;
}

.creator-preview .stage img.layer {
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
  object-fit: contain;
}

.trait-groups {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

/* display:contents drops each wrapper div out of the grid (its
   children become direct grid items of .creator instead), then each
   individual .trait-slot gets placed at an explicit cell — column 1
   or 3, row matching its position in the group (1st/2nd/3rd child =
   top/middle/bottom). Same at every breakpoint now, since both use
   the same grid — only .trait-slot's own size changes (see the
   @media(min-width:1100px) override below it). */
#trait-groups-left,
#trait-groups-right {
  display: contents;
}
#trait-groups-left .trait-slot:nth-child(1) { grid-column: 1; grid-row: 1; }
#trait-groups-left .trait-slot:nth-child(2) { grid-column: 1; grid-row: 2; }
#trait-groups-left .trait-slot:nth-child(3) { grid-column: 1; grid-row: 3; }
#trait-groups-right .trait-slot:nth-child(1) { grid-column: 3; grid-row: 1; }
#trait-groups-right .trait-slot:nth-child(2) { grid-column: 3; grid-row: 2; }
#trait-groups-right .trait-slot:nth-child(3) { grid-column: 3; grid-row: 3; }

/* ---------- Trait picker: compact slot + popover ---------- */
/* A single icon showing just the current selection for one category.
   Tapping opens a small popover (.trait-popover further down) to
   swipe through the rest. Built by buildTraitSlot() in js/play.js —
   same component at every breakpoint, just bigger past 1100px (see
   the size override below and slotThumbSize() in js/play.js, which
   has to stay in sync with whatever .trait-slot-thumb's width/height
   actually is here). */
.trait-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  /* Matches .trait-slot-label's max-width below — see the long
     comment on .creator-top above for why this matters even for
     Background/Fur, which don't render a label at all. */
  min-width: 64px;
}

.trait-slot-thumb {
  display: block;
  width: 60px;
  height: 60px;
  border-radius: 10px;
  background-color: var(--bg-deep);
  background-repeat: no-repeat;
  border: 2px solid var(--accent);
}

.trait-slot-label {
  font-size: 0.7rem;
  font-weight: 700;
  color: var(--on-accent);
  max-width: 64px;
  text-align: center;
  line-height: 1.15;
}

@media (min-width: 1100px) {
  .trait-slot { min-width: 104px; }
  .trait-slot-thumb { width: 92px; height: 92px; border-radius: 14px; }
  .trait-slot-label { font-size: 0.85rem; max-width: 104px; }
}

/* Reused by the popover's tiered thumbnails below — sizing comes from
   .tier-0/1/2 there, this is just the shared look (border, selected
   state, background). */
.thumb {
  width: 68px;
  height: 68px;
  padding: 0;
  border-radius: 8px;
  overflow: hidden;
  background: var(--bg-deep);
  border: 2px solid transparent;
  cursor: pointer;
  flex-shrink: 0;
  scroll-snap-align: center;
  transition: border-color 0.1s ease, transform 0.1s ease, width 0.15s ease, height 0.15s ease, opacity 0.15s ease;
}

.thumb:hover { border-color: var(--border); transform: translateY(-1px); }

.thumb.selected {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(255, 181, 46, 0.25);
}

/* ---------- Trait popover ---------- */
/* Opens when a .trait-slot is tapped (see openPopover() in
   js/play.js). Shows 5 thumbnails at once — current selection at
   tier-0, two either side at tier-1, then tier-2 — sized/dimmed down
   with distance, while still being a fully swipeable strip through
   every option (tier is just recalculated after each swipe settles).
   Fixed + centered horizontally so it can never spill off-screen
   regardless of which slot triggered it. Bigger past 1100px, matching
   .trait-slot's own size bump above — see popoverTierSizes() in
   js/play.js for the pixel values these have to stay in sync with. */
.trait-popover {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  z-index: 300;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  max-width: 92vw;
  /* Light gold, not the dark panel color — the popover can open over
     any selected background color (including dark ones), and dark
     thumbs on a dark popover on a dark bg was unreadable. */
  background: #F2FFD2;
  border: 2px solid var(--accent);
  border-radius: 16px;
  padding: 16px 14px 12px;
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.55);
}

.trait-popover-close {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 2px solid var(--accent);
  background: var(--bg-deep);
  color: var(--accent);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}

.trait-popover-label {
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--on-accent); /* dark text now that the popover background is light */
  padding-right: 20px; /* clears the close button */
}

.trait-popover-track-wrap { position: relative; }

/* A flex row inside align-items:center column parent has nothing to
   bound its width — left unset it just grows to fit every option at
   once, and overflow-x:auto never actually has anything to scroll
   (the same "nothing to measure against" trap .creator-preview hit
   earlier on this page). An explicit width, sized to fit exactly the
   5 visible tiers + side padding for the arrows, is what makes both
   the clipping and the swipe/scroll actually engage. */
.trait-popover-track {
  display: flex;
  align-items: center;
  gap: 8px;
  width: min(376px, 82vw);
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  padding: 6px 30px; /* room for the overlaid arrows either side */
}

.trait-popover-track::-webkit-scrollbar { display: none; }

.trait-popover-track .thumb.tier-0 { width: 76px; height: 76px; opacity: 1; }
.trait-popover-track .thumb.tier-1 { width: 58px; height: 58px; opacity: 0.7; }
.trait-popover-track .thumb.tier-2 { width: 46px; height: 46px; opacity: 0.4; }

.trait-popover-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: none;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  z-index: 2;
}

.trait-popover-arrow.prev { left: -2px; }
.trait-popover-arrow.next { right: -2px; }

/* Bigger popover at the desktop breakpoint, matching .trait-slot's
   own size bump — tier sizes here have to stay in sync with
   popoverTierSizes() in js/play.js (crop math is computed per-tier
   from that array, not read back from CSS). */
@media (min-width: 1100px) {
  .trait-popover {
    gap: 10px;
    padding: 20px 18px 14px;
  }
  .trait-popover-close { width: 34px; height: 34px; font-size: 1.3rem; }
  .trait-popover-label { font-size: 1rem; }
  .trait-popover-track {
    width: 580px;
    max-width: 80vw;
    gap: 10px;
    padding: 8px 40px;
  }
  .trait-popover-track .thumb.tier-0 { width: 126px; height: 126px; }
  .trait-popover-track .thumb.tier-1 { width: 96px; height: 96px; }
  .trait-popover-track .thumb.tier-2 { width: 72px; height: 72px; }
  .trait-popover-arrow { width: 40px; height: 40px; font-size: 1.5rem; }
  .trait-popover-arrow.prev { left: -4px; }
  .trait-popover-arrow.next { right: -4px; }
}

.saved-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 14px;
  margin-top: 16px;
}

/* Not scoped to .saved-gallery — Top 3 (below) reuses this same
   card look outside the gallery grid, via buildCharacterCard() in
   js/play.js. */
.card {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px;
  text-align: center;
}

.card p {
  margin: 8px 0 0 0;
  font-size: 0.85rem;
  color: var(--text-dim);
}

/* Optional credit link — only rendered at all if a character was
   saved with a handle attached (see buildCharacterCard in
   js/play.js). Sits between the name and the like button, in Top 3
   cards too since they share the same card markup. */
.instagram-credit {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  margin-top: 4px;
  color: var(--accent2);
  font-size: 0.8rem;
  font-weight: 600;
}

.instagram-credit:hover { text-decoration: underline; }

.instagram-credit svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

/* Big preview of any saved character (main gallery or Top 3) — the
   real behavior is split by (hover:) media query below, since "hover
   to preview, no close button needed" and "tap to preview, needs a
   close button" are genuinely different interactions, not the same
   one adapted. This base rule just covers what's shared: hidden by
   default, sits on top of everything, fixed to the viewport so it's
   never clipped near the edge of the grid regardless of where the
   card lives. */
.card-preview {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  z-index: 200;
  transition: opacity 0.15s ease;
}

/* The sized square box — image, name, and location/date all live on
   top of each other inside this, not on top of each other in the
   image file itself. The saved JPEG (what you'd hand to a
   print-on-demand API later) never has text drawn into it; this is
   purely a viewer overlay. */
.card-preview-frame {
  position: relative;
  border: 2px solid var(--accent);
  border-radius: var(--radius);
  background: var(--bg-panel);
  overflow: hidden;
}

.card-preview-frame img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* cover, not contain — the saved image is always a square JPEG and
     this frame is always square too, so there's no real cropping
     either way, but contain leaves a hairline gap at one edge if the
     two squares don't land on exactly the same sub-pixel size (which
     showed up as a thin sliver of the frame's own background peeking
     through at the top). cover always fills the box completely. */
  object-fit: cover;
}

/* Scoped to .card-preview-frame (not just the bare class) on purpose:
   both of these are <p> elements inside .card, and .card p (below,
   under "Not scoped to .saved-gallery") sets its own color at
   specificity (0,1,1) — one class + one element. A bare
   ".card-preview-name { color: #000 }" is only (0,1,0), which loses
   to that regardless of source order, so the color silently fell
   back to .card p's dim gray no matter what it said here. Two
   classes — (0,2,0) — unambiguously outranks it. */
.card-preview-frame .card-preview-name,
.card-preview-frame .card-preview-meta {
  position: absolute;
  left: 0;
  right: 0;
  margin: 0;
  padding: 10px 14px;
}

/* Black text with a light halo instead of white-on-a-dark-gradient —
   no gradient bar needed behind it, which was the simplest fix for a
   persistent visual glitch right at the top edge of the preview that
   a couple of other approaches didn't fully clear up. */
.card-preview-frame .card-preview-name {
  top: 0;
  font-weight: 700;
  font-size: 1.1rem;
  text-align: left;
  color: #000;
}

.card-preview-frame .card-preview-meta {
  bottom: 0;
  font-size: 0.8rem;
  text-align: right;
  color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
  background: linear-gradient(to top, rgba(0, 0, 0, 0.6), transparent);
}

/* Real mouse + hover support (desktop/laptop): the original
   float-on-hover behavior, no backdrop, no close button — moving the
   mouse off the card is the "close." */
@media (hover: hover) and (pointer: fine) {
  .card-preview {
    background: transparent;
    transform: scale(0.94);
    transition: opacity 0.15s ease, transform 0.15s ease;
  }
  .card-preview-frame {
    width: 500px;
    height: 500px;
    max-width: 90vw;
    max-height: 90vw;
    /* .card-preview itself is fully transparent by design (float on
       hover, no dark modal) — but that means whatever's on the page
       directly behind the frame's edges (at whatever the current
       scroll position happens to be — often the live character stage
       up top) shows through right up against the border, which reads
       as a stray sliver of "wrong" content rather than a clean card.
       This large var(--bg)-colored glow fades the frame smoothly into
       the page's own background color for ~70px around it, so nothing
       else can show up touching the border, without the preview
       turning into an actual solid backdrop. */
    box-shadow: 0 0 45px 35px var(--bg), 0 20px 50px rgba(0, 0, 0, 0.6);
  }
  .card-preview-close { display: none; }
  .card:hover .card-preview {
    opacity: 1;
    transform: scale(1);
  }
}

/* Touch (no real hover): tapping the thumbnail toggles a
   .preview-open class on the card (see js/play.js) instead of
   relying on :hover, and shows a dark full-screen backdrop + close
   button since there's no "move the mouse away" equivalent on a
   phone. */
@media (hover: none), (pointer: coarse) {
  .card-preview {
    background: rgba(10, 10, 14, 0.88);
  }
  .card-preview-frame {
    width: min(500px, 90vw);
    height: min(500px, 90vw);
  }
  .card.preview-open .card-preview {
    opacity: 1;
    pointer-events: auto;
  }
  .card-preview-close {
    position: absolute;
    top: 20px;
    right: 20px;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: 2px solid var(--accent);
    background: var(--bg-deep);
    color: var(--accent);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
  }
}

/* ---------- Top 3 ---------- */

.top-three-heading {
  margin-bottom: 14px;
}

.top-three {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 20px;
  margin-bottom: 30px;
}

/* Plain 1st/2nd/3rd left to right, same order they come back from
   the API (ranked by likes) — no podium stagger. */
.top-three .card {
  position: relative;
  width: 150px;
  border-width: 2px;
}

.top-card.gold {
  border-color: #ffd700;
  box-shadow: 0 0 0 1px #ffd700, 0 0 18px rgba(255, 215, 0, 0.35);
}

.top-card.silver {
  border-color: #c8ced6;
  box-shadow: 0 0 0 1px #c8ced6, 0 0 14px rgba(200, 206, 214, 0.3);
}

.top-card.bronze {
  border-color: #cd7f32;
  box-shadow: 0 0 0 1px #cd7f32, 0 0 14px rgba(205, 127, 50, 0.3);
}

.rank-badge {
  position: absolute;
  top: -14px;
  left: -10px;
  font-size: 1.7rem;
  line-height: 1;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.5));
}

.like-btn {
  margin-top: 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 6px 12px;
  font-size: 0.9rem;
  color: var(--text);
  cursor: pointer;
}

.like-btn:hover { border-color: var(--accent); }
.like-btn.liked { border-color: var(--accent2); }
.like-btn:disabled { opacity: 0.6; cursor: default; }

/* ---------- Utility ---------- */

.center { text-align: center; }
.muted { color: var(--text-dim); }
.small { font-size: 0.85rem; }

/* Visually hidden but still readable by screen readers and search
   engines — use this instead of deleting a heading outright when you
   just want to reclaim the visual space it was taking up. */
.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;
}


/* =============================================
   Brand layer — The Artistic Engineer
   Violet field, acid-lime voltage, magenta on action.
   Display face is Archivo (variable width/weight) to match the
   condensed heavy lettering on the posters. Body stays system.
============================================= */

h1, h2, .logo, .gallery figcaption .title, .top-three-heading {
  font-family: "Archivo", "Arial Narrow", -apple-system, "Segoe UI", sans-serif;
  font-variation-settings: "wdth" 72, "wght" 800;
  text-transform: uppercase;
  letter-spacing: .01em;
  line-height: .95;
}

h1 { font-variation-settings: "wdth" 66, "wght" 900; }

/* The header sits on a violet field so the lime has something to burn
   against; the rest of the page stays on the near-black ground. */
header.site-header {
  background: linear-gradient(170deg, var(--violet) 0%, #3B0C9E 55%, var(--bg) 100%);
  border-bottom: 1px solid rgba(200, 245, 60, .38);
}
.site-header nav.main-nav a { color: #fff; }
.site-header nav.main-nav a:hover,
.site-header nav.main-nav a[aria-current="page"] {
  background: rgba(10, 1, 32, .45);
  color: var(--accent);
}

/* Logo is two inlined SVGs: the monogram, and the wordmark. */
.logo { color: #fff; }
.logo:hover { color: var(--accent); }

/* Magenta earns its keep on the one irreversible action. */
#save-btn { background: var(--accent2); color: #fff; }
#save-btn:hover { background: #FF5FE0; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
::selection { background: var(--accent); color: var(--on-accent); }






/* =============================================================
   Simulated poster field (js/nebula.js). Canvas behind the page.
============================================================= */
#jp-neb{
  position:fixed; inset:0;
  width:100%; height:100%;
  z-index:0; pointer-events:none;
  display:block;
}
body > header, body > section, body > footer{ position:relative; z-index:auto; }
.creator, .saved-gallery, .top-three{ position:relative; z-index:1; }

/* The banner paints its own violet field instead of going transparent and
   letting the nebula canvas show through. Only the home page loads that
   canvas, so transparent meant identical markup rendered violet on one
   page and flat near-black on every other — and the banner is the one
   thing that has to look the same everywhere. The gradient matches what
   the canvas was painting behind it, so the home page doesn't change. */
header.site-header{
  background:linear-gradient(170deg, var(--violet) 0%, #3B0C9E 55%, var(--bg) 100%);
  border-bottom:1px solid rgba(200,245,60,.28);
}


/* =============================================================
   Header lockup — monogram + wordmark, both inline SVG so they
   can be coloured and blended from CSS.
============================================================= */
.logo{
  display:flex;
  align-items:center;
  gap:clamp(8px,1vw,13px);
  text-decoration:none;
  line-height:0;
}
.logo:hover{ text-decoration:none; }

/* The monogram carries the stroke weight from the source file, so it
   keeps the thickness drawn into it rather than the hairline outline. */
.logo-icon{
  height:clamp(48px,6.5vw,68px);
  width:auto; flex:none; display:block;
  overflow:visible;
}
.logo-icon{ mix-blend-mode:exclusion; }
.logo-icon path{
  fill:#fff;
  stroke:#fff;
  stroke-width:7;
  stroke-miterlimit:10;
}

/* White type on exclusion: it inverts whatever sits behind it, which is
   the same treatment the poster lettering uses. Over the violet field
   that resolves to lime on its own. */

@media (max-width:520px){
  .logo-word{ display:none; }   /* monogram alone at phone width */
}


/* The lockup is large now, so the header needs room and the nav has to
   stop fighting it for the same line on narrow screens. */
header.site-header{ padding:10px 0; }
.site-header .wrap{ gap:18px; }
@media (max-width:900px){
  .logo-icon{ height:clamp(32px,5.5vw,45px); }
}


@media (max-width:900px){ .logo-word{ height:clamp(18px,3.1vw,25px); } }
@media (max-width:520px){ .logo-word{ display:none; } }


/* The wordmark: Jaron's outlined SVG, inlined so the fill is CSS's to set
   and there's no second request. 13 paths, no text node, no font. */
.logo-word{
  display:block; flex:none;
  height:clamp(27px,3.7vw,38px);
  width:auto;
  mix-blend-mode:exclusion;
  overflow:visible;
}
.logo-word path{ fill:#fff; }
@media (max-width:900px){ .logo-word{ height:clamp(18px,3.1vw,25px); } }
@media (max-width:520px){ .logo-word{ display:none; } }


/* ---------- Legal pages ---------- */

.legal {
  padding: clamp(36px, 6vw, 80px) 0 clamp(50px, 8vw, 110px);
}

.legal .wrap { max-width: 760px; }

.legal h1 {
  margin: 0 0 6px;
  font-size: clamp(2.2rem, 6vw, 3.4rem);
}

.legal h2 {
  margin: clamp(30px, 4vw, 46px) 0 10px;
  font-size: clamp(1.1rem, 2.2vw, 1.4rem);
  color: var(--accent);
}

.legal p,
.legal li {
  color: #C9BCEA;
  font-size: 1rem;
  line-height: 1.7;
}

.legal p { margin: 0 0 1em; }

.legal ul {
  margin: 0 0 1em;
  padding-left: 1.2em;
}

.legal li { margin-bottom: .45em; }

.legal a { color: var(--accent); }

.legal-date {
  color: var(--text-dim) !important;
  font-size: .86rem !important;
  margin-bottom: clamp(24px, 4vw, 38px) !important;
}

.legal-contact {
  margin-top: clamp(34px, 5vw, 54px) !important;
  padding-top: 20px;
  border-top: 1px solid rgba(156, 139, 201, .22);
}


/* ---------- Deleting your own bears ---------- */

.card.is-mine { position: relative; }

/* Sits on the card corner, only on bears the viewer owns. */
.card-del {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 5;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  padding: 0;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .35);
  background: rgba(10, 1, 32, .78);
  color: #fff;
  cursor: pointer;
  opacity: 0;
  transition: opacity .18s ease, background .18s ease, border-color .18s ease;
}

.card-del svg { width: 12px; height: 12px; }

/* Revealed on hover so it doesn't clutter the gallery, but always
   present for keyboard and touch — opacity, not display. */
.card.is-mine:hover .card-del,
.card-del:focus-visible { opacity: 1; }

@media (hover: none) {
  .card-del { opacity: 1; }
}

.card-del:hover,
.card-del:focus-visible {
  background: #ff4d4d;
  border-color: #ff4d4d;
}

.btn-danger {
  color: #ff8f8f;
  border-color: rgba(255, 143, 143, .6);
}

.btn-danger:hover {
  background: #ff4d4d;
  border-color: #ff4d4d;
  color: #fff;
}

#save-btn[disabled] { opacity: .5; cursor: not-allowed; }

#save-signin-note { margin-top: 8px; }


/* ---------- Cart ---------- */

.hdr-cart {
  position: relative;
  text-decoration: none;
}

.hdr-cart:hover { color: var(--accent); }

/* Ready for a count once there is a basket to count. Stays hidden until
   something sets it, so it can't show a stale or meaningless zero. */
.hdr-cart-count {
  position: absolute;
  top: -2px;
  right: -4px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}

.hdr-cart-count[hidden] { display: none; }
