Why this direction
loominus.art's centre of gravity is five collection pages carrying 210 of its 229 images. That makes it a looking surface — the first in this portfolio, whose other families (cinematic, broadsheet, journal, signpost) are all reading surfaces. Selvedge is the finished, self-bound edge of woven cloth, and the page borrows the loom's structure literally:
- The warp — a fixed selvedge rail down the left edge (the top edge on small screens) gives every page one continuous rhythm.
- Weft bands — sections arrive as alternating linen/cotton bands, so the arbitrary section order of a migrated page reads as a pattern repeat.
- Selvedge edges — bands are bound by drawn stitch-rows, never rules or shadows.
- The masthead is a woven swatch drawn in code — CSS/SVG only, colours sampled from the products on that page, no photographic hero and no image weight. The interlace is two dashed SVG warp lines tiled as a pattern over the weft strips.
- The specimen grid + a native
<dialog>lightbox — the source pages already wrap every gallery image in a link to its own file, so with JavaScript off our anchors degrade exactly the way WordPress's lightbox does. The grid lays the photographs out as bricks rather than rows: a row-aligned grid leaves a band of dead ground under every short image beside a tall one, and multi-column flow packs each specimen against the one above it. The grid carries no text at all — captions belong to the enlarged view, because a wall of cloth wearing 137 labels reads as a spreadsheet. In the lightbox the frame is a fixed stage: the photograph is centred inside it whatever its aspect, so Previous and Next hold one position for a whole collection instead of moving with every image.
Motion: the weave draws itself in once on load — five weft strips, ≤300ms total, prefers-reduced-motion turns it off, and prose never animates. No cursor effects, no ripple.
Colour — measured, not chosen
The palette is sampled from the products the store actually sells (Wayuu brights, Kashmiri naturals, indigo), with every pairing contrast-computed before use. Three pins are permanent:
| Token | Role | Contrast note |
|---|---|---|
--wayuu-pink | Swatch only | No accessible label colour exists — white 4.29 FAIL, ink 4.04 FAIL. It appears only inside the drawn weave, never as text, never behind text. |
--marigold | Fill only | Ink on it 7.81 AA; white on it 2.22 FAIL. |
--turquoise | Fill only | Ink on it 5.48 AA; white on it 3.16 FAIL. |
--madder | Link text | 9.32 AAA on linen. |
--cta | Button fill | White on it 7.19 AAA. |
Type — cleared through the licence before it was chosen
Display is Fraunces 600 (a soft display serif with visible pen-warmth) and everything else is Work Sans 400/600, drawn for small on-screen sizes. Body is 19px minimum at 1.6. Both faces were run through the self-hosted-font-licensing check before selection: latin subsets, copyright read from the shipped binaries, and neither family declares a Reserved Font Name — inventory and the FAQ 2.6 modification note are in fonts/OFL.txt.
Fidelity — every departure from the source is recorded
/refund_returns — the source has zero headings, zero links, zero images (and its opening list betrays it as WooCommerce's stock sample policy: “Book with obvious signs of use” on a store that sells no books — flagged for Kerry, migrated as-is). Recorded transforms: the WordPress page title promoted to the missing <h1>; the two bare-paragraph section labels (“Shipping returns”, “Need help?”) promoted to <h2>; three “- ”-prefixed paragraphs made a real list. Every word is otherwise byte-true, and no link was invented — “Contact us” stays plain text because the author left it plain.
/bags-wayuu-colombia — prose, heading order, list items and all 17 gallery images in the gallery's own declared order. Recorded transforms: the source's “Shop Colombian Wayuu Bags!” button self-linked to its own page and now jumps to the collection (#gallery); captions are derived from the filenames (colour · size · view), are furniture rather than source prose, and appear in the lightbox only — never on the grid. The “Bags Homepage” button points at /selvedge/bags/, which is build-order step 6 — a known, documented gap until that page lands. The genuine external link to Femperium survives untouched.
Alt text: the source declares 10 of the 17 photographs decorative (alt="") and they are not — they are the product photography a shopper using a screen reader needs described. Those 10 now carry alt text drafted by looking at each photograph, marked data-alt-status="draft" in the markup and routed to Kerry for review (briefs/selvedge-alt-drafts.md in the repo). The other 7 alts are the source's own, verbatim. The migration's override of the source's decorative declaration is deliberate and recorded here.
The manifest defect this build caught
The section manifest this family builds from initially stamped the trailing section of all four collection pages DROP:empty — “0 words, 0 images”. Those four “empty” sections are the main lightbox galleries: 17 + 137 + 26 + 30 = 210 images, the entire specimen inventory. A Kadence advancedgallery is a self-closing block whose images live in its JSON attributes, invisible to any markup scan. Ratifying that manifest as written would have deleted every product photograph on the site — which is why a generated manifest is a proposal, and only a ratified one is a fact. The auditor now reads gallery attributes; the corrected, ratified manifest ships in the repo beside the old reading.
What the whole job found, beyond the pages
Eight numbers in the accepted baseline were wrong, and each was caught by checking a derived
figure against an authored one. The word count was short by 75 words — every
Kadence button’s visible label lives in the block’s JSON attributes, invisible to a markup
text scan, so a build that dropped every button would have passed a word count. The image count was short
by 25 — the four collection pages each carry a second, four-image gallery nobody had
counted. The CSS-background count came out at 4 instead of 15 until the reader walked
nested attribute shapes. By the end this was the same defect eight times over, always
attribute-borne content invisible to a scan of the markup: links, then gallery images, then button
labels, then background images, then a ruling that described an anchor which turned out to be a button
attribute, then a dead # destination that only an attribute read could see.
Two claims in the brief turned out to be false, and both mattered. “Zero
placeholder prose in the twelve kept pages” missed two: /bags and
/scarves each carried Kadence’s own default label Text link → behind a
dead # href — a UI-kit control label, which the token detector was not written to catch.
And /home’s “Featured In” band held five placeholder
partner logos, not four: the fifth had been uploaded into this site’s own media library,
so the provenance-by-host test — the only decidable test the brief permits — passed it. Its artwork reads
“ALOGO”. The band asserted five press relationships and none was real.
/scarves is an unfinished duplicate of /bags. Its prose was
rewritten for scarves; its links and photographs never were. All three category buttons pointed at
bags pages, each contradicting its own label, and four of its five images are bags. The
clincher is the mirror symmetry: /bags has correct category links and a stale top button,
/scarves has a correct top button and stale category links — two halves of one template,
each finished by a different pass. The links are fixed here, resolved from the store’s own category
names. The photographs are not: an image is dropped only on provenance, never on judgement, so replacing
them is the owner’s call.
And the mobile hero was stock filler. /home declared a separate
mobileBackground, and it was not a crop of the desktop photograph — it was a stock shot of a
woman in a grey hoodie. A phone visitor’s first impression of the shop was a garment it does not
sell, and no desktop review could ever have seen it.
Alt text — 220 authored descriptions
207 of the 264 image references carry alt="" in the source. On a commerce
site these are the product photographs, so the migration overrides that declaration and says so. Every
draft was written by opening the photograph, never from the filename — and on
/bags-india-suede-leather, where 137 of them live, that caught four classes of filename
defect: a file named Light-Purple holding a navy bag, a Light-Brown set that is mustard,
two different products sharing the words Strapless-Orange, and colour pairs that do not match
the panels. Where the source’s alt is non-empty it survives verbatim: the rule
overrides an empty attribute, never an author’s words. All drafts are marked
data-alt-status="draft" and tabled per page in briefs/alt-drafts/ for review.
What is deliberately not here
Twenty-two source pages are refused by name — WooCommerce state pages, five donation pages whose products never existed, and 3,052 words of template scaffolding — and the gate fails the build if any of them appears in the output, the sitemap, the redirects or the inventory. Three bands that cannot be honestly baked render as declared gaps rather than silent omissions: two live product carousels, and the newsletter form, which a static page could only accept an email address into and discard. No page emits Product or Offer structured data: the source shop’s checkout serves the cart’s own markup, and structured data for a shop that cannot sell is a lie in JSON.