/* ==========================================================================
   Stitch Wishess — "Holo-Craft"
   Pastel futurism. Palette sampled directly from the studio's own artwork.
   ========================================================================== */

:root {
  /* Surface */
  --paper: #f2fafc;
  --paper-warm: #fdf4ea;
  --paper-raised: rgba(255, 255, 255, 0.72);

  /* Ink */
  --ink: #0d2a31;
  --ink-soft: #4a6e75;
  --ink-faint: #8aa5aa;

  /* Aurora — sampled from sky-texture.png */
  --aurora: #8fd4e6;
  --periwinkle: #9cc2e6;
  --mint: #aee2d2;

  /* Medallion — sampled from stitch-witch-portrait.png */
  --lilac: #e4cce4;
  --blossom: #f0c0e4;
  --peach: #fcd8cc;

  /* The palette has no error colour because the storefront never needed one.
     A deep rose keeps it in the family — blossom taken down to a weight that
     reads as "stop". */
  --alert: #a8456a;

  --line: rgba(13, 42, 49, 0.14);
  --line-soft: rgba(13, 42, 49, 0.07);

  --holo: linear-gradient(
    100deg,
    var(--aurora) 0%,
    var(--periwinkle) 22%,
    var(--lilac) 44%,
    var(--blossom) 62%,
    var(--peach) 78%,
    var(--mint) 100%
  );

  /* Same iridescence, pitched dark enough to read as type */
  --holo-ink: linear-gradient(
    100deg,
    #2f9cba 0%,
    #5b86c9 28%,
    #9a6dc4 52%,
    #c25fa0 74%,
    #3f9e83 100%
  );

  --font-display: "Unbounded", "Segoe UI", system-ui, sans-serif;
  --font-body: "Manrope", "Segoe UI", system-ui, sans-serif;
  --font-mono: "DM Mono", ui-monospace, "Cascadia Mono", monospace;

  --rail: 5.5rem;
  --content-max: 78rem;
  --radius: 20px;

  --s1: 0.5rem;
  --s2: 1rem;
  --s3: 1.75rem;
  --s4: 3rem;
  --s5: 5.5rem;
  --s6: 9rem;
}

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

/*
   The page scrolls up and down, and only up and down.

   overflow-x: hidden on the body alone was not enough. It makes a scrollport
   that is merely scrolled to its limit, and a sideways drag on a touch screen
   can still move it - so the whole site slid left and right under a thumb while
   someone was trying to scroll down a page.

   clip is the one that cannot: it creates no scroll container at all, so there
   is nothing to drag. hidden stays as the fallback for anything too old to know
   clip, and the declaration order means a browser that understands both takes
   clip.

   This is a lock, not a cure: something genuinely wider than the screen is
   still a bug, and is still worth finding. It just must not be able to take the
   rest of the site sideways with it.
*/
html {
  scroll-behavior: smooth;
  overflow-x: hidden;
  overflow-x: clip;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16.5px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  overflow-x: clip;

  /* A horizontal swipe that starts inside one of the rows that scroll
     sideways - the filter chips, the design thumbnails, the picks - must stay
     in that row rather than handing the rest of the gesture to the page. That
     chaining is the other half of why the site moved under a thumb. */
  overscroll-behavior-x: none;
}

/* The studio's own painted sky, fixed behind everything.
   The veil on top keeps body copy readable over the saturated bands. */
body::before {
  content: "";
  position: fixed;
  inset: -8%;
  z-index: -4;
  /* A palette tint over the photo. `cover` crops differently at every window
     shape, and the image's centre is its palest region — without this the
     background washes out on tall or narrow viewports. */
  background:
    linear-gradient(
      160deg,
      rgba(143, 212, 230, 0.42) 0%,
      rgba(156, 194, 230, 0.3) 28%,
      rgba(174, 226, 210, 0.26) 52%,
      rgba(240, 192, 228, 0.3) 76%,
      rgba(252, 216, 204, 0.34) 100%
    ),
    url("assets/sky-texture.png") center / cover no-repeat;
  filter: saturate(1.14);
  animation: drift 46s ease-in-out infinite alternate;
}

/* Drifting cloud layers, cut from the same painted sky.
   Each layer is 200% wide and travels exactly a whole number of tiles,
   so the loop never shows a seam. */
.clouds {
  position: fixed;
  inset: 0;
  z-index: -3;
  overflow: hidden;
  pointer-events: none;
}

/* Puffs are gradients, not the photo: the sky texture has no tileable edge,
   so tiling it leaves a seam. Every blob fades out well inside its own tile,
   which makes the repeat invisible. */
.cloud-layer {
  position: absolute;
  top: 0;
  left: 0;
  width: 200%;
  height: 100%;
  background-repeat: repeat-x;
  will-change: transform;
}

.cloud-layer--far {
  background-image:
    radial-gradient(22% 30% at 24% 28%, rgba(255, 255, 255, 0.95), transparent 72%),
    radial-gradient(19% 25% at 56% 52%, rgba(255, 255, 255, 0.85), transparent 74%),
    radial-gradient(16% 21% at 81% 24%, rgba(240, 250, 253, 0.88), transparent 74%);
  background-size: 50% 100%;
  opacity: 1;
  animation: cloud-drift 190s linear infinite;
}

.cloud-layer--near {
  background-image:
    radial-gradient(18% 23% at 20% 70%, rgba(255, 255, 255, 0.82), transparent 74%),
    radial-gradient(15% 19% at 52% 84%, rgba(255, 248, 242, 0.72), transparent 76%),
    radial-gradient(13% 17% at 78% 62%, rgba(255, 255, 255, 0.68), transparent 76%);
  background-size: 25% 100%;
  opacity: 0.9;
  animation: cloud-drift 105s linear infinite;
}

@keyframes cloud-drift {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(-50%, 0, 0); }
}

/* Fine grain so the pastels read as material, not flat gradient */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.05;
  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='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
}

@keyframes drift {
  0% { transform: translate3d(0, 0, 0) scale(1); }
  50% { transform: translate3d(-2.5%, 2%, 0) scale(1.06); }
  100% { transform: translate3d(2%, -1.5%, 0) scale(1.03); }
}

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

a {
  color: inherit;
  text-decoration: none;
}

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

/* --------------------------------------------------------------------------
   Type
   -------------------------------------------------------------------------- */

h1,
h2,
h3 {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 0.98;
  letter-spacing: -0.035em;
  margin: 0 0 var(--s2);
}

h1 { font-size: clamp(2.6rem, 5.2vw + 0.6rem, 5.4rem); }
h2 { font-size: clamp(2rem, 3.4vw + 0.6rem, 3.4rem); }

h3 {
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  line-height: 1.25;
}

p {
  margin: 0 0 var(--s2);
  color: var(--ink-soft);
}

/* Mono is the utility voice: labels, prices, specs, coordinates */
.label {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 var(--s2);
}

.holo-text {
  background: var(--holo-ink);
  background-size: 220% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: holo-shift 14s ease-in-out infinite alternate;
}

@keyframes holo-shift {
  0% { background-position: 0% 50%; }
  100% { background-position: 100% 50%; }
}

/* --------------------------------------------------------------------------
   Layout
   -------------------------------------------------------------------------- */

.wrap {
  max-width: var(--content-max);
  margin: 0 auto;
  padding-inline: var(--s3);
}

main {
  padding-left: var(--rail);
}

.band {
  padding-block: var(--s6);
}

