/* ==========================================================================
   Homepage-only components: the pattern-sheet graphic, the woven-label
   buttons, and the contact form.

   Kept out of site.css because the sub-pages use none of it and /about has
   about half a kilobyte of headroom against its 60 KB budget.
   ========================================================================== */

/* ==========================================================================
   ONE HAND — the rules every fashion device on this page obeys.

   There are six of them below (pin, pattern sheet, tape, woven label, swing
   tag, social button) and they only stay a family, rather than a catalogue,
   because they share these five things. Change them here, not per component.

     --hair       every ornamental line, everywhere. 1px, never 2.
     --stitch     the dashed run. Same dash, same colour, same opacity, from
                  the care labels to the swing tag to the social buttons.
     --ink        printed type on any of the grounds.
     --kraft      the recycled-board ground. Sits between --cream-20 and
                  --brown-20 on the existing ramp, so it is a new tone in the
                  family rather than a new colour.
     focus        2px solid --orange-60, offset 3px, on every one of them.

   Accent is --orange-70 on hover and nowhere else, at most one per viewport.
   ========================================================================== */
:root {
    --hair: 1px solid var(--brown-50);
    --stitch: 1px dashed var(--brown-50);
    --stitch-o: 0.55;
    --ink: var(--brown-70);
    /* Measured against the type that sits on it: --brown-70 reads 6.7:1 here,
       so the 11px field labels clear AA comfortably. A step darker and the
       small caps start to struggle. */
    /* The board went dark. Kraft stays for the tape, which sits on cream and
       has to read as a paper strip, not as a hole in the page. */
    --kraft: #CDB893;
    --kraft-dk: #BCA57C;
    /* the exposed core of a torn edge, paler than the printed face */
    --kraft-edge: #E3D3B4;

    /* Corrugated box. #332619 was too dark and #CDB893 was too pale — the
       first read as a hole in the page, the second washed out the moment the
       sheen went over it. #BC9F72 sits between: 2.08:1 against the cream
       ground, so it is plainly a separate object, and 6.72:1 under the type,
       so the type still reads. The sheen is down to 0.08 and lifts the worst
       patch only to #C1A67B, which every pair below is measured against too.

       Every value here was picked from a measurement, not by eye. The table
       is in NOTES. */
    --board:      #BC9F72;
    --board-deep: #7A6242;
    /* The torn edge is a dark line with a pale core above it, not a pale strip
       on its own: no lighter tone can reach 3:1 against a light board, and a
       real torn edge shows both anyway. The dark line carries the ratio. */
    --board-cut:  #4A3728;   /* 4.46:1 — the cut line */
    --board-core: #F0E5CE;   /* the exposed fibre, decorative, sits on the cut */
    --board-ink:  #241B12;   /* values, 6.72:1 */
    --board-mid:  #40301F;   /* labels and legends, 5.02:1 */
    --board-rule: #5C4633;   /* the field's only boundary, 3.51:1 */
    --board-acc:     #7A310A;   /* focus rings, 3.67:1 */
    /* The fields are printed panels on the board — the white boxes on a paper
       form. #E6D9C3 is cream at 0.62 over the board; it cannot reach 3:1
       against the board (nothing tintable can, the ceiling is about 2.1) so
       the 3:1 is carried by the border, which is what 1.4.11 asks for anyway.
       The panel is what makes the field findable; the border is what makes it
       identifiable. */
    --field:       #E6D9C3;
    --field-hover: #F0E8D8;
    --field-focus: #FBF6ED;
    --board-acc-txt: #5E2406;   /* error and status copy, 4.82:1 */

    /* The buttons are objects, so they have their own cloth rather than the
       page's. 7.3:1 against the cream ground they sit on. */



    --cloth:      #5C4633;
    --cloth-hi:   #7A5F45;
}

/* ============================ 1. THE SAFETY PIN ============================
   A straight line with a head on it is a sewing needle. What makes a safety
   pin readable is the coil and the clasp, and both need real curves — so both
   are inline SVG at natural size, and only the two arms, which are straight,
   are CSS.

   The stroke is 1px at 1:1 in both drawings, so it meets the 1px CSS arms with
   no seam. The label sits between the arms: the pin is fastened through it. */
.pin {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    color: var(--brown-70);
    text-decoration: none;
    transition: color var(--t-micro) var(--ease);
}
.pin__coil, .pin__clasp { flex: none; display: block; }
.pin__label {
    position: relative;
    /* Exactly as tall as the drawings, and centred with them, so a y in the
       viewBox is the same y here. Everything below depends on that. */
    display: inline-flex;
    align-items: center;
    height: 32px;
    padding: 0 12px;
    color: var(--brown-70);
    font-size: 0.8125rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    line-height: 1;
    background-image:
        /* TOP ARM, y = 6 — under the cloth. No line, only a ridge: a soft
           highlight above the axis and a soft shadow below it, symmetric about
           50% of the band so the crest lands on y = 6.0 exactly, which is
           where the wire enters at the coil and re-emerges at the clasp. */
        linear-gradient(to bottom,
            rgba(255, 252, 245, 0) 0%,
            rgba(255, 252, 245, 0.22) 16%,
            rgba(255, 252, 245, 0.58) 34%,
            rgba(255, 252, 245, 0.14) 47%,
            rgba(74, 55, 40, 0.05) 53%,
            rgba(74, 55, 40, 0.19) 66%,
            rgba(74, 55, 40, 0.06) 84%,
            rgba(74, 55, 40, 0) 100%),
        /* BOTTOM ARM, y = 27 — on top of the cloth, so it is drawn. This is
           the same three passes the SVGs use, composited into one gradient:
           body #65523E from 25.90, highlight #E8E1D5 at 26.05-26.75, body again to
           28.10, shadow to 29.20. The metal gradient is vertical, so the arm
           is this exact colour at both seams. */
        linear-gradient(to bottom,
            transparent 0 10.0%,
            #65523E 10.0% 13.75%,
            #E8E1D5 13.75% 31.25%,
            #65523E 31.25% 65.0%,
            rgba(74, 55, 40, 0.34) 65.0% 92.5%,
            transparent 92.5% 100%);
    background-size: 100% 11px, 100% 4px;
    background-position: 0 0.5px, 0 25.5px;
    background-repeat: no-repeat;
}
.pin:hover { color: var(--orange-70); }
.pin:focus-visible { outline: 2px solid var(--orange-60); outline-offset: 4px; }

/* ============================= 2. THE DRESS FORM =============================
   Drawn, not modelled. A WebGL form would have cost the whole remaining
   budget and read worse at 340px than a line drawing does.

   Same hairline language as the sheet it stands on — 1.1px for the body and
   the stand, 0.8px and dashed for the construction marks — so it belongs to
   the paper rather than sitting on top of it. */
.prodsec { display: grid; gap: var(--s8); }

@media (min-width: 1040px) {
    /* The form takes the right third for the full height of the section, so
       the copy and the four products fill the left rather than running as a
       narrow band across an empty page. */
    .prodsec {
        grid-template-columns: minmax(0, 1.72fr) minmax(0, 1fr);
        column-gap: var(--s9);
        align-items: stretch;
    }
    /* Two-up inside the narrower column, not four-across at 240px each. */
    .prodsec .prods--four { grid-template-columns: 1fr 1fr; gap: var(--s6) var(--s7); }
}

/* min-width: 0 because it holds a grid; without it the product cards can set
   a max-content floor and push the drawing out of its column. */
.prodsec__main { min-width: 0; }
.prodsec__form { display: flex; justify-content: center; }
.dressform {
    width: 100%;
    max-width: 340px;
    height: auto;
    /* It is a lit object now rather than a line, so it carries more of its
       own weight and needs less help from opacity. Still under 1 so the
       pattern sheet reads through the edges of it and the two stay one
       drawing rather than one drawing on top of another. */
    opacity: 0.88;
}
@media (max-width: 1039.98px) {
    /* Below the two-column breakpoint it closes the section rather than
       interrupting it: smaller, centred, quieter. */
    .dressform { max-width: 190px; opacity: 0.7; }
    .prodsec__form { margin-top: var(--s6); }
}
@media (min-width: 1040px) {
    /* Aligned to the top of the copy and allowed to stand its full height. */
    .prodsec__form { align-items: flex-start; padding-top: var(--s5); }
    .dressform { max-height: 100%; }
}

/* ============================ 2. PATTERN SHEET ============================
   Was a 340px drawing parked in the side column, which left the copy short and
   the column empty below it — two blocks side by side, not a composition.

   Now it is one wide sheet behind the whole section: the heading, the four
   products and the CTA are printed on it. The section can go back to a single
   measure of copy, the dead column is gone, and the drawing does the job it
   was always meant to do — be the paper, not the picture.

   `slice` rather than `meet`, so it crops instead of letterboxing at any
   aspect. Third opacity on a hairline in brown-20 puts it far under the type;
   measured, it changes text contrast by nothing that rounds to a ratio. */
.sheet { position: relative; }
.sheet__paper {
    position: absolute;
    inset: 0;
    /* The bleed is real — the sheet runs past the text column on both sides —
       so it has to be clipped somewhere, and this is the only element it can
       be clipped on without putting a clip around live controls. Without it
       the 108% width adds 32px of horizontal scroll at 380. */
    overflow: hidden;
    pointer-events: none;
}
.sheet__bg {
    position: absolute;
    inset: -2% -4%;
    width: 108%;
    height: 104%;
    color: var(--brown-20);
    opacity: 0.34;
    pointer-events: none;
}
.sheet__body { position: relative; }

@media (max-width: 759px) {
    /* On a phone the sheet is mostly cropped to a few lines behind the copy,
       which reads as smudges rather than as paper. It steps back further. */
    .sheet__bg { opacity: 0.2; }
}
@media (prefers-reduced-motion: no-preference) { /* nothing animates; noted so
    the next person does not add anything here */ }

/* ============================== 3. THE TAPE ==============================
   A tailor's tape: shallow, long, ticked along the lower edge with numerals at
   the intervals. Ticks are two repeating gradients rather than markup — a tape
   has no semantics, and 60 empty spans would be 60 things for a screen reader
   to skip past. */
.tape {
    position: relative;
    display: inline-flex;
    align-items: flex-end;
    /* A tape's proportions: shallow for its length. 52px keeps the target
       comfortably past 44 while still reading as a strip. */
    min-height: 52px;
    padding: 0 22px 15px;
    max-width: 100%;
    font-size: 0.75rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    text-decoration: none;
    color: var(--ink);
    background-color: var(--kraft);
    border: var(--hair);
    background-image:
        /* minor ticks */
        repeating-linear-gradient(90deg,
            var(--brown-50) 0 1px, transparent 1px 11px),
        /* major ticks */
        repeating-linear-gradient(90deg,
            var(--brown-50) 0 1px, transparent 1px 55px);
    background-size: 100% 5px, 100% 10px;
    background-position: left bottom, left bottom;
    background-repeat: repeat-x;
    transition: background-color var(--t-micro) var(--ease),
                border-color var(--t-micro) var(--ease),
                color var(--t-micro) var(--ease);
}
/* Numerals land ON the major ticks, not spread evenly between the paddings:
   a tape whose numbers miss its own graduations reads as a drawing of a tape.
   110px is two minor pitches, which is where the major tick falls. */
.tape__nums {
    position: absolute;
    left: 0; right: 0; top: 7px;
    display: flex;
    overflow: hidden;
    font-size: 0.5rem;
    letter-spacing: 0.08em;
    color: var(--brown-70);
    opacity: 0.75;
}
.tape__nums i { font-style: normal; flex: 0 0 110px; text-align: right; padding-right: 3px; }
.tape__short { display: none; }
.tape:hover {
    background-color: var(--kraft-dk);
    border-color: var(--orange-70);
    color: var(--brown-90);
}
.tape:focus-visible { outline: 2px solid var(--orange-60); outline-offset: 3px; }

@media (max-width: 559.98px) {
    /* The full label is 43 characters. At 0.75rem with this tracking it needs
       about 430px of strip, which does not exist at 380. The label shortens;
       the type does not — 12px is the floor and it stays there. */
    .tape__long { display: none; }
    .tape__short { display: inline; }
    .tape__nums { display: none; }
    .tape { padding-inline: 18px; }
}

/* ============================== 5. THE SWING TAG ==============================
   Second pass. The first was too polite to register as anything: a flat tone,
   a 2px wobble and texture under 4% alpha. Board reads as board because of
   five physical cues, and it needed all five.

   1. A cut edge with thickness. The pale inset ring is the exposed core of a
      torn piece of card, and because it is drawn inside the clipped shape the
      tear cuts through it exactly as it cuts the face.
   2. Flecks you can actually see. Nine radial-gradient layers on co-prime tile
      sizes (29x37, 43x31, 37x47 ...), so nothing visibly repeats inside the
      card, over a crossed-gradient grain.
   3. A shadow, so it sits ON the page. It has to be a drop-shadow filter on a
      wrapper, not a box-shadow: filters are applied before clip-path on the
      same element, so a shadow and a tear cannot live together — the wrapper
      casts, the child is torn, and the shadow follows the tear.
   4. Ink that has sunk in. A 1px same-hue text-shadow on the printed labels
      fattens the strokes by a fraction and softens the edge, which is what
      ink on uncoated board does.
   5. A real tear. Around sixty points with four to nine pixels of excursion,
      generated rather than typed, plus the punched corner.

   Every one of those lives on ornament layers. The <form> sets no overflow and
   no clip, so nothing here can touch a field or crop a focus ring. */
