/* The Fringe Ting: site styles. Every colour, size and duration comes from tokens.css (inlined by build.mjs).
   Cream page, two kinds of type (Bodoni Moda caps for names, Archivo condensed caps for facts), square corners,
   no shadows, no gradients. The fringe is the only thing that moves. */
/* The Fringe Ting: design tokens v0.4 (19 Oct 2026: editorial buttons; round 3: fixed mats, seam, dark). Cream and black from her logo; silver only for the theme toggle's rim
   (the old accent colour was dropped by Ben, 19 Oct, to confirm with Amanda). Contrast (WCAG): ink on cream 15.9 · ink-2 on cream 8.3 · mute on ink 8.5. */
:root {
  --cream: #f3ecdc;        /* page */
  --cream-2: #e9e0cc;      /* facts table fill */
  --ink: #15120f;          /* text, hero band, footer */
  --ink-2: #4a4339;        /* secondary text on cream, 8.3 (was #5e564c, 6.1; Ben, 19 Oct: type read too thin) */
  --mute: #b8aea0;         /* secondary text on ink, 8.5 */
  --silver: #d3cfc7;       /* the theme toggle's nickel rim (the buttons dropped the grommet, 19 Oct) */
  --silver-2: #7d786f;     /* its edges, 1px */
  --silver-hi: #e4e0d8;    /* its rim on hover */
  --line: #15120f;         /* 1px rules inside the facts table only */

  /* type */
  --f-display: "Bodoni Moda", "Didot", "Bodoni 72", Georgia, serif;
  --f-text: "Archivo", "Helvetica Neue", Arial, sans-serif;
  --t-ratio: 1.333;
  --t-xs: 0.875rem;    /* 14 condensed caps: facts, prices, tags */
  --t-s: 0.9375rem;    /* 15 captions, footer */
  --t-m: 1.0625rem;    /* 17 body */
  --t-l: 1.5rem;       /* 24 */
  --t-xl: clamp(2rem, 1.2rem + 3vw, 3.25rem);       /* 32 to 52 section titles */
  --t-2xl: clamp(2.5rem, 1rem + 5.5vw, 5.5rem);     /* 40 to 88 product names, page titles */
  --t-poster: clamp(3.5rem, 11vw, 10.5rem);         /* 56 to 168 the wordmark in the hero */
  --lh-text: 1.55; --lh-tight: 1.05; --lh-display: 0.92;
  --track-caps: 0.06em; --track-cond: 0.07em; --track-poster: -0.01em;
  --stretch-cond: 87.5%;
  --w-text: 450;       /* Archivo body, a touch above regular so it holds on cream */
  --w-label: 700;      /* condensed caps labels: facts, prices, tags, links */
  --w-display: 800;    /* Bodoni Moda titles, product names, the mark */
  --w-poster: 700;     /* Bodoni Moda wordmark at poster scale */
  --opsz-display: 6;   /* optical size cap above tile size, so the hairlines stay strokes */

  /* space (4px base) */
  --s-1: 4px; --s-2: 8px; --s-3: 12px; --s-4: 16px; --s-5: 24px; --s-6: 32px; --s-7: 48px; --s-8: 64px; --s-9: 96px; --s-10: 144px;
  --gutter: 24px; --gutter-phone: 16px; --max: 1440px;

  /* shape and motion */
  --radius: 0; --shadow: none;
  --ease: cubic-bezier(.2,.8,.2,1); --dur: 300ms; --dur-fast: 150ms; --dur-fringe: 600ms;
  --dur-swap: 250ms;       /* the product tile's hover photo fade and zoom */
  --fringe-min: 90px; --fringe-len: 150px; /* strand lengths; space left under a band so strands never cover text */
  --fringe-b-len: 90px;    /* B: the header trim's box (the strips plus room to swing); page tops start below it */
  --fringe-trim: 79px; --fringe-trim-w: 5px; --fringe-trim-pitch: 7px; /* B: the header strips, like a suede fringe trim: one length (within 3%), narrow, packed close */
  --fringe-pitch: 14px;    /* root to root, each gap jittered up to 35% */
  --fringe-w-min: 6px; --fringe-w-max: 9px; /* strap face width, whole pixels */
  --seam-thread: 2px; --seam-strip: 6px;   /* the sewn seam under the header (B): cream thread, ink binding strip the fringe hangs from */
  --how-gap: 88px; --how-row-gap: 84px;    /* how ordering works: the arrow gap between steps (desktop), the row gap (phone, tablet) */
  --dur-lift: 200ms; --lift: 6px;          /* the product tile lift on hover */

  /* added for the shop build (18 Oct) */
  --mat: var(--cream); --mat-ink: #15120f; --mat-ink-2: #4a4339;   /* the photo mat is the page cream (Ben, 20 Oct: one shade, not three): photos multiply onto it and
                           the photos step lifts their ground to pure white, so a photo's ground, the mat and the page are one colour. In dark the mat stays this light cream (below) */
  --panel: #e9e0cc;        /* the "Photo coming" panels, fixed in both themes, so a missing photo still reads as a frame */
  --btn-text: #f3ecdc;                                          /* cream type on the fixed ink tag (on the mat), both themes */
  --mark-filter: none;                                          /* the cream monogram; brightness(0) in dark */
  --focus: var(--ink);     /* focus ring on cream; .on-ink switches it to cream */
  --ring: 2px; --ring-gap: 3px;
  --target: 44px;          /* minimum touch target */
  --bar-h: 72px;           /* the phone buy bar on product pages */
  --t-lede: clamp(1.125rem, 0.95rem + 0.8vw, 1.5rem);   /* 18 to 24 the hero line */
  --t-name: clamp(1rem, 0.85rem + 0.5vw, 1.375rem);     /* 16 to 22 product names in tiles */
  --t-btn: 1.375rem; --t-btn-bar: 1.25rem;              /* 22 the editorial button label, 20 in the phone buy bar */
}
@media (prefers-reduced-motion: reduce) { :root { --dur: 0ms; --dur-fast: 0ms; --dur-fringe: 0ms; --dur-swap: 0ms; --dur-lift: 0ms; --lift: 0px; } }

