/* ==========================================================================
   SELVEDGE — the loominus.art migration family.
   The page is structured as a bolt of cloth: a fixed warp (the selvedge rail
   down the left edge), content arriving as weft bands in a repeating stripe
   sequence, bands bound by drawn selvedge edges instead of rules or shadows,
   and a CSS/SVG-drawn weave swatch as the masthead (no photographic hero).

   Palette: measured from the products the site sells (brief §5A, computed
   contrast beside every pair). THREE PERMANENT PINS:
     · --wayuu-pink #c94f7c is SWATCH ONLY — white on it 4.29 FAIL, ink 4.04
       FAIL. It has no accessible label colour at all. It may appear only
       inside the drawn weave, never as text and never as a fill behind text.
     · white-on-marigold 2.22 FAIL — marigold fills carry INK text only.
     · white-on-turquoise 3.16 FAIL — turquoise fills carry INK text only.

   Type: Fraunces 600 (display — humanist, visible pen-warmth) + Work Sans
   400/600 (body + specimen captions). Both cleared through
   self-hosted-font-licensing 2026-08-22: latin subsets, copyright read from
   the shipped binaries, NO Reserved Font Name declared by either family —
   see fonts/OFL.txt MODIFICATION STATUS. Body 19px minimum, 1.6 (brief §5B).

   Motion: the masthead weave draws itself in once on load — ≤300ms, and
   prefers-reduced-motion turns it off entirely. Prose never animates
   (family invariant). No cursor effects, no ripple.
   ========================================================================== */

:root {
  --linen:      #faf6ef;  /* page ground */
  --cotton:     #f2ebdd;  /* panel / band ground */
  --ink:        #1d1a17;  /* headings           16.08 / 14.60 AAA */
  --ink-read:   #2e2823;  /* body prose         13.50 / 12.26 AAA */
  --ink-muted:  #443c34;  /* captions, meta     10.04 /  9.12 AAA */
  --ink-faint:  #6b5f52;  /* fine print (floor)  5.76 /  5.23 AA  */
  --madder:     #7a1f3d;  /* link text           9.32 /  8.47 AAA */
  --indigo:     #3b2f6b;  /* visited / accent   10.80 /  9.81 AAA */
  --teal:       #0f5c63;  /* accent text         7.14 AAA / 6.48 AA */
  --walnut:     #8a4b0f;  /* accent text         6.30 /  5.72 AA  */
  --cta:        #a3244f;  /* CTA fill — white on it 7.19 AAA */
  --cta-hover:  #8a1d42;
  --edge:       #8a7b6b;  /* selvedge rule, focus ring — non-text 3.80 ≥ 3.0 */
  --wayuu-pink: #c94f7c;  /* 🔴 SWATCH ONLY — never text, never behind a label */
  --marigold:   #e0a33c;  /* fill only — ink on it 7.81 AA; white FAILS 2.22 */
  --turquoise:  #2aa198;  /* fill only — ink on it 5.48 AA; white FAILS 3.16 */
  --hairline:   #e2d8c4;  /* figure borders on linen/cotton (non-text decoration) */

  --font-display: 'Fraunces', 'Iowan Old Style', Georgia, serif;
  --font-text:    'Work Sans', 'Helvetica Neue', Arial, sans-serif;

  --rail-w: 18px;
  --measure-prose: 42rem;
  --measure-wide:  72rem;
}

