HOW THIS
WAS POURED
This page is the colophon for MASSVOID, site 06 of the fable-25 set. Same mix design as the gallery: two Archivos, four hexes, twelve columns, and concrete that is computed, never photographed. Everything below is quoted from the real source.
THE BRIEF
A brutalist gallery for concrete art. Aggressive order: the grid is visible, enormous, and load-bearing. Signature techniques, as issued: procedural concrete from layered feTurbulence grain plus multiplied stains, built as a small “pour” utility so no two panels match; an exposed 12-column grid with visible gridlines and A1–L12 coordinates used as navigation; massive display type set in the grid with deliberate overlaps and hard cropping; a hover slice/skew on works with a hard 120 ms snap and no easing softness.
The one thing this site must prove: that “ugly” materials and a rigid grid can produce beauty: the chaos is gridded underneath, every overlap placed, not accidental. Type: Archivo Black for display, Archivo (variable width) for everything else, vendored as latin woff2 subsets. The width axis stands in for italics everywhere; the variable file ships no italic and nothing here pretends otherwise.
THE POUR UTILITY
Every element carrying data-pour gets its own batch of concrete at runtime. A seeded PRNG (mulberry32 over an FNV-1a hash of kind, index, and batch number) drives four layer families: fine feTurbulence tooth, a pale soft-light fleck, one to four multiplied radial stains (sometimes rust-tinted), an occasional trowel-drift banding, and a large-scale cure mottle. The layers compose as CSS background-images with per-layer blend modes. Deterministic per panel, different across panels: no two match, and the RE-POUR control in the title block advances the batch number to prove it.
This panel you are reading sits on the page, not in a pour; the code panels below are poured black. On top of the texture, each work’s slab gets a small procedural SVG artwork per kind — slumped cone, board-marked wall, aggregate cube, rain streaks, post-tensioned beam, 144 tie holes — drawn from the same seed stream.
function grainLayer(r, o) {
var seed = 1 + ((r() * 9998) | 0);
var bf = (o.bf * (0.8 + r() * 0.5)).toFixed(4);
var svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' + o.size + '" height="' + o.size + '">'
+ '<filter id="g"><feTurbulence type="fractalNoise" baseFrequency="' + bf
+ '" numOctaves="' + o.oct + '" seed="' + seed + '" stitchTiles="stitch"/>'
+ '<feColorMatrix type="matrix" values="0 0 0 0 ' + o.v + ' 0 0 0 0 ' + o.v
+ ' 0 0 0 0 ' + o.v + ' ' + o.a + ' 0 0 0 0"/></filter>'
+ '<rect width="100%" height="100%" filter="url(#g)"/></svg>';
return { img: 'url("data:image/svg+xml,' + encodeURIComponent(svg) + '")',
size: o.size + 'px ' + o.size + 'px', blend: o.blend || 'multiply' };
}
function pour(el, i) {
var kind = el.getAttribute('data-kind') || 'plain';
var r = rng(hash(kind + '/' + i + '/' + EPOCH)); // EPOCH++ on RE-POUR
var b = (BASE[kind] || BASE.plain).map(function (v) { return Math.round(v + r() * 10 - 5); });
el.style.backgroundColor = 'rgb(' + b.join(',') + ')';
var L = [];
L.push(grainLayer(r, { bf: 0.82, oct: 2, v: 0.06, a: 0.11, size: 264 })); // fine tooth
L.push(grainLayer(r, { bf: 0.62, oct: 2, v: 1, a: 0.085, size: 300,
blend: 'soft-light' })); // pale fleck
var stains = kind === 'sheet' ? 1 : 2 + ((r() * 3) | 0);
for (var k = 0; k < stains; k++) L.push(stainLayer(r)); // multiplied stains
if (r() < 0.7 && kind !== 'sheet') L.push(trowelLayer(r)); // trowel drift
L.push(grainLayer(r, { bf: 0.012, oct: 3, v: 0.10, a: 0.20, size: 860 })); // cure mottle
el.style.backgroundImage = L.map(function (x) { return x.img; }).join(',');
el.style.backgroundSize = L.map(function (x) { return x.size; }).join(',');
el.style.backgroundBlendMode = L.map(function (x) { return x.blend; }).join(',');
drawArt(el, kind, r);
}
THE LOAD-BEARING GRID
The twelve columns are drawn, not implied: a fixed layer of hairlines runs over the whole page in mix-blend-mode: difference, so one gray reads on raw concrete, on void black, and across the type. A ruler strip pins letters A–L to the columns; numbered row rules mark the twelve rows; navigation is grid references (WORKS lives at A4, the schedule at G7). Every section is the same repeat(12, 1fr), and the works are placed by coordinate, overlaps included: Kettunen’s slab is pulled up into the WORKS heading by exactly 30% of the display size.
The header readout is a survey instrument: it reports the column letter under your cursor and the row band in view, e.g. E·04 / L12. The page is a drawing; you are on it.
.gridlines{
position:fixed;inset:0;z-index:60;pointer-events:none;
display:grid;grid-template-columns:repeat(12,1fr);
mix-blend-mode:difference;
}
.gridlines i{border-left:1px solid #45423b}
.grid{display:grid;grid-template-columns:repeat(12,1fr);position:relative}
#w-kettunen{grid-column:2/7;grid-row:2/4;margin-top:calc(var(--worksF) * -0.30)}
#w-sokol{grid-column:7/13;grid-row:2/4;margin-top:calc(var(--worksF) * 0.62)}
#w-fuchs{grid-column:1/6;grid-row:5/7;margin-top:clamp(40px,5vw,80px)}
#w-jonsdottir{grid-column:6/12;grid-row:5/7;margin-top:clamp(150px,20vw,330px)}
#w-reedik{grid-column:1/13;grid-row:8}
#w-demir{grid-column:1/7;grid-row:10}
.sched{grid-column:7/13;grid-row:10;margin-top:clamp(80px,10vw,160px)}
THE 120 MS SLICE
Hovering a work displaces it like a mis-struck form: the slab shifts, its artwork skews the other way, and a ::after that inherits the slab’s exact poured background is clipped to two horizontal bands and shoved 11 px left. Everything snaps in 120 ms on steps(2, jump-end): a mechanical detent, no easing softness. The same state serves :focus-visible for keyboards, and the harness can photograph it via ?slice=B4.
Under prefers-reduced-motion the pour-in, reveals, and all snap transitions are removed; panels arrive settled and the page stays complete. The only easing curves on the site are a custom cubic-bezier and stepped timing; the words ease and linear appear nowhere.
--snap:120ms;
--steps:steps(2,jump-end);
.wk-slab::after{
content:"";position:absolute;inset:0;z-index:2;
background:inherit;background-blend-mode:inherit; /* same batch of concrete */
clip-path:polygon(0 12%,100% 12%,100% 26%,0 26%,
0 55%,100% 55%,100% 71%,0 71%); /* two bands */
opacity:0;filter:brightness(.94);
transition:opacity var(--snap) var(--steps),transform var(--snap) var(--steps);
}
.wk-slab:hover,.wk-slab:focus-visible{transform:translate(-4px,2px)}
.wk-slab:hover .art{transform:translate(9px,-4px) skewX(-1.2deg)}
.wk-slab:hover::after{opacity:1;transform:translateX(-11px)}
PROVENANCE
Every pixel on MASSVOID is procedural: the concrete is layered SVG turbulence and CSS gradients computed in the browser, the six artworks are seeded SVG drawn by the pour utility, the stamp and gridlines are CSS. No photography, no stock, no AI-generated imagery, no runtime requests beyond this folder. Fonts are Archivo Black and Archivo Variable (width axis) from the fontsource distribution of the Google Fonts originals, vendored locally as latin woff2 subsets. No frameworks, no build step; one HTML file, one stylesheet, one script. All artists, dates, and the silo itself are fictional; the writing treats them as real because that is the job.
Weight: the whole folder ships under 200 KB including both fonts, because the concrete is math, not JPEGs. The heaviest asset is Archivo Variable at 90 KB.
CRITIQUE LOG
-
PASS 1
Saw: every poured panel was invisible: boxes held their space, painted nothing. Cause: the pour-in pre-state hid panels with clip-path inset(0 0 100%), and Chrome folds an element’s own clip-path into IntersectionObserver’s intersection rect, so hidden panels never “intersected” and never revealed. A genuine chicken-and-egg. Also: the ticket, schedule, and plate lost their internal padding to a higher-specificity cell-padding reset; the doctrine paragraph, sharing row 2, evicted the headline to row 3 (sparse auto-placement); straight quotes in the silo copy; hero stencils colliding with MASS and VOID glyphs.
Fixed: replaced the observer with plain scroll math; explicit per-panel padding rules; doctrine display lines got the full row and the paragraph moved below; curly quotes; hero stencils to clear corners; ticket bottom-locked to the slab’s baseline. Upgrade: the survey readout: the header instrument reports the cursor as a live grid coordinate.
-
PASS 2
Saw: the PASSED FOR POUR stamp truncated: the post-pour clip-path inset(0) was cropping the pseudo-element hanging off the title block. The schedule table’s YEAR column floated unanchored mid-panel; the silo section had a dead middle three columns wide; two em-dash crutches in the prose; the ticket stopped 136 px short of the hero slab’s edge instead of meeting it.
Fixed: settled clip becomes inset(-64px) so overhangs live; fixed table layout with a right-aligned year; a rust cell-history stencil fills the silo’s void; both em-dashes rewritten out; ticket stretched to kiss the slab. Upgrade: RE-POUR: a working control in the title block that re-seeds every panel (the no-two-alike proof you can operate) and advances the drawing’s REV letter.
-
PASS 3
Saw: Reedik’s beam read muddy: the catenary shadow smeared under mid-span and the beam tone sat too close to its slab; Jónsdóttir’s rain streaks could come out of the generator too faint on some seeds; everything else held under re-shoot, including RE-POUR flipping REV to B on camera.
Fixed: beam redrawn with darker supports, a contained bearing shadow, and a flagged cut line; streak generator given a darkness and length floor so every batch reads. Upgrade: the choir answers: on Tie Hole Choir (A7), holes near your cursor light safety orange and fall dark as it passes, through-lighting that follows whoever is looking.
Reduced motion was audited in CSS (all reveals, pours, and snaps removed under the media query; no continuous animation exists on the page) and the scroll-progress lerp collapses to direct assignment.