/* ============================================================
   LEGO WORLD
   Everything is measured in studs. --stud is the pitch of one
   knob; every gap, radius and pad is a multiple of it.
   ============================================================ */

:root {
  /* ---- the unit ---- */
  --stud: 0.5rem;
  --s1: var(--stud);
  --s2: calc(var(--stud) * 2);
  --s3: calc(var(--stud) * 3);
  --s4: calc(var(--stud) * 4);
  --s5: calc(var(--stud) * 5);
  --s6: calc(var(--stud) * 6);
  --s7: calc(var(--stud) * 7);
  --s8: calc(var(--stud) * 8);
  --s10: calc(var(--stud) * 10);
  --s12: calc(var(--stud) * 12);
  --s16: calc(var(--stud) * 16);

  /* ---- baseplate (dark, faintly blue: shadow on grey plastic) ---- */
  --bg:        oklch(0.17 0.016 265);
  --bg-deep:   oklch(0.13 0.014 265);
  --surface:   oklch(0.23 0.020 265);
  --surface-2: oklch(0.28 0.022 265);
  --edge:      oklch(0.36 0.024 265);

  --ink:       oklch(0.97 0.008 265);
  --muted:     oklch(0.76 0.018 265);   /* 7.3:1 on --bg */

  /* ---- moulded ABS, straight off the sprue ---- */
  --red:    oklch(0.56 0.213 27);
  --yellow: oklch(0.855 0.174 92);
  --blue:   oklch(0.53 0.170 253);
  --green:  oklch(0.60 0.165 148);

  --red-dark:    oklch(0.44 0.185 27);
  --yellow-dark: oklch(0.70 0.155 88);
  --blue-dark:   oklch(0.41 0.145 253);
  --green-dark:  oklch(0.47 0.140 148);

  --danger: oklch(0.68 0.180 30);

  /* ink for anything inside the editor panel. The panel sits on a dark
     night scene, so this is the light end of the ramp. */
  --plate-ink:   oklch(0.98 0.006 80);
  --plate-muted: oklch(0.80 0.020 80);
  --plate-line:  oklch(1 0 0 / 0.14);

  /* ---- type ---- */
  /* One family, carried on weight contrast: Baloo 2's rounded terminals
     read as moulded plastic at every size, from 800 headings down to
     400 body. */
  --display: "Baloo 2", "Trebuchet MS", ui-sans-serif, system-ui, sans-serif;
  --body: "Baloo 2", "Trebuchet MS", ui-sans-serif, system-ui, sans-serif;

  /* ---- depth ---- */
  --lift-1: 0 1px 0 oklch(1 0 0 / 0.06) inset, 0 2px 6px oklch(0 0 0 / 0.35);
  --lift-2: 0 1px 0 oklch(1 0 0 / 0.07) inset, 0 10px 30px oklch(0 0 0 / 0.45);

  /* ---- z scale ---- */
  --z-plate: 0;
  --z-wash: 1;
  --z-content: 2;
  --z-nav: 40;
  --z-toast: 60;

  /* ---- motion ---- */
  --out: cubic-bezier(0.22, 1, 0.36, 1);      /* quint-ish */
  --snap: cubic-bezier(0.16, 1.02, 0.3, 1);
}

/* ============================================================
   base
   ============================================================ */

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

/* Class rules below set display: grid/flex on elements that are also
   toggled with [hidden]. Without this the attribute loses and every
   state renders at once. */
[hidden] { display: none !important; }

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* the nav is fixed — anchor targets must clear it or the heading
   lands underneath the bar */
:target, section[id] { scroll-margin-top: 6.5rem; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--body);
  font-size: clamp(1rem, 0.96rem + 0.2vw, 1.0625rem);
  line-height: 1.65;                 /* light-on-dark wants the extra */
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1, h2, h3 {
  font-family: var(--display);
  font-weight: 800;
  line-height: 1.08;
  letter-spacing: -0.01em;
  text-wrap: balance;
  margin: 0;
}

p { margin: 0; text-wrap: pretty; }
img { max-width: 100%; display: block; }

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

.skip {
  position: absolute; left: var(--s2); top: -100vh;
  z-index: var(--z-toast);
  background: var(--yellow); color: var(--bg-deep);
  padding: var(--s2) var(--s3);
  border-radius: var(--s1);
  font-weight: 700; text-decoration: none;
}
.skip:focus { top: var(--s2); }

/* the baseplate texture: a field of studs, very faint */
.studs {
  background-image: radial-gradient(
    circle at center,
    oklch(1 0 0 / 0.045) 0 22%,
    transparent 23%
  );
  background-size: var(--s4) var(--s4);
}