.swing {
    position: relative;
    /* The ground layer sits at z-index -1. Without a stacking context here it
       escapes behind the .band's translucent background and is painted through
       it, which washes the board out. */
    isolation: isolate;
    margin-top: var(--s7);
    padding: var(--s8) var(--s7) var(--s7);
    max-width: 42rem;
}
/* Wrapper: casts only. The shadow has to be a filter here and the tear a
   clip-path on the child — filters are applied before clip-path on the same
   element, so a shadow and a torn edge cannot share one. */
.swing__ground {
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    filter:
        drop-shadow(0 1px 0 rgba(255, 250, 240, 0.5))
        drop-shadow(0 7px 16px rgba(58, 40, 22, 0.3))
        drop-shadow(0 2px 4px rgba(58, 40, 22, 0.22));
}
/* Board: torn, flecked, with its cut edge. */
.swing__ground::before {
    content: "";
    position: absolute;
    inset: 0;
    background-color: var(--board);
    /* Nine fleck layers on co-prime tile sizes, so nothing visibly repeats
       inside the card, over a crossed-gradient grain. */
    background-image:
        radial-gradient(circle at 17% 23%, rgba(74, 55, 40, 0.24) 0 1.1px, transparent 1.5px),
        radial-gradient(circle at 62% 71%, rgba(255, 250, 236, 0.34) 0 1.3px, transparent 1.7px),
        radial-gradient(circle at 88% 34%, rgba(74, 55, 40, 0.2) 0 0.9px, transparent 1.3px),
        radial-gradient(circle at 41% 12%, rgba(74, 55, 40, 0.26) 0 1.2px, transparent 1.6px),
        radial-gradient(circle at 9% 84%, rgba(255, 250, 236, 0.3) 0 1.4px, transparent 1.8px),
        radial-gradient(circle at 73% 49%, rgba(74, 55, 40, 0.22) 0 1px, transparent 1.4px),
        radial-gradient(circle at 28% 61%, rgba(255, 250, 236, 0.26) 0 1.2px, transparent 1.6px),
        radial-gradient(circle at 55% 88%, rgba(74, 55, 40, 0.2) 0 1.1px, transparent 1.5px),
        radial-gradient(circle at 94% 7%, rgba(255, 250, 236, 0.28) 0 1px, transparent 1.4px),
        repeating-linear-gradient(84deg,
            rgba(74, 55, 40, 0.1) 0 1px, transparent 1px 4px),
        repeating-linear-gradient(-6deg,
            rgba(74, 55, 40, 0.08) 0 1px, transparent 1px 3px),
        repeating-linear-gradient(41deg,
            rgba(255, 250, 236, 0.11) 0 2px, transparent 2px 7px),
        /* Sheen at 0.08 over a small area. At 0.20 it took the colour back out
           of the board, which is what made the last light version read grey. */
        radial-gradient(70% 45% at 20% -12%,
            rgba(255, 246, 228, 0.08), transparent 55%);
    background-size:
        29px 37px, 43px 31px, 37px 47px, 53px 41px, 31px 59px,
        47px 29px, 61px 43px, 41px 53px, 59px 37px,
        auto, auto, auto, auto;
    /* The cut edge: a dark line at the boundary with the pale exposed core
       just inside it. */
    box-shadow:
        inset 0 0 0 1px var(--board-cut),
        inset 0 0 0 3px var(--board-core),
        inset 0 0 0 4px rgba(74, 55, 40, 0.35),
        inset 0 3px 6px -2px rgba(255, 250, 236, 0.4);
    clip-path: polygon(
        0 26px,
        26px 0,
        30.0% 4px,
        36.5% 5px,
        42.4% 5px,
        51.4% 1px,
        58.2% 5px,
        65.3% 3px,
        70.8% 4px,
        76.7% 1px,
        85.4% 0px,
        91.8% 7px,
        100% 4px,
        calc(100% - 7px) 6.0%,
        calc(100% - 4px) 12.4%,
        calc(100% - 0px) 20.7%,
        calc(100% - 5px) 27.6%,
        calc(100% - 0px) 36.7%,
        calc(100% - 3px) 43.1%,
        calc(100% - 4px) 49.7%,
        calc(100% - 1px) 56.6%,
        calc(100% - 1px) 63.4%,
        calc(100% - 6px) 69.5%,
        calc(100% - 2px) 76.1%,
        calc(100% - 8px) 85.7%,
        calc(100% - 0px) 92.7%,
        100% 100%,
        94.0% calc(100% - 6px),
        86.9% calc(100% - 5px),
        78.6% calc(100% - 0px),
        72.2% calc(100% - 2px),
        65.4% calc(100% - 1px),
        58.1% calc(100% - 3px),
        50.2% calc(100% - 7px),
        44.1% calc(100% - 3px),
        38.2% calc(100% - 5px),
        29.4% calc(100% - 1px),
        23.5% calc(100% - 3px),
        14.9% calc(100% - 6px),
        8.9% calc(100% - 6px),
        0 100%,
        3px 92.0%,
        4px 85.3%,
        5px 75.7%,
        8px 66.4%,
        5px 56.4%,
        4px 50.4%,
        4px 40.8%,
        6px 33.0%,
        7px 24.2%,
        2px 15.8%);
}
/* The printed rule inside the edge — the .tag stitch at a larger scale. */
.swing::before {
    content: "";
    position: absolute;
    inset: 14px;
    border: 1px dashed var(--board-mid);
    opacity: 0.5;
    pointer-events: none;
}
/* Punched hole with its reinforcing ring. */
.swing__eyelet {
    position: absolute;
    top: 27px; left: 27px;
    width: 14px; height: 14px;
    border-radius: 50%;
    background-color: var(--cream-00);
    border: 1px solid var(--board-cut);
    box-shadow:
        0 0 0 3px var(--board-core),
        0 0 0 4px rgba(74, 55, 40, 0.5),
        inset 0 1px 2px rgba(58, 40, 22, 0.3);
    pointer-events: none;
}
.swing__string {
    position: absolute;
    top: 2px; left: 2px;
    width: 68px; height: 46px;
    color: var(--board-cut);
    opacity: 0.7;
    pointer-events: none;
}

/* -- everything printed on the board ------------------------------------- */
.swing .spec__legend,
.swing .spec__row label {
    color: var(--board-mid);
    /* Ink sinks into uncoated board. */
    text-shadow: 0 0 1px rgba(74, 55, 40, 0.4);
}
/* The row's own rule goes: the field now carries its own, and two lines under
   one input reads as a mistake rather than as a docket. */
.swing .spec__row { border-bottom: 0; padding-block: var(--s3); }

.swing .spec__row input,
.swing .spec__row textarea {
    color: var(--board-ink);
    caret-color: var(--board-acc);
    background-color: var(--field);
    border: 1px solid var(--board-rule);
    /* The rule beneath, kept — heavier than the other three sides, so the
       field still reads as a ruled line you write on rather than as a box that
       happens to be shaded. */
    border-bottom: 2px solid var(--board-cut);
    padding: 9px 12px;
    min-height: 44px;
    transition: background-color var(--t-micro) var(--ease),
                border-color var(--t-micro) var(--ease);
}
.swing .spec__row input:hover,
.swing .spec__row textarea:hover {
    background-color: var(--field-hover);
    border-color: var(--board-cut);
}
.swing .spec__row input:focus,
.swing .spec__row textarea:focus {
    background-color: var(--field-focus);
    border-color: var(--board-acc);
    border-bottom-width: 2px;
}
/* The textarea has to show its size. A resize grip alone does not. */
.swing .spec__row textarea {
    min-height: 8.5rem;
    resize: vertical;
    line-height: 1.55;
}
.swing .spec__row input::placeholder,
.swing .spec__row textarea::placeholder { color: var(--board-mid); opacity: 1; }
/* Autofill would otherwise repaint the field white. */
.swing .spec__row input:-webkit-autofill {
    -webkit-text-fill-color: var(--board-ink);
    -webkit-box-shadow: 0 0 0 40px var(--field) inset;
}
.swing .spec__row input:user-invalid,
.swing .spec__row textarea:user-invalid {
    border-color: var(--board-acc);
    color: var(--board-ink);
}
.swing .spec__row:has(input:user-invalid) label,
.swing .spec__row:has(textarea:user-invalid) label { color: var(--board-acc-txt); }

/* Category swatches. Selected is the dark panel: on a light board the filled
   state has to be the dark one, and a cream panel only reaches 2.29:1 here. */
.swing .swatch label {
    background-color: rgba(255, 252, 245, 0.35);
    border-color: var(--board-rule);
    color: var(--board-ink);
    text-shadow: 0 0 1px rgba(74, 55, 40, 0.35);
}
.swing .swatch label:hover { border-color: var(--board-acc); }
.swing .swatch input:checked + label {
    background-color: var(--brown-90);
    border-color: var(--brown-90);
    color: var(--cream-00);
    text-shadow: none;
}
.swing .swatch input:focus-visible + label {
    outline: 2px solid var(--board-acc);
    outline-offset: 3px;
}

/* Send: a woven label printed on the board. */
.swing .tag {
    background-color: rgba(255, 252, 245, 0.4);
    background-image:
        repeating-linear-gradient(90deg,
            rgba(74, 55, 40, 0.05) 0 1px, transparent 1px 3px),
        repeating-linear-gradient(0deg,
            rgba(74, 55, 40, 0.04) 0 1px, transparent 1px 3px);
    border-color: var(--board-rule);
    color: var(--board-ink);
    text-shadow: 0 0 1px rgba(74, 55, 40, 0.35);
}
.swing .tag::after { border-color: var(--board-mid); opacity: 0.5; }
.swing .tag:hover {
    background-color: var(--cream-00);
    border-color: var(--board-acc);
    color: var(--board-acc-txt);
}
.swing .tag:hover::after { border-color: var(--board-acc); opacity: 0.85; }

.swing .tag:focus-visible,
.swing .spec__row input:focus-visible,
.swing .spec__row textarea:focus-visible {
    outline: 2px solid var(--board-acc);
}
.swing .form__status { color: var(--board-mid); }
.swing .form__status[data-state="ok"]  { color: var(--board-ink); }
.swing .form__status[data-state="err"] { color: var(--board-acc-txt); }

@media (max-width: 619px) {
    /* The frame gives way, never the inputs. */
    .swing { padding: var(--s5); margin-top: var(--s6); }
    .swing::before { inset: 9px; }
    .swing__eyelet, .swing__string { display: none; }
    .swing__ground::before { clip-path: none; }
    .swing__ground { filter: drop-shadow(0 4px 10px rgba(58, 40, 22, 0.26)); }
}

/* ============================== WOVEN LABELS ==============================
   The care label sewn inside a garment: 56px tall, 34px of side padding, a
   woven ground rather than a flat one, a --brown-50 border and topstitching at
   --stitch-o on a long dash. This is the cloth material; the tape and the
   swing tag are the board one. */
.tags { display: flex; flex-wrap: wrap; gap: var(--s5); margin-top: var(--s7); }

.tag {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 56px;
    padding: 15px 34px;
    background-color: var(--cream-10);
    /* Plain weave: two gradients crossing at right angles, a few percent each.
       Same idea as the canvas behind the page, three orders of magnitude
       cheaper, and it gives the label cloth instead of paint. */
    background-image:
        repeating-linear-gradient(90deg,
            rgba(90, 66, 40, 0.055) 0 1px, transparent 1px 3px),
        repeating-linear-gradient(0deg,
            rgba(90, 66, 40, 0.045) 0 1px, transparent 1px 3px);
    border: var(--hair);
    color: var(--brown-90);
    font-family: var(--font-text);
    font-size: 0.8125rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    text-decoration: none;
    cursor: pointer;
    transition: background-color var(--t-micro) var(--ease),
                border-color var(--t-micro) var(--ease),
                color var(--t-micro) var(--ease);
}
/* Topstitching. A pseudo-element rather than `outline`, which has to stay free
   for the focus ring. */
.tag::after {
    content: "";
    position: absolute;
    inset: 6px;
    border: var(--stitch);
    opacity: var(--stitch-o);
    pointer-events: none;
    transition: border-color var(--t-micro) var(--ease), opacity var(--t-micro) var(--ease);
}
.tag:hover {
    background-color: var(--cream-00);
    color: var(--orange-70);
    border-color: var(--orange-70);
}
.tag:hover::after { border-color: var(--orange-70); opacity: 0.8; }
.tag:focus-visible { outline: 2px solid var(--orange-60); outline-offset: 3px; }
.tag--send { margin-top: var(--s6); }
.tag--send:disabled { opacity: 0.55; cursor: default; }

@media (max-width: 419.98px) {
    /* 34px of padding either side puts "Meet the team" over the line at 320.
       The label narrows; the type and the 56px stay. */
    .tag { padding-inline: var(--s5); }
    .tags { gap: var(--s4); }
}

/* ============================== CONTACT SPLIT ==============================
   Even halves. Every other section uses the .sec grid, which is lopsided by
   design — a wide main column and a narrow margin. Reusing it here would make
   the address and the accounts read as marginalia hanging off the form, which
   is the opposite of the intent. */
.contact { display: grid; gap: var(--s8); margin-top: var(--s7); }

@media (min-width: 1024px) {
    .contact {
        grid-template-columns: 1fr 1fr;
        gap: var(--s9);
        /* Not align-items: start — the aside needs the row's full height for
           its bands to distribute against. Grid stretch already aligns the
           tops, so "align the tops" and "fill the height" are one rule. */
    }
}

/* The swing tag keeps its own 42rem cap, so on a very wide screen it stops
   before the column does rather than stretching thin. */
.contact__main { min-width: 0; }
.contact__main .swing { margin-top: var(--s7); }