.band--tight {
  padding-block: var(--s5);
}

/* Section heading: a stitch dash, then the name in mono */
.band-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  margin-bottom: var(--s4);
  padding-bottom: var(--s2);
  border-bottom: 1px dashed var(--line);
}

.band-head h2 {
  margin: 0;
}

.band-head .band-note {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-faint);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   The thread — signature element.
   A dashed stitch line that sews itself down the page as you scroll,
   with beads at each section anchor. This is how the beaded pens are made.
   -------------------------------------------------------------------------- */

.thread {
  position: fixed;
  top: 0;
  left: 0;
  width: var(--rail);
  height: 100vh;
  z-index: -2;
  pointer-events: none;
}

.thread__svg {
  width: 100%;
  height: 100%;
  overflow: visible;
}

.thread__track {
  fill: none;
  stroke: rgba(13, 42, 49, 0.18);
  stroke-width: 1.75;
  stroke-dasharray: 7 7;
}

.thread__stitch {
  fill: none;
  stroke: url(#threadHolo);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-dasharray: 7 7;
}

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

.bead {
  position: absolute;
  left: 50%;
  width: 13px;
  height: 13px;
  margin-left: -6.5px;
  margin-top: -6.5px;
  border-radius: 50%;
  background: var(--paper);
  border: 1.5px solid rgba(13, 42, 49, 0.28);
  transition: transform 0.45s cubic-bezier(0.2, 0.9, 0.3, 1), border-color 0.45s ease, box-shadow 0.45s ease;
  pointer-events: auto;
}

/* No printed labels: they sat 22px right of the bead, overflowed the rail and
   collided with body copy. The section name lives in the bead's title
   attribute instead, so the rail stays purely a spine. */
.bead.is-strung {
  background: var(--holo);
  border-color: transparent;
  transform: scale(1.5);
  box-shadow: 0 0 0 4px rgba(143, 212, 230, 0.22);
}

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

.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  padding-left: var(--rail);
  /* The original storefront ran a dark bar over the pastel sky; it is what
     gave the site its contrast, so it comes back here. */
  background: rgba(11, 34, 40, 0.9);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}

.site-header .brand {
  color: #f1f3f3;
}

.site-header .nav a {
  color: rgba(241, 243, 243, 0.72);
}

.site-header .nav a:hover,
.site-header .nav a[aria-current="page"] {
  color: #ffffff;
  border-bottom-color: var(--aurora);
}

.site-header :focus-visible {
  outline-color: var(--aurora);
}

.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  padding-top: 0.85rem;
  padding-bottom: 0.85rem;
}

.brand {
  display: flex;
  align-items: center;
  /* wide enough that an orbiting heart never crosses the wordmark */
  gap: 1.6rem;
  font-family: var(--font-display);
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: -0.02em;
}

.brand__mark {
  position: relative;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--holo);
  background-size: 200% 100%;
  animation: holo-shift 14s ease-in-out infinite alternate;
  flex: none;
}

/* The two blue hearts the original wordmark wore, set orbiting the mark.
   The counter-rotation keeps each heart upright as it travels. */
.brand__mark::before,
.brand__mark::after {
  content: "\1F499";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 1em;
  height: 1em;
  margin: -0.5em 0 0 -0.5em;
  font-size: 11px;
  line-height: 1;
  text-align: center;
  animation: heart-orbit 11s linear infinite;
}

.brand__mark::after {
  font-size: 8px;
  animation-delay: -5.5s;
  opacity: 0.85;
}

@keyframes heart-orbit {
  from { transform: rotate(0deg) translateX(22px) rotate(0deg); }
  to { transform: rotate(360deg) translateX(22px) rotate(-360deg); }
}

.nav {
  display: flex;
  align-items: center;
  gap: var(--s3);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.nav a {
  color: var(--ink-soft);
  padding-bottom: 2px;
  border-bottom: 1px solid transparent;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.nav a:hover,
.nav a[aria-current="page"] {
  color: var(--ink);
  border-bottom-color: var(--aurora);
}

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

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.85rem 1.7rem;
  border-radius: 999px;
  border: 1px solid transparent;
  font-family: var(--font-mono);
  font-size: 0.74rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  cursor: pointer;
  transition: transform 0.25s cubic-bezier(0.2, 0.9, 0.3, 1), box-shadow 0.25s ease, border-color 0.25s ease;
}

.btn-primary {
  background: var(--ink);
  color: var(--paper);
}

.btn-primary:hover {
  transform: translateY(-2px);
  box-shadow: 0 14px 30px -14px rgba(13, 42, 49, 0.55);
}

.btn-ghost {
  background: var(--paper-raised);
  color: var(--ink);
  border-color: var(--line);
  backdrop-filter: blur(12px);
}

.btn-ghost:hover {
  transform: translateY(-2px);
  border-color: var(--aurora);
}

/* --------------------------------------------------------------------------
   Hero — the medallion is the thesis
   -------------------------------------------------------------------------- */

.hero {
  padding-block: var(--s6) var(--s5);
}

.hero__grid {
  display: grid;
  grid-template-columns: 1fr 1.25fr;
  align-items: center;
  gap: var(--s4);
}

.hero__eyebrow {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0 0 var(--s3);
}

.hero__eyebrow::before {
  content: "";
  width: 34px;
  height: 1px;
  background: var(--ink-faint);
  flex: none;
}

.hero h1 {
  margin-bottom: var(--s3);
}

.hero__lede {
  max-width: 38ch;
  font-size: 1.08rem;
  margin-bottom: var(--s3);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
}

/* Medallion: circular crop of the artwork, ringed and lit */
.medallion {
  position: relative;
  aspect-ratio: 1;
  border-radius: 50%;
  justify-self: center;
  width: min(100%, 42rem);
  padding: 10px;
  background: var(--holo);
  background-size: 200% 200%;
  animation: holo-spin 20s linear infinite;
  box-shadow: 0 30px 70px -30px rgba(13, 42, 49, 0.4);
}

.medallion__disc {
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  overflow: hidden;
  background: var(--paper);
}

.medallion__disc img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Slow sheen sweep across the disc — iridescent foil */
.medallion__sheen {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: linear-gradient(
    115deg,
    transparent 34%,
    rgba(255, 255, 255, 0.5) 47%,
    rgba(255, 255, 255, 0.16) 53%,
    transparent 66%
  );
  background-size: 260% 260%;
  mix-blend-mode: screen;
  animation: sheen 9s ease-in-out infinite;
}

@keyframes holo-spin {
  0% { background-position: 0% 50%; }
  100% { background-position: 200% 50%; }
}

@keyframes sheen {
  0%, 62% { background-position: 120% 0%; }
  100% { background-position: -40% 100%; }
}

/* --------------------------------------------------------------------------
   Product grid — specimen cards
   -------------------------------------------------------------------------- */

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15.5rem, 1fr));
  gap: var(--s3);
}

.card {
  display: flex;
  flex-direction: column;
  background: var(--paper-raised);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  padding: 10px 10px 1.1rem;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  transition: transform 0.3s cubic-bezier(0.2, 0.9, 0.3, 1), box-shadow 0.3s ease, border-color 0.3s ease;
}

.card:hover {
  transform: translateY(-6px);
  border-color: rgba(143, 212, 230, 0.7);
  box-shadow: 0 26px 50px -26px rgba(13, 42, 49, 0.34);
}

