export type LucideIconName = | 'circle-play' | 'play' | 'pause' | 'maximize' | 'arrow-left' | 'chevron-left' | 'chevron-right' | 'rotate-ccw' | 'rotate-cw' | 'repeat' | 'volume-2' | 'volume-x' | 'log-out' | 'x'; type IconAttrs = Record; type IconNode = readonly [tagName: string, attrs: IconAttrs]; const SVG_NS = 'http://www.w3.org/2000/svg'; const ICONS: Record = { 'circle-play': [ ['circle', { cx: '12', cy: '12', r: '10' }], ['polygon', { points: '10 8 16 12 10 16 10 8' }] ], play: [ ['polygon', { points: '6 3 20 12 6 21 6 3' }] ], pause: [ ['rect', { x: '14', y: '4', width: '4', height: '16', rx: '1' }], ['rect', { x: '6', y: '4', width: '4', height: '16', rx: '1' }] ], maximize: [ ['path', { d: 'M8 3H5a2 2 0 0 0-2 2v3' }], ['path', { d: 'M21 8V5a2 2 0 0 0-2-2h-3' }], ['path', { d: 'M3 16v3a2 2 0 0 0 2 2h3' }], ['path', { d: 'M16 21h3a2 2 0 0 0 2-2v-3' }] ], 'arrow-left': [ ['path', { d: 'm12 19-7-7 7-7' }], ['path', { d: 'M19 12H5' }] ], 'chevron-left': [ ['path', { d: 'm15 18-6-6 6-6' }] ], 'chevron-right': [ ['path', { d: 'm9 18 6-6-6-6' }] ], 'rotate-ccw': [ ['path', { d: 'M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8' }], ['path', { d: 'M3 3v5h5' }] ], 'rotate-cw': [ ['path', { d: 'M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8' }], ['path', { d: 'M21 3v5h-5' }] ], repeat: [ ['path', { d: 'm17 2 4 4-4 4' }], ['path', { d: 'M3 11v-1a4 4 0 0 1 4-4h14' }], ['path', { d: 'm7 22-4-4 4-4' }], ['path', { d: 'M21 13v1a4 4 0 0 1-4 4H3' }] ], 'volume-2': [ ['path', { d: 'M11 4.702a1 1 0 0 0-1.664-.747L5.5 7.5H4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h1.5l3.836 3.545A1 1 0 0 0 11 19.298z' }], ['path', { d: 'M16 9a5 5 0 0 1 0 6' }], ['path', { d: 'M19.364 18.364a9 9 0 0 0 0-12.728' }] ], 'volume-x': [ ['path', { d: 'M11 4.702a1 1 0 0 0-1.664-.747L5.5 7.5H4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h1.5l3.836 3.545A1 1 0 0 0 11 19.298z' }], ['line', { x1: '22', y1: '9', x2: '16', y2: '15' }], ['line', { x1: '16', y1: '9', x2: '22', y2: '15' }] ], 'log-out': [ ['path', { d: 'M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4' }], ['polyline', { points: '16 17 21 12 16 7' }], ['line', { x1: '21', y1: '12', x2: '9', y2: '12' }] ], x: [ ['path', { d: 'M18 6 6 18' }], ['path', { d: 'm6 6 12 12' }] ] }; export function createLucideIcon(name: LucideIconName, className = 'vrwp-icon'): SVGSVGElement { const svg = document.createElementNS(SVG_NS, 'svg'); svg.setAttribute('class', className); svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('fill', 'none'); svg.setAttribute('stroke', 'currentColor'); svg.setAttribute('stroke-width', '2'); svg.setAttribute('stroke-linecap', 'round'); svg.setAttribute('stroke-linejoin', 'round'); svg.setAttribute('aria-hidden', 'true'); svg.setAttribute('focusable', 'false'); for (const [tagName, attrs] of ICONS[name]) { const node = document.createElementNS(SVG_NS, tagName); for (const [key, value] of Object.entries(attrs)) { node.setAttribute(key, value); } svg.appendChild(node); } return svg; } export function setLucideIcon(target: HTMLElement, name: LucideIconName): void { const existingIcon = target.querySelector('.vrwp-icon'); if (existingIcon) { existingIcon.replaceWith(createLucideIcon(name)); return; } target.prepend(createLucideIcon(name)); } export function drawLucideIcon( ctx: CanvasRenderingContext2D, name: LucideIconName, x: number, y: number, size: number, color = '#ffffff', strokeWidth = 2 ): void { ctx.save(); ctx.translate(x, y); ctx.scale(size / 24, size / 24); ctx.strokeStyle = color; ctx.lineWidth = strokeWidth; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; for (const [tagName, attrs] of ICONS[name]) { drawIconNode(ctx, tagName, attrs); } ctx.restore(); } function drawIconNode(ctx: CanvasRenderingContext2D, tagName: string, attrs: IconAttrs): void { switch (tagName) { case 'path': ctx.stroke(new Path2D(attrs.d)); break; case 'line': ctx.beginPath(); ctx.moveTo(Number(attrs.x1), Number(attrs.y1)); ctx.lineTo(Number(attrs.x2), Number(attrs.y2)); ctx.stroke(); break; case 'polyline': case 'polygon': drawPoints(ctx, attrs.points, tagName === 'polygon'); break; case 'rect': drawRect(ctx, attrs); break; case 'circle': ctx.beginPath(); ctx.arc(Number(attrs.cx), Number(attrs.cy), Number(attrs.r), 0, Math.PI * 2); ctx.stroke(); break; } } function drawPoints(ctx: CanvasRenderingContext2D, pointsAttr: string, closePath: boolean): void { const coordinates = pointsAttr.trim().split(/[\s,]+/).map(Number); const points = []; for (let index = 0; index < coordinates.length - 1; index += 2) { const x = coordinates[index]; const y = coordinates[index + 1]; if (Number.isFinite(x) && Number.isFinite(y)) { points.push([x, y]); } } if (points.length === 0) return; ctx.beginPath(); ctx.moveTo(points[0][0], points[0][1]); for (const [x, y] of points.slice(1)) { ctx.lineTo(x, y); } if (closePath) { ctx.closePath(); } ctx.stroke(); } function drawRect(ctx: CanvasRenderingContext2D, attrs: IconAttrs): void { const x = Number(attrs.x); const y = Number(attrs.y); const width = Number(attrs.width); const height = Number(attrs.height); const radius = Number(attrs.rx || 0); ctx.beginPath(); if (radius > 0 && typeof ctx.roundRect === 'function') { ctx.roundRect(x, y, width, height, radius); } else { ctx.rect(x, y, width, height); } ctx.stroke(); }