"use client";
import * as React from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp, Hourglass, Magnet, Pause, Play, RotateCcw, Sparkles, Trophy, Volume2, VolumeX, Zap } from "lucide-react";
import { cn } from "@/lib/utils";
import { DARK_PALETTE, LIGHT_PALETTE, POWER_DURATION, SerpentEngine, type Dir, type PowerKind, type SerpentPalette, type SoundKind, type Status } from "./engine";
import { keyIsForGame, readBest, useBlips, useCanvasFit, useIsDark, usePauseOnBlur, writeBest } from "./kit";
export type { SerpentPalette } from "./engine";
export interface NeonSerpentProps {
/** Starting level (1–9). Higher levels start faster with more pylons. */
initialLevel?: number;
/** Fixed seed for deterministic orb/pylon placement (tests, daily challenges). */
seed?: number;
/** Called once per run when the serpent crashes. */
onGameOver?: (score: number) => void;
/** Override canvas colours (hex). Applied on top of the light/dark palette. */
theme?: Partial<SerpentPalette>;
/** localStorage key for the best score. */
storageKey?: string;
title?: string;
className?: string;
}
interface Hud {
status: Status;
score: number;
level: number;
combo: number;
comboPct: number;
length: number;
eaten: number;
maxCombo: number;
active: Record<PowerKind, number>;
}
const KEY_DIR: Record<string, Dir> = { ArrowUp: 0, KeyW: 0, ArrowRight: 1, KeyD: 1, ArrowDown: 2, KeyS: 2, ArrowLeft: 3, KeyA: 3 };
const POWER_INFO: { kind: PowerKind; name: string; blurb: string; Icon: typeof Hourglass; color: string }[] = [
{ kind: "slow", name: "Slow-mo", blurb: "Time crawls for 6s", Icon: Hourglass, color: "text-cyan-500" },
{ kind: "phase", name: "Phase", blurb: "Glide through walls & pylons", Icon: Sparkles, color: "text-violet-500" },
{ kind: "magnet", name: "Magnet", blurb: "Pulls nearby orbs to you", Icon: Magnet, color: "text-amber-500" },
];
function gridFor(w: number, h: number) {
const target = w < 520 ? 22 : 30;
return {
cols: Math.max(12, Math.min(32, Math.round(w / target))),
rows: Math.max(12, Math.min(22, Math.round(h / target))),
};
}
export function NeonSerpent({ initialLevel = 1, seed, onGameOver, theme, storageKey = "neon-serpent:best", title = "Neon Serpent", className }: NeonSerpentProps) {
const reduce = useReducedMotion() ?? false;
const dark = useIsDark();
const rootRef = React.useRef<HTMLDivElement>(null);
const canvasRef = React.useRef<HTMLCanvasElement>(null);
const sizeRef = useCanvasFit(canvasRef);
const engineRef = React.useRef<SerpentEngine | null>(null);
const runRef = React.useRef(0);
const [muted, setMuted] = React.useState(true);
const [best, setBest] = React.useState(0);
const [newBest, setNewBest] = React.useState(false);
const blip = useBlips(muted);
const [hud, setHud] = React.useState<Hud>({ status: "ready", score: 0, level: initialLevel, combo: 1, comboPct: 0, length: 4, eaten: 0, maxCombo: 1, active: { slow: 0, phase: 0, magnet: 0 } });
const palette = React.useMemo(() => ({ ...(dark ? DARK_PALETTE : LIGHT_PALETTE), ...theme }), [dark, theme]);
const paletteRef = React.useRef(palette);
React.useEffect(() => {
paletteRef.current = palette;
}, [palette]);
const cbRef = React.useRef({ onGameOver, blip, storageKey });
React.useEffect(() => {
cbRef.current = { onGameOver, blip, storageKey };
}, [onGameOver, blip, storageKey]);
React.useEffect(() => {
const b = readBest(storageKey);
// eslint-disable-next-line react-hooks/set-state-in-effect -- hydrate from localStorage after mount
setBest(b);
}, [storageKey]);
const sound = React.useCallback((k: SoundKind) => {
const b = cbRef.current.blip;
if (k === "eat") b(660, 0.07, "square", 0.04, 220);
else if (k === "prism") b(880, 0.18, "triangle", 0.06, 440);
else if (k === "power") b(320, 0.25, "sawtooth", 0.04, 600);
else if (k === "level") b(520, 0.3, "triangle", 0.05, 520);
else if (k === "die") b(220, 0.5, "sawtooth", 0.06, -180);
}, []);
const makeEngine = React.useCallback((w: number, h: number) => {
const { cols, rows } = gridFor(w || 800, h || 600);
runRef.current += 1;
const runSeed = seed !== undefined ? seed + runRef.current - 1 : Math.floor(Math.random() * 2 ** 31);
engineRef.current = new SerpentEngine({
seed: runSeed,
level: Math.max(1, Math.min(9, initialLevel)),
cols,
rows,
reduced: reduce,
onSound: sound,
onGameOver: (score) => {
const key = cbRef.current.storageKey;
const prev = readBest(key);
if (score > prev) {
writeBest(key, score);
setBest(score);
setNewBest(true);
} else setNewBest(false);
cbRef.current.onGameOver?.(score);
},
});
}, [initialLevel, reduce, seed, sound]);
// main loop
React.useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas?.getContext("2d");
if (!canvas || !ctx) return;
let raf = 0;
let last = performance.now();
let lastHud = "";
const frame = (now: number) => {
const dt = Math.min(50, now - last);
last = now;
const { w, h, dpr } = sizeRef.current;
if (!engineRef.current) makeEngine(w, h);
const e = engineRef.current!;
// regrid while idle so the arena always fits the container
if (e.status === "ready") {
const g = gridFor(w, h);
if (g.cols !== e.cols || g.rows !== e.rows) makeEngine(w, h);
}
const eng = engineRef.current!;
eng.update(dt);
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
eng.render(ctx, w, h, paletteRef.current);
const next: Hud = {
status: eng.status,
score: eng.score,
level: eng.level,
combo: eng.combo,
comboPct: Math.round((Math.max(0, eng.comboTimer) / 3200) * 20) / 20,
length: eng.snake.length,
eaten: eng.eaten,
maxCombo: eng.maxCombo,
active: { slow: Math.ceil(eng.active.slow / 100), phase: Math.ceil(eng.active.phase / 100), magnet: Math.ceil(eng.active.magnet / 100) },
};
const key = JSON.stringify(next);
if (key !== lastHud) {
lastHud = key;
setHud(next);
}
raf = requestAnimationFrame(frame);
};
raf = requestAnimationFrame(frame);
return () => cancelAnimationFrame(raf);
}, [makeEngine, sizeRef]);
const start = React.useCallback(() => {
const e = engineRef.current;
if (!e) return;
if (e.status === "over") {
makeEngine(sizeRef.current.w, sizeRef.current.h);
engineRef.current!.start();
} else if (e.status === "ready") e.start();
else if (e.status === "paused") e.status = "playing";
setNewBest(false);
rootRef.current?.focus({ preventScroll: true });
}, [makeEngine, sizeRef]);
const restart = React.useCallback(() => {
makeEngine(sizeRef.current.w, sizeRef.current.h);
engineRef.current!.start();
setNewBest(false);
rootRef.current?.focus({ preventScroll: true });
}, [makeEngine, sizeRef]);
const togglePause = React.useCallback(() => {
const e = engineRef.current;
if (!e) return;
if (e.status === "playing") e.status = "paused";
else if (e.status === "paused") e.status = "playing";
}, []);
const turn = React.useCallback((d: Dir) => {
const e = engineRef.current;
if (!e) return;
if (e.status === "over") return;
if (e.status === "paused") e.status = "playing";
e.turn(d);
}, []);
usePauseOnBlur(
React.useCallback(() => {
const e = engineRef.current;
if (e?.status === "playing") e.status = "paused";
}, []),
);
React.useEffect(() => {
const onKey = (ev: KeyboardEvent) => {
if (!keyIsForGame(ev, rootRef.current)) return;
const onButton = ev.target instanceof HTMLButtonElement;
const d = KEY_DIR[ev.code];
if (d !== undefined) {
ev.preventDefault();
turn(d);
return;
}
const e = engineRef.current;
if (ev.code === "Space" || ev.code === "Enter") {
if (onButton) return;
ev.preventDefault();
if (e?.status === "playing") togglePause();
else start();
} else if (ev.code === "KeyP" || ev.code === "Escape") {
ev.preventDefault();
togglePause();
} else if (ev.code === "KeyM") setMuted((m) => !m);
else if (ev.code === "KeyR") restart();
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [restart, start, togglePause, turn]);
// swipe / tap on the arena
const touch = React.useRef<{ x: number; y: number; id: number } | null>(null);
const onPointerDown = (e: React.PointerEvent) => {
touch.current = { x: e.clientX, y: e.clientY, id: e.pointerId };
};
const onPointerMove = (e: React.PointerEvent) => {
const t = touch.current;
if (!t || t.id !== e.pointerId) return;
const dx = e.clientX - t.x;
const dy = e.clientY - t.y;
if (Math.hypot(dx, dy) < 24) return;
turn(Math.abs(dx) > Math.abs(dy) ? (dx > 0 ? 1 : 3) : dy > 0 ? 2 : 0);
touch.current = { x: e.clientX, y: e.clientY, id: e.pointerId };
};
const onPointerUp = () => {
const st = engineRef.current?.status;
if (touch.current && (st === "ready" || st === "over")) start();
touch.current = null;
};
const status = hud.status;
const playing = status === "playing";
return (
<div
ref={rootRef}
tabIndex={0}
data-status={status}
aria-label={`${title} game`}
className={cn("relative flex h-[700px] w-full flex-col overflow-hidden bg-background text-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-inset", className)}
>
{/* header */}
<header className="relative z-10 flex h-14 shrink-0 items-center gap-3 border-b bg-background/80 px-3 backdrop-blur sm:px-5">
<div className="flex min-w-0 items-center gap-2.5">
<SerpentMark className="size-8 shrink-0" />
<div className="min-w-0 leading-none">
<p className="truncate bg-gradient-to-r from-cyan-500 via-fuchsia-500 to-pink-500 bg-clip-text font-mono text-sm font-black tracking-[0.18em] text-transparent uppercase">{title}</p>
<p className="mt-1 hidden font-mono text-[10px] tracking-widest text-muted-foreground uppercase sm:block">Level {hud.level} · Len {hud.length}</p>
</div>
</div>
<div className="ml-auto flex items-center gap-2 sm:gap-4">
<Stat label="Score" value={hud.score} highlight />
<Stat label="Best" value={Math.max(best, hud.score)} className="hidden sm:flex" />
<div className="hidden items-center gap-2 md:flex" aria-label={`Combo multiplier ${hud.combo}`}>
<span className={cn("font-mono text-lg font-black tabular-nums", hud.combo > 1 ? "text-fuchsia-500" : "text-muted-foreground")}>×{hud.combo}</span>
<span className="h-1.5 w-16 overflow-hidden rounded-full bg-muted">
<span className="block h-full rounded-full bg-gradient-to-r from-fuchsia-500 to-cyan-400 transition-[width] duration-100" style={{ width: `${hud.comboPct * 100}%` }} />
</span>
</div>
<div className="flex items-center gap-1">
<IconButton label={playing ? "Pause (P)" : "Resume (P)"} onClick={playing ? togglePause : start} disabled={status === "dying"}>
{playing ? <Pause className="size-4" /> : <Play className="size-4" />}
</IconButton>
<IconButton label={muted ? "Unmute (M)" : "Mute (M)"} onClick={() => setMuted((m) => !m)} pressed={!muted}>
{muted ? <VolumeX className="size-4" /> : <Volume2 className="size-4" />}
</IconButton>
<IconButton label="Restart (R)" onClick={restart}>
<RotateCcw className="size-4" />
</IconButton>
</div>
</div>
</header>
<div className="flex min-h-0 flex-1">
{/* arena */}
<div className="relative min-h-0 min-w-0 flex-1 touch-none select-none" onPointerDown={onPointerDown} onPointerMove={onPointerMove} onPointerUp={onPointerUp} onPointerCancel={() => (touch.current = null)}>
<canvas ref={canvasRef} className="absolute inset-0 block" role="img" aria-label={`Arena. Score ${hud.score}, level ${hud.level}, length ${hud.length}.`} />
{/* active boosts (mobile + compact) */}
<div className="pointer-events-none absolute top-3 left-3 flex flex-col gap-1.5 lg:hidden">
{POWER_INFO.filter((p) => hud.active[p.kind] > 0).map((p) => (
<span key={p.kind} className="flex items-center gap-1.5 rounded-full border bg-background/80 px-2 py-1 font-mono text-[10px] font-bold tracking-wider uppercase backdrop-blur">
<p.Icon className={cn("size-3", p.color)} aria-hidden />
{p.name} {(hud.active[p.kind] / 10).toFixed(1)}s
</span>
))}
</div>
<AnimatePresence>
{status !== "playing" && status !== "dying" && (
<Overlay key={status} reduce={reduce}>
{status === "ready" && (
<>
<SerpentMark className="mx-auto mb-4 size-16" />
<h2 className="bg-gradient-to-b from-cyan-400 via-fuchsia-500 to-pink-600 bg-clip-text font-mono text-3xl font-black tracking-[0.2em] text-transparent uppercase sm:text-5xl">{title}</h2>
<p className="mx-auto mt-3 max-w-sm text-sm text-muted-foreground">Chain orbs fast to stack the combo. Grab boosts, dodge the pylons, and don't kiss the wall.</p>
<PrimaryButton onClick={start}>
<Play className="size-4" /> Start run
</PrimaryButton>
<p className="mt-4 hidden font-mono text-[11px] tracking-wider text-muted-foreground uppercase sm:block">Arrows / WASD steer · P pause · M sound</p>
<p className="mt-4 font-mono text-[11px] tracking-wider text-muted-foreground uppercase sm:hidden">Swipe or use the pad to steer</p>
</>
)}
{status === "paused" && (
<>
<p className="font-mono text-xs tracking-[0.3em] text-muted-foreground uppercase">Signal held</p>
<h2 className="mt-2 font-mono text-4xl font-black tracking-widest uppercase">Paused</h2>
<div className="mt-6 flex justify-center gap-2">
<PrimaryButton onClick={start} className="mt-0">
<Play className="size-4" /> Resume
</PrimaryButton>
<button type="button" onClick={restart} className="inline-flex h-11 items-center gap-2 rounded-full border bg-background px-5 text-sm font-semibold hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none">
<RotateCcw className="size-4" /> Restart
</button>
</div>
</>
)}
{status === "over" && (
<>
<p className="font-mono text-xs tracking-[0.3em] text-pink-500 uppercase">Signal lost</p>
<h2 className="mt-2 font-mono text-5xl font-black tabular-nums sm:text-6xl">{hud.score}</h2>
{newBest ? (
<motion.p initial={reduce ? false : { scale: 0.6, opacity: 0 }} animate={{ scale: 1, opacity: 1 }} className="mx-auto mt-3 inline-flex items-center gap-1.5 rounded-full bg-gradient-to-r from-amber-400 to-pink-500 px-3 py-1 text-xs font-bold text-white">
<Trophy className="size-3.5" /> New best!
</motion.p>
) : (
<p className="mt-3 font-mono text-xs text-muted-foreground uppercase">Best {best}</p>
)}
<dl className="mx-auto mt-5 grid max-w-xs grid-cols-3 gap-2 text-center">
<MiniStat label="Orbs" value={hud.eaten} />
<MiniStat label="Length" value={hud.length} />
<MiniStat label="Max combo" value={`×${hud.maxCombo}`} />
</dl>
<PrimaryButton onClick={start}>
<RotateCcw className="size-4" /> Play again
</PrimaryButton>
</>
)}
</Overlay>
)}
</AnimatePresence>
</div>
{/* side panel */}
<aside className="hidden w-72 shrink-0 flex-col gap-5 overflow-y-auto border-l bg-muted/30 p-5 lg:flex">
<section>
<h3 className="mb-3 font-mono text-[11px] font-bold tracking-[0.2em] text-muted-foreground uppercase">Boosts</h3>
<ul className="space-y-2">
{POWER_INFO.map((p) => {
const left = hud.active[p.kind] * 100;
const on = left > 0;
return (
<li key={p.kind} className={cn("rounded-xl border bg-card p-3 transition-colors", on && "border-fuchsia-500/50 shadow-[0_0_24px_-8px] shadow-fuchsia-500/50")}>
<div className="flex items-center gap-2.5">
<span className={cn("grid size-8 place-items-center rounded-lg bg-muted", p.color)}>
<p.Icon className="size-4" aria-hidden />
</span>
<div className="min-w-0 flex-1">
<p className="text-sm font-semibold">{p.name}</p>
<p className="truncate text-xs text-muted-foreground">{p.blurb}</p>
</div>
{on && <span className="font-mono text-xs font-bold tabular-nums">{(left / 1000).toFixed(1)}s</span>}
</div>
<div className="mt-2 h-1 overflow-hidden rounded-full bg-muted">
<div className="h-full rounded-full bg-gradient-to-r from-cyan-400 to-fuchsia-500" style={{ width: `${on ? (left / POWER_DURATION[p.kind]) * 100 : 0}%` }} />
</div>
</li>
);
})}
</ul>
</section>
<section>
<h3 className="mb-3 font-mono text-[11px] font-bold tracking-[0.2em] text-muted-foreground uppercase">This run</h3>
<dl className="grid grid-cols-3 gap-2">
<MiniStat label="Orbs" value={hud.eaten} />
<MiniStat label="Level" value={hud.level} />
<MiniStat label="Combo" value={`×${hud.maxCombo}`} />
</dl>
</section>
<section className="mt-auto">
<h3 className="mb-3 font-mono text-[11px] font-bold tracking-[0.2em] text-muted-foreground uppercase">Controls</h3>
<ul className="space-y-1.5 text-xs text-muted-foreground">
<li className="flex justify-between"><span>Steer</span><span><Kbd>←↑↓→</Kbd> <Kbd>WASD</Kbd></span></li>
<li className="flex justify-between"><span>Pause</span><Kbd>P</Kbd></li>
<li className="flex justify-between"><span>Sound</span><Kbd>M</Kbd></li>
<li className="flex justify-between"><span>Restart</span><Kbd>R</Kbd></li>
</ul>
<p className="mt-4 flex items-start gap-2 rounded-lg bg-card p-3 text-xs text-muted-foreground">
<Zap className="mt-0.5 size-3.5 shrink-0 text-amber-500" aria-hidden />
Golden prisms are worth 50 × combo but fade in 6 seconds.
</p>
</section>
</aside>
</div>
{/* mobile pad */}
<div className="flex shrink-0 items-center justify-between gap-3 border-t bg-muted/30 px-4 py-3 sm:hidden">
<div className="flex flex-col gap-1 font-mono text-[10px] tracking-wider text-muted-foreground uppercase">
<span>Best <b className="text-foreground tabular-nums">{Math.max(best, hud.score)}</b></span>
<span>Level <b className="text-foreground">{hud.level}</b></span>
<span>Combo <b className={cn(hud.combo > 1 ? "text-fuchsia-500" : "text-foreground")}>×{hud.combo}</b></span>
</div>
<div className="grid grid-cols-3 grid-rows-2 gap-1.5" role="group" aria-label="Direction pad">
<PadButton label="Up" className="col-start-2" onPress={() => turn(0)}>
<ArrowUp className="size-5" />
</PadButton>
<PadButton label="Left" className="col-start-1 row-start-2" onPress={() => turn(3)}>
<ArrowLeft className="size-5" />
</PadButton>
<PadButton label="Down" className="col-start-2 row-start-2" onPress={() => turn(2)}>
<ArrowDown className="size-5" />
</PadButton>
<PadButton label="Right" className="col-start-3 row-start-2" onPress={() => turn(1)}>
<ArrowRight className="size-5" />
</PadButton>
</div>
</div>
<p className="sr-only" aria-live="polite">
{status === "over" ? `Game over. Score ${hud.score}.` : status === "paused" ? "Paused" : ""}
</p>
</div>
);
}
function Overlay({ children, reduce }: { children: React.ReactNode; reduce: boolean }) {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: reduce ? 0 : 0.2 }}
className="absolute inset-0 z-10 grid place-items-center bg-background/55 p-6 backdrop-blur-[3px]"
>
<motion.div initial={reduce ? false : { y: 16, scale: 0.97 }} animate={{ y: 0, scale: 1 }} transition={{ type: "spring", stiffness: 320, damping: 26 }} className="w-full max-w-md text-center">
{children}
</motion.div>
</motion.div>
);
}
function PrimaryButton({ children, onClick, className }: { children: React.ReactNode; onClick: () => void; className?: string }) {
return (
<button
type="button"
onClick={onClick}
className={cn(
"mt-6 inline-flex h-11 items-center gap-2 rounded-full bg-gradient-to-r from-cyan-500 via-fuchsia-500 to-pink-500 px-6 text-sm font-bold text-white shadow-lg shadow-fuchsia-500/30 transition hover:brightness-110 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none active:scale-95",
className,
)}
>
{children}
</button>
);
}
function IconButton({ label, onClick, children, disabled, pressed }: { label: string; onClick: () => void; children: React.ReactNode; disabled?: boolean; pressed?: boolean }) {
return (
<button
type="button"
aria-label={label}
title={label}
aria-pressed={pressed}
disabled={disabled}
onClick={onClick}
className="grid size-9 place-items-center rounded-lg text-muted-foreground transition hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none disabled:opacity-40"
>
{children}
</button>
);
}
function PadButton({ label, onPress, className, children }: { label: string; onPress: () => void; className?: string; children: React.ReactNode }) {
return (
<button
type="button"
aria-label={label}
onPointerDown={(e) => {
e.preventDefault();
onPress();
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onPress();
}
}}
className={cn("grid size-12 place-items-center rounded-xl border bg-card text-foreground shadow-sm transition active:scale-90 active:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", className)}
>
{children}
</button>
);
}
function Stat({ label, value, highlight, className }: { label: string; value: number; highlight?: boolean; className?: string }) {
return (
<div className={cn("flex flex-col items-end leading-none", className)}>
<span className="font-mono text-[9px] tracking-[0.2em] text-muted-foreground uppercase">{label}</span>
<span className={cn("mt-1 font-mono text-lg font-black tabular-nums", highlight && "text-foreground")}>{value}</span>
</div>
);
}
function MiniStat({ label, value }: { label: string; value: React.ReactNode }) {
return (
<div className="rounded-lg border bg-card px-2 py-2 text-center">
<dt className="font-mono text-[9px] tracking-wider text-muted-foreground uppercase">{label}</dt>
<dd className="mt-0.5 font-mono text-base font-bold tabular-nums">{value}</dd>
</div>
);
}
function Kbd({ children }: { children: React.ReactNode }) {
return <kbd className="rounded border bg-card px-1.5 py-0.5 font-mono text-[10px] text-foreground">{children}</kbd>;
}
function SerpentMark({ className }: { className?: string }) {
const id = React.useId();
return (
<svg viewBox="0 0 40 40" className={className} aria-hidden>
<defs>
<linearGradient id={`${id}g`} x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stopColor="#22d3ee" />
<stop offset="0.55" stopColor="#d946ef" />
<stop offset="1" stopColor="#ec4899" />
</linearGradient>
</defs>
<rect x="1" y="1" width="38" height="38" rx="10" fill="#12062b" />
<path d="M9 29 H18 V20 H27 V11" fill="none" stroke={`url(#${id}g)`} strokeWidth="5" strokeLinecap="round" strokeLinejoin="round" />
<circle cx="27" cy="11" r="4.2" fill="#5ff6ff" />
<circle cx="31" cy="29" r="2.6" fill="#ffe45c" />
</svg>
);
}