/* 3:4 rather than 4:5. Cards stay cropped so the grid holds a clean line, but
   the commonest photograph in this catalog is exactly 3:4 — at this ratio
   those lose nothing at all, where 4:5 was shaving them. */
.card__media {
  position: relative;
  aspect-ratio: 3 / 4;
  border-radius: calc(var(--radius) - 8px);
  overflow: hidden;
  background: linear-gradient(150deg, var(--aurora), var(--lilac));
  margin-bottom: 0.95rem;
}

.card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.55s cubic-bezier(0.2, 0.9, 0.3, 1);
}

.card:hover .card__media img {
  transform: scale(1.05);
}

.card__media--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(13, 42, 49, 0.5);
  text-align: center;
  padding: var(--s2);
}

.card__body {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0 0.45rem;
  flex: 1;
}

.card__title {
  margin: 0;
  color: var(--ink);
}

.card__price {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--ink-soft);
  margin: 0;
  margin-top: auto;
  padding-top: 0.35rem;
}

/* --------------------------------------------------------------------------
   The maker
   -------------------------------------------------------------------------- */

.maker {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: var(--s5);
  align-items: center;
}

.maker__frame {
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--line-soft);
  background: var(--paper-raised);
  padding: 10px;
  box-shadow: 0 24px 50px -30px rgba(13, 42, 49, 0.35);
}

.maker__frame img {
  border-radius: calc(var(--radius) - 8px);
  aspect-ratio: 3 / 4;
  object-fit: cover;
  width: 100%;
}

.maker__quote {
  font-family: var(--font-display);
  font-size: clamp(1.2rem, 1.5vw + 0.7rem, 1.7rem);
  font-weight: 500;
  line-height: 1.32;
  letter-spacing: -0.025em;
  color: var(--ink);
  margin: 0 0 var(--s3);
}

.maker__sig {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* --------------------------------------------------------------------------
   Materials — the studio's vernacular, as a spec table
   -------------------------------------------------------------------------- */

.materials {
  display: grid;
  /* eight materials, so four columns fill two rows exactly */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  border-top: 1px dashed var(--line);
  border-left: 1px dashed var(--line);
}

.materials div {
  padding: 1.25rem 1.1rem;
  border-bottom: 1px dashed var(--line);
  border-right: 1px dashed var(--line);
  font-family: var(--font-mono);
  font-size: 0.76rem;
  letter-spacing: 0.06em;
  color: var(--ink-soft);
  transition: background-color 0.3s ease, color 0.3s ease;
}

.materials div:hover {
  background: rgba(255, 255, 255, 0.6);
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   Page heads (collection / about / product)
   -------------------------------------------------------------------------- */

.page-head {
  padding-block: var(--s5) var(--s4);
}

.page-head h1 {
  margin-bottom: var(--s2);
}

.page-head p {
  max-width: 44ch;
}

/* Product detail */
.detail {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s5);
  align-items: start;
  padding-block: var(--s4) var(--s5);
}

.detail__media {
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--line-soft);
  background: var(--paper-raised);
  padding: 10px;
}

/* contain, not cover. 85% of this catalog is portrait and the pens run as
   narrow as 0.51 — a square crop was cutting half the pen away. The frame is
   2:3 because that is the median shape here, so most photographs sit in it
   with little letterboxing, and none of them lose anything.
   A fixed ratio keeps the page from jumping as you step through the strip. */
.detail__media img {
  border-radius: calc(var(--radius) - 8px);
  display: block;
  width: 100%;
  aspect-ratio: 2 / 3;
  object-fit: contain;
}

/* Gallery — every photograph of a piece, not just the first.
   Products carry up to ten; the strip wraps rather than scrolling so none of
   them are hidden behind a gesture. */
.thumbs {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
  gap: 0.5rem;
  margin-top: 0.7rem;
}

.thumb {
  padding: 0;
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  overflow: hidden;
  background: var(--paper-raised);
  cursor: pointer;
  aspect-ratio: 1;
  transition: border-color 0.25s ease, box-shadow 0.25s ease, transform 0.25s ease;
}

/* Also contain: a square crop of a pen photograph shows the middle handful of
   beads, which made every thumbnail in a strip of ten look identical. Whole
   photographs are what make them tellable apart. */
.thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

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

/* The selected photograph gets the same holo ring the strung beads wear, so
   "this one is active" reads the same way everywhere on the site. */
.thumb.is-active {
  border-color: transparent;
  box-shadow: 0 0 0 2px var(--aurora), 0 0 0 6px rgba(143, 212, 230, 0.22);
}

/* Count on the card: tells you there is more to see before you open a piece */
.card__count {
  position: absolute;
  right: 0.6rem;
  bottom: 0.6rem;
  padding: 0.25rem 0.55rem;
  border-radius: 999px;
  background: rgba(13, 42, 49, 0.62);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  backdrop-filter: blur(6px);
}

/* Product titles run long; they must not inherit the hero scale */
.detail h1 {
  font-size: clamp(1.8rem, 1.9vw + 0.9rem, 2.7rem);
  line-height: 1.04;
  margin-bottom: var(--s2);
}

.detail__price {
  font-family: var(--font-mono);
  font-size: 1.15rem;
  color: var(--ink);
  margin-bottom: var(--s3);
}

.detail__desc {
  border-top: 1px dashed var(--line);
  padding-top: var(--s3);
  margin-bottom: var(--s3);
  max-width: 58ch;
}

.detail .not-found {
  grid-column: 1 / -1;
  text-align: center;
  padding: var(--s6) 0;
}

/* About */
.about-body {
  max-width: 44rem;
  padding-block: var(--s4) var(--s6);
}

.about-body p {
  font-size: 1.05rem;
}

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

.site-footer {
  padding-left: var(--rail);
  border-top: 1px dashed var(--line);
  margin-top: var(--s5);
}

.footer-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr;
  gap: var(--s4);
  padding-block: var(--s4) var(--s3);
}

.footer-grid h3 {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 400;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: var(--s2);
}

.footer-grid ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.footer-grid li {
  margin-bottom: 0.45rem;
  font-size: 0.9rem;
  color: var(--ink-soft);
}

.footer-grid a:hover {
  color: var(--ink);
}

.footer-bottom {
  border-top: 1px solid var(--line-soft);
  padding-block: var(--s2) var(--s4);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  display: flex;
  justify-content: space-between;
  gap: var(--s2);
  flex-wrap: wrap;
}

/* --------------------------------------------------------------------------
   Scroll reveal
   -------------------------------------------------------------------------- */

.reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.7s ease, transform 0.7s cubic-bezier(0.2, 0.9, 0.3, 1);
}

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

/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */

@media (max-width: 1000px) {
  .hero__grid,
  .maker {
    grid-template-columns: 1fr;
    gap: var(--s4);
  }

  .medallion {
    order: -1;
    width: min(100%, 30rem);
  }

  .detail {
    grid-template-columns: 1fr;
    gap: var(--s4);
  }

  .footer-grid {
    grid-template-columns: 1fr 1fr;
  }
}