/* dark (prototype B, round 3): cream and ink swap, nothing else. The mats (fixed at the light page cream, one shade inside every tile), the "Photo coming" panels and the tag's cream type keep their light-mode values.
   Contrast: cream on ink 15.9 · ink-2 #b8aea0 on #15120f 8.5 · mute on the cream bands 8.3 · mat-ink on mat 15.9 · mat-ink-2 on mat 8.3 · mat-ink on panel 14.5.
   The system preference applies only where build.mjs marks <html class="themable"> (B), so variant A stays light;
   data-theme is set only by theme.js (the header toggle, B only). */
@media (prefers-color-scheme: dark) { :root.themable:not([data-theme="light"]) { --cream: #15120f; --mat: #f3ecdc; --cream-2: #231f1a; --ink: #f3ecdc; --ink-2: #b8aea0; --mute: #4a4339; --line: #f3ecdc; --mark-filter: brightness(0); } }
:root[data-theme="dark"] { --cream: #15120f; --mat: #f3ecdc; --cream-2: #231f1a; --ink: #f3ecdc; --ink-2: #b8aea0; --mute: #4a4339; --line: #f3ecdc; --mark-filter: brightness(0); }


/* Fallback faces sized to the web fonts so the swap does not move anything (measured against Bodoni Moda and Archivo). */
@font-face { font-family: "Bodoni Fallback"; src: local("Georgia"); size-adjust: 92%; ascent-override: 123%; descent-override: 44%; }
@font-face { font-family: "Archivo Fallback"; src: local("Arial"); size-adjust: 98.4%; ascent-override: 89%; descent-override: 21%; }
:root { --f-display: "Bodoni Moda", "Bodoni Fallback", "Didot", Georgia, serif; --f-text: "Archivo", "Archivo Fallback", "Helvetica Neue", Arial, sans-serif; }

/* ---------- base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { background: var(--cream); -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
body { margin: 0; background: var(--cream); color: var(--ink); font-family: var(--f-text); font-size: var(--t-m); font-weight: var(--w-text); line-height: var(--lh-text); overflow-x: clip; }
img { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration-thickness: 1px; text-underline-offset: 0.2em; }
h1, h2, h3, p, dl, dd, figure, blockquote { margin: 0; }
h1, h2, h3 { text-wrap: balance; }
p, dd { text-wrap: pretty; }
ul[role="list"], .links ul, .foot ul { list-style: none; margin: 0; padding: 0; }
:focus-visible { outline: var(--ring) solid var(--focus); outline-offset: var(--ring-gap); }
.on-ink { --focus: var(--cream); background: var(--ink); color: var(--cream); }
.vh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.skip { --focus: var(--cream); position: absolute; left: var(--s-4); top: var(--s-2); z-index: 20; display: inline-flex; align-items: center; min-height: var(--target); padding: 0 var(--s-4); background: var(--cream); color: var(--ink); text-decoration: none; transform: translateY(-200%); }
.skip:focus { transform: none; }

.wrap { width: 100%; max-width: var(--max); margin-inline: auto; padding-inline: var(--gutter-phone); }
@media (min-width: 768px) { .wrap { padding-inline: var(--gutter); } }

/* ---------- the two type voices ---------- */
.mark, .poster, .h-section, .h-page, .h-product, .tile-name, .foot-mark {
  font-family: var(--f-display); font-weight: var(--w-display); text-transform: uppercase; letter-spacing: var(--track-caps); line-height: var(--lh-tight);
}
.poster, .h-section, .h-page, .h-product, .foot-mark { font-variation-settings: "opsz" var(--opsz-display); }
.links a, .ig, .tile-meta, .tag, .facts dt, .how-list dt, .how-step h3, .back, .type-price, .buybar-price, .missing-head,
.missing-facts dt, .quote figcaption, .sign, .foot-legal, .coming {
  font-family: var(--f-text); font-stretch: var(--stretch-cond); font-size: var(--t-xs); font-weight: var(--w-label);
  text-transform: uppercase; letter-spacing: var(--track-cond); line-height: 1.3;
}
.coming { color: var(--ink-2); white-space: nowrap; }
.on-ink .coming { color: var(--mute); }
.h-section { font-size: var(--t-xl); letter-spacing: 0.02em; }
.h-page, .h-product { font-size: var(--t-2xl); line-height: var(--lh-display); letter-spacing: 0.01em; }

/* links in running text: ink, with a 1px ink-2 underline that turns ink on hover */
.how-list a, .how-step a, .facts a, .ship-note a, .text-link, .about-text a {
  color: var(--ink); text-decoration: underline 1px var(--ink-2); text-underline-offset: 0.15em;
}
.how-list a:hover, .how-step a:hover, .facts a:hover, .ship-note a:hover, .text-link:hover, .about-text a:hover { text-decoration-color: var(--ink); }

/* ---------- buttons: the editorial rule (Ben, 19 Oct, option D; the silver grommet is retired from the buttons) ----------
   No box: the label in Bodoni Moda caps, a hairline under it (currentColor at 45%) and an arrow after it. On hover or
   keyboard focus a 3px rule draws in from the left and the arrow steps forward 6px, like the underline a tile's name gets;
   pressing collapses the rule to a hairline. Ink on cream, cream on ink (.on-ink, the phone buy bar), so dark mode follows
   the token swap. 44px tall at least, whatever the type size. Reduced motion: --dur is 0, so the rule appears at once. */
.btn {
  position: relative; display: inline-flex; align-items: center; gap: 0.6em; min-height: var(--target); padding: 9px 6px 11px 0;  /* the 6px the arrow steps into, so it stays on the rule */
  color: var(--ink); background: none; text-decoration: none; white-space: nowrap; cursor: pointer;
  font-family: var(--f-display); font-weight: var(--w-display); font-variation-settings: "opsz" var(--opsz-display);
  font-size: var(--t-btn); line-height: 1.1; text-transform: uppercase; letter-spacing: var(--track-caps);
  border-bottom: 1px solid color-mix(in srgb, currentColor 45%, transparent);
}
.on-ink .btn { color: var(--cream); }
.btn .arrow {
  font-family: var(--f-text); font-stretch: var(--stretch-cond); font-weight: 500; font-size: 0.91em; letter-spacing: 0;
  transition: transform var(--dur) var(--ease);
}
.btn::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 3px; background: currentColor;
  transform: scaleX(0); transform-origin: left; transition: transform var(--dur) var(--ease);
}
.btn:hover::after, .btn:focus-visible::after { transform: scaleX(1); }
.btn:hover .arrow, .btn:focus-visible .arrow { transform: translateX(6px); }
.btn:active { transform: translateY(1px); }
.btn:active::after { transform: scaleX(1); height: 1px; bottom: 0; }
/* the product page's main button spans the details column: label left, arrow at the far end, the rule the full width */
.btn-wide { display: flex; width: 100%; justify-content: space-between; }
.text-link { display: inline-flex; align-items: center; min-height: var(--target); margin-top: var(--s-2); font-size: var(--t-s); }

/* ---------- header (black band, cream nav) ---------- */
.top-row { display: grid; grid-template-columns: 1fr auto; grid-template-areas: "mark ig" "links links"; align-items: center; column-gap: var(--s-4); padding-block: var(--s-2); }
.mark { grid-area: mark; justify-self: start; display: inline-flex; align-items: center; min-height: var(--target); font-size: 1rem; font-variation-settings: "opsz" 12; letter-spacing: 0.1em; text-decoration: none; }
.mark-icon { height: 30px; width: auto; margin-right: var(--s-3); }
.mark { white-space: nowrap; }
@media (max-width: 379px) { .mark-icon { height: 22px; margin-right: 6px; } }  /* keeps mark, monogram and @handle on one row at 360 */  /* her tFt monogram, cut from her logo sheet until the real files come */
.links { grid-area: links; }
.links ul { display: flex; gap: var(--s-5); }
.links a, .ig { display: inline-flex; align-items: center; min-height: var(--target); text-decoration: none; }
.ig { grid-area: ig; justify-self: end; }   /* A: the handle sits straight in the grid; B wraps it with the toggle in .top-end */
/* the dark mode toggle (B, round 3): a small grommet whose hole is half ink, half cream; no transition on the flip */
.top-end { grid-area: ig; justify-self: end; display: flex; align-items: center; gap: var(--s-3); }
.theme { display: inline-flex; align-items: center; justify-content: center; width: var(--target); height: var(--target); margin: 0; padding: 0; border: 0; background: none; color: inherit; cursor: pointer; }
.theme svg { display: block; }
.theme:hover svg circle:first-child { fill: var(--silver-hi); }   /* the toggle keeps its grommet: --silver, --silver-2, --silver-hi */
.mark-icon { filter: var(--mark-filter); }   /* the cream monogram turns ink on the cream header in dark */
.links a:hover, .ig:hover, .mark:hover { text-decoration: underline; }
.links a[aria-current="page"] { text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 0.35em; }
@media (min-width: 600px) {
  .top-row { grid-template-columns: 1fr auto 1fr; grid-template-areas: "mark links ig"; }
  .links ul { gap: var(--s-6); }
}

/* ---------- home hero ---------- */
.hero { position: relative; z-index: 2; }
.hero-body { display: grid; grid-template-columns: minmax(0, 1fr); padding-block: var(--s-6) var(--s-7); }
.poster { position: relative; z-index: 1; font-size: var(--t-poster); font-weight: var(--w-poster); line-height: var(--lh-display); letter-spacing: var(--track-poster); }
.poster span { display: block; white-space: nowrap; }
.hero-text { position: relative; z-index: 1; margin-top: var(--s-6); }
.lede { font-size: var(--t-lede); line-height: 1.4; max-width: 30ch; margin-bottom: var(--s-5); }
.lede mark { background: none; color: inherit; font-family: var(--f-display); font-style: italic; font-size: 1.08em; }  /* Ben, 19 Oct: the phrase is set in Bodoni italic, no colour highlight */
.hero-cut { grid-row: 3; justify-self: end; width: 62%; margin-top: var(--s-5); }
/* phone: the wordmark is sized to its longer line ("THE FRINGE" measures 6.45em at opsz 6 weight 700 in Bodoni Moda caps at poster size, a little wider at smaller optical sizes) so it breaks THE FRINGE / TING */
@media (max-width: 767px) { .poster { font-size: min(var(--t-poster), calc((100vw - 2 * var(--gutter-phone)) / 6.7)); } }
@media (min-width: 768px) {
  .hero-body { grid-template-columns: repeat(12, minmax(0, 1fr)); grid-template-rows: auto 1fr; column-gap: var(--gutter); padding-block: var(--s-7) var(--s-8); }
  .poster { grid-column: 1 / -1; grid-row: 1; }
  .hero-text { grid-column: 1 / 8; grid-row: 2; align-self: end; margin-top: var(--s-7); }
  /* the necklace sits behind the end of the wordmark, like the type over the photo on her poster */
  .hero-cut { grid-column: 7 / -1; grid-row: 1 / -1; align-self: end; justify-self: start; width: 88%; margin: 0 0 calc(-1 * var(--s-8)); }
}

/* ---------- the fringe ---------- */
/* One span per strap, laid out by build.mjs (--w width, --h length, --gap to the next root). The spans are the no-JS and
   reduced-motion fringe and the geometry fringe.js measures; once its canvas has drawn them, .live hides the spans. */
.fringe {
  position: absolute; left: 0; right: 0; top: 100%; height: var(--fringe-len); overflow: hidden;
  pointer-events: none; display: flex; align-items: flex-start;
}
.fringe span {
  flex: none; display: block; width: var(--w); height: var(--h); margin-right: var(--gap); background: var(--ink); /* a straight strip, cut square */
}
.fringe canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.fringe.live span { visibility: hidden; }

/* space under a fringed band so the strands never sit on text */
.after-fringe { padding-top: calc(var(--fringe-len) + var(--s-7)); }
.block { margin-top: var(--s-9); }

/* the pieces (B, round 3): 144px of air, one 1px ink rule across the wrap, then a head row like the Top sellers head */
.sect { margin-top: var(--s-10); }
.rule { margin: 0; border: 0; border-top: 1px solid var(--ink); }
.sect-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--s-2) var(--s-5); margin-top: var(--s-6); }
.sect-head .sub { color: var(--ink-2); }
.sect-head-page { margin: var(--s-8) 0 var(--s-6); } .sect-head-page .h-page { margin: 0; }