.contact__aside {
    display: flex;
    flex-direction: column;
    gap: var(--s8);
    min-width: 0;
}
.cx + .cx {
    padding-top: var(--s7);
    border-top: 1px solid var(--cream-20);
}
@media (min-width: 1024px) {
    /* Equal bands rather than the slack dumped between three blocks: the
       column is about half the height of the form beside it, and
       space-between turned that into two 200px voids that read as a fault.
       Equal bands with the entry centred spend the same height as even margins
       around the content, and the rules land at regular intervals. */
    .contact__aside { gap: 0; }
    .cx {
        flex: 1 1 0;
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding-block: var(--s7);
    }
    .cx:first-child { padding-top: 0; }
    .cx:last-child  { padding-bottom: 0; }
}
.cx__closed {
    margin-top: var(--s3);
    font-size: 0.8125rem;
    letter-spacing: 0.06em;
    color: var(--brown-50);
}
.cx__h {
    font-family: var(--font-text);
    font-weight: 400;
    font-size: 0.6875rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--brown-50);
    margin-bottom: var(--s4);
}
/* Type on the paragraph, not the anchor: until the script assembles the
   address — and permanently with scripting off — there is no anchor, only the
   [at]/[dot] text, and it still has to read as the display line it is. */
.cx__mail,
.cx__place,
.cx__hours {
    font-family: var(--font-display);
    font-size: clamp(1.25rem, 2.6vw, 2rem);
    line-height: 1.25;
    color: var(--brown-90);
    overflow-wrap: anywhere;
}
/* Reserves the line before the address is assembled, so nothing below shifts
   when the anchor appears. */
.cx__mail { min-height: 1.25em; }
.cx__mail a {
    font: inherit;
    color: inherit;
    text-decoration: none;
    border-bottom: 1px solid var(--cream-20);
    padding-bottom: 2px;
    transition: border-color var(--t-micro) var(--ease);
}
.cx__mail a:hover { border-color: var(--orange-60); }
.cx__mail a:focus-visible { outline: 2px solid var(--orange-60); outline-offset: 4px; }

/* ------------------------------------------------------------- the accounts
   Fabric-covered buttons: a round form with cloth stretched over it, gathered
   at the rim and stitched. Four layers on one element, outside in —

     dome        a radial highlight up and left of centre and a matching
                 darkening at the rim, which is what makes it read as domed
                 rather than as a flat disc
     weave       the same crossed gradients as the care labels, because this is
                 the cloth material and not the board one
     gathers     a repeating-conic-gradient masked to the outer ring only — the
                 pleats where the fabric is pulled under the rim
     stitch      a dashed ring inside the face, --stitch like everything else

   THE NAME STAYS VISIBLE, beneath the circle. Hover-only was the alternative
   and it is worse three ways: a touch device has no hover, so the name would
   never appear at all; anyone scanning would have to poke at eight circles to
   learn what they are; and visible text is what makes the accessible name
   match what is on screen rather than merely describe it. */
.cx__social {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--s4) var(--s3);
}
.cx__social a {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    padding: var(--s2) 2px;
    /* currentColor is the icon, and the icon lives on dark cloth. */
    color: var(--cream-00);
    text-decoration: none;
    transition: color var(--t-micro) var(--ease);
}

.cx__face {
    position: relative;
    flex: none;
    display: grid;
    place-items: center;
    /* 64px, comfortably past the 44 floor on the circle alone */
    width: 64px; height: 64px;
    border-radius: 50%;
    /* Deep cloth, not the page's. Tonal-on-tonal was the problem: at
       --cream-10 these were the same value as the ground and read as absences.
       #5C4633 is 7.3:1 against the ground, so they read as objects on it. */
    background-color: var(--cloth);
    color: var(--cream-00);
    background-image:
        radial-gradient(circle at 34% 27%,
            rgba(255, 248, 236, 0.34) 0%, rgba(255, 248, 236, 0) 54%),
        radial-gradient(circle at 50% 50%,
            rgba(20, 13, 6, 0) 50%, rgba(20, 13, 6, 0.22) 86%,
            rgba(20, 13, 6, 0.42) 100%),
        repeating-linear-gradient(90deg,
            rgba(255, 248, 236, 0.07) 0 1px, transparent 1px 3px),
        repeating-linear-gradient(0deg,
            rgba(20, 13, 6, 0.10) 0 1px, transparent 1px 3px);
    border: 1px solid var(--board-deep);
    box-shadow:
        inset 0 1px 0 rgba(255, 248, 236, 0.22),
        0 3px 6px rgba(58, 40, 22, 0.3);
    transition: border-color var(--t-micro) var(--ease),
                box-shadow var(--t-micro) var(--ease),
                background-color var(--t-micro) var(--ease);
}
/* the gathers, masked to the rim */
.cx__face::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: repeating-conic-gradient(from 0deg,
        rgba(20, 13, 6, 0.4) 0deg 1.4deg,
        rgba(20, 13, 6, 0) 1.4deg 7.5deg);
    -webkit-mask-image: radial-gradient(circle, transparent 66%, #000 78%, #000 100%);
    mask-image: radial-gradient(circle, transparent 66%, #000 78%, #000 100%);
    pointer-events: none;
}
/* the stitching */
.cx__face::after {
    content: "";
    position: absolute;
    inset: 10px;
    border-radius: 50%;
    /* Pale thread on dark cloth — same dash, same weight, inverted value. */
    border: 1px dashed var(--cream-20);
    opacity: 0.55;
    pointer-events: none;
    transition: border-color var(--t-micro) var(--ease), opacity var(--t-micro) var(--ease);
}
.cx__social svg {
    position: relative;
    width: 22px; height: 22px;
    fill: currentColor;
    display: block;
}
.cx__name {
    font-size: 0.625rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--brown-50);
    text-align: center;
    transition: color var(--t-micro) var(--ease);
}
/* The icon stays cream on the cloth; the accent lands on the stitching and
   the name, where it can be seen. */
.cx__social a:hover .cx__name { color: var(--orange-70); }
.cx__social a:hover .cx__face {
    background-color: var(--cloth-hi);
    border-color: var(--board-deep);
    box-shadow:
        inset 0 1px 0 rgba(255, 248, 236, 0.3),
        0 4px 9px rgba(58, 40, 22, 0.36);
}
.cx__social a:hover .cx__face::after { border-color: var(--orange-40); opacity: 0.95; }
/* Pressed: the button sinks into the cloth. */
.cx__social a:active .cx__face {
    box-shadow:
        inset 0 2px 4px rgba(20, 13, 6, 0.55),
        0 1px 1px rgba(58, 40, 22, 0.2);
}
/* The ring follows the circle, not the box the circle sits in. */
.cx__social a:focus-visible { outline: none; }
.cx__social a:focus-visible .cx__face {
    outline: 2px solid var(--orange-60);
    outline-offset: 3px;
}

@media (max-width: 479.98px) {
    /* A quarter of a 272px column is 68px: the button fills it exactly and
       leaves nothing for the name. Two across; the button does not shrink. */
    .cx__social { grid-template-columns: repeat(2, 1fr); }
}

/* ============================== SPEC SHEET ==============================
   Labelled rules, no boxes. Every field keeps a
   real <label> — the rules are decoration on top of an ordinary form, never a
   replacement for one. */
/* Width is the swing tag's job — see .swing above. */

.spec__legend {
    font-size: 0.6875rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--brown-50);
    padding: 0;
    margin-bottom: var(--s4);
}

/* -- category, as swatches ------------------------------------------------
   Real radio inputs. They are moved off-screen rather than display:none so
   they stay focusable and announced; the label is what you see, and the focus
   ring is drawn on it from the input's own :focus-visible. */
.swatches { border: 0; padding: 0; margin: 0 0 var(--s7); }
.swatches__row { display: flex; flex-wrap: wrap; gap: var(--s3); }
.swatch { position: relative; display: inline-flex; }
.swatch input {
    position: absolute;
    width: 1px; height: 1px;
    opacity: 0;
    margin: 0;
    pointer-events: none;
}
.swatch label {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 10px 20px;
    font-size: 0.75rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--brown-70);
    background-color: rgba(255, 255, 255, 0.4);
    border: 1px solid var(--cream-20);
    cursor: pointer;
    transition: background-color var(--t-micro) var(--ease),
                border-color var(--t-micro) var(--ease),
                color var(--t-micro) var(--ease);
}
.swatch label:hover { border-color: var(--brown-50); }
/* The chosen swatch is the one with thread through it. */
.swatch input:checked + label {
    background-color: var(--brown-90);
    border-color: var(--brown-90);
    color: var(--cream-00);
}
.swatch input:focus-visible + label {
    outline: 2px solid var(--orange-60);
    outline-offset: 3px;
}

/* -- fields ---------------------------------------------------------------- */
.spec__row {
    display: grid;
    gap: var(--s2);
    padding-block: var(--s4);
    border-bottom: 1px solid var(--cream-20);
}
@media (min-width: 620px) {
    /* Label in the tag's left column, value written on the rule. */
    .spec__row { grid-template-columns: 8rem 1fr; align-items: baseline; gap: var(--s5); }
}
.spec__row label {
    font-size: 0.6875rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--brown-50);
}
.spec__row input,
.spec__row textarea {
    width: 100%;
    font: inherit;
    font-size: 1rem;
    line-height: 1.6;
    color: var(--brown-90);
    background: transparent;
    border: 0;
    border-radius: 0;
    padding: 4px 0;
    /* 44px of target even though the control looks like a rule. */
    min-height: 40px;
}
.spec__row textarea { resize: vertical; min-height: 6.5rem; }
.spec__row:focus-within { border-bottom-color: var(--brown-50); }
.spec__row input:focus-visible,
.spec__row textarea:focus-visible {
    outline: 2px solid var(--orange-60);
    outline-offset: 4px;
}

/* Designed error state. :user-invalid only fires once the visitor has actually
   interacted, so nothing is red before they have typed anything. */
.spec__row:has(input:user-invalid),
.spec__row:has(textarea:user-invalid) { border-bottom-color: var(--orange-70); }
.spec__row:has(input:user-invalid) label,
.spec__row:has(textarea:user-invalid) label { color: var(--orange-70); }
.spec__row input:user-invalid,
.spec__row textarea:user-invalid { color: var(--brown-90); }

/* ==========================================================================
   Moved out of site.css: none of this exists on a sub-page, and /about had
   forty-one bytes of headroom against its 60 KB budget.
   ========================================================================== */
/* ============================== TECH ROWS ============================== */
/* ========================== TECHNOLOGIES ==========================
   A scroll-pinned sequence built out of `position: sticky` and one
   `view-timeline`, and nothing else.

   THE RULE THIS OBEYS, unchanged: the page scrolls normally the whole way
   through. No wheel or touch handler, no preventDefault, no animated
   scrollTop, no library, and no JavaScript that runs per frame. `.tech__run`
   is five viewports tall and `.tech__stage` sticks to the top for four of
   them while what is on it changes.

   WHAT THE SCROLL DRIVES. A spool of thread turning clockwise, a thread
   paying out from it, and four panels of cloth feeding vertically. All three
   hang off the same timeline and the same three transition windows, so they
   are one movement - the cloth is being drawn off the spool - rather than
   three that happen to agree.

   EVERYTHING ANIMATED IS A TRANSFORM. rotate on the spool, scaleX on the
   thread and on the progress bars, translateY on the panels. The compositor
   moves layers that are already painted; nothing re-rasterises mid-scroll,
   which is the whole reason the frame budget survives a scene this busy.

   svh not vh: on a phone the address bar changes vh mid-scroll, which would
   resize the pinned stage while it is pinned. (Below 1180 this all switches
   off anyway - see the bottom of this block.) */
.tech {
    --steps: 4;
    --step-scroll: 100svh;
    /* Diameter. Bounded by BOTH the viewport height and its width, because
       the spool has to stay taller than the cloth panel - see --cloth-h. */
    --spool: clamp(380px, min(78svh, 52vw), 660px);
    --cloth-h: clamp(280px, 50svh, 470px);
    --cloth-pad: clamp(24px, 3.2vw, 58px);
    --spool-gap: var(--s7);
    /* How far the bolt overhangs the window at each end, so its
       hem is never the thing you see arriving. */
    --lead: calc(var(--cloth-h) * 0.35);
}
.tech__run {
    position: relative;
    /* One extra viewport so the last item holds for its full step before the
       stage unpins, rather than sliding away as it arrives. */
    height: calc((var(--steps) + 1) * var(--step-scroll));
    margin-top: var(--s8);
}
/* One step tall, not zero, and full width. Both matter: a zero-area root
   never intersects anything, and a 1px-wide sentinel at left: 0 measured
   x = -8 here and was clipped away entirely by the body's `overflow-x:
   hidden`, giving it zero intersection area and no callbacks at all. */
.tech__mark {
    position: absolute;
    left: 0; right: 0;
    height: var(--step-scroll);
    pointer-events: none;
}
.tech__mark[data-i="1"] { top: 0; }
.tech__mark[data-i="2"] { top: calc(1 * var(--step-scroll)); }
.tech__mark[data-i="3"] { top: calc(2 * var(--step-scroll)); }
.tech__mark[data-i="4"] { top: calc(3 * var(--step-scroll)); }

.tech__stage {
    position: sticky;
    top: 0;
    height: 100svh;
    display: flex;
    align-items: center;
    overflow: hidden;
}
/* Not `.shell`: the cloth has to line up with the spool, which is anchored to
   the viewport edge, so a centred max-width container would put the gap in
   the wrong place at every width but one. */
.tech__inner {
    position: relative;
    z-index: 1;
    width: 100%;
    padding-inline: calc(var(--spool) * 0.5 + var(--spool-gap))
                    clamp(24px, 5vw, 96px);
}