@media (max-width: 720px) {
  :root {
    --rail: 0rem;
    --s5: 3.5rem;
    --s6: 5rem;
  }

  .thread {
    display: none;
  }

  .band-head {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.4rem;
  }

  .footer-grid {
    grid-template-columns: 1fr;
  }

  .materials {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .nav {
    gap: 1rem;
    font-size: 0.66rem;
    letter-spacing: 0.1em;
  }
}

/* Phone: drop the wordmark so the nav never runs off the edge */
@media (max-width: 560px) {
  .wrap {
    padding-inline: 1.1rem;
  }

  .brand__label {
    display: none;
  }

  .nav {
    gap: 0.8rem;
    font-size: 0.62rem;
    letter-spacing: 0.07em;
  }
}

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

  body::before,
  .holo-text,
  .brand__mark,
  .medallion,
  .medallion__sheen,
  .cloud-layer,
  .brand__mark::before,
  .brand__mark::after {
    animation: none;
  }

  .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .card,
  .btn,
  .card__media img,
  .thumb {
    transition: none;
  }
}

/* ==========================================================================
   The bag.
   ========================================================================== */

.bag-wrap { padding-block: var(--s4) var(--s5); }

.bag {
  display: grid;
  grid-template-columns: 1fr 20rem;
  gap: var(--s4);
  align-items: start;
}

/* Three children: the items, the total, and the suggestions. On a wide screen
   the items and the suggestions stack down the left and the total holds the
   right - which is the layout it always had, now spelled out rather than
   relying on where the markup happened to sit. */
.bag-main { grid-column: 1; }
.bag-extras { grid-column: 1; }

/* The total spans both rows so it can go on being sticky past the end of the
   items. Confined to its own row it would unstick the moment the suggestions
   scrolled into view, which is exactly when someone is deciding. */
.bag-total {
  grid-column: 2;
  grid-row: 1 / -1;
}

.bag h1 { font-size: clamp(1.8rem, 3vw, 2.6rem); margin-bottom: var(--s3); }

.bag-empty { text-align: center; padding-block: var(--s5) var(--s6); }
.bag-empty h1 { font-size: clamp(1.8rem, 4vw, 2.8rem); margin-bottom: var(--s2); }
.bag-empty p { color: var(--ink-soft); margin-bottom: var(--s3); }

/* Anything the server removed or reduced is said out loud. A bag that quietly
   gets smaller is a bag nobody trusts. */
.bag-problems {
  margin: 0 0 var(--s3);
  padding: var(--s2) var(--s3);
  list-style: none;
  border-radius: 14px;
  background: rgba(168, 69, 106, 0.1);
  color: var(--alert);
  font-size: 0.9rem;
}

.bag-items { list-style: none; margin: 0; padding: 0; }

.bag-item {
  display: grid;
  grid-template-columns: 5rem 1fr auto auto auto;
  align-items: center;
  gap: var(--s2);
  padding: var(--s2) 0;
  border-bottom: 1px dashed var(--line);
}

.bag-item__media {
  aspect-ratio: 1;
  border-radius: 12px;
  overflow: hidden;
  background: var(--paper-raised);
  border: 1px solid var(--line-soft);
}

.bag-item__media img { width: 100%; height: 100%; object-fit: contain; }
.bag-item__media--empty { background: linear-gradient(150deg, var(--aurora), var(--lilac)); }

.bag-item__title {
  display: block;
  font-family: var(--font-display);
  font-size: 0.98rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-decoration: none;
}

.bag-item__title:hover { text-decoration: underline; }

.bag-item__price {
  margin: 0.2rem 0 0;
  font-family: var(--font-mono);
  font-size: 0.74rem;
  color: var(--ink-faint);
}

.bag-item__line {
  margin: 0;
  min-width: 4.5rem;
  text-align: right;
  font-family: var(--font-mono);
}

.bag-item__drop {
  width: 2rem; height: 2rem;
  border: 0; background: none;
  color: var(--ink-faint);
  font-size: 1.2rem; line-height: 1;
  cursor: pointer;
}

.bag-item__drop:hover { color: var(--alert); }

.stepper {
  display: flex;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--paper-raised);
}

.stepper button {
  width: 2rem; height: 2rem;
  border: 0; background: none;
  color: var(--ink);
  font-size: 1rem; line-height: 1;
  cursor: pointer;
}

.stepper button:hover { color: var(--aurora); }
.stepper span { min-width: 1.8rem; text-align: center; font-family: var(--font-mono); font-size: 0.85rem; }

/* ----------------------------------------------------------------- total */

.bag-total {
  position: sticky;
  top: var(--s3);
  padding: var(--s3);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  background: var(--paper-raised);
  backdrop-filter: blur(14px);
}

.bag-total dl { margin: 0 0 var(--s3); }

.bag-total dl div {
  display: flex;
  justify-content: space-between;
  gap: var(--s2);
  padding: 0.35rem 0;
}

.bag-total dt { color: var(--ink-soft); font-size: 0.92rem; }
.bag-total dd { margin: 0; font-family: var(--font-mono); font-size: 0.92rem; }

/* The one row in the total that is good news. Given the ink colour and a
   little weight so it is read rather than skimmed past - it is the reason the
   number above it is what it is. */
.bag-total__free dd {
  color: var(--ink);
  font-weight: 600;
  font-family: var(--font-display);
  letter-spacing: 0.01em;
}

.bag-total__sum {
  margin-top: 0.4rem;
  padding-top: 0.7rem !important;
  border-top: 1px solid var(--line);
}

.bag-total__sum dt { color: var(--ink) !important; font-weight: 600; }
.bag-total__sum dd { font-size: 1.1rem; }

.bag-total__pay { width: 100%; justify-content: center; }

.bag-total__note {
  margin: var(--s2) 0 0;
  font-size: 0.84rem;
  color: var(--ink-soft);
  text-wrap: pretty;
}

/* Under the items rather than under the pay button.

   Where it was, it was the last thing read before paying - a way out, sitting
   directly beneath the way forward. Up here it is what it actually means:
   you have seen what is in the bag, and you can go and add to it. */