/* ============================================================
   nav
   ============================================================ */

/* Transparent and tall over the hero; condenses into a floating brick
   once the page scrolls. Ported from the FrostSender marketing nav,
   re-skinned onto the LEGO palette. */

.nav {
  position: fixed; top: 0; left: 0; right: 0;
  z-index: var(--z-nav);
  transition: left 500ms var(--out), right 500ms var(--out), top 500ms var(--out);
}
.nav.is-stuck { left: var(--s2); right: var(--s2); top: var(--s2); }

.nav__bar {
  position: relative;
  z-index: 2;                     /* must stay above the overlay, or the close button is buried */
  max-width: 87.5rem;
  margin-inline: auto;
  height: 5rem;
  display: flex; align-items: center; gap: var(--s3);
  padding-inline: clamp(var(--s3), 3.5vw, var(--s6));
  border: 2px solid transparent;
  border-radius: var(--s2);
  transition: height 500ms var(--out), max-width 500ms var(--out),
              background 500ms var(--out), border-color 500ms var(--out),
              box-shadow 500ms var(--out);
}
.nav.is-stuck .nav__bar,
.nav.is-open .nav__bar {
  max-width: 75rem;
  height: 3.5rem;
  background: oklch(0.13 0.014 265 / 0.74);
  border-color: var(--edge);
  backdrop-filter: blur(18px) saturate(1.2);
  -webkit-backdrop-filter: blur(18px) saturate(1.2);
  /* the brick's bottom edge, not a generic drop shadow */
  box-shadow: 0 4px 0 oklch(0.10 0.012 265 / 0.9), 0 14px 40px oklch(0 0 0 / 0.55);
}

/* ---- brand ---- */
.nav__mark {
  display: inline-flex; align-items: center; gap: var(--s1);
  color: var(--ink); text-decoration: none;
  margin-right: auto;
}
/* The brick wordmark. Sized by height so it shrinks with the bar; width
   follows its own 4.9:1 ratio. */
.nav__logo {
  display: block;
  height: 1.9rem;
  width: auto;
  transition: height 500ms var(--out);
}
.nav.is-stuck .nav__logo { height: 1.5rem; }

@media (max-width: 26rem) {
  .nav__logo { height: 1.6rem; }
  .nav.is-stuck .nav__logo { height: 1.35rem; }
}

/* Desktop has the bar height to carry a much larger mark. The 5rem bar
   leaves room for ~3rem; the condensed 3.5rem bar takes ~2.25rem. */
@media (min-width: 48rem) {
  .nav__logo { height: 3rem; }
  .nav.is-stuck .nav__logo { height: 2.25rem; }
}

.nav__tag {
  align-self: flex-start; margin-top: 0.15rem;
  font-size: 0.6rem; font-weight: 600; letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--yellow);
  opacity: 0.65;
}

/* ---- links ---- */
.nav__links { display: none; align-items: center; gap: var(--s5); }
.nav__links a {
  font-size: 0.9rem; font-weight: 500;
  color: var(--muted); text-decoration: none;
  transition: color 300ms var(--out);
}
.nav__links a:hover { color: var(--ink); }

.btn--pill { border-radius: 999px; }

/* ---- mobile toggle ---- */
.nav__toggle {
  display: grid; place-items: center;
  width: 44px; height: 44px;
  margin-right: calc(var(--s1) * -1);
  background: none; border: 0; padding: 0;
  color: var(--ink); cursor: pointer;
}
.nav__icon { width: 26px; height: 26px; }
.nav__icon--close { display: none; }
.nav.is-open .nav__icon--menu { display: none; }
.nav.is-open .nav__icon--close { display: block; }

/* Must come AFTER .nav__toggle's base rule — same specificity, so source
   order decides. Above it, `display: grid` won and the hamburger showed
   on desktop alongside the links. */
@media (min-width: 48rem) {
  .nav__links { display: flex; }
  .nav__toggle { display: none; }
}

/* ---- fullscreen mobile menu ---- */
.menu {
  position: fixed; inset: 0;
  z-index: 1;
  background: var(--bg-deep);
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transition: opacity 400ms var(--out), visibility 400ms;
}
.nav.is-open .menu { opacity: 1; visibility: visible; pointer-events: auto; }
.menu__inner {
  height: 100%;
  display: flex; flex-direction: column;
  padding: 7rem var(--s4) var(--s6);
}
.menu__links {
  flex: 1;
  display: flex; flex-direction: column; justify-content: center;
  gap: var(--s4);
}
.menu__links a {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(2rem, 11vw, 2.75rem);
  color: var(--ink); text-decoration: none;
  opacity: 0; transform: translateY(var(--s2));
  transition: opacity 500ms var(--out), transform 500ms var(--out);
}
.nav.is-open .menu__links a { opacity: 1; transform: none; }
.nav.is-open .menu__links a:nth-child(1) { transition-delay: 70ms; }
.nav.is-open .menu__links a:nth-child(2) { transition-delay: 140ms; }

