diff --git a/src/components/vaffel.tsx b/src/components/vaffel.tsx
new file mode 100644
index 0000000..baefdd6
--- /dev/null
+++ b/src/components/vaffel.tsx
@@ -0,0 +1,154 @@
+"use client";
+import { useEffect, useRef } from "react";
+
+export default function HeartsMinimal({ amount }: { amount: number }) {
+ const ref = useRef
(null);
+
+ useEffect(() => {
+ const canvas = ref.current;
+ if (!canvas) return;
+
+ const ctx = canvas.getContext("2d");
+ if (!ctx) return;
+
+ const SPEED_Y = 0.35;
+ const DRIFT_X = 0.2;
+ const heartSpriteSize = 27;
+
+ function randomInt(min: number, max: number) {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ }
+ function rand(min: number, max: number) {
+ return Math.random() * (max - min) + min;
+ }
+
+ let w = 0,
+ h = 0,
+ DPR = 1;
+
+ const resize = () => {
+ w = window.innerWidth;
+ h = window.innerHeight;
+ DPR = Math.min(Math.max(window.devicePixelRatio || 1, 1), 2);
+ canvas.width = Math.floor(w * DPR);
+ canvas.height = Math.floor(h * DPR);
+ canvas.style.width = w + "px";
+ canvas.style.height = h + "px";
+ ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
+ };
+
+ resize();
+ window.addEventListener("resize", resize);
+
+ const heartSprite = makeWaffleSprite(heartSpriteSize);
+
+ type Heart = {
+ x: number;
+ y: number;
+ rot: number;
+ vr: number;
+ s: number;
+ vy: number;
+ vx: number;
+ alpha: number;
+ };
+
+ const hearts: Heart[] = [];
+ const numHearts = amount;
+
+ for (let i = 0; i < numHearts; i += 1) {
+ hearts.push({
+ x: randomInt(0, w),
+ y: randomInt(0, h),
+ rot: Math.random() * Math.PI * 2,
+ vr: rand(-0.01, 0.01),
+ s: rand(12, 22),
+ vy: rand(0.6, 1.2),
+ vx: rand(0.6, 1.15),
+ alpha: rand(0.45, 0.8)
+ });
+ }
+
+ let raf = 0;
+
+ const draw = () => {
+ ctx.clearRect(0, 0, w, h);
+
+ for (let i = 0; i < hearts.length; i += 1) {
+ const ht = hearts[i];
+
+ ctx.save();
+ ctx.translate(ht.x, ht.y);
+ ht.rot += ht.vr;
+ ctx.rotate(ht.rot);
+ ctx.globalAlpha = ht.alpha;
+
+ ctx.drawImage(heartSprite, -ht.s / 2, -ht.s / 2, ht.s, ht.s);
+
+ ctx.restore();
+ ctx.globalAlpha = 1;
+
+ ht.y += SPEED_Y * ht.vy;
+ ht.x += DRIFT_X * ht.vx;
+
+ // litt “svev”
+ ht.x += Math.sin((ht.y + i) * 0.02) * 0.15;
+
+ // wrap sidene
+ if (ht.x > w) ht.x = 0;
+ else if (ht.x < 0) ht.x = w;
+
+ // respawn på toppen
+ if (ht.y > h) {
+ ht.x = randomInt(0, w);
+ ht.y = -20;
+ ht.rot = Math.random() * Math.PI * 2;
+ ht.vr = rand(-0.01, 0.01);
+ ht.s = rand(12, 22);
+ ht.vy = rand(0.6, 1.2);
+ ht.vx = rand(0.6, 1.15);
+ ht.alpha = rand(0.45, 0.8);
+ }
+ }
+
+ raf = requestAnimationFrame(draw);
+ };
+
+ raf = requestAnimationFrame(draw);
+
+ return () => {
+ cancelAnimationFrame(raf);
+ window.removeEventListener("resize", resize);
+ };
+ }, [amount]);
+
+ return (
+
+ );
+}
+
+function makeWaffleSprite(size: number): HTMLCanvasElement {
+ const spriteSize = size * 3;
+ const c = document.createElement("canvas");
+ c.width = c.height = spriteSize;
+
+ const g = c.getContext("2d")!;
+ g.font = `${spriteSize * 0.85}px serif`;
+ g.textAlign = "center";
+ g.textBaseline = "middle";
+ g.globalAlpha = 0.75;
+ g.fillText("🧇", spriteSize / 2, spriteSize / 2);
+
+ return c;
+}
diff --git a/src/hooks/use-utepils.ts b/src/hooks/use-utepils.ts
index 102b8ff..72d5935 100644
--- a/src/hooks/use-utepils.ts
+++ b/src/hooks/use-utepils.ts
@@ -17,7 +17,6 @@ export function useUtepils() {
fetch("https://utepils-ten.vercel.app/api/utepils/bergen")
.then((res) => {
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
- console.log("Fetched utepils data:", res);
return res.json();
})
.then((data) => {
diff --git a/src/hooks/use-vaffel.ts b/src/hooks/use-vaffel.ts
new file mode 100644
index 0000000..8911419
--- /dev/null
+++ b/src/hooks/use-vaffel.ts
@@ -0,0 +1,29 @@
+import { useQuery } from "@tanstack/react-query";
+
+export type VaffelQueue = {
+ user_id: string;
+ display_name: string;
+ total_orders: number;
+}[];
+
+const API_URL = "https://vaffel.echo-webkom.no/687650156262195217";
+
+export function useVaffel() {
+ return useQuery({
+ queryKey: ["vaffel"],
+ queryFn: async () => {
+ const [queueRes, statusRes, totalRes] = await Promise.all([
+ fetch(`${API_URL}/queue`),
+ fetch(`${API_URL}/status`),
+ fetch(`${API_URL}/total`)
+ ]);
+
+ const queue: VaffelQueue = await queueRes.json();
+ const status: string = await statusRes.text();
+ const total: number = await totalRes.json();
+
+ return { queue, status, total };
+ },
+ refetchInterval: 1000
+ });
+}
diff --git a/src/index.css b/src/index.css
index 9f46ccf..23efb41 100644
--- a/src/index.css
+++ b/src/index.css
@@ -17,7 +17,7 @@
.valentines {
--border: 338, 68%, 63%;
- --primary: 338, 68%, 63%
+ --primary: 338, 68%, 63%;
}
@theme inline {
diff --git a/src/pages/vaffel-screen.tsx b/src/pages/vaffel-screen.tsx
new file mode 100644
index 0000000..80bb29e
--- /dev/null
+++ b/src/pages/vaffel-screen.tsx
@@ -0,0 +1,45 @@
+import type { VaffelQueue } from "../hooks/use-vaffel";
+import WaffleRain from "../components/vaffel";
+
+type VaffelProps = {
+ queue: VaffelQueue;
+ status: string;
+ total: number;
+};
+
+export default function VaffelScreen({ queue, status, total }: VaffelProps) {
+ return (
+
+
+
+
Vaffelkø
+
+ Status:{" "}
+
+ {status === "open" ? "Åpen" : "Stengt"}
+
+
+ Vafler stekt: {total}
+
+
+
+ {queue.length === 0 ? (
+
Ingen i køen
+ ) : (
+
+ {queue.map((person, index) => (
+ -
+ {index + 1}:
+ {person.display_name}
+
+ ))}
+
+ )}
+
+
+ );
+}