.bag-back {
  display: inline-block;
  margin-top: var(--s2);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.bag-back:hover { color: var(--ink-soft); text-decoration: underline; }

/* --------------------------------------------------------- sold out ---- */

.card__sold {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgba(13, 42, 49, 0.42);
  backdrop-filter: blur(2px);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.buy-note {
  margin: 0 0 var(--s2);
  font-size: 0.9rem;
  color: var(--ink-soft);
  text-wrap: pretty;
}

.btn[disabled] { opacity: 0.5; cursor: not-allowed; }
.btn[disabled]:hover { transform: none; box-shadow: none; }

@media (max-width: 860px) {
  /* One column, and the order is the order someone needs: what they chose,
     then the total and the button to pay it, then anything they might add.
     The upsell goes after the decision rather than in front of it. */
  .bag { grid-template-columns: 1fr; }
  .bag-main,
  .bag-extras,
  .bag-total { grid-column: 1; }
  .bag-total { grid-row: auto; position: static; }
}

@media (max-width: 560px) {
  .bag-item {
    grid-template-columns: 3.5rem 1fr auto;
    grid-template-areas: 'pic body drop' 'pic step line';
    row-gap: 0.5rem;
  }
  .bag-item__media { grid-area: pic; }
  .bag-item__body { grid-area: body; }
  .stepper { grid-area: step; }
  .bag-item__line { grid-area: line; }
  .bag-item__drop { grid-area: drop; }
}

/* ------------------------------------------------------- plain choices -- */

.choices { margin-bottom: var(--s3); }

.choices__row { display: flex; flex-wrap: wrap; gap: 0.5rem; }

.choice {
  padding: 0.5rem 1rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--paper-raised);
  color: var(--ink-soft);
  font-family: var(--font-body);
  font-size: 0.88rem;
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease;
}

.choice:hover { border-color: var(--aurora); color: var(--ink); }

.choice.is-on {
  background: var(--ink);
  border-color: transparent;
  color: var(--paper);
}

.bag-item__variant {
  margin: 0.15rem 0 0;
  font-size: 0.8rem;
  color: var(--ink-soft);
}

@media (prefers-reduced-motion: reduce) {
  .design, .choice { transition: none; }
}

/* ==========================================================================
   One strip.

   Every design photograph is also a gallery photograph, so a second row of
   the same pictures was showing the same thing twice. A thumbnail IS the
   design: it shows large and it is chosen, in one press.
   ========================================================================== */

/* A thumbnail that can be chosen sits a little prouder than one that is only
   a picture, so the difference is visible before anything is clicked. */
.thumb.is-design {
  border-color: var(--line);
  cursor: pointer;
}

/* Currently shown. */
.thumb.is-active { border-color: var(--aurora); }

/* Currently chosen — the same holo ring the strung beads wear. */
.thumb.is-chosen {
  border-color: transparent;
  box-shadow: 0 0 0 2px var(--aurora), 0 0 0 6px rgba(143, 212, 230, 0.22);
}

.thumb.is-gone { cursor: not-allowed; opacity: 0.45; }
.thumb.is-gone img { filter: grayscale(1); }

.thumb__gone {
  position: absolute;
  inset: auto 0 0 0;
  background: rgba(13, 42, 49, 0.72);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: 0.48rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-align: center;
  padding: 0.12rem 0;
}

.thumb { position: relative; }

/* What is being bought, said next to the button rather than over the strip. */
.chosen {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  margin: 0 0 var(--s2);
  color: var(--ink);
}

.chosen .label { margin: 0; }

/* --------------------------------------------------------- the small print */

.policy { max-width: 42rem; }

.policy h2 {
  font-size: clamp(1.4rem, 2.4vw, 2rem);
  margin-bottom: var(--s2);
  scroll-margin-top: var(--s4);
}

.policy p { text-wrap: pretty; }

.policy ul {
  margin: 0 0 var(--s2);
  padding-left: 1.1rem;
  color: var(--ink-soft);
}

.policy li { margin-bottom: 0.5rem; }

.policy a { text-decoration: underline; text-underline-offset: 2px; }

/* ------------------------------------------------- shopping on a phone ---- */
/*
   The catalog grid is auto-fill with a 15.5rem minimum. On a desktop that is
   right; on a 390px phone the available width is about 355px, two columns would
   need roughly 520px, and so exactly one card fits. The catalog read as one
   product at a time — a column to scroll rather than a shop to browse.

   Two fixed columns below this width instead. The photographs stay 3:4 portrait
   rather than being squared off, because most of these are beaded pens standing
   upright and a square crop cuts their heads off.
*/
@media (max-width: 560px) {
  .grid {
    /* minmax(0, 1fr) rather than 1fr: a long unbroken word in a title would
       otherwise stretch its column past its share and unbalance the row. */
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.7rem;
  }

  .card {
    padding: 6px 6px 0.85rem;
    border-radius: calc(var(--radius) - 4px);
    /* Backdrop blur costs real frames per element, and this doubles how many
       are on screen at once. Phones stutter scrolling it, so it comes off. */
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  .card__media {
    margin-bottom: 0.55rem;
    border-radius: calc(var(--radius) - 10px);
  }

  .card__body {
    padding: 0 0.25rem;
    gap: 0.3rem;
  }

  /* Titles in this catalog run to 68 characters. At full size in a 165px card
     that is six lines, one card becomes twice the height of its neighbour and
     the rows stop lining up. Two lines, then ellipsis. */
  .card__title {
    font-size: 0.82rem;
    line-height: 1.3;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  .card__price {
    font-size: 0.78rem;
  }

  .card__count {
    right: 0.35rem;
    bottom: 0.35rem;
    padding: 0.2rem 0.45rem;
    font-size: 0.56rem;
    letter-spacing: 0.06em;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  .card__sold {
    font-size: 0.6rem;
    letter-spacing: 0.1em;
  }

  /* The page heading above the grid eats most of the first screen, so the
     catalog starts below the fold. Tighten it so products are visible on
     arrival, which is the whole point of a catalog page. */
  .page-head {
    padding-block: var(--s3) var(--s2);
  }

  .page-head h1 {
    font-size: clamp(1.9rem, 10vw, 2.6rem);
  }

  .band-head {
    margin-bottom: var(--s2);
  }
}

/* Very narrow phones still get two columns — a 320px screen leaves about 140px
   a card, which is tight but still a grid rather than a queue. */
@media (max-width: 360px) {
  .grid { gap: 0.55rem; }
  .card__title { font-size: 0.78rem; }
}

/* ----------------------------------------------------------- filters ---- */
/*
   Fifteen pieces in one flat grid reads as a pile. The same fifteen behind five
   chips reads as a shop with a range — which it is: pens, wall pieces, things
   for a car, desk pieces, keychains.

   Rendered from the categories actually present, so a chip never leads to an
   empty grid, and the whole bar disappears if there is only one kind left.
*/
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 0.85rem;
}

.filters:empty { display: none; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--paper-raised);
  color: var(--ink-soft);
  font-family: var(--font-body);
  font-size: 0.8rem;
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease, transform 0.2s ease;
}

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

.chip.is-on {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.chip__count {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  opacity: 0.65;
}

.filters__note {
  margin: 0 0 var(--s2);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* ---------------------------------------------------------- swatches ---- */
/*
   The designs, shown instead of counted. A card that says "8 designs" is
   hiding the reason to look; four thumbnails and a remainder is the reason
   itself. Decorative, so it is aria-hidden — the count already reads on the
   photograph and every design is named on the piece's own page.
*/
.swatches {
  display: flex;
  gap: 0.3rem;
  margin: 0.15rem 0 0.1rem;
}

.swatch {
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 7px;
  overflow: hidden;
  flex: 0 0 auto;
  border: 1px solid var(--line-soft);
  background: var(--aurora);
}

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

.swatch.is-gone { opacity: 0.35; }

.swatch--more {
  display: grid;
  place-items: center;
  font-family: var(--font-mono);
  font-size: 0.6rem;
  color: var(--ink-soft);
  background: transparent;
}

@media (max-width: 560px) {
  .swatches { gap: 0.22rem; margin-top: 0.1rem; }
  .swatch { width: 1.6rem; height: 1.6rem; border-radius: 5px; }
  .swatch--more { font-size: 0.5rem; }

  .chip {
    padding: 0.42rem 0.7rem;
    font-size: 0.74rem;
  }

  .filters {
    /* One scrollable row rather than three stacked ones eating the screen
       before any product is visible. */
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    margin-inline: calc(var(--s3) * -1);
    padding-inline: var(--s3);
  }

  .filters::-webkit-scrollbar { display: none; }
  .chip { flex: 0 0 auto; }
}

/* ------------------------------------------------------- bag add-ons ---- */
/*
   Shipping is free, so a small piece added here really does cost only the
   piece. That is worth saying plainly - it is the honest version of the
   nudge, and it happens to be the persuasive one.
*/
.addons {
  margin-top: var(--s4);
  padding-top: var(--s3);
  border-top: 1px solid var(--line);
}

/* It sits below the total on a phone, where the grid gap has already set it
   apart and the rule above would be a second separator on top of the one the
   total's own border already draws. */
@media (max-width: 860px) {
  .addons {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
  }
}

.addons__why {
  margin: 0 0 var(--s2);
  color: var(--ink-soft);
  font-size: 0.9rem;
  text-wrap: pretty;
}

.addons__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.6rem;
}

.addon {
  display: grid;
  grid-template-columns: 3.2rem 1fr auto;
  align-items: center;
  gap: 0.8rem;
  padding: 0.5rem;
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  background: var(--paper-raised);
}

.addon__media {
  width: 3.2rem;
  height: 3.2rem;
  border-radius: 8px;
  overflow: hidden;
  background: var(--aurora);
  display: block;
}

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

.addon__body {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}

.addon__title {
  color: var(--ink);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.9rem;
  line-height: 1.25;
  /* These titles run long; two lines keeps every row the same height. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.addon__title:hover { color: var(--holo-ink); }

.addon__price {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--ink-soft);
}

.addon__btn {
  padding: 0.45rem 0.9rem;
  font-size: 0.78rem;
  white-space: nowrap;
}

@media (max-width: 560px) {
  .addon {
    grid-template-columns: 2.6rem 1fr auto;
    gap: 0.6rem;
  }
  .addon__media { width: 2.6rem; height: 2.6rem; }
  .addon__title { font-size: 0.84rem; }
  .addon__btn { padding: 0.4rem 0.7rem; font-size: 0.72rem; }
}

/* The description now sits below the buy button rather than above it, so it
   needs to read as "more about this" rather than as the next thing to do. */
.detail__desc--after {
  margin-top: var(--s3);
  padding-top: var(--s3);
  border-top: 1px solid var(--line-soft);
}

/* -------------------------------------------- the buy area, made to sell -- */
/*
   What turns a competent page into one that feels worth paying for is not
   decoration, it is answering the questions someone is already asking: how
   long, how much to send it, and who is making it. Every line here is true.

   No countdown, no viewer count, no invented review. Stock on these pieces is
   null - made to order - so a scarcity line would be a lie, and where stock is
   genuinely finite the real number is already shown above the button.
*/
.btn-buy {
  width: 100%;
  justify-content: center;
  padding-block: 0.95rem;
  font-size: 1rem;
}

.assure {
  list-style: none;
  margin: var(--s2) 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
}

.assure li {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.82rem;
  color: var(--ink-soft);
}

/* A tick drawn in CSS rather than an icon file: no extra request, and it keeps
   working when the font does not load. */
.assure li::before {
  content: '';
  width: 0.55rem;
  height: 0.3rem;
  border-left: 1.5px solid var(--holo-ink);
  border-bottom: 1.5px solid var(--holo-ink);
  transform: rotate(-45deg) translateY(-0.1rem);
  flex: 0 0 auto;
}

.assure__maker {
  margin: 0.85rem 0 0;
  padding-top: 0.85rem;
  border-top: 1px solid var(--line-soft);
  font-size: 0.85rem;
  color: var(--ink-soft);
  text-wrap: pretty;
}

.assure__maker a {
  color: var(--holo-ink);
  text-decoration: none;
  white-space: nowrap;
}

.assure__maker a:hover { text-decoration: underline; }

/* The price carried the mono face, which reads as a spec sheet. On the piece
   itself it is the number someone is deciding about, so it gets the display
   face and room to be looked at. */
.detail__price {
  font-family: var(--font-display);
  font-size: 1.75rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--ink);
  margin: 0.15rem 0 var(--s2);
}

/* Sits directly under the price, because that is the number it changes the
   meaning of. Quiet rather than a badge: a shop that shouts "FREE SHIPPING"
   looks like it is compensating, and this one is simply telling you. */
.detail__ship {
  font-size: 0.85rem;
  color: var(--ink-soft);
  margin: -0.6rem 0 var(--s2);
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.detail__ship::before {
  content: '';
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  background: var(--mint);
  flex: none;
}

@media (max-width: 560px) {
  .assure { gap: 0.35rem 0.8rem; }
  .assure li { font-size: 0.78rem; }
  .detail__price { font-size: 1.5rem; }
  .detail__ship { font-size: 0.8rem; margin-top: -0.4rem; }
}

/* ------------------------------------------------- the colour picker ---- */
/*
   A native colour input, deliberately. A grid of swatches would be the shop
   choosing for them, and the whole point of this control is that they choose.
*/
.colour { margin-bottom: var(--s3); }

.colour__row {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin-top: 0.3rem;
}

/* The browser's own control is a small grey box with a colour inside it. This
   makes the colour the whole control, which is what it is for. */
.colour__input {
  inline-size: 3.4rem;
  block-size: 2.4rem;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: calc(var(--radius) / 2);
  background: none;
  cursor: pointer;
}

.colour__input::-webkit-color-swatch-wrapper { padding: 3px; }
.colour__input::-webkit-color-swatch { border: none; border-radius: calc(var(--radius) / 3); }
.colour__input::-moz-color-swatch { border: none; border-radius: calc(var(--radius) / 3); }

.colour__value {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.colour__note {
  margin: 0.35rem 0 0;
  font-size: 0.8rem;
  color: var(--ink-soft);
}

/* In the bag, where two rows of one piece differ only by this. */
.bag-item__colour { display: flex; align-items: center; gap: 0.4rem; }

.bag-item__swatch {
  inline-size: 0.8rem;
  block-size: 0.8rem;
  border-radius: 50%;
  border: 1px solid var(--line);
  flex: none;
}

/* ------------------------------------- the piece, on one phone screen ---- */
/*
   Everything needed to decide - photograph, the designs, name, price, choose,
   buy - inside one screen, with no scrolling to reach the button.

   What was eating it: the main photograph is 2/3 aspect, which on a 370px
   column is 555px tall before anything else exists; nine design thumbnails
   wrapped onto two rows; and the h1 clamps to a 2.6rem floor, so a long title
   ran to three lines at 42px each.

   The photograph is object-fit: contain, so capping its height shrinks it
   rather than cropping - which matters here, because these are pens standing
   upright and the earlier complaint about this catalog was that their tops
   were being cut off.
*/
@media (max-width: 560px) {
  .detail {
    gap: 0.9rem;
    padding-block: 0.6rem 1.5rem;
  }

  .detail__media {
    padding: 6px;
  }

  .detail__media img {
    /* A share of the screen rather than a shape. Small enough to leave room
       for the decision, large enough to still be the thing you look at. */
    aspect-ratio: auto;
    height: 34vh;
    max-height: 20rem;
    object-fit: contain;
  }

  .thumbs {
    /* One row that scrolls, not two that stack. Nine designs on two rows cost
       150px of a screen that has to hold a decision. */
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    gap: 0.4rem;
    margin-top: 0.5rem;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
  }

  .thumbs::-webkit-scrollbar { display: none; }

  .thumb {
    flex: 0 0 3.2rem;
    width: 3.2rem;
    scroll-snap-align: start;
  }

  /* The wordmark sits in the header two inches above this. Saying it again
     costs a line of a screen that has none to spare. */
  /* p.label, not .label: the design row contains a span.label too, and hiding
     that would take the word DESIGN with it. */
  .detail > div > p.label:first-child {
    display: none;
  }

  .detail h1 {
    font-size: 1.45rem;
    line-height: 1.15;
    margin-bottom: 0.35rem;
  }

  .detail__price {
    font-size: 1.35rem;
    margin: 0 0 0.6rem;
  }

  .chosen {
    margin-bottom: 0.6rem;
    font-size: 0.9rem;
  }

  .btn-buy {
    padding-block: 0.85rem;
  }

  .assure {
    margin-top: 0.7rem;
  }

  .assure__maker {
    margin-top: 0.6rem;
    padding-top: 0.6rem;
    font-size: 0.8rem;
  }

  .detail__desc--after {
    margin-top: 1.2rem;
    padding-top: 1.2rem;
  }
}

/* Short phones - an SE is 667px tall - give the photograph less again rather
   than pushing the button off the bottom. */
@media (max-width: 560px) and (max-height: 720px) {
  .detail__media img { height: 26vh; }
  .thumb { flex-basis: 2.8rem; width: 2.8rem; }
  .detail h1 { font-size: 1.3rem; }
}

/* --------------------------------------------- stop the page overflowing -- */
/*
   A grid item's automatic minimum size is its content, not zero. So the
   thumbnail row - nine fixed-width thumbs that deliberately do not wrap -
   pushed its track wider than the screen, and because both tracks are 1fr the
   whole layout went with it: the title, the button label, the assurance strip
   and every line of the description were clipped at the same right edge, and
   the photograph sat off-centre with dead space beside it because contain
   centres inside a box wider than the viewport.

   body { overflow-x: hidden } was only hiding it. min-width: 0 is the fix:
   it lets the track shrink and lets the thumbnail row scroll inside itself,
   which is what it was always meant to do.
*/
.detail > *,
.gallery,
.thumbs,
.assure,
.chosen {
  min-width: 0;
}

.gallery,
.detail__media,
.detail__desc {
  max-width: 100%;
}

/* ------------------------------------------- the piece, looking its best -- */
@media (max-width: 560px) {
  /* The white frame hugs the photograph instead of being a full-width box with
     a portrait image floating in the middle of it. Centred, so it reads as a
     deliberate frame rather than a gap. */
  .detail__media {
    width: fit-content;
    max-width: 100%;
    margin-inline: auto;
    display: grid;
    place-items: center;
  }

  .detail__media img {
    width: auto;
    max-width: 100%;
    height: 34vh;
    max-height: 19rem;
    object-fit: contain;
  }

  /* Centred under a centred photograph. justify-content rather than a margin,
     so a row too wide to fit still starts at the left edge and scrolls. */
  .thumbs {
    justify-content: safe center;
  }

  /* "CHOOSE A DESIGN FIRST" in caps with letter-spacing is a long label on a
     narrow screen; it was being clipped rather than wrapped. */
  .btn-buy {
    font-size: 0.82rem;
    letter-spacing: 0.06em;
    white-space: normal;
    line-height: 1.3;
  }

  /* Three facts on one line do not fit. Two columns reads as a list rather
     than as a sentence that ran out of room. */
  .assure {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.3rem 0.7rem;
  }

  .assure li { font-size: 0.75rem; }
}

/* --------------------------------------------------------- catalog head -- */
/*
   The heading was three lines of display type and a paragraph, which pushed
   the first product most of a screen down. A shop should open on the things
   it sells.
*/
.catalog-head {
  padding-block: var(--s4) var(--s2);
}

.catalog-head h1 {
  margin: 0;
  font-size: clamp(2.4rem, 7vw, 4rem);
}

/* -------------------------------------------------------------- our picks */
.picks {
  padding-block: var(--s2) var(--s3);
}

.picks[hidden] { display: none; }

.picks__head h2 {
  margin: 0 0 0.15rem;
  font-size: clamp(1.3rem, 4vw, 1.8rem);
}

.picks__head p {
  margin: 0 0 var(--s2);
  color: var(--ink-soft);
  font-size: 0.88rem;
}

/*
   The mask is what makes a card look like it swooshes in and out: it arrives
   out of nothing at the left edge, is solid across the middle, and dissolves
   again on the right, so the row has no visible start or end.
*/
.picks__viewport {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(
    90deg,
    transparent 0,
    #000 9%,
    #000 91%,
    transparent 100%
  );
  mask-image: linear-gradient(90deg, transparent 0, #000 9%, #000 91%, transparent 100%);
}

/* The transform is driven frame by frame in main.js rather than by a keyframe
   animation.

   A keyframe can only go from somewhere to somewhere and then start again,
   which is what the row used to do: the list twice, travelling half its own
   width. Eight cards is about one screen wide, so on a wide monitor you
   watched the second copy run out before the reset came - and it was the same
   eight pieces in the same order every time round.

   There is no cycle now. Cards are recycled off the left and new random ones
   added on the right, so the row never repeats and never runs out, and the
   thing being animated is one number that only ever goes up. */
.picks__track {
  display: flex;
  gap: var(--s2);
  width: max-content;
  padding-block: 0.35rem;
  will-change: transform;
}

.pick {
  flex: 0 0 11.5rem;
  width: 11.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  text-decoration: none;
  color: inherit;
  transition: transform 0.35s cubic-bezier(0.2, 0.9, 0.3, 1);
}

.pick:hover { transform: translateY(-4px); }

.pick__media {
  position: relative;
  display: block;
  aspect-ratio: 3 / 4;
  border-radius: var(--radius);
  overflow: hidden;
  background: linear-gradient(150deg, var(--aurora), var(--lilac));
  border: 1px solid var(--line-soft);
}

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

.pick__count {
  position: absolute;
  right: 0.45rem;
  bottom: 0.45rem;
  padding: 0.2rem 0.45rem;
  border-radius: 999px;
  background: rgba(13, 42, 49, 0.62);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: 0.56rem;
  letter-spacing: 0.06em;
}

.pick__title {
  font-weight: 600;
  font-size: 0.85rem;
  line-height: 1.3;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.pick__price {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--ink-soft);
}

@media (max-width: 560px) {
  .catalog-head { padding-block: var(--s3) 0.4rem; }
  .picks { padding-block: 0.4rem var(--s2); }
  .pick { flex-basis: 8.5rem; width: 8.5rem; }
  .pick__title { font-size: 0.78rem; }
}

/*
   Anyone who has asked their system for less motion gets a row that simply
   sits still and scrolls by hand. A drifting carousel is exactly the kind of
   thing that setting exists for.
*/
@media (prefers-reduced-motion: reduce) {
  /* Every piece, once, going nowhere. main.js does not start the drift at all
     under this setting, so there is no transform to undo. */
  .picks__track {
    width: auto;
    overflow-x: auto;
    overscroll-behavior-x: contain;
  }
}

/* ------------------------------------------------- a code, and what it took */
/*
   The code box sits under the total and above the button, because it is the
   last thing somebody does before paying and the first thing they look for if
   they were given one.
*/
.bag-code { margin: var(--s2) 0; }

.bag-code__row { display: flex; gap: 0.5rem; margin-top: 0.3rem; }
.bag-code__row .field { flex: 1 1 auto; min-width: 0; text-transform: uppercase; }
.bag-code__row .btn { flex: none; }

.bag-code__note { margin: 0.4rem 0 0; font-size: 0.82rem; }
.bag-code__note--good { color: #2c7a5a; }
.bag-code__note--bad { color: var(--alert, #a8456a); }

/* Money coming off reads as money coming off. */
.bag-total__off dd { color: #2c7a5a; font-weight: 600; }

/* ============================================================== on sale ==== */
/*
   A sale has to read at a glance, in every place a price appears, or somebody
   sees a reduced price on one page and the old one on another and trusts
   neither. So: the old price struck through, the new one in the shop's alert
   colour, and a corner flash on the photograph.

   No countdowns and no "only 3 left!" The rest of this shop says nothing it
   cannot stand behind, and a sale is already a good enough reason to buy.
*/

/* The old price. Marked up as <s> so it is struck through even with no CSS,
   and carrying its own aria-label so a screen reader says "was" rather than
   reading two prices with no idea which is being charged. */
.was {
  color: var(--ink-faint);
  font-weight: 400;
  margin-inline-end: 0.35rem;
  text-decoration-thickness: 1px;
}

.now { color: var(--alert, #a8456a); font-weight: 600; }

/* The corner flash on a card or a pick. Sits opposite the design count so the
   two never collide. */
.card__sale {
  position: absolute;
  inset-block-start: 0.6rem;
  inset-inline-start: 0.6rem;
  z-index: 2;
  padding: 0.2rem 0.5rem;
  border-radius: 999px;
  background: var(--alert, #a8456a);
  color: #fff;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* On the piece itself the struck-through price is already loud, so this is the
   quiet half: what they actually save, in money rather than a percentage,
   because money is the thing being decided about. */
.detail__price--sale { display: flex; align-items: baseline; flex-wrap: wrap; gap: 0.5rem; }

.detail__saving {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--alert, #a8456a);
  border: 1px solid currentColor;
  border-radius: 999px;
  padding: 0.15rem 0.5rem;
}


}



/* ============================================================== offers ==== */
/*
   A deal should look like a good thing happening, not like a footnote.

   These were dashed grey pills, which read as a disclaimer. They now wear the
   shop's own iridescence - the same holo-shift the rest of the site is made
   of - because a red SALE badge would belong to a different shop entirely.

   What actually convinces is the arithmetic rather than the adjective:
   "$38.97 -> $15.00, save $23.97" does more work than any amount of shouting,
   and it has the advantage of being true. Still no countdowns and no invented
   scarcity; a real saving is exciting enough on its own.
*/

.offers {
  list-style: none;
  margin: 0 0 var(--s3);
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
}

.offer {
  position: relative;
  overflow: hidden;
  flex: 1 1 15rem;
  min-width: 0;
  padding: 0.9rem 1.1rem;
  border-radius: var(--radius);
  background:
    linear-gradient(135deg,
      rgba(143, 212, 230, 0.38) 0%,
      rgba(228, 204, 228, 0.38) 38%,
      rgba(240, 192, 228, 0.34) 68%,
      rgba(174, 226, 210, 0.38) 100%);
  background-size: 220% 220%;
  border: 1px solid var(--line-soft);
  animation: holo-shift 14s ease-in-out infinite alternate;
}

/* A slow sheen travelling across, the same gesture the buy button uses. It is
   the only motion here, and it stops entirely for anyone who asked it to. */
.offer::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(105deg, transparent 38%, rgba(255, 255, 255, 0.5) 50%, transparent 62%);
  transform: translateX(-120%);
  animation: offer-sheen 7s ease-in-out infinite;
  pointer-events: none;
}

@keyframes offer-sheen {
  0%, 62% { transform: translateX(-120%); }
  86%, 100% { transform: translateX(120%); }
}

.offer__kind {
  display: inline-block;
  margin-bottom: 0.3rem;
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.offer__says {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.01em;
}

/* The sum. This is the part that sells it. */
.offer__sum {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0.45rem 0 0;
  font-family: var(--font-mono);
  font-size: 0.85rem;
}

.offer__sum s { color: var(--ink-faint); }
.offer__arrow { color: var(--ink-soft); }
.offer__sum strong { font-size: 1rem; }

.offer__save {
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
  background: var(--ink);
  color: var(--paper);
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.offer__ends {
  margin: 0.4rem 0 0;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* Stacked under the price on a piece, where there is one and it should read
   as a sentence rather than sit in a row. */
.offers--piece { flex-direction: column; margin-block: 0.5rem var(--s2); }
.offers--piece .offer { flex: 1 1 auto; }

/* In the bag, where they are about to change the number below them. */
.offers--bag { flex-direction: column; margin-block: var(--s2); }
.offers--bag .offer { flex: 1 1 auto; }

/* The one worth acting on. Mint rather than iridescent, so it reads as
   progress instead of as another advert. */
.offer--near {
  background: linear-gradient(135deg, rgba(174, 226, 210, 0.55), rgba(143, 212, 230, 0.4));
  border-color: var(--mint);
  animation: none;
}

.offer--near::after { animation: none; display: none; }

.offer__bar {
  display: block;
  block-size: 0.4rem;
  margin-top: 0.55rem;
  border-radius: 999px;
  background: rgba(13, 42, 49, 0.12);
  overflow: hidden;
}

.offer__bar-fill {
  display: block;
  block-size: 100%;
  border-radius: 999px;
  background: var(--ink);
  transition: inline-size 0.5s cubic-bezier(0.2, 0.9, 0.3, 1);
}

.offer__count {
  display: inline-block;
  margin-top: 0.3rem;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
}

/* ------------------------------------------------- the tag on a card ---- */
/*
   Small, iridescent, and tilted a degree so it reads as something stuck on
   rather than something typed. A grid is scanned, so this has one job: catch
   the eye on the way past.
*/
.card__offer {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin: 0.35rem 0.3rem 0 0;
  padding: 0.22rem 0.6rem;
  border-radius: 999px;
  rotate: -1deg;
  background:
    linear-gradient(120deg,
      rgba(143, 212, 230, 0.75),
      rgba(228, 204, 228, 0.75) 45%,
      rgba(240, 192, 228, 0.7));
  background-size: 200% 200%;
  animation: holo-shift 14s ease-in-out infinite alternate;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--ink);
  box-shadow: 0 1px 0 rgba(13, 42, 49, 0.08);
}

.card__offer-spark {
  inline-size: 0.4rem;
  block-size: 0.4rem;
  border-radius: 50%;
  background: var(--ink);
  flex: none;
}

@media (prefers-reduced-motion: reduce) {
  .offer,
  .card__offer { animation: none; }
  .offer::after { display: none; }
  .offer__bar-fill { transition: none; }
}

@media (max-width: 560px) {
  .offer { flex: 1 1 100%; padding: 0.75rem 0.9rem; }
  .offer__says { font-size: 0.98rem; }
  .card__offer { font-size: 0.68rem; }
}

/* ---------------------------------------------------- two ways to buy ---- */
/*
   Buy it now first and filled, Add to bag beside it and outlined.

   The ordering is deliberate. Most people on a product page are deciding
   about that one thing, so the button that finishes is the loud one. Add to
   bag is for the person collecting several designs, and it stays on the page
   for exactly that reason - bouncing them to the bag between each is the one
   thing that stops them adding a second.
*/
.buy-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.6rem;
  margin-bottom: 0.5rem;
}

.buy-pair .btn-buy { width: 100%; justify-content: center; }

.buy-pair .btn-ghost {
  border: 1px solid var(--line);
  background: var(--paper-raised);
}

.buy-note--bad { color: var(--alert, #a8456a); }

/* On a narrow phone they stack, with the finishing button on top. Two half
   width buttons at 320px are two buttons nobody can read. */
@media (max-width: 420px) {
  .buy-pair { grid-template-columns: 1fr; }
}