.menu__foot { border-top: 2px solid var(--edge); padding-top: var(--s4); }

@media (min-width: 48rem) { .menu { display: none; } }

/* ============================================================
   buttons — pressed-down brick physics
   ============================================================ */

.btn {
  --btn-face: var(--surface-2);
  --btn-edge: var(--bg-deep);
  --btn-ink: var(--ink);

  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--s1);
  padding: 0.6rem var(--s3);
  min-height: 44px;                        /* touch target */
  border: 0; border-radius: 9px;
  background: var(--btn-face);
  color: var(--btn-ink);
  font-family: var(--body);
  font-size: 0.9375rem; font-weight: 800; letter-spacing: 0.005em;
  text-decoration: none; cursor: pointer;
  /* the moulded edge: bricks have a lip, and they press down */
  box-shadow:
    0 4px 0 var(--btn-edge),
    inset 0 2px 0 rgba(255, 255, 255, 0.28),
    0 6px 14px rgba(0, 0, 0, 0.45);
  transition: transform 110ms var(--snap), box-shadow 110ms var(--snap),
              filter 160ms var(--out);
}
.btn:hover:not(:disabled) { filter: brightness(1.07); }
.btn:active:not(:disabled) {
  transform: translateY(4px);
  box-shadow: 0 0 0 var(--btn-edge), inset 0 2px 0 rgba(255, 255, 255, 0.2);
}
.btn:disabled { cursor: not-allowed; }
/* a dimmed accent reads as washed-out, not disabled — inactive drops it */
.btn:disabled {
  --btn-face: rgba(255, 255, 255, 0.07);
  --btn-edge: rgba(0, 0, 0, 0.35);
  --btn-ink: oklch(0.62 0.012 80);
  box-shadow: 0 4px 0 var(--btn-edge);
}

/* red is the loudest brick on a yellow plate, and white on it clears 4.5:1 */
.btn--primary {
  --btn-face: var(--red);
  --btn-edge: var(--red-dark);
  --btn-ink: #fff;
}
/* secondary: a white brick, still a brick */
.btn--quiet {
  --btn-face: rgba(255, 255, 255, 0.13);
  --btn-edge: rgba(0, 0, 0, 0.45);
  --btn-ink: var(--plate-ink);
}
.btn--quiet:hover:not(:disabled) { --btn-face: rgba(255, 255, 255, 0.2); }

.btn--tiny { padding: 0.4rem 0.75rem; font-size: 0.8125rem; min-height: 36px; }
.btn--block { width: 100%; }