/* ---------- product tiles ---------- */
.tile a { display: block; text-decoration: none; }
.mat { position: relative; aspect-ratio: 3 / 4; background: var(--mat); isolation: isolate; overflow: hidden; }
.mat img, .shot img { width: 100%; height: 100%; object-fit: contain; mix-blend-mode: multiply; }
/* one shade (Ben, 20 Oct): the mat is the page cream, and the photos step lifts each photo's off-white ground to pure white
   (photos.json _note), so multiply turns the ground into exactly the mat and the page; no box, no band beside a narrow photo.
   The tile reads as a tile by its spacing and the name under it; on hover the lift and its 1px edge (site-b.css). */
/* her shots that run off their left edge (products.json flushLeft) sit against the frame's left edge */
.mat img.flush, .shot img.flush { object-position: 0 50%; }

/* hover photo (Ben, 19 Oct): a tile fades to a second shot, someone wearing the piece where she has one (products.json
   hoverPhoto). The second shot sits on its own mat so it covers the first cleanly. Mouse and trackpad only: touch never
   swaps and never downloads it (display: none keeps the lazy image unloaded). Keyboard focus on the tile does the same.
   A piece with one photo zooms it slightly instead. Reduced motion: --dur-swap is 0, so the swap is instant, and no zoom. */
