asw/archive/lab/css-art.html
exe.dev user e47a9f4401 asw-v01: archive deferred content (packs, site, lab, legacy examples)
- 2.1: packs/ -> archive/packs/
- 2.2: site/ -> archive/site/
- 2.3: src/lab/ -> archive/lab/
- 2.4: examples/ -> archive/examples-legacy/ (SSI-based)
2026-06-07 10:39:21 +02:00

521 lines
18 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/asw.css">
<meta name="color-scheme" content="dark">
<title>CSS Art — ASW Lab</title>
<meta name="description" content="Pure CSS art using Open Props tokens. No JS, no images, no classes.">
<style>
/* ── Gallery layout ─────────────────────────────────────────────── */
body { background: var(--surface); }
[data-gallery] {
display: grid;
gap: 3rem;
padding: 2rem 0;
}
[data-piece] {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
align-items: center;
padding: 2rem;
background: var(--surface-1);
border: 1px solid var(--border);
border-radius: 0.75rem;
}
@media (max-width: 700px) {
[data-piece] { grid-template-columns: 1fr; }
}
[data-piece] > [data-stage] {
display: flex;
align-items: center;
justify-content: center;
min-height: 280px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 0.5rem;
overflow: hidden;
}
[data-piece] > [data-explain] > h3 {
margin-top: 0;
color: var(--text);
font-size: 1.1rem;
}
[data-piece] > [data-explain] > p {
color: var(--text-2);
font-size: 0.9rem;
line-height: 1.6;
margin-bottom: 1rem;
}
[data-piece] > [data-explain] > pre {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 0.375rem;
padding: 0.75rem;
font-size: 0.8rem;
overflow-x: auto;
margin: 0;
}
/* ── Header ─────────────────────────────────────────────────────── */
[data-art-header] {
padding: 3rem 0 1rem;
}
[data-art-header] h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
}
[data-art-header] p {
color: var(--text-2);
font-size: 1.1rem;
margin: 0;
}
/* ── Hue grid ────────────────────────────────────────────────────── */
[data-hue-grid] {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1rem;
}
[data-hue-grid] > [data-hue-swatch] {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
}
[data-hue-grid] > [data-hue-swatch] > span {
font-family: var(--font-mono);
font-size: 0.75rem;
color: var(--text-3);
}
@media (max-width: 700px) {
[data-hue-grid] { grid-template-columns: repeat(2, 1fr); }
}
/* ── Section divider ─────────────────────────────────────────────── */
[data-section-label] {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--text-3);
padding: 1rem 0 0.25rem;
border-top: 1px solid var(--border);
margin-top: 1rem;
}
/* ════════════════════════════════════════════════════════════════
ART PIECES — all via data-attributes, zero classes
════════════════════════════════════════════════════════════════ */
/* ── 1. Floating blob ──────────────────────────────────────────── */
[data-art="blob-float"] {
width: 160px;
height: 160px;
border-radius: var(--radius-blob-2);
background: var(--gradient-4);
animation: var(--animation-float);
}
/* ── 2. Spinning prism ─────────────────────────────────────────── */
[data-art="prism"] {
width: 140px;
height: 140px;
border-radius: var(--radius-blob-3);
background: var(--gradient-10);
animation: spin 4s linear infinite;
}
/* ── 3. Layered depth ──────────────────────────────────────────── */
[data-art="depth"] {
position: relative;
width: 200px;
height: 200px;
}
[data-art="depth"] > [data-layer] {
position: absolute;
border-radius: var(--radius-blob-1);
animation: var(--animation-float);
}
[data-art="depth"] > [data-layer="3"] {
width: 160px;
height: 160px;
top: 20px;
left: 20px;
background: var(--gradient-11);
opacity: 0.6;
animation-delay: 0s;
}
[data-art="depth"] > [data-layer="2"] {
width: 120px;
height: 120px;
top: 50px;
left: 40px;
background: var(--gradient-24);
opacity: 0.75;
animation-delay: -1s;
}
[data-art="depth"] > [data-layer="1"] {
width: 80px;
height: 80px;
top: 80px;
left: 60px;
background: var(--gradient-4);
animation-delay: -2s;
}
/* ── 4. Pulse ring ─────────────────────────────────────────────── */
[data-art="pulse-ring"] {
position: relative;
width: 120px;
height: 120px;
display: flex;
align-items: center;
justify-content: center;
}
[data-art="pulse-ring"]::before,
[data-art="pulse-ring"]::after {
content: "";
position: absolute;
inset: 0;
border-radius: var(--radius-blob-5);
background: var(--gradient-13);
}
[data-art="pulse-ring"]::before {
animation: pulse 2s var(--ease-out-3) infinite;
opacity: 0.5;
}
[data-art="pulse-ring"]::after {
animation: pulse 2s var(--ease-out-3) infinite;
animation-delay: -1s;
opacity: 0.4;
}
[data-art="pulse-ring"] > [data-core] {
width: 60px;
height: 60px;
border-radius: var(--radius-blob-5);
background: var(--gradient-13);
position: relative;
z-index: 1;
}
/* ── 5. Gradient text ──────────────────────────────────────────── */
[data-art="gradient-text"] {
font-family: var(--font-mono);
font-size: 3.5rem;
font-weight: 900;
line-height: 1;
background: var(--gradient-1);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
text-align: center;
letter-spacing: -0.03em;
user-select: none;
}
/* ── 6. Hue tinting demo ───────────────────────────────────────── */
[data-art="hue-blob"] {
width: 90px;
height: 90px;
border-radius: var(--radius-blob-4);
background:
radial-gradient(circle at 30% 30%, oklch(65% 0.18 var(--gray-hue, 220)), oklch(25% 0.06 var(--gray-hue, 220)));
animation: var(--animation-float);
}
[data-hue="0"] > [data-art="hue-blob"] { --gray-hue: 0; animation-delay: 0s; }
[data-hue="45"] > [data-art="hue-blob"] { --gray-hue: 45; animation-delay: -0.75s; }
[data-hue="150"] > [data-art="hue-blob"] { --gray-hue: 150; animation-delay: -1.5s; }
[data-hue="220"] > [data-art="hue-blob"] { --gray-hue: 220; animation-delay: -2.25s; }
/* ── 7. Waveform ───────────────────────────────────────────────── */
[data-art="waveform"] {
display: flex;
align-items: flex-end;
gap: 4px;
height: 80px;
padding: 0 8px;
}
[data-art="waveform"] > [data-bar] {
flex: 1;
border-radius: 3px 3px 0 0;
background: var(--gradient-4);
animation: var(--animation-bounce);
}
[data-art="waveform"] > [data-bar="1"] { height: 45%; animation-delay: -0.0s; }
[data-art="waveform"] > [data-bar="2"] { height: 70%; animation-delay: -0.2s; }
[data-art="waveform"] > [data-bar="3"] { height: 90%; animation-delay: -0.4s; }
[data-art="waveform"] > [data-bar="4"] { height: 55%; animation-delay: -0.6s; }
[data-art="waveform"] > [data-bar="5"] { height: 80%; animation-delay: -0.8s; }
[data-art="waveform"] > [data-bar="6"] { height: 65%; animation-delay: -1.0s; }
[data-art="waveform"] > [data-bar="7"] { height: 40%; animation-delay: -1.2s; }
[data-art="waveform"] > [data-bar="8"] { height: 75%; animation-delay: -1.4s; }
[data-art="waveform"] > [data-bar="9"] { height: 50%; animation-delay: -1.6s; }
[data-art="waveform"] > [data-bar="10"] { height: 85%; animation-delay: -1.8s; }
[data-art="waveform"] > [data-bar="11"] { height: 60%; animation-delay: -2.0s; }
[data-art="waveform"] > [data-bar="12"] { height: 35%; animation-delay: -2.2s; }
</style>
</head>
<body>
<nav>
<ul>
<li><a href="../index.html"><strong>ASW</strong></a></li>
<li><a href="../docs/index.html">Docs</a></li>
<li><a href="../lab/kitchen-sink.html">Lab</a></li>
</ul>
<ul>
<li><span data-text="dim">CSS Art</span></li>
</ul>
</nav>
<main data-layout="prose">
<section data-art-header>
<h1>CSS Art</h1>
<p>Pure CSS using Open Props tokens. No JavaScript, no images, no classes. Each piece is a data-attribute.</p>
</section>
<p data-text="dim">
Open Props ships <code>--radius-blob-*</code> (organic shapes), <code>--gradient-1</code><code>--gradient-30</code>,
<code>--animation-float</code>, and <code>--ease-spring-*</code>. Combined with ASW's data-attribute vocabulary,
these compose into genuine visual objects with a single HTML element.
</p>
<div data-gallery>
<!-- 1. Floating blob -->
<article data-piece>
<div data-stage>
<div data-art="blob-float"></div>
</div>
<div data-explain>
<h3>Floating blob</h3>
<p>
A single <code>&lt;div&gt;</code> with an organic border-radius from
<code>--radius-blob-2</code>, a teal-to-green linear gradient from
<code>--gradient-4</code>, and <code>--animation-float</code> — a gentle
2-axis sine wave encoded in 3 lines of CSS.
</p>
<pre><code>[data-art="blob-float"] {
width: 160px;
height: 160px;
border-radius: var(--radius-blob-2);
background: var(--gradient-4);
animation: var(--animation-float);
}</code></pre>
</div>
</article>
<!-- 2. Spinning prism -->
<article data-piece>
<div data-stage>
<div data-art="prism"></div>
</div>
<div data-explain>
<h3>Spinning prism</h3>
<p>
<code>--gradient-10</code> is a conic rainbow sweep — originally designed
for color wheels. Applied to an organic shape and spun at 4 seconds per
revolution, it becomes something else entirely. The color order is preserved,
the edge is alive.
</p>
<pre><code>[data-art="prism"] {
width: 140px;
height: 140px;
border-radius: var(--radius-blob-3);
background: var(--gradient-10);
animation: spin 4s linear infinite;
}</code></pre>
</div>
</article>
<!-- 3. Layered depth -->
<article data-piece>
<div data-stage>
<div data-art="depth">
<div data-layer="3"></div>
<div data-layer="2"></div>
<div data-layer="1"></div>
</div>
</div>
<div data-explain>
<h3>Layered depth</h3>
<p>
Three blobs, each a different gradient and opacity, each floating at
a different phase offset. The same <code>--animation-float</code>
keyframes with staggered <code>animation-delay</code> values create
independent rhythms. Nothing is synchronized; depth emerges from
desynchronization.
</p>
<pre><code>[data-layer="3"] { background: var(--gradient-11); }
[data-layer="2"] { background: var(--gradient-24); }
[data-layer="1"] { background: var(--gradient-4); }
/* stagger: 0s / -1s / -2s */</code></pre>
</div>
</article>
<!-- 4. Pulse ring -->
<article data-piece>
<div data-stage>
<div data-art="pulse-ring">
<div data-core></div>
</div>
</div>
<div data-explain>
<h3>Pulse ring</h3>
<p>
Three concentric blobs from <code>--gradient-13</code> (a deep purple-to-coral
radial), pulsing outward with staggered timing. Two are <code>::before</code>
and <code>::after</code> pseudo-elements — no extra HTML. The core sits above
them on a higher stacking context.
</p>
<pre><code>[data-art="pulse-ring"]::before,
[data-art="pulse-ring"]::after {
border-radius: var(--radius-blob-5);
background: var(--gradient-13);
animation: pulse 2s var(--ease-out-3) infinite;
}
::after { animation-delay: -1s; }</code></pre>
</div>
</article>
<!-- 5. Gradient text -->
<article data-piece>
<div data-stage>
<div data-art="gradient-text">ASW</div>
</div>
<div data-explain>
<h3>Gradient text</h3>
<p>
<code>--gradient-1</code> is a purple-to-amber diagonal — normally
a background gradient. Applied via <code>background-clip: text</code>,
it becomes a fill. The typeset word becomes a window into the gradient
space below it.
</p>
<pre><code>[data-art="gradient-text"] {
background: var(--gradient-1);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}</code></pre>
</div>
</article>
<!-- 6. Hue tinting demo -->
<article data-piece>
<div data-stage>
<div data-hue-grid>
<div data-hue-swatch>
<div data-hue="0">
<div data-art="hue-blob"></div>
</div>
<span>--gray-hue: 0</span>
<span data-text="dim">red</span>
</div>
<div data-hue-swatch>
<div data-hue="45">
<div data-art="hue-blob"></div>
</div>
<span>--gray-hue: 45</span>
<span data-text="dim">amber</span>
</div>
<div data-hue-swatch>
<div data-hue="150">
<div data-art="hue-blob"></div>
</div>
<span>--gray-hue: 150</span>
<span data-text="dim">green</span>
</div>
<div data-hue-swatch>
<div data-hue="220">
<div data-art="hue-blob"></div>
</div>
<span>--gray-hue: 220</span>
<span data-text="dim">blue</span>
</div>
</div>
</div>
<div data-explain>
<h3>Hue tinting</h3>
<p>
ASW's <code>--gray-hue</code> cascades through all surfaces. The same blob,
the same gradient formula — only the hue angle changes. The oklch color space
keeps perceptual lightness constant across hues, so red and blue land at the
same visual weight.
</p>
<pre><code>[data-hue="0"] { --gray-hue: 0; }
[data-hue="45"] { --gray-hue: 45; }
[data-hue="150"] { --gray-hue: 150; }
[data-hue="220"] { --gray-hue: 220; }
background: radial-gradient(
circle at 30% 30%,
oklch(65% 0.18 var(--gray-hue)),
oklch(25% 0.06 var(--gray-hue))
);</code></pre>
</div>
</article>
<!-- 7. Waveform -->
<article data-piece>
<div data-stage>
<div data-art="waveform">
<div data-bar="1"></div>
<div data-bar="2"></div>
<div data-bar="3"></div>
<div data-bar="4"></div>
<div data-bar="5"></div>
<div data-bar="6"></div>
<div data-bar="7"></div>
<div data-bar="8"></div>
<div data-bar="9"></div>
<div data-bar="10"></div>
<div data-bar="11"></div>
<div data-bar="12"></div>
</div>
</div>
<div data-explain>
<h3>Waveform</h3>
<p>
Twelve bars, each a different height and animation phase. The bounce keyframe
is a squish-ease: it overshoots slightly at peak and returns. The stagger
is 200ms between each bar — enough to create a wave motion, not enough to
look sequential. It reads as a signal, not a list.
</p>
<pre><code>[data-art="waveform"] > [data-bar] {
background: var(--gradient-4);
animation: var(--animation-bounce);
}
[data-bar="1"] { height: 45%; animation-delay: -0.0s; }
[data-bar="2"] { height: 70%; animation-delay: -0.2s; }
/* … 12 bars total */</code></pre>
</div>
</article>
</div><!-- /gallery -->
<hr>
<p data-text="dim">
All pieces use only Open Props tokens and ASW data-attributes.
No <code>class</code>, no <code>id</code>, no JavaScript.
Source: <a href="../packs/flask/"><code>packs/</code></a> ·
Tokens: <a href="../docs/design-tokens.html">design-tokens</a>
</p>
</main>
<footer>
<p><a href="../index.html">ASW</a> · <a href="../docs/index.html">Docs</a> · CSS Art Lab</p>
</footer>
</body>
</html>