Showcase
A gallery of what's possible when you combine <pattern-grid>, modern CSS, and the seeded <seed-context> companion. Each tile is a live element β view source or expand the details below it to see exactly how it's built.
Source
<pattern-grid shim="sibling" cells="12x12"></pattern-grid>
#sc-hex pattern-grid > i {
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
background: hsl(calc(var(--i, sibling-index()) * 6) 65% 55%);
}Source
<pattern-grid shim="sibling" cells="6x6"></pattern-grid>
#sc-pinwheels pattern-grid > i {
background: conic-gradient(
from calc(var(--i, sibling-index()) * 10deg),
hsl(calc(var(--i, sibling-index()) * 20) 70% 55%),
hsl(calc(var(--i, sibling-index()) * 20 + 180) 70% 55%),
hsl(calc(var(--i, sibling-index()) * 20) 70% 55%)
);
}Source
<pattern-grid shim="sibling" cells="10x10"></pattern-grid>
#sc-triangles pattern-grid > i:nth-child(odd) { clip-path: polygon(50% 0%, 100% 100%, 0% 100%); }
#sc-triangles pattern-grid > i:nth-child(even) { clip-path: polygon(0% 0%, 100% 0%, 50% 100%); }
#sc-triangles pattern-grid > i {
background: hsl(calc(var(--i, sibling-index()) * 4 + 200) 70% 55%);
}sin(sibling-index() * 30deg).Source
<pattern-grid shim="sibling" cols="24" rows="1"></pattern-grid>
#sc-sine pattern-grid > i {
align-self: end;
height: calc(50% + sin(var(--i, sibling-index()) * 30deg) * 50%);
background: hsl(calc(var(--i, sibling-index()) * 15) 70% 55%);
}Source
<pattern-grid shim="sibling" cells="400"></pattern-grid>
#sc-fib pattern-grid {
display: block;
position: relative;
}
#sc-fib pattern-grid > i {
--r: calc(sqrt(var(--i, sibling-index())) * 2.4%);
--a: calc(var(--i, sibling-index()) * 137.5deg);
position: absolute;
left: calc(50% + cos(var(--a)) * var(--r));
top: calc(50% + sin(var(--a)) * var(--r));
width: 8px; height: 8px;
border-radius: 50%;
background: hsl(calc(var(--i, sibling-index()) * 3) 70% 55%);
transform: translate(-50%, -50%);
}Source
<pattern-grid shim="sibling" cells="48"></pattern-grid>
#sc-bloom pattern-grid > i {
grid-area: 1 / 1;
width: calc(100% * var(--i, sibling-index()) / var(--n, sibling-count()));
aspect-ratio: 1;
border: 2px solid hsla(calc(var(--i, sibling-index()) * 7) 80% 60% / 0.6);
border-radius: 50%;
}Source
<pattern-grid shim="sibling" cells="14x14"></pattern-grid>
#sc-wipe pattern-grid > i {
--idx: calc(var(--i, sibling-index()) - 1);
background: hsl(220 70% 50%);
animation: wipe 3s ease-in-out infinite;
animation-delay: calc(var(--idx) * 12ms);
}
@keyframes wipe {
0%, 100% { background: hsl(220 70% 50%); }
50% { background: hsl(50 90% 60%); }
}Source
<pattern-grid shim="sibling" cells="12x12"></pattern-grid>
#sc-pulse pattern-grid > i {
background: hsl(calc(var(--i, sibling-index()) * 3) 70% 55%);
animation: pulse 2.4s ease-in-out infinite;
animation-delay: calc(var(--i, sibling-index()) * -30ms);
}
@keyframes pulse {
0%, 100% { transform: scale(1); border-radius: 0%; }
50% { transform: scale(0.6); border-radius: 50%; }
}--rand-0.Source
<seed-context seed="mosaic">
<pattern-grid cells="14x14"></pattern-grid>
</seed-context>
#sc-mosaic pattern-grid > i {
background: hsl(calc(var(--rand-0) * 360deg) 70% 55%);
}Source
<seed-context seed="frost">
<pattern-grid cells="14x14"></pattern-grid>
</seed-context>
#sc-frost pattern-grid > i {
background: hsl(220 80% 65% / calc(0.3 + var(--rand-0) * 0.7));
transform: rotate(calc((var(--randi-1) - 50) * 1deg));
}Source
<seed-context seed="cryst" count="2">
<pattern-grid cells="10x10"></pattern-grid>
</seed-context>
#sc-crystals pattern-grid > i:nth-child(odd) { clip-path: polygon(0% 0%, 100% 0%, 50% 100%); }
#sc-crystals pattern-grid > i:nth-child(even) { clip-path: polygon(50% 0%, 100% 100%, 0% 100%); }
#sc-crystals pattern-grid > i {
background: hsl(calc(var(--rand-1) * 360deg) 70% 55%);
}sibling-index(), so it works in every browser.Source
<seed-context seed="stair">
<pattern-grid cells="12x12"></pattern-grid>
</seed-context>
#sc-staircase pattern-grid > i {
background: hsl(calc(var(--rand-1) * 360) 70% 55%);
transform: translateY(calc((var(--rand-0) - 0.5) * 40%));
}Source
<seed-context seed="rain">
<pattern-grid cells="12x12">
<template><span class="glyph"></span></template>
</pattern-grid>
</seed-context>
#sc-rain .glyph::before { content: "β"; }
#sc-rain .glyph:nth-child(10n+1)::before { content: "β"; }
/* ... 8 more rotations of nth-child ... */
#sc-rain .glyph {
color: hsl(140 80% calc(40% + var(--rand-0) * 40%));
opacity: calc(0.3 + var(--rand-0) * 0.7);
}Source
<pattern-grid cells="10x10"></pattern-grid>
#sc-ripple pattern-grid > i {
background: var(--bg-tertiary);
transition: background 0.4s, transform 0.4s;
}
#sc-ripple pattern-grid > i:hover,
#sc-ripple pattern-grid > i:hover + i,
#sc-ripple pattern-grid > i:has(+ i:hover) {
background: hsl(200 80% 55%);
transform: scale(1.1);
}Source
<pattern-grid shim="sibling" cols="14" rows="1">
<template><span class="col"></span></template>
</pattern-grid>
#sc-matrix .col {
/* repeating linear-gradient of katakana glyphs scrolls vertically */
background: linear-gradient(180deg, transparent, #0f3 50%, transparent);
animation: fall 6s linear infinite;
animation-delay: calc(var(--i, sibling-index()) * -0.5s);
}
@keyframes fall { to { background-position-y: 200%; } }Source
<pattern-grid shim="sibling" cells="14x14"></pattern-grid>
#sc-plasma pattern-grid > i {
background: conic-gradient(
from calc(var(--i, sibling-index()) * 13deg),
hsl(calc(var(--i, sibling-index()) * 7) 80% 60%),
hsl(calc(var(--i, sibling-index()) * 7 + 180) 80% 60%)
);
animation: plasma 6s linear infinite;
animation-delay: calc(var(--i, sibling-index()) * -20ms);
}
@keyframes plasma {
to { filter: hue-rotate(360deg); }
}Source
<pattern-grid shim="sibling" cols="1" rows="60"></pattern-grid>
#sc-raster pattern-grid > i {
background: hsl(calc(var(--i, sibling-index()) * 6) 90% 55%);
animation: scroll 3s linear infinite;
animation-delay: calc(var(--i, sibling-index()) * -50ms);
}
@keyframes scroll {
0%, 100% { filter: brightness(1); }
50% { filter: brightness(1.4) saturate(1.5); }
}Source
<pattern-grid shim="sibling" cells="240"></pattern-grid>
#sc-sphere pattern-grid > i {
/* fibonacci sphere lattice */
--i: calc(var(--i, sibling-index()) - 1);
--phi: calc(acos(1 - 2 * var(--i) / 240));
--theta: calc(var(--i) * 137.5deg);
position: absolute;
left: calc(50% + sin(var(--phi)) * cos(var(--theta)) * 40%);
top: calc(50% + cos(var(--phi)) * 40%);
width: calc(4px + sin(var(--phi)) * sin(var(--theta)) * 4px + 4px);
background: hsl(180 50% calc(60% + sin(var(--phi)) * sin(var(--theta)) * 30%));
}Source
<seed-context seed="ranch" count="2">
<pattern-grid cols="1" rows="12"></pattern-grid>
</seed-context>
#sc-searanch pattern-grid > i {
background: hsl(var(--vb-stripe-hue, calc(var(--rand-0) * 360)) 75% 55%);
clip-path: polygon(0 calc(var(--rand-1) * 30%), 100% 0, 100% calc(100% - var(--rand-1) * 30%), 0 100%);
}Source
<seed-context seed="pushpin" count="2">
<pattern-grid cells="6x6"></pattern-grid>
</seed-context>
/* 4 shapes picked by --randi-0 % 4, hue from --rand-1 */
#sc-pushpin pattern-grid > i:nth-child(4n+0) { clip-path: circle(46% at 50% 50%); }
#sc-pushpin pattern-grid > i:nth-child(4n+1) { clip-path: inset(8%); }
#sc-pushpin pattern-grid > i:nth-child(4n+2) { clip-path: polygon(50% 5%, 95% 95%, 5% 95%); }
#sc-pushpin pattern-grid > i:nth-child(4n+3) { clip-path: polygon(50% 5%, 95% 35%, 78% 90%, 22% 90%, 5% 35%); }
#sc-pushpin pattern-grid > i {
background: hsl(calc(var(--rand-1) * 360deg) 75% 55%);
filter: drop-shadow(3px 3px 0 #111);
}Source
<pattern-grid shim="sibling" cells="40x20"></pattern-grid>
#sc-riley pattern-grid > i {
--col: calc(mod(var(--i, sibling-index()) - 1, 40));
background: black;
transform: scaleY(calc(0.6 + sin(var(--col) * 9deg) * 0.4));
}
#sc-riley pattern-grid > i:nth-child(even) { background: white; }--rand-0.Source
<seed-context seed="halftone">
<pattern-grid cells="20x20"></pattern-grid>
</seed-context>
#sc-halftone pattern-grid > i {
background: radial-gradient(
circle,
var(--vb-ink, #c2185b) calc(var(--rand-0) * 50%),
transparent calc(var(--rand-0) * 50% + 1%)
);
}Source
<seed-context seed="emoji" count="3">
<pattern-grid cells="10x10">
<template><span class="em"></span></template>
</pattern-grid>
</seed-context>
#sc-emoji .em::before { content: "πΈ"; }
#sc-emoji .em:nth-child(10n+1)::before { content: "β"; }
#sc-emoji .em:nth-child(10n+2)::before { content: "π"; }
/* ... 7 more rotations ... */
#sc-emoji .em {
/* static random tilt + scale */
transform: rotate(calc((var(--randi-1) - 50) * 4deg))
scale(calc(0.7 + var(--rand-2) * 0.5));
/* animated bob + wobble; standalone properties compose with transform */
animation: shower 3.6s ease-in-out infinite;
animation-delay: calc(var(--rand-0) * -3.6s);
}
@keyframes shower {
0%, 100% { translate: 0 -10%; rotate: -6deg; }
50% { translate: 0 10%; rotate: 6deg; }
}Source
<seed-context seed="stars" count="3">
<pattern-grid cells="20x20"></pattern-grid>
</seed-context>
#sc-stars pattern-grid > i {
background: radial-gradient(circle, #fff 0%, transparent 60%);
opacity: calc((var(--rand-0) - 0.88) * 100);
animation: twinkle 4s ease-in-out infinite;
animation-delay: calc(var(--rand-1) * -4s);
}
@keyframes twinkle {
0%, 100% { transform: scale(1); filter: brightness(1); }
50% { transform: scale(1.4); filter: brightness(1.6); }
}Source
<pattern-grid shim="sibling" cells="6"></pattern-grid>
#sc-clover pattern-grid > i {
grid-area: 1 / 1;
width: 70%;
aspect-ratio: 1;
background: hsl(calc(var(--i, sibling-index()) * 60) 80% 55%);
clip-path: ellipse(20% 50% at 50% 50%);
transform: rotate(calc(var(--i, sibling-index()) * 60deg));
mix-blend-mode: multiply;
}Source
<div class="mandala-stack">
<pattern-grid shim="sibling" cells="8"></pattern-grid> <!-- inner -->
<pattern-grid shim="sibling" cells="12"></pattern-grid> <!-- middle -->
<pattern-grid shim="sibling" cells="24"></pattern-grid> <!-- outer -->
</div>
/* Each ring positions its cells via polar math with a
different --r (radius) per ring. */feGaussianBlur + feColorMatrix.Source
<filter id="sc-goo-filter">
<feGaussianBlur stdDeviation="6" />
<feColorMatrix values="... 0 0 0 22 -10" />
</filter>
#sc-goo .tile { filter: url(#sc-goo-filter); }
#sc-goo pattern-grid > i {
/* cells drift around with a per-cell offset animation */
animation: drift 5s ease-in-out infinite;
animation-delay: calc(var(--i, sibling-index()) * -80ms);
}feTurbulence + feDisplacementMap warps a colorful grid.Source
<filter id="sc-turb-filter">
<feTurbulence type="fractalNoise" baseFrequency="0.018" numOctaves="2" />
<feDisplacementMap scale="22" />
</filter>
#sc-turb pattern-grid { filter: url(#sc-turb-filter); }
#sc-turb pattern-grid > i {
background: hsl(calc(var(--i, sibling-index()) * 7) 80% 60%);
}Source
<pattern-grid id="sc-gol-grid" cells="24x24"></pattern-grid>
<script>
const grid = document.getElementById('sc-gol-grid');
const W = 24, N = W * W;
let state = Array.from({ length: N }, () => +(Math.random() < 0.3));
function step() {
const next = state.slice();
for (let i = 0; i < N; i++) {
const x = i % W, y = (i / W) | 0;
let n = 0;
for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {
if (dx || dy) n += state[((y+dy+W)%W) * W + (x+dx+W)%W];
}
next[i] = state[i] ? (n === 2 || n === 3 ? 1 : 0) : (n === 3 ? 1 : 0);
}
state = next;
grid.cellElements.forEach((c, i) => c.classList.toggle('alive', !!state[i]));
}
grid.addEventListener('pattern-grid:render', () => { step(); }, { once: true });
if (!matchMedia('(prefers-reduced-motion: reduce)').matches) setInterval(step, 200);
</script>Source
<pattern-grid shim="sibling" id="sc-heat-grid" cells="20x20"></pattern-grid>
<script>
const grid = document.getElementById('sc-heat-grid');
grid.addEventListener('pointermove', (e) => {
const r = grid.getBoundingClientRect();
grid.style.setProperty('--mx', (e.clientX - r.left) / r.width);
grid.style.setProperty('--my', (e.clientY - r.top) / r.height);
});
</script>
@property --mx { syntax: '<number>'; inherits: true; initial-value: 0.5; }
@property --my { syntax: '<number>'; inherits: true; initial-value: 0.5; }
#sc-heat pattern-grid > i {
--idx: calc(var(--i, sibling-index()) - 1);
--col: calc(mod(var(--idx), 20) / 20);
--row: calc(floor(calc(var(--idx) / 20)) / 20);
--dx: calc(var(--col) - var(--mx));
--dy: calc(var(--row) - var(--my));
--d: calc(sqrt(var(--dx) * var(--dx) + var(--dy) * var(--dy)));
background: hsl(calc(var(--d) * 720deg) 75% calc(40% + var(--d) * 50%));
}cells="6" β six cube faces via translateZ + rotate.Source
<div class="cube-perspective">
<div class="cube-wrap"> <!-- plain div holds preserve-3d -->
<pattern-grid cells="6"></pattern-grid>
</div>
</div>
.cube-wrap { transform-style: preserve-3d; }
/* display:contents so the faces parent to the plain div above β Safari
won't establish a 3D context on the custom element itself. */
#sc-cube pattern-grid { display: contents; }
#sc-cube pattern-grid > i {
position: absolute;
inset: 0;
margin: auto;
width: 80px; height: 80px;
}
#sc-cube pattern-grid > i:nth-child(1) { transform: translateZ( 40px); background: #c33; }
#sc-cube pattern-grid > i:nth-child(2) { transform: translateZ(-40px) rotateY(180deg); background: #3c3; }
#sc-cube pattern-grid > i:nth-child(3) { transform: rotateY( 90deg) translateZ(40px); background: #33c; }
#sc-cube pattern-grid > i:nth-child(4) { transform: rotateY(-90deg) translateZ(40px); background: #cc3; }
#sc-cube pattern-grid > i:nth-child(5) { transform: rotateX( 90deg) translateZ(40px); background: #3cc; }
#sc-cube pattern-grid > i:nth-child(6) { transform: rotateX(-90deg) translateZ(40px); background: #c3c; }Source
<pattern-grid shim="sibling" cells="12"></pattern-grid>
#sc-kaleido pattern-grid > i {
grid-area: 1 / 1;
width: 100%; aspect-ratio: 1;
/* 30Β° pie wedge from center */
clip-path: polygon(50% 50%, 50% 0%, 100% 0%);
/* same source pattern on every wedge */
background: conic-gradient(from -15deg,
hsl(0 90% 60%), hsl(40 90% 60%), hsl(80 90% 60%),
hsl(140 80% 55%), hsl(220 80% 55%), hsl(300 80% 55%), hsl(360 90% 60%));
transform: rotate(calc((var(--i, sibling-index()) - 1) * 30deg));
}
#sc-kaleido pattern-grid > i:nth-child(even) {
transform: rotate(calc((var(--i, sibling-index()) - 1) * 30deg)) scale(-1, 1);
transform-origin: 50% 50%;
}Source
<pattern-grid id="sc-fractal-grid" cells="50x50"></pattern-grid>
<script>
const grid = document.getElementById('sc-fractal-grid');
const W = 50;
/* three vertices of a triangle, in [0,1] grid coords */
const V = [
{ x: 0.5, y: 0.05 },
{ x: 0.05, y: 0.95 },
{ x: 0.95, y: 0.95 },
];
let px = 0.5, py = 0.5;
function step() {
for (let n = 0; n < 60; n++) {
const v = V[Math.floor(Math.random() * 3)];
px = (px + v.x) / 2;
py = (py + v.y) / 2;
const cell = grid.cellElements[((py * W) | 0) * W + ((px * W) | 0)];
if (cell) cell.classList.add('hit');
}
}
grid.addEventListener('pattern-grid:render', () => step(), { once: true });
setInterval(step, 50);
</script>atan2() angle plus radial distance β so the pulse sweeps outward as a rotating spiral. Pure CSS, no JS.Source
<pattern-grid cells="20x20" shim="sibling"></pattern-grid>
#sc-swirl pattern-grid > i {
--sw-i: calc(var(--i, sibling-index()) - 1); /* 0-based; --i from shim */
--sw-x: calc(mod(var(--sw-i), var(--pg-cols)) - (var(--pg-cols) - 1) / 2);
--sw-y: calc((var(--pg-cols) - 1) / 2 - round(down, var(--sw-i) / var(--pg-cols)));
--sw-t: calc(var(--sw-i) / (var(--n, sibling-count()) - 1));
--sw-hue: calc(mod(sin(var(--sw-t) * 90deg) * 3600, 1) * 360);
--sw-dist: calc(sqrt(var(--sw-x) * var(--sw-x) + var(--sw-y) * var(--sw-y)) / sqrt(200));
--sw-ang: calc(atan2(var(--sw-x), var(--sw-y)) / -360deg);
--sw-delay: calc(var(--sw-dist) * 9s + var(--sw-ang) * 3s - 12s);
translate: calc(var(--sw-x) * var(--size)) calc(var(--sw-y) * var(--size));
background: hsl(var(--sw-hue) 100% calc(80% - var(--sw-dist) * 60%));
animation: sc-swirl-pulse 1.5s var(--sw-delay) infinite ease-in-out alternate;
}Modern platform showcase
The previous pieces match what css-doodle does. The next ten show what only a light-DOM component can do β real semantic cells, View Transitions, Anchor Positioning, Popover, scroll-driven animations, :has(), container queries, Houdini Paint Worklet β features that live outside the Shadow-DOM wall.
<button> cells with ARIA pressed state; Web Audio plays the toggled cells on a 120 BPM transport.Source
<pattern-grid cols="16" rows="4">
<template><button type="button" aria-pressed="false"></button></template>
</pattern-grid>view-transition-name animates a reshuffle natively via the View Transitions API.Source
document.startViewTransition(() => {
// reorder cells; each has a unique view-transition-name
});animation-timeline: view() β entry/exit driven entirely by scroll position. The Replay button retriggers the same keyframes on a time-based fallback.Source
#sc-scroll pattern-grid > i {
animation: sc-reveal both;
animation-timeline: view();
animation-range: entry 0% cover 30%;
animation-delay: calc((var(--i, sibling-index())) * -8ms);
}<button popovertarget>. Popovers dock beside their cell via CSS Anchor Positioning.Source
<button popovertarget="pop-N" style="anchor-name: --c-N"></button>
<div id="pop-N" popover style="position-anchor: --c-N; left: anchor(right); top: anchor(top)">...</div>:has() with zero JavaScript.Source
#sc-neighbor i:hover,
#sc-neighbor i:has(+ i:hover),
#sc-neighbor i:hover + i { background: var(--accent); }clip-path: polygon() β pattern-grid's answer to css-doodle's @shape().Source
#sc-shape pattern-grid > i {
clip-path: polygon(...trig...);
rotate: calc(var(--i, sibling-index()) * 8deg);
}<pattern-grid> whose <template> contains another <pattern-grid>. Custom elements upgrade naturally inside cloned templates β Sierpinski carpet without a DSL.Source
<pattern-grid cells="3x3">
<template>
<div><pattern-grid shim="sibling" cells="3x3"></pattern-grid></div>
</template>
</pattern-grid>Houdini paint() isnβt supported in this browser. Open in Chrome to see the worklet.
paint(swirl) β a Houdini Paint Worklet registered declaratively via <paint-worklet src>. The platform answer to css-doodle's @shaders(). Chrome-only; Firefox/Safari show a "not supported" notice.Source
<paint-worklet src="worklets/swirl.js"></paint-worklet>
#sc-paint pattern-grid > i { background: paint(swirl); }
/* No paint() API (Firefox, Safari): hide the demo, show a notice */
@supports not (background: paint(id)) {
#sc-paint seed-context { display: none; }
#sc-paint .sc-no-support { display: grid; }
}<button> cells β keyboard-focusable, screen-reader-readable, real dates, color-mix() for perceptual gradients. css-doodle's Shadow DOM cannot expose this surface.Source
<pattern-grid cols="53" rows="7">
<template><button aria-label="day"></button></template>
</pattern-grid>
/* color via color-mix(in oklch, low, high calc(--randi-0 * 1%)) */cos(3t)/sin(4t) β pattern-grid's answer to css-doodle's @plot().Source
#sc-lissajous pattern-grid > i {
--t: calc(var(--i, sibling-index()) / var(--pg-cols) * 6.2832);
translate: calc(cos(var(--t) * 3) * 40%) calc(sin(var(--t) * 4) * 40%);
}Tributes β abstract art & 8-bit sprites
A small gallery tour: three hard-edge geometric tributes (Mondrian, Albers, Sol LeWitt), three painterly ones (Kandinsky, Kelly, Kusama), six pixel-sprite pieces (single sprites, an animated chomp, a hover-swap, a CSS-only sprite-sheet picker, a palette-cycling sprite, and an interactive paint scratchpad), and two pop-and-illusion crossovers (Escher tessellation, Lichtenstein POW with popover and CSS Anchor Positioning).
grid-area spans; primary colours, off-white fields, thick black gutters via gap + black host background. The grid here is plain CSS β <pattern-grid> would re-render uneven cell counts.Source
.sc-mondrian-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-template-rows: repeat(6, 1fr);
gap: 6px;
background: #111;
aspect-ratio: 1;
padding: 6px;
}
.sc-mondrian-grid > [data-fill="red"] { background: #dd2222; }
.sc-mondrian-grid > [data-fill="blue"] { background: #1f3fbf; }
.sc-mondrian-grid > [data-fill="yellow"] { background: #f0d040; }
.sc-mondrian-grid > [data-fill="white"] { background: #f4f0e6; }<pattern-grid> needed here β pure nesting demonstrates the same compositional grammar.Source
<div class="sc-albers-layer sc-albers-l1">
<div class="sc-albers-layer sc-albers-l2">
<div class="sc-albers-layer sc-albers-l3">
<div class="sc-albers-layer sc-albers-l4"></div>
</div>
</div>
</div>
.sc-albers-layer { aspect-ratio: 1; display: grid; }
.sc-albers-l1 { background: #6a2818; padding: 8% 8% 14% 8%; }
.sc-albers-l2 { background: #b25a26; padding: 8% 8% 14% 8%; }
.sc-albers-l3 { background: #d77a37; padding: 8% 8% 14% 8%; }
.sc-albers-l4 { background: #f0c266; }calc(sibling-index() * 17deg). Zero JavaScript art that feels hand-drawn but is entirely algorithmic.Source
#sc-lewitt pattern-grid > i {
aspect-ratio: 1;
background: #f4ede2;
position: relative;
}
#sc-lewitt pattern-grid > i::before {
content: "";
position: absolute;
inset: 0;
background: #1a1a1a;
clip-path: polygon(0 49%, 100% 49%, 100% 51%, 0 51%);
rotate: calc(var(--i, sibling-index()) * 17deg);
}Source
<seed-context seed="kandinsky" count="3">
<pattern-grid cols="4" rows="3"></pattern-grid>
</seed-context>
#sc-kandinsky pattern-grid > i {
background:
radial-gradient(circle at center,
hsl(calc(var(--rand-0) * 360) 75% 55%) 0 18%,
transparent 19%),
radial-gradient(circle at center,
hsl(calc(var(--rand-1) * 360) 75% 55%) 0 30%,
transparent 31%),
radial-gradient(circle at center,
hsl(calc(var(--rand-2) * 360) 75% 55%) 0 42%,
transparent 43%),
hsl(calc(var(--rand-0) * 360) 30% 90%);
}color-mix(in oklch).Source
<seed-context seed="kelly" count="2">
<pattern-grid cols="8" rows="8"></pattern-grid>
</seed-context>
#sc-kelly pattern-grid > i {
aspect-ratio: 1;
background: color-mix(in oklch,
hsl(calc(var(--rand-0) * 360) 85% 55%),
white calc(var(--rand-1) * 25%));
}--rand-0. Hover any cell to make its dot bloom.Source
<seed-context seed="kusama" count="1">
<pattern-grid cols="16" rows="16"></pattern-grid>
</seed-context>
#sc-kusama pattern-grid > i {
aspect-ratio: 1;
background:
radial-gradient(circle at center,
#c00 0 calc(var(--rand-0) * 40%),
#fff calc(var(--rand-0) * 40% + 1px) 100%);
transition: scale 200ms;
}
#sc-kusama pattern-grid > i:hover { scale: 1.4; z-index: 2; }data-px="0|1". CSS maps data-px="1" to bright green. The pixel string is the source of truth.Source
<pattern-grid class="pixel-grid" cols="11" rows="8"></pattern-grid>
const pixels =
"00100000100" +
"00010001000" +
"00111111100" +
"01101110110" +
"11111111111" +
"10111111101" +
"10100000101" +
"00011011000";
grid.innerHTML = [...pixels].map(c => `<i data-px="${c}"></i>`).join('');
#sc-invader [data-px="1"] { --px: #2eff2e; }innerHTML. CSS handles the colour.Source
const frames = [
/* closed */ "...",
/* open */ "...",
];
let f = 0;
const draw = () => grid.innerHTML = [...frames[f]].map(c => `<i data-px="${c}"></i>`).join('');
setInterval(() => { f = 1 - f; draw(); }, 200);
#sc-chomp [data-px="1"] { --px: #ffeb00; }Source
<div class="tile sc-hoverswap-tile">
<pattern-grid class="pixel-grid" cols="8" rows="8" id="..."></pattern-grid>
<pattern-grid class="pixel-grid" cols="8" rows="8" id="..."></pattern-grid>
</div>
.sc-hoverswap-tile { position: relative; }
.sc-hoverswap-tile > pattern-grid { position: absolute; inset: 0; transition: opacity 200ms; }
#sc-hover-jump { opacity: 0; }
.sc-hoverswap-tile:hover #sc-hover-idle { opacity: 0; }
.sc-hoverswap-tile:hover #sc-hover-jump { opacity: 1; }<pattern-grid> with its own pixel string and palette.Source
<div class="sc-sheet-atlas">
<pattern-grid class="pixel-grid sc-hero" cols="16" rows="16" style="grid-area: 1/1/3/3"></pattern-grid>
<pattern-grid class="pixel-grid" cols="8" rows="8" style="grid-area: 1/3"></pattern-grid>
β¦ 17 more small sprites β¦
</div>
.sc-sheet-atlas {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(5, 1fr);
gap: 4px;
aspect-ratio: 1;
}
#sc-sp-h1 [data-px="1"] { --px: #f4c290; } /* skin */
#sc-sp-h1 [data-px="2"] { --px: #4a6cd4; } /* armor */
β¦@property --hue + @keyframes. CSS-only colour cycling.Source
@property --hue { syntax: '<angle>'; inherits: true; initial-value: 0deg; }
#sc-cycle { animation: sc-hue-cycle 4s linear infinite; }
@keyframes sc-hue-cycle { to { --hue: 360deg; } }
#sc-cycle [data-px="1"] { --px: hsl(var(--hue) 85% 60%); }<button> cells. Click cycles data-px through four palette slots. No save, no load β pure play.Source
grid.addEventListener('click', e => {
const btn = e.target.closest('button[data-px]');
if (!btn) return;
btn.dataset.px = (Number(btn.dataset.px) + 1) % 4;
});
#sc-paint-pad [data-px="0"] { --px: #1a1a24; }
#sc-paint-pad [data-px="1"] { --px: #ff5470; }
#sc-paint-pad [data-px="2"] { --px: #ffd166; }
#sc-paint-pad [data-px="3"] { --px: #06d6a0; }clip-path can't tessellate β any shape that recedes from the cell edges leaves background showing β so the motif lives in a tiling background-image instead.Source
<pattern-grid cols="11" rows="11"></pattern-grid>
#sc-escher pattern-grid > i {
/* full square; a seamless SVG gull tile, offset every other row */
background: url("data:image/svg+xml,…three gull paths…") 0 0 / 100% 100%;
}popover). Click the outlined cell.Source
<pattern-grid cols="6" rows="4"></pattern-grid>
<div id="sc-pow-balloon" popover>POW!</div>
// make one existing cell the trigger (a <button> grid item lays out
// outside the grid in Chrome/Safari, so keep the <i> and wire it up)
const hero = grid.cellElements[10];
hero.className = 'sc-pow-hero';
hero.addEventListener('click', () => popover.togglePopover());
// position beside the cell before it paints (no anchor() in the top layer yet)
popover.addEventListener('beforetoggle', e => {
if (e.newState !== 'open') return;
const r = hero.getBoundingClientRect();
popover.style.top = r.top - 80 + 'px';
popover.style.left = r.right + 8 + 'px';
});