split the project in 2

This commit is contained in:
Heidi
2026-05-29 18:55:56 +01:00
parent 8aadda2d72
commit aef2113198
523 changed files with 2929 additions and 10 deletions
+105
View File
@@ -0,0 +1,105 @@
import React, { useEffect, useRef } from "react";
import { TRUNK_TOP_CSS } from "./Tree";
// this is a comment
interface Leaf {
x: number;
y: number;
size: number;
speed: number;
drift: number;
rotation: number;
rotationSpeed: number;
opacity: number;
sway: number;
swaySpeed: number;
phase: number;
}
export default function FallingLeaves({ treeRef }: { treeRef: React.RefObject<HTMLCanvasElement | null> }) {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current!;
const ctx = canvas.getContext("2d")!;
let animId: number;
const leaves: Leaf[] = [];
const MAX_LEAVES = 50;
function resize() {
const parent = canvas.parentElement;
const rect = parent?.getBoundingClientRect();
canvas.width = Math.max(1, Math.round(rect?.width || window.innerWidth));
canvas.height = Math.max(1, Math.round(rect?.height || window.innerHeight));
}
resize();
window.addEventListener("resize", resize);
function spawnLeaf(): Leaf {
const treeRect = treeRef.current?.getBoundingClientRect();
const canvasRect = canvas.getBoundingClientRect();
const spawnY = treeRect ? treeRect.top - canvasRect.top + TRUNK_TOP_CSS : canvas.height * 0.4;
const spawnX = treeRect
? treeRect.left - canvasRect.left + Math.random() * treeRect.width * 0.8 + treeRect.width * 0.1
: Math.random() * canvas.width * 0.6 + canvas.width * 0.2;
return {
x: spawnX,
y: spawnY,
size: Math.random() * 4 + 2,
speed: Math.random() * 0.3 + 0.15,
drift: Math.random() * 0.5 - 0.25,
rotation: Math.random() * Math.PI * 2,
rotationSpeed: (Math.random() - 0.5) * 0.02,
opacity: Math.random() * 0.25 + 0.08,
sway: Math.random() * 30 + 15,
swaySpeed: Math.random() * 0.008 + 0.003,
phase: Math.random() * Math.PI * 2,
};
}
let spawnTimer = 0;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
spawnTimer++;
if (spawnTimer > 30 && leaves.length < MAX_LEAVES) {
leaves.push(spawnLeaf());
spawnTimer = 0;
}
for (let i = leaves.length - 1; i >= 0; i--) {
const l = leaves[i];
l.y += l.speed;
l.x += l.drift + Math.sin(l.phase) * l.swaySpeed * l.sway * 0.05;
l.phase += l.swaySpeed;
l.rotation += l.rotationSpeed;
ctx.save();
ctx.translate(l.x, l.y);
ctx.rotate(l.rotation);
ctx.globalAlpha = l.opacity;
ctx.fillStyle = "#fdb068";
const s = Math.round(l.size);
ctx.fillRect(-s / 2, -s / 2, s, s);
ctx.restore();
if (l.y > canvas.height + 10) {
leaves.splice(i, 1);
}
}
animId = requestAnimationFrame(draw);
}
draw();
return () => {
cancelAnimationFrame(animId);
window.removeEventListener("resize", resize);
};
}, []);
return <canvas ref={canvasRef} className="falling-leaves" />;
}
+292
View File
@@ -0,0 +1,292 @@
import React, { forwardRef, useEffect, useRef } from "react";
const PX = 2;
const W = 500;
const H = 420;
const LEAF_FLOOR = H - 60;
function buildTree(seed: number) {
const woodGrid = new Uint8Array(W * H);
const leafGrid = new Uint8Array(W * H);
let s = seed;
function rand() {
s = (s * 16807 + 0) % 2147483647;
return (s & 0x7fffffff) / 0x7fffffff;
}
function setWood(x: number, y: number, shade: number) {
const px = Math.round(x), py = Math.round(y);
if (px >= 0 && px < W && py >= 0 && py < H) {
const idx = py * W + px;
if (woodGrid[idx] === 0) woodGrid[idx] = shade;
}
}
function setLeaf(x: number, y: number, shade: number) {
const px = Math.round(x), py = Math.round(y);
if (px >= 0 && px < W && py >= 0 && py < LEAF_FLOOR) {
const idx = py * W + px;
if (shade > leafGrid[idx]) leafGrid[idx] = shade;
}
}
function drawLeaf(x: number, y: number, shade: number) {
const px = Math.round(x), py = Math.round(y);
setLeaf(px, py, shade);
const r = rand();
if (r < 0.60) setLeaf(px + (rand() > 0.5 ? 1 : -1), py, shade);
if (r < 0.32) setLeaf(px, py + (rand() > 0.45 ? -1 : 1), shade);
}
function leafScatter(cx: number, cy: number, radius: number, count: number) {
for (let i = 0; i < count; i++) {
const a = rand() * Math.PI * 2;
const r = Math.sqrt(rand()) * radius;
const lx = cx + Math.cos(a) * r * 0.5;
const vertStretch = 1.0 + (cy / H) * 3.0;
const ly = cy + Math.abs(Math.sin(a)) * r * vertStretch;
const yFr = (ly - cy) / radius;
const shade = yFr < 0.5 ? 3 : yFr < 1.2 ? 2 : 1;
drawLeaf(lx, ly, shade);
}
}
function drawLine(
x0: number, y0: number, x1: number, y1: number,
thickness: number, shade: number,
) {
const dx = x1 - x0, dy = y1 - y0;
const dist = Math.sqrt(dx * dx + dy * dy);
const steps = Math.max(Math.ceil(dist * 2), 1);
for (let i = 0; i <= steps; i++) {
const t = i / steps;
const bx = x0 + dx * t, by = y0 + dy * t;
const r = (thickness / 2) * (1 - t * 0.4);
for (let oy = -Math.ceil(r); oy <= Math.ceil(r); oy++)
for (let ox = -Math.ceil(r); ox <= Math.ceil(r); ox++)
if (ox * ox + oy * oy <= r * r) setWood(bx + ox, by + oy, shade);
}
}
function branch(
x: number, y: number,
angle: number, length: number,
thickness: number, depth: number,
) {
const endX = x + Math.cos(angle) * length;
const endY = y + Math.sin(angle) * length;
const woodShade = thickness > 5 ? 1 : thickness > 2.5 ? 2 : 3;
drawLine(x, y, endX, endY, thickness, woodShade);
if (depth <= 0 || length < 1.5) {
const r = 4 + rand() * 6;
const normalised = ((angle % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2);
const deg = normalised * (180 / Math.PI);
const isNearHorizontal = deg < 30 || deg > 150;
if (isNearHorizontal) {
for (let i = 0; i < Math.round(r * r * 0.8); i++) {
const ox = (rand() - 0.5) * r * 2;
const oy = -rand() * r * 2.5;
const shade = oy < -r ? 3 : 2;
drawLeaf(endX + ox, endY + oy, shade);
}
} else {
leafScatter(endX, endY, r, Math.round(r * r * 0.6));
}
return;
}
if (depth === 1) {
const steps = Math.ceil(length / 5);
for (let i = 0; i <= steps; i++) {
const t = i / steps;
const mx = x + Math.cos(angle) * length * t;
const my = y + Math.sin(angle) * length * t;
leafScatter(mx, my, 3 + rand() * 2, 8 + Math.round(rand() * 6));
}
}
const numChildren = depth > 4 ? 2 : 3 + (rand() > 0.6 ? 1 : 0);
for (let i = 0; i < numChildren; i++) {
const spread = 0.30 + rand() * 0.20;
const angleOffset = (i - (numChildren - 1) / 2) * spread;
const droopBias = 0;
const childAngle = angle + angleOffset + (rand() - 0.5) * 0.1 + droopBias;
const lenFactor = 0.60 + rand() * 0.12;
const thickFactor = 0.50 + rand() * 0.10;
branch(endX, endY, childAngle, length * lenFactor,
Math.max(1, thickness * thickFactor), depth - 1);
}
if (depth > 1 && rand() > 0.25) {
const t = 0.25 + rand() * 0.35;
const sx = x + Math.cos(angle) * length * t;
const sy = y + Math.sin(angle) * length * t;
const side = rand() > 0.5 ? 1 : -1;
branch(sx, sy,
angle + side * (0.30 + rand() * 0.35), length * 0.5,
Math.max(1, thickness * 0.45), depth - 2);
}
}
const cx = W / 2;
const trunkTop = H - 150;
const base = H - 50;
for (let ty = trunkTop; ty <= base; ty++) {
const t = (ty - trunkTop) / (base - trunkTop);
const width = 10 + t * t * 16;
const hw = Math.ceil(width);
for (let ox = -hw; ox <= hw; ox++) {
const px = Math.round(cx + ox), py = ty;
if (px < 0 || px >= W || py < 0 || py >= H) continue;
woodGrid[py * W + px] = 2;
}
}
branch(cx - 30, trunkTop + 8, -Math.PI * 0.95, 70, 11, 7);
branch(cx - 22, trunkTop + 4, -Math.PI * 0.87, 68, 10, 7);
branch(cx - 14, trunkTop, -Math.PI * 0.78, 66, 10, 7);
branch(cx - 8, trunkTop - 4, -Math.PI * 0.68, 64, 9, 7);
branch(cx - 8, trunkTop + 4, -Math.PI * 0.70, 71, 10, 7);
branch(cx - 4, trunkTop - 7, -Math.PI * 0.60, 62, 9, 7);
branch(cx, trunkTop - 6, -Math.PI * 0.55, 70, 9, 7);
branch(cx, trunkTop - 7, -Math.PI * 0.50, 73, 9, 7);
branch(cx, trunkTop - 7, -Math.PI * 0.55, 77, 9, 7);
branch(cx, trunkTop - 6, -Math.PI * 0.45, 70, 9, 7);
branch(cx, trunkTop - 6, -Math.PI * 0.60, 70, 9, 7);
branch(cx, trunkTop - 6, -Math.PI * 0.40, 70, 9, 7);
branch(cx + 30, trunkTop + 8, -Math.PI * 0.1, 65, 11, 7);
branch(cx + 22, trunkTop + 4, -Math.PI * 0.18, 65, 10, 7);
branch(cx + 14, trunkTop, -Math.PI * 0.22, 63, 10, 7);
branch(cx + 14, trunkTop, -Math.PI * 0.25, 65, 10, 7);
branch(cx + 8, trunkTop - 4, -Math.PI * 0.32, 61, 9, 7);
branch(cx + 4, trunkTop - 7, -Math.PI * 0.40, 62, 9, 7);
branch(cx + 2, trunkTop + 4, -Math.PI * 0.45, 67, 10, 7);
branch(cx + 2, trunkTop + 5, -Math.PI * 0.17, 67, 10, 7);
branch(cx - 10, trunkTop + 20, -Math.PI * 0.75, 45, 7, 6);
branch(cx - 5, trunkTop + 20, -Math.PI * 0.62, 42, 6, 6);
branch(cx, trunkTop + 18, -Math.PI * 0.50, 44, 7, 6);
branch(cx + 10, trunkTop + 20, -Math.PI * 0.25, 45, 7, 6);
branch(cx + 5, trunkTop + 20, -Math.PI * 0.38, 42, 6, 6);
branch(cx + 3, trunkTop - 6, -Math.PI * 0.68, 10, 9, 7);
branch(cx - 3, trunkTop - 7, -Math.PI * 0.32, 10, 9, 7);
branch(cx + 3, trunkTop - 6, -Math.PI * 0.7, 5, 9, 7);
branch(cx - 3, trunkTop - 7, -Math.PI * 0.3, 5, 9, 7);
branch(cx + 3, trunkTop - 6, -Math.PI * 0.73, 20, 9, 7);
branch(cx, trunkTop - 3, -Math.PI * 0.15, 90, 5, 3);
return { woodGrid, leafGrid };
}
const WOOD_COLORS = ['', '#c96303', '#c96303', '#c96303', '#321901'];
const LEAF_COLORS = ['', '#ed5145', '#fdb068', '#fee5cd'];
export const TRUNK_TOP_CSS = (H - 150) + 10;
let cachedTreeCanvas: HTMLCanvasElement | null = null;
function renderTreeCanvas() {
if (cachedTreeCanvas) return cachedTreeCanvas;
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d")!;
canvas.width = W * PX;
canvas.height = H * PX;
ctx.clearRect(0, 0, canvas.width, canvas.height);
const { woodGrid, leafGrid } = buildTree(55);
for (let y = 0; y < H; y++)
for (let x = 0; x < W; x++) {
const v = woodGrid[y * W + x];
if (v) {
ctx.fillStyle = WOOD_COLORS[v] ?? '#321901';
ctx.fillRect(x * PX, y * PX, PX, PX);
}
}
for (let y = 0; y < H; y++)
for (let x = 0; x < W; x++) {
const v = leafGrid[y * W + x];
if (v) {
ctx.fillStyle = LEAF_COLORS[v];
ctx.fillRect(x * PX, y * PX, PX, PX);
}
}
const GROUND_Y = H - 50;
const groundColors = ['#fee5cd', '#fdca9b', '#fdb068'];
const grassColors = ['#ed5145', '#fb7b04', '#c96303', '#f17c74'];
let gs = 12345;
function grassRand() {
gs = (gs * 16807 + 0) % 2147483647;
return (gs & 0x7fffffff) / 0x7fffffff;
}
for (let x = 0; x < W; x++) {
const wave = Math.sin(x * 0.05) * 3 + Math.sin(x * 0.12) * 2;
const groundTop = Math.round(GROUND_Y + wave);
for (let y = groundTop; y < H; y++) {
const depth = (y - groundTop) / (H - groundTop);
const ci = depth < 0.3 ? 0 : depth < 0.7 ? 1 : 2;
ctx.fillStyle = groundColors[ci];
ctx.fillRect(x * PX, y * PX, PX, PX);
}
}
for (let x = 0; x < W; x++) {
if (grassRand() > 0.35) continue;
const wave = Math.sin(x * 0.05) * 3 + Math.sin(x * 0.12) * 2;
const surfaceY = Math.round(GROUND_Y + wave);
const bladeH = Math.floor(grassRand() * 4) + 2;
const color = grassColors[Math.floor(grassRand() * grassColors.length)];
ctx.fillStyle = color;
for (let b = 0; b < bladeH; b++) {
ctx.fillRect(x * PX, (surfaceY - b - 1) * PX, PX, PX);
}
if (grassRand() > 0.6 && x + 1 < W) {
ctx.fillRect((x + 1) * PX, (surfaceY - 1) * PX, PX, PX);
}
}
cachedTreeCanvas = canvas;
return canvas;
}
export function prewarmTreeCanvas() {
renderTreeCanvas();
}
const Tree = forwardRef<HTMLCanvasElement>(function Tree(_, ref) {
const internalRef = useRef<HTMLCanvasElement>(null);
const canvasRef = (ref as React.RefObject<HTMLCanvasElement>) ?? internalRef;
useEffect(() => {
const canvas = canvasRef.current!;
const ctx = canvas.getContext("2d")!;
canvas.width = W * PX;
canvas.height = H * PX;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(renderTreeCanvas(), 0, 0);
}, []);
return (
<canvas
ref={canvasRef}
className="tree"
style={{ imageRendering: "pixelated" }}
/>
);
});
export default Tree;
+50
View File
@@ -0,0 +1,50 @@
<!doctype html>
<html lang="en">
<head>
<link rel="preconnect" href="https://challenges.cloudflare.com" />
<script
src="https://challenges.cloudflare.com/turnstile/v0/api.js"
async
defer>
</script>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#e82517" />
<meta
name="description"
content="Live TSS team leaderboards, battle logs, team profiles, uptime, and consented viewer analytics for Toothless' TSS Bot."
/>
<meta name="robots" content="index, follow" />
<meta property="og:type" content="website" />
<meta property="og:url" content="__PUBLIC_ORIGIN__/" />
<meta property="og:title" content="Toothless' TSS Bot" />
<meta
property="og:description"
content="Live TSS team leaderboards, battle logs, team profiles, uptime, and consented viewer analytics."
/>
<meta property="og:image" content="__PUBLIC_ORIGIN__/embed.svg" />
<meta property="og:image:secure_url" content="__PUBLIC_ORIGIN__/embed.svg" />
<meta property="og:image:type" content="image/svg+xml" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="Toothless' TSS Bot share card" />
<meta property="og:logo" content="__PUBLIC_ORIGIN__/embed-icon.svg" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Toothless' TSS Bot" />
<meta
name="twitter:description"
content="Live TSS team leaderboards, battle logs, team profiles, uptime, and consented viewer analytics."
/>
<meta name="twitter:image" content="__PUBLIC_ORIGIN__/embed.svg" />
<meta name="twitter:image:alt" content="Toothless' TSS Bot share card" />
<link rel="icon" type="image/svg+xml" href="/embed-icon.svg" />
<link rel="apple-touch-icon" href="/embed-icon.svg" />
<title>Toothless' TSS Bot</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
+22
View File
@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96" role="img" aria-labelledby="title">
<title id="title">Toothless' TSS Bot tree icon</title>
<rect width="96" height="96" rx="18" fill="#fefde7"/>
<rect x="7" y="7" width="82" height="82" rx="14" fill="#fff2e6" stroke="#fdca9b" stroke-width="4"/>
<g shape-rendering="crispEdges">
<rect x="43" y="48" width="10" height="26" fill="#c96303"/>
<rect x="39" y="62" width="18" height="10" fill="#c96303"/>
<rect x="34" y="70" width="28" height="5" fill="#c96303"/>
<rect x="18" y="28" width="28" height="14" fill="#ed5145"/>
<rect x="28" y="18" width="28" height="15" fill="#ed5145"/>
<rect x="48" y="24" width="30" height="16" fill="#ed5145"/>
<rect x="28" y="40" width="40" height="16" fill="#ed5145"/>
<rect x="24" y="32" width="18" height="6" fill="#fdb068"/>
<rect x="34" y="22" width="18" height="6" fill="#fdb068"/>
<rect x="54" y="28" width="18" height="6" fill="#fdb068"/>
<rect x="38" y="44" width="20" height="6" fill="#fdb068"/>
<rect x="40" y="25" width="8" height="4" fill="#fee5cd"/>
<rect x="56" y="31" width="8" height="4" fill="#fee5cd"/>
<rect x="46" y="47" width="8" height="4" fill="#fee5cd"/>
<rect x="14" y="76" width="68" height="6" fill="#fdca9b"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+80
View File
@@ -0,0 +1,80 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630" role="img" aria-labelledby="title description">
<title id="title">Toothless' TSS Bot</title>
<desc id="description">A share card for Toothless' TSS Bot using the pixel tree from the home page.</desc>
<rect width="1200" height="630" fill="#fefde7"/>
<path d="M0 452 120 384l118 42 152-126 168 112 132-76 124 84 174-154 212 176v188H0Z" fill="#fcfbcf"/>
<path d="M0 498 130 430l110 42 142-92 170 102 120-54 132 66 180-112 216 128v120H0Z" fill="#f9f69f"/>
<rect x="72" y="72" width="1056" height="486" rx="28" fill="#fefde7" stroke="#fdca9b" stroke-width="6"/>
<rect x="96" y="96" width="1008" height="438" rx="18" fill="#fff2e6" opacity=".54"/>
<g transform="translate(780 144) scale(.58)" shape-rendering="crispEdges">
<rect x="0" y="0" width="500" height="420" fill="#fefde7" stroke="#fdca9b" stroke-width="6"/>
<path d="M0 372c55-16 116-12 173 3 46 12 101 20 157 4 58-17 112-10 170 5v36H0Z" fill="#fee5cd"/>
<path d="M0 394c54-10 110-8 172 6 54 12 108 10 160-4 58-15 112-10 168 4v20H0Z" fill="#fdca9b"/>
<g fill="#c96303">
<rect x="232" y="248" width="38" height="118"/>
<rect x="224" y="300" width="54" height="72"/>
<rect x="214" y="346" width="76" height="34"/>
<rect x="198" y="366" width="112" height="18"/>
<rect x="196" y="232" width="62" height="16" transform="rotate(-28 196 232)"/>
<rect x="248" y="222" width="64" height="14" transform="rotate(25 248 222)"/>
<rect x="176" y="198" width="88" height="14" transform="rotate(-46 176 198)"/>
<rect x="242" y="188" width="88" height="14" transform="rotate(42 242 188)"/>
<rect x="150" y="164" width="92" height="12" transform="rotate(-20 150 164)"/>
<rect x="270" y="154" width="98" height="12" transform="rotate(18 270 154)"/>
<rect x="212" y="120" width="82" height="12" transform="rotate(-72 212 120)"/>
<rect x="246" y="118" width="78" height="12" transform="rotate(68 246 118)"/>
</g>
<g fill="#ed5145">
<rect x="76" y="110" width="112" height="52"/>
<rect x="114" y="70" width="128" height="62"/>
<rect x="198" y="48" width="116" height="68"/>
<rect x="278" y="76" width="132" height="60"/>
<rect x="334" y="126" width="92" height="54"/>
<rect x="102" y="160" width="116" height="58"/>
<rect x="190" y="134" width="148" height="66"/>
<rect x="278" y="172" width="116" height="58"/>
</g>
<g fill="#fdb068">
<rect x="98" y="124" width="84" height="26"/>
<rect x="138" y="84" width="92" height="30"/>
<rect x="216" y="66" width="82" height="28"/>
<rect x="292" y="92" width="96" height="30"/>
<rect x="354" y="142" width="58" height="24"/>
<rect x="126" y="178" width="78" height="24"/>
<rect x="210" y="150" width="104" height="28"/>
<rect x="300" y="190" width="76" height="24"/>
</g>
<g fill="#fee5cd">
<rect x="150" y="98" width="44" height="16"/>
<rect x="238" y="76" width="38" height="16"/>
<rect x="314" y="104" width="48" height="16"/>
<rect x="230" y="160" width="48" height="16"/>
<rect x="322" y="202" width="36" height="14"/>
</g>
<g fill="#fb7b04">
<rect x="74" y="360" width="6" height="18"/>
<rect x="116" y="366" width="6" height="16"/>
<rect x="342" y="362" width="6" height="20"/>
<rect x="402" y="370" width="6" height="14"/>
<rect x="444" y="364" width="6" height="18"/>
</g>
</g>
<g transform="translate(122 128)">
<text x="0" y="36" fill="#e82517" font-family="Inter, Segoe UI, Arial, sans-serif" font-size="26" font-weight="800" letter-spacing="2">LIVE TSS INTEL</text>
<text x="0" y="132" fill="#000" font-family="Inter, Segoe UI, Arial, sans-serif" font-size="76" font-weight="900">Toothless'</text>
<text x="0" y="216" fill="#000" font-family="Inter, Segoe UI, Arial, sans-serif" font-size="76" font-weight="900">TSS Bot</text>
<text x="0" y="282" fill="#555" font-family="Inter, Segoe UI, Arial, sans-serif" font-size="30" font-weight="600">Leaderboards, battle logs, uptime, and viewer analytics.</text>
</g>
<g transform="translate(122 458)" fill="#000" font-family="Inter, Segoe UI, Arial, sans-serif" font-size="24" font-weight="800">
<rect x="0" y="-34" width="184" height="54" rx="8" fill="#fcfbcf" stroke="#fdca9b"/>
<text x="24" y="2">Teams</text>
<rect x="208" y="-34" width="214" height="54" rx="8" fill="#fcfbcf" stroke="#fdca9b"/>
<text x="232" y="2">Battle Logs</text>
<rect x="446" y="-34" width="154" height="54" rx="8" fill="#fcfbcf" stroke="#fdca9b"/>
<text x="470" y="2">Uptime</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3733
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './styles.css'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)
+574
View File
@@ -0,0 +1,574 @@
@import "tailwindcss";
@theme {
--color-cerulean-50: #e5f9ff;
--color-cerulean-100: #ccf3ff;
--color-cerulean-200: #99e7ff;
--color-cerulean-300: #66dbff;
--color-cerulean-400: #33cfff;
--color-cerulean-500: #00c3ff;
--color-cerulean-600: #009ccc;
--color-cerulean-700: #007599;
--color-cerulean-800: #004e66;
--color-cerulean-900: #002733;
--color-cerulean-950: #001b24;
--color-tropical-teal-50: #e5feff;
--color-tropical-teal-100: #ccfcff;
--color-tropical-teal-200: #99faff;
--color-tropical-teal-300: #66f7ff;
--color-tropical-teal-400: #33f5ff;
--color-tropical-teal-500: #00f2ff;
--color-tropical-teal-600: #00c2cc;
--color-tropical-teal-700: #009199;
--color-tropical-teal-800: #006166;
--color-tropical-teal-900: #003033;
--color-tropical-teal-950: #002224;
--color-light-yellow-50: #fefde7;
--color-light-yellow-100: #fcfbcf;
--color-light-yellow-200: #f9f69f;
--color-light-yellow-300: #f7f26e;
--color-light-yellow-400: #f4ee3e;
--color-light-yellow-500: #f1e90e;
--color-light-yellow-600: #c1bb0b;
--color-light-yellow-700: #918c08;
--color-light-yellow-800: #605d06;
--color-light-yellow-900: #302f03;
--color-light-yellow-950: #222102;
--color-soft-apricot-50: #fff2e6;
--color-soft-apricot-100: #fee5cd;
--color-soft-apricot-200: #fdca9b;
--color-soft-apricot-300: #fdb068;
--color-soft-apricot-400: #fc9636;
--color-soft-apricot-500: #fb7b04;
--color-soft-apricot-600: #c96303;
--color-soft-apricot-700: #974a02;
--color-soft-apricot-800: #643102;
--color-soft-apricot-900: #321901;
--color-soft-apricot-950: #231101;
--color-vibrant-coral-50: #fde9e8;
--color-vibrant-coral-100: #fad3d1;
--color-vibrant-coral-200: #f6a8a2;
--color-vibrant-coral-300: #f17c74;
--color-vibrant-coral-400: #ed5145;
--color-vibrant-coral-500: #e82517;
--color-vibrant-coral-600: #ba1e12;
--color-vibrant-coral-700: #8b160e;
--color-vibrant-coral-800: #5d0f09;
--color-vibrant-coral-900: #2e0705;
--color-vibrant-coral-950: #200503;
--color-bg: #fefde7;
--color-surface: #fcfbcf;
--color-surface-alt: #f9f69f;
--color-fury-white: #fefde7;
--color-fury-ice: #fcfbcf;
--color-fury-blue: #f9f69f;
--color-fury-glow: #fdb068;
--color-fury-cyan: #e82517;
--color-fury-aqua: #ed5145;
--color-fury-violet: #fb7b04;
--color-text: #000000;
--color-text-soft: #555555;
--color-text-muted: #888888;
--color-border: #fee5cd;
--color-ring: #ed5145;
--color-shadow: rgba(232, 37, 23, 0.12);
--color-success: #00f2ff;
--color-warning: #f4ee3e;
--color-danger: #e82517;
}
:root[data-theme="dark"] {
--color-bg: #130d08;
--color-surface: #24170d;
--color-surface-alt: #34210f;
--color-fury-white: #18100a;
--color-fury-ice: #24170d;
--color-fury-blue: #34210f;
--color-fury-glow: #a86224;
--color-fury-cyan: #ff6a5f;
--color-fury-aqua: #ff8d84;
--color-fury-violet: #ffac4d;
--color-text: #fdb068;
--color-text-soft: #fff2e6;
--color-text-muted: #fee5cd;
--color-border: #68401f;
--color-ring: #ff8d84;
--color-shadow: rgba(255, 106, 95, 0.18);
--color-success: #58f0f5;
--color-warning: #f4ee3e;
--color-danger: #ff6a5f;
color-scheme: dark;
}
:root[data-theme="light"] {
color-scheme: light;
}
@font-face {
font-display: swap;
font-family: "SF Pro Text Local";
font-style: normal;
font-weight: 400;
src: url("/fonts/SF-Pro-Text-Regular.otf") format("opentype");
}
@font-face {
font-display: swap;
font-family: "SF Pro Text Local";
font-style: normal;
font-weight: 500;
src: url("/fonts/SF-Pro-Text-Medium.otf") format("opentype");
}
@font-face {
font-display: swap;
font-family: "SF Pro Text Local";
font-style: normal;
font-weight: 600;
src: url("/fonts/SF-Pro-Text-Semibold.otf") format("opentype");
}
@font-face {
font-display: swap;
font-family: "SF Pro Text Local";
font-style: normal;
font-weight: 700;
src: url("/fonts/SF-Pro-Text-Bold.otf") format("opentype");
}
@font-face {
font-display: swap;
font-family: "SF Pro Text Local";
font-style: normal;
font-weight: 800;
src: url("/fonts/SF-Pro-Text-Heavy.otf") format("opentype");
}
@font-face {
font-display: swap;
font-family: "SF Pro Text Local";
font-style: normal;
font-weight: 900;
src: url("/fonts/SF-Pro-Text-Black.otf") format("opentype");
}
@font-face {
font-display: swap;
font-family: "SF Pro Rounded Local";
font-style: normal;
font-weight: 400;
src: url("/fonts/SF-Pro-Rounded-Regular.otf") format("opentype");
}
@font-face {
font-display: swap;
font-family: "SF Pro Rounded Local";
font-style: normal;
font-weight: 600;
src: url("/fonts/SF-Pro-Rounded-Semibold.otf") format("opentype");
}
@font-face {
font-display: swap;
font-family: "SF Pro Rounded Local";
font-style: normal;
font-weight: 700 900;
src: url("/fonts/SF-Pro-Rounded-Black.otf") format("opentype");
}
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
min-width: 320px;
min-height: 100vh;
background: var(--color-bg);
font-family:
"SF Pro Rounded Local", "SF Pro Rounded", "SF Pro Text Local",
"SF Pro Text", -apple-system, BlinkMacSystemFont, "Segoe UI", Inter,
ui-sans-serif, system-ui, sans-serif;
}
h1,
h2,
h3 {
font-family:
"SF Pro Text Local", "SF Pro Text", -apple-system, BlinkMacSystemFont,
"Segoe UI", Inter, ui-sans-serif, system-ui, sans-serif;
}
.pixel-mountains {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 0;
}
.pixel-mountains canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
image-rendering: pixelated;
object-fit: cover;
object-position: center bottom;
transition: opacity 420ms ease;
}
.pixel-mountains-previous {
animation: pixelMountainsFadeOut 420ms ease forwards;
z-index: 1;
}
.pixel-mountains-active {
z-index: 0;
}
.pixel-sky {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 2;
}
.pixel-sun,
.pixel-moon {
position: absolute;
display: block;
image-rendering: pixelated;
transform: translate(-50%, -50%);
will-change: left, opacity, top, transform;
}
.pixel-sun {
left: 78%;
top: 18%;
width: 96px;
height: 96px;
background: var(--color-fury-glow);
clip-path: polygon(
33.333% 0,
66.666% 0,
66.666% 8.333%,
83.333% 8.333%,
83.333% 16.666%,
91.666% 16.666%,
91.666% 33.333%,
100% 33.333%,
100% 66.666%,
91.666% 66.666%,
91.666% 83.333%,
83.333% 83.333%,
83.333% 91.666%,
66.666% 91.666%,
66.666% 100%,
33.333% 100%,
33.333% 91.666%,
16.666% 91.666%,
16.666% 83.333%,
8.333% 83.333%,
8.333% 66.666%,
0 66.666%,
0 33.333%,
8.333% 33.333%,
8.333% 16.666%,
16.666% 16.666%,
16.666% 8.333%,
33.333% 8.333%
);
box-shadow:
0 -40px 0 -32px var(--color-fury-glow),
0 40px 0 -32px var(--color-fury-glow),
-40px 0 0 -32px var(--color-fury-glow),
40px 0 0 -32px var(--color-fury-glow),
32px 32px 0 -34px var(--color-fury-violet),
-32px 32px 0 -34px var(--color-fury-violet),
32px -32px 0 -34px var(--color-fury-violet),
-32px -32px 0 -34px var(--color-fury-violet),
inset -12px -12px 0 0 var(--color-soft-apricot-300);
}
.pixel-sun::after {
position: absolute;
left: 24px;
top: 20px;
width: 24px;
height: 24px;
background: var(--color-soft-apricot-50);
clip-path: polygon(0 0, 100% 0, 100% 66.666%, 66.666% 66.666%, 66.666% 100%, 0 100%);
content: "";
}
.pixel-moon {
left: 78%;
top: 18%;
width: 88px;
height: 88px;
background: var(--color-soft-apricot-50);
clip-path: polygon(
36.363% 0,
72.727% 0,
72.727% 9.09%,
90.909% 9.09%,
90.909% 27.272%,
100% 27.272%,
100% 72.727%,
90.909% 72.727%,
90.909% 90.909%,
72.727% 90.909%,
72.727% 100%,
36.363% 100%,
36.363% 90.909%,
18.181% 90.909%,
18.181% 72.727%,
0 72.727%,
0 27.272%,
18.181% 27.272%,
18.181% 9.09%,
36.363% 9.09%
);
box-shadow:
inset -8px -8px 0 0 var(--color-soft-apricot-100),
24px 20px 0 -18px var(--color-soft-apricot-100);
}
.pixel-moon::after {
position: absolute;
left: 34px;
top: -10px;
width: 82px;
height: 82px;
background: var(--color-bg);
clip-path: polygon(
36.363% 0,
72.727% 0,
72.727% 9.09%,
90.909% 9.09%,
90.909% 27.272%,
100% 27.272%,
100% 72.727%,
90.909% 72.727%,
90.909% 90.909%,
72.727% 90.909%,
72.727% 100%,
36.363% 100%,
36.363% 90.909%,
18.181% 90.909%,
18.181% 72.727%,
0 72.727%,
0 27.272%,
18.181% 27.272%,
18.181% 9.09%,
36.363% 9.09%
);
content: "";
}
.pixel-star {
position: absolute;
display: block;
width: 6px;
height: 6px;
background: var(--color-soft-apricot-100);
box-shadow: 8px 0 0 -2px var(--color-fury-glow), 0 8px 0 -2px var(--color-fury-glow);
}
.pixel-star-a {
right: -68px;
top: -32px;
}
.pixel-star-b {
left: -92px;
top: 22px;
}
.pixel-star-c {
right: -116px;
top: 54px;
}
.pixel-sky-light .pixel-sun,
.pixel-sky-dark .pixel-moon {
opacity: 1;
}
.pixel-sky-light .pixel-moon,
.pixel-sky-dark .pixel-sun {
opacity: 0;
}
.pixel-sky-light-to-dark .pixel-sun {
animation: celestialPathExit 980ms cubic-bezier(0.55, 0.085, 0.68, 0.53) forwards;
left: 0;
offset-anchor: center;
offset-path: var(--celestial-exit-path);
offset-rotate: 0deg;
top: 0;
transform: none;
}
.pixel-sky-light-to-dark .pixel-moon {
animation: celestialPathEnter 980ms cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
left: 0;
offset-anchor: center;
offset-path: var(--celestial-enter-path);
offset-rotate: 0deg;
top: 0;
transform: none;
}
.pixel-sky-dark-to-light .pixel-moon {
animation: celestialPathExit 980ms cubic-bezier(0.55, 0.085, 0.68, 0.53) forwards;
left: 0;
offset-anchor: center;
offset-path: var(--celestial-exit-path);
offset-rotate: 0deg;
top: 0;
transform: none;
}
.pixel-sky-dark-to-light .pixel-sun {
animation: celestialPathEnter 980ms cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
left: 0;
offset-anchor: center;
offset-path: var(--celestial-enter-path);
offset-rotate: 0deg;
top: 0;
transform: none;
}
:root[data-theme="dark"] .pixel-mountains {
opacity: 1;
}
.tree {
width: min(100%, 500px);
height: auto;
display: block;
background: var(--color-fury-white);
border: 3px solid var(--color-border);
border-radius: 16px;
padding: 12px;
box-shadow:
0 4px 24px rgba(253, 202, 155, 0.35),
0 1px 3px rgba(50, 25, 1, 0.08);
}
:root[data-theme="dark"] .apricot-button-text {
color: #fff2e6;
}
:root[data-theme="dark"] .tree {
filter: brightness(0.78) sepia(0.18) saturate(0.92);
box-shadow:
0 4px 24px rgba(255, 106, 95, 0.14),
0 1px 3px rgba(0, 0, 0, 0.35);
}
.theme-toggle-mover-arc {
animation: themeToggleArc 560ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
:root.theme-transition,
:root.theme-transition *,
:root.theme-transition *::before,
:root.theme-transition *::after {
transition-duration: 420ms;
transition-property: background-color, border-color, box-shadow, color, fill, opacity, stroke, text-decoration-color;
transition-timing-function: ease;
}
.falling-leaves {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 2;
}
.location-signal-map .leaflet-pane,
.location-signal-map .leaflet-top,
.location-signal-map .leaflet-bottom {
z-index: 0;
}
.location-signal-map .leaflet-control {
z-index: 1;
}
@keyframes scrollPulse {
0% {
transform: translateY(-100%);
opacity: 0;
}
35% {
opacity: 1;
}
100% {
transform: translateY(250%);
opacity: 0;
}
}
@keyframes pixelMountainsFadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes themeToggleArc {
0% {
transform: translate3d(var(--from-x), var(--from-y), 0);
}
50% {
transform: translate3d(var(--mid-x), var(--mid-y), 0);
}
100% {
transform: translate3d(var(--to-x), var(--to-y), 0);
}
}
@keyframes celestialPathExit {
0% {
offset-distance: 0%;
opacity: 1;
}
100% {
offset-distance: 100%;
opacity: 0;
}
}
@keyframes celestialPathEnter {
0% {
offset-distance: 0%;
opacity: 0;
}
100% {
offset-distance: 100%;
opacity: 1;
}
}