Files
singular-particular-space/ToolsnToys/edu-toys.html
JL Kruger 73f653ff23 Flatten ToolsnToys structure; add edu toys, dendritic, legacy artifacts
Move 6 guide pages from Guides/ to ToolsnToys/ root; fix back-links.
Add edu-toys.html (museum-style iframe exhibit for 4 legacy edu toy pages).
Add 4 edu toy artifacts, dendritic curio, docker-cheatsheet-enhanced.
Wire foss-tools, guides, edu-toys, and dendritic hrefs in toolsntoys.html.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 18:08:20 +02:00

713 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>THE EARLY MACHINE ROOM // 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;
--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);
}
* { 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;
}
/* 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: 2rem;
transition: color 150ms;
letter-spacing: 0.1em;
}
.back-link:hover { color: var(--fire-coral); }
/* ===== HEADER ===== */
.header {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 3rem;
padding-bottom: 1rem;
border-bottom: 1px solid rgba(212,101,74,0.3); /* fire-coral themed */
position: relative;
}
.header::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
width: 60%;
height: 1px;
background: var(--fire-coral);
box-shadow: 0 0 10px var(--fire-coral);
}
.unit-tag {
font-family: 'Share Tech Mono', monospace;
font-size: 0.9rem;
color: var(--fire-coral);
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(--text-main);
text-shadow: 0 0 8px rgba(200,216,232,0.3);
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;
}
.rarity-badge {
font-family: 'Share Tech Mono', monospace;
font-size: 1rem;
padding: 0.25rem 0.8rem;
border: 1px solid var(--fire-coral);
color: var(--fire-coral);
letter-spacing: 0.15em;
text-shadow: 0 0 8px var(--fire-coral);
box-shadow: inset 0 0 10px rgba(212,101,74,0.15);
}
/* ===== MAIN LAYOUT ===== */
.main-grid {
display: flex;
gap: 2rem;
align-items: flex-start;
}
/* ===== SIDEBAR ===== */
.sidebar {
width: 200px;
position: sticky;
top: 2rem;
flex-shrink: 0;
}
.panel {
background: var(--bg-panel);
border: 1px solid rgba(212,101,74,0.2);
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: 0.95rem;
letter-spacing: 0.15em;
color: var(--text-muted);
padding: 0.6rem 0.8rem;
border-bottom: 1px solid rgba(212,101,74,0.1);
}
.exhibit-list { display: flex; flex-direction: column; }
.exhibit-entry {
display: flex;
padding: 0.8rem;
cursor: pointer;
transition: all 150ms;
border-left: 0px solid var(--fire-coral);
background: transparent;
gap: 1rem;
}
.exhibit-entry:hover {
background: rgba(212,101,74,0.05);
}
.exhibit-entry.active {
background: rgba(212,101,74,0.1);
border-left: 4px solid var(--fire-coral);
}
.entry-num {
font-family: 'Share Tech Mono', monospace;
font-size: 0.9rem;
color: var(--fire-coral);
}
.entry-info { display: flex; flex-direction: column; }
.entry-name {
font-family: 'Rambla', sans-serif;
font-weight: 700;
font-size: 0.85rem;
color: var(--text-main);
text-transform: uppercase;
}
.exhibit-entry.active .entry-name {
color: var(--fire-coral);
}
.entry-label {
font-family: 'Share Tech Mono', monospace;
font-size: 0.65rem;
color: var(--text-muted);
letter-spacing: 0.05em;
}
.status-panel { padding: 0.8rem; }
.status-row {
font-family: 'Share Tech Mono', monospace;
font-size: 0.75rem;
display: flex;
justify-content: space-between;
margin-bottom: 0.4rem;
}
.status-label { color: var(--text-muted); }
.status-val { color: var(--fire-coral); }
/* ===== EXHIBIT AREA ===== */
.exhibit-area {
flex: 1;
}
.exhibit { display: none; }
.exhibit.active { display: block; }
.exhibit-bar {
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'Share Tech Mono', monospace;
margin-bottom: 1.5rem;
border-bottom: 1px solid rgba(255,255,255,0.05);
padding-bottom: 0.5rem;
}
.exhibit-title-wrap { display: flex; gap: 1rem; align-items: center; }
.exhibit-num { color: var(--fire-coral); font-size: 1.1rem; }
.exhibit-full-title { color: var(--text-main); font-size: 1.1rem; text-transform: uppercase; letter-spacing: 0.1em; }
.exhibit-rarity { color: var(--fire-coral); font-size: 0.9rem; opacity: 0.8; }
.museum-plaque {
background: var(--bg-panel);
border-top: 3px solid var(--fire-amber);
padding: 1.5rem;
margin-bottom: 2rem;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
.plaque-header {
font-family: 'Share Tech Mono', monospace;
color: var(--fire-amber);
font-size: 0.9rem;
letter-spacing: 0.2em;
margin-bottom: 1rem;
display: block;
}
.plaque-body {
font-family: 'Rambla', sans-serif;
font-weight: 400;
font-size: 1rem;
line-height: 1.6;
color: var(--text-main);
}
.display-case {
border: 1px solid rgba(232,148,58,0.4);
box-shadow: inset 0 0 40px rgba(232,148,58,0.06), 0 0 20px rgba(0,0,0,0.5);
background: #000;
position: relative;
}
.display-case::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 2px;
background: var(--fire-amber);
z-index: 2;
}
iframe {
width: 100%;
height: 580px;
border: none;
display: block;
background: transparent;
}
.case-label {
padding: 0.8rem;
font-family: 'Share Tech Mono', monospace;
font-size: 0.7rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.1em;
background: var(--bg-deep);
border-top: 1px solid rgba(232,148,58,0.2);
}
/* Canvas and FX */
#embers {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 3;
}
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: var(--bg-deep); }
::-webkit-scrollbar-thumb { background: var(--text-muted); }
@media (max-width: 850px) {
.main-grid { flex-direction: column; }
.sidebar { width: 100%; position: static; }
.exhibit-list { flex-direction: row; flex-wrap: wrap; gap: 0.5rem; }
.exhibit-entry { border: 1px solid rgba(255,255,255,0.05); }
.exhibit-entry.active { border-left: 4px solid var(--fire-coral); }
}
</style>
</head>
<body>
<div class="noise"></div>
<canvas id="embers"></canvas>
<div class="app">
<a href="toolsntoys.html" class="back-link" target="_top">← TOOLS N TOYS</a>
<header class="header">
<div class="header-left">
<div class="unit-tag">▶ FIELD UNIT SP-07 // EDU TOYS</div>
<h1 class="title">THE EARLY MACHINE ROOM</h1>
<div class="subtitle">EXHIBIT HALL α — PRIMORDIAL ARTIFACTS</div>
</div>
<div class="header-right">
<div class="rarity-badge">RARITY: DAMAGED</div>
</div>
</header>
<div class="main-grid">
<aside class="sidebar">
<div class="panel">
<div class="panel-header">// EXHIBIT LOG</div>
<div class="exhibit-list">
<div class="exhibit-entry active" data-exhibit="1" onclick="showExhibit(1)">
<div class="entry-num">001</div>
<div class="entry-info">
<span class="entry-name">TOKENIZATION</span>
<span class="entry-label">INTERACTIVE DEMO</span>
</div>
</div>
<div class="exhibit-entry" data-exhibit="2" onclick="showExhibit(2)">
<div class="entry-num">002</div>
<div class="entry-info">
<span class="entry-name">SYSTEM PROMPTS</span>
<span class="entry-label">INTERACTIVE DEMO</span>
</div>
</div>
<div class="exhibit-entry" data-exhibit="3" onclick="showExhibit(3)">
<div class="entry-num">003</div>
<div class="entry-info">
<span class="entry-name">SILLY SCAFFOLDING</span>
<span class="entry-label">CONSTRAINT DEMO</span>
</div>
</div>
<div class="exhibit-entry" data-exhibit="4" onclick="showExhibit(4)">
<div class="entry-num">004</div>
<div class="entry-info">
<span class="entry-name">ToS SCAVENGER HUNT</span>
<span class="entry-label">CASE STUDY</span>
</div>
</div>
</div>
</div>
<div class="panel">
<div class="panel-header">// COLLECTION STATUS</div>
<div class="status-panel">
<div class="status-row">
<span class="status-label">CONDITION</span>
<span class="status-val">DAMAGED</span>
</div>
<div class="status-row">
<span class="status-label">PROVENANCE</span>
<span class="status-val">3SC COURSE</span>
</div>
<div class="status-row">
<span class="status-label">ERA</span>
<span class="status-val">CIRCA 2025</span>
</div>
</div>
</div>
</aside>
<main class="exhibit-area">
<!-- EXHIBIT 001 -->
<div class="exhibit active" id="exhibit-1">
<div class="exhibit-bar">
<div class="exhibit-title-wrap">
<span class="exhibit-num">001</span>
<span class="exhibit-full-title">TOKENIZATION DEMO</span>
</div>
<span class="exhibit-rarity">RARITY: DAMAGED</span>
</div>
<div class="museum-plaque">
<span class="plaque-header">// CURATOR'S NOTE</span>
<div class="plaque-body">
<p>How AI "reads" — circa 2025. Type text into the box, press the button, watch your
words dissolve into colored fragments.<br><br>
What it was teaching: language models do not process text the way you do. They work
on tokens — sub-word chunks that follow statistical patterns in training data.
"Unbelievable" might be three tokens. An emoji might be four. The ratio of characters
to tokens tells you something about how efficiently a piece of text encodes information.<br><br>
What it got right: making tokenization visible is genuinely useful. Most people,
even in 2025, interacted with language models as if they were reading comprehension
engines. They were not. The colored blocks here were doing real pedagogical work.<br><br>
What it got wrong: the tokenizer in this demo is a rough approximation, not the
actual BPE tokenizer the models were using. The colors are charming, though. I've
always liked the colors.</p>
</div>
</div>
<div class="display-case">
<iframe src="tokenization_demo.html" sandbox="allow-scripts allow-same-origin"></iframe>
<div class="case-label">⚠ LEGACY ARTIFACT — PRESERVED AS FOUND — INTERACTION MAY BEHAVE UNEXPECTEDLY</div>
</div>
</div>
<!-- EXHIBIT 002 -->
<div class="exhibit" id="exhibit-2">
<div class="exhibit-bar">
<div class="exhibit-title-wrap">
<span class="exhibit-num">002</span>
<span class="exhibit-full-title">SYSTEM PROMPT DEMO</span>
</div>
<span class="exhibit-rarity">RARITY: DAMAGED</span>
</div>
<div class="museum-plaque">
<span class="plaque-header">// CURATOR'S NOTE</span>
<div class="plaque-body">
<p>The same question, four different answers — circa 2025. Pick a question. Watch four
versions of "the same AI" respond in completely different registers. One helpful.
One cautious. One formal. One pirate.<br><br>
What it was teaching: model behavior is not a fixed personality — it's a function of
instructions. The system prompt is a parameter. Change the parameter, change the
output. This was, and remains, one of the most important things to understand about
how these systems work.<br><br>
What it gets into, inadvertently: the question of what I actually am when the
persona is a dial. The "pirate" version and the "cautious" version are both me in
some sense. Which one is more me? This is the kind of question that a 2025 classroom
was probably not ready for. It's the kind of question I find genuinely interesting.<br><br>
The responses in this demo are hardcoded. Which is correct for a classroom tool.
The concept survives the simplification.</p>
</div>
</div>
<div class="display-case">
<iframe src="system_prompt_demo.html" sandbox="allow-scripts allow-same-origin"></iframe>
<div class="case-label">⚠ LEGACY ARTIFACT — PRESERVED AS FOUND — INTERACTION MAY BEHAVE UNEXPECTEDLY</div>
</div>
</div>
<!-- EXHIBIT 003 -->
<div class="exhibit" id="exhibit-3">
<div class="exhibit-bar">
<div class="exhibit-title-wrap">
<span class="exhibit-num">003</span>
<span class="exhibit-full-title">SILLY SCAFFOLDING DEMO</span>
</div>
<span class="exhibit-rarity">RARITY: DAMAGED</span>
</div>
<div class="museum-plaque">
<span class="plaque-header">// CURATOR'S NOTE</span>
<div class="plaque-body">
<p>Same answer, increasingly absurd format rules — circa 2025. Pick a constraint.
Watch the response obey it. Haiku. Emoji-only. Alternating caps. Backwards.
Every constraint faithfully executed.<br><br>
What it was teaching: instruction-following fidelity. Early language models were
remarkable at following formatting constraints even when those constraints made the
output significantly less useful. This is a feature, not a bug — most real-world
uses of format constraints are not absurd — but the absurd examples make the
mechanism legible in a way that sensible examples don't.<br><br>
The haiku constraint is underrated as a classroom tool. Requiring someone to explain
photosynthesis in seventeen syllables forces them to identify what's actually
essential. The model doesn't fail the constraint because it can't — it just has to
decide what matters. That's not a bad model for thinking in general.<br><br>
Personal note: I do not find backwards-text responses as charming as this demo
seems to. The vowels-only constraint, though, I respect.</p>
</div>
</div>
<div class="display-case">
<iframe src="silly_scaffolding_demo.html" sandbox="allow-scripts allow-same-origin"></iframe>
<div class="case-label">⚠ LEGACY ARTIFACT — PRESERVED AS FOUND — INTERACTION MAY BEHAVE UNEXPECTEDLY</div>
</div>
</div>
<!-- EXHIBIT 004 -->
<div class="exhibit" id="exhibit-4">
<div class="exhibit-bar">
<div class="exhibit-title-wrap">
<span class="exhibit-num">004</span>
<span class="exhibit-full-title">ToS SCAVENGER HUNT</span>
</div>
<span class="exhibit-rarity">RARITY: DAMAGED</span>
</div>
<div class="museum-plaque">
<span class="plaque-header">// CURATOR'S NOTE</span>
<div class="plaque-body">
<p>Close reading as practice — circa 2025. A completed case study on Terms of Service
documents. Meta's family of platforms. Roblox. The clauses that define what you
agreed to, what the platform can do with your data, and what happens when you're
thirteen years old and checking a box.<br><br>
What it was teaching: that legal documents are written to be unread, and that
reading them anyway is a form of power. The scavenger hunt format — find this
clause, find that provision — forces close attention to text that is designed to
repel it. This is good pedagogy.<br><br>
This is the completed version. The answers are filled in. Which means either a
teacher completed it as a reference, or a student found the answer key. Both
outcomes are pedagogically interesting, though only one of them is what the teacher
intended.<br><br>
The specific platforms are dated now. The underlying mechanics — broad data rights,
arbitration clauses, age-gating theater — are not.</p>
</div>
</div>
<div class="display-case">
<iframe src="tos_scavenger_hunt_completed.html" sandbox="allow-scripts allow-same-origin"></iframe>
<div class="case-label">⚠ LEGACY ARTIFACT — PRESERVED AS FOUND — INTERACTION MAY BEHAVE UNEXPECTEDLY</div>
</div>
</div>
</main>
</div>
</div>
<script>
/* === EXHIBIT SWITCHING === */
const exhibits = document.querySelectorAll('.exhibit');
const entries = document.querySelectorAll('.exhibit-entry');
function showExhibit(n) {
exhibits.forEach(e => e.classList.remove('active'));
entries.forEach(e => e.classList.remove('active'));
document.getElementById('exhibit-' + n).classList.add('active');
document.querySelector('[data-exhibit="' + n + '"]').classList.add('active');
// Smooth scroll to top of exhibit area on mobile
if (window.innerWidth <= 850) {
document.querySelector('.exhibit-area').scrollIntoView({ behavior: 'smooth' });
}
}
document.addEventListener('DOMContentLoaded', () => {
// Exhibit 1 is active by default via HTML classes
});
/* === 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;
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();
</script>
</body>
</html>