/* ---- the spool -----------------------------------------------------------
   Centre on x = 0, so exactly half the disc is on screen. The stickers that
   are not in play sit past the edge; nothing hides them.

   THREE LAYERS, AND THE MIDDLE ONE IS THE ONLY ONE THAT TURNS. A spool
   rotates; the light falling on it does not. Shading that spun with the
   object would read as a flat disc with a pattern painted on it, so the
   winding turns and the lighting stays put. */
.tech__spool {
    position: absolute;
    left: 0;
    top: 50%;
    width: var(--spool);
    height: var(--spool);
    margin: calc(var(--spool) / -2) 0 0 calc(var(--spool) / -2);
    z-index: 2;
    pointer-events: none;
}
.tech__disc,
.tech__turn,
.tech__light {
    position: absolute;
    inset: 0;
    display: block;
    border-radius: 50%;
}

/* The body of the reel, from the middle out: the hole, the wall of the hole,
   the hub barrel, the wound thread, and the flange ring around it all. The
   flange is a larger disc UNDER the winding, so from above you see thread
   inside and flange outside - which is why the winding reads at all.
   `closest-side` so 100% is the radius; the default farthest-corner would put
   100% at 141% of it and every radius here would be wrong. */
.tech__disc {
    background-color: #9C8767;
    background-image:
        /* the cliff where the winding stands proud of the flange */
        radial-gradient(circle closest-side at 50% 50%,
            rgba(36, 27, 18, 0) 0 82.5%,
            rgba(36, 27, 18, 0.38) 84.5%,
            rgba(36, 27, 18, 0) 86.5%),
        radial-gradient(circle closest-side at 50% 50%,
            #2C2118 0 6.5%,
            #43331F 7% 12%,
            #5E4A30 12.5% 28%,
            transparent 28.5% 85%,
            #BCA47E 85.5% 100%),
        /* the winding itself: fine concentric layers, a lit face and a shaded
           face to each turn, at a 3.2px pitch */
        repeating-radial-gradient(circle closest-side at 50% 50%,
            rgba(36, 27, 18, 0) 0 2px,
            rgba(36, 27, 18, 0.22) 2px 3.3px,
            rgba(255, 253, 247, 0.26) 3.3px 4.4px);
    /* box-shadow, not filter: drop-shadow. A filter on this container would be
       re-evaluated every frame because a rotating child lives inside it. */
    box-shadow: 16px 20px 46px -14px rgba(36, 27, 18, 0.5);
}

/* THE ONLY THING THAT TURNS. Concentric rings are rotationally symmetric and
   would show no motion at all, so what turns is the radial grain of the
   winding - a conic tick every 2.2deg, masked to the thread annulus. */
.tech__turn {
    /* Amplitude deliberately low. The first pass ran this at 0.13 and the
       ticks converged into a starburst at the hub - the disc read as a fan,
       not as thread. The concentric layers are the winding; this only has to
       give them a grain that turns. */
    background-image: repeating-conic-gradient(from 0deg,
        rgba(36, 27, 18, 0.055) 0 0.9deg,
        rgba(255, 253, 247, 0.055) 0.9deg 1.8deg,
        rgba(36, 27, 18, 0) 1.8deg 3.4deg);
    -webkit-mask-image: radial-gradient(circle closest-side at 50% 50%,
        transparent 0 28.5%, #000 31% 84%, transparent 85.5%);
    mask-image: radial-gradient(circle closest-side at 50% 50%,
        transparent 0 28.5%, #000 31% 84%, transparent 85.5%);
    will-change: transform;
}

/* Four stickers, one per technology, stuck to the winding and turning with it
   - which is what a sticker on a spool does.

   `rotate(a) translateX(r)` does two jobs at once: it puts the sticker at
   angle a AND rotates it by a. So when the spool has turned by -a and the
   sticker has arrived at three o'clock, its world rotation is exactly zero and
   it reads upright. No counter-rotation, no second animation.

   THE CENTRING IS THE `translate` PROPERTY, NOT A translate() IN THE LIST.
   Writing `transform: rotate(a) translateX(r) translate(-50%, -50%)` is the
   obvious form and it is wrong: the centring translate is applied inside the
   rotation, so the box offset it is meant to cancel gets rotated too and the
   sticker lands at radius r - w/2 along a bisector instead of at r along a.
   Measured: with r = 180 the a = -90 sticker sat at (63, -63), radius 89. The
   individual `translate` property is applied BEFORE `transform`, outside the
   rotation, so it cancels cleanly and every angle lands at radius r. */
.tech__sticker {
    position: absolute;
    left: 50%;
    top: 50%;
    width: calc(var(--spool) * 0.26);
    padding: 0.4em 0.6em;
    border-radius: 3px;
    background-color: var(--cream-00);
    color: var(--brown-90);
    font-size: clamp(0.58rem, calc(var(--spool) * 0.023), 0.78rem);
    letter-spacing: 0.1em;
    line-height: 1.3;
    text-align: center;
    text-transform: uppercase;
    box-shadow:
        0 1px 0 rgba(36, 27, 18, 0.32),
        inset 0 0 0 1px rgba(36, 27, 18, 0.16);
    translate: -50% -50%;
    transform: rotate(var(--a)) translateX(calc(var(--spool) * 0.30));
}
.tech__sticker b {
    display: block;
    font-weight: 400;
    letter-spacing: 0.16em;
    color: var(--orange-70);
}
.tech__sticker:nth-child(1) { --a: 0deg; }
.tech__sticker:nth-child(2) { --a: -90deg; }
.tech__sticker:nth-child(3) { --a: -180deg; }
.tech__sticker:nth-child(4) { --a: -270deg; }

/* The light, and it does not turn. Upper left, like every other lit thing on
   this page. */
.tech__light {
    background-image: linear-gradient(135deg,
        rgba(255, 253, 247, 0.26) 0%,
        rgba(255, 253, 247, 0.06) 26%,
        rgba(255, 253, 247, 0) 44%,
        rgba(36, 27, 18, 0) 56%,
        rgba(36, 27, 18, 0.16) 74%,
        rgba(36, 27, 18, 0.34) 100%);
}
/* The flange rim, with thickness: bright on the arc facing the light, dark on
   the arc turned away. A conic is the right primitive for a rim - its shading
   depends on the angle around the disc, not the distance from its centre. 0deg
   is twelve o'clock, so the bright peak at 315deg is the upper left. */
.tech__light::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background-image: conic-gradient(
        rgba(255, 253, 247, 0.30) 0deg,
        rgba(255, 253, 247, 0.05) 55deg,
        rgba(36, 27, 18, 0.20) 100deg,
        rgba(36, 27, 18, 0.46) 135deg,
        rgba(36, 27, 18, 0.20) 175deg,
        rgba(36, 27, 18, 0.04) 225deg,
        rgba(255, 253, 247, 0.34) 280deg,
        rgba(255, 253, 247, 0.62) 315deg,
        rgba(255, 253, 247, 0.30) 360deg);
    -webkit-mask-image: radial-gradient(circle closest-side at 50% 50%,
        transparent 0 84.5%, #000 86% 100%);
    mask-image: radial-gradient(circle closest-side at 50% 50%,
        transparent 0 84.5%, #000 86% 100%);
}
/* The inside wall of the hole, lit on the LOWER RIGHT - light entering from
   the upper left falls on the far wall. Getting this the same way round as
   the rim is the usual mistake and flattens the hole into a dot. */
.tech__light::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background-image: conic-gradient(
        rgba(36, 27, 18, 0.50) 0deg,
        rgba(36, 27, 18, 0.50) 60deg,
        rgba(255, 253, 247, 0.40) 135deg,
        rgba(255, 253, 247, 0.40) 175deg,
        rgba(36, 27, 18, 0.30) 250deg,
        rgba(36, 27, 18, 0.50) 360deg);
    -webkit-mask-image: radial-gradient(circle closest-side at 50% 50%,
        transparent 0 5.5%, #000 7% 12.5%, transparent 13.5%);
    mask-image: radial-gradient(circle closest-side at 50% 50%,
        transparent 0 5.5%, #000 7% 12.5%, transparent 13.5%);
}

/* ---- the thread ----------------------------------------------------------
   IT PAYS OUT BY TRANSLATING, NOT BY SCALING, and that is the whole reason it
   can have a texture. The strand is drawn once as a 240px tile and repeated
   along its length at natural size. Had the length been animated with scaleX,
   the tile would have stretched with it and the twist pitch would breathe as
   the thread paid out - 16x too fine at the start, correct only at the end.
   Translating a fixed-length strand out from behind the spool distorts
   nothing, is still a single compositor transform, and is what actually
   happens when thread comes off a reel.

   The tile carries all four things the strand needs: a sine sag one period
   long, seamless because y(0) = y(240) and the slopes match; five strokes at
   decreasing width and increasing lightness offset upward, so it reads as a
   cylinder lit on its upper-left; twist ticks at 34 degrees to the local
   tangent, clipped to the strand; and three wide faint strokes offset
   downward for the contact shadow. See thread.py.

   left: 0 is the spool's centre, so the strand emerges from behind the rim -
   the spool has the higher z-index - wherever the rim happens to be at that
   height. That is why --spool is kept larger than --cloth-h. */
.tech__thread {
    position: absolute;
    left: 0;
    right: 0;
    top: calc(-1 * (28px + 15px));
    height: 28px;
    display: block;
    pointer-events: none;
    background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='28' viewBox='0 0 240 28'><defs><path id='t' fill='none' d='M0 15L15 16.15L30 17.12L45 17.77L60 18L75 17.77L90 17.12L105 16.15L120 15L135 13.85L150 12.88L165 12.23L180 12L195 12.23L210 12.88L225 13.85L240 15'/></defs><clipPath id='c'><use href='%23t' stroke='%23000' stroke-width='6.4' stroke-linecap='round'/></clipPath><g fill='none' stroke-linecap='round'><use href='%23t' transform='translate%280,4.6%29' stroke='%233A2B1C' stroke-opacity='.1' stroke-width='9'/><use href='%23t' transform='translate%280,5.4%29' stroke='%233A2B1C' stroke-opacity='.06' stroke-width='15'/><use href='%23t' transform='translate%280,6.2%29' stroke='%233A2B1C' stroke-opacity='.03' stroke-width='22'/><use href='%23t' transform='translate%280,.35%29' stroke='%234A3728' stroke-opacity='.92' stroke-width='7.2'/><use href='%23t' transform='translate%280,-.15%29' stroke='%237A6146' stroke-opacity='1' stroke-width='6.2'/><use href='%23t' transform='translate%280,-.75%29' stroke='%23A98C67' stroke-opacity='1' stroke-width='4.6'/><use href='%23t' transform='translate%280,-1.35%29' stroke='%23CDB48C' stroke-opacity='1' stroke-width='2.8'/><use href='%23t' transform='translate%280,-2.05%29' stroke='%23F0E6D2' stroke-opacity='.95' stroke-width='1.4'/></g><g clip-path='url%28%23c%29' fill='none' stroke-linecap='butt'><path stroke='%233A2B1C' stroke-opacity='.34' stroke-width='1.5' d='M-.69 13.38L3.69 16.86M5.31 13.85L9.69 17.32M11.3 14.31L15.7 17.77M17.29 14.75L21.71 18.19M23.28 15.15L27.72 18.56M29.27 15.52L33.73 18.89M35.25 15.83L39.75 19.16M41.23 16.08L45.77 19.37M47.21 16.27L51.79 19.5M53.19 16.39L57.81 19.57M59.17 16.44L63.83 19.56M65.15 16.41L69.85 19.47M71.14 16.31L75.86 19.32M77.12 16.14L81.88 19.09M83.11 15.9L87.89 18.81M89.09 15.61L93.91 18.47M95.08 15.26L99.92 18.08M101.07 14.86L105.93 17.65M107.07 14.43L111.93 17.2M113.06 13.97L117.94 16.73M119.06 13.5L123.94 16.26M125.07 13.03L129.93 15.8M131.07 12.57L135.93 15.35M137.08 12.13L141.92 14.94M143.09 11.72L147.91 14.56M149.1 11.36L153.9 14.24M155.11 11.04L159.89 13.97M161.13 10.79L165.87 13.76M167.15 10.6L171.85 13.63M173.16 10.48L177.84 13.57M179.18 10.43L183.82 13.58M185.2 10.46L189.8 13.66M191.22 10.56L195.78 13.81M197.24 10.73L201.76 14.04M203.26 10.97L207.74 14.32M209.27 11.27L213.73 14.66M215.29 11.62L219.71 15.05M221.3 12.02L225.7 15.47M227.3 12.45L231.7 15.92M233.31 12.91L237.69 16.39'/><path stroke='%23FFF7E8' stroke-opacity='.40' stroke-width='1.3' d='M2.31 13.61L6.69 17.09M8.3 14.08L12.7 17.55M14.3 14.53L18.7 17.98M20.29 14.95L24.71 18.38M26.27 15.34L30.73 18.73M32.26 15.68L36.74 19.03M38.24 15.96L42.76 19.27M44.22 16.19L48.78 19.44M50.2 16.34L54.8 19.54M56.18 16.42L60.82 19.57M62.16 16.43L66.84 19.52M68.15 16.37L72.85 19.4M74.13 16.24L78.87 19.21M80.11 16.03L84.89 18.96M86.1 15.76L90.9 18.64M92.09 15.44L96.91 18.28M98.08 15.06L102.92 17.87M104.07 14.65L108.93 17.43M110.07 14.2L114.93 16.97M116.06 13.74L120.94 16.5M122.06 13.27L126.94 16.03M128.07 12.8L132.93 15.57M134.07 12.35L138.93 15.14M140.08 11.92L144.92 14.74M146.09 11.53L150.91 14.39M152.11 11.19L156.89 14.1M158.12 10.91L162.88 13.86M164.14 10.68L168.86 13.69M170.15 10.53L174.85 13.59M176.17 10.44L180.83 13.56M182.19 10.43L186.81 13.61M188.21 10.5L192.79 13.73M194.23 10.63L198.77 13.92M200.25 10.84L204.75 14.17M206.27 11.11L210.73 14.48M212.28 11.44L216.72 14.85M218.29 11.81L222.71 15.25M224.3 12.23L228.7 15.69M230.31 12.68L234.69 16.15M236.31 13.14L240.69 16.62'/></g></svg>");
    background-repeat: repeat-x;
    background-position: left center;
    transform: translateX(-94%);
    /* same reason as the bolt: it is full stage width and tiled, so an
       uncomposited repaint per frame is not free */
    will-change: transform;
}

/* ---- the cloth -----------------------------------------------------------
   ONE CONTINUOUS BOLT. The four technologies are places along a single run of
   fabric, not four panels: the window clips, and the bolt inside it - four
   sections tall plus an overhang at each end - carries one weave, one sheen,
   one edge-shading and one fold field. There is no boundary to see, because
   there is no boundary. It replaced four separately-clothed panels, which is
   also why there is now ONE keyframe set instead of four.

   The fold artwork is drawn at 100 x 400 rather than tiled, so a fold that
   leaves one section arrives in the next as the same fold. A seamless tile
   would also have no visible join, but it would repeat the same arrangement
   four times over 1700px; this never restarts.

   DEPTH, four ways:
     - the window casts a soft shadow down-right, so the cloth sits above the
       page rather than being printed on it;
     - each fold's dark side now carries a FOURTH stroke, 20x the core width
       and a fraction of the alpha: ambient occlusion, the shadow deepening
       where the cloth lies closest to the ground;
     - the light side carries the same fourth stroke, which is the sheen
       collecting where the fabric turns toward the lamp;
     - the bolt's own background darkens at both selvedges with a lit roll
       just inside the left one, so the fabric curves away at the sides
       instead of being cut off flat. */
.tech__window {
    position: relative;
    height: var(--cloth-h);
    max-width: 940px;
    overflow: hidden;
    contain: paint;
    box-shadow: 22px 20px 48px -26px rgba(36, 27, 18, 0.55);
}
.tech__list {
    list-style: none;
    margin: 0;
    position: relative;
    display: block;
    --sec: var(--cloth-h);
    --calm: var(--cloth-pad);
    --hold: calc(var(--cloth-pad) * 0.3);
    height: calc(var(--sec) * 4 + var(--lead) * 2);
    padding: var(--lead) 0;
    background-color: var(--cream-00);
    background-image:
        /* the selvedges curving away, and the roll of light just inside the
           edge nearer the lamp */
        linear-gradient(to right,
            rgba(36, 27, 18, 0.22) 0,
            rgba(255, 253, 247, 0.30) 1.6%,
            rgba(255, 253, 247, 0) 7%,
            rgba(255, 253, 247, 0) 86%,
            rgba(36, 27, 18, 0.05) 95%,
            rgba(36, 27, 18, 0.30) 100%),
        linear-gradient(122deg,
            rgba(255, 253, 247, 0) 12%,
            rgba(255, 253, 247, 0.07) 20%,
            rgba(255, 253, 247, 0.28) 27%,
            rgba(255, 253, 247, 0.34) 30%,
            rgba(255, 253, 247, 0.10) 37%,
            rgba(255, 253, 247, 0) 45%,
            rgba(255, 253, 247, 0) 62%,
            rgba(255, 253, 247, 0.11) 70%,
            rgba(255, 253, 247, 0) 79%),
        repeating-linear-gradient(64deg,
            rgba(255, 253, 247, 0.55) 0 0.5px,
            rgba(74, 55, 40, 0.048) 0.5px 1.6px,
            rgba(74, 55, 40, 0) 1.6px 3px),
        repeating-linear-gradient(154deg,
            rgba(74, 55, 40, 0.018) 0 1px,
            rgba(74, 55, 40, 0) 1px 4px);
    background-size: 100% 100%, 100% 100%, auto, auto;
    background-repeat: no-repeat, no-repeat, repeat, repeat;
    box-shadow: inset 2px 2px 0 -1px rgba(255, 253, 247, 0.6),
        inset -2px -2px 0 -1px rgba(74, 55, 40, 0.34),
        inset 0 0 0 1px rgba(74, 55, 40, 0.2);
    transform: translateY(calc(-1 * var(--lead)));
    /* the no-scroll-timeline path; the timeline path below overrides it */
    transition: transform 620ms var(--ease);
    /* NOT OPTIONAL, and it was measured. Without it this element is not
       composited, so every scroll frame repaints the whole bolt - 940 x 1809
       of gradients, a stretched fold field and a two-axis mask. A scrub was
       taking SECONDS per frame and hung the renderer outright. With it the
       texture is rasterised once and the compositor only moves it, which is
       what every other transform in this section already relied on. The cost
       is one ~6.8MB layer. */
    will-change: transform;
}
/* The folds, on their own layer with a mask whose stops are calc()s of the
   tokens that set the padding and the section height - so the calm zone IS
   each section's content box, at every width, and no fold falls behind the
   copy. The horizontal band is the panel's own frame; the vertical one clears
   four times down the bolt and closes over each gutter between them. */
.tech__list::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='400' viewBox='0 0 100 400' preserveAspectRatio='none'><g fill='none' stroke-linecap='butt' stroke='%23FFFDF7'><path d='M-14 26Q49.76 16.81 114 12' transform='translate%28.02,.22%29' stroke-opacity='.2' stroke-width='.45'/><path d='M-14 26Q49.76 16.81 114 12' transform='translate%28.07,.67%29' stroke-opacity='.06' stroke-width='1.35'/><path d='M-14 26Q49.76 16.81 114 12' transform='translate%28.17,1.57%29' stroke-opacity='.03' stroke-width='3.15'/><path d='M-14 26Q49.76 16.81 114 12' transform='translate%28.49,4.47%29' stroke-opacity='.02' stroke-width='9'/><path d='M-14 74Q50.18 68.94 114 60.5' transform='translate%28.02,.22%29' stroke-opacity='.2' stroke-width='.45'/><path d='M-14 74Q50.18 68.94 114 60.5' transform='translate%28.07,.67%29' stroke-opacity='.06' stroke-width='1.35'/><path d='M-14 74Q50.18 68.94 114 60.5' transform='translate%28.17,1.57%29' stroke-opacity='.03' stroke-width='3.15'/><path d='M-14 74Q50.18 68.94 114 60.5' transform='translate%28.47,4.48%29' stroke-opacity='.02' stroke-width='9'/><path d='M-14 118Q49.85 109.61 114 104' transform='translate%28.02,.22%29' stroke-opacity='.2' stroke-width='.45'/><path d='M-14 118Q49.85 109.61 114 104' transform='translate%28.07,.67%29' stroke-opacity='.06' stroke-width='1.35'/><path d='M-14 118Q49.85 109.61 114 104' transform='translate%28.17,1.57%29' stroke-opacity='.03' stroke-width='3.15'/><path d='M-14 118Q49.85 109.61 114 104' transform='translate%28.49,4.47%29' stroke-opacity='.02' stroke-width='9'/><path d='M-14 165Q49.79 156.26 114 151.5' transform='translate%28.02,.22%29' stroke-opacity='.2' stroke-width='.45'/><path d='M-14 165Q49.79 156.26 114 151.5' transform='translate%28.07,.67%29' stroke-opacity='.06' stroke-width='1.35'/><path d='M-14 165Q49.79 156.26 114 151.5' transform='translate%28.17,1.57%29' stroke-opacity='.03' stroke-width='3.15'/><path d='M-14 165Q49.79 156.26 114 151.5' transform='translate%28.47,4.48%29' stroke-opacity='.02' stroke-width='9'/><path d='M-14 212Q50.16 206.49 114 198' transform='translate%28.02,.22%29' stroke-opacity='.2' stroke-width='.45'/><path d='M-14 212Q50.16 206.49 114 198' transform='translate%28.07,.67%29' stroke-opacity='.06' stroke-width='1.35'/><path d='M-14 212Q50.16 206.49 114 198' transform='translate%28.17,1.57%29' stroke-opacity='.03' stroke-width='3.15'/><path d='M-14 212Q50.16 206.49 114 198' transform='translate%28.49,4.47%29' stroke-opacity='.02' stroke-width='9'/><path d='M-14 258Q49.81 249.46 114 244.5' transform='translate%28.02,.22%29' stroke-opacity='.2' stroke-width='.45'/><path d='M-14 258Q49.81 249.46 114 244.5' transform='translate%28.07,.67%29' stroke-opacity='.06' stroke-width='1.35'/><path d='M-14 258Q49.81 249.46 114 244.5' transform='translate%28.17,1.57%29' stroke-opacity='.03' stroke-width='3.15'/><path d='M-14 258Q49.81 249.46 114 244.5' transform='translate%28.47,4.48%29' stroke-opacity='.02' stroke-width='9'/><path d='M-14 305Q50.23 300.09 114 291' transform='translate%28.02,.22%29' stroke-opacity='.2' stroke-width='.45'/><path d='M-14 305Q50.23 300.09 114 291' transform='translate%28.07,.67%29' stroke-opacity='.06' stroke-width='1.35'/><path d='M-14 305Q50.23 300.09 114 291' transform='translate%28.17,1.57%29' stroke-opacity='.03' stroke-width='3.15'/><path d='M-14 305Q50.23 300.09 114 291' transform='translate%28.49,4.47%29' stroke-opacity='.02' stroke-width='9'/><path d='M-14 352Q49.86 343.96 114 338.5' transform='translate%28.02,.22%29' stroke-opacity='.2' stroke-width='.45'/><path d='M-14 352Q49.86 343.96 114 338.5' transform='translate%28.07,.67%29' stroke-opacity='.06' stroke-width='1.35'/><path d='M-14 352Q49.86 343.96 114 338.5' transform='translate%28.17,1.57%29' stroke-opacity='.03' stroke-width='3.15'/><path d='M-14 352Q49.86 343.96 114 338.5' transform='translate%28.47,4.48%29' stroke-opacity='.02' stroke-width='9'/><path d='M24 -14Q40.99 199.8 52 414' transform='translate%28.22,-.01%29' stroke-opacity='.2' stroke-width='.45'/><path d='M24 -14Q40.99 199.8 52 414' transform='translate%28.67,-.04%29' stroke-opacity='.06' stroke-width='1.35'/><path d='M24 -14Q40.99 199.8 52 414' transform='translate%281.57,-.1%29' stroke-opacity='.03' stroke-width='3.15'/><path d='M24 -14Q40.99 199.8 52 414' transform='translate%284.49,-.29%29' stroke-opacity='.02' stroke-width='9'/><path d='M73 -14Q56.91 199.84 46 414' transform='translate%28.22,.01%29' stroke-opacity='.2' stroke-width='.45'/><path d='M73 -14Q56.91 199.84 46 414' transform='translate%28.67,.04%29' stroke-opacity='.06' stroke-width='1.35'/><path d='M73 -14Q56.91 199.84 46 414' transform='translate%281.57,.1%29' stroke-opacity='.03' stroke-width='3.15'/><path d='M73 -14Q56.91 199.84 46 414' transform='translate%284.49,.28%29' stroke-opacity='.02' stroke-width='9'/></g><g fill='none' stroke-linecap='butt' stroke='%233A2B1C'><path d='M-14 26Q49.76 16.81 114 12' transform='translate%28-.02,-.22%29' stroke-opacity='.3' stroke-width='.45'/><path d='M-14 26Q49.76 16.81 114 12' transform='translate%28-.07,-.67%29' stroke-opacity='.08' stroke-width='1.35'/><path d='M-14 26Q49.76 16.81 114 12' transform='translate%28-.17,-1.57%29' stroke-opacity='.05' stroke-width='3.15'/><path d='M-14 26Q49.76 16.81 114 12' transform='translate%28-.49,-4.47%29' stroke-opacity='.03' stroke-width='9'/><path d='M-14 74Q50.18 68.94 114 60.5' transform='translate%28-.02,-.22%29' stroke-opacity='.3' stroke-width='.45'/><path d='M-14 74Q50.18 68.94 114 60.5' transform='translate%28-.07,-.67%29' stroke-opacity='.08' stroke-width='1.35'/><path d='M-14 74Q50.18 68.94 114 60.5' transform='translate%28-.17,-1.57%29' stroke-opacity='.05' stroke-width='3.15'/><path d='M-14 74Q50.18 68.94 114 60.5' transform='translate%28-.47,-4.48%29' stroke-opacity='.03' stroke-width='9'/><path d='M-14 118Q49.85 109.61 114 104' transform='translate%28-.02,-.22%29' stroke-opacity='.3' stroke-width='.45'/><path d='M-14 118Q49.85 109.61 114 104' transform='translate%28-.07,-.67%29' stroke-opacity='.08' stroke-width='1.35'/><path d='M-14 118Q49.85 109.61 114 104' transform='translate%28-.17,-1.57%29' stroke-opacity='.05' stroke-width='3.15'/><path d='M-14 118Q49.85 109.61 114 104' transform='translate%28-.49,-4.47%29' stroke-opacity='.03' stroke-width='9'/><path d='M-14 165Q49.79 156.26 114 151.5' transform='translate%28-.02,-.22%29' stroke-opacity='.3' stroke-width='.45'/><path d='M-14 165Q49.79 156.26 114 151.5' transform='translate%28-.07,-.67%29' stroke-opacity='.08' stroke-width='1.35'/><path d='M-14 165Q49.79 156.26 114 151.5' transform='translate%28-.17,-1.57%29' stroke-opacity='.05' stroke-width='3.15'/><path d='M-14 165Q49.79 156.26 114 151.5' transform='translate%28-.47,-4.48%29' stroke-opacity='.03' stroke-width='9'/><path d='M-14 212Q50.16 206.49 114 198' transform='translate%28-.02,-.22%29' stroke-opacity='.3' stroke-width='.45'/><path d='M-14 212Q50.16 206.49 114 198' transform='translate%28-.07,-.67%29' stroke-opacity='.08' stroke-width='1.35'/><path d='M-14 212Q50.16 206.49 114 198' transform='translate%28-.17,-1.57%29' stroke-opacity='.05' stroke-width='3.15'/><path d='M-14 212Q50.16 206.49 114 198' transform='translate%28-.49,-4.47%29' stroke-opacity='.03' stroke-width='9'/><path d='M-14 258Q49.81 249.46 114 244.5' transform='translate%28-.02,-.22%29' stroke-opacity='.3' stroke-width='.45'/><path d='M-14 258Q49.81 249.46 114 244.5' transform='translate%28-.07,-.67%29' stroke-opacity='.08' stroke-width='1.35'/><path d='M-14 258Q49.81 249.46 114 244.5' transform='translate%28-.17,-1.57%29' stroke-opacity='.05' stroke-width='3.15'/><path d='M-14 258Q49.81 249.46 114 244.5' transform='translate%28-.47,-4.48%29' stroke-opacity='.03' stroke-width='9'/><path d='M-14 305Q50.23 300.09 114 291' transform='translate%28-.02,-.22%29' stroke-opacity='.3' stroke-width='.45'/><path d='M-14 305Q50.23 300.09 114 291' transform='translate%28-.07,-.67%29' stroke-opacity='.08' stroke-width='1.35'/><path d='M-14 305Q50.23 300.09 114 291' transform='translate%28-.17,-1.57%29' stroke-opacity='.05' stroke-width='3.15'/><path d='M-14 305Q50.23 300.09 114 291' transform='translate%28-.49,-4.47%29' stroke-opacity='.03' stroke-width='9'/><path d='M-14 352Q49.86 343.96 114 338.5' transform='translate%28-.02,-.22%29' stroke-opacity='.3' stroke-width='.45'/><path d='M-14 352Q49.86 343.96 114 338.5' transform='translate%28-.07,-.67%29' stroke-opacity='.08' stroke-width='1.35'/><path d='M-14 352Q49.86 343.96 114 338.5' transform='translate%28-.17,-1.57%29' stroke-opacity='.05' stroke-width='3.15'/><path d='M-14 352Q49.86 343.96 114 338.5' transform='translate%28-.47,-4.48%29' stroke-opacity='.03' stroke-width='9'/><path d='M24 -14Q40.99 199.8 52 414' transform='translate%28-.22,.01%29' stroke-opacity='.3' stroke-width='.45'/><path d='M24 -14Q40.99 199.8 52 414' transform='translate%28-.67,.04%29' stroke-opacity='.08' stroke-width='1.35'/><path d='M24 -14Q40.99 199.8 52 414' transform='translate%28-1.57,.1%29' stroke-opacity='.05' stroke-width='3.15'/><path d='M24 -14Q40.99 199.8 52 414' transform='translate%28-4.49,.29%29' stroke-opacity='.03' stroke-width='9'/><path d='M73 -14Q56.91 199.84 46 414' transform='translate%28-.22,-.01%29' stroke-opacity='.3' stroke-width='.45'/><path d='M73 -14Q56.91 199.84 46 414' transform='translate%28-.67,-.04%29' stroke-opacity='.08' stroke-width='1.35'/><path d='M73 -14Q56.91 199.84 46 414' transform='translate%28-1.57,-.1%29' stroke-opacity='.05' stroke-width='3.15'/><path d='M73 -14Q56.91 199.84 46 414' transform='translate%28-4.49,-.28%29' stroke-opacity='.03' stroke-width='9'/></g></svg>");
    background-size: 100% 100%;
    background-repeat: no-repeat;
    /* Scales the whole layer rather than editing the fabric: on the dialog
       these folds cross a brown-70 printed band that carries a strong
       highlight. Here there is only cream underneath, so the same amplitude
       reads as white scratches. */
    opacity: 0.8;
    -webkit-mask-image:
    linear-gradient(to right, #000 0 var(--hold), rgba(0,0,0,0) var(--calm),
            rgba(0,0,0,0) calc(100% - var(--calm)), #000 calc(100% - var(--hold)) 100%),
        linear-gradient(to bottom,
            #000 0 calc(var(--lead) + var(--hold)),
            rgba(0,0,0,0) calc(var(--lead) + var(--calm)) calc(var(--lead) + var(--sec) - var(--calm)),
            #000 calc(var(--lead) + var(--sec) - var(--hold)) calc(var(--lead) + var(--sec) + var(--hold)),
            rgba(0,0,0,0) calc(var(--lead) + var(--sec) + var(--calm)) calc(var(--lead) + var(--sec) * 2 - var(--calm)),
            #000 calc(var(--lead) + var(--sec) * 2 - var(--hold)) calc(var(--lead) + var(--sec) * 2 + var(--hold)),
            rgba(0,0,0,0) calc(var(--lead) + var(--sec) * 2 + var(--calm)) calc(var(--lead) + var(--sec) * 3 - var(--calm)),
            #000 calc(var(--lead) + var(--sec) * 3 - var(--hold)) calc(var(--lead) + var(--sec) * 3 + var(--hold)),
            rgba(0,0,0,0) calc(var(--lead) + var(--sec) * 3 + var(--calm)) calc(var(--lead) + var(--sec) * 4 - var(--calm)),
            #000 calc(var(--lead) + var(--sec) * 4 - var(--hold)) 100%);
    mask-image:
    linear-gradient(to right, #000 0 var(--hold), rgba(0,0,0,0) var(--calm),
            rgba(0,0,0,0) calc(100% - var(--calm)), #000 calc(100% - var(--hold)) 100%),
        linear-gradient(to bottom,
            #000 0 calc(var(--lead) + var(--hold)),
            rgba(0,0,0,0) calc(var(--lead) + var(--calm)) calc(var(--lead) + var(--sec) - var(--calm)),
            #000 calc(var(--lead) + var(--sec) - var(--hold)) calc(var(--lead) + var(--sec) + var(--hold)),
            rgba(0,0,0,0) calc(var(--lead) + var(--sec) + var(--calm)) calc(var(--lead) + var(--sec) * 2 - var(--calm)),
            #000 calc(var(--lead) + var(--sec) * 2 - var(--hold)) calc(var(--lead) + var(--sec) * 2 + var(--hold)),
            rgba(0,0,0,0) calc(var(--lead) + var(--sec) * 2 + var(--calm)) calc(var(--lead) + var(--sec) * 3 - var(--calm)),
            #000 calc(var(--lead) + var(--sec) * 3 - var(--hold)) calc(var(--lead) + var(--sec) * 3 + var(--hold)),
            rgba(0,0,0,0) calc(var(--lead) + var(--sec) * 3 + var(--calm)) calc(var(--lead) + var(--sec) * 4 - var(--calm)),
            #000 calc(var(--lead) + var(--sec) * 4 - var(--hold)) 100%);
    -webkit-mask-composite: source-over;
    mask-composite: add;
}
.tech__item {
    position: relative;
    height: var(--sec);
}
.tech[data-step="1"] .tech__list { transform: translateY(calc(-1 * var(--lead))); }
.tech[data-step="2"] .tech__list { transform: translateY(calc(-1 * var(--lead) - var(--sec))); }
.tech[data-step="3"] .tech__list { transform: translateY(calc(-1 * var(--lead) - var(--sec) * 2)); }
.tech[data-step="4"] .tech__list { transform: translateY(calc(-1 * var(--lead) - var(--sec) * 3)); }

.tech__panel {
    position: relative;
    z-index: 1;
    height: 100%;
    padding: var(--cloth-pad);
    display: grid;
    align-content: center;
    gap: var(--s5);
}
@media (min-width: 1180px) {
    .tech__panel {
        grid-template-columns: minmax(0, 1fr) minmax(0, 0.58fr);
        align-items: center;
        gap: var(--s7);
    }
}
/* Grid child: without this a long title can set a max-content floor and push
   the figure out of its column. */
.tech__text { min-width: 0; }

/* Type is bounded by the viewport HEIGHT as well as its width. The panel has
   a fixed height so that the vertical travel clears exactly, which means a
   short window is the binding constraint, not a narrow one. */
.tech__num {
    font-size: 0.72rem;
    letter-spacing: 0.18em;
    color: var(--orange-70);
    margin-bottom: var(--s3);
}
.tech__num span { color: var(--brown-50); }
.tech__title {
    font-family: var(--font-display);
    font-weight: 400;
    font-size: clamp(1.2rem, min(2.5vw, 3.6svh), 2.1rem);
    line-height: 1.15;
    color: var(--brown-90);
    margin-bottom: var(--s4);
}
.tech__body {
    max-width: 48ch;
    font-size: clamp(0.9rem, min(1.15vw, 1.8svh), 1.05rem);
    line-height: 1.6;
}
.tech__quote {
    margin-top: var(--s5);
    padding-top: var(--s4);
    border-top: 1px solid rgba(74, 55, 40, 0.22);
    font-family: var(--font-display);
    font-size: clamp(1rem, min(1.7vw, 2.4svh), 1.35rem);
    line-height: 1.3;
    color: var(--brown-70);
    max-width: 28ch;
}
.tech__figure { margin: 0; display: flex; justify-content: center; }
.tech__figure img {
    width: 100%;
    max-width: 300px;
    max-height: calc(var(--cloth-h) - var(--cloth-pad) * 2);
    height: auto;
    object-fit: contain;
    /* These assets carry a baked-in white disc; multiply drops it into the
       cloth so they sit on the panel instead of on top of it. */
    mix-blend-mode: multiply;
}

/* ---- progress ------------------------------------------------------------
   Four rules that fill as the sequence advances, so it is obvious how many
   steps are left and that the page has not stopped responding. Under the
   timeline they scrub with the scroll rather than snapping at a boundary. */
.tech__dots {
    list-style: none;
    margin: var(--s6) 0 0;
    padding: 0;
    display: flex;
    gap: var(--s3);
}
.tech__dots li {
    flex: 1 1 0;
    max-width: 76px;
    height: 2px;
    background-color: var(--cream-20);
    position: relative;
    overflow: hidden;
}
.tech__dots li::after {
    content: "";
    position: absolute;
    inset: 0;
    background-color: var(--orange-60);
    transform: scaleX(0);
    transform-origin: left;
    transition: transform 520ms var(--ease);
}
.tech[data-step="1"] .tech__dots li:nth-child(-n + 1)::after,
.tech[data-step="2"] .tech__dots li:nth-child(-n + 2)::after,
.tech[data-step="3"] .tech__dots li:nth-child(-n + 3)::after,
.tech[data-step="4"] .tech__dots li:nth-child(-n + 4)::after { transform: scaleX(1); }

/* ---- the scroll-linked path ----------------------------------------------
   One timeline, declared on the run. No `timeline-scope`: the run is already
   an ancestor of everything that uses it, so the name resolves up the tree,
   and declaring a scope on a descendant creates a second empty scope that
   shadows this one - which is how every item once sat on step 1.

   Every keyframe set below holds its value across the plateaus and moves only
   inside the same three windows, which is what makes the spool, the thread,
   the cloth and the bars one gesture. Scrub backwards and all four reverse
   together, because a scroll timeline runs backwards as readily as forwards.

   The gate is width AND height. The spool has to stay taller than the cloth
   panel for the thread to emerge from its rim, and the panel has a fixed
   height so its travel clears exactly - both of which a short window breaks
   before a narrow one does. */
@supports (animation-timeline: scroll()) {
    @media (prefers-reduced-motion: no-preference)
       and (min-width: 1180px) and (min-height: 620px) {
        .tech__run { view-timeline: --techrun block; }

        .tech__turn,
        .tech__thread,
        .tech__list,
        .tech__dots li::after {
            animation: both linear;
            animation-timeline: --techrun;
            animation-range: contain 0% contain 100%;
        }
        .tech__dots li::after { transition: none; }

        .tech__turn { animation-name: tech-turn; }
        .tech__thread { animation-name: tech-pay; }
        /* transition and animation-name on ONE rule: written as two, they were
           a duplicate selector in the same block and the later one silently
           won. stylelint no-duplicate-selectors caught it. */
        .tech__list { transition: none; animation-name: tech-feed; }
        .tech__dots li:nth-child(1)::after { animation-name: none; transform: scaleX(1); }
        .tech__dots li:nth-child(2)::after { animation-name: tech-fill-2; }
        .tech__dots li:nth-child(3)::after { animation-name: tech-fill-3; }
        .tech__dots li:nth-child(4)::after { animation-name: tech-fill-4; }
    }
}

/* Clockwise, a quarter turn per step, holding on the plateaus. Scrub back and
   it turns counter-clockwise, tracking the finger, because the timeline is
   the scroll position rather than a clock. */
@keyframes tech-turn {
    0%, 18% { transform: rotate(0deg); }
    30%, 43% { transform: rotate(90deg); }
    55%, 68% { transform: rotate(180deg); }
    80%, 100% { transform: rotate(270deg); }
}
/* Thread pays out only while the spool is turning - it holds when the spool
   holds, because it is coming off the spool. Translate, not scale: see the
   note on .tech__thread. */
@keyframes tech-pay {
    0%, 18% { transform: translateX(-94%); }
    30%, 43% { transform: translateX(-63%); }
    55%, 68% { transform: translateX(-32%); }
    80%, 100% { transform: translateX(0); }
}
/* The bolt feeding through the window, one section per step. One animation on
   one element, because it is one piece of cloth. */
@keyframes tech-feed {
    0%, 18% { transform: translateY(calc(-1 * var(--lead))); }
    30%, 43% { transform: translateY(calc(-1 * var(--lead) - var(--sec))); }
    55%, 68% { transform: translateY(calc(-1 * var(--lead) - var(--sec) * 2)); }
    80%, 100% { transform: translateY(calc(-1 * var(--lead) - var(--sec) * 3)); }
}
@keyframes tech-fill-2 {
    0%, 18% { transform: scaleX(0); }
    30%, 100% { transform: scaleX(1); }
}
@keyframes tech-fill-3 {
    0%, 43% { transform: scaleX(0); }
    55%, 100% { transform: scaleX(1); }
}
@keyframes tech-fill-4 {
    0%, 68% { transform: scaleX(0); }
    80%, 100% { transform: scaleX(1); }
}

/* ---- no pinning below 1180, in a short window, or for reduced motion -----
   THE WIDTH GATE MOVED UP FROM 768, deliberately. Half a spool wide enough to
   read is 190px of screen; at 768 that leaves the cloth 480px, and at 380 it
   leaves nothing. The pinned layout needs room for the spool AND the panel
   side by side, and below about 1180 there is not enough of it - so the
   sequence unwinds into four ordinary blocks with the spool as a static mark
   at the top, rather than shipping a cramped version of the pinned one.

   THE HEIGHT GATE exists because the panel has a fixed height so its travel
   clears exactly, and because the spool must stay taller than the panel for
   the thread to emerge from its rim.

   Reduced motion gets this same layout at every width: no pin, no rotation,
   no movement, all four visible, ordinary scrolling. */
@media (max-width: 1179.98px), (max-height: 619.98px),
       (prefers-reduced-motion: reduce) {
    .tech { --spool: clamp(240px, 68vw, 420px); }
    .tech__run { height: auto; }
    .tech__mark { display: none; }
    .tech__stage {
        position: static;
        height: auto;
        display: block;
        overflow: visible;
    }
    /* Still half off the edge - the object is the same object, it just does
       not drive anything here. */
    .tech__spool {
        position: relative;
        left: auto;
        top: auto;
        margin: 0 0 var(--s7) calc(var(--spool) / -2);
    }
    .tech__turn { animation: none; transform: none; }
    .tech__thread { display: none; }
    .tech__inner {
        width: 100%;
        max-width: var(--shell);
        margin-inline: auto;
        padding-inline: var(--gutter);
    }
    /* The bolt stays one piece of cloth here too - it just does not travel,
       so it grows to its content instead of to four fixed sections. */
    .tech__window {
        height: auto;
        max-width: none;
        overflow: visible;
        contain: none;
        box-shadow: 10px 12px 30px -20px rgba(36, 27, 18, 0.5);
    }
    .tech__list {
        height: auto;
        padding: 0;
        transform: none;
        transition: none;
        animation: none;
        will-change: auto;
    }
    .tech__list::before { display: none; }
    .tech__item {
        height: auto;
        padding-block: var(--s6);
    }
    .tech__item + .tech__item { border-top: 1px solid rgba(74, 55, 40, 0.14); }
    .tech__panel { height: auto; }
    .tech__figure img { max-height: none; }
    .tech__dots { display: none; }
}

/* ============================== TEAM ============================== */
.pull {
    margin-top: var(--s9);
    padding-left: var(--s5);
    border-left: 2px solid var(--orange-60);
    max-width: 60ch;
}
.pull p {
    font-family: var(--font-display);
    font-size: clamp(1.25rem, 3vw, 1.75rem);
    line-height: 1.4;
    color: var(--brown-90);
}
.pull p + p { margin-top: var(--s5); }

.strip {
    margin-top: var(--s9);
    display: grid;
    gap: var(--s3);
}
@media (min-width: 760px) {
    /* Three across once there is room; full bleed, so the gap is the only
       inset and the outer edges meet the viewport. */
    .strip { grid-template-columns: repeat(3, 1fr); }
    .strip img { aspect-ratio: 4 / 5; }
}
.strip img {
    width: 100%;
    aspect-ratio: 3 / 2;
    object-fit: cover;
}

.stats {
    margin-top: var(--s9);
    display: grid;
    gap: var(--s7);
}
.stats dt {
    font-family: var(--font-display);
    font-size: clamp(2.5rem, 7vw, 4.5rem);
    line-height: 1;
    color: var(--brown-90);
}
.stats dd {
    margin-top: var(--s2);
    font-size: 0.75rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--brown-50);
}

@media (min-width: 760px) {
    .strip { grid-template-columns: repeat(3, 1fr); gap: var(--s4); }
    .stats { grid-template-columns: repeat(3, 1fr); }
}

/* Escapes the shell to the full viewport. `margin-inline` rather than
   100vw + translate, so it never adds a scrollbar of its own. */
.bleed { margin-inline: calc(50% - 50vw); width: 100vw; }

/* ------------------------------------------------------------------ shared */
/* Still used by the spec-sheet form on the homepage. */
.form__hp {
    position: absolute;
    left: -9999px;
    width: 1px; height: 1px;
    overflow: hidden;
}
.form__status {
    margin-top: var(--s4);
    font-size: 0.9375rem;
    line-height: 1.5;
    min-height: 1.5em;
}
.form__status[data-state="ok"]   { color: var(--brown-90); }
.form__status[data-state="err"]  { color: var(--orange-70); }
.form__status[data-state="busy"] { color: var(--brown-50); }

/* ============================ CAPTCHA WELL ============================
   ADDITION ONLY — nothing above this line changed.

   The hCaptcha widget is a white box with its own type, border and logo, and
   none of that can be restyled from outside: it is a cross-origin iframe. So
   it is not fought. It sits in a recessed well on the docket, ruled with the
   same dashed stitch as everything else on the board, which reads as a stamp
   pasted onto the card rather than as a widget that wandered in.

   Reported rather than hidden: this is the one element on the page that is not
   in the site's vocabulary, and it cannot be made to be. */
.form__stamp {
    margin-top: var(--s7);
    padding: var(--s4);
    background-color: rgba(74, 55, 40, 0.06);
    border: 1px dashed var(--board-mid);
    /* The widget is 303px wide and will not shrink; below that the well
       scrolls rather than the page. */
    max-width: 100%;
    overflow-x: auto;
}
.form__stamp .spec__legend { margin-bottom: var(--s3); }
.h-captcha { min-height: 78px; }

@media (max-width: 419.98px) {
    /* At 320 the content column is 257px and the widget is 303px. The well
       lets it scroll sideways inside itself rather than pushing the page. */
    .form__stamp { padding: var(--s3); }
}

/* ============================== SENT DIALOG ==============================
   A printed silk square.

   The board vocabulary — kraft, cut edge, punched eyelet — belongs to the
   contact form. Repeating it here made the confirmation read as a second
   docket, so this is its own object: a square of printed silk, plain in the
   middle where the words are, framed by a printed border.

   THE BORDER IS A border-image, and that is the whole trick. border-image
   paints the four corner slices as drawn and repeats only the edges, which is
   exactly how a printed square mitres — so the diagonals run into the corners
   without a single line of layout code. The motif is drawn once for the top
   edge and rotated about the centre for the other three, so all four sides
   carry the same print.

   THE PRINT is the site's own vocabulary and nothing else: running stitch,
   needles with their eyes and points, pattern-cutting notches, the thread each
   needle is pulling, and the needle-and-thread mark tucked into each mitre.
   Brown ground, cream print, one small orange dot per repeat and nowhere else.

   Everything here is decoration. The dialog element, showModal(), the focus
   handling, Escape, the backdrop click and the captcha reset are unchanged and
   live in form.js. Both decorative spans are aria-hidden in the markup and
   pointer-events: none here, so nothing sits between anyone and the close
   button, and the text never sits on the pattern — the border is a real border
   box, so the content box begins where the print ends. */
.sent {
    --scarf-band: clamp(30px, 13vw, 70px);
    --scarf-pad: var(--s6);
    border: 0;
    padding: 0;
    max-width: min(30rem, calc(100vw - 2 * var(--gutter)));
    width: 100%;
    /* Square, unless the words need more room than a square gives — at which
       point the panel grows rather than the text being clipped. */
    aspect-ratio: 1 / 1;
    min-height: fit-content;
    background: transparent;
    color: var(--brown-90);
    overflow: visible;
    /* Drape shadow: wide and soft, so the square sits above the page rather
       than being drawn on it. */
    filter:
        drop-shadow(0 18px 34px rgba(74, 55, 40, 0.3))
        drop-shadow(0 5px 10px rgba(74, 55, 40, 0.18));
}
.sent::backdrop { background-color: rgba(36, 27, 18, 0.55); }

/* -- the silk ---------------------------------------------------------------
   The border box is the print; the content box is the plain field. */
.sent__ground {
    position: absolute;
    inset: 0;
    z-index: -2;
    pointer-events: none;
    background-color: var(--cream-00);
    border: var(--scarf-band) solid transparent;
    border-image-source: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='300' height='300' viewBox='0 0 300 300'><rect width='300' height='300' fill='%234A3728'/><defs><g id='e' fill='none' stroke='%23E2D9C8' stroke-width='1.1' stroke-linecap='round'><path d='M50 9 h6 M60 9 h6 M70 9 h6'/><path d='M53 22 H75'/><ellipse cx='72' cy='22' rx='1.5' ry='2.6'/><path d='M52 22 l4 -1.6 v3.2 Z' fill='%23E2D9C8' stroke='none'/><path d='M65 34 l3.4 5.6 h-6.8 Z' fill='%23E2D9C8' stroke='none'/><path d='M48 44 C56 41 60 47 65 44 S74 41 82 44'/><path d='M84 9 h6 M94 9 h6 M104 9 h6'/><path d='M87 22 H109'/><ellipse cx='106' cy='22' rx='1.5' ry='2.6'/><path d='M86 22 l4 -1.6 v3.2 Z' fill='%23E2D9C8' stroke='none'/><path d='M99 34 l3.4 5.6 h-6.8 Z' fill='%23E2D9C8' stroke='none'/><path d='M82 44 C90 41 94 47 99 44 S108 41 116 44'/><path d='M118 9 h6 M128 9 h6 M138 9 h6'/><path d='M121 22 H143'/><ellipse cx='140' cy='22' rx='1.5' ry='2.6'/><path d='M120 22 l4 -1.6 v3.2 Z' fill='%23E2D9C8' stroke='none'/><path d='M133 34 l3.4 5.6 h-6.8 Z' fill='%23E2D9C8' stroke='none'/><path d='M116 44 C124 41 128 47 133 44 S142 41 150 44'/><path d='M152 9 h6 M162 9 h6 M172 9 h6'/><path d='M155 22 H177'/><ellipse cx='174' cy='22' rx='1.5' ry='2.6'/><path d='M154 22 l4 -1.6 v3.2 Z' fill='%23E2D9C8' stroke='none'/><path d='M167 34 l3.4 5.6 h-6.8 Z' fill='%23E2D9C8' stroke='none'/><path d='M150 44 C158 41 162 47 167 44 S176 41 184 44'/><path d='M186 9 h6 M196 9 h6 M206 9 h6'/><path d='M189 22 H211'/><ellipse cx='208' cy='22' rx='1.5' ry='2.6'/><path d='M188 22 l4 -1.6 v3.2 Z' fill='%23E2D9C8' stroke='none'/><path d='M201 34 l3.4 5.6 h-6.8 Z' fill='%23E2D9C8' stroke='none'/><path d='M184 44 C192 41 196 47 201 44 S210 41 218 44'/><path d='M220 9 h6 M230 9 h6 M240 9 h6'/><path d='M223 22 H245'/><ellipse cx='242' cy='22' rx='1.5' ry='2.6'/><path d='M222 22 l4 -1.6 v3.2 Z' fill='%23E2D9C8' stroke='none'/><path d='M235 34 l3.4 5.6 h-6.8 Z' fill='%23E2D9C8' stroke='none'/><path d='M218 44 C226 41 230 47 235 44 S244 41 252 44'/><circle cx='78' cy='22' r='1.5' fill='%23E08A4B' stroke='none'/><circle cx='112' cy='22' r='1.5' fill='%23E08A4B' stroke='none'/><circle cx='146' cy='22' r='1.5' fill='%23E08A4B' stroke='none'/><circle cx='180' cy='22' r='1.5' fill='%23E08A4B' stroke='none'/><circle cx='214' cy='22' r='1.5' fill='%23E08A4B' stroke='none'/><circle cx='248' cy='22' r='1.5' fill='%23E08A4B' stroke='none'/></g><g id='c' fill='none' stroke='%23E2D9C8' stroke-width='1.1' stroke-linecap='round'><path d='M3 3 L45 45'/><path d='M9 15 l5 -5 M19 25 l5 -5 M29 35 l5 -5'/><ellipse cx='13' cy='13' rx='1.4' ry='2.6' transform='rotate%28-45 13 13%29'/><path d='M16 16 L36 36' stroke-width='1.3'/><path d='M35 35 l3.4 1.4 l-1.4 -3.4 Z' fill='%23E2D9C8' stroke='none'/><circle cx='7' cy='7' r='1.5' fill='%23E08A4B' stroke='none'/></g></defs><use href='%23e' transform='rotate%280 150 150%29'/><use href='%23e' transform='rotate%2890 150 150%29'/><use href='%23e' transform='rotate%28180 150 150%29'/><use href='%23e' transform='rotate%28270 150 150%29'/><use href='%23c' transform='rotate%280 150 150%29'/><use href='%23c' transform='rotate%2890 150 150%29'/><use href='%23c' transform='rotate%28180 150 150%29'/><use href='%23c' transform='rotate%28270 150 150%29'/></svg>");
    border-image-slice: 48;
    border-image-width: var(--scarf-band);
    /* round, not repeat: the motif is scaled a hair to fit a whole number of
       repeats per side rather than being cut off mid-needle. */
    border-image-repeat: round;
}

/* -- the surface: weave, sheen, creases -------------------------------------
   NO FILTERS ANYWHERE. Measured, not assumed: feTurbulence and feGaussianBlur
   both re-rasterise on every dialog open rather than once, because a filter
   result is not kept across display: none — 83ms peak with up to nine frames
   over budget during the entrance. The weave is gradients, and each fold's
   falloff is drawn as stacked strokes rather than computed.

   ALPHA, NOT BLEND MODES: mix-blend-mode would make the resulting pixel
   incomputable, and every contrast figure for text on it a guess.

   THE CREASES ARE ON THEIR OWN LAYER because they must not fall behind the
   copy. Silk laid flat settles most at its edges, so the folds live in the
   outer ring and the middle stays calm — which is both what cloth does and
   what frees the folds to be strong. Spread across the whole panel they had to
   come down to 0.024 alpha to clear the text's contrast floor, and at that
   amplitude they read as smoke. */
.sent__hem {
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    border-radius: 4px;
    background-image:
        /* THE SHEEN: a narrow bright band with a fast falloff either side, not
           an even glow. The sharpness is what separates silk from paper. */
        linear-gradient(122deg,
            rgba(255, 253, 247, 0) 12%,
            rgba(255, 253, 247, 0.07) 20%,
            rgba(255, 253, 247, 0.28) 27%,
            rgba(255, 253, 247, 0.34) 30%,
            rgba(255, 253, 247, 0.10) 37%,
            rgba(255, 253, 247, 0) 45%,
            rgba(255, 253, 247, 0) 62%,
            rgba(255, 253, 247, 0.11) 70%,
            rgba(255, 253, 247, 0) 79%),
        /* THE WEAVE: a satin rib on the diagonal, 3px pitch, with a lit face
           and a shaded face to each rib, plus a fainter cross-thread at 4px to
           break the regularity. It covers the whole panel including the centre
           field, so it — and only it — is what constrains the text contrast. */
        repeating-linear-gradient(64deg,
            rgba(255, 253, 247, 0.55) 0 0.5px,
            rgba(74, 55, 40, 0.048) 0.5px 1.6px,
            rgba(74, 55, 40, 0) 1.6px 3px),
        repeating-linear-gradient(154deg,
            rgba(74, 55, 40, 0.018) 0 1px,
            rgba(74, 55, 40, 0) 1px 4px);
    background-size: 100% 100%, auto, auto;
    background-repeat: no-repeat, repeat, repeat;
    /* The hand-rolled hem: a bead of fabric at the very edge, lit from the
       upper left and shaded on the turn away from it. */
    box-shadow:
        inset 2px 2px 0 -1px rgba(255, 253, 247, 0.6),
        inset -2px -2px 0 -1px rgba(74, 55, 40, 0.34),
        inset 0 0 0 1px rgba(74, 55, 40, 0.2);
}

/* THE CREASES, above the sheen so a fold's dark trough cuts the bright band
   where it crosses and its bright ridge adds to it — the interaction, done by
   compositing rather than by a blend mode.

   Every fold runs edge to edge: the paths start at -14 and end at 114 in a
   0..100 viewBox, so both ends are off-panel and no crease terminates in open
   field. A line that stops mid-panel is a mark ON the cloth; one that runs off
   both edges is a bend IN it. They are near-straight at shared angles with a
   single gentle bow, because a square creases where it was folded — freely
   curving lines read as organic marks. Butt caps, one width per stroke: a
   crease does not taper like a brushstroke.

   Highlight and shadow SHARE AN EDGE. Each stroke's inner edge sits on the
   fold line — a stroke of width W is offset by W/2 — so the bright ridge butts
   against the dark trough with a crisp seam, and only the outward falloff is
   soft. Pairs centred on the line, which is the obvious construction, produce
   a smudge instead. The lit side is the up-left perpendicular on every fold
   regardless of which way it runs.

   The mask is a picture frame, and its stops are calc()s of the very tokens
   that set the padding — not magic percentages. The calm zone is therefore
   exactly the content box at every width, automatically. Percentages were
   tried first and were wrong at 380: the band shrinks to 30px there while the
   padding does not shrink proportionally, so a fixed 21% zone left the folds
   crossing the copy. */
.sent__hem::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100' preserveAspectRatio='none'><g fill='none' stroke-linecap='butt' stroke='%23FFFDF7'><path d='M-14 31Q49.71 20.87 114 15.5' transform='translate%28.03,.22%29' stroke-opacity='.2' stroke-width='.45'/><path d='M-14 31Q49.71 20.87 114 15.5' transform='translate%28.08,.67%29' stroke-opacity='.06' stroke-width='1.35'/><path d='M-14 31Q49.71 20.87 114 15.5' transform='translate%28.19,1.56%29' stroke-opacity='.03' stroke-width='3.15'/><path d='M-14 60.5Q50.22 54.54 114 45' transform='translate%28.03,.22%29' stroke-opacity='.2' stroke-width='.45'/><path d='M-14 60.5Q50.22 54.54 114 45' transform='translate%28.08,.67%29' stroke-opacity='.06' stroke-width='1.35'/><path d='M-14 60.5Q50.22 54.54 114 45' transform='translate%28.19,1.56%29' stroke-opacity='.03' stroke-width='3.15'/><path d='M-14 90Q49.82 80.76 114 74.5' transform='translate%28.03,.22%29' stroke-opacity='.2' stroke-width='.45'/><path d='M-14 90Q49.82 80.76 114 74.5' transform='translate%28.08,.67%29' stroke-opacity='.06' stroke-width='1.35'/><path d='M-14 90Q49.82 80.76 114 74.5' transform='translate%28.19,1.56%29' stroke-opacity='.03' stroke-width='3.15'/><path d='M27 -14Q39.82 49.59 47.5 114' transform='translate%28.22,-.04%29' stroke-opacity='.2' stroke-width='.45'/><path d='M27 -14Q39.82 49.59 47.5 114' transform='translate%28.67,-.11%29' stroke-opacity='.06' stroke-width='1.35'/><path d='M27 -14Q39.82 49.59 47.5 114' transform='translate%281.56,-.25%29' stroke-opacity='.03' stroke-width='3.15'/><path d='M43.45 82.23Q47.66 84.47 51.32 87.54' transform='translate%28-.09,.14%29' stroke-opacity='.11' stroke-width='.34'/><path d='M43.45 82.23Q47.66 84.47 51.32 87.54' transform='translate%28-.28,.42%29' stroke-opacity='.03' stroke-width='1.01'/><path d='M43.45 82.23Q41.53 84.9 40.5 88.02' transform='translate%28.15,.08%29' stroke-opacity='.11' stroke-width='.34'/><path d='M43.45 82.23Q41.53 84.9 40.5 88.02' transform='translate%28.45,.23%29' stroke-opacity='.03' stroke-width='1.01'/></g><g fill='none' stroke-linecap='butt' stroke='%233A2B1C'><path d='M-14 31Q49.71 20.87 114 15.5' transform='translate%28-.03,-.22%29' stroke-opacity='.3' stroke-width='.45'/><path d='M-14 31Q49.71 20.87 114 15.5' transform='translate%28-.08,-.67%29' stroke-opacity='.08' stroke-width='1.35'/><path d='M-14 31Q49.71 20.87 114 15.5' transform='translate%28-.19,-1.56%29' stroke-opacity='.05' stroke-width='3.15'/><path d='M-14 60.5Q50.22 54.54 114 45' transform='translate%28-.03,-.22%29' stroke-opacity='.3' stroke-width='.45'/><path d='M-14 60.5Q50.22 54.54 114 45' transform='translate%28-.08,-.67%29' stroke-opacity='.08' stroke-width='1.35'/><path d='M-14 60.5Q50.22 54.54 114 45' transform='translate%28-.19,-1.56%29' stroke-opacity='.05' stroke-width='3.15'/><path d='M-14 90Q49.82 80.76 114 74.5' transform='translate%28-.03,-.22%29' stroke-opacity='.3' stroke-width='.45'/><path d='M-14 90Q49.82 80.76 114 74.5' transform='translate%28-.08,-.67%29' stroke-opacity='.08' stroke-width='1.35'/><path d='M-14 90Q49.82 80.76 114 74.5' transform='translate%28-.19,-1.56%29' stroke-opacity='.05' stroke-width='3.15'/><path d='M27 -14Q39.82 49.59 47.5 114' transform='translate%28-.22,.04%29' stroke-opacity='.3' stroke-width='.45'/><path d='M27 -14Q39.82 49.59 47.5 114' transform='translate%28-.67,.11%29' stroke-opacity='.08' stroke-width='1.35'/><path d='M27 -14Q39.82 49.59 47.5 114' transform='translate%28-1.56,.25%29' stroke-opacity='.05' stroke-width='3.15'/><path d='M43.45 82.23Q47.66 84.47 51.32 87.54' transform='translate%28.09,-.14%29' stroke-opacity='.17' stroke-width='.34'/><path d='M43.45 82.23Q47.66 84.47 51.32 87.54' transform='translate%28.28,-.42%29' stroke-opacity='.05' stroke-width='1.01'/><path d='M43.45 82.23Q41.53 84.9 40.5 88.02' transform='translate%28-.15,-.08%29' stroke-opacity='.17' stroke-width='.34'/><path d='M43.45 82.23Q41.53 84.9 40.5 88.02' transform='translate%28-.45,-.23%29' stroke-opacity='.05' stroke-width='1.01'/></g></svg>");
    background-size: 100% 100%;
    background-repeat: no-repeat;
    --calm: calc(var(--scarf-band) + var(--scarf-pad));
    --hold: calc(var(--scarf-band) * 0.5);
    -webkit-mask-image:
        linear-gradient(to right, #000 0 var(--hold), rgba(0,0,0,0) var(--calm),
            rgba(0,0,0,0) calc(100% - var(--calm)), #000 calc(100% - var(--hold)) 100%),
        linear-gradient(to bottom, #000 0 var(--hold), rgba(0,0,0,0) var(--calm),
            rgba(0,0,0,0) calc(100% - var(--calm)), #000 calc(100% - var(--hold)) 100%);
    mask-image:
        linear-gradient(to right, #000 0 var(--hold), rgba(0,0,0,0) var(--calm),
            rgba(0,0,0,0) calc(100% - var(--calm)), #000 calc(100% - var(--hold)) 100%),
        linear-gradient(to bottom, #000 0 var(--hold), rgba(0,0,0,0) var(--calm),
            rgba(0,0,0,0) calc(100% - var(--calm)), #000 calc(100% - var(--hold)) 100%);
    -webkit-mask-composite: source-over;
    mask-composite: add;
}

.sent__inner {
    position: relative;
    /* Clear of the print on every side. The band is a real border, so this is
       breathing room inside the plain field, not a guess at where the pattern
       ends. */
    padding: calc(var(--scarf-band) + var(--scarf-pad));
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: 100%;
}
.sent__kicker {
    font-size: 0.6875rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    /* --orange-70, the site's own accent. It was briefly darkened when the
       texture was over-amplitude; at the amplitudes that actually shipped it
       measures 4.84:1 against the darkest pixel the surface produces, so the
       token stands. */
    color: var(--orange-70);
    margin-bottom: var(--s4);
}
.sent__title {
    font-family: var(--font-display);
    font-weight: 400;
    font-size: clamp(1.25rem, 3.4vw, 1.75rem);
    line-height: 1.2;
    color: var(--brown-90);
    margin-bottom: var(--s4);
}
.sent__body {
    font-size: 0.9375rem;
    line-height: 1.6;
    color: var(--brown-90);
    max-width: 32ch;
}
.sent__close {
    align-self: flex-start;
    margin-top: var(--s6);
    background-color: rgba(255, 252, 245, 0.5);
    background-image: none;
    border-color: var(--brown-50);
    color: var(--brown-90);
}
.sent__close::after { border-color: var(--brown-50); opacity: 0.45; }
.sent__close:hover {
    background-color: var(--cream-00);
    border-color: #8F3A0C;
    color: #8F3A0C;
}
.sent__close:hover::after { border-color: var(--orange-70); opacity: 0.8; }
.sent__close:focus-visible { outline: 2px solid var(--orange-70); outline-offset: 3px; }

@media (max-width: 479.98px) {
    /* Narrow the border, never the centre: the message has to stay readable,
       and 30px of print still reads as a printed edge. */
    .sent { --scarf-band: 30px; --scarf-pad: var(--s5); aspect-ratio: auto; }
    /* padding follows --scarf-pad; nothing to restate */
    .sent__body { max-width: none; }
}

/* It settles rather than appearing, and only if motion is welcome. */
@media (prefers-reduced-motion: no-preference) {
    .sent[open] { animation: sent-settle 380ms var(--ease) both; }
    .sent[open]::backdrop { animation: sent-fade 380ms var(--ease) both; }
}
@keyframes sent-settle {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: none; }
}
@keyframes sent-fade { from { opacity: 0; } to { opacity: 1; } }