.btn__spinner { display: none; width: 1em; height: 1em; }
.btn.is-busy .btn__spinner {
  display: block;
  border: 2px solid oklch(0 0 0 / 0.25);
  border-top-color: currentColor;
  border-radius: 50%;
  animation: spin 700ms linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ============================================================
   builder — a LEGO colour field behind one glass editor
   ============================================================ */

:root {
  /* how far the editor hangs past the image and down into the gallery */
  --editor-overlap: clamp(6rem, 15vh, 11rem);

  /* One definition for the builder's scrim — it was repeated verbatim in
     every background-image rule below, which is four places to forget. */
  --builder-scrim: linear-gradient(to bottom,
    oklch(0.13 0.014 265 / 0.70) 0%,
    oklch(0.13 0.014 265 / 0.26) 11%,
    oklch(0.13 0.014 265 / 0.10) 38%,
    oklch(0.13 0.014 265 / 0.16) 76%,
    oklch(0.13 0.014 265 / 0.90) 100%);
}

.builder {
  position: relative;
  z-index: 1;                    /* the panel has to paint over the gallery */
  min-height: 100svh;
  display: flex; align-items: flex-start;
  /* A full band of image sits above the editor, so when the reveal fires
     the panel lands mid-viewport instead of jammed under the nav. */
  padding: min(72svh, 42rem) clamp(var(--s3), 4vw, var(--s8)) 0;

  /* The scene is a plain CSS background, deliberately. An <img> in a
     z-indexed wrapper with object-fit, srcset and lazy-loading gave this
     six independent ways to fail; a background-image has none of them.
     Gradient first (it paints on top), photo second, solid colour last as
     the fallback if the file never arrives. */
  background-color: var(--bg-deep);
  background-image: var(--builder-scrim), url("assets/bg-1500.jpg");
  background-position: center top, center top;
  background-size: cover, cover;
  background-repeat: no-repeat, no-repeat;
}

/* Phones get a purpose-shot portrait crop — the landscape frame reduces
   the scene to a sliver of ceiling at 9:16. */
@media (max-width: 48rem) {
  .builder {
    background-image: var(--builder-scrim), url("assets/bg-mobile-600.jpg");
    /* Scrim stays pinned to the top; the photo layer alone slides up 15px
       so its top edge is cropped off. */
    background-position: center top, center -15px;
  }
}
@media (max-width: 48rem) and (min-resolution: 2dppx) {
  .builder {
    background-image: var(--builder-scrim), url("assets/bg-mobile-1100.jpg");
    background-position: center top, center -15px;
  }
}

@media (min-width: 100rem) {
  .builder {
    background-image: var(--builder-scrim), url("assets/bg-2200.jpg");
  }
}

/* Pulled down so the panel straddles the seam: its lower half hangs into
   the gallery, which pads its own top by the same amount to make room. */
.builder__inner {
  position: relative; z-index: 2;
  width: 100%;
  max-width: 78rem; margin-inline: auto;
  margin-bottom: calc(var(--editor-overlap) * -1);
}

/* ---- the editor's entrance ----
   Hidden only when JS is running, so a no-JS visitor still gets a working
   editor rather than an invisible one. It keeps its layout box either way,
   which is what makes the page tall enough to scroll in the first place.

   The transition sits on the base rule, not on .is-revealed — declared only
   on the revealed state it would animate in and then snap out. */
.js .editor {
  opacity: 0;
  transform: translateY(34px) scale(0.985);
  transform-origin: 50% 0;
  transition: opacity 620ms var(--out), transform 720ms var(--out);
}
.js .builder.is-revealed .editor {
  opacity: 1;
  transform: none;
}

/* leaving is quicker than arriving — a slow fade-out reads as lag */
.js .builder:not(.is-revealed) .editor {
  transition: opacity 380ms var(--out), transform 420ms var(--out);
}

@media (prefers-reduced-motion: reduce) {
  .js .editor { transform: none; transition: opacity 200ms linear; }
  .js .builder:not(.is-revealed) .editor { transition: opacity 160ms linear; }
}

/* ---- the panel: a clear plate laid on the baseplate ----
   Transparent, so the studs behind it read straight through the glass.
   Everything inside therefore flips to dark ink — light-on-dark UI would
   be invisible here. Light blur only: too much and the studs mush. */
.editor {
  position: relative;
  border-radius: 24px;
  border: 2px solid rgba(255, 255, 255, 0.16);
  background: rgba(10, 9, 13, 0.58);
  backdrop-filter: blur(14px) saturate(1.1);
  -webkit-backdrop-filter: blur(14px) saturate(1.1);
  box-shadow:
    0 28px 70px -24px rgba(0, 0, 0, 0.75),
    inset 0 2px 0 rgba(255, 255, 255, 0.10);
  display: grid;
  overflow: hidden;
  color: var(--plate-ink);
}

/* a row of knobs along the top edge — the panel is a plate, so it has studs */
.editor::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 14px;
  pointer-events: none;
  background-image: radial-gradient(
    circle at center 100%,
    rgba(255, 255, 255, 0.16) 0 42%,
    rgba(255, 255, 255, 0) 44%);
  background-size: 34px 14px;
  background-repeat: repeat-x;
}

.editor__controls {
  padding: var(--s4);
  display: grid; gap: var(--s4); align-content: start;
}
.editor__stage {
  padding: var(--s4);
  display: grid; gap: var(--s3); align-content: center;
  min-height: 13rem;              /* stacked: don't leave a void under the controls */
  border-top: 2px solid var(--plate-line);
}

@media (min-width: 62rem) {
  .editor { grid-template-columns: 20.5rem minmax(0, 1fr); }
  .editor__stage {
    min-height: 22rem;
    border-top: 0; border-left: 2px solid var(--plate-line);
  }
}

/* ---- fields ---- */
.field { display: grid; gap: 0.6rem; border: 0; padding: 0; margin: 0; min-width: 0; }

.field__label {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s2);
  padding: 0;
  font-family: var(--body);
  font-size: 0.8125rem; font-weight: 600; letter-spacing: 0.01em;
  color: var(--plate-ink);
  text-transform: uppercase; font-size: 0.75rem; letter-spacing: 0.07em; font-weight: 800;
}
.field__value { color: var(--plate-ink); font-weight: 800; text-transform: none; letter-spacing: 0; font-size: 0.875rem; }
.field__help { font-size: 0.8125rem; line-height: 1.5; color: var(--plate-muted); }
.field__error { margin: 0; font-size: 0.8125rem; font-weight: 700; color: var(--danger); }

