Фосфор
Phosphor — компактная изометрическая матрица 7×7. Точки формируют рябь, диагональный проход и послесвечение, а указатель запускает локальную вспышку.
Интерактивное демо
loop
Пропсы
Акцент
320
Подсказка
<script lang="ts">
import { onMount } from 'svelte';
type Pt = [number, number];
type Slab = { hull: string; arc: string; edges: string[]; top: string };
/** Цвет точек. */
export let accent = '#f43f5e';
/** Послесвечение τ в мс: как медленно гаснет зажжённая точка. Можно биндить. */
export let tau = 320;
export let width = 'min(94vw, 560px)';
export let showControls = true;
export let label = '7 by 7 phosphor dot matrix on an isometric tile';
const N = 7, PITCH = 10, M = 8, EXT = M * 2 + PITCH * (N - 1);
const T = 5, R = 2.5, DROP = 15, BT = 4, BO = 7;
const K = 2.3, C = 0.7071, OX = 200, OY = 95;
const SPLASH: [number, number, number][] = [[0, 0, 1], [1, 0, 0.45], [-1, 0, 0.45], [0, 1, 0.45], [0, -1, 0.45]];
/** Кадры автоматической анимации: рябь, пауза, диагональный проход, пауза. */
const LOOP: number[][] = [
[0, 0, 0, 8, 0, 0, 0], [0, 0, 8, 20, 8, 0, 0], [0, 28, 34, 34, 34, 28, 0], [28, 34, 65, 65, 65, 34, 28], [65, 0, 0, 0, 0, 0, 65],
[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0],
[64, 0, 0, 0, 0, 0, 0], [32, 64, 0, 0, 0, 0, 0], [16, 32, 64, 0, 0, 0, 0], [8, 16, 32, 64, 0, 0, 0], [4, 8, 16, 32, 64, 0, 0], [2, 4, 8, 16, 32, 64, 0], [1, 2, 4, 8, 16, 32, 64],
[0, 1, 2, 4, 8, 16, 32], [0, 0, 1, 2, 4, 8, 16], [0, 0, 0, 1, 2, 4, 8], [0, 0, 0, 0, 1, 2, 4], [0, 0, 0, 0, 0, 1, 2], [0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0]
];
const lit = (f: number, r: number, c: number): number => (LOOP[f][r] >> (N - 1 - c)) & 1;
const P = (u: number, v: number, z: number): Pt => [OX + (u - v) * C * K, OY + ((u + v) * C * 0.5 - z) * K];
const U = (sx: number, sy: number, z: number): Pt => {
const a = (sx - OX) / (C * K), b = ((sy - OY) / K + z) / (C * 0.5);
return [(a + b) / 2, (b - a) / 2];
};
const path = (pts: Pt[], close = false): string =>
pts.map((q, i) => (i ? 'L' : 'M') + q[0].toFixed(1) + ',' + q[1].toFixed(1)).join('') + (close ? 'Z' : '');
function rr(x0: number, y0: number, x1: number, y1: number, r: number): Pt[] {
const p: Pt[] = [], S = 8;
const corners: [number, number, number][] = [
[x1 - r, y0 + r, -90],
[x1 - r, y1 - r, 0],
[x0 + r, y1 - r, 90],
[x0 + r, y0 + r, 180]
];
corners.forEach((c) => {
for (let i = 0; i <= S; i++) {
const t = ((c[2] + (90 * i) / S) * Math.PI) / 180;
p.push([c[0] + r * Math.cos(t), c[1] + r * Math.sin(t)]);
}
});
return p;
}
function hull(pts: Pt[]): Pt[] {
const p = pts.slice().sort((a, b) => a[0] - b[0] || a[1] - b[1]);
const cr = (o: Pt, a: Pt, b: Pt): number => (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
const lo: Pt[] = [], up: Pt[] = [];
p.forEach((q) => {
while (lo.length > 1 && cr(lo[lo.length - 2], lo[lo.length - 1], q) <= 0) lo.pop();
lo.push(q);
});
for (let i = p.length - 1; i >= 0; i--) {
const q = p[i];
while (up.length > 1 && cr(up[up.length - 2], up[up.length - 1], q) <= 0) up.pop();
up.push(q);
}
lo.pop();
up.pop();
return lo.concat(up);
}
function slab(ring: Pt[], zt: number, zb: number): Slab {
const t = ring.map((q) => P(q[0], q[1], zt));
const b = ring.map((q) => P(q[0], q[1], zb));
const n = ring.length;
let iL = 0, iR = 0;
b.forEach((q, i) => {
if (q[0] < b[iL][0]) iL = i;
if (q[0] > b[iR][0]) iR = i;
});
const walk = (d: number): { a: Pt[]; y: number } => {
const a: Pt[] = [];
let i = iL, y = 0;
for (;;) {
a.push(b[i]);
y += b[i][1];
if (i === iR) break;
i = (i + d + n) % n;
}
return { a, y: y / a.length };
};
const w1 = walk(1), w2 = walk(-1);
return {
hull: path(hull(t.concat(b)), true),
arc: path((w1.y > w2.y ? w1 : w2).a),
edges: [iL, iR].map((i) => path([t[i], b[i]])),
top: path(t, true)
};
}
const plate = slab(rr(-BO, -BO, EXT + BO, EXT + BO, 11), -DROP, -DROP - BT);
const plateInset = path(rr(-BO + 1.8, -BO + 1.8, EXT + BO - 1.8, EXT + BO - 1.8, 9.5).map((q) => P(q[0], q[1], -DROP)), true);
const tileRing = rr(0, 0, EXT, EXT, 7);
const tile = slab(tileRing, T, 0);
const tileInset = path(rr(3.5, 3.5, EXT - 3.5, EXT - 3.5, 4.5).map((q) => P(q[0], q[1], T)), true);
const drops: string[] = (() => {
const t0 = tileRing.map((q) => P(q[0], q[1], 0));
const ex = [0, 0, 0];
t0.forEach((q, i) => {
if (q[0] < t0[ex[0]][0]) ex[0] = i;
if (q[0] > t0[ex[1]][0]) ex[1] = i;
if (q[1] > t0[ex[2]][1]) ex[2] = i;
});
return ex.map((i) => path([P(tileRing[i][0], tileRing[i][1], 0), P(tileRing[i][0], tileRing[i][1], -DROP)]));
})();
const dots = Array.from({ length: N * N }, (_, i) => {
const [cx, cy] = P(M + (i % N) * PITCH, M + Math.floor(i / N) * PITCH, T);
return { cx, cy };
});
let svg: SVGSVGElement;
let op: number[] = new Array<number>(N * N).fill(0.14);
let readout = 'loop';
const I = new Float32Array(N * N);
let painting = false;
let prev: Pt | null = null;
function excite(x: number, y: number, a: number): void {
const c = Math.round((x - M) / PITCH), r = Math.round((y - M) / PITCH);
for (const [dr, dc, k] of SPLASH) {
const rr_ = r + dr, cc = c + dc;
if (rr_ < 0 || rr_ >= N || cc < 0 || cc >= N) continue;
I[rr_ * N + cc] = Math.max(I[rr_ * N + cc], a * k);
}
}
function at(e: PointerEvent): Pt {
const pt = svg.createSVGPoint();
pt.x = e.clientX;
pt.y = e.clientY;
const m = svg.getScreenCTM();
const q = m ? pt.matrixTransform(m.inverse()) : pt;
return U(q.x, q.y, T);
}
function move(e: PointerEvent): void {
painting = true;
const q = at(e);
if (prev) {
const n = Math.max(1, Math.ceil(Math.hypot(q[0] - prev[0], q[1] - prev[1]) / 3));
for (let k = 1; k <= n; k++) excite(prev[0] + ((q[0] - prev[0]) * k) / n, prev[1] + ((q[1] - prev[1]) * k) / n, 1);
} else excite(q[0], q[1], 1);
prev = q;
}
function leave(): void {
painting = false;
prev = null;
}
onMount(() => {
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const FRAME_MS = 95;
let raf = 0;
let last = performance.now();
let frame = 0;
let frameClock = 0;
let renderClock = 0;
const applyFrame = (f: number): void => {
for (let r = 0; r < N; r++) {
for (let c = 0; c < N; c++) {
if (lit(f, r, c)) I[r * N + c] = Math.max(I[r * N + c], 0.95);
}
}
};
applyFrame(0);
const tick = (now: number): void => {
const dt = Math.min(0.05, (now - last) / 1000);
last = now;
const decay = Math.exp((-dt * 1000) / tau);
for (let i = 0; i < I.length; i++) I[i] *= decay;
frameClock += dt * 1000;
renderClock += dt * 1000;
if (!reduce && frameClock >= FRAME_MS) {
const steps = Math.floor(frameClock / FRAME_MS);
frame = (frame + steps) % LOOP.length;
frameClock %= FRAME_MS;
applyFrame(frame);
readout = 'loop';
}
if (reduce && !painting) {
for (let i = 0; i < N * N; i++) I[i] = Math.max(I[i], lit(3, Math.floor(i / N), i % N) * 0.8);
readout = 'still';
} else if (painting) {
readout = 'paint';
}
if (renderClock >= 33 || painting) {
renderClock = 0;
op = Array.from(I, (v) => 0.08 + 0.92 * Math.min(1, Math.max(0, v)));
}
raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
return () => cancelAnimationFrame(raf);
});
</script>
<div class="hl-wrap" style="--hl-accent:{accent}">
<svg
class="hl"
bind:this={svg}
viewBox="0 0 400 340"
role="img"
aria-label={label}
style="width:{width}"
on:pointermove={move}
on:pointerdown={move}
on:pointerleave={leave}
on:pointerup={(e) => {
if (e.pointerType !== 'mouse') leave();
}}
>
<path class="s nostroke" d={plate.hull} />
<path class="n" d={plate.arc} />
{#each plate.edges as d, i (i)}<path class="n" {d} />{/each}
<path class="s" d={plate.top} />
<path class="lo" d={plateInset} />
{#each drops as d, i (i)}<path class="n dash" {d} />{/each}
<path class="s nostroke" d={tile.hull} />
<path class="n" d={tile.arc} />
{#each tile.edges as d, i (i)}<path class="n" {d} />{/each}
<path class="s" d={tile.top} />
<path class="lo" d={tileInset} />
{#each dots as d, i (i)}
<ellipse class="dot" cx={d.cx} cy={d.cy} rx={R * K} ry={R * K * 0.5} opacity={op[i].toFixed(3)} />
{/each}
</svg>
{#if showControls}
<div class="read">{readout}</div>
<label class="tau">
persistence τ
<input type="range" min="60" max="1200" step="10" bind:value={tau} />
<span>{tau} ms</span>
</label>
{/if}
</div>
<style>
.hl-wrap {
--hl-bg: #ffffff;
--hl-line: #2b2f3a;
--hl-faint: #2b2f3a66;
--hl-txt: #6b6f7a;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
color: var(--hl-txt);
font: 12px ui-monospace, Menlo, Consolas, monospace;
letter-spacing: 0.06em;
}
:global(.dark) .hl-wrap {
--hl-bg: #090909;
--hl-line: oklch(0.9674 0.0013 var(--base-hue));
--hl-faint: color-mix(in srgb, oklch(0.9674 0.0013 var(--base-hue)) 22%, transparent);
--hl-txt: oklch(0.669 0.0107 var(--base-hue));
}
.hl {
display: block;
height: auto;
max-height: 70vh;
cursor: crosshair;
touch-action: none;
}
.hl * {
vector-effect: non-scaling-stroke;
}
.s {
fill: var(--hl-bg);
stroke: var(--hl-line);
stroke-width: 1.2;
stroke-linejoin: round;
stroke-linecap: round;
}
.nostroke { stroke: none; }
.n {
fill: none;
stroke: var(--hl-line);
stroke-width: 1.2;
stroke-linecap: round;
}
.lo {
fill: none;
stroke: var(--hl-faint);
stroke-width: 1.2;
}
.dash {
stroke: var(--hl-faint);
stroke-dasharray: 3 4;
}
.dot {
fill: var(--hl-accent);
}
.read { min-height: 1.2em; }
.tau {
display: flex;
gap: 10px;
align-items: center;
}
.tau input {
width: min(60vw, 240px);
accent-color: var(--hl-accent);
}
</style>
<script lang="ts">
import { onMount } from 'svelte';
type Pt = [number, number];
type Slab = { hull: string; arc: string; edges: string[]; top: string };
/** Цвет точек. */
export let accent = '#f43f5e';
/** Послесвечение τ в мс: как медленно гаснет зажжённая точка. Можно биндить. */
export let tau = 320;
export let width = 'min(94vw, 560px)';
export let showControls = true;
export let label = '7 by 7 phosphor dot matrix on an isometric tile';
const N = 7, PITCH = 10, M = 8, EXT = M * 2 + PITCH * (N - 1);
const T = 5, R = 2.5, DROP = 15, BT = 4, BO = 7;
const K = 2.3, C = 0.7071, OX = 200, OY = 95;
const SPLASH: [number, number, number][] = [[0, 0, 1], [1, 0, 0.45], [-1, 0, 0.45], [0, 1, 0.45], [0, -1, 0.45]];
/** Кадры автоматической анимации: рябь, пауза, диагональный проход, пауза. */
const LOOP: number[][] = [
[0, 0, 0, 8, 0, 0, 0], [0, 0, 8, 20, 8, 0, 0], [0, 28, 34, 34, 34, 28, 0], [28, 34, 65, 65, 65, 34, 28], [65, 0, 0, 0, 0, 0, 65],
[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0],
[64, 0, 0, 0, 0, 0, 0], [32, 64, 0, 0, 0, 0, 0], [16, 32, 64, 0, 0, 0, 0], [8, 16, 32, 64, 0, 0, 0], [4, 8, 16, 32, 64, 0, 0], [2, 4, 8, 16, 32, 64, 0], [1, 2, 4, 8, 16, 32, 64],
[0, 1, 2, 4, 8, 16, 32], [0, 0, 1, 2, 4, 8, 16], [0, 0, 0, 1, 2, 4, 8], [0, 0, 0, 0, 1, 2, 4], [0, 0, 0, 0, 0, 1, 2], [0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0]
];
const lit = (f: number, r: number, c: number): number => (LOOP[f][r] >> (N - 1 - c)) & 1;
const P = (u: number, v: number, z: number): Pt => [OX + (u - v) * C * K, OY + ((u + v) * C * 0.5 - z) * K];
const U = (sx: number, sy: number, z: number): Pt => {
const a = (sx - OX) / (C * K), b = ((sy - OY) / K + z) / (C * 0.5);
return [(a + b) / 2, (b - a) / 2];
};
const path = (pts: Pt[], close = false): string =>
pts.map((q, i) => (i ? 'L' : 'M') + q[0].toFixed(1) + ',' + q[1].toFixed(1)).join('') + (close ? 'Z' : '');
function rr(x0: number, y0: number, x1: number, y1: number, r: number): Pt[] {
const p: Pt[] = [], S = 8;
const corners: [number, number, number][] = [
[x1 - r, y0 + r, -90],
[x1 - r, y1 - r, 0],
[x0 + r, y1 - r, 90],
[x0 + r, y0 + r, 180]
];
corners.forEach((c) => {
for (let i = 0; i <= S; i++) {
const t = ((c[2] + (90 * i) / S) * Math.PI) / 180;
p.push([c[0] + r * Math.cos(t), c[1] + r * Math.sin(t)]);
}
});
return p;
}
function hull(pts: Pt[]): Pt[] {
const p = pts.slice().sort((a, b) => a[0] - b[0] || a[1] - b[1]);
const cr = (o: Pt, a: Pt, b: Pt): number => (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
const lo: Pt[] = [], up: Pt[] = [];
p.forEach((q) => {
while (lo.length > 1 && cr(lo[lo.length - 2], lo[lo.length - 1], q) <= 0) lo.pop();
lo.push(q);
});
for (let i = p.length - 1; i >= 0; i--) {
const q = p[i];
while (up.length > 1 && cr(up[up.length - 2], up[up.length - 1], q) <= 0) up.pop();
up.push(q);
}
lo.pop();
up.pop();
return lo.concat(up);
}
function slab(ring: Pt[], zt: number, zb: number): Slab {
const t = ring.map((q) => P(q[0], q[1], zt));
const b = ring.map((q) => P(q[0], q[1], zb));
const n = ring.length;
let iL = 0, iR = 0;
b.forEach((q, i) => {
if (q[0] < b[iL][0]) iL = i;
if (q[0] > b[iR][0]) iR = i;
});
const walk = (d: number): { a: Pt[]; y: number } => {
const a: Pt[] = [];
let i = iL, y = 0;
for (;;) {
a.push(b[i]);
y += b[i][1];
if (i === iR) break;
i = (i + d + n) % n;
}
return { a, y: y / a.length };
};
const w1 = walk(1), w2 = walk(-1);
return {
hull: path(hull(t.concat(b)), true),
arc: path((w1.y > w2.y ? w1 : w2).a),
edges: [iL, iR].map((i) => path([t[i], b[i]])),
top: path(t, true)
};
}
const plate = slab(rr(-BO, -BO, EXT + BO, EXT + BO, 11), -DROP, -DROP - BT);
const plateInset = path(rr(-BO + 1.8, -BO + 1.8, EXT + BO - 1.8, EXT + BO - 1.8, 9.5).map((q) => P(q[0], q[1], -DROP)), true);
const tileRing = rr(0, 0, EXT, EXT, 7);
const tile = slab(tileRing, T, 0);
const tileInset = path(rr(3.5, 3.5, EXT - 3.5, EXT - 3.5, 4.5).map((q) => P(q[0], q[1], T)), true);
const drops: string[] = (() => {
const t0 = tileRing.map((q) => P(q[0], q[1], 0));
const ex = [0, 0, 0];
t0.forEach((q, i) => {
if (q[0] < t0[ex[0]][0]) ex[0] = i;
if (q[0] > t0[ex[1]][0]) ex[1] = i;
if (q[1] > t0[ex[2]][1]) ex[2] = i;
});
return ex.map((i) => path([P(tileRing[i][0], tileRing[i][1], 0), P(tileRing[i][0], tileRing[i][1], -DROP)]));
})();
const dots = Array.from({ length: N * N }, (_, i) => {
const [cx, cy] = P(M + (i % N) * PITCH, M + Math.floor(i / N) * PITCH, T);
return { cx, cy };
});
let svg: SVGSVGElement;
let op: number[] = new Array<number>(N * N).fill(0.14);
let readout = 'loop';
const I = new Float32Array(N * N);
let painting = false;
let prev: Pt | null = null;
function excite(x: number, y: number, a: number): void {
const c = Math.round((x - M) / PITCH), r = Math.round((y - M) / PITCH);
for (const [dr, dc, k] of SPLASH) {
const rr_ = r + dr, cc = c + dc;
if (rr_ < 0 || rr_ >= N || cc < 0 || cc >= N) continue;
I[rr_ * N + cc] = Math.max(I[rr_ * N + cc], a * k);
}
}
function at(e: PointerEvent): Pt {
const pt = svg.createSVGPoint();
pt.x = e.clientX;
pt.y = e.clientY;
const m = svg.getScreenCTM();
const q = m ? pt.matrixTransform(m.inverse()) : pt;
return U(q.x, q.y, T);
}
function move(e: PointerEvent): void {
painting = true;
const q = at(e);
if (prev) {
const n = Math.max(1, Math.ceil(Math.hypot(q[0] - prev[0], q[1] - prev[1]) / 3));
for (let k = 1; k <= n; k++) excite(prev[0] + ((q[0] - prev[0]) * k) / n, prev[1] + ((q[1] - prev[1]) * k) / n, 1);
} else excite(q[0], q[1], 1);
prev = q;
}
function leave(): void {
painting = false;
prev = null;
}
onMount(() => {
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const FRAME_MS = 95;
let raf = 0;
let last = performance.now();
let frame = 0;
let frameClock = 0;
let renderClock = 0;
const applyFrame = (f: number): void => {
for (let r = 0; r < N; r++) {
for (let c = 0; c < N; c++) {
if (lit(f, r, c)) I[r * N + c] = Math.max(I[r * N + c], 0.95);
}
}
};
applyFrame(0);
const tick = (now: number): void => {
const dt = Math.min(0.05, (now - last) / 1000);
last = now;
const decay = Math.exp((-dt * 1000) / tau);
for (let i = 0; i < I.length; i++) I[i] *= decay;
frameClock += dt * 1000;
renderClock += dt * 1000;
if (!reduce && frameClock >= FRAME_MS) {
const steps = Math.floor(frameClock / FRAME_MS);
frame = (frame + steps) % LOOP.length;
frameClock %= FRAME_MS;
applyFrame(frame);
readout = 'loop';
}
if (reduce && !painting) {
for (let i = 0; i < N * N; i++) I[i] = Math.max(I[i], lit(3, Math.floor(i / N), i % N) * 0.8);
readout = 'still';
} else if (painting) {
readout = 'paint';
}
if (renderClock >= 33 || painting) {
renderClock = 0;
op = Array.from(I, (v) => 0.08 + 0.92 * Math.min(1, Math.max(0, v)));
}
raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
return () => cancelAnimationFrame(raf);
});
</script>
<div class="hl-wrap" style="--hl-accent:{accent}">
<svg
class="hl"
bind:this={svg}
viewBox="0 0 400 340"
role="img"
aria-label={label}
style="width:{width}"
on:pointermove={move}
on:pointerdown={move}
on:pointerleave={leave}
on:pointerup={(e) => {
if (e.pointerType !== 'mouse') leave();
}}
>
<path class="s nostroke" d={plate.hull} />
<path class="n" d={plate.arc} />
{#each plate.edges as d, i (i)}<path class="n" {d} />{/each}
<path class="s" d={plate.top} />
<path class="lo" d={plateInset} />
{#each drops as d, i (i)}<path class="n dash" {d} />{/each}
<path class="s nostroke" d={tile.hull} />
<path class="n" d={tile.arc} />
{#each tile.edges as d, i (i)}<path class="n" {d} />{/each}
<path class="s" d={tile.top} />
<path class="lo" d={tileInset} />
{#each dots as d, i (i)}
<ellipse class="dot" cx={d.cx} cy={d.cy} rx={R * K} ry={R * K * 0.5} opacity={op[i].toFixed(3)} />
{/each}
</svg>
{#if showControls}
<div class="read">{readout}</div>
<label class="tau">
persistence τ
<input type="range" min="60" max="1200" step="10" bind:value={tau} />
<span>{tau} ms</span>
</label>
{/if}
</div>
<style>
.hl-wrap {
--hl-bg: #ffffff;
--hl-line: #2b2f3a;
--hl-faint: #2b2f3a66;
--hl-txt: #6b6f7a;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
color: var(--hl-txt);
font: 12px ui-monospace, Menlo, Consolas, monospace;
letter-spacing: 0.06em;
}
:global(.dark) .hl-wrap {
--hl-bg: #090909;
--hl-line: oklch(0.9674 0.0013 var(--base-hue));
--hl-faint: color-mix(in srgb, oklch(0.9674 0.0013 var(--base-hue)) 22%, transparent);
--hl-txt: oklch(0.669 0.0107 var(--base-hue));
}
.hl {
display: block;
height: auto;
max-height: 70vh;
cursor: crosshair;
touch-action: none;
}
.hl * {
vector-effect: non-scaling-stroke;
}
.s {
fill: var(--hl-bg);
stroke: var(--hl-line);
stroke-width: 1.2;
stroke-linejoin: round;
stroke-linecap: round;
}
.nostroke { stroke: none; }
.n {
fill: none;
stroke: var(--hl-line);
stroke-width: 1.2;
stroke-linecap: round;
}
.lo {
fill: none;
stroke: var(--hl-faint);
stroke-width: 1.2;
}
.dash {
stroke: var(--hl-faint);
stroke-dasharray: 3 4;
}
.dot {
fill: var(--hl-accent);
}
.read { min-height: 1.2em; }
.tau {
display: flex;
gap: 10px;
align-items: center;
}
.tau input {
width: min(60vw, 240px);
accent-color: var(--hl-accent);
}
</style>
Использование
<script lang="ts">
import Phosphor from '$lib/components/ui-registry/Phosphor.svelte';
</script>
<Phosphor accent="#f43f5e" tau={320} /><script lang="ts">
import Phosphor from '$lib/components/ui-registry/Phosphor.svelte';
</script>
<Phosphor accent="#f43f5e" tau={320} />Пропсы
| Пропс | Тип | По умолчанию | Описание |
|---|---|---|---|
accent | string | '#f43f5e' | Цвет светящихся точек |
tau | number | 320 | Время послесвечения в миллисекундах |
width | string | 'min(94vw, 560px)' | CSS-ширина иллюстрации |
showControls | boolean | true | Показывать подсказку с состоянием |
label | string | — | Доступное имя SVG |