@font-face {
  font-family: 'Fraunces';
  src: url('/selvedge/fonts/fraunces-latin-600-normal.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Work Sans';
  src: url('/selvedge/fonts/work-sans-latin-400-normal.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Work Sans';
  src: url('/selvedge/fonts/work-sans-latin-600-normal.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

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

body {
  background: var(--linen);
  color: var(--ink-read);
  font-family: var(--font-text);
  font-size: 1.1875rem;          /* 19px floor — brief §5B */
  line-height: 1.6;
  padding-left: var(--rail-w);   /* room for the warp rail */
  -webkit-font-smoothing: antialiased;
}

/* ── The warp: a selvedge rail down the left edge of every page ─────────── */
.selvedge-rail {
  position: fixed;
  inset: 0 auto 0 0;
  width: var(--rail-w);
  background:
    repeating-linear-gradient(
      180deg,
      var(--madder)     0 22px,
      var(--cotton)   22px 26px,
      var(--indigo)   26px 48px,
      var(--cotton)   48px 52px,
      var(--teal)     52px 74px,
      var(--cotton)   74px 78px,
      var(--walnut)   78px 100px,
      var(--cotton)  100px 104px
    );
  border-right: 2px solid var(--edge);
  z-index: 5;
}

/* ── Weft bands ─────────────────────────────────────────────────────────── */
.band { padding: 3.5rem 1.5rem; }
.band--cotton { background: var(--cotton); }
.band > .band-inner { max-width: var(--measure-wide); margin: 0 auto; }
.band > .band-inner--prose { max-width: var(--measure-prose); margin: 0 auto; }

/* the drawn selvedge edge that binds two bands — never a rule, never a shadow */
.selvedge-edge {
  /* margin: 0 comes from the reset. Between BANDS that is right — the bands'
     own 3.5rem padding supplies the air. Inside a band it is wrong: the edge
     landed hard against the bottom of a photograph. See the rule below. */
  height: 10px;
  border: 0;
  background:
    repeating-linear-gradient(90deg,
      var(--madder) 0 16px, transparent 16px 24px,
      var(--indigo) 24px 40px, transparent 40px 48px) top / 100% 4px no-repeat,
    repeating-linear-gradient(90deg,
      transparent 0 8px,
      var(--teal) 8px 24px, transparent 24px 32px,
      var(--walnut) 32px 48px) bottom / 100% 4px no-repeat;
}

/* An edge used as a DIVIDER WITHIN a band has no band padding to sit in, so it
   supplies its own — otherwise it crowds the figure directly above it
   (reported on /selvedge/bags/, where three .split rows are divided this way). */
.band-inner > .selvedge-edge,
.band-inner--prose > .selvedge-edge { margin: 3rem 0; }

/* the "pick" — a thread-count label opening each band */
.pick {
  display: inline-block;
  font-family: var(--font-text);
  font-weight: 600;
  font-size: 0.875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--teal);
  border-bottom: 3px double var(--edge);
  padding-bottom: 0.3rem;
  margin-bottom: 1.4rem;
}

/* ── Masthead ───────────────────────────────────────────────────────────── */
.masthead { padding: 2rem 1.5rem 2.75rem; }
.masthead .band-inner { max-width: var(--measure-wide); margin: 0 auto; }

.brandline {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem 1rem;
  margin-bottom: 1.5rem;
}
.brandline .wordmark {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.65rem;
  color: var(--ink);
  letter-spacing: 0.01em;
}
.brandline .tagline {
  font-size: 1rem;
  color: var(--ink-muted);
  letter-spacing: 0.04em;
}

.weave-swatch { display: block; width: 100%; height: auto; }
.swatch-frame {
  border: 2px solid var(--edge);
  line-height: 0;
  margin-bottom: 2rem;
}

/* the weave draws itself in — once, ≤300ms, and not at all under
   prefers-reduced-motion. Motion that depicts the subject belongs;
   motion that arrives by default does not. */
.weave-swatch .weft {
  transform-origin: left center;
  animation: weft-in 0.22s ease-out backwards;
}
.weave-swatch .weft:nth-child(2) { animation-delay: 0.04s; }
.weave-swatch .weft:nth-child(3) { animation-delay: 0.08s; }
.weave-swatch .weft:nth-child(4) { animation-delay: 0.12s; }
.weave-swatch .weft:nth-child(5) { animation-delay: 0.16s; }
@keyframes weft-in { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@media (prefers-reduced-motion: reduce) {
  .weave-swatch .weft { animation: none; }
}

h1 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(2.3rem, 5.5vw, 3.4rem);
  line-height: 1.12;
  color: var(--ink);
  max-width: 24ch;
}
.masthead .deck {
  margin-top: 1rem;
  font-size: 1.25rem;
  color: var(--ink-muted);
  max-width: 52ch;
}

h2 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.5rem, 3vw, 1.95rem);
  line-height: 1.2;
  color: var(--ink);
  margin: 0 0 1rem;
  max-width: 30ch;
}
* + h2 { margin-top: 2.2rem; }

p { margin: 0 0 1.15rem; max-width: 65ch; }
strong { font-weight: 600; color: var(--ink); }

a { color: var(--madder); text-underline-offset: 3px; }
a:visited { color: var(--indigo); }
a:hover { text-decoration-thickness: 2px; }
a:focus-visible, button:focus-visible, .btn:focus-visible {
  outline: 3px solid var(--edge);
  outline-offset: 2px;
}

/* ── Buttons — pill, as the source drew them (radius 36) ────────────────── */
.btn-row { display: flex; flex-wrap: wrap; gap: 0.9rem; margin-top: 1.6rem; }
.btn {
  display: inline-block;
  background: var(--cta);
  color: #fff;                    /* 7.19 AAA on --cta */
  font-family: var(--font-text);
  font-weight: 600;
  font-size: 1.0625rem;
  text-decoration: none;
  padding: 0.8rem 1.7rem;
  border-radius: 36px;
}
.btn:visited { color: #fff; }
.btn:hover { background: var(--cta-hover); }
.btn--quiet {
  background: transparent;
  color: var(--madder);
  border: 2px solid var(--edge);
}
.btn--quiet:visited { color: var(--madder); }
.btn--quiet:hover { background: rgba(138, 123, 107, 0.12); }

/* ── Two-column info band (source: 55/45 rowlayout) ─────────────────────── */
.split {
  display: grid;
  grid-template-columns: 55fr 45fr;
  gap: 2.5rem 3.5rem;
  align-items: start;
}
@media (max-width: 860px) { .split { grid-template-columns: 1fr; } }

/* ── The loom list (source: kadence/iconlist) ───────────────────────────── */
.loomlist { list-style: none; margin: 1.2rem 0 0; }
.loomlist li {
  position: relative;
  padding-left: 1.9rem;
  margin-bottom: 0.85rem;
  max-width: 60ch;
}
.loomlist li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.42em;
  width: 0.72em; height: 0.72em;
  background: var(--madder);
  transform: rotate(45deg);      /* a woven pick, not a bullet */
}
.loomlist li:nth-child(2n)::before { background: var(--teal); }
.loomlist li:nth-child(3n)::before { background: var(--indigo); }

ul.plainlist { margin: 0 0 1.15rem 1.4rem; max-width: 63ch; }
ul.plainlist li { margin-bottom: 0.5rem; }

/* ── Figures & the specimen grid ────────────────────────────────────────── */
figure { margin: 0; }
figure img {
  display: block;
  width: 100%;
  height: auto;                  /* natural aspect preserved; cells sit well
                                    under the files' native ~1024px, so no
                                    image is displayed wider than its pixels */
  border: 1px solid var(--hairline);
  background: var(--cotton);
}
figcaption {
  font-size: 0.9375rem;
  line-height: 1.45;
  color: var(--ink-muted);
  padding: 0.45rem 0.15rem 0;
}

/* BRICKS, not rows. A grid aligns every cell in a row to the same top, so a
   tall portrait shot beside a short landscape one leaves a band of dead ground
   under the short one. Multi-column flow packs each brick against the one above
   it — the specimens stack like a wall, which is also how cloth is stacked.
   Reading order runs DOWN a column and then across; for an unordered specimen
   inventory that is a fair trade for losing the ragged bottoms. */
.specimens {
  column-width: 244px;
  column-gap: 1.25rem;
}
.specimens a {
  display: block;
  text-decoration: none;
  break-inside: avoid;          /* a photograph must never split across columns */
  margin-bottom: 1.25rem;
}
.specimens a:hover img { border-color: var(--edge); }
/* the bound lower edge that gives each specimen its selvedge */
.specimens figure img { border-bottom: 4px solid var(--edge); }
/* The grid carries NO text: every caption belongs to the lightbox now (a wall of
   bricks reads as cloth; a wall of bricks each wearing a label reads as a
   spreadsheet). Belt-and-braces in case a stray figcaption is ever emitted. */
.specimens figcaption { display: none; }

.minigrid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1rem;
  align-items: start;
}