/* ---- dropzone ---- */
.drop {
  display: grid; place-items: center;
  min-height: 8.5rem;
  padding: var(--s3);
  border: 2px dashed rgba(255, 255, 255, 0.22);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.04);
  cursor: pointer;
  transition: border-color 180ms var(--out), background 180ms var(--out);
}
.drop:hover, .drop:focus-visible {
  border-color: var(--blue);
  background: rgba(255, 255, 255, 0.08);
}
.drop.is-dragging {
  border-color: var(--green); border-style: solid;
  background: oklch(0.60 0.165 148 / 0.20);
}
.drop.has-file {
  border-style: solid; border-color: rgba(255, 255, 255, 0.14);
  cursor: default; min-height: 0; padding: 0.6rem;
}

.drop__empty { display: grid; gap: 0.3rem; justify-items: center; text-align: center; }
.drop__icon { width: 1.6rem; height: 1.6rem; color: oklch(0.70 0.16 253); margin-bottom: 0.1rem; }
.drop__title { font-size: 0.9375rem; font-weight: 800; color: var(--plate-ink); }
.drop__hint { font-size: 0.8125rem; color: var(--plate-muted); }

.drop__filled {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 0.6rem; width: 100%;
}
.drop__thumb {
  width: 3rem; height: 3rem; flex: none;
  object-fit: cover; border-radius: 8px;
}
.drop__meta { min-width: 5rem; flex: 1 1 5rem; display: grid; }
.drop__meta strong {
  font-size: 0.875rem; font-weight: 600; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.drop__meta span { font-size: 0.78rem; color: var(--plate-muted); white-space: nowrap; }
.drop__filled .btn { flex: none; margin-left: auto; }

/* ---- range ---- */
.range {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 1.25rem;
  background: transparent; cursor: pointer;
}
.range::-webkit-slider-runnable-track {
  height: 6px; border-radius: 6px;
  background: rgba(255, 255, 255, 0.18);
}
.range::-moz-range-track {
  height: 6px; border-radius: 6px;
  background: rgba(255, 255, 255, 0.18);
}
.range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 20px; height: 20px;
  margin-top: -7px;
  border-radius: 50%;
  background: var(--red);
  box-shadow: inset 0 -3px 0 var(--red-dark), inset 0 2px 0 rgba(255,255,255,0.35), 0 2px 6px rgba(0, 0, 0, 0.6);
  transition: transform 150ms var(--out);
}
.range::-moz-range-thumb {
  width: 20px; height: 20px; border: 0; border-radius: 50%;
  background: var(--red);
  box-shadow: inset 0 -3px 0 var(--red-dark), 0 2px 6px rgba(0, 0, 0, 0.6);
}
.range:hover::-webkit-slider-thumb { transform: scale(1.15); }
.range__ticks {
  display: flex; justify-content: space-between;
  font-size: 0.75rem; font-weight: 600; color: var(--plate-muted);
  margin-top: -0.2rem;
}

