<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>avatar gradient playground</title>
<style>
:root {
color-scheme: dark;
--bg: #0b0d12;
--panel: #14171f;
--border: #262b36;
--text: #e7e9ee;
--muted: #8b93a5;
--accent: #9db4ff;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
background: radial-gradient(120% 120% at 50% 0%, #161a24 0%, var(--bg) 60%);
color: var(--text);
font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
min-height: 100vh;
}
header {
padding: 40px 24px 8px;
max-width: 1100px;
margin: 0 auto;
}
h1 {
margin: 0 0 6px;
font-size: 24px;
letter-spacing: -0.02em;
}
header p {
margin: 0;
color: var(--muted);
max-width: 62ch;
}
main {
max-width: 1100px;
margin: 0 auto;
padding: 24px;
}
.panel {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 16px;
padding: 20px;
}
.controls {
display: flex;
flex-wrap: wrap;
gap: 12px;
align-items: flex-end;
margin-bottom: 20px;
}
label {
display: flex;
flex-direction: column;
gap: 6px;
font-size: 12px;
color: var(--muted);
}
input[type="text"],
input[type="number"] {
background: #0e1118;
border: 1px solid var(--border);
color: var(--text);
border-radius: 10px;
padding: 9px 12px;
font: inherit;
min-width: 220px;
}
input[type="text"]:focus,
input[type="number"]:focus {
outline: 2px solid var(--accent);
outline-offset: -1px;
}
button {
background: #202633;
color: var(--text);
border: 1px solid var(--border);
border-radius: 10px;
padding: 9px 14px;
font: inherit;
cursor: pointer;
transition: background 0.15s, border-color 0.15s;
}
button:hover {
background: #2a3140;
border-color: #3a4353;
}
button.primary {
background: var(--accent);
border-color: var(--accent);
color: #0b0d12;
font-weight: 600;
}
button.primary:hover {
background: #b6c7ff;
}
.preview {
display: flex;
gap: 24px;
align-items: center;
flex-wrap: wrap;
padding-top: 4px;
}
.preview img.hero {
width: 200px;
height: 200px;
border-radius: 20px;
border: 1px solid var(--border);
}
.sizes {
display: flex;
align-items: flex-end;
gap: 14px;
}
.sizes figure {
margin: 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.sizes img {
border-radius: 8px;
border: 1px solid var(--border);
}
.sizes figcaption {
font-size: 11px;
color: var(--muted);
}
.meta {
margin-left: auto;
font-size: 12px;
color: var(--muted);
display: flex;
flex-direction: column;
gap: 8px;
}
.swatches {
display: flex;
gap: 4px;
}
.swatch {
width: 26px;
height: 26px;
border-radius: 6px;
border: 1px solid var(--border);
}
.gallery-head {
display: flex;
justify-content: space-between;
align-items: baseline;
margin: 34px 0 14px;
}
.gallery-head h2 {
margin: 0;
font-size: 16px;
}
.gallery-head span {
color: var(--muted);
font-size: 12px;
}
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 14px;
}
.card {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 14px;
padding: 10px;
cursor: pointer;
transition: transform 0.15s, border-color 0.15s;
}
.card:hover {
transform: translateY(-2px);
border-color: #3a4353;
}
.card img {
width: 100%;
aspect-ratio: 1;
border-radius: 10px;
display: block;
}
.card code {
display: block;
margin-top: 8px;
font-size: 10px;
color: var(--muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
</head>
<body>
<header>
<h1>avatar gradient playground</h1>
<p>
Every gradient below comes from the same deterministic algorithm the
avatar service uses for placeholder avatars. The same actor string always
produces the same multicolor atmosphere. Edit the actor, roll new ones,
and click a card to inspect it.
</p>
</header>
<main>
<section class="panel">
<div class="controls">
<label>
actor
<input id="actor" type="text" value="did:plc:7bm5yjysthhk3f7dcctt6rbe" />
</label>
<label>
render size
<input id="size" type="number" min="16" max="512" value="200" />
</label>
<button id="randomize">random actor</button>
<button id="add" class="primary">add to gallery</button>
</div>
<div class="preview">
<img id="hero" class="hero" alt="gradient preview" />
<div class="sizes" id="sizes"></div>
<div class="meta">
<div>stops</div>
<div class="swatches" id="swatches"></div>
<div id="params"></div>
</div>
</div>
</section>
<div class="gallery-head">
<h2>gallery</h2>
<span id="count"></span>
</div>
<div class="gallery" id="gallery"></div>
</main>
<script type="module">
import { gradientFor, gradientSvg } from "./src/gradient.js";
const $ = (id) => document.getElementById(id);
const dataUri = (svg) =>
"data:image/svg+xml," + encodeURIComponent(svg);
const AVATAR_SIZES = [26, 34, 42, 52];
const ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
const randomActor = () => {
let id = "";
for (let i = 0; i < 24; i++) {
id += ALPHABET[Math.floor(Math.random() * ALPHABET.length)];
}
return "did:plc:" + id;
};
const renderPreview = () => {
const actor = $("actor").value.trim() || "did:plc:anonymous";
const size = Math.max(16, Math.min(512, Number($("size").value) || 200));
$("hero").src = dataUri(gradientSvg(actor, size));
$("sizes").innerHTML = AVATAR_SIZES.map(
(s) => `
<figure>
<img width="${s}" height="${s}" src="${dataUri(gradientSvg(actor, s))}" alt="${s}px" />
<figcaption>${s}px</figcaption>
</figure>`
).join("");
const g = gradientFor(actor);
$("swatches").innerHTML = g.stops
.map((s) => `<div class="swatch" style="background:${s.color}" title="${s.color}"></div>`)
.join("");
$("params").textContent = `${g.stops.length} stops · ${g.angle}° · glow ${g.glow.opacity}`;
};
const addCard = (actor) => {
const card = document.createElement("div");
card.className = "card";
card.title = actor;
card.innerHTML = `<img alt="${actor}" src="${dataUri(gradientSvg(actor, 160))}" /><code>${actor}</code>`;
card.addEventListener("click", () => {
$("actor").value = actor;
renderPreview();
window.scrollTo({ top: 0, behavior: "smooth" });
});
$("gallery").prepend(card);
$("count").textContent = $("gallery").children.length + " generated";
};
$("actor").addEventListener("input", renderPreview);
$("size").addEventListener("input", renderPreview);
$("randomize").addEventListener("click", () => {
$("actor").value = randomActor();
renderPreview();
});
$("add").addEventListener("click", () => {
addCard($("actor").value.trim() || "did:plc:anonymous");
});
// seed the gallery with a spread of examples
for (let i = 0; i < 24; i++) addCard(randomActor());
renderPreview();
</script>
</body>
</html>