- 2.1: packs/ -> archive/packs/ - 2.2: site/ -> archive/site/ - 2.3: src/lab/ -> archive/lab/ - 2.4: examples/ -> archive/examples-legacy/ (SSI-based)
521 lines
18 KiB
HTML
521 lines
18 KiB
HTML
<!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><div></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>
|