0 reported it broke · the ▶ count is distinct visitors who ran it, counted once each, anonymously
Every sampled pixel drops into one of k groups by how close it sits in CIE Lab, the group centers slide to the middle of whatever they caught, and the loop repeats until nothing moves — those centers are your palette. It runs on a downsampled copy in a few milliseconds, so the k slider feels like a dial rather than a submit button. The half that earns its keep is the working interface card beside it, repainted live: it picks the page color by luminance and calm rather than by loudness, then reports the contrast ratio it actually shipped and nudges the text whenever the picture holds no legible pair.
Drag k from 6 down to 3 and watch the sunset collapse into sea, sky and sand, then push the wipe slider left to redraw the whole picture in only those colors; a photo of your own is read in the tab and never uploaded.
Source — the code you see is the code that just ran 1056 lines 81.8 KB index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Palette Extractor — sloprun.dev</title>
<style>
/* sloprun design tokens — inline this block into every demo (self-contained rule).
Identity: instrument-panel. Machine facts in mono; human words in sans.
Green is EARNED: only for "it ran / it worked" states, never decoration. */
:root {
--bg: #F6F6F3; /* neutral paper, not cream */
--surface: #FFFFFF;
--ink: #1A1D21;
--muted: #5A6068;
--line: #E3E2DC;
--accent: #C05600; /* phosphor amber, darkened for light bg */
--accent-soft: #FFF3E6;
--run: #1A7F37; /* earned green */
--run-soft: #E7F4EA;
--danger: #C93C37;
--radius: 10px;
--font-sans: system-ui, -apple-system, "Segoe UI", sans-serif;
--font-mono: ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, monospace;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg: #14161A; --surface: #1C1F26; --ink: #E8E6E1; --muted: #9AA0A8;
--line: #2A2E36; --accent: #FFB454; --accent-soft: #2A2118;
--run: #3FB950; --run-soft: #16281B; --danger: #F47067;
}
}
:root[data-theme="dark"] {
--bg: #14161A; --surface: #1C1F26; --ink: #E8E6E1; --muted: #9AA0A8;
--line: #2A2E36; --accent: #FFB454; --accent-soft: #2A2118;
--run: #3FB950; --run-soft: #16281B; --danger: #F47067;
}
body { background: var(--bg); color: var(--ink); font-family: var(--font-sans); }
/* ---------- local tokens ---------- */
:root { --field: #FBFBF9; }
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) { --field: #171A20; }
}
:root[data-theme="dark"] { --field: #171A20; }
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0; padding: 18px 16px 26px; line-height: 1.5;
font-size: 15px; overflow-x: hidden;
}
.wrap { max-width: 1140px; margin: 0 auto; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.sr-only {
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* ---------- header ---------- */
header { margin-bottom: 16px; }
.kicker {
font-family: var(--font-mono); font-size: 11px; letter-spacing: .09em;
text-transform: uppercase; color: var(--muted); margin: 0 0 6px;
}
h1 { font-size: clamp(21px, 4.8vw, 28px); line-height: 1.18; margin: 0 0 6px; letter-spacing: -.015em; }
.lede { margin: 0; color: var(--muted); max-width: 66ch; font-size: clamp(13.5px, 3.4vw, 15px); }
/* ---------- layout ---------- */
.grid { display: grid; gap: 14px; grid-template-columns: minmax(0,1fr); align-items: start; }
@media (min-width: 980px) { .grid { grid-template-columns: minmax(0,1.05fr) minmax(0,.95fr); } }
.col { display: grid; gap: 14px; align-content: start; }
.card {
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--radius); padding: 14px;
}
.card > h2 {
font-family: var(--font-mono); font-size: 11px; letter-spacing: .09em;
text-transform: uppercase; color: var(--muted); margin: 0 0 10px; font-weight: 600;
}
.hint { font-size: 12.5px; color: var(--muted); margin: 8px 0 0; }
/* ---------- buttons ---------- */
.btn {
font: inherit; font-size: 13px; font-family: var(--font-sans);
background: var(--surface); color: var(--ink);
border: 1px solid var(--line); border-radius: 8px;
padding: 7px 12px; cursor: pointer; line-height: 1.2;
transition: background .12s ease, border-color .12s ease, transform .08s ease;
}
.btn:hover { border-color: var(--accent); }
.btn:active { transform: translateY(1px); }
.btn.primary { background: var(--accent-soft); border-color: var(--accent); color: var(--ink); font-weight: 600; }
.btn.tiny { font-size: 12px; padding: 5px 9px; font-family: var(--font-mono); }
.btn[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent); font-weight: 600; }
.btn[disabled] { opacity: .45; cursor: not-allowed; }
.btn[disabled]:hover { border-color: var(--line); }
/* fingers need more room than cursors */
@media (pointer: coarse) {
.btn { padding: 10px 14px; }
.btn.tiny { padding: 9px 12px; }
input[type="range"] { padding: 10px 0; }
}
.row { display: flex; flex-wrap: wrap; gap: 7px; align-items: center; }
/* ---------- drop area + image stage ---------- */
.drop { position: relative; border-radius: 8px; }
.drop.over::after {
content: "drop the image"; position: absolute; inset: 0; z-index: 4;
display: grid; place-items: center; border-radius: 8px;
font-family: var(--font-mono); font-size: 12px; letter-spacing: .06em;
color: var(--ink); background: color-mix(in srgb, var(--accent-soft) 88%, transparent);
border: 2px dashed var(--accent); pointer-events: none;
}
.stage {
position: relative; border: 1px solid var(--line); border-radius: 8px;
overflow: hidden; background: var(--field); line-height: 0;
}
.stage { margin: 0 auto; }
.stage canvas { display: block; width: 100%; height: auto; }
kbd {
font-family: var(--font-mono); font-size: 11.5px; padding: 1px 5px;
border: 1px solid var(--line); border-bottom-width: 2px; border-radius: 5px;
background: var(--field); color: var(--ink); white-space: nowrap;
}
#quant { position: absolute; inset: 0; width: 100%; height: 100%; }
.divider {
position: absolute; top: 0; bottom: 0; width: 2px; margin-left: -1px;
background: rgba(255,255,255,.85); box-shadow: 0 0 0 1px rgba(0,0,0,.35);
pointer-events: none; z-index: 3;
}
.stagelabels {
display: flex; justify-content: space-between; gap: 8px; margin-top: 7px;
font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .07em;
text-transform: uppercase; color: var(--muted);
}
/* ---------- controls ---------- */
.ctl { margin-top: 12px; }
.ctl-head {
display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
font-family: var(--font-mono); font-size: 11px; letter-spacing: .07em;
text-transform: uppercase; color: var(--muted); margin-bottom: 5px;
}
.ctl-head b { color: var(--ink); font-size: 13px; letter-spacing: 0; }
input[type="range"] {
-webkit-appearance: none; appearance: none; width: 100%;
background: transparent; margin: 0; padding: 6px 0; cursor: pointer;
}
input[type="range"]::-webkit-slider-runnable-track {
height: 5px; border-radius: 99px; background: var(--line);
}
input[type="range"]::-moz-range-track { height: 5px; border-radius: 99px; background: var(--line); }
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none; appearance: none; width: 17px; height: 17px; margin-top: -6px;
border-radius: 50%; background: var(--accent); border: 2px solid var(--surface);
box-shadow: 0 0 0 1px var(--accent);
}
input[type="range"]::-moz-range-thumb {
width: 15px; height: 15px; border-radius: 50%; background: var(--accent);
border: 2px solid var(--surface); box-shadow: 0 0 0 1px var(--accent);
}
input[type="range"]:focus-visible { outline-offset: 4px; }
/* ---------- readouts ---------- */
.facts {
display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 12px;
padding-top: 11px; border-top: 1px solid var(--line);
font-family: var(--font-mono); font-size: 11.5px; color: var(--muted);
}
.facts b { color: var(--ink); font-weight: 600; }
.badge {
font-family: var(--font-mono); font-size: 11px; padding: 2px 7px;
border-radius: 99px; border: 1px solid var(--line); color: var(--muted);
}
.badge.ok { background: var(--run-soft); border-color: transparent; color: var(--run); font-weight: 600; }
.badge.warn { background: var(--accent-soft); border-color: transparent; color: var(--accent); font-weight: 600; }
/* ---------- palette ---------- */
.sharebar {
display: flex; width: 100%; height: 12px; border-radius: 99px;
overflow: hidden; border: 1px solid var(--line); margin-bottom: 12px;
}
.sharebar span { display: block; height: 100%; }
.swatches {
display: grid; gap: 8px;
grid-template-columns: repeat(auto-fill, minmax(102px, 1fr));
}
.sw {
font: inherit; padding: 0; cursor: pointer; text-align: left;
background: var(--field); border: 1px solid var(--line); border-radius: 9px;
overflow: hidden; display: block; color: var(--ink);
transition: border-color .12s ease, transform .08s ease;
}
.sw:hover { border-color: var(--accent); }
.sw:active { transform: translateY(1px); }
.sw-chip {
display: flex; align-items: flex-end; height: 62px; padding: 7px 8px;
font-family: var(--font-mono); font-size: 12.5px; font-weight: 600; letter-spacing: .02em;
}
.sw-meta {
display: flex; justify-content: space-between; align-items: center; gap: 6px;
padding: 6px 8px; font-family: var(--font-mono); font-size: 11px; color: var(--muted);
border-top: 1px solid var(--line);
}
.sw-act { color: var(--muted); }
.sw.ok .sw-act { color: var(--run); font-weight: 600; }
.sw.ok { border-color: var(--run); }
.copyrow { margin-top: 12px; }
#copyout {
margin-top: 9px; width: 100%; display: none; resize: vertical;
font-family: var(--font-mono); font-size: 11.5px; line-height: 1.5;
background: var(--field); color: var(--ink); border: 1px solid var(--line);
border-radius: 8px; padding: 8px;
}
#copyout.show { display: block; }
.status { font-family: var(--font-mono); font-size: 11.5px; color: var(--muted); margin: 9px 0 0; min-height: 1.2em; }
.status.ok { color: var(--run); }
.status.bad { color: var(--danger); }
/* ---------- generated preview card ---------- */
.pv {
/* defaults so the card is a real card on the very first frame, before the
picture has decoded; the inline style set by JS overrides every one */
--c-bg: var(--field); --c-surface: var(--surface); --c-text: var(--ink);
--c-muted: var(--muted); --c-muted-page: var(--muted); --c-line: var(--line);
--c-accent: var(--accent); --c-accent-text: var(--accent); --c-accent-ink: var(--surface);
background: var(--c-bg); border-radius: 10px; padding: 14px;
border: 1px solid var(--line); color: var(--c-text);
}
.pv-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 11px; }
.pv-brand { display: flex; align-items: center; gap: 8px; font-weight: 650; font-size: 14px; }
.pv-mark { width: 22px; height: 22px; border-radius: 6px; background: var(--c-accent); flex: none; }
.pv-dots { display: flex; gap: 4px; }
.pv-dots i { width: 7px; height: 7px; border-radius: 50%; background: var(--c-muted-page); opacity: .6; }
.pv-card { background: var(--c-surface); border: 1px solid var(--c-line); border-radius: 9px; padding: 12px; }
.pv-label { font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--c-muted); }
.pv-stat { display: flex; align-items: baseline; gap: 8px; margin: 3px 0 10px; }
.pv-num { font-family: var(--font-mono); font-size: 26px; font-weight: 700; letter-spacing: -.02em; }
.pv-delta { font-family: var(--font-mono); font-size: 12px; font-weight: 600; color: var(--c-accent-text); }
.pv-bars { display: flex; align-items: flex-end; gap: 5px; height: 54px; }
.pv-bars i { flex: 1; border-radius: 3px 3px 1px 1px; min-width: 6px; }
.pv-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 11px; }
.pv-tag {
font-family: var(--font-mono); font-size: 10.5px; padding: 3px 8px; border-radius: 99px;
border: 1px solid var(--c-line); color: var(--c-muted);
}
.pv-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.pv-btn {
font-family: var(--font-sans); font-size: 12.5px; padding: 7px 13px; border-radius: 7px;
background: var(--c-accent); color: var(--c-accent-ink); font-weight: 600; border: 1px solid transparent;
}
.pv-btn.ghost { background: transparent; color: var(--c-text); border-color: var(--c-line); font-weight: 500; }
.pv-note { font-size: 12px; color: var(--c-muted-page); margin: 10px 0 0; }
.roles { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 11px; font-family: var(--font-mono); font-size: 11px; color: var(--muted); }
.roles span { display: inline-flex; align-items: center; gap: 5px; }
.roles i { width: 11px; height: 11px; border-radius: 3px; border: 1px solid var(--line); }
/* ---------- footer ---------- */
footer {
margin-top: 20px; padding-top: 12px; border-top: 1px solid var(--line);
font-family: var(--font-mono); font-size: 11px; color: var(--muted);
display: flex; justify-content: space-between; gap: 10px; flex-wrap: wrap;
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { transition: none !important; animation: none !important; scroll-behavior: auto !important; }
}
</style>
</head>
<body>
<div class="wrap">
<header>
<p class="kicker">k-means · cie lab · canvas</p>
<h1>Palette Extractor</h1>
<p class="lede">Drop in any picture and it groups every pixel into a handful of colors — then shows you what a real interface looks like painted with them. Click a swatch to copy its hex.</p>
</header>
<div class="grid">
<!-- ============ left ============ -->
<div class="col">
<section class="card">
<h2>Picture</h2>
<div class="drop" id="drop">
<div class="stage" id="stage">
<canvas id="orig" width="560" height="350" role="img"
aria-label="The source picture"></canvas>
<canvas id="quant" width="560" height="350" role="img"
aria-label="The same picture redrawn using only the extracted palette"></canvas>
<div class="divider" id="divider" style="left:55%"></div>
</div>
<div class="stagelabels"><span>← original</span><span>palette-mapped →</span></div>
</div>
<div class="ctl">
<div class="ctl-head">
<label for="reveal">wipe</label>
<b id="revealOut">45% mapped</b>
</div>
<input type="range" id="reveal" min="0" max="100" step="1" value="55"
aria-label="Wipe between the original picture and the palette-mapped version">
</div>
<div class="row" style="margin-top:12px">
<button class="btn primary" id="pick" type="button">Choose an image…</button>
<button class="btn tiny" type="button" data-sample="sunset" aria-pressed="true">sunset</button>
<button class="btn tiny" type="button" data-sample="citrus" aria-pressed="false">citrus</button>
<button class="btn tiny" type="button" data-sample="deep" aria-pressed="false">deep water</button>
<input type="file" id="file" accept="image/*" hidden>
</div>
<p class="hint">Drag an image anywhere onto this page, or paste one with <kbd>Ctrl/Cmd + V</kbd>. Nothing is uploaded — it never leaves this tab.</p>
<p class="status" id="loadStatus" role="status"></p>
<div class="facts">
<span>source <b id="fSize">—</b></span>
<span>sampled <b id="fPix">—</b></span>
<span>passes <b id="fIter">—</b></span>
<span>time <b id="fMs">—</b></span>
<span id="fConv" class="badge">idle</span>
</div>
</section>
<section class="card">
<h2>How many colors</h2>
<div class="ctl" style="margin-top:0">
<div class="ctl-head">
<label for="k">k — clusters</label>
<b id="kOut">6</b>
</div>
<input type="range" id="k" min="3" max="8" step="1" value="6">
</div>
<p class="hint">Every pixel is dropped into whichever of the <span id="kInline" style="font-family:var(--font-mono)">6</span> groups it sits closest to in Lab color space, the group centers move to the middle of what they caught, repeat until nothing moves. The centers are your palette.</p>
</section>
</div>
<!-- ============ right ============ -->
<div class="col">
<section class="card">
<h2>Palette</h2>
<div class="sharebar" id="sharebar" aria-hidden="true"></div>
<div class="swatches" id="swatches"></div>
<div class="row copyrow">
<button class="btn" type="button" data-copy="hex">Copy hex list</button>
<button class="btn" type="button" data-copy="css">Copy CSS variables</button>
<button class="btn" type="button" data-copy="json">Copy JSON</button>
</div>
<textarea id="copyout" rows="5" readonly aria-label="Palette text — select and copy manually"></textarea>
<p class="status" id="copyStatus" role="status"></p>
</section>
<section class="card">
<h2>Interface preview</h2>
<div class="row" style="margin-bottom:11px">
<button class="btn tiny" type="button" id="modeLight" aria-pressed="true">light card</button>
<button class="btn tiny" type="button" id="modeDark" aria-pressed="false">dark card</button>
</div>
<div class="pv" id="pv">
<div class="pv-top">
<span class="pv-brand"><span class="pv-mark"></span>Fernway</span>
<span class="pv-dots"><i></i><i></i><i></i></span>
</div>
<div class="pv-card">
<span class="pv-label">visits · last 30 days</span>
<div class="pv-stat">
<span class="pv-num">12,486</span>
<span class="pv-delta">+8.4%</span>
</div>
<div class="pv-bars" id="pvBars"></div>
<div class="pv-tags">
<span class="pv-tag">weekly</span><span class="pv-tag">all pages</span><span class="pv-tag">live</span>
</div>
<div class="pv-actions">
<span class="pv-btn">Save view</span>
<span class="pv-btn ghost">Share</span>
</div>
</div>
<p class="pv-note">Every color on this card came out of the picture — the page is a tint of its calmest one.</p>
</div>
<div class="roles" id="roles"></div>
<p class="status" id="pvStatus"></p>
</section>
</div>
</div>
<footer>
<span>demo · sloprun.dev</span>
<span id="engine">k-means++ · cie-lab · canvas</span>
</footer>
</div>
<p class="sr-only" id="live" role="status" aria-live="polite"></p>
<script>
(function () {
"use strict";
/* ---------- platform theme bridge ---------- */
addEventListener("message", function (e) {
var d = e && e.data;
if (d && d.type === "sloprun:theme" && (d.theme === "light" || d.theme === "dark")) {
document.documentElement.setAttribute("data-theme", d.theme);
}
});
var $ = function (id) { return document.getElementById(id); };
/* ---------- color math ---------- */
var LIN = new Float32Array(256);
for (var i = 0; i < 256; i++) {
var v = i / 255;
LIN[i] = v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
}
function fLab(t) { return t > 0.008856451679 ? Math.cbrt(t) : (7.787037037 * t + 0.137931034); }
function toLab(r, g, b, out, o) {
var R = LIN[r], G = LIN[g], B = LIN[b];
var X = (0.4124564 * R + 0.3575761 * G + 0.1804375 * B) / 0.95047;
var Y = (0.2126729 * R + 0.7151522 * G + 0.0721750 * B);
var Z = (0.0193339 * R + 0.1191920 * G + 0.9503041 * B) / 1.08883;
var fx = fLab(X), fy = fLab(Y), fz = fLab(Z);
out[o] = 116 * fy - 16; out[o + 1] = 500 * (fx - fy); out[o + 2] = 200 * (fy - fz);
}
function chan(v) { v = v < 0 ? 0 : (v > 255 ? 255 : v); return LIN[v | 0]; }
function lum(r, g, b) { return 0.2126 * chan(r) + 0.7152 * chan(g) + 0.0722 * chan(b); }
/* black or white — whichever reads better on this color */
function ink(c) { return contrast(c, [255, 255, 255]) >= contrast(c, [17, 17, 17]) ? "#FFFFFF" : "#111111"; }
function contrast(c1, c2) {
var a = lum(c1[0], c1[1], c1[2]), b = lum(c2[0], c2[1], c2[2]);
if (a < b) { var t = a; a = b; b = t; }
return (a + 0.05) / (b + 0.05);
}
function hex2(n) { n = Math.max(0, Math.min(255, Math.round(n))); return (n < 16 ? "0" : "") + n.toString(16); }
function toHex(c) { return "#" + hex2(c[0]) + hex2(c[1]) + hex2(c[2]); }
function css(c) { return "rgb(" + Math.round(c[0]) + "," + Math.round(c[1]) + "," + Math.round(c[2]) + ")"; }
function mix(a, b, t) {
return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];
}
function chroma(c) {
var l = [0, 0, 0]; toLab(c[0] | 0, c[1] | 0, c[2] | 0, l, 0);
return Math.sqrt(l[1] * l[1] + l[2] * l[2]);
}
/* a "light card" has to actually be light: lift or deepen the page color
just far enough, keeping its hue */
function anchor(c, mode) {
var goal = mode === "light" ? [255, 255, 255] : [13, 15, 19];
var want = mode === "light" ? 0.45 : 0.09;
for (var t = 0; t <= 1.001; t += 0.05) {
var out = mix(c, goal, t);
var L = lum(out[0], out[1], out[2]);
if (mode === "light" ? L >= want : L <= want) return out;
}
return goal;
}
/* push a color toward black or white until it clears a contrast ratio */
function legible(fg, bg, want) {
if (contrast(fg, bg) >= want) return fg.slice();
var dark = lum(bg[0] | 0, bg[1] | 0, bg[2] | 0) > 0.35;
var goal = dark ? [0, 0, 0] : [255, 255, 255];
var out = fg.slice();
for (var s = 0.08; s <= 1.001; s += 0.08) {
out = mix(fg, goal, s);
if (contrast(out, bg) >= want) return out;
}
return goal;
}
/* ---------- seeded rng (deterministic runs = stable demo) ---------- */
function rng(seed) {
return function () {
seed |= 0; seed = (seed + 0x6D2B79F5) | 0;
var t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
/* ---------- k-means (k-means++ seeding, Lab distance) ---------- */
function kmeans(lab, rgb, n, k) {
var rand = rng(0x5EED17), cent = new Float32Array(k * 3), i, j, c, d, best, bi;
var first = (Math.floor(rand() * n)) * 3;
cent[0] = lab[first]; cent[1] = lab[first + 1]; cent[2] = lab[first + 2];
var near = new Float32Array(n);
for (i = 0; i < n; i++) near[i] = Infinity;
for (c = 1; c < k; c++) {
var total = 0;
for (i = 0; i < n; i++) {
var dl = lab[i * 3] - cent[(c - 1) * 3],
da = lab[i * 3 + 1] - cent[(c - 1) * 3 + 1],
db = lab[i * 3 + 2] - cent[(c - 1) * 3 + 2];
d = dl * dl + da * da + db * db;
if (d < near[i]) near[i] = d;
total += near[i];
}
var mark = rand() * total, acc = 0, pick = n - 1;
for (i = 0; i < n; i++) { acc += near[i]; if (acc >= mark) { pick = i; break; } }
cent[c * 3] = lab[pick * 3]; cent[c * 3 + 1] = lab[pick * 3 + 1]; cent[c * 3 + 2] = lab[pick * 3 + 2];
}
var owner = new Uint8Array(n), sumL = new Float64Array(k), sumA = new Float64Array(k),
sumB = new Float64Array(k), sR = new Float64Array(k), sG = new Float64Array(k),
sBl = new Float64Array(k), cnt = new Float64Array(k);
var passes = 0, converged = false, MAX = 40;
for (var it = 0; it < MAX; it++) {
passes++;
sumL.fill(0); sumA.fill(0); sumB.fill(0); sR.fill(0); sG.fill(0); sBl.fill(0); cnt.fill(0);
var worst = -1, worstAt = 0;
for (i = 0; i < n; i++) {
var L = lab[i * 3], A = lab[i * 3 + 1], B2 = lab[i * 3 + 2];
best = Infinity; bi = 0;
for (j = 0; j < k; j++) {
var x = L - cent[j * 3], y = A - cent[j * 3 + 1], z = B2 - cent[j * 3 + 2];
d = x * x + y * y + z * z;
if (d < best) { best = d; bi = j; }
}
owner[i] = bi;
if (best > worst) { worst = best; worstAt = i; }
cnt[bi]++; sumL[bi] += L; sumA[bi] += A; sumB[bi] += B2;
sR[bi] += rgb[i * 3]; sG[bi] += rgb[i * 3 + 1]; sBl[bi] += rgb[i * 3 + 2];
}
var shift = 0;
for (j = 0; j < k; j++) {
if (cnt[j] === 0) {
/* revive an empty cluster on the loneliest pixel, and count only the
distance it actually travelled — a picture that genuinely holds
fewer than k colours revives onto a pixel it is already sitting on,
and must be allowed to settle instead of grinding out all 40 passes */
var rl = lab[worstAt * 3], ra = lab[worstAt * 3 + 1], rb = lab[worstAt * 3 + 2];
var rmv = Math.abs(rl - cent[j * 3]) + Math.abs(ra - cent[j * 3 + 1]) + Math.abs(rb - cent[j * 3 + 2]);
cent[j * 3] = rl; cent[j * 3 + 1] = ra; cent[j * 3 + 2] = rb;
if (rmv > shift) shift = rmv;
continue;
}
var nl = sumL[j] / cnt[j], na = sumA[j] / cnt[j], nb = sumB[j] / cnt[j];
var mv = Math.abs(nl - cent[j * 3]) + Math.abs(na - cent[j * 3 + 1]) + Math.abs(nb - cent[j * 3 + 2]);
if (mv > shift) shift = mv;
cent[j * 3] = nl; cent[j * 3 + 1] = na; cent[j * 3 + 2] = nb;
}
if (shift < 0.02) { converged = true; break; }
}
var out = [];
for (j = 0; j < k; j++) {
if (!cnt[j]) continue;
out.push({
rgb: [Math.round(sR[j] / cnt[j]), Math.round(sG[j] / cnt[j]), Math.round(sBl[j] / cnt[j])],
lab: [cent[j * 3], cent[j * 3 + 1], cent[j * 3 + 2]],
count: cnt[j], share: cnt[j] / n
});
}
out.sort(function (a, b) { return b.count - a.count; });
return { palette: out, passes: passes, converged: converged };
}
/* ---------- canvases ---------- */
var origC = $("orig"), quantC = $("quant");
var origX = origC.getContext("2d"), quantX = quantC.getContext("2d", { willReadFrequently: false });
var work = document.createElement("canvas"), workX = work.getContext("2d", { willReadFrequently: true });
var state = {
sampleLab: null, sampleRgb: null, sampleN: 0,
fullLab: null, fullData: null, w: 0, h: 0,
palette: [], mode: "light", srcLabel: ""
};
var LONG = 560, SAMPLE = 132;
/* every load request takes a ticket. A big photo that decodes slowly must not
land on screen after a newer picture was already asked for — otherwise the
last thing you dropped is not the thing you end up looking at. */
var reqId = 0;
function setSource(img, label) {
var iw = img.naturalWidth || img.width, ih = img.naturalHeight || img.height;
if (!iw || !ih) { fail("That file did not decode as an image."); return; }
/* --- small buffer that the clustering actually chews on.
Read it FIRST: if there is nothing to cluster we bail before
touching the display, so the last good picture stays on screen. --- */
var s = Math.min(1, SAMPLE / Math.max(iw, ih)); /* never cluster invented pixels */
var sw = Math.max(1, Math.round(iw * s)), sh = Math.max(1, Math.round(ih * s));
work.width = sw; work.height = sh;
workX.imageSmoothingEnabled = true; workX.imageSmoothingQuality = "high";
workX.clearRect(0, 0, sw, sh);
workX.drawImage(img, 0, 0, sw, sh);
var sd = workX.getImageData(0, 0, sw, sh).data;
var p, n = sw * sh, lab = new Float32Array(n * 3), rgb = new Uint8Array(n * 3), keep = 0;
for (p = 0; p < n; p++) {
if (sd[p * 4 + 3] < 8) continue; /* skip see-through pixels */
rgb[keep * 3] = sd[p * 4]; rgb[keep * 3 + 1] = sd[p * 4 + 1]; rgb[keep * 3 + 2] = sd[p * 4 + 2];
toLab(sd[p * 4], sd[p * 4 + 1], sd[p * 4 + 2], lab, keep * 3);
keep++;
}
if (!keep) { fail("That image is fully transparent — nothing to read."); return; }
state.sampleLab = lab; state.sampleRgb = rgb; state.sampleN = keep;
state.srcLabel = iw + " × " + ih + " px";
/* --- display + full-res mapping buffer --- */
var scale = LONG / Math.max(iw, ih);
var w = Math.max(1, Math.round(iw * scale)), h = Math.max(1, Math.round(ih * scale));
origC.width = quantC.width = w; origC.height = quantC.height = h;
origX.imageSmoothingEnabled = true; origX.imageSmoothingQuality = "high";
origX.clearRect(0, 0, w, h);
origX.drawImage(img, 0, 0, w, h);
state.w = w; state.h = h;
/* a portrait picture must not become a mile-high column */
$("stage").style.maxWidth = (h > w) ? Math.round(430 * w / h) + "px" : "";
var full = origX.getImageData(0, 0, w, h);
state.fullData = full;
var np = w * h, flab = new Float32Array(np * 3);
for (p = 0; p < np; p++) toLab(full.data[p * 4], full.data[p * 4 + 1], full.data[p * 4 + 2], flab, p * 3);
state.fullLab = flab;
$("loadStatus").className = "status";
$("loadStatus").textContent = label ? "loaded · " + label : "";
run();
return true;
}
function fail(msg) {
var el = $("loadStatus");
el.className = "status bad";
el.textContent = msg;
}
/* ---------- the run ---------- */
function run() {
if (!state.sampleN) return;
var k = +$("k").value;
var t0 = performance.now();
var res = kmeans(state.sampleLab, state.sampleRgb, state.sampleN, k);
var ms = performance.now() - t0;
state.palette = res.palette;
/* a picture that holds fewer than k distinct colours gives back fewer
centers — say so, or the slider looks broken above that ceiling */
var found = res.palette.length;
$("kOut").textContent = found < k ? k + " · " + found + " found" : String(k);
$("k").setAttribute("aria-valuetext",
found < k ? k + ", only " + found + (found === 1 ? " color" : " colors") + " found" : k + " colors");
$("fSize").textContent = state.srcLabel;
$("fPix").textContent = state.sampleN.toLocaleString("en-US") + " px";
$("fIter").textContent = res.passes;
$("fMs").textContent = ms < 0.1 ? "<0.1 ms" : (ms < 10 ? ms.toFixed(1) : Math.round(ms)) + " ms";
var conv = $("fConv");
conv.className = "badge " + (res.converged ? "ok" : "warn");
conv.textContent = res.converged ? "settled" : "stopped at 40";
paintQuantized();
renderPalette();
renderPreview();
}
/* map every displayed pixel to its nearest palette color */
function paintQuantized() {
var pal = state.palette, k = pal.length, w = state.w, h = state.h;
if (!k || !state.fullData) return;
var src = state.fullData.data;
var out = quantX.createImageData(w, h), o = out.data, lab = state.fullLab;
var cl = new Float32Array(k * 3), cr = new Uint8Array(k * 3), j;
for (j = 0; j < k; j++) {
cl[j * 3] = pal[j].lab[0]; cl[j * 3 + 1] = pal[j].lab[1]; cl[j * 3 + 2] = pal[j].lab[2];
cr[j * 3] = pal[j].rgb[0]; cr[j * 3 + 1] = pal[j].rgb[1]; cr[j * 3 + 2] = pal[j].rgb[2];
}
var n = w * h;
for (var p = 0; p < n; p++) {
var L = lab[p * 3], A = lab[p * 3 + 1], B = lab[p * 3 + 2];
var best = Infinity, bi = 0;
for (j = 0; j < k; j++) {
var x = L - cl[j * 3], y = A - cl[j * 3 + 1], z = B - cl[j * 3 + 2];
var d = x * x + y * y + z * z;
if (d < best) { best = d; bi = j; }
}
o[p * 4] = cr[bi * 3]; o[p * 4 + 1] = cr[bi * 3 + 1]; o[p * 4 + 2] = cr[bi * 3 + 2];
o[p * 4 + 3] = src[p * 4 + 3];
}
quantX.putImageData(out, 0, 0);
applyReveal();
}
function applyReveal() {
var v = +$("reveal").value;
quantC.style.clipPath = "inset(0 0 0 " + v + "%)";
$("divider").style.left = v + "%";
$("divider").style.display = (v <= 0 || v >= 100) ? "none" : "block";
$("revealOut").textContent = (100 - v) + "% mapped";
}
/* ---------- palette UI ---------- */
function renderPalette() {
var pal = state.palette;
var bar = $("sharebar"); bar.innerHTML = "";
var box = $("swatches"); box.innerHTML = "";
pal.forEach(function (c) {
var seg = document.createElement("span");
seg.style.width = (c.share * 100).toFixed(3) + "%";
seg.style.background = css(c.rgb);
bar.appendChild(seg);
var hex = toHex(c.rgb).toUpperCase();
var pct = (c.share * 100).toFixed(1) + "%";
var chipInk = ink(c.rgb);
var b = document.createElement("button");
b.type = "button"; b.className = "sw"; b.setAttribute("data-hex", hex);
b.setAttribute("aria-label", "Copy " + hex + " — " + pct + " of the picture");
b.innerHTML =
'<span class="sw-chip" style="background:' + css(c.rgb) + ';color:' + chipInk + '">' + hex + '</span>' +
'<span class="sw-meta"><span>' + pct + '</span><span class="sw-act">copy</span></span>';
b.addEventListener("click", function () {
copyText(hex).then(function (ok) {
if (!ok) { showManual(hex); return; }
b.classList.add("ok");
b.querySelector(".sw-act").textContent = "copied";
say(hex + " copied");
setTimeout(function () {
b.classList.remove("ok");
var a = b.querySelector(".sw-act"); if (a) a.textContent = "copy";
}, 1400);
});
});
box.appendChild(b);
});
}
/* ---------- generated interface card ---------- */
function renderPreview() {
var pal = state.palette.map(function (c) { return c.rgb; });
if (!pal.length) return;
if (pal.length === 1) {
/* one flat colour is still an answer: pair it with the tone that reads
on top of it, so the card repaints instead of leaving the previous
picture's colours sitting there lying about what was just loaded */
pal = [pal[0], ink(pal[0]) === "#FFFFFF" ? [250, 250, 250] : [18, 18, 18]];
}
var mode = state.mode;
/* the page color wants to be bright (or dark) AND calm — a screaming
hue behind a whole interface is where palette tools usually go wrong */
var scored = pal.slice().sort(function (a, b) {
var sa = (mode === "light" ? lum(a[0], a[1], a[2]) : 1 - lum(a[0], a[1], a[2])) - chroma(a) / 190;
var sb = (mode === "light" ? lum(b[0], b[1], b[2]) : 1 - lum(b[0], b[1], b[2])) - chroma(b) / 190;
return sb - sa;
});
var base = scored[0];
var bg = anchor(base, mode);
var rest = pal.filter(function (c) { return c !== base; });
var textRaw = rest.slice().sort(function (a, b) { return contrast(b, bg) - contrast(a, bg); })[0] || base;
var rawRatio = contrast(textRaw, bg);
/* accent: the most colorful palette entry that already stands up on the
page — only if none qualifies do we bend one */
var accentPool = rest.filter(function (c) { return c !== textRaw; });
if (!accentPool.length) accentPool = rest.slice();
var strong = accentPool.filter(function (c) { return contrast(c, bg) >= 3; });
var accentRaw = (strong.length ? strong : accentPool).slice().sort(function (a, b) {
return chroma(b) - chroma(a);
})[0] || textRaw;
var accent = legible(accentRaw, bg, 3);
var accentInk = ink(accent);
/* cards lift off the page: a tint of the page color, not a new hue */
var surface = mix(bg, [255, 255, 255], mode === "light" ? 0.55 : 0.075);
var text = legible(legible(textRaw, bg, 4.5), surface, 4.5);
var line = mix(surface, text, 0.17);
/* muted text is still text: fade it toward its own background for hierarchy,
then hold it at AA — a palette tool that ships 3:1 labels is arguing
against itself */
var muted = legible(mix(text, surface, 0.42), surface, 4.5);
var mutedPage = legible(mix(text, bg, 0.32), bg, 4.5); /* the note sits on the page, not the card */
/* the accent is picked to hold the page; the small +8.4% figure sits on the
card instead, so it gets its own text-safe version */
var accentText = legible(accent, surface, 4.5);
var pv = $("pv");
pv.style.setProperty("--c-bg", css(bg));
pv.style.setProperty("--c-surface", css(surface));
pv.style.setProperty("--c-text", css(text));
pv.style.setProperty("--c-muted", css(muted));
pv.style.setProperty("--c-muted-page", css(mutedPage));
pv.style.setProperty("--c-line", css(line));
pv.style.setProperty("--c-accent", css(accent));
pv.style.setProperty("--c-accent-text", css(accentText));
pv.style.setProperty("--c-accent-ink", accentInk);
/* bars cycle only through palette colors that read on the card */
var barPool = pal.filter(function (c) { return c !== base && contrast(c, surface) >= 1.45; });
if (barPool.length < 2) barPool = pal.filter(function (c) { return c !== base; });
if (barPool.length < 2) barPool = [accent, textRaw];
var bars = $("pvBars"); bars.innerHTML = "";
var heights = [42, 64, 51, 88, 70, 100, 76];
for (var i = 0; i < heights.length; i++) {
var b = document.createElement("i");
b.style.height = heights[i] + "%";
b.style.background = css(barPool[i % barPool.length]);
bars.appendChild(b);
}
var roles = $("roles"); roles.innerHTML = "";
[["page", bg], ["text", text], ["accent", accent], ["lines", line]].forEach(function (r) {
var s = document.createElement("span");
s.innerHTML = '<i style="background:' + css(r[1]) + '"></i>' + r[0] + " " + toHex(r[1]).toUpperCase();
roles.appendChild(s);
});
var st = $("pvStatus");
if (rawRatio >= 4.5) {
st.className = "status ok";
/* quote the pair actually painted, not the raw one — the text may still
have been nudged a little to also clear the card it sits on */
st.textContent = "text on page " + contrast(text, bg).toFixed(2) + ":1 — a legible pair straight from the picture";
} else {
st.className = "status";
st.textContent = "raw pair was only " + rawRatio.toFixed(2) + ":1 — text nudged to "
+ contrast(text, bg).toFixed(2) + ":1 so it stays readable";
}
}
/* ---------- copy ---------- */
function copyText(t) {
return new Promise(function (resolve) {
var done = function (ok) { resolve(!!ok); };
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(t).then(function () { done(true); }, function () { done(legacyCopy(t)); });
return;
}
} catch (e) { /* fall through */ }
done(legacyCopy(t));
});
}
function legacyCopy(t) {
try {
var ta = document.createElement("textarea");
ta.value = t; ta.setAttribute("readonly", "");
ta.style.cssText = "position:fixed;top:-2000px;left:0;opacity:0";
document.body.appendChild(ta);
ta.select(); ta.setSelectionRange(0, t.length);
var ok = document.execCommand("copy");
document.body.removeChild(ta);
return ok;
} catch (e) { return false; }
}
function showManual(t) {
var out = $("copyout");
out.value = t; out.classList.add("show");
out.focus(); out.select();
var st = $("copyStatus");
st.className = "status"; st.textContent = "clipboard is blocked here — it is selected, press Ctrl/Cmd + C";
}
function say(msg) { $("live").textContent = msg; }
function paletteText(kind) {
var pal = state.palette;
if (kind === "hex") {
return pal.map(function (c) { return toHex(c.rgb).toUpperCase(); }).join("\n");
}
if (kind === "css") {
return ":root {\n" + pal.map(function (c, i) {
return " --color-" + (i + 1) + ": " + toHex(c.rgb).toUpperCase() + "; /* " + (c.share * 100).toFixed(1) + "% */";
}).join("\n") + "\n}";
}
return JSON.stringify(pal.map(function (c) {
return { hex: toHex(c.rgb).toUpperCase(), rgb: c.rgb, share: +(c.share).toFixed(4) };
}), null, 2);
}
Array.prototype.forEach.call(document.querySelectorAll("[data-copy]"), function (btn) {
btn.addEventListener("click", function () {
var kind = btn.getAttribute("data-copy");
if (!state.palette.length) {
var s0 = $("copyStatus");
s0.className = "status"; s0.textContent = "no palette yet — load a picture first";
return;
}
var txt = paletteText(kind);
copyText(txt).then(function (ok) {
var st = $("copyStatus");
if (ok) {
st.className = "status ok";
st.textContent = "copied · " + state.palette.length + " colors as " + kind;
$("copyout").classList.remove("show");
setTimeout(function () { if (st.textContent.indexOf("copied ·") === 0) { st.textContent = ""; st.className = "status"; } }, 2600);
} else {
showManual(txt);
}
});
});
});
/* ---------- controls ---------- */
$("k").addEventListener("input", function () {
$("kOut").textContent = this.value;
$("kInline").textContent = this.value;
run();
});
$("reveal").addEventListener("input", applyReveal);
$("pick").addEventListener("click", function () { $("file").click(); });
$("file").addEventListener("change", function () {
if (this.files && this.files[0]) loadFile(this.files[0]);
this.value = "";
});
function loadFile(f) {
if (!f.type || f.type.indexOf("image/") !== 0) { fail("That is not an image file."); return; }
var my = ++reqId;
var url = URL.createObjectURL(f);
var img = new Image();
img.onload = function () {
/* only drop the sample highlight if this picture actually loaded, and only
if it is still the picture that was last asked for */
if (my === reqId && setSource(img, f.name.length > 26 ? f.name.slice(0, 24) + "…" : f.name)) markSample(null);
URL.revokeObjectURL(url);
};
img.onerror = function () {
URL.revokeObjectURL(url);
if (my === reqId) fail("Could not read that image.");
};
img.src = url;
}
/* drag + drop — the WHOLE page takes the drop, not just the picture panel.
A drop that misses an element which called preventDefault makes the browser
navigate away and open the raw image, throwing the demo away; swallowing it
at window level is the only way to stop that. */
var drop = $("drop");
function dragOn() { drop.classList.add("over"); }
function dragOff() { drop.classList.remove("over"); }
["dragenter", "dragover"].forEach(function (ev) {
addEventListener(ev, function (e) { e.preventDefault(); dragOn(); });
});
/* a relatedTarget means the pointer only crossed onto another element;
leaving the window itself reports none */
addEventListener("dragleave", function (e) { if (!e.relatedTarget) dragOff(); });
addEventListener("dragend", dragOff);
addEventListener("drop", function (e) {
e.preventDefault(); dragOff();
var dt = e.dataTransfer;
if (dt && dt.files && dt.files[0]) loadFile(dt.files[0]);
});
/* paste */
addEventListener("paste", function (e) {
var items = e.clipboardData && e.clipboardData.items;
if (!items) return;
for (var i = 0; i < items.length; i++) {
if (items[i].type && items[i].type.indexOf("image/") === 0) {
var f = items[i].getAsFile();
if (f) { e.preventDefault(); loadFile(f); return; }
}
}
});
/* preview light / dark */
function setMode(m) {
state.mode = m;
$("modeLight").setAttribute("aria-pressed", String(m === "light"));
$("modeDark").setAttribute("aria-pressed", String(m === "dark"));
renderPreview();
}
$("modeLight").addEventListener("click", function () { setMode("light"); });
$("modeDark").addEventListener("click", function () { setMode("dark"); });
/* ---------- built-in pictures ---------- */
var SAMPLE_SRC = "data:image/png;base64,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";
function blob(g, w, h, x, y, r, c, a) {
var gx = g.createRadialGradient(x * w, y * h, 0, x * w, y * h, r * w);
gx.addColorStop(0, "rgba(" + c + "," + a + ")");
gx.addColorStop(0.55, "rgba(" + c + "," + (a * 0.92) + ")");
gx.addColorStop(1, "rgba(" + c + ",0)");
g.fillStyle = gx;
g.fillRect(0, 0, w, h);
}
function makeCitrus() {
var c = document.createElement("canvas"); c.width = 320; c.height = 200;
var g = c.getContext("2d"), w = 320, h = 200;
g.fillStyle = "#FFF3DC"; g.fillRect(0, 0, w, h);
blob(g, w, h, 0.22, 0.32, 0.30, "255,122,47", 1);
blob(g, w, h, 0.63, 0.24, 0.26, "255,197,48", 1);
blob(g, w, h, 0.80, 0.72, 0.28, "232,70,124", 1);
blob(g, w, h, 0.33, 0.79, 0.24, "111,207,91", 1);
blob(g, w, h, 0.52, 0.53, 0.17, "20,168,160", 1);
return c;
}
function makeDeep() {
var c = document.createElement("canvas"); c.width = 320; c.height = 200;
var g = c.getContext("2d"), w = 320, h = 200;
var lg = g.createLinearGradient(0, 0, 0, h);
lg.addColorStop(0, "#0A1B34"); lg.addColorStop(1, "#10425C");
g.fillStyle = lg; g.fillRect(0, 0, w, h);
blob(g, w, h, 0.28, 0.68, 0.33, "27,179,166", 0.95);
blob(g, w, h, 0.72, 0.40, 0.24, "108,79,216", 0.9);
blob(g, w, h, 0.55, 0.78, 0.16, "69,224,201", 0.95);
blob(g, w, h, 0.84, 0.16, 0.11, "242,166,90", 1);
return c;
}
function markSample(name) {
Array.prototype.forEach.call(document.querySelectorAll("[data-sample]"), function (b) {
b.setAttribute("aria-pressed", String(b.getAttribute("data-sample") === name));
});
}
Array.prototype.forEach.call(document.querySelectorAll("[data-sample]"), function (b) {
b.addEventListener("click", function () {
var kind = b.getAttribute("data-sample");
reqId++; /* cancel any picture still decoding */
markSample(kind);
if (kind === "citrus") setSource(makeCitrus(), "citrus");
else if (kind === "deep") setSource(makeDeep(), "deep water");
else bootSample();
});
});
/* ---------- boot ---------- */
var sampleImg = new Image();
function fallbackSample() {
/* the embedded picture is gone; the chip that loads it would do nothing at
all from here on, so take it out of service instead of leaving a dead button */
var chip = document.querySelector('[data-sample="sunset"]');
if (chip) { chip.disabled = true; chip.title = "the built-in picture did not decode"; }
markSample("citrus"); setSource(makeCitrus(), "citrus");
}
function bootSample() {
var my = ++reqId;
if (sampleImg.complete && sampleImg.naturalWidth) { markSample("sunset"); setSource(sampleImg, "sunset"); return; }
/* it decoded once and came back empty — do not sit waiting on a load event
that will never fire again, or the sunset chip becomes a dead button */
if (sampleImg.complete && sampleImg.src) { fallbackSample(); return; }
sampleImg.onload = function () {
if (my !== reqId) return; /* someone dropped their own picture first */
markSample("sunset"); setSource(sampleImg, "sunset");
};
sampleImg.onerror = function () { if (my === reqId) fallbackSample(); };
if (!sampleImg.src) sampleImg.src = SAMPLE_SRC;
}
applyReveal();
bootSample();
})();
</script>
</body>
</html>
source-visible by construction · nothing is published here without its code
Comments
0 totalNo comments yet. If you ran it, say what happened.