/* ── Lightbox — a real <dialog>; JS-off, the anchor opens the image file ── */
/* THE FRAME IS A FIXED STAGE, NOT A BOX THAT FITS THE PHOTOGRAPH.
   Two defects fixed here, both reported on /selvedge/bags-wayuu-colombia/:
     1. The dialog opened pinned to the TOP-LEFT of the viewport. The UA
        stylesheet centres a modal dialog with `margin: auto`; the reset at the
        top of this file (`* { margin: 0 }`) silently overrode it. `margin: auto`
        is restored below and must stay.
     2. Previous/Next moved every time the photograph's aspect changed, because
        the frame sized itself to the image. The frame now has a FIXED width and
        height, the image is centred inside a stage that absorbs the difference,
        and the controls are right-aligned in a bar of stable height — so the
        two buttons hold one position for the whole collection and a visitor can
        click through 137 specimens without chasing them. */
dialog.lightbox {
  margin: auto;                  /* 🔴 restores the UA centring the reset killed */
  border: 0;
  padding: 0;
  background: transparent;
  width: min(94vw, 1080px);
  max-width: min(94vw, 1080px);
  height: min(92vh, 940px);      /* fixed — this is what holds the buttons still */
  max-height: 92vh;
}
dialog.lightbox::backdrop { background: rgba(29, 26, 23, 0.86); }
dialog.lightbox .lb-frame {
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;   /* stage, then the control bar */
  height: 100%;
  background: var(--linen);
  border: 2px solid var(--edge);
  padding: 0.9rem;
}
/* The stage: the photograph is centred in it both ways, whatever its shape.
   The image is ABSOLUTELY POSITIONED, and that is load-bearing rather than
   stylistic. Centred as an ordinary grid item, its `max-height: 100%` did not
   resolve — a 954x1024 portrait rendered at natural size in a 779px-tall stage
   and was clipped at the bottom (measured, not assumed). Against an absolutely
   positioned box the percentage resolves against the stage's padding box, which
   is definite, and `inset: 0` + `margin: auto` centres on both axes.
   width/height stay `auto`, so no photograph is ever upscaled past its own
   pixels — the same rule the specimen grid keeps. */