/* ---- segmented ---- */
.segmented {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 2px; padding: 2px;
  background: rgba(0, 0, 0, 0.32);
  border: 2px solid rgba(255, 255, 255, 0.07);
  border-radius: 12px;
}
.seg { position: relative; display: block; }
.seg input { position: absolute; opacity: 0; inset: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.seg span {
  display: grid; place-items: center;
  min-height: 36px; padding: 0 0.4rem;
  border-radius: 8px;
  font-size: 0.8125rem; font-weight: 700; text-align: center;
  color: var(--plate-ink);
  transition: background 180ms var(--out), color 180ms var(--out), box-shadow 180ms var(--out);
}
@media (pointer: coarse) { .seg span { min-height: 44px; } }
.seg input:hover + span { background: rgba(255, 255, 255, 0.09); }
/* selected reads as a brick pressed into the plate */
.seg input:checked + span {
  background: var(--blue);
  color: #fff;
  box-shadow: inset 0 -3px 0 var(--blue-dark), inset 0 2px 0 rgba(255,255,255,0.22);
}
.seg input:focus-visible + span { outline: 3px solid var(--yellow); outline-offset: 2px; }

.editor__go { display: grid; gap: 0.5rem; }
.status { min-height: 1.3em; font-size: 0.8125rem; font-weight: 600; color: var(--plate-muted); margin: 0; text-align: center; }

/* ---- stage ----
   No inner card: the result sits directly on the glass. A bordered well
   inside a bordered panel would be a card in a card. */
.result {
  display: grid; place-items: center;
  width: 100%; min-height: 9rem;
}
@media (min-width: 62rem) { .result { min-height: 18rem; } }
.result__idle, .result__busy, .result__error {
  display: grid; gap: 0.6rem; justify-items: center; text-align: center;
  color: var(--plate-muted); font-size: 0.9375rem; font-weight: 600;
}
.result__error strong { font-size: 1rem; font-weight: 800; color: var(--plate-ink); }

.result__done { margin: 0; display: grid; gap: 0.7rem; justify-items: center; width: 100%; }
.result__done img {
  max-width: 100%; max-height: 30rem;
  border-radius: 10px;
  border: 3px solid rgba(255, 255, 255, 0.14);
  box-shadow: 0 22px 50px -18px rgba(0, 0, 0, 0.8);
}
.result__done figcaption { font-size: 0.8125rem; color: var(--plate-muted); font-weight: 600; text-align: center; }

/* skeleton beats a spinner: it shows the shape of what's coming */
.skeleton {
  width: min(100%, 26rem);
  aspect-ratio: 16 / 10;
  border-radius: 10px;
  background:
    linear-gradient(100deg,
      rgba(255, 255, 255, 0.05) 30%,
      rgba(255, 255, 255, 0.14) 50%,
      rgba(255, 255, 255, 0.05) 70%)
    0 0 / 220% 100%;
  animation: sheen 1.5s linear infinite;
}
@keyframes sheen { to { background-position: -220% 0; } }

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


/* ============================================================
   gallery — prints laid out on a table
   ============================================================ */

.gallery {
  position: relative;
  z-index: 0;                    /* sits under the overlapping editor */
  /* top padding carries the editor's overhang plus its own breathing room */
  padding:
    calc(var(--editor-overlap) + clamp(var(--s8), 8vw, var(--s12)))
    clamp(var(--s3), 4vw, var(--s8))
    clamp(var(--s10), 10vw, var(--s16));
  background: var(--bg);
  /* raking light across the surface, so the prints sit ON something */
  background-image:
    radial-gradient(120% 70% at 50% 0%, oklch(0.22 0.02 265 / 0.9), transparent 60%);
  overflow-x: clip;
}

.gallery__inner { max-width: 64rem; margin-inline: auto; }

.gallery__head { margin: 0 0 clamp(var(--s6), 6vw, var(--s10)); display: grid; gap: 0.2rem; }
.gallery__head h2 { font-size: 1.75rem; font-weight: 800; letter-spacing: -0.01em; }
.gallery__lede { color: var(--muted); font-size: 0.9375rem; }

/* Mobile is the base case: one column, strict left/right alternation.
   Desktop switches to real columns so the prints genuinely spread across
   the surface instead of queueing down the middle. */
.gallery__stack {
  display: flex; flex-direction: column; align-items: stretch;
  gap: var(--s5);
}

.shot {
  --tilt: -2deg;
  --x: 0%;
  --lift: 0rem;
  width: 78%;
  margin: 0 0 var(--lift);
  padding: 0.85rem;                    /* even on all four sides */
  border-radius: 14px;                 /* photo stock has a soft corner */
  position: relative;
  isolation: isolate;

  /* Real photo paper is never flat white: it is warm, and light falls
     unevenly across it. */
  background:
    linear-gradient(157deg,
      #fffefb 0%,
      #f9f6ee 38%,
      #f1ede2 72%,
      #e9e4d6 100%);

  /* contact shadow, mid shadow, ambient throw, then the paper's own
     lit top edge and shaded bottom edge */
  box-shadow:
    0 2px 3px rgba(0, 0, 0, 0.35),
    0 10px 20px -6px rgba(0, 0, 0, 0.5),
    0 30px 55px -18px rgba(0, 0, 0, 0.78),
    inset 0 1px 0 rgba(255, 255, 255, 0.95),
    inset 0 -1px 0 rgba(0, 0, 0, 0.07);

  transform: translateX(var(--x)) rotate(var(--tilt));
  transition: transform 380ms var(--out), box-shadow 380ms var(--out);
}

/* paper grain — fine noise, multiplied so it darkens rather than fogs */
.shot::before {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  pointer-events: none;
  z-index: 2;
  opacity: 0.055;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 140px 140px;
}

/* mobile: alternate sides */
.shot:nth-child(odd)  { align-self: flex-start; --tilt: -2.2deg; }
.shot:nth-child(even) { align-self: flex-end;   --tilt:  2.2deg; }

/* picking one up off the table */
/* Lifted off the table: straightens, rises, and throws a longer, softer
   shadow the way a real print would once it leaves the surface. */
.shot:hover, .shot:focus-within {
  transform: translateX(var(--x)) rotate(0deg) translateY(-14px) scale(1.045);
  box-shadow:
    0 3px 4px rgba(0, 0, 0, 0.3),
    0 20px 32px -10px rgba(0, 0, 0, 0.55),
    0 56px 80px -22px rgba(0, 0, 0, 0.85),
    inset 0 1px 0 rgba(255, 255, 255, 0.98),
    inset 0 -1px 0 rgba(0, 0, 0, 0.07);
  z-index: 3;
}
.shot:hover .shot__frame, .shot:focus-within .shot__frame {
  filter: brightness(1.12);
}
.shot__frame { transition: filter 380ms var(--out); }

/* The photo window sits below the paper surface, so it gets a recess
   rather than a border. */
.shot__frame {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 8px;                   /* nested inside the card radius */
  background-color: oklch(0.30 0.022 265);
  background-image:
    radial-gradient(circle at center, oklch(1 0 0 / 0.07) 0 21%, transparent 22%);
  background-size: 22px 22px;
  box-shadow:
    inset 0 0 0 1px rgba(0, 0, 0, 0.30),
    inset 0 2px 7px rgba(0, 0, 0, 0.45);
}
img.shot__frame { object-fit: cover; }




/* Desktop: true columns. Prints flow across as well as down, and the
   per-item tilt, drop and nudge cycle over nine so twelve prints never
   show a repeat. */
@media (min-width: 48rem) {
  .gallery__stack {
    display: block;
    columns: 2;
    column-gap: clamp(2rem, 4vw, 3.5rem);
  }
  .shot {
    width: 100%;
    display: inline-block;
    break-inside: avoid;
    margin: 0 0 clamp(2rem, 3.5vw, 3.25rem);
  }

  .shot:nth-child(9n + 1) { --tilt: -2.4deg; --x: -3%; margin-bottom: 2.5rem; }
  .shot:nth-child(9n + 2) { --tilt:  1.8deg; --x:  4%; margin-bottom: 4rem; }
  .shot:nth-child(9n + 3) { --tilt: -1.2deg; --x: -1%; margin-bottom: 2rem; }
  .shot:nth-child(9n + 4) { --tilt:  2.6deg; --x:  5%; margin-bottom: 4.75rem; }
  .shot:nth-child(9n + 5) { --tilt: -1.9deg; --x: -5%; margin-bottom: 2.25rem; }
  .shot:nth-child(9n + 6) { --tilt:  1.1deg; --x:  2%; margin-bottom: 3.5rem; }
  .shot:nth-child(9n + 7) { --tilt: -2.8deg; --x: -4%; margin-bottom: 2rem; }
  .shot:nth-child(9n + 8) { --tilt:  2.1deg; --x:  3%; margin-bottom: 4.25rem; }
  .shot:nth-child(9n + 9) { --tilt: -0.8deg; --x: -2%; margin-bottom: 3rem; }
}

@media (min-width: 72rem) {
  .gallery__stack { columns: 3; }
}

@media (prefers-reduced-motion: reduce) {
  .shot:hover, .shot:focus-within { transform: rotate(var(--tilt)); }
}

/* ============================================================
   how to buy — a numbered sequence, not three identical cards
   ============================================================ */

.buy {
  position: relative;
  padding: clamp(var(--s10), 10vw, var(--s16)) clamp(var(--s3), 4vw, var(--s8));
  background: var(--bg-deep);
  border-top: 1px solid oklch(1 0 0 / 0.06);
}

.buy__inner { max-width: 46rem; margin-inline: auto; }

.buy__head { margin: 0 0 clamp(var(--s6), 6vw, var(--s10)); display: grid; gap: 0.35rem; }
.buy__head h2 { font-size: 1.75rem; font-weight: 800; letter-spacing: -0.01em; }
.buy__lede { color: var(--muted); font-size: 0.9375rem; max-width: 46ch; }

/* ---- the sequence ---- */
.steps {
  list-style: none;
  margin: 0; padding: 0;
  display: grid;
  gap: clamp(var(--s5), 5vw, var(--s8));
  counter-reset: none;
}

.step {
  position: relative;
  display: grid;
  grid-template-columns: 2.75rem minmax(0, 1fr);
  gap: var(--s3);
  align-items: start;
}

/* The through-line that makes this read as one sequence rather than three
   separate blocks. Runs from under each numeral to the next one. */
.step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: calc(1.375rem - 1px);
  top: 3.1rem;
  bottom: calc(clamp(var(--s5), 5vw, var(--s8)) * -1);
  width: 2px;
  background: repeating-linear-gradient(
    to bottom,
    oklch(1 0 0 / 0.22) 0 6px,
    transparent 6px 12px);
}

/* a numbered brick, matching the button vocabulary */
.step__num {
  display: grid; place-items: center;
  width: 2.75rem; height: 2.75rem;
  border-radius: 9px;
  font-family: var(--body);
  font-size: 1.25rem; font-weight: 800;
  color: #fff;
  background: var(--red);
  box-shadow:
    0 4px 0 var(--red-dark),
    inset 0 2px 0 rgba(255, 255, 255, 0.28),
    0 6px 14px rgba(0, 0, 0, 0.45);
}
.step__num--red    { background: var(--red);    box-shadow: 0 4px 0 var(--red-dark),    inset 0 2px 0 rgba(255,255,255,0.28), 0 6px 14px rgba(0,0,0,0.45); }
.step__num--yellow { background: var(--yellow); color: oklch(0.19 0.03 92); box-shadow: 0 4px 0 var(--yellow-dark), inset 0 2px 0 rgba(255,255,255,0.4),  0 6px 14px rgba(0,0,0,0.45); }
.step__num--blue   { background: var(--blue);   box-shadow: 0 4px 0 var(--blue-dark),   inset 0 2px 0 rgba(255,255,255,0.28), 0 6px 14px rgba(0,0,0,0.45); }

.step__body { display: grid; gap: 0.45rem; padding-top: 0.15rem; }
.step__title { font-size: 1.125rem; font-weight: 800; letter-spacing: -0.005em; }
.step__copy {
  color: var(--muted);
  font-size: 0.9375rem; line-height: 1.6;
  max-width: 60ch;
}
.step__copy a {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: oklch(1 0 0 / 0.35);
  text-underline-offset: 3px;
  transition: text-decoration-color 200ms var(--out);
}
.step__copy a:hover { text-decoration-color: var(--yellow); }

/* ---- contract address ---- */
.ca {
  margin-top: var(--s2);
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem;
  padding: 0.85rem 0.9rem;
  border: 1px solid oklch(1 0 0 / 0.12);
  border-radius: 12px;
  background: oklch(1 0 0 / 0.04);
}
.ca__label {
  flex: 0 0 100%;
  font-size: 0.7rem; font-weight: 800;
  letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--muted);
}
/* Mono is functional here, not costume: a contract address has to be
   read character by character, and 0/O and l/1 must not collide. */
