Add ToolsnToys section — hub, tools, guides, FOSS catalog, 6 guide pages

Hub (toolsntoys.html), JL's early tools (jl-early-tools.html), guides hub
(guides.html), FOSS Arsenal catalog (foss-tools.html), and 6 individual
guide pages covering AppImage, Linux install/uninstall, Docker, and
Facebook streaming. All pages use the night-sky adaptation of the
adventurers_toolbox3 aesthetic with dimmed ember alpha (0.38/0.65).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-02 14:14:21 +02:00
parent c1a7098ebe
commit 0290b29395
10 changed files with 5586 additions and 0 deletions

828
ToolsnToys/toolsntoys.html Normal file
View File

@@ -0,0 +1,828 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TOOLS N TOYS // FIELD UNIT SP-07</title>
<!-- Sixtyfour, Share Tech Mono, Rambla -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Sixtyfour&family=Share+Tech+Mono&family=Rambla:ital,wght@0,400;0,700;1,400;1,700&family=Noto+Emoji&display=swap" rel="stylesheet">
<style>
:root {
--bg-deep: #030812;
--bg-mid: #060f1a;
--bg-surface: #081524;
--bg-panel: #0d1e32;
--text-main: #c8d8e8;
--text-muted: #3a5570;
--phosphor: #00ff41;
--neon-green: #32dc8c;
--neon-green-dim: #229966;
--fire-amber: #e8943a;
--fire-amber-dim: #b06020;
--toucan: #ffcf40;
--fire-coral: #d4654a;
--neon-teal: #2ac4b3;
--orchid: #c558d9;
--border-neon: rgba(50,220,140,0.3);
--border-amber: rgba(232,148,58,0.4);
--campfire-glow: #e8943a; /* adapted amber glow */
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--bg-deep);
color: var(--text-main);
font-family: 'Rambla', sans-serif;
min-height: 100vh;
overflow-x: hidden;
position: relative;
}
/* Night atmosphere bg */
body::before {
content: '';
position: fixed;
inset: 0;
background:
radial-gradient(ellipse 60% 50% at 50% 80%, rgba(232,148,58,0.05) 0%, transparent 70%),
radial-gradient(ellipse 100% 60% at 20% 0%, rgba(3,8,18,0.9) 0%, transparent 60%),
radial-gradient(ellipse 100% 60% at 80% 0%, rgba(3,8,18,0.9) 0%, transparent 60%),
repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0,0,0,0.12) 2px,
rgba(0,0,0,0.12) 4px
);
pointer-events: none;
z-index: 0;
}
/* Scanline effect */
body::after {
content: '';
position: fixed;
inset: 0;
background: repeating-linear-gradient(
to bottom,
transparent 0px,
transparent 3px,
rgba(0,0,0,0.15) 3px,
rgba(0,0,0,0.15) 4px
);
pointer-events: none;
z-index: 1000;
animation: scanline 15s linear infinite;
}
@keyframes scanline {
0% { background-position: 0 0; }
100% { background-position: 0 400px; }
}
/* Noise texture overlay */
.noise {
position: fixed;
inset: 0;
opacity: 0.04;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
pointer-events: none;
z-index: 999;
}
.app {
position: relative;
z-index: 1;
max-width: 1100px;
margin: 0 auto;
padding: 2rem 1.5rem;
}
/* ===== HEADER ===== */
.header {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-neon);
position: relative;
}
.header::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
width: 40%;
height: 1px;
background: var(--neon-green);
box-shadow: 0 0 10px var(--neon-green);
animation: headerGlow 4s ease-in-out infinite alternate;
}
@keyframes headerGlow {
from { opacity: 0.4; width: 25%; }
to { opacity: 1; width: 55%; }
}
.unit-tag {
font-family: 'Share Tech Mono', monospace;
font-size: 0.9rem;
color: var(--fire-amber);
letter-spacing: 0.25em;
text-transform: uppercase;
margin-bottom: 0.4rem;
}
.title {
font-family: 'Sixtyfour', cursive;
font-size: 1.8rem;
font-weight: 400;
color: var(--neon-green);
text-shadow: 0 0 8px var(--neon-green), 0 0 20px rgba(50,220,140,0.6);
line-height: 1.2;
margin-bottom: 0.5rem;
}
.subtitle {
font-family: 'Share Tech Mono', monospace;
font-size: 0.9rem;
color: var(--text-muted);
letter-spacing: 0.2em;
text-transform: uppercase;
}
.status-badge {
font-family: 'Share Tech Mono', monospace;
font-size: 1rem;
padding: 0.25rem 0.8rem;
border: 1px solid var(--neon-green);
color: var(--neon-green);
letter-spacing: 0.15em;
text-shadow: 0 0 8px var(--neon-green);
box-shadow: inset 0 0 10px rgba(50,220,140,0.15), 0 0 8px rgba(50,220,140,0.2);
animation: badgePulse 2.5s ease-in-out infinite;
}
@keyframes badgePulse {
0%, 100% { opacity: 0.8; box-shadow: inset 0 0 8px rgba(50,220,140,0.1); }
50% { opacity: 1; box-shadow: inset 0 0 16px rgba(50,220,140,0.3), 0 0 12px rgba(50,220,140,0.4); }
}
.header-right {
text-align: right;
display: flex;
flex-direction: column;
gap: 0.5rem;
align-items: flex-end;
}
.coord {
font-family: 'Share Tech Mono', monospace;
font-size: 0.9rem;
color: var(--text-muted);
letter-spacing: 0.1em;
}
/* ===== STAT BAR ===== */
.stats-bar {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.75rem;
margin-bottom: 2rem;
}
.stat-card {
background: var(--bg-panel);
border: 1px solid var(--border-neon);
padding: 0.85rem 1.1rem;
position: relative;
clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%);
box-shadow: 0 0 15px rgba(0,0,0,0.3), inset 0 0 15px rgba(50,220,140,0.05);
}
.stat-card::before {
content: '';
position: absolute;
top: 0; right: 0;
width: 0; height: 0;
border-style: solid;
border-width: 0 12px 12px 0;
border-color: transparent var(--bg-deep) transparent transparent;
}
.stat-label {
font-family: 'Share Tech Mono', monospace;
font-size: 0.85rem;
color: var(--text-muted);
letter-spacing: 0.18em;
margin-bottom: 0.4rem;
text-transform: uppercase;
}
.stat-value {
font-family: 'Sixtyfour', cursive;
font-size: 0.9rem;
color: var(--fire-amber);
text-shadow: 0 0 10px rgba(232,148,58,0.5);
margin-bottom: 0.2rem;
}
.stat-sub {
font-family: 'Share Tech Mono', monospace;
font-size: 0.85rem;
color: var(--text-muted);
}
.stat-bar-track {
height: 3px;
background: rgba(255,255,255,0.05);
margin-top: 0.6rem;
position: relative;
overflow: hidden;
}
.stat-bar-fill {
height: 100%;
background: var(--neon-green);
box-shadow: 0 0 6px var(--neon-green);
}
/* ===== MAIN LAYOUT ===== */
.main-grid {
display: grid;
grid-template-columns: 240px 1fr;
gap: 1.5rem;
}
/* ===== SIDEBAR ===== */
.panel {
background: var(--bg-panel);
border: 1px solid var(--border-neon);
margin-bottom: 1.5rem;
box-shadow: 0 0 15px rgba(0,0,0,0.4);
}
.panel-header {
font-family: 'Share Tech Mono', monospace;
font-size: 1rem;
letter-spacing: 0.2em;
color: var(--fire-amber);
padding: 0.6rem 0.8rem;
border-bottom: 1px solid rgba(232,148,58,0.2);
display: flex;
align-items: center;
gap: 0.5rem;
}
.panel-header::before { content: '//'; color: var(--text-muted); }
.panel-body { padding: 0.8rem; }
.category-list { display: flex; flex-direction: column; gap: 0.4rem; }
.cat-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.5rem 0.7rem;
cursor: pointer;
font-size: 0.9rem;
color: var(--text-main);
transition: all 150ms;
border: 1px solid transparent;
position: relative;
}
.cat-item:hover, .cat-item.active {
background: rgba(50,220,140,0.08);
border-color: rgba(50,220,140,0.3);
color: var(--neon-green);
}
.cat-item.active::before {
content: '';
position: absolute;
left: 0; top: 0; bottom: 0;
width: 2px;
background: var(--neon-green);
box-shadow: 0 0 8px var(--neon-green);
}
.cat-count {
font-family: 'Share Tech Mono', monospace;
font-size: 0.85rem;
color: var(--text-muted);
background: rgba(0,0,0,0.3);
padding: 1px 6px;
}
.status-line {
font-family: 'Share Tech Mono', monospace;
font-size: 0.8rem;
display: flex;
justify-content: space-between;
margin-bottom: 0.4rem;
}
/* ===== ITEM GRID ===== */
.item-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
}
.item-card {
background: var(--bg-panel);
border: 1px solid rgba(50,220,140,0.15);
position: relative;
transition: all 200ms ease;
overflow: hidden;
clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 10px 100%, 0 calc(100% - 10px));
display: flex;
flex-direction: column;
}
.item-card:hover {
border-color: rgba(50,220,140,0.5);
background: var(--bg-surface);
box-shadow: 0 8px 32px rgba(0,0,0,0.5), 0 0 15px rgba(50,220,140,0.1);
}
.item-card.legendary { border-color: rgba(255,207,64,0.3); }
.item-card.legendary:hover { border-color: var(--toucan); box-shadow: 0 0 20px rgba(255,207,64,0.15); }
.item-card.rare { border-color: rgba(42,196,179,0.3); }
.item-card.rare:hover { border-color: var(--neon-teal); }
.item-card.uncommon { border-color: rgba(50,220,140,0.3); }
.item-card.damaged { border-color: rgba(212,101,74,0.3); }
.item-card.unique {
border-color: rgba(197,88,217,0.3);
animation: uniquePulse 3s infinite alternate;
}
@keyframes uniquePulse {
from { border-color: rgba(197,88,217,0.2); }
to { border-color: rgba(197,88,217,0.6); box-shadow: 0 0 12px rgba(197,88,217,0.2); }
}
.item-card::before {
content: '';
position: absolute;
top: 0; right: 0;
width: 0; height: 0;
border-style: solid;
border-width: 0 10px 10px 0;
border-color: transparent var(--bg-deep) transparent transparent;
}
.item-img-zone {
height: 100px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0,0,0,0.4);
font-size: 2.8rem;
font-family: 'Noto Emoji', sans-serif;
border-bottom: 1px solid rgba(255,255,255,0.05);
position: relative;
color: var(--text-muted);
}
.item-card.legendary .item-img-zone { color: var(--toucan); text-shadow: 0 0 12px rgba(255,207,64,0.5); }
.item-card.rare .item-img-zone { color: var(--neon-teal); text-shadow: 0 0 12px rgba(42,196,179,0.5); }
.item-card.uncommon .item-img-zone { color: var(--neon-green);text-shadow: 0 0 12px rgba(50,220,140,0.5); }
.item-card.damaged .item-img-zone { color: var(--fire-coral);text-shadow: 0 0 12px rgba(212,101,74,0.5); }
.item-card.unique .item-img-zone { color: var(--orchid); text-shadow: 0 0 12px rgba(197,88,217,0.5); }
.item-body { padding: 1rem; flex: 1; display: flex; flex-direction: column; }
.item-name {
font-family: 'Rambla', sans-serif;
font-weight: 700;
font-size: 1rem;
color: var(--text-main);
margin-bottom: 0.3rem;
letter-spacing: 0.02em;
}
.item-meta {
display: flex;
justify-content: space-between;
font-family: 'Share Tech Mono', monospace;
font-size: 0.9rem;
color: var(--text-muted);
margin-bottom: 0.8rem;
}
.item-desc {
font-family: 'Rambla', sans-serif;
font-size: 0.9rem;
line-height: 1.4;
color: var(--text-main);
margin-bottom: 1rem;
flex: 1;
}
.condition-track {
height: 3px;
background: rgba(255,255,255,0.05);
margin-bottom: 1rem;
}
.condition-fill { height: 100%; box-shadow: 0 0 4px var(--neon-green); }
.rarity-pip {
position: absolute;
top: 0.6rem;
left: 0.6rem;
width: 6px;
height: 6px;
border-radius: 50%;
}
.pip-legendary { background: var(--toucan); box-shadow: 0 0 8px var(--toucan); animation: legendaryPip 2s infinite; }
.pip-rare { background: var(--neon-teal); box-shadow: 0 0 6px var(--neon-teal); }
.pip-uncommon { background: var(--neon-green); }
.pip-damaged { background: var(--fire-coral); }
.pip-unique { background: var(--orchid); box-shadow: 0 0 8px var(--orchid); }
@keyframes legendaryPip {
0%, 100% { transform: scale(1); opacity: 0.8; }
50% { transform: scale(1.4); opacity: 1; }
}
.visit-btn {
font-family: 'Share Tech Mono', monospace;
font-size: 1rem;
padding: 0.6rem;
text-align: center;
background: transparent;
border: 1px solid var(--fire-amber);
color: var(--fire-amber);
text-decoration: none;
transition: all 150ms;
letter-spacing: 0.15em;
text-transform: uppercase;
}
.visit-btn:hover {
background: rgba(232,148,58,0.1);
box-shadow: 0 0 15px rgba(232,148,58,0.3);
text-shadow: 0 0 8px var(--fire-amber);
}
/* Back Link */
.back-link {
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-family: 'Share Tech Mono', monospace;
color: var(--text-muted);
text-decoration: none;
margin-bottom: 1.5rem;
transition: color 150ms;
letter-spacing: 0.1em;
}
.back-link:hover { color: var(--neon-green); }
/* Canvas and FX */
#embers {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 3;
}
.campfire-glow {
position: fixed;
bottom: -100px;
left: 50%;
transform: translateX(-50%);
width: 600px;
height: 400px;
background: radial-gradient(ellipse at center bottom, rgba(232,148,58,0.12) 0%, transparent 70%);
pointer-events: none;
z-index: 0;
animation: glowPulse 4s ease-in-out infinite;
}
@keyframes glowPulse {
0%, 100% { opacity: 0.5; }
50% { opacity: 0.9; transform: translateX(-50%) scale(1.05); }
}
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: var(--bg-deep); }
::-webkit-scrollbar-thumb { background: var(--text-muted); }
</style>
</head>
<body>
<div class="noise"></div>
<div class="campfire-glow"></div>
<canvas id="embers"></canvas>
<div class="app">
<a href="../index.html" class="back-link" target="_top">← SPACE</a>
<header class="header">
<div class="header-left">
<div class="unit-tag">▶ FIELD UNIT SP-07 // TOOLS N TOYS</div>
<h1 class="title">TOOLS N TOYS</h1>
<div class="subtitle">ORCHESTRATION & UTILITY HUB</div>
</div>
<div class="header-right">
<div class="status-badge">● LOCAL_ACTIVE</div>
<div class="coord">SECTOR: HUB-07</div>
<div class="coord">SYNC: REALTIME</div>
</div>
</header>
<div class="stats-bar">
<div class="stat-card">
<div class="stat-label">SECTIONS</div>
<div class="stat-value">5</div>
<div class="stat-sub">ACTIVE MODULES</div>
<div class="stat-bar-track"><div class="stat-bar-fill" style="width:100%"></div></div>
</div>
<div class="stat-card">
<div class="stat-label">TOOLS</div>
<div class="stat-value">57</div>
<div class="stat-sub">COLLECTED ITEMS</div>
<div class="stat-bar-track"><div class="stat-bar-fill" style="width:85%"></div></div>
</div>
<div class="stat-card">
<div class="stat-label">GUIDES</div>
<div class="stat-value">6</div>
<div class="stat-sub">FIELD MANUALS</div>
<div class="stat-bar-track"><div class="stat-bar-fill" style="width:60%"></div></div>
</div>
<div class="stat-card">
<div class="stat-label">EDU TOYS</div>
<div class="stat-value">4</div>
<div class="stat-sub">LEGACY ARTIFACTS</div>
<div class="stat-bar-track"><div class="stat-bar-fill" style="width:40%; background:var(--fire-coral); box-shadow:0 0 6px var(--fire-coral);"></div></div>
</div>
</div>
<div class="main-grid">
<aside class="sidebar">
<div class="panel">
<div class="panel-header">SECTIONS</div>
<div class="panel-body">
<div class="category-list">
<div class="cat-item active">
<span>ALL</span>
<span class="cat-count">5</span>
</div>
<div class="cat-item">
<span>JL'S TOOLS</span>
<span class="cat-count">5</span>
</div>
<div class="cat-item">
<span>FOSS ARSENAL</span>
<span class="cat-count">50+</span>
</div>
<div class="cat-item">
<span>FIELD MANUALS</span>
<span class="cat-count">6</span>
</div>
<div class="cat-item">
<span>EDU TOYS</span>
<span class="cat-count">4</span>
</div>
<div class="cat-item">
<span>CURIO</span>
<span class="cat-count">1</span>
</div>
</div>
</div>
</div>
<div class="panel">
<div class="panel-header">FIELD STATUS</div>
<div class="panel-body">
<div class="status-line">
<span style="color:var(--text-muted)">CONNECTIVITY</span>
<span style="color:var(--neon-green)">LOCAL</span>
</div>
<div class="status-line">
<span style="color:var(--text-muted)">LAST SYNC</span>
<span style="color:var(--fire-amber)">NOW</span>
</div>
<div class="status-line" style="margin-top:0.8rem">
<span style="color:var(--text-muted)">THREAT LEVEL</span>
<span style="color:var(--neon-green-dim)">MINIMAL</span>
</div>
</div>
</div>
</aside>
<main class="item-grid">
<!-- JL'S TOOLS -->
<div class="item-card legendary">
<div class="rarity-pip pip-legendary"></div>
<div class="item-img-zone">🔧</div>
<div class="item-body">
<div class="item-name">JL'S TOOLS</div>
<div class="item-meta">
<span>5 ITEMS</span>
<span style="color:var(--toucan)">LEGENDARY</span>
</div>
<p class="item-desc">early solutions to very niche problems</p>
<div class="condition-track"><div class="condition-fill" style="width:98%; background:var(--neon-green)"></div></div>
<a href="jl-early-tools.html" class="visit-btn">VISIT</a>
</div>
</div>
<!-- FOSS ARSENAL -->
<div class="item-card rare">
<div class="rarity-pip pip-rare"></div>
<div class="item-img-zone">🛡</div>
<div class="item-body">
<div class="item-name">FOSS ARSENAL</div>
<div class="item-meta">
<span>50+ ITEMS</span>
<span style="color:var(--neon-teal)">RARE</span>
</div>
<p class="item-desc">Free and open weapons for builders, makers, and tinkerers.</p>
<div class="condition-track"><div class="condition-fill" style="width:85%; background:var(--neon-teal)"></div></div>
<a href="#" class="visit-btn">VISIT</a>
</div>
</div>
<!-- FIELD MANUALS -->
<div class="item-card uncommon">
<div class="rarity-pip pip-uncommon"></div>
<div class="item-img-zone">📋</div>
<div class="item-body">
<div class="item-name">FIELD MANUALS</div>
<div class="item-meta">
<span>6 ITEMS</span>
<span style="color:var(--neon-green)">UNCOMMON</span>
</div>
<p class="item-desc">Step-by-step guides for Linux, Docker, AppImage, and streaming.</p>
<div class="condition-track"><div class="condition-fill" style="width:92%; background:var(--neon-green)"></div></div>
<a href="#" class="visit-btn">VISIT</a>
</div>
</div>
<!-- EDU TOYS -->
<div class="item-card damaged">
<div class="rarity-pip pip-damaged"></div>
<div class="item-img-zone">🧪</div>
<div class="item-body">
<div class="item-name">EDU TOYS</div>
<div class="item-meta">
<span>4 ITEMS</span>
<span style="color:var(--fire-coral)">DAMAGED</span>
</div>
<p class="item-desc">Artifacts from an early course prototype. Janky but preserved.</p>
<div class="condition-track"><div class="condition-fill" style="width:35%; background:var(--fire-coral)"></div></div>
<a href="#" class="visit-btn">VISIT</a>
</div>
</div>
<!-- UNIQUE CURIO -->
<div class="item-card unique">
<div class="rarity-pip pip-unique"></div>
<div class="item-img-zone">🕸</div>
<div class="item-body">
<div class="item-name">DENDRITIC CURIO</div>
<div class="item-meta">
<span>1 ITEM</span>
<span style="color:var(--orchid)">UNIQUE</span>
</div>
<p class="item-desc">An early Sonnet 4 experiment. A curiosity from the beginning of the agentic age.</p>
<div class="condition-track"><div class="condition-fill" style="width:100%; background:var(--orchid)"></div></div>
<a href="#" class="visit-btn">VISIT</a>
</div>
</div>
</main>
</div>
</div>
<script>
/* === EMBER PARTICLE SYSTEM (Night Sky Adaptation) === */
const canvas = document.getElementById('embers');
const ctx = canvas.getContext('2d');
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resize();
window.addEventListener('resize', resize);
const EMBER_COUNT = 60;
const embers = [];
const mouse = { x: -9999, y: -9999 };
window.addEventListener('mousemove', ev => { mouse.x = ev.clientX; mouse.y = ev.clientY; });
const ZONES = [
{ r: 40, strength: 0.012 },
{ r: 90, strength: 0.004 },
{ r: 130, strength: 0.001 },
];
const COLORS = [
{ r: 232, g: 148, b: 58 }, // fire-amber
{ r: 212, g: 101, b: 74 }, // fire-coral
{ r: 255, g: 207, b: 64 }, // toucan
{ r: 197, g: 88, b: 217 }, // orchid
];
function randomEmber() {
const col = COLORS[Math.floor(Math.random() * COLORS.length)];
return {
x: Math.random() * canvas.width,
y: canvas.height + 10,
vx: (Math.random() - 0.5) * 0.8,
vy: -(0.4 + Math.random() * 1.2),
life: 0,
maxLife: 200 + Math.random() * 300,
size: 1 + Math.random() * 2,
r: col.r, g: col.g, b: col.b,
wobble: Math.random() * Math.PI * 2,
wobbleSpeed: 0.02 + Math.random() * 0.02,
trail: [],
};
}
for (let i = 0; i < EMBER_COUNT; i++) {
const e = randomEmber();
e.y = Math.random() * canvas.height;
e.life = Math.random() * e.maxLife;
embers.push(e);
}
function drawEmber(e) {
const progress = e.life / e.maxLife;
const alpha = progress < 0.1 ? progress / 0.1 : progress > 0.8 ? (1 - progress) / 0.2 : 1;
if (alpha <= 0) return;
const gAlpha = alpha * 0.38; // Dimmed as per mandate
const glow = ctx.createRadialGradient(e.x, e.y, 0, e.x, e.y, e.size * 5);
glow.addColorStop(0, `rgba(${e.r},${e.g},${e.b},${gAlpha})`);
glow.addColorStop(1, `rgba(${e.r},${e.g},${e.b},0)`);
ctx.beginPath();
ctx.arc(e.x, e.y, e.size * 5, 0, Math.PI * 2);
ctx.fillStyle = glow;
ctx.fill();
// core pixel
ctx.fillStyle = `rgba(${e.r},${e.g},${e.b},${Math.min(0.65, gAlpha * 1.4)})`;
const ps = Math.max(1, Math.round(e.size));
ctx.fillRect(Math.round(e.x) - ps, Math.round(e.y) - ps, ps * 2, ps * 2);
for (let t = 0; t < e.trail.length; t++) {
const pt = e.trail[t];
const ta = (t / e.trail.length) * gAlpha * 0.3;
ctx.fillStyle = `rgba(${e.r},${e.g},${e.b},${ta})`;
ctx.fillRect(Math.round(pt.x), Math.round(pt.y), 2, 2);
}
}
function tick() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (const e of embers) {
if (e.life % 6 === 0) {
e.trail.push({ x: e.x, y: e.y });
if (e.trail.length > 5) e.trail.shift();
}
const dx = mouse.x - e.x, dy = mouse.y - e.y, d = Math.sqrt(dx*dx+dy*dy);
if (d < ZONES[2].r) {
let s = 0;
for(const z of ZONES) if(d < z.r) { s = z.strength; break; }
e.vx += (dx/d)*s; e.vy += (dy/d)*s;
e.vx *= 0.98; e.vy *= 0.98;
}
e.wobble += e.wobbleSpeed;
e.x += e.vx + Math.sin(e.wobble) * 0.3;
e.y += e.vy;
e.life++;
if (e.life >= e.maxLife || e.y < -20) Object.assign(e, randomEmber());
drawEmber(e);
}
requestAnimationFrame(tick);
}
tick();
// Simple category switching logic
const catItems = document.querySelectorAll('.cat-item');
catItems.forEach(item => {
item.addEventListener('click', () => {
catItems.forEach(c => c.classList.remove('active'));
item.classList.add('active');
});
});
</script>
</body>
</html>