dialog.lightbox .lb-stage {
  position: relative;
  min-height: 0;                 /* let the 1fr row actually shrink */
  overflow: hidden;
}
dialog.lightbox img {
  position: absolute;
  inset: 0;
  margin: auto;                  /* 🔴 with inset:0 this is the centring */
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto; height: auto;
}
/* caption | purchase | nav — nav is the last column, so it is flush right and
   therefore in the SAME PLACE on every specimen. A purchase button appearing
   for one piece and not the next grows the middle column, never the nav. */
dialog.lightbox .lb-bar {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 0.5rem 1rem;
  padding-top: 0.8rem;
  min-height: 3.6rem;            /* stable even when the caption wraps */
}
dialog.lightbox .lb-meta { min-width: 0; grid-column: 1; }
dialog.lightbox .lb-caption {
  font-size: 1rem;
  color: var(--ink-muted);
  margin: 0;
  max-width: none;
  display: -webkit-box;          /* two lines maximum — a third would move the bar */
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
dialog.lightbox .lb-count {
  display: block;
  font-size: 0.9375rem;
  color: var(--ink-faint);
  letter-spacing: 0.06em;
}
/* 🔴 The columns are pinned EXPLICITLY, and that is the whole point of them.
   A display:none purchase slot leaves the box tree altogether, so with auto
   placement the nav slid up into column 2 and Previous/Next jumped 16px sideways
   between a piece that is for sale and one that is not (measured). Pinned to
   column 3, the nav holds its position whether the slot is filled or empty. */
dialog.lightbox .lb-nav { display: flex; gap: 0.5rem; grid-column: 3; }
dialog.lightbox button {
  font: 600 1rem var(--font-text);
  color: var(--madder);
  background: transparent;
  border: 2px solid var(--edge);
  border-radius: 36px;
  padding: 0.45rem 1rem;
  cursor: pointer;
  white-space: nowrap;
}
dialog.lightbox button:hover { background: rgba(138, 123, 107, 0.14); }

/* The purchase link — rendered ONLY for a specimen whose data says published
   AND in stock (see lightbox.js). Until product pages exist no anchor carries
   that data, so this slot is empty on every page today. */
dialog.lightbox .lb-buy {
  grid-column: 2;
  display: none;                 /* lightbox.js flips this per specimen */
  font: 600 1rem var(--font-text);
  text-decoration: none;
  color: #fff;
  background: var(--cta);        /* white on --cta 7.19 AAA */
  border: 2px solid var(--cta);
  border-radius: 36px;
  padding: 0.45rem 1.15rem;
  white-space: nowrap;
}
dialog.lightbox .lb-buy:visited { color: #fff; }
dialog.lightbox .lb-buy:hover { background: var(--cta-hover); border-color: var(--cta-hover); }
dialog.lightbox .lb-buy[data-shown="true"] { display: inline-block; }

/* Narrow viewports: the bar stacks, and the nav stays a full-width row of its
   own so the buttons are still in one predictable place.

   The stage stays TALL here on purpose. The tallest specimen in these
   collections is 385x1024 (h/w 2.66) — on a phone-width stage it is
   height-constrained and needs every pixel. Shrinking the frame to hug a
   landscape shot would shrink that one too, so a short photograph is allowed to
   sit in some linen instead. Air above a small image is the price of the buttons
   never moving, and that trade is the requirement here, not a compromise. */
@media (max-width: 640px) {
  dialog.lightbox { height: min(94vh, 940px); }
  dialog.lightbox .lb-bar {
    grid-template-columns: 1fr;
    justify-items: stretch;
    min-height: 0;
  }
  /* one column here, so the pins above must be released or they would create
     implicit columns 2 and 3 off the side of the frame */
  dialog.lightbox .lb-meta,
  dialog.lightbox .lb-buy,
  dialog.lightbox .lb-nav { grid-column: 1; }
  dialog.lightbox .lb-nav { justify-content: space-between; }
  dialog.lightbox .lb-buy[data-shown="true"] { display: block; text-align: center; }
}

/* ── Footer ─────────────────────────────────────────────────────────────── */
.foot { padding: 2.5rem 1.5rem 3rem; background: var(--cotton); }
.foot .band-inner { max-width: var(--measure-wide); margin: 0 auto; }
.foot .wordmark {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.3rem;
  color: var(--ink);
}
.foot nav { margin: 0.9rem 0 1.1rem; display: flex; flex-wrap: wrap; gap: 0.5rem 1.6rem; }
.foot nav a { font-size: 1rem; }
.foot .fine { font-size: 1rem; color: var(--ink-faint); max-width: 70ch; }

/* ── Small screens: the rail becomes the page's top selvedge ────────────── */
/* ── Tile row (source: kadence/column with a section-link overlay) ───────
   The source builds these as whole-card links via an absolutely positioned
   overlay anchor. Here the anchor IS the card, which is the same affordance
   with none of the overlay's focus and screen-reader problems. A tile whose
   destination we refused renders WITHOUT an anchor (see /about's Blog tile) —
   it keeps the source's label rather than inventing or deleting one. */
.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 1.1rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.tiles > li { margin: 0; }
.tile {
  display: block;
  padding: 1.5rem 1.25rem;
  background: var(--linen);
  border: 1px solid var(--hairline);
  border-bottom: 4px solid var(--madder);
  text-decoration: none;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.3rem;
  color: var(--ink);
}
.tiles > li:nth-child(2n) .tile { border-bottom-color: var(--teal); }
.tiles > li:nth-child(3n) .tile { border-bottom-color: var(--indigo); }
a.tile:visited { color: var(--ink); }
a.tile:hover { background: var(--cotton); border-bottom-width: 6px; }
/* Not a link: the source's destination was a page this migration refuses, so
   the label survives and the affordance does not. Marked so nobody "fixes" it
   back into a link without reading the guide. */
.tile--unlinked { color: var(--ink-muted); border-bottom-style: dotted; }
.tile .tile-note {
  display: block;
  margin-top: 0.4rem;
  font-family: var(--font-text);
  font-weight: 400;
  font-size: 0.9375rem;
  color: var(--ink-faint);
}

/* ── Sign-off (source: a paragraph of <br>-separated lines) ─────────────── */
.signoff {
  margin-top: 1.8rem;
  padding-top: 1.1rem;
  border-top: 2px solid var(--edge);
  font-family: var(--font-display);
  font-size: 1.1rem;
  line-height: 1.5;
  color: var(--ink-muted);
  max-width: 40ch;
}

/* ── Contact particulars (source: three h5 blocks used as a data list) ─── */
.particulars { margin: 1.4rem 0 0; }
.particulars > div { margin-bottom: 1.15rem; }
.particulars dt {
  font-size: 0.9375rem;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 0.2rem;
}
.particulars dd {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.35rem;
  line-height: 1.35;
  color: var(--ink);
}

/* ── A figure that runs the full band width (a TRANSFORMED CSS background) ─
   A background image has no alt and no intrinsic size, so brief §4 turns these
   into real <img>. Capped at the file's own pixel width so the natural-width
   pin (§7 clause 6) holds. */
.figure-wide { max-width: var(--measure-wide); margin: 0 auto; }
.figure-wide img { max-width: 100%; }

/* ── A declared gap where something is BLOCKED ──────────────────────────────
   Brief §7 clause 10: a blocked element must render as an EXPLICIT VISIBLE GAP,
   never as an omission. So this is styled to be noticed and read, not to blend
   in — a visible band with a bound edge, in the fine-print colour so it does
   not compete with the page's own prose. If it looked like nothing, it would be
   an omission with extra steps. */
.gap-blocked {
  border: 1px solid var(--edge);
  border-left: 6px solid var(--walnut);
  background: var(--linen);
  padding: 1.3rem 1.4rem 0.4rem;
  max-width: 60ch;
}
.gap-blocked .gap-label {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.9375rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--walnut);
  margin-bottom: 0.5rem;
}
.gap-blocked p { color: var(--ink-muted); font-size: 1.0625rem; }

@media (max-width: 640px) {
  body { padding-left: 0; }
  .selvedge-rail {
    position: static;
    width: 100%;
    height: 14px;
    border-right: 0;
    border-bottom: 2px solid var(--edge);
    background:
      repeating-linear-gradient(
        90deg,
        var(--madder)     0 22px,
        var(--cotton)   22px 26px,
        var(--indigo)   26px 48px,
        var(--cotton)   48px 52px,
        var(--teal)     52px 74px,
        var(--cotton)   74px 78px,
        var(--walnut)   78px 100px,
        var(--cotton)  100px 104px
      );
  }
  .band { padding: 2.5rem 1.15rem; }
  .split { gap: 1.8rem; }
}