.ca__value {
  flex: 1 1 16rem;
  min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--ink);
  overflow-wrap: anywhere;
}
.ca:not([data-ca-live]) .ca__value { color: var(--muted); font-style: italic; }
.ca__copy { flex: 0 0 auto; }
.ca__status {
  flex: 0 0 auto;
  font-size: 0.8rem; font-weight: 700;
  color: var(--green);
  min-height: 1em;
}

@media (max-width: 30rem) {
  .step { grid-template-columns: 2.25rem minmax(0, 1fr); gap: var(--s2); }
  .step__num { width: 2.25rem; height: 2.25rem; font-size: 1.05rem; }
  .step:not(:last-child)::before { left: calc(1.125rem - 1px); top: 2.6rem; }
}

/* ============================================================
   footer
   ============================================================ */

.foot {
  padding: 0;
  background: var(--bg-deep);
  border-top: 2px solid var(--edge);
}
.foot__inner {
  max-width: 78rem; margin-inline: auto;
  padding: var(--s8) clamp(var(--s3), 4vw, var(--s8)) var(--s5);
}
.foot__mark {
  font-family: var(--display); font-weight: 800; font-size: 1.1rem;
  letter-spacing: 0.02em;
}

/* Full-bleed band at the very bottom of the page: edge to edge, no inner
   column, no max-width. #8a8a8a on #121212 is 5.4:1 — fine print still
   has to be readable. */
.foot__legal {
  margin: 0;
  width: 100%;
  max-width: none;
  background: #121212;
  color: #8a8a8a;
  border-top: 1px solid rgba(255, 255, 255, 0.05);
  padding: var(--s3) clamp(var(--s3), 4vw, var(--s8));
  font-size: 0.75rem;
  line-height: 1.6;
  text-align: center;
  text-wrap: pretty;
}

/* ============================================================
   reveals — content is visible by default; JS only adds the
   animation when it can actually run it
   ============================================================ */

.js .reveal { opacity: 0; transform: translateY(var(--s3)); }
.js .reveal.is-in {
  opacity: 1; transform: none;
  transition: opacity 620ms var(--out), transform 620ms var(--out);
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .js .reveal { opacity: 1; transform: none; }
  [data-parallax] { transform: none !important; }
}