.ph-alt { position: absolute; inset: 0; display: none; background: var(--mat); isolation: isolate; opacity: 0; transition: opacity var(--dur-swap) var(--ease); }
.mat img.solo { transition: transform var(--dur-swap) var(--ease); }
@media (hover: hover) and (pointer: fine) {
  .ph-alt { display: block; }
  .tile a:hover .ph-alt, .tile a:focus-visible .ph-alt { opacity: 1; }
}
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .tile a:hover .solo, .tile a:focus-visible .solo { transform: scale(1.03); }
}
.tag { position: absolute; top: var(--s-2); left: var(--s-2); z-index: 1; padding: var(--s-1) var(--s-2); background: var(--mat-ink); color: var(--btn-text); }   /* sits on the mat, so it keeps its light colours in dark (the fixed ink and cream of the grommet face) */
.tag-inline { position: static; display: inline-block; margin-top: var(--s-3); background: var(--ink); color: var(--cream); }
.tile-name { font-size: var(--t-name); margin: var(--s-3) 0 var(--s-1); }
.tile-meta { display: flex; flex-wrap: wrap; justify-content: space-between; column-gap: var(--s-3); color: var(--ink-2); }
.tile a:hover .tile-name { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.15em; }
.tile a:focus-visible { outline-offset: var(--s-1); }

