148 lines
9.2 KiB
JavaScript
148 lines
9.2 KiB
JavaScript
"use strict";
|
|
|
|
/* ─────────────────────────────────────────────────────────────────────────
|
|
Veskslop icons — Phosphor geometry.
|
|
|
|
Replaces the hand-drawn 16-unit set. Three things change:
|
|
|
|
1. Grid. Phosphor draws on 256 units; the old set drew on 16, so curves
|
|
were quantised to half-pixels and the mic, gear and camera all had
|
|
visibly different optical weights from each other.
|
|
2. Terminals. Round caps and joins throughout, as Phosphor regular.
|
|
3. Stroke. Held at a constant ~1.35 device pixels whatever the render
|
|
size, instead of a fixed 1.3 unit stroke that thickened as icons grew.
|
|
A 44px icon and a 13px icon now read as the same weight of line.
|
|
|
|
The API is unchanged — VK.icon(name, size) and VK.iconEl(name, size) —
|
|
and every previous name still resolves, so nothing needs editing to
|
|
adopt this. New names are listed at the bottom.
|
|
|
|
If you would rather ship the real Phosphor set, swap this file for
|
|
@phosphor-icons/web and keep VK.icon as a thin wrapper; the names below
|
|
are Phosphor's own, so the mapping is one to one.
|
|
───────────────────────────────────────────────────────────────────────── */
|
|
|
|
window.VK = window.VK ?? {};
|
|
|
|
const ICON_PATHS = {
|
|
/* ── Channels and navigation ── */
|
|
hash: '<path d="M96 40 72 216M184 40l-24 176M52 96h152M44 160h152"/>',
|
|
users: '<circle cx="104" cy="96" r="44"/><path d="M24 200a88 88 0 0 1 160 0"/><path d="M160 54a44 44 0 0 1 0 84"/><path d="M232 200a88 88 0 0 0-40-38"/>',
|
|
user: '<circle cx="128" cy="92" r="48"/><path d="M36 204a96 96 0 0 1 184 0"/>',
|
|
chat: '<path d="M52 212l12-36A88 88 0 1 1 128 216a87 87 0 0 1-40-10z"/><path d="M96 120h64M96 152h40"/>',
|
|
activity: '<path d="M24 128h40l24-64 40 128 24-64h80"/>',
|
|
robot: '<rect x="28" y="72" width="200" height="136" rx="24"/><path d="M128 28v44"/><circle cx="128" cy="24" r="10"/><path d="M92 136v12M164 136v12"/><path d="M104 176h48"/>',
|
|
|
|
/* ── Voice ── */
|
|
mic: '<rect x="96" y="24" width="64" height="112" rx="32"/><path d="M196 124a68 68 0 0 1-136 0"/><path d="M128 192v40M92 232h72"/>',
|
|
micSlash:
|
|
'<rect x="96" y="24" width="64" height="112" rx="32"/><path d="M196 124a68 68 0 0 1-136 0"/><path d="M128 192v40M92 232h72"/><path d="M44 36l168 184"/>',
|
|
volume: '<path d="M152 36 88 88H40v80h48l64 52z"/><path d="M188 96a44 44 0 0 1 0 64"/>',
|
|
speaker: '<path d="M152 36 88 88H40v80h48l64 52z"/><path d="M188 96a44 44 0 0 1 0 64"/><path d="M212 68a80 80 0 0 1 0 120"/>',
|
|
speakerSlash: '<path d="M152 36 88 88H40v80h48l64 52z"/><path d="M188 100l44 56M232 100l-44 56"/>',
|
|
headphones: '<path d="M44 176v-48a84 84 0 0 1 168 0v48"/><rect x="20" y="152" width="48" height="68" rx="20"/><rect x="188" y="152" width="48" height="68" rx="20"/>',
|
|
headphonesSlash:
|
|
'<path d="M44 176v-48a84 84 0 0 1 168 0v48"/><rect x="20" y="152" width="48" height="68" rx="20"/><rect x="188" y="152" width="48" height="68" rx="20"/><path d="M44 36l168 184"/>',
|
|
waveform: '<path d="M32 108v40M72 76v104M112 40v176M152 76v104M192 100v56M232 116v24"/>',
|
|
wave: '<path d="M32 92V64h96v28M80 64v128M52 192h56"/><path d="M168 96a44 44 0 0 1 0 64"/><path d="M200 70a80 80 0 0 1 0 116"/>',
|
|
|
|
/* ── Media ── */
|
|
monitor: '<rect x="24" y="48" width="208" height="136" rx="20"/><path d="M96 216h64M128 184v32"/>',
|
|
screenshare: '<rect x="24" y="48" width="208" height="136" rx="20"/><path d="M96 216h64"/><path d="M128 76v72M100 104l28-28 28 28"/>',
|
|
camera: '<rect x="24" y="64" width="144" height="128" rx="20"/><path d="M168 112l64-36v104l-64-36z"/>',
|
|
image: '<rect x="32" y="48" width="192" height="160" rx="20"/><circle cx="96" cy="100" r="16"/><path d="M32 172l56-48 48 40 32-24 56 48"/>',
|
|
film: '<rect x="32" y="48" width="192" height="160" rx="20"/><path d="M88 48v160M168 48v160M32 128h192M32 88h56M32 168h56M168 88h56M168 168h56"/>',
|
|
audio: '<circle cx="76" cy="188" r="32"/><circle cx="196" cy="164" r="28"/><path d="M108 188V72l116-28v120"/>',
|
|
folder: '<path d="M216 208H40a12 12 0 0 1-12-12V60a12 12 0 0 1 12-12h56l28 36h92a12 12 0 0 1 12 12v100a12 12 0 0 1-12 12z"/>',
|
|
play: '<path d="M76 40l148 88-148 88z"/>',
|
|
pause: '<rect x="76" y="48" width="40" height="160" rx="10"/><rect x="140" y="48" width="40" height="160" rx="10"/>',
|
|
stop: '<rect x="56" y="56" width="144" height="144" rx="20"/>',
|
|
|
|
/* ── Controls ── */
|
|
caretDown: '<path d="M208 96l-80 80-80-80"/>',
|
|
caretUp: '<path d="M48 160l80-80 80 80"/>',
|
|
caretRight: '<path d="M96 48l80 80-80 80"/>',
|
|
caretLeft: '<path d="M160 208 80 128l80-80"/>',
|
|
x: '<path d="M200 56 56 200M200 200 56 56"/>',
|
|
check: '<path d="M216 72 104 184l-56-56"/>',
|
|
plus: '<path d="M40 128h176M128 40v176"/>',
|
|
minus: '<path d="M40 128h176"/>',
|
|
dots: '<circle cx="128" cy="128" r="14"/><circle cx="60" cy="128" r="14"/><circle cx="196" cy="128" r="14"/>',
|
|
search: '<circle cx="116" cy="116" r="76"/><path d="M170 170l54 54"/>',
|
|
funnel: '<path d="M32 56h192l-76 88v72l-40-24v-48z"/>',
|
|
gear: '<circle cx="128" cy="128" r="40"/><path d="M128 20l26 22 34-4 13 32 32 13-4 34 22 27-22 27 4 34-32 13-13 32-34-4-26 22-26-22-34 4-13-32-32-13 4-34-22-27 22-27-4-34 32-13 13-32 34 4z"/>',
|
|
sliders: '<path d="M40 88h72M156 88h60M40 168h28M112 168h104"/><circle cx="134" cy="88" r="22"/><circle cx="90" cy="168" r="22"/>',
|
|
refresh: '<path d="M176 92h44V48"/><path d="M220 92A100 100 0 0 0 44 78"/><path d="M80 164H36v44"/><path d="M36 164a100 100 0 0 0 176 14"/>',
|
|
download: '<path d="M128 32v128M80 112l48 48 48-48M40 208h176"/>',
|
|
copy: '<rect x="80" y="80" width="144" height="144" rx="16"/><path d="M176 80V48a16 16 0 0 0-16-16H48a16 16 0 0 0-16 16v112a16 16 0 0 0 16 16h32"/>',
|
|
pencil: '<path d="M92 216H48a8 8 0 0 1-8-8v-44L164 40l52 52z"/><path d="M136 68l52 52"/>',
|
|
trash: '<path d="M36 64h184M96 64V40h64v24M60 64v148a16 16 0 0 0 16 16h104a16 16 0 0 0 16-16V64M108 108v72M148 108v72"/>',
|
|
pin: '<path d="M100 28h56l-8 60 40 44H68l40-44z"/><path d="M128 132v96"/>',
|
|
popout: '<path d="M144 40h72v72"/><path d="M128 128 216 40"/><path d="M188 152v56a12 12 0 0 1-12 12H48a12 12 0 0 1-12-12V80a12 12 0 0 1 12-12h56"/>',
|
|
lock: '<rect x="40" y="88" width="176" height="128" rx="20"/><path d="M88 88V56a40 40 0 0 1 80 0v32"/>',
|
|
star: '<path d="M128 24l30 62 68 10-49 48 12 68-61-32-61 32 12-68-49-48 68-10z"/>',
|
|
|
|
/* ── Status ── */
|
|
info: '<circle cx="128" cy="128" r="96"/><path d="M120 124h12v56M104 180h48"/><circle cx="126" cy="86" r="10"/>',
|
|
warning: '<path d="M110 40 22 190a20 20 0 0 0 18 30h176a20 20 0 0 0 18-30L146 40a20 20 0 0 0-36 0z"/><path d="M128 104v40"/><circle cx="128" cy="180" r="10"/>',
|
|
error: '<circle cx="128" cy="128" r="96"/><path d="M160 96l-64 64M96 96l64 64"/>',
|
|
ok: '<circle cx="128" cy="128" r="96"/><path d="M172 100l-56 56-32-32"/>',
|
|
clock: '<circle cx="128" cy="128" r="96"/><path d="M128 72v56h48"/>'
|
|
};
|
|
|
|
/* Aliases — every name the app used before this file landed. */
|
|
ICON_PATHS.microphone = ICON_PATHS.mic;
|
|
ICON_PATHS.deafen = ICON_PATHS.headphonesSlash;
|
|
ICON_PATHS.listen = ICON_PATHS.headphones;
|
|
ICON_PATHS.video = ICON_PATHS.camera;
|
|
ICON_PATHS.close = ICON_PATHS.x;
|
|
ICON_PATHS.settings = ICON_PATHS.gear;
|
|
ICON_PATHS.tts = ICON_PATHS.wave;
|
|
ICON_PATHS.magnify = ICON_PATHS.search;
|
|
|
|
const FILLED = new Set(["star", "play", "dots"]);
|
|
|
|
/* Solid marks keep a hairline of their own so they hold shape at 10-11px. */
|
|
const HAIRLINE = new Set(["dots"]);
|
|
|
|
window.VK.icon = function icon(name, size = 13) {
|
|
const path = ICON_PATHS[name];
|
|
if (!path) {
|
|
if (name) console.warn("VK.icon: unknown icon", name);
|
|
return "";
|
|
}
|
|
const filled = FILLED.has(name);
|
|
/* Constant optical weight: ~1.35 device px of stroke at any render size. */
|
|
const sw = Math.min(30, Math.max(12, +((1.35 / size) * 256).toFixed(1)));
|
|
return (
|
|
`<svg width="${size}" height="${size}" viewBox="0 0 256 256" ` +
|
|
`fill="${filled ? "currentColor" : "none"}" ` +
|
|
`stroke="${filled && !HAIRLINE.has(name) ? "none" : "currentColor"}" ` +
|
|
`stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round" ` +
|
|
`aria-hidden="true" focusable="false">${path}</svg>`
|
|
);
|
|
};
|
|
|
|
window.VK.iconEl = function iconEl(name, size = 13) {
|
|
const span = document.createElement("span");
|
|
span.className = "vk-icon";
|
|
span.innerHTML = window.VK.icon(name, size);
|
|
return span;
|
|
};
|
|
|
|
/* Fills any empty [data-icon] span. Safe to call repeatedly — it skips
|
|
anything already rendered, so it will not fight whatever app.js does. */
|
|
window.VK.hydrateIcons = function hydrateIcons(root = document) {
|
|
root.querySelectorAll("[data-icon]").forEach((el) => {
|
|
if (el.firstElementChild) return;
|
|
const size = Number(el.dataset.iconSize) || 13;
|
|
el.innerHTML = window.VK.icon(el.dataset.icon, size);
|
|
});
|
|
};
|
|
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", () => window.VK.hydrateIcons());
|
|
} else {
|
|
window.VK.hydrateIcons();
|
|
}
|