.missing { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--s-5); height: 100%; padding: var(--s-5); background: var(--panel); color: var(--mat-ink); text-align: center; }
.missing-head { font-size: var(--t-s); }
.tile-big .missing-head { font-size: var(--t-l); line-height: 1; }
.missing-facts { display: grid; gap: var(--s-3); max-width: 22rem; }
.missing-facts dt { color: var(--mat-ink-2); }
.missing-facts dd { font-size: var(--t-s); color: var(--mat-ink); }

.pieces { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-7) var(--gutter-phone); margin-top: var(--s-6); }
@media (min-width: 768px) { .pieces { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s-8) var(--gutter); } }

/* top sellers: title left, two big tiles right on desktop */
.sellers { display: grid; gap: var(--s-6) var(--gutter); }
.sellers-head .sub { margin-top: var(--s-3); color: var(--ink-2); max-width: 28ch; }
.sellers-list { display: grid; gap: var(--s-7) var(--gutter-phone); }
@media (min-width: 600px) { .sellers-list { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gutter); } }
@media (min-width: 1024px) {
  .sellers { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .sellers-head { grid-column: 1 / 5; }
  .sellers-list { grid-column: 5 / -1; }
}

/* ---------- her story band ---------- */
.story { margin-top: var(--s-9); padding-block: var(--s-9); }
.story-grid { display: grid; gap: var(--s-6) var(--gutter); }
.quote blockquote p { font-family: var(--f-display); font-weight: 500; font-size: clamp(1.375rem, 1rem + 1.4vw, 2.25rem); line-height: 1.3; max-width: 34ch; }
.quote blockquote p::before { content: "\201C"; margin-left: -0.45em; }
.quote blockquote p::after { content: "\201D"; }
.quote figcaption { margin-top: var(--s-5); color: var(--mute); }
@media (min-width: 1024px) {
  .story-grid { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .story-grid .h-section { grid-column: 1 / 6; }
  .quote { grid-column: 7 / -1; }  /* one empty column between, so the heavier heading never runs into the hanging quote mark */
}
@media (max-width: 599px) { .quote blockquote p::before { margin-left: 0; } }

/* ---------- how ordering works ---------- */
.how { padding-bottom: var(--s-9); }
.how-list { display: grid; gap: var(--s-6) var(--gutter); margin-top: var(--s-6); }
.how-list dt { margin-bottom: var(--s-2); }
.how-list dd { max-width: 32ch; }
@media (min-width: 600px) { .how-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .how-list { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* B (round 3): four steps in an ordered list, a fringed arrow pointing to the next (right at 1024 and up, down below).
   The <dl> rules above stay for variant A. */
ol.how-list { gap: var(--how-row-gap) var(--how-gap); list-style: none; padding: 0; }
.how-step { position: relative; }
.how-step h3 { margin-bottom: var(--s-2); }
.how-step p { max-width: 32ch; }
.how-arrow { position: absolute; display: block; width: 68px; height: 32px; fill: currentColor; color: var(--ink); }
.how-arrow svg { display: block; }
/* the arrow's straps: a small fringe box under the shaft that fringe.js moves like the header fringe (data-n, data-k in
   build.mjs). The box runs 8px past the arrow on each side and 4px below the longest strap so a swinging tip is not cut;
   the 11px left padding puts the first 2px strap at x 3, as in the old static SVG. */
.how-arrow .fringe { left: -8px; right: auto; top: 7px; width: 84px; height: 26px; padding-left: 11px; }
.how-step:last-child .how-arrow { display: none; }
@media (max-width: 1023px) {
  ol.how-list { grid-template-columns: minmax(0, 1fr); max-width: 36rem; }
  .how-arrow { left: 0; top: 100%; transform: translateY(26px); }  /* stays horizontal so the fringe hangs down, not sideways (a rotated arrow read as a ladder) */
}
@media (min-width: 1024px) {
  ol.how-list { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .how-arrow { left: 100%; top: 0.1em; margin-left: calc((var(--how-gap) - 68px) / 2); }
}

/* ---------- footer (black band, the fringe again at its bottom edge) ---------- */
.foot { position: relative; margin-bottom: var(--fringe-len); padding-block: var(--s-8) var(--s-7); }
.foot-grid { display: grid; gap: var(--s-5) var(--gutter); }
.foot-mark { font-size: var(--t-xl); letter-spacing: 0.02em; }
.foot a { display: inline-flex; align-items: center; min-height: var(--target); text-decoration: none; }
.foot a:hover { text-decoration: underline; }
.foot-links ul { display: flex; flex-wrap: wrap; column-gap: var(--s-5); }
.foot-legal { color: var(--mute); }
@media (min-width: 768px) {
  .foot-grid { grid-template-columns: repeat(12, minmax(0, 1fr)); align-items: start; }
  .foot-mark { grid-column: 1 / -1; margin-bottom: var(--s-5); }
  .foot-contact { grid-column: 1 / 6; }
  .foot-links { grid-column: 6 / 10; }
  .foot-legal { grid-column: 10 / -1; padding-top: var(--s-3); }
}

/* ---------- inner pages ---------- */
.page { padding-bottom: var(--s-9); }
.h-page { margin: var(--s-8) 0 var(--s-6); }

/* product */
.product { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s-6); padding-top: var(--s-5); padding-bottom: var(--s-9); }
.shot { aspect-ratio: 4 / 5; background: var(--mat); isolation: isolate; overflow: hidden; }
.shot.missing { background: var(--panel); }
.media-rest { display: grid; gap: var(--s-4); }
.back { display: inline-flex; align-items: center; min-height: var(--target); text-decoration: none; }
.back::before { content: "\2190"; margin-right: var(--s-2); }
.back:hover { text-decoration: underline; }
.h-product { margin-top: var(--s-3); }
.type-price { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; gap: var(--s-3); margin-top: var(--s-4); }
.type-price .price { font-size: var(--t-l); font-variant-numeric: tabular-nums; letter-spacing: 0.02em; }
.caption { margin-top: var(--s-5); max-width: 60ch; }
.facts { margin-top: var(--s-6); border-top: 1px solid var(--line); }
.facts > div { display: grid; grid-template-columns: minmax(7rem, 2fr) 5fr; gap: var(--s-4); padding: var(--s-3) 0; border-bottom: 1px solid var(--line); }
.facts dt { padding-top: 0.25em; }
.buy { margin-top: var(--s-6); }
.ship-note { margin-top: var(--s-2); font-size: var(--t-s); color: var(--ink-2); max-width: 52ch; }
.buybar { display: none; }
@media (max-width: 767px) {
  body.has-bar { padding-bottom: calc(var(--bar-h) + env(safe-area-inset-bottom)); }
  .buy-main { display: none; }
  .buy { margin-top: var(--s-4); }
  .buybar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 10; display: flex; align-items: center; justify-content: space-between; gap: var(--s-4);
    min-height: calc(var(--bar-h) + env(safe-area-inset-bottom)); padding: var(--s-3) var(--gutter-phone) calc(var(--s-3) + env(safe-area-inset-bottom));
  }
  .buybar-price { font-size: var(--t-l); }
  .buybar .btn { font-size: var(--t-btn-bar); }
  .buybar-price .coming { font-size: var(--t-xs); }
}
@media (min-width: 768px) {
  .product { grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gutter); row-gap: var(--s-4); align-items: start; padding-top: var(--s-6); }
  .media-first { grid-column: 1 / 7; grid-row: 1; }
  .media-rest { grid-column: 1 / 7; grid-row: 2; }
  .details { grid-column: 7 / -1; grid-row: 1 / span 2; position: sticky; top: var(--s-4); padding-left: var(--s-5); }
}
@media (min-width: 1024px) { .details { padding-left: var(--s-8); } }

/* about */
.about-grid { display: grid; gap: var(--s-7) var(--gutter); }
.portrait { aspect-ratio: 4 / 5; }
.about-what { font-family: var(--f-display); font-weight: 500; font-size: clamp(1.375rem, 1rem + 1.4vw, 2.25rem); line-height: 1.3; max-width: 30ch; }
.about-story { margin-top: var(--s-6); max-width: 60ch; }
.sign { margin-top: var(--s-4); color: var(--ink-2); }
.facts-short { max-width: 36rem; }
@media (min-width: 768px) {
  .about-grid { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .portrait { grid-column: 1 / 6; }
  .about-text { grid-column: 7 / -1; }
}

/* shipping */
.facts-page { max-width: 52rem; }
.facts-page > div { grid-template-columns: minmax(8rem, 1fr) 3fr; padding-block: var(--s-4); }

/* Prototype B (Ben, 19 Oct): the home hero is cream, and the black header is sticky on every page with the live fringe
   hanging from its bottom edge the whole time you scroll. Appended to site.css by build.mjs --variant b; every rule is
   scoped to body.vb, so variant A is untouched. The fringe here is a trim like a suede fringe trim (Ben's photo, 19 Oct):
   5px strips on a 7px pitch, 80px long within 3%, each hanging in a soft S (tokens --fringe-trim*, box --fringe-b-len 90px),
   so it reads as an edge on the header, not a curtain over the page. The footer drops its fringe in B: one fringe
   on screen at a time is calmer, and this one never leaves. */

.vb .top { position: sticky; top: 0; z-index: 30; }
.vb .top .fringe { height: var(--fringe-b-len); }

/* page tops start below the strands, so at scroll 0 nothing sits under them */
.vb #main { padding-top: calc(var(--fringe-b-len) + var(--s-2)); }

/* the cream hero: ink wordmark and lede (no .on-ink in B), her cut-out necklace straight on cream, the editorial button as everywhere */
.vb .hero-body { padding-top: var(--s-6); }  /* Ben, 20 Oct: air between the header fringe and the logo (was 0) */
@media (min-width: 768px) { .vb .hero-body { padding-top: var(--s-8); } }
.vb .after-fringe { padding-top: var(--s-9); }

.vb .foot { margin-bottom: 0; }

/* the header carries only her tFt monogram (Ben, 19 Oct), at every width; the link is named "The Fringe Ting, home"
   in build.mjs, so the image is decorative. A little larger than in A because it stands alone. */
.vb .mark-icon { height: 40px; margin-right: 0; }

/* phone: one compact row (monogram, the three links, the theme toggle); the Instagram handle is in the footer */
@media (max-width: 599px) {
  .vb .top-row { grid-template-columns: auto 1fr auto; grid-template-areas: "mark links ig"; column-gap: var(--s-3); }
  .vb .links ul { justify-content: flex-end; gap: var(--s-3); }
  .vb .mark-icon { height: 34px; }
  .vb .ig { display: none; }
}
@media (max-width: 379px) { .vb .mark-icon { height: 28px; } .vb .links ul { gap: var(--s-2); } }

/* round 3: the fringe is sewn onto the header: a cream thread line, then an ink binding strip; the fringe hangs from the
   strip (.fringe is top: 100% of the header, so it starts under the seam). In dark: cream header, ink thread, cream strip. */
.vb .top .seam { height: calc(var(--seam-thread) + var(--seam-strip)); background: var(--cream); border-bottom: var(--seam-strip) solid var(--ink); }

/* round 3: the whole tile rises 6px and the mat gets a 1px ink edge; the photo swap and name underline carry on.
   Keyboard focus does the same. Reduced motion: --lift and --dur-lift are 0, so the edge shows and nothing moves. */
.vb .tile a { transition: transform var(--dur-lift) var(--ease); }
.vb .mat { outline: 1px solid transparent; outline-offset: -1px; transition: outline-color var(--dur-lift) var(--ease); }
@media (hover: hover) and (pointer: fine) { .vb .tile a:hover { transform: translateY(calc(-1 * var(--lift))); } .vb .tile a:hover .mat { outline-color: var(--ink); } }
.vb .tile a:focus-visible { transform: translateY(calc(-1 * var(--lift))); } .vb .tile a:focus-visible .mat { outline-color: var(--ink); }

/* the hero is her logo (Amanda, 19 Oct: "she wants the hero to just BE the icon"): the tFt monogram, traced to SVG from
   her logo sheet until her files arrive, inline and filled with the text colour (ink on cream, cream in dark), aria-hidden.
   Under it, like the "With wordmark" lockup on her logo sheet, THE FRINGE TING in Bodoni caps, wide-tracked and centred,
   much smaller (Ben, 19 Oct): it is the h1's visible text. Sized to the icon (cqi), so the lockup keeps its proportions
   at every width. The icon's box holds the viewBox ratio, so nothing shifts. */
.vb .hero-logo { position: relative; z-index: 1; grid-row: 1; margin: 0; container-type: inline-size; text-align: center; }
.vb .hero-mark { display: block; width: 100%; height: auto; aspect-ratio: 4222 / 2158; }
/* the strands on the two t's are live (Amanda, then Ben, 19 Oct): fringe.js draws the t's and their wings on a canvas laid
   over the SVG (placed and sized by the script, a little larger than the logo so the tips have room) and only then hides
   the SVG's t's, so no frame is ever without them; the F stays the SVG. Without JavaScript the SVG is the whole logo. */
.vb .hero-mark-box { position: relative; display: block; }
.vb .hero-mark-box canvas { position: absolute; display: block; pointer-events: none; }
.vb .hero-mark-box.live .hm-t { visibility: hidden; }
.vb .hero-name { display: block; margin-top: 3.5cqi; font-family: var(--f-display); font-variation-settings: "opsz" var(--opsz-display); font-size: 20px; font-size: 5.2cqi; font-weight: 700; line-height: 1; letter-spacing: 0.2em; margin-right: -0.2em; text-transform: uppercase; white-space: nowrap; }
.about-what mark { background: none; color: inherit; font-style: italic; }  /* the italic phrase, as it was in the home hero */
@media (min-width: 768px) { .vb .hero-logo { grid-column: 1 / 8; grid-row: 1; } }
/* tablet: the necklace starts a column later (no lede under the lockup, so it rides higher), clear of the right wing */
@media (min-width: 768px) and (max-width: 1023px) { .vb .hero-cut { grid-column: 8 / -1; width: 100%; } }
/* desktop: six columns, so the right wing ends where the necklace's strands begin instead of tangling with them */
@media (min-width: 1024px) { .vb .hero-logo { grid-column: 1 / 7; } }
/* phone: About's intro line (the old home lede) comes first, above the portrait, so it is near the top on every width */
@media (max-width: 767px) {
  .vb .about-grid { row-gap: 0; }
  .vb .about-text { display: contents; }
  .vb .about-what { order: -1; margin-bottom: var(--s-7); }
}
/* the button sits centred right under the lockup, in the lockup's own column, so icon, name and button read as one stack (Ben, 19 Oct) */
.vb .hero-text { text-align: center; margin-top: var(--s-6); }
@media (min-width: 768px) { .vb .hero-text { grid-column: 1 / 8; grid-row: 2; align-self: start; margin-top: var(--s-6); } }
@media (min-width: 1024px) { .vb .hero-text { grid-column: 1 / 7; } }
/* phone: the necklace centred under the button, on the same axis as the lockup (Ben, 19 Oct) */
@media (max-width: 767px) { .vb .hero-cut { justify-self: center; } }
/* the long-copy voice from her Instagram composites (Amanda, 19 Oct): heavy grotesque caps, tight tracking, set solid and
   justified. Her graphics use a Helvetica-style bold; Archivo at full width and weight 800 is the same shape, already loaded. */
.vb .caption, .vb .about-text p, .vb .quote blockquote p { font-family: var(--f-text); font-stretch: 100%; font-weight: 800; text-transform: uppercase;
  letter-spacing: -0.035em; word-spacing: 0.04em; line-height: 1.02; font-size: clamp(1.0625rem, 0.95rem + 0.5vw, 1.375rem);
  text-align: justify; hyphens: manual; max-width: 34ch; }
.vb .quote blockquote p { max-width: 30ch; }  /* the home story quote in the same voice (Ben, 19 Oct) */
/* phone: the bandana hero is landscape, so it takes most of the width (the portrait necklace sat at 62%) */
@media (max-width: 767px) { .vb .hero-cut { width: 90%; } }
/* phone: the whole hero (logo, name, button, bandana) fits the first screen of a phone browser (Ben, 20 Oct: the bandana was cut off at the bottom) */
@media (max-width: 767px) {
  .vb .hero-body { padding-top: var(--s-4); }
  .vb .hero-logo { width: 86%; justify-self: center; }
  .vb .hero-text { margin-top: var(--s-5); }
  .vb .hero-cut { width: 76%; margin-top: var(--s-1); }
}
