Some code survived. The rest became documentation.
My earliest foray into coding began with the purchase of a Commodore 64 and an Amiga 500 – a time when loading screens were an exercise in sheer patience. Although my career is not strictly centred around developing software myself, grasping the mechanics of code has always been a priority: understanding its functions, its potential, and its limitations.
The modest experiments compiled here are mostly concerned with quite ordinary web frontend matters – understated interface concepts, effects, animations, and snippets that others may also find beneficial. I have personally forked, modified, and learnt from an endless array of code snippets, a habit I have maintained ever since.
The more substantial computer science subjects are paused for now; they will eventually cover solution architectures and the insights gained from deployment and operations in enterprise organisations.
Until then: „10 PRINT CHR$(205.5+RND(1)); : GOTO 10“.
Magic Scroll_01
Retro-inspired, tilted marquees come to life here, blending 3D-perspective CSS transforms with dynamic pixel-art icons. A JavaScript engine orchestrates randomized movement and timing, ensuring a continuous, visual flow.
<div class="space-scene">
<div class="tilt-wrapper">
<div class="css-grid-bg"></div>
<div class="m-marquee-stack">
<div class="m-row">
<div class="m-scroll speed-fast" data-text="DYNAMIC DUO * THE HUMBLE GUYS * EAGLE SOFT INC * FAIRLIGHT * TRIAD * THE RED SECTOR INC * THE LIGHT CIRCLE * IKARI * RADWAR * 711 * ABC"></div>
<div class="scanner-wrap"><div class="scanner-beam gold-glow s-very-fast"></div></div>
</div>
<div class="m-row row-middle-border">
<div class="m-scroll speed-med" data-text="ANTIC * THE SHADOWS * THE JOKER CREW * SNATCH N' CRACK * THE VIZZION * BEASTIE BOYS * THE ACTION GROUP * THE GREAT COUNCIL * X-RATED"></div>
<div class="scanner-wrap"><div class="scanner-beam gold-glow s-med"></div></div>
</div>
<div class="m-row">
<div class="m-scroll speed-fast" data-text="ROB HUBBARD * CHRIS HÜLSBECK * MARTIN GALWAY * JEROEN TEL * CHARLES DEENEN * JEFF MINTER * THE SARGE * ZZAP * MR. Z * ILLEGAL * THE JUDGES"></div>
<div class="scanner-wrap"><div class="scanner-beam gold-glow s-fast"></div></div>
</div>
<div id="icon-container" style="position: absolute; inset: 0; z-index: 50; pointer-events: none; overflow: hidden;">
<div class="pixel-alien" id="alien1"></div>
<div class="pixel-alien" id="alien2"></div>
<div class="pixel-alien" id="alien3"></div>
</div>
</div>
</div>
</div>
<style>
/* --- BASIS & GRID --- */
.space-scene {
position: relative; width: 100%; height: 500px; background: #fff;
overflow: hidden; perspective: 1200px; display: flex; align-items: center; justify-content: center;
}
.tilt-wrapper { position: relative; width: 130%; transform: rotateX(25deg) rotateY(-5deg) rotateZ(-2deg); transform-style: preserve-3d; }
.css-grid-bg {
position: absolute; top: -80%; left: -50%; width: 200%; height: 260%;
background-image: linear-gradient(rgba(180,180,180,0.3) 1px, transparent 1px), linear-gradient(90deg, rgba(180,180,180,0.3) 1px, transparent 1px);
background-size: 50px 50px; z-index: 1;
mask-image: linear-gradient(to bottom, transparent 0%, black 20%, black 80%, transparent 100%);
}
/* --- MARQUEE --- */
.m-marquee-stack { position: relative; z-index: 10; width: 100%; display: flex; flex-direction: column; background: #fff; box-shadow: 0 20px 50px rgba(0,0,0,0.08); }
.m-row { position: relative; width: 100%; overflow: hidden; display: flex; padding: 20px 0; background: #fff; }
.row-middle-border { border-top: 2px solid #000; border-bottom: 2px solid #000; }
.m-scroll { display: flex; white-space: nowrap; width: max-content; }
.m-scroll span { display: inline-block; font-family: "Arial Black", sans-serif; font-size: 44px; font-weight: 900; text-transform: uppercase; color: #000; line-height: 1; }
.char-separator { color: #d5ab4a; font-size: 28px !important; margin: 0 30px; }
/* --- SCANNER --- */
.scanner-wrap { position: absolute; inset: 0; z-index: 3; pointer-events: none; mix-blend-mode: screen; }
.gold-glow { position: absolute; top: 0; width: 60%; height: 100%; background: linear-gradient(90deg, transparent 0%, rgba(213, 171, 74, 0.4) 20%, rgba(213, 171, 74, 0.9) 50%, rgba(213, 171, 74, 0.4) 80%, transparent 100%); }
.s-very-fast { animation: laserMove 1.5s ease-in-out infinite alternate; }
.s-fast { animation: laserMove 2s ease-in-out infinite alternate; }
.s-med { animation: laserMove 3s ease-in-out infinite alternate; }
@keyframes laserMove { 0% { left: -60%; } 100% { left: 100%; } }
/* --- ICONS --- */
.pixel-alien {
position: absolute;
z-index: 20;
font-size: 44px;
filter: drop-shadow(0 5px 10px rgba(0,0,0,0.1));
pointer-events: none;
opacity: 0;
top: 0;
left: 0;
will-change: transform, opacity;
}
.speed-fast { animation: scrollLeft 40s linear infinite; }
.speed-med { animation: scrollRight 30s linear infinite; }
.rev { animation-direction: reverse; }
@keyframes scrollLeft { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@keyframes scrollRight { from { transform: translateX(-50%); } to { transform: translateX(0); } }
@media (max-width: 767px) {
.m-scroll span { font-size: 26px; }
.char-separator { font-size: 18px !important; margin: 0 15px; }
.pixel-alien { font-size: 32px; }
}
</style>
<script>
const symbols = ['🚀', '🛸', '👾', '🌟', '⭐', '🍹'];
const aliens = document.querySelectorAll('.pixel-alien');
const stackEl = document.querySelector('.m-marquee-stack');
const easings = ['linear', 'ease-in-out', 'ease-in', 'ease-out', 'cubic-bezier(0.4, 0, 0.6, 1)'];
// Globaler State: wer fliegt gerade?
const MAX_CONCURRENT = 2;
const flying = new Set();
function buildKeyframes(stackWidth, stackHeight, iconSize) {
const maxY = Math.max(0, stackHeight - iconSize);
const ry = () => Math.random() * maxY;
const rr = () => Math.floor(Math.random() * 720 - 360);
const ltr = Math.random() < 0.5;
const startX = ltr ? -120 : stackWidth + 120;
const endX = ltr ? stackWidth + 120 : -120;
const numMid = 2 + Math.floor(Math.random() * 4);
const kf = [
{ transform: `translate3d(${startX}px, ${ry()}px, 0) rotate(0deg)`, opacity: 0, offset: 0 }
];
for (let i = 1; i <= numMid; i++) {
const t = i / (numMid + 1);
const jitter = (Math.random() - 0.5) * 0.15;
const x = startX + (endX - startX) * (t + jitter);
kf.push({
transform: `translate3d(${x}px, ${ry()}px, 0) rotate(${rr()}deg)`,
opacity: 1,
offset: t
});
}
kf.push({
transform: `translate3d(${endX}px, ${ry()}px, 0) rotate(${rr()}deg)`,
opacity: 0,
offset: 1
});
return kf;
}
function launchAlien(alien) {
if (!stackEl) return;
// Wenn schon 2 fliegen, kurz warten und nochmal versuchen
if (flying.size >= MAX_CONCURRENT) {
setTimeout(() => launchAlien(alien), 600 + Math.random() * 1400);
return;
}
flying.add(alien);
const stackWidth = stackEl.offsetWidth;
const stackHeight = stackEl.offsetHeight;
const isMobile = window.innerWidth <= 767;
const iconSize = isMobile ? 32 : 44;
alien.textContent = symbols[Math.floor(Math.random() * symbols.length)];
const keyframes = buildKeyframes(stackWidth, stackHeight, iconSize);
const duration = 3000 + Math.random() * 10000;
const easing = easings[Math.floor(Math.random() * easings.length)];
const anim = alien.animate(keyframes, { duration, easing, fill: 'forwards' });
anim.onfinish = () => {
flying.delete(alien);
// Pause: 2–10 s
const nextGap = 2000 + Math.random() * 8000;
setTimeout(() => launchAlien(alien), nextGap);
};
}
function initIcons() {
aliens.forEach((alien, index) => {
const initialDelay = 800 + Math.random() * 2500 + index * 1500;
setTimeout(() => launchAlien(alien), initialDelay);
});
}
if (document.readyState === 'complete') {
initIcons();
} else {
window.addEventListener('load', initIcons);
}
document.querySelectorAll('.m-scroll').forEach(track => {
const rawText = track.getAttribute('data-text');
const build = () => {
const wrap = document.createElement('div');
wrap.style.display = 'flex';
wrap.style.alignItems = 'center';
rawText.split(/(\s\*\s)/).forEach(p => {
const s = document.createElement('span');
if (p === " * ") {
s.className = 'char-separator';
s.textContent = "★";
} else {
s.textContent = p.replace(/\s/g, '\u00A0');
}
wrap.appendChild(s);
});
return wrap;
};
track.innerHTML = '';
track.appendChild(build());
track.appendChild(build());
});
</script>
Magic Scroll_02
Vibrant, retro-inspired marquees come to life here through neon-glowing text across three distinct rows. CSS keyframe animations drive continuous, multi-directional scrolling, recreating the high-energy aesthetic of classic demo-scene software.
<style>
/* Behållare för hela sektionen __gpoh */
.marquee-container {
background: #000;
padding: 0px 0;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Grundläggande styling för alla rader __gpoh */
.marquee-row {
display: flex;
overflow: hidden;
line-height: 1.2;
padding: 15px 0;
}
/* Endast mittraden får linjer __gpoh */
.row-2 {
border-top: 1px solid #fff; /* Linje överst __gpoh */
border-bottom: 1px solid #fff; /* Linje underst __gpoh */
margin: 5px 0; /* Marginal för utseende __gpoh */
}
.marquee-content {
display: flex;
white-space: nowrap;
}
/* Neon-design för objekten __gpoh */
.marquee-item {
display: flex;
align-items: center;
flex-shrink: 0;
font-weight: 900;
font-family: sans-serif;
font-size: 1.3rem;
padding-right: 30px;
letter-spacing: 1px;
text-transform: uppercase;
/* Vertikal flödeseffekt __gpoh */
-webkit-background-clip: text;
background-clip: text;
background-size: 100% 200%;
animation: flow-vertical 3s linear infinite;
}
/* Logik för neonfärger __gpoh */
.marquee-item:nth-child(5n+1) { background-color: #ff00ff; color: #ff00ff; filter: drop-shadow(0 0 10px #ff00ff); }
.marquee-item:nth-child(5n+2) { background-color: #00ffff; color: #00ffff; filter: drop-shadow(0 0 10px #00ffff); }
.marquee-item:nth-child(5n+3) { background-color: #39ff14; color: #39ff14; filter: drop-shadow(0 0 10px #39ff14); }
.marquee-item:nth-child(5n+4) { background-color: #ffff00; color: #ffff00; filter: drop-shadow(0 0 10px #ffff00); }
.marquee-item:nth-child(5n+5) { background-color: #ff3131; color: #ff3131; filter: drop-shadow(0 0 10px #ff3131); }
/* Den vita stjärnan __gpoh */
.marquee-item::after {
content: "★";
margin-left: 30px;
font-size: 0.9rem;
color: #fff;
-webkit-text-fill-color: #fff;
text-shadow: 0 0 12px #fff;
filter: none;
}
/* Animationer __gpoh */
@keyframes flow-vertical {
from { background-position: 0% 0%; }
to { background-position: 0% 200%; }
}
@keyframes scroll-left {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
/* Hastigheter och riktningar __gpoh */
.row-1 .marquee-content { animation: scroll-left 20s linear infinite; }
.row-2 .marquee-content { animation: scroll-left 90s linear infinite; animation-direction: reverse; }
.row-3 .marquee-content { animation: scroll-left 30s linear infinite; }
</style>
<div class="marquee-container">
<div class="marquee-row row-1">
<div class="marquee-content">
<div class="marquee-item">Fairlight</div><div class="marquee-item">Antidote</div><div class="marquee-item">Eagle Soft Inc.</div><div class="marquee-item">Scoopex</div><div class="marquee-item">Paradox</div><div class="marquee-item">Skid Row</div><div class="marquee-item">TRSI</div><div class="marquee-item">Dynamic Duo</div><div class="marquee-item">Ikari</div><div class="marquee-item">Red Sector</div><div class="marquee-item">Razor 1911</div><div class="marquee-item">Defjam</div><div class="marquee-item">Anarchy</div><div class="marquee-item">Vision Factory</div><div class="marquee-item">Oracle</div><div class="marquee-item">Quartex</div>
<div class="marquee-item">Fairlight</div><div class="marquee-item">Antidote</div><div class="marquee-item">Eagle Soft Inc.</div><div class="marquee-item">Scoopex</div><div class="marquee-item">Paradox</div><div class="marquee-item">Skid Row</div><div class="marquee-item">TRSI</div><div class="marquee-item">Dynamic Duo</div><div class="marquee-item">Ikari</div><div class="marquee-item">Red Sector</div><div class="marquee-item">Razor 1911</div><div class="marquee-item">Defjam</div><div class="marquee-item">Anarchy</div><div class="marquee-item">Vision Factory</div><div class="marquee-item">Oracle</div><div class="marquee-item">Quartex</div>
</div>
</div>
<div class="marquee-row row-2">
<div class="marquee-content">
<div class="marquee-item">NorthStar</div><div class="marquee-item">The Humble Guys</div><div class="marquee-item">Crystal</div><div class="marquee-item">Onslaught</div><div class="marquee-item">Legend</div><div class="marquee-item">Anthrox</div><div class="marquee-item">Genesis</div><div class="marquee-item">The Light Circle</div><div class="marquee-item">Cryptoburners</div><div class="marquee-item">Rebels</div><div class="marquee-item">Alpha Flight</div><div class="marquee-item">Dual Crew</div><div class="marquee-item">Shining</div><div class="marquee-item">Horizon</div>
<div class="marquee-item">NorthStar</div><div class="marquee-item">The Humble Guys</div><div class="marquee-item">Crystal</div><div class="marquee-item">Onslaught</div><div class="marquee-item">Legend</div><div class="marquee-item">Anthrox</div><div class="marquee-item">Genesis</div><div class="marquee-item">The Light Circle</div><div class="marquee-item">Cryptoburners</div><div class="marquee-item">Rebels</div><div class="marquee-item">Alpha Flight</div><div class="marquee-item">Dual Crew</div><div class="marquee-item">Shining</div><div class="marquee-item">Horizon</div>
</div>
</div>
<div class="marquee-row row-3">
<div class="marquee-content">
<div class="marquee-item">Success + TRC</div><div class="marquee-item">Triad</div><div class="marquee-item">1001 Crew</div><div class="marquee-item">Chromance</div><div class="marquee-item">M.A.D.</div><div class="marquee-item">Flash Inc.</div><div class="marquee-item">Hotline</div><div class="marquee-item">Bamiga Sector One</div><div class="marquee-item">Alcatraz</div><div class="marquee-item">Dominators</div><div class="marquee-item">ESI</div><div class="marquee-item">Section 8</div>
<div class="marquee-item">Success + TRC</div><div class="marquee-item">Triad</div><div class="marquee-item">1001 Crew</div><div class="marquee-item">Chromance</div><div class="marquee-item">M.A.D.</div><div class="marquee-item">Flash Inc.</div><div class="marquee-item">Hotline</div><div class="marquee-item">Bamiga Sector One</div><div class="marquee-item">Alcatraz</div><div class="marquee-item">Dominators</div><div class="marquee-item">ESI</div><div class="marquee-item">Section 8</div>
</div>
</div>
</div>
Magic Scroll_03
This digital tapestry weaves relentless streams of classic scene names into a hypnotic, liquid-distorted horizon. An SVG displacement engine breathes organic, surreal movement into the typography, transforming rigid text into a shifting dance of neon-edged nostalgia.
<style>
/* 1. Grundläggande konfiguration _gpoh */
.master-smooth-wrapper {
background: #ffffff;
padding: 30px 0;
overflow: hidden;
contain: paint;
}
/* 2. Statiskt SVG-filter - finjusterat för rena kanter _gpoh */
.warp-svg-layer {
position: absolute;
width: 0; height: 0;
visibility: hidden;
pointer-events: none;
}
.scroll-system {
display: flex;
flex-direction: column;
gap: 2px; /* Smalare, renare separation mellan raderna _gpoh */
width: 100%;
filter: url(#liquid-clean-warp);
transform: translateZ(0);
}
.scroll-row {
background: #000000;
overflow: hidden;
padding: 14px 0; /* Proportionell mot den större textstorleken _gpoh */
display: flex;
position: relative;
}
/* 3. Högpresterande spår _gpoh */
.scroll-track {
display: flex;
white-space: nowrap;
width: max-content;
will-change: transform;
animation: flow-linear linear infinite;
}
/* 4. Typsnitt och vita stjärnor _gpoh */
.scroll-item {
font-family: 'Arial Black', sans-serif;
font-weight: 900;
font-size: 2.2rem; /* Något större typsnitt _gpoh */
color: #ffffff;
padding: 0 45px;
text-transform: uppercase;
display: flex;
align-items: center;
line-height: 1;
letter-spacing: -0.5px;
}
.scroll-item::after {
content: "★";
margin-left: 70px;
font-size: 1.4rem;
color: #ffffff;
/* Subtil vit glöd för renhet _gpoh */
text-shadow: 0 0 8px rgba(255, 255, 255, 0.6);
}
/* Hastigheter _gpoh */
.row-1 { --speed: 18s; }
.row-2 { --speed: 25s; }
.row-3 { --speed: 38s; }
.row-1 .scroll-track { animation-duration: var(--speed); }
.row-2 .scroll-track { animation-duration: var(--speed); animation-direction: reverse; }
.row-3 .scroll-track { animation-duration: var(--speed); }
/* Animation _gpoh */
@keyframes flow-linear {
from { transform: translate3d(0, 0, 0); }
to { transform: translate3d(-50%, 0, 0); }
}
</style>
<svg class="warp-svg-layer">
<defs>
<filter id="liquid-clean-warp" color-interpolation-filters="sRGB">
<feTurbulence type="turbulence" baseFrequency="0.015 0.04" numOctaves="1" result="noise" seed="12" />
<feDisplacementMap in="SourceGraphic" in2="noise" scale="10" />
</filter>
</defs>
</svg>
<div class="master-smooth-wrapper">
<div class="scroll-system">
<div class="scroll-row row-1">
<div class="scroll-track">
<span class="scroll-item">Fairlight</span><span class="scroll-item">Antidote</span><span class="scroll-item">Scoopex</span><span class="scroll-item">Paradox</span><span class="scroll-item">Skid Row</span><span class="scroll-item">TRSI</span>
<span class="scroll-item">Fairlight</span><span class="scroll-item">Antidote</span><span class="scroll-item">Scoopex</span><span class="scroll-item">Paradox</span><span class="scroll-item">Skid Row</span><span class="scroll-item">TRSI</span>
</div>
</div>
<div class="scroll-row row-2">
<div class="scroll-track">
<span class="scroll-item">NorthStar</span><span class="scroll-item">Crystal</span><span class="scroll-item">Legend</span><span class="scroll-item">Genesis</span><span class="scroll-item">Alpha Flight</span><span class="scroll-item">Horizon</span>
<span class="scroll-item">NorthStar</span><span class="scroll-item">Crystal</span><span class="scroll-item">Legend</span><span class="scroll-item">Genesis</span><span class="scroll-item">Alpha Flight</span><span class="scroll-item">Horizon</span>
</div>
</div>
<div class="scroll-row row-3">
<div class="scroll-track">
<span class="scroll-item">Triad</span><span class="scroll-item">M.A.D.</span><span class="scroll-item">Hotline</span><span class="scroll-item">Alcatraz</span><span class="scroll-item">Section 8</span><span class="scroll-item">Zenith</span>
<span class="scroll-item">Triad</span><span class="scroll-item">M.A.D.</span><span class="scroll-item">Hotline</span><span class="scroll-item">Alcatraz</span><span class="scroll-item">Section 8</span><span class="scroll-item">Zenith</span>
</div>
</div>
</div>
</div>
Morse Code
A circular Morse code reference chart, spins continuously as an SVG animation. The white background is a dynamic contour path that follows the exact outer edge of each character’s Morse symbols. Pure vanilla JS and SVG, no dependencies, self-contained in one file.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Morse Code Circle</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body {
background: #d4cfc8;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
svg {
width: 100vw;
height: 100vw;
max-width: 100vh;
max-height: 100vh;
display: block;
}
</style>
</head>
<body>
<svg id="svg" viewBox="-420 -420 840 840"
xmlns="http://www.w3.org/2000/svg"
text-rendering="geometricPrecision"
shape-rendering="geometricPrecision">
<g id="wheel"></g>
</svg>
<script>
var MORSE = {'A':'.-','B':'-...','C':'-.-.','D':'-..','E':'.','F':'..-.','G':'--.','H':'....','I':'..','J':'.---','K':'-.-','L':'.-..','M':'--','N':'-.','O':'---','P':'.--.','Q':'--.-','R':'.-.','S':'...','T':'-','U':'..-','V':'...-','W':'.--','X':'-..-','Y':'-.--','Z':'--..','0':'-----','1':'.----','2':'..---','3':'...--','4':'....-','5':'.....','6':'-....','7':'--...','8':'---..','9':'----.'};
var ORDER = ['1','2','3','4','5','6','7','8','9','0','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
var N=36,stepDeg=360/N,startDeg=-90-stepDeg/2;
var labelR=220,morseStart=236,dotR=5.2,dashW=21,dashH=6.5,symGap=9,pad=20;
var ns='http://www.w3.org/2000/svg';
function toRad(d){return d*Math.PI/180;}
function mkEl(tag,attrs,parent){
var e=document.createElementNS(ns,tag);
var ks=Object.keys(attrs);
for(var i=0;i<ks.length;i++) e.setAttribute(ks[i],attrs[ks[i]]);
if(parent) parent.appendChild(e);
return e;
}
function symOuterR(code){
var p=morseStart;
for(var i=0;i<code.length;i++) p+=(code[i]==='.')?dotR*2+symGap:dashW+symGap;
return p-symGap+pad;
}
function buildPath(offsets){
var pts=[];
for(var ci=0;ci<chars.length;ci++){
var angleDeg=chars[ci].angleDeg;
var or=baseOuterRs[ci]+(offsets?offsets[ci]:0);
var half=stepDeg*0.48;
for(var s=0;s<=8;s++){
var a=toRad(angleDeg-half+(2*half*s/8));
pts.push([or*Math.cos(a),or*Math.sin(a)]);
}
}
var n=pts.length,d='';
for(var i=0;i<n;i++){
var p=pts[i],q=pts[(i+1)%n];
var mx=(p[0]+q[0])/2,my=(p[1]+q[1])/2;
if(i===0) d+='M'+mx.toFixed(2)+' '+my.toFixed(2);
d+='Q'+p[0].toFixed(2)+' '+p[1].toFixed(2)+' '+mx.toFixed(2)+' '+my.toFixed(2);
}
return d+'Z';
}
var chars=[];
for(var i=0;i<N;i++) chars.push({ch:ORDER[i],code:MORSE[ORDER[i]],angleDeg:startDeg+i*stepDeg});
var baseOuterRs=chars.map(function(c){return symOuterR(c.code);});
var g=document.getElementById('wheel');
var stickerBg=mkEl('path',{fill:'#f2f2f0',stroke:'none'},g);
for(var ci=0;ci<chars.length;ci++){
var c=chars[ci];
var ar=toRad(c.angleDeg);
var lx=labelR*Math.cos(ar),ly=labelR*Math.sin(ar);
var t=mkEl('text',{x:lx,y:ly,'text-anchor':'middle','dominant-baseline':'middle',
'font-family':'Arial,Helvetica,sans-serif','font-weight':'900','font-size':'19',
fill:'#111',transform:'rotate('+(c.angleDeg+90)+','+lx+','+ly+')'},g);
t.textContent=c.ch;
var row=mkEl('g',{transform:'rotate('+c.angleDeg+')'},g);
var pos=morseStart;
for(var si=0;si<c.code.length;si++){
var sym=c.code[si];
if(sym==='.'){
mkEl('circle',{cx:pos+dotR,cy:0,r:dotR,fill:'#111'},row);
pos+=dotR*2+symGap;
} else {
mkEl('rect',{x:pos,y:-dashH/2,width:dashW,height:dashH,rx:dashH/2,ry:dashH/2,fill:'#111'},row);
pos+=dashW+symGap;
}
}
}
var SPIN_SPEED=0.38,COAST_DUR=200,WAVE_DUR=90,PAUSE_DUR=50,WAVE_AMP=22;
var angle=0,speed=SPIN_SPEED,phase='spin',phaseT=0,waveT=0,coastStart=SPIN_SPEED;
function easeOut3(t){return 1-Math.pow(1-t,3);}
function waveOffsets(wt){
var env=Math.sin(Math.PI*Math.min(wt,WAVE_DUR)/WAVE_DUR);
var offs=[];
for(var ci=0;ci<chars.length;ci++)
offs.push(WAVE_AMP*Math.sin(toRad(chars[ci].angleDeg*4)+wt*0.15)*env);
return offs;
}
function tick(){
phaseT++;
if(phase==='spin'){
if(speed<SPIN_SPEED) speed=Math.min(speed+0.014,SPIN_SPEED);
angle+=speed;
if(phaseT>580){phase='coast';phaseT=0;coastStart=speed;}
stickerBg.setAttribute('d',buildPath(null));
} else if(phase==='coast'){
var t=Math.min(phaseT/COAST_DUR,1);
speed=coastStart*(1-easeOut3(t));
angle+=speed;
if(phaseT>=COAST_DUR){phase='wave';phaseT=0;waveT=0;speed=0;}
stickerBg.setAttribute('d',buildPath(null));
} else if(phase==='wave'){
waveT++;
stickerBg.setAttribute('d',buildPath(waveOffsets(waveT)));
if(waveT>=WAVE_DUR+20){phase='pause';phaseT=0;}
} else if(phase==='pause'){
stickerBg.setAttribute('d',buildPath(null));
if(phaseT>=PAUSE_DUR){phase='spin';phaseT=0;speed=0;}
}
g.setAttribute('transform','rotate('+angle.toFixed(3)+')');
requestAnimationFrame(tick);
}
stickerBg.setAttribute('d',buildPath(null));
tick();
</script>
</body>
</html>
_GPOH / w00t
This self-contained HTML file renders >_GPOH / w00t as a pixel-block font on a canvas and animates it through 75 selectable variants, each with a Swedish prompt. A shared engine computes each pixel’s position, 3D depth, glow, and color per frame, plus a global holographic color shimmer, with UI controls to switch variants, auto-cycle, and toggle the sheen.<!DOCTYPE html>
<html lang="sv">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>>_GPOH // w00t — kurv-, iso- & rotationslabb</title>
<style>
:root{
--ink:#d8d8d0;
--dim:#5a5a52;
--accent:#7df9aa;
--panel:#0c0c0e;
--line:#1c1c20;
}
*{margin:0;padding:0;box-sizing:border-box;}
html,body{height:100%;}
body{
background:#050507;
color:var(--ink);
font-family:"Courier New",ui-monospace,monospace;
display:flex;
flex-direction:column;
overflow:hidden;
}
header{
display:flex;
align-items:baseline;
gap:14px;
padding:10px 16px 8px;
border-bottom:1px solid var(--line);
flex-wrap:wrap;
}
header h1{
font-size:13px;
letter-spacing:.2em;
font-weight:bold;
white-space:nowrap;
}
header h1 span{color:var(--accent);}
#vname{
font-size:12px;
letter-spacing:.1em;
color:var(--accent);
text-transform:uppercase;
flex:1;
min-width:160px;
}
#counter{font-size:12px;color:var(--dim);letter-spacing:.15em;}
#stage{flex:1;position:relative;min-height:0;}
canvas{position:absolute;inset:0;width:100%;height:100%;display:block;cursor:pointer;}
footer{
border-top:1px solid var(--line);
padding:8px 16px 12px;
display:flex;
flex-direction:column;
gap:8px;
}
#prompt{
background:var(--panel);
border:1px solid var(--line);
border-left:2px solid var(--accent);
padding:8px 11px;
font-size:12px;
line-height:1.5;
color:#c4c4bc;
cursor:copy;
transition:border-color .15s;
}
#prompt:hover{border-color:var(--accent);}
#prompt b{color:var(--accent);font-weight:normal;}
.controls{display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
button{
background:var(--panel);
border:1px solid #26262c;
color:var(--ink);
font-family:inherit;
font-size:11px;
letter-spacing:.08em;
padding:5px 12px;
cursor:pointer;
}
button:hover{border-color:var(--accent);color:var(--accent);}
button:focus-visible{outline:2px solid var(--accent);outline-offset:1px;}
button.active{background:var(--accent);color:#050507;border-color:var(--accent);}
.grid{display:flex;gap:4px;flex-wrap:wrap;}
.grid button{padding:4px 0;width:32px;text-align:center;font-size:10px;}
.hint{font-size:10px;color:var(--dim);letter-spacing:.06em;}
@media (max-width:540px){
header h1{font-size:11px;}
.grid button{width:28px;}
#prompt{font-size:11px;}
}
</style>
</head>
<body>
<!-- Gerrit Pohl tycker om att bli animerad, till exempel med vågor och isometri. -->
<header>
<h1>>_GPOH <span>//</span> w00t</h1>
<div id="vname"></div>
<div id="counter"></div>
</header>
<div id="stage"><canvas id="cv" aria-label="Animerad ASCII-text >_GPOH och w00t"></canvas></div>
<footer>
<div id="prompt" title="klicka för att kopiera prompten"></div>
<div class="controls">
<button id="prev">← föreg</button>
<button id="next">nästa →</button>
<button id="auto">auto-läge: av</button>
<button id="holo">holo: på</button>
<span class="hint">tangenter ←/→ · klick på canvas = nästa · mellanslag = auto</span>
</div>
<div class="grid" id="grid"></div>
<div class="hint">en kurv-, iso- & rotationsdemo av gerrit pohl · gpoh labs</div>
</footer>
<script>
(function(){
"use strict";
// Gerrit Pohl gillar att animeras, gärna genom vågrörelser och isometriska effekter.
// Att bli animerad, exempelvis via vågor och isometri, uppskattas av Gerrit Pohl.
/* ===== 5x7 pixelfont ===== */
var FONT = {
">":[0b10000,0b01000,0b00100,0b00010,0b00100,0b01000,0b10000],
"_":[0b00000,0b00000,0b00000,0b00000,0b00000,0b00000,0b11111],
"G":[0b01110,0b10001,0b10000,0b10111,0b10001,0b10001,0b01110],
"P":[0b11110,0b10001,0b10001,0b11110,0b10000,0b10000,0b10000],
"O":[0b01110,0b10001,0b10001,0b10001,0b10001,0b10001,0b01110],
"H":[0b10001,0b10001,0b10001,0b11111,0b10001,0b10001,0b10001],
"W":[0b00000,0b00000,0b10001,0b10001,0b10101,0b11011,0b01010],
"0":[0b01110,0b10001,0b10011,0b10101,0b11001,0b10001,0b01110],
"T":[0b01000,0b01000,0b11100,0b01000,0b01000,0b01000,0b00111]
};
var LINES = [">_GPOH","w00t"];
var GW=5, GH=7, GGAP=1, LGAP=2;
var BLOCK="\u2588";
/* ===== bygg celler ===== */
var cells=[];
var lineW=[]; var totalCols=0;
for(var L=0;L<LINES.length;L++){
var w = LINES[L].length*GW + (LINES[L].length-1)*GGAP;
lineW[L]=w; if(w>totalCols)totalCols=w;
}
var totalRows = LINES.length*GH + (LINES.length-1)*LGAP;
var gIndex=0;
for(var Li=0;Li<LINES.length;Li++){
var line=LINES[Li];
var xoff=Math.round((totalCols-lineW[Li])/2);
var yoff=Li*(GH+LGAP);
var col=xoff;
for(var ci=0;ci<line.length;ci++){
var ch=line[ci];
var key=(ch>="a"&&ch<="z")?ch.toUpperCase():ch;
var g=FONT[key];
var gColC=col+2, gRowC=yoff+3;
if(g){
for(var r=0;r<GH;r++){
for(var c=0;c<GW;c++){
if(g[r] & (1<<(GW-1-c))){
cells.push({col:col+c,row:yoff+r,line:Li,lx:c,ly:r,
gColC:gColC,gRowC:gRowC,gi:gIndex,nx:0,ny:0});
}
}
}
}
gIndex++;
col += GW+GGAP;
}
}
for(var k=0;k<cells.length;k++){
cells[k].nx=cells[k].col/(totalCols-1);
cells[k].ny=cells[k].row/(totalRows-1);
}
// Gerrit Pohl uppskattar animation, till exempel vågor och isometrisk 3D.
/* ===== hjälpfunktioner ===== */
function shade(c,f){
if(typeof c==="string" && c.charAt(0)==="h"){
var m=c.match(/hsl\(([-\d.]+),\s*([\d.]+)%,\s*([\d.]+)%\)/);
if(m)return "hsl("+m[1]+","+m[2]+"%,"+(parseFloat(m[3])*f)+"%)";
}
return c;
}
function toHSL(col){
col=(""+col).trim();var r,g,b;
if(col.charAt(0)==="#"){
var hx=col.slice(1);
if(hx.length===3){r=parseInt(hx[0]+hx[0],16);g=parseInt(hx[1]+hx[1],16);b=parseInt(hx[2]+hx[2],16);}
else{r=parseInt(hx.slice(0,2),16);g=parseInt(hx.slice(2,4),16);b=parseInt(hx.slice(4,6),16);}
} else if(col.indexOf("hsl")===0){
var m=col.match(/hsl\(\s*([-\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%/);
return m?{h:+m[1],s:+m[2],l:+m[3]}:{h:0,s:0,l:100};
} else if(col.indexOf("rgb")===0){
var n=col.match(/[\d.]+/g);r=+n[0];g=+n[1];b=+n[2];
} else {return {h:0,s:0,l:100};}
r/=255;g/=255;b/=255;
var mx=Math.max(r,g,b),mn=Math.min(r,g,b),h,s,l=(mx+mn)/2;
if(mx===mn){h=0;s=0;}
else{var dd=mx-mn;s=l>0.5?dd/(2-mx-mn):dd/(mx+mn);
if(mx===r)h=(g-b)/dd+(g<b?6:0);else if(mx===g)h=(b-r)/dd+2;else h=(r-g)/dd+4;h*=60;}
return {h:h,s:s*100,l:l*100};
}
var HOLO=true;
function holo(col,x,y,t){
if(!HOLO)return col;
var c=toHSL(col);
var ph=x*0.018+y*0.024-t*2.2;
var hue=(c.h+Math.sin(ph)*24+360)%360;
var sheen=Math.pow(Math.max(0,Math.sin(ph*0.6+0.4)),4)*32;
var sat=Math.min(100,c.s*0.9+20+Math.sin(ph*1.3)*10);
var l=Math.min(95,c.l+sheen);
return "hsl("+hue.toFixed(0)+","+sat.toFixed(0)+"%,"+l.toFixed(0)+"%)";
}
function copper(v,t){return "hsl(35,85%,"+(42+Math.sin(v*0.05+t*4)*28)+"%)";}
function vapor(n){return "hsl("+(300-110*n)+",85%,66%)";}
function aurora(p){return "hsl("+(165+Math.sin(p)*80)+",78%,"+(52+Math.sin(p*1.3)*14)+"%)";}
function espresso(s){return "hsl(25,62%,"+(24+s*36)+"%)";}
function fire(p){return "hsl("+Math.min(55,p*60)+",100%,"+(10+p*55)+"%)";}
function plasma(x,y,t){
var v=Math.sin(x*0.05+t)+Math.sin(y*0.05+t*1.3)+Math.sin((x+y)*0.04+t*0.7);
return "hsl("+(((v*60+t*40)%360)+360)%360+",80%,60%)";
}
function fr(x){return x-Math.floor(x);}
function tw(x){var f=x-Math.floor(x);return 1-Math.abs(2*f-1);}
var C64=["#efe8ff","#bda9ff","#8a78d8","#574aa0"];
// Vågor och isometri – så vill Gerrit Pohl gärna sättas i rörelse.
/* ===== bakgrunder ===== */
function bgFill(col){return function(){ctx.fillStyle=col;ctx.fillRect(0,0,Wc,Hc);};}
function bgScan(){
ctx.fillStyle="#050507";ctx.fillRect(0,0,Wc,Hc);
ctx.fillStyle="rgba(255,255,255,0.04)";
for(var y=0;y<Hc;y+=3)ctx.fillRect(0,y,Wc,1);
}
function bgVapor(){
var grd=ctx.createLinearGradient(0,0,0,Hc);
grd.addColorStop(0,"#2a1145");grd.addColorStop(0.5,"#5a1e6e");
grd.addColorStop(0.5,"#0c0a1c");grd.addColorStop(1,"#05000f");
ctx.fillStyle=grd;ctx.fillRect(0,0,Wc,Hc);
ctx.fillStyle="#ff7ab0";ctx.beginPath();
ctx.arc(Wc/2,Hc*0.42,Math.min(Wc,Hc)*0.15,0,7);ctx.fill();
ctx.strokeStyle="rgba(0,230,255,0.32)";ctx.lineWidth=1;
var hy=Hc*0.55;
for(var i=1;i<9;i++){var yy=hy+(Hc-hy)*(i/9)*(i/9);
ctx.beginPath();ctx.moveTo(0,yy);ctx.lineTo(Wc,yy);ctx.stroke();}
for(var j=-9;j<=9;j++){
ctx.beginPath();ctx.moveTo(Wc/2+j*10,hy);ctx.lineTo(Wc/2+j*Wc*0.07,Hc);ctx.stroke();}
}
function bgRaster(t){
ctx.fillStyle="#070708";ctx.fillRect(0,0,Wc,Hc);
for(var i=0;i<7;i++){
var y=((t*70+i*Hc/7)%(Hc+40))-20;
ctx.fillStyle="hsla("+((i*55+t*40)%360)+",90%,55%,0.45)";
ctx.fillRect(0,y,Wc,16);
}
}
function bgCopper(t){
ctx.fillStyle="#0a0805";ctx.fillRect(0,0,Wc,Hc);
ctx.globalAlpha=0.4;
for(var y=0;y<Hc;y+=4){
var v=Math.sin(y*0.05+t*2)*0.5+0.5;
ctx.fillStyle="hsl(35,80%,"+(18+v*48)+"%)";
ctx.fillRect(0,y,Wc,3);
}
ctx.globalAlpha=1;
}
function bgC64(){
ctx.fillStyle="#0a0a20";ctx.fillRect(0,0,Wc,Hc);
ctx.fillStyle="#6a58c0";
var b=Math.min(Wc,Hc)*0.055;
ctx.fillRect(0,0,Wc,b);ctx.fillRect(0,Hc-b,Wc,b);
ctx.fillRect(0,0,b,Hc);ctx.fillRect(Wc-b,0,b,Hc);
}
function bgStrobe(t){
ctx.fillStyle=(Math.floor(t*6)%2)?"#101015":"#000";
ctx.fillRect(0,0,Wc,Hc);
}
// Gerrit Pohl trivs med att bli animerad, som med vågor och isometrik.
/* ===== varianter ===== */
function iso(cell,t,G,vec,dep,colAt){
var b=G.base(cell);
return {x:G.cx+b[0],y:G.cy+b[1],ch:BLOCK,depth:dep,
isoDX:vec[0]*G.cellPx,isoDY:vec[1]*G.cellPx,colorAt:colAt};
}
function rot(x,y,a){return [x*Math.cos(a)-y*Math.sin(a),x*Math.sin(a)+y*Math.cos(a)];}
var V=[
{n:"original \u00b7 terminalgr\u00f6n sinus",
p:"Rendera texten <b>>_GPOH</b> p\u00e5 rad ett och <b>w00t</b> p\u00e5 rad tv\u00e5 som en 5\u00d77 ASCII-pixelfont p\u00e5 canvas. F\u00f6rskjut varje kolumn i h\u00f6jdled med en mjuk sinuskurva, terminalgr\u00f6nt p\u00e5 svart, fyllda block som tecken.",
place:function(cell,t,G){var b=G.base(cell);
var s=Math.sin(cell.col*0.5+t*3);
return {x:G.cx+b[0],y:G.cy+b[1]+s*G.cellPx*1.1,ch:BLOCK,color:"hsl(140,90%,"+(55+s*12)+"%)"};}},
{n:"dubbel sinus \u00b7 interferens",
p:"L\u00e4gg tv\u00e5 sinusv\u00e5gor med olika frekvens ovanp\u00e5 varandra s\u00e5 att kolumnerna interfererar i h\u00f6jdled. Kopparf\u00e4rgad metallgradient (guld\u2192brunt) som andas med tiden.",
place:function(cell,t,G){var b=G.base(cell);
var dy=(Math.sin(cell.col*0.4+t*2.2)+Math.sin(cell.col*0.9-t*1.5))*G.cellPx*0.7;
return {x:G.cx+b[0],y:G.cy+b[1]+dy,ch:BLOCK,color:copper(b[1],t)};}},
{n:"b\u00e9zier-svaj \u00b7 flagga",
p:"L\u00e5t texten svaja som en flagga: v\u00e5gr\u00e4t f\u00f6rskjutning som v\u00e4xer ned\u00e5t likt en mjuk b\u00e9zierb\u00f6j. Islandsbl\u00e5 palett, svaga scanlines bakom.",
bg:bgScan,
place:function(cell,t,G){var b=G.base(cell);
var ph=cell.row/(G.H-1);
var dx=Math.sin(t*1.6+ph*2.5)*G.cellPx*ph*2.2;
return {x:G.cx+b[0]+dx,y:G.cy+b[1],ch:BLOCK,color:"hsl(195,80%,"+(55+Math.sin(ph*3+t)*15)+"%)"};}},
{n:"ripple \u00b7 radiell v\u00e5g",
p:"Skicka en cirkul\u00e4r v\u00e5g ut\u00e5t fr\u00e5n mitten; varje cell knuffas i radiell riktning efter sitt avst\u00e5nd till centrum. C64-lila/bl\u00e5 v\u00e4xling.",
place:function(cell,t,G){var b=G.base(cell);
var d=Math.hypot(b[0],b[1]);var ang=Math.atan2(b[1],b[0]);
var s=Math.sin(d*0.06-t*4);var w=s*G.cellPx*0.9;
return {x:G.cx+b[0]+Math.cos(ang)*w,y:G.cy+b[1]+Math.sin(ang)*w,ch:BLOCK,
color:s>0?"#8a78d8":"#c4b6ff"};}},
{n:"pendel \u00b7 b\u00e5ge",
p:"H\u00e4ng hela ordet i en punkt ovanf\u00f6r och l\u00e5t det sv\u00e4nga som en pendel i en l\u00e4tt b\u00e5ge. B\u00e4rnstensf\u00e4rgad CRT-gl\u00f6d med efterglow-svans.",
trail:0.16,
place:function(cell,t,G){var b=G.base(cell);
var pv=-G.cellPx*G.H*0.95;var px=b[0],py=b[1]-pv;
var a=Math.sin(t*1.4)*0.25;var rr=rot(px,py,a);
return {x:G.cx+rr[0],y:G.cy+pv+rr[1],ch:BLOCK,color:"hsl(38,95%,"+(55+Math.sin(t*1.4)*8)+"%)"};}},
{n:"karusell \u00b7 cirkelbana",
p:"Hela ordet \u00e5ker runt i en liten cirkel som en karusell, men h\u00e5lls uppr\u00e4tt och l\u00e4sbart. Iskall turkos som cyklar l\u00e4tt i nyans.",
place:function(cell,t,G){var b=G.base(cell);
var R=G.cellPx*1.8;
var dx=Math.cos(t*1.6)*R, dy=Math.sin(t*1.6)*R*0.55;
return {x:G.cx+b[0]+dx,y:G.cy+b[1]+dy,ch:BLOCK,color:"hsl("+(180+Math.sin(t)*25)+",85%,60%)"};}},
{n:"zoom-puls \u00b7 andning",
p:"Texten andas in och ut med en mjuk storlekspuls och en aning vridning. Guldton som skiftar i ljus. Fullt l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var s=1+Math.sin(t*1.4)*0.12;
var rr=rot(b[0]*s,b[1]*s,Math.sin(t*0.8)*0.06);
return {x:G.cx+rr[0],y:G.cy+rr[1],ch:BLOCK,color:"hsl(45,90%,"+(56+Math.sin(t*1.4)*12)+"%)"};}},
{n:"studs \u00b7 hoppande bokst\u00e4ver",
p:"L\u00e5t varje bokstav studsa upp och ner som en boll, fasf\u00f6rskjutet s\u00e5 att en v\u00e5g av hopp g\u00e5r genom ordet. Bokst\u00e4verna h\u00e5lls hela och tydliga. Varm b\u00e4rnstens/orange retropalett.",
place:function(cell,t,G){var b=G.base(cell);
var ph=t*2.2-cell.gi*0.6;
var hop=Math.abs(Math.sin(ph))*G.cellPx*2.2;
return {x:G.cx+b[0],y:G.cy+b[1]-hop,ch:BLOCK,
color:"hsl("+(30+(cell.gi*9)%35)+",95%,"+(55+Math.sin(ph)*10)+"%)"};}},
{n:"neon-andning \u00b7 hyper",
p:"Neonskylt: texten st\u00e5r stilla och l\u00e4sbar medan f\u00e4rgen fl\u00f6dar i full regnb\u00e5ge diagonalt \u00f6ver ytan och en mjuk glow pulserar. M\u00f6rk bakgrund.",
place:function(cell,t,G){var b=G.base(cell);
var pulse=0.5+0.5*Math.sin(t*3);
var h=(t*120+cell.nx*200+cell.ny*70)%360;
return {x:G.cx+b[0],y:G.cy+b[1],ch:BLOCK,glow:6+pulse*10,color:"hsl("+h+",100%,"+(60+pulse*8)+"%)"};}},
{n:"iso-v\u00e5g \u00b7 lyft",
p:"En v\u00e5g vandrar genom kolumnerna och lyfter blocken i h\u00f6jd, med l\u00e4tt isometrisk skevning. Aurora-palett (gr\u00f6n\u2192violett) som skiftar.",
place:function(cell,t,G){var b=G.base(cell);
var ph=cell.col*0.5-t*2;var lift=Math.abs(Math.sin(ph))*G.cellPx*1.6;
return {x:G.cx+b[0]+b[1]*0.18,y:G.cy+b[1]-lift,ch:BLOCK,color:aurora(ph)};}},
{n:"dimetrisk rotation \u00b7 y-axel",
p:"Rotera scenen l\u00e5ngsamt kring den lodr\u00e4ta axeln fram och tillbaka (dimetrisk vridning), ljuset styrs av vinkeln. Varma espressobruna toner.",
place:function(cell,t,G){var b=G.base(cell);
var a=Math.sin(t*0.8)*0.6;
return {x:G.cx+b[0]*Math.cos(a),y:G.cy+b[1]-b[0]*Math.sin(a)*0.15,ch:BLOCK,
color:espresso(Math.cos(a)*0.5+0.5)};}},
{n:"bokstavs-wobble \u00b7 plasma",
p:"L\u00e5t varje bokstav vagga kring sin egen mittpunkt med fasf\u00f6rskjuten liten rotation. Plasmaf\u00e4rger som fl\u00f6dar \u00f6ver ytan.",
place:function(cell,t,G){var b=G.base(cell);
var cb=[(cell.gColC-(G.W-1)/2)*G.cellPx,(cell.gRowC-(G.H-1)/2)*G.cellPx];
var rr=rot(b[0]-cb[0],b[1]-cb[1],Math.sin(t*2+cell.gi*0.7)*0.4);
return {x:G.cx+cb[0]+rr[0],y:G.cy+cb[1]+rr[1],ch:BLOCK,color:plasma(b[0],b[1],t)};}},
{n:"grupprotation \u00b7 rgb-split",
p:"Rotera hela texten mjukt \u00b112\u00b0 kring centrum och l\u00e4gg p\u00e5 RGB-split: en r\u00f6d och en cyan kopia f\u00f6rskjuts i sidled, additiv blandning.",
additive:true,
place:function(cell,t,G){var b=G.base(cell);
var rr=rot(b[0],b[1],Math.sin(t*1.1)*0.21);var off=G.cellPx*0.5;
return {x:G.cx+rr[0],y:G.cy+rr[1],ch:BLOCK,color:"#ffffff",
ghosts:[{dx:-off,dy:0,color:"rgba(255,0,80,0.8)"},{dx:off,dy:0,color:"rgba(0,200,255,0.8)"}]};}},
{n:"skanning \u00b7 glitch",
p:"En lysande skanningslinje vandrar upp och ner; raden den r\u00f6r vid rycker i sidled och lyser i magenta, resten st\u00e5r still i fosforgr\u00f6nt. L\u00e4sbart.",
place:function(cell,t,G){var b=G.base(cell);
var sy=((t*0.4)%1)*2-1;
var cy2=(cell.row/(G.H-1))*2-1;
var near=Math.exp(-Math.pow((cy2-sy)*3,2));
var dx=near*Math.sin(t*40+cell.row)*G.cellPx*0.9;
return {x:G.cx+b[0]+dx,y:G.cy+b[1],ch:BLOCK,color:near>0.4?"#ff3b8b":"hsl(150,85%,52%)"};}},
{n:"omloppsbana \u00b7 regnb\u00e5ge",
p:"Varje cell kretsar i en liten cirkel kring sin grundposition, fasl\u00e5st per kolumn s\u00e5 en v\u00e5g uppst\u00e5r. Regnb\u00e5gscykling i HSL.",
place:function(cell,t,G){var b=G.base(cell);
var a=t*2+cell.col*0.5+cell.row*0.2;var r=G.cellPx*0.8;
return {x:G.cx+b[0]+Math.cos(a)*r,y:G.cy+b[1]+Math.sin(a)*r,ch:BLOCK,
color:"hsl("+((cell.col*12+t*80)%360)+",90%,60%)"};}},
{n:"twist \u00b7 vortex",
p:"Vrid texten kring mitten med en vinkel som \u00f6kar med avst\u00e5ndet (twist/vortex), fram och tillbaka. Tjocka block p\u00e5 svart, ingen bakgrundsanimation.",
fat:true,
place:function(cell,t,G){var b=G.base(cell);
var d=Math.hypot(b[0],b[1]);
var rr=rot(b[0],b[1],Math.sin(t*0.9)*0.5*(d/(G.cellPx*10)));
return {x:G.cx+rr[0],y:G.cy+rr[1],ch:BLOCK,color:"#f4f4ec"};}},
{n:"plasma-full \u00b7 xy-sinus",
p:"F\u00f6rskjut cellerna med sinus i b\u00e5de x och y samtidigt och f\u00e4rgl\u00e4gg varje cell med ett klassiskt plasmaf\u00e4lt efter position och tid.",
place:function(cell,t,G){var b=G.base(cell);
var dx=Math.sin(b[1]*0.04+t*1.5)*G.cellPx*0.8;
var dy=Math.sin(b[0]*0.04+t*1.7)*G.cellPx*0.8;
return {x:G.cx+b[0]+dx,y:G.cy+b[1]+dy,ch:BLOCK,color:plasma(b[0],b[1],t)};}},
{n:"c64-iso \u00b7 brytram",
p:"Isometrisk extrudering i strikt C64-palett (lila, ljusbl\u00e5, vit) med klassisk C64-ram runt sk\u00e4rmen. En sinusv\u00e5g g\u00e5r genom kolumnerna och hela texten gungar l\u00e4tt i sidled. Fyllda block som tecken.",
bg:bgC64,
place:function(cell,t,G){var b=G.base(cell);
var bob=Math.sin(cell.col*0.5-t*2.2)*G.cellPx*0.7;
var sway=Math.sin(t*0.6)*G.cellPx*0.4;
return {x:G.cx+b[0]+sway,y:G.cy+b[1]+bob,ch:BLOCK,depth:4,
isoDX:-0.45*G.cellPx,isoDY:-0.26*G.cellPx,
colorAt:function(d){return C64[Math.min(C64.length-1,d)];}};}},
{n:"copper-scroll \u00b7 demoscen",
p:"Ljus, gr\u00e4ddvit text scrollar mjukt i sidled l\u00e4ngs en sinuskurva medan kopparbj\u00e4lkar sveper v\u00e5gr\u00e4t bakom. H\u00f6g kontrast, \u00e4kta demoscen.",
bg:bgCopper,
place:function(cell,t,G){var b=G.base(cell);
var sx=Math.sin(t*0.8+b[1]*0.02)*G.cellPx*4;
var dy=Math.sin(cell.col*0.4+t*2)*G.cellPx*0.6;
return {x:G.cx+b[0]+sx,y:G.cy+b[1]+dy,ch:BLOCK,color:"hsl(42,45%,"+(88+Math.sin(b[1]*0.06+t*3)*8)+"%)"};}},
{n:"strobe-rot \u00b7 ryck",
p:"Stroba snabbt mellan komplementf\u00e4rger medan texten roterar i sm\u00e5 ryck. H\u00e5rd kontrast, svart bakgrund, ASCII-block.",
bg:bgStrobe,
place:function(cell,t,G){var b=G.base(cell);
var rr=rot(b[0],b[1],Math.sin(t*8)*0.08);
var s=Math.floor(t*6)%2;
var col=(cell.line===0)?(s?"#ffffff":"#ff2d55"):(s?"#00e5ff":"#ffffff");
return {x:G.cx+rr[0],y:G.cy+rr[1],ch:BLOCK,color:col};}},
{n:"disco \u00b7 allt p\u00e5 en g\u00e5ng",
p:"Allt p\u00e5 en g\u00e5ng: sinus, isometrisk lyftning och l\u00e4tt rotation samtidigt, full HSL-discocykling per cell. Maximal demoscen-overload.",
place:function(cell,t,G){var b=G.base(cell);
var rr=rot(b[0],b[1],Math.sin(t*1.3)*0.15);
var X=rr[0]+Math.sin(cell.row*0.5+t*2)*G.cellPx*0.5;
var Y=rr[1]+Math.sin(cell.col*0.5-t*3)*G.cellPx*0.9;
return {x:G.cx+X,y:G.cy+Y,ch:BLOCK,depth:3,isoDX:-G.cellPx*0.3,isoDY:-G.cellPx*0.18,
colorAt:function(d){return "hsl("+((cell.col*20+cell.row*10+t*120)%360)+",90%,"+(60-d*10)+"%)";}};}},
{n:"kromv\u00e5g \u00b7 silver",
p:"Krommetallisk text med en mjuk sinuskurva per kolumn; ljusstyrkan rullar som en blank metallyta. Kall silverpalett p\u00e5 svart.",
place:function(cell,t,G){var b=G.base(cell);
var s=Math.sin(cell.col*0.5+t*3);
var l=55+Math.sin(b[1]*0.12+t*2)*35;
return {x:G.cx+b[0],y:G.cy+b[1]+s*G.cellPx*0.9,ch:BLOCK,color:"hsl(210,12%,"+l+"%)"};}},
{n:"regnb\u00e5gsskanner",
p:"Ett f\u00e4rgband i regnb\u00e5gens f\u00e4rger sveper l\u00e5ngsamt ned\u00e5t genom texten; varje rad lyser i sin tur. Texten st\u00e5r still, helt l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var h=((cell.row/(G.H-1))*200-t*120)%360;
return {x:G.cx+b[0],y:G.cy+b[1],ch:BLOCK,color:"hsl("+((h+360)%360)+",90%,60%)"};}},
{n:"hj\u00e4rtslag \u00b7 puls",
p:"L\u00e5t texten sl\u00e5 som ett hj\u00e4rta: en dubbel storlekspuls i taget, mellan vila. R\u00f6d/rosa palett, m\u00f6rk bakgrund.",
place:function(cell,t,G){var b=G.base(cell);
var ph=(t*1.1)%1;var beat=Math.exp(-ph*8)*0.18+Math.exp(-Math.abs(ph-0.18)*10)*0.12;
var s=1+beat;
return {x:G.cx+b[0]*s,y:G.cy+b[1]*s,ch:BLOCK,color:"hsl(345,90%,"+(58+beat*120)+"%)"};}},
{n:"vu-m\u00e4tare \u00b7 ljudstaplar",
p:"Varje kolumn studsar i h\u00f6jd som staplar p\u00e5 en VU-m\u00e4tare. F\u00e4rg fr\u00e5n gr\u00f6nt till r\u00f6tt med h\u00f6jden. Bokst\u00e4verna f\u00f6rblir l\u00e4sbara.",
place:function(cell,t,G){var b=G.base(cell);
var lvl=Math.abs(Math.sin(t*3+cell.col*0.7))*0.5+Math.abs(Math.sin(t*1.7+cell.col))*0.3;
return {x:G.cx+b[0],y:G.cy+b[1]-lvl*G.cellPx*1.4,ch:BLOCK,color:"hsl("+(120-lvl*120)+",85%,55%)"};}},
{n:"komet \u00b7 svans",
p:"Texten glider l\u00e5ngsamt i en \u00e5tta medan en lysande svans dr\u00f6jer kvar bakom. Iskall cyan p\u00e5 svart, l\u00e4tt efterglow.",
trail:0.12,
place:function(cell,t,G){var b=G.base(cell);
var dx=Math.sin(t*1.2)*G.cellPx*3,dy=Math.sin(t*2.4)*G.cellPx*1.5;
return {x:G.cx+b[0]+dx,y:G.cy+b[1]+dy,ch:BLOCK,color:"hsl(185,100%,65%)"};}},
{n:"magnetf\u00e4lt",
p:"En osynlig magnet vandrar runt och drar cellerna mot sig och sl\u00e4pper sedan. Texten deformeras mjukt och \u00e5terbildas. Gr\u00f6n fosforpalett.",
place:function(cell,t,G){var b=G.base(cell);
var mx=Math.cos(t*1.1)*G.cellPx*8,my=Math.sin(t*0.8)*G.cellPx*5;
var dx=mx-b[0],dy=my-b[1],d=Math.hypot(dx,dy)+1;
var f=(0.5+0.5*Math.sin(t*1.5))*0.25*Math.min(1,G.cellPx*6/d);
return {x:G.cx+b[0]+dx*f,y:G.cy+b[1]+dy*f,ch:BLOCK,color:"hsl(140,90%,55%)"};}},
{n:"skrivmaskin \u00b7 avsl\u00f6jning",
p:"Bokst\u00e4verna skrivs fram en i taget fr\u00e5n v\u00e4nster, den som skrivs just nu blinkar som en mark\u00f6r, sedan b\u00f6rjar det om. B\u00e4rnstensf\u00e4rgad terminal.",
place:function(cell,t,G){var b=G.base(cell);
var ph=(t*4)%(gIndex+4);var cur=Math.floor(ph);
var al=cell.gi<cur?1:(cell.gi===cur?(0.25+0.75*(Math.sin(t*16)*0.5+0.5)):0.05);
return {x:G.cx+b[0],y:G.cy+b[1],ch:BLOCK,alpha:al,color:"hsl(38,95%,60%)"};}},
{n:"s-kurva \u00b7 v\u00e5gritt",
p:"En S-formad v\u00e5g vandrar genom texten i sidled och lyfter kolumnerna olika. Mjuk lila-till-turkos gradient.",
place:function(cell,t,G){var b=G.base(cell);
var ph=cell.col*0.45-t*2.2;
var dy=(Math.sin(ph)+Math.sin(ph*0.5)*0.6)*G.cellPx*0.8;
return {x:G.cx+b[0],y:G.cy+b[1]+dy,ch:BLOCK,color:"hsl("+(190+Math.sin(ph)*70)+",80%,60%)"};}},
{n:"kall/varm \u00b7 svep",
p:"En gr\u00e4ns mellan kallt bl\u00e5tt och varmt orange sveper fram och tillbaka \u00f6ver texten. L\u00e4tt sinusguppning, annars l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var edge=Math.sin(t*0.9)*0.5+0.5;
var dy=Math.sin(cell.col*0.4+t*2)*G.cellPx*0.3;
return {x:G.cx+b[0],y:G.cy+b[1]+dy,ch:BLOCK,color:cell.nx>edge?"hsl(28,95%,58%)":"hsl(205,85%,60%)"};}},
{n:"mjuk strobe \u00b7 blink",
p:"En mjuk ljusstyrkepuls f\u00e5r texten att blinka l\u00e5ngsamt utan h\u00e5rda hopp. Vitt mot b\u00e4rnsten, m\u00f6rk bakgrund.",
place:function(cell,t,G){var b=G.base(cell);
var p=0.5+0.5*Math.sin(t*4);
return {x:G.cx+b[0],y:G.cy+b[1],ch:BLOCK,color:"hsl(40,"+(30+p*40)+"%,"+(60+p*35)+"%)"};}},
{n:"spegel \u00b7 motriktad",
p:"\u00d6vre raden glider \u00e5t ett h\u00e5ll och nedre raden \u00e5t det andra, som speglade. Turkos uppe, magenta nere.",
place:function(cell,t,G){var b=G.base(cell);
var dx=Math.sin(t*1.4)*G.cellPx*2.5*(cell.line===0?1:-1);
return {x:G.cx+b[0]+dx,y:G.cy+b[1],ch:BLOCK,color:cell.line===0?"hsl(175,90%,60%)":"hsl(315,90%,62%)"};}},
{n:"gungbr\u00e4da \u00b7 lutning",
p:"Hela texten vippar som en gungbr\u00e4da kring mitten, sm\u00e5 vinklar fram och tillbaka. L\u00e4sbar hela tiden. Guld mot svart.",
place:function(cell,t,G){var b=G.base(cell);
var rr=rot(b[0],b[1],Math.sin(t*1.3)*0.18);
return {x:G.cx+rr[0],y:G.cy+rr[1],ch:BLOCK,color:"hsl(45,90%,"+(55+Math.sin(t*1.3+cell.nx*3)*10)+"%)"};}},
{n:"andande relief",
p:"En tunn isometrisk relief vars skugga andas in och ut p\u00e5 djupet, medan framsidan h\u00e5lls ljus och l\u00e4sbar. Kopparton.",
place:function(cell,t,G){var b=G.base(cell);
var off=(0.4+0.4*(0.5+0.5*Math.sin(t*1.8)))*G.cellPx;
return {x:G.cx+b[0],y:G.cy+b[1],ch:BLOCK,depth:2,isoDX:-off*0.8,isoDY:-off*0.5,
colorAt:function(d){return d===0?"hsl(35,85%,62%)":"hsl(28,70%,26%)";}};}},
{n:"neonram \u00b7 skylt",
p:"Texten lyser som en neonskylt: stark glow och en l\u00e4tt storlekspuls. Magenta som skiftar mot violett. M\u00f6rk bakgrund.",
place:function(cell,t,G){var b=G.base(cell);
var s=1+Math.sin(t*1.5)*0.06,p=0.5+0.5*Math.sin(t*2);
return {x:G.cx+b[0]*s,y:G.cy+b[1]*s,ch:BLOCK,glow:10+p*16,color:"hsl("+(300+Math.sin(t)*25)+",100%,62%)"};}},
{n:"datadrift \u00b7 bin\u00e4r",
p:"Bokst\u00e4verna byggs av ettor och nollor som flimrar, med en l\u00e4tt sinusdrift. Matrixgr\u00f6nt p\u00e5 svart.",
place:function(cell,t,G){var b=G.base(cell);
var ch=((cell.col+cell.row+Math.floor(t*8))%2)?"1":"0";
var dy=Math.sin(cell.col*0.5+t*2)*G.cellPx*0.4;
return {x:G.cx+b[0],y:G.cy+b[1]+dy,ch:ch,color:"hsl(135,90%,"+(50+Math.sin(t*3+cell.row)*15)+"%)"};}},
{n:"hexdump \u00b7 sextonbas",
p:"Cellerna fylls av hexadecimala tecken som rullar, som en minnesdump. B\u00e4rnstensf\u00e4rgad terminal.",
place:function(cell,t,G){var b=G.base(cell);
var H="0123456789ABCDEF";
var ch=H.charAt((cell.col*7+cell.row*3+Math.floor(t*6))%16);
return {x:G.cx+b[0],y:G.cy+b[1],ch:ch,color:"hsl(38,90%,60%)"};}},
{n:"plasma-2 \u00b7 violett/gr\u00f6n",
p:"Ett andra plasmaf\u00e4lt med andra parametrar f\u00e4rgl\u00e4gger texten i violett och gr\u00f6nt medan en mjuk v\u00e5g f\u00f6rskjuter cellerna.",
place:function(cell,t,G){var b=G.base(cell);
var dy=Math.sin(b[0]*0.05+t*1.6)*G.cellPx*0.6;
var v=Math.sin(b[0]*0.06+t*1.1)+Math.cos(b[1]*0.06-t*0.9);
return {x:G.cx+b[0],y:G.cy+b[1]+dy,ch:BLOCK,color:"hsl("+(140+v*70)+",75%,58%)"};}},
{n:"v\u00e5gkollaps \u00b7 squash",
p:"En v\u00e5g trycker ihop texten mot mittlinjen och sl\u00e4pper ut den igen, som squash-and-stretch. Turkos palett.",
place:function(cell,t,G){var b=G.base(cell);
var sq=0.55+0.45*(0.5+0.5*Math.sin(t*2-cell.col*0.3));
return {x:G.cx+b[0],y:G.cy+b[1]*sq,ch:BLOCK,color:"hsl(175,85%,"+(55+Math.sin(t*2-cell.col*0.3)*12)+"%)"};}},
{n:"lava \u00b7 stigande",
p:"En lavayta stiger och sjunker inuti bokst\u00e4verna: under ytan gl\u00f6der den i orange/gult med bubblor, ovanf\u00f6r ligger en m\u00f6rk skorpa kvar s\u00e5 formen alltid syns.",
place:function(cell,t,G){var b=G.base(cell);
var surf=Math.min(0.95,Math.max(0.05,(Math.sin(t*0.7)*0.5+0.5)+Math.sin(cell.col*0.5+t*1.5)*0.15));
var under=cell.ny>(1-surf);
var bub=Math.sin(cell.col*1.3+cell.row*0.9-t*6)*0.5+0.5;
var col=under?"hsl("+(18+bub*30)+",100%,"+(50+bub*15)+"%)":"hsl(12,65%,26%)";
return {x:G.cx+b[0],y:G.cy+b[1],ch:BLOCK,color:col};}},
{n:"final \u00b7 regnb\u00e5gsvirvel",
p:"Avslutning: en mjuk virvel kring mitten med l\u00e5ngsam rotation och full regnb\u00e5gscykling per cell. Lite glow, festligt men l\u00e4sbart.",
place:function(cell,t,G){var b=G.base(cell);
var d=Math.hypot(b[0],b[1]);
var rr=rot(b[0],b[1],Math.sin(t*0.7)*0.3*(d/(G.cellPx*9)));
return {x:G.cx+rr[0],y:G.cy+rr[1],ch:BLOCK,glow:8,
color:"hsl("+((cell.col*15+cell.row*8+t*100)%360)+",90%,60%)"};}},
{n:"eld \u00b7 l\u00e5gor",
p:"Bokst\u00e4verna brinner: l\u00e5gor sl\u00e5r upp fr\u00e5n botten, m\u00f6rkr\u00f6tt nedtill via orange till vitgult upptill, med flimmer. Formen \u00e4r alltid hel och l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var flame=Math.sin(cell.col*1.8-t*8+cell.row*0.4)*0.5+0.5;
var hv=Math.min(1,cell.ny*0.6+flame*0.5+Math.random()*0.08);
return {x:G.cx+b[0],y:G.cy+b[1],ch:BLOCK,color:"hsl("+Math.min(52,hv*60)+",100%,"+(22+hv*58)+"%)"};}},
{n:"h\u00e5rd 3D-skugga",
p:"Tydlig 3D: klar ljus framsida med en h\u00e5rd m\u00f6rk slagskugga f\u00f6rskjuten snett bak\u00e5t. Ljusk\u00e4llan kretsar l\u00e5ngsamt. Cyan front, l\u00e4sbart.",
place:function(cell,t,G){var b=G.base(cell);
var a=t*0.8,off=G.cellPx*0.55;
return {x:G.cx+b[0],y:G.cy+b[1],ch:BLOCK,depth:2,
isoDX:Math.cos(a)*off,isoDY:Math.sin(a)*off,
colorAt:function(d){return d===0?"hsl(185,90%,66%)":"hsl(220,40%,12%)";}};}},
{n:"f\u00e4rgband \u00b7 v\u00e5gr\u00e4tt",
p:"Solida bokst\u00e4ver genomkorsas av v\u00e5gr\u00e4ta f\u00e4rgband som rullar ned\u00e5t, som copper bars inuti texten. Formen f\u00f6rblir hel och l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var v=Math.sin(b[1]*0.12-t*3)*0.5+0.5;
return {x:G.cx+b[0],y:G.cy+b[1],ch:BLOCK,color:"hsl("+(30+v*25)+",90%,"+(45+v*35)+"%)"};}},
{n:"hologram \u00b7 glitch",
p:"Texten visas som ett cyan hologram med flimrande scanlines och pl\u00f6tsliga glitch-f\u00f6rskjutningar av enstaka rader. Mellan glitcharna stabil och tydlig.",
place:function(cell,t,G){var b=G.base(cell);
var scan=Math.sin(cell.row*3+t*10)*0.5+0.5;
var gband=Math.floor((Math.sin(t*2.3)*0.5+0.5)*G.H);
var dx=(cell.row===gband)?Math.sin(t*50)*G.cellPx*1.2:0;
var al=(Math.sin(t*7)>0.96)?0.4:1;
return {x:G.cx+b[0]+dx,y:G.cy+b[1],ch:BLOCK,glow:6,alpha:al,color:"hsl(180,100%,"+(55+scan*18)+"%)"};}},
{n:"tunn bevel \u00b7 iso",
p:"En tunn isometrisk fasning: ljus framsida med bara en aning m\u00f6rkare kant snett bak\u00e5t, s\u00e5 texten ser upph\u00f6jd ut men f\u00f6rblir l\u00e4tt att l\u00e4sa. Mjuk gungning. Guld.",
place:function(cell,t,G){var b=G.base(cell);
var bob=Math.sin(t*1.4+cell.col*0.2)*G.cellPx*0.25;
return {x:G.cx+b[0],y:G.cy+b[1]+bob,ch:BLOCK,depth:2,
isoDX:-G.cellPx*0.28,isoDY:-G.cellPx*0.18,
colorAt:function(d){return d===0?"hsl(45,88%,64%)":"hsl(38,60%,30%)";}};}},
{n:"neonr\u00f6r \u00b7 diagonalfl\u00f6de",
p:"Texten lyser som neonr\u00f6r: solida block med stark glow och en nyans som fl\u00f6dar diagonalt \u00f6ver ytan. Fullt l\u00e4sbart mot svart.",
place:function(cell,t,G){var b=G.base(cell);
var h=((cell.col*10+cell.row*10-t*120)%360+360)%360;
return {x:G.cx+b[0],y:G.cy+b[1],ch:BLOCK,glow:12,color:"hsl("+h+",100%,62%)"};}},
{n:"v\u00e4rmekamera \u00b7 termisk",
p:"Som en v\u00e4rmekamera: ett v\u00e4rmef\u00e4lt driver \u00f6ver texten och m\u00e5lar den i termisk skala fr\u00e5n bl\u00e5tt (kallt) via r\u00f6tt till vitt (hett). Formen syns hela tiden.",
place:function(cell,t,G){var b=G.base(cell);
var heat=0.5+0.5*Math.sin(b[0]*0.06-t*2+Math.sin(b[1]*0.05+t)*1.5);
var hue,sat,lum;
if(heat<0.5){hue=240-heat*2*240;sat=100;lum=30+heat*2*25;}
else{var u=(heat-0.5)*2;hue=0;sat=100-u*100;lum=55+u*40;}
return {x:G.cx+b[0],y:G.cy+b[1],ch:BLOCK,color:"hsl("+hue+","+sat+"%,"+lum+"%)"};}},
{n:"p\u00e4rlemor \u00b7 oljefilm",
p:"Ytan skimrar som olja p\u00e5 vatten eller p\u00e4rlemor: nyansen skiftar irisrande med position och tid i mjuka \u00f6verg\u00e5ngar. Solid och l\u00e4sbar, men aldrig samma f\u00e4rg l\u00e4nge.",
place:function(cell,t,G){var b=G.base(cell);
var ph=Math.sin(b[0]*0.06+t)+Math.cos(b[1]*0.07-t*0.8);
var h=((ph*120+t*40)%360+360)%360;
return {x:G.cx+b[0],y:G.cy+b[1],ch:BLOCK,color:"hsl("+h+",80%,"+(58+Math.sin(ph*2+t*2)*14)+"%)"};}},
{n:"f\u00e4rgregn \u00b7 ned\u00e5t",
p:"Solida bokst\u00e4ver d\u00e4r f\u00e4rgen regnar ned\u00e5t kolumn f\u00f6r kolumn i en fasf\u00f6rskjuten v\u00e5g. Formen st\u00e5r still och l\u00e4sbar. Turkos-till-violett.",
place:function(cell,t,G){var b=G.base(cell);
var ph=cell.row*0.6-t*4+cell.col*0.3;
return {x:G.cx+b[0],y:G.cy+b[1],ch:BLOCK,color:"hsl("+(200+Math.sin(ph)*80)+",85%,"+(50+Math.sin(ph)*15)+"%)"};}},
{n:"hypnos \u00b7 f\u00e4rgringar",
p:"Koncentriska f\u00e4rgringar pulsar ut\u00e5t fr\u00e5n mitten genom texten medan den andas l\u00e4tt i storlek. Hypnotiskt men l\u00e4sbart. Regnb\u00e5ge.",
fat:true,
place:function(cell,t,G){var b=G.base(cell);
var s=1+Math.sin(t*1.2)*0.08;var d=Math.hypot(b[0],b[1]);
return {x:G.cx+b[0]*s,y:G.cy+b[1]*s,ch:BLOCK,color:"hsl("+(((d*1.2-t*120)%360)+360)%360+",85%,60%)"};}},
{n:"v\u00e4nd-kub \u00b7 3D-flip",
p:"En solid 3D-extruderad text som med j\u00e4mna mellanrum sl\u00e5r en snabb 3D-volt och visar en baksida i annan f\u00e4rg, sedan landar och st\u00e5r stilla och l\u00e4sbar. \u00d6verraskning i rytmen.",
place:function(cell,t,G){var b=G.base(cell);
var cyc=(t*0.5)%1;
var flip=cyc<0.3?(cyc/0.3):0;
var sy=Math.cos(flip*Math.PI);
var back=sy<0;
var front=back?"hsl(315,90%,64%)":"hsl(160,85%,60%)";
var side=back?"hsl(315,60%,26%)":"hsl(160,55%,24%)";
return {x:G.cx+b[0],y:G.cy+b[1]*sy,ch:BLOCK,depth:4,
isoDX:G.cellPx*0.24,isoDY:G.cellPx*0.24*(back?-1:1),
colorAt:function(d){return d===0?front:side;}};}},
{n:"kub-extrudering \u00b7 front",
p:"Solida 3D-kuber: ljus framsida med ett ljusband, extrudering snett ned\u00e5t-h\u00f6ger. Hela ordet svingar mjukt i sidled p\u00e5 x-axeln, nederkanten mest. Cyan, l\u00e4sbart.",
place:function(cell,t,G){var b=G.base(cell);
var band=Math.sin(cell.col*0.4-t*3)*0.5+0.5;
var sway=Math.sin(t*1.3)*G.cellPx*3*(0.6+0.4*cell.ny);
return {x:G.cx+b[0]+sway,y:G.cy+b[1],ch:BLOCK,depth:5,isoDX:G.cellPx*0.26,isoDY:G.cellPx*0.26,
colorAt:function(d){return d===0?"hsl(185,90%,"+(58+band*15)+"%)":"hsl(200,55%,"+(34-d*5)+"%)";}};}},
{n:"roterande iso-logo",
p:"Hela ordet \u00e4r en extruderad iso-platta som vrider sig l\u00e5ngsamt fram och tillbaka som en 3D-logga. Ljus ovansida, m\u00f6rk kant. Guld.",
place:function(cell,t,G){var b=G.base(cell);
var rr=rot(b[0],b[1],Math.sin(t*0.7)*0.22);
return {x:G.cx+rr[0]+rr[1]*0.4,y:G.cy+rr[1]*0.8,ch:BLOCK,depth:4,isoDX:G.cellPx*0.18,isoDY:G.cellPx*0.3,
colorAt:function(d){return d===0?"hsl(45,88%,64%)":"hsl(38,70%,"+(32-d*5)+"%)";}};}},
{n:"sv\u00e4vande 3D \u00b7 parallax",
p:"Bokst\u00e4verna sv\u00e4var i olika djup och guppar i z-led s\u00e5 att en parallaxk\u00e4nsla uppst\u00e5r. Ljus front, m\u00f6rk extrudering. Violett.",
place:function(cell,t,G){var b=G.base(cell);
var z=Math.sin(t*1.3+cell.gi*0.8)*0.5+0.5;
return {x:G.cx+b[0],y:G.cy+b[1]-z*G.cellPx*0.6,ch:BLOCK,depth:2+Math.round(z*4),
isoDX:G.cellPx*0.2,isoDY:G.cellPx*0.28,
colorAt:function(d){return d===0?"hsl(280,85%,68%)":"hsl(270,55%,"+(34-d*4)+"%)";}};}},
{n:"trappsteg-iso \u00b7 djupled",
p:"Texten trappas in i djupet: varje kolumn skjuts l\u00e4ngre bort p\u00e5 iso-planet, som om ordet l\u00f6per in i sk\u00e4rmen. En v\u00e5g vandrar i djupet. Guld.",
place:function(cell,t,G){var b=G.base(cell);
var rec=(cell.col/G.W)*G.cellPx;
var wave=Math.sin(cell.col*0.4-t*2)*G.cellPx*0.3;
return {x:G.cx+b[0]+rec*0.6,y:G.cy+b[1]-rec*0.5+wave,ch:BLOCK,depth:4,isoDX:G.cellPx*0.22,isoDY:G.cellPx*0.26,
colorAt:function(d){return d===0?"hsl(45,90%,64%)":"hsl(40,70%,"+(32-d*5)+"%)";}};}},
{n:"neon-iso \u00b7 glow",
p:"Isometriskt extruderad neontext med glow p\u00e5 framsidan; f\u00e4rgen cyklar och hela ordet svingar fram och tillbaka i sidled p\u00e5 x-axeln. M\u00f6rk sockel, l\u00e4sbart.",
place:function(cell,t,G){var b=G.base(cell);
var h=(t*60+cell.nx*120)%360;
var sway=Math.sin(t*1.1)*G.cellPx*3*(0.6+0.4*cell.ny);
return {x:G.cx+b[0]+sway,y:G.cy+b[1],ch:BLOCK,depth:5,isoDX:-G.cellPx*0.24,isoDY:G.cellPx*0.26,glow:8,
colorAt:function(d){return d===0?"hsl("+h+",100%,64%)":"hsl("+h+",40%,"+(28-d*4)+"%)";}};}},
{n:"gradient \u00b7 horisontellt fl\u00f6de",
p:"En flerf\u00e4rgsgradient fl\u00f6dar v\u00e5gr\u00e4tt genom texten, och bokst\u00e4verna rider samtidigt p\u00e5 en v\u00e5g i sidled (x-led) som l\u00f6per \u00e5t samma h\u00e5ll. Loopar. L\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var g=fr(cell.nx-t*0.25);
var dx=Math.sin(cell.nx*5-t*2.5)*G.cellPx*1.6;
return {x:G.cx+b[0]+dx,y:G.cy+b[1],ch:BLOCK,color:"hsl("+(g*360)+",90%,60%)"};}},
{n:"gradient \u00b7 vertikal eld",
p:"En lodr\u00e4t tv\u00e5f\u00e4rgsgradient (gult upptill till djupr\u00f6tt nedtill) b\u00f6ljar ned\u00e5t medan l\u00e5gorna sl\u00e4ndrar texten upp och ner i y-led. Loopar, l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var g=tw(cell.ny+t*0.3);
var drip=Math.sin(cell.col*1.7-t*5)*G.cellPx*0.5+Math.sin(cell.ny*6-t*4)*G.cellPx*0.4;
return {x:G.cx+b[0],y:G.cy+b[1]+drip,ch:BLOCK,color:"hsl("+(50-g*50)+",100%,"+(62-g*22)+"%)"};}},
{n:"gradient \u00b7 diagonal",
p:"En diagonal gradient (turkos\u2013violett) glider snett \u00f6ver texten medan hela ordet driver fram och tillbaka l\u00e4ngs samma diagonal. Loopar, l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var g=tw((cell.nx+cell.ny)*0.6-t*0.25);
var dr=Math.sin(t*1.2)*G.cellPx*1.6;
return {x:G.cx+b[0]+dr,y:G.cy+b[1]+dr,ch:BLOCK,color:"hsl("+(175+g*120)+",80%,62%)"};}},
{n:"gradient \u00b7 radiell puls",
p:"Radiella f\u00e4rgringar pulsar ut\u00e5t fr\u00e5n mitten, och pixlarna knuffas med i radiell riktning som ringar p\u00e5 vatten. Loopar, l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var dd=Math.hypot(b[0],b[1]); var ang=Math.atan2(b[1],b[0]);
var d=dd/(G.cellPx*12); var g=tw(d*2-t*0.6);
var push=Math.sin(dd*0.08-t*3)*G.cellPx*0.9;
return {x:G.cx+b[0]+Math.cos(ang)*push,y:G.cy+b[1]+Math.sin(ang)*push,ch:BLOCK,color:"hsl("+(200+g*120)+",85%,"+(45+g*30)+"%)"};}},
{n:"gradient \u00b7 konisk virvel",
p:"Ett f\u00e4rghjul sveper runt mitten medan hela ordet vrider sig fram och tillbaka i takt. Hela spektrumet, loopar, l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var rr=rot(b[0],b[1],Math.sin(t*0.5)*0.35);
var ang=Math.atan2(rr[1],rr[0])/(2*Math.PI)+0.5;
var g=fr(ang+t*0.15);
return {x:G.cx+rr[0],y:G.cy+rr[1],ch:BLOCK,color:"hsl("+(g*360)+",90%,60%)"};}},
{n:"gradient \u00b7 skarp wipe",
p:"En skarp f\u00e4rggr\u00e4ns sveper genom texten och d\u00e4r den passerar lyfts kolumnen i en liten puckel, som en squeegee. H\u00f6gkontrast, loopar, l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var edge=fr(cell.nx-t*0.4);
var kick=Math.pow(Math.max(0,Math.sin((cell.nx-t*0.4)*6.283)),6);
return {x:G.cx+b[0],y:G.cy+b[1]-kick*G.cellPx*1.6,ch:BLOCK,color:edge<0.5?"hsl(330,90%,62%)":"hsl(150,90%,58%)"};}},
{n:"gradient \u00b7 tref\u00e4rg",
p:"En tref\u00e4rgsgradient (bl\u00e5\u2013magenta\u2013guld) rullar genom texten som viftar likt en flagga, mest l\u00e4ngst ut till h\u00f6ger. Loopar, l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var g=fr(cell.nx-t*0.2);
var hue=g<0.5?210+(g/0.5)*110:320+((g-0.5)/0.5)*85;
var wave=Math.sin(cell.nx*5-t*3)*G.cellPx*1.8*cell.nx;
return {x:G.cx+b[0],y:G.cy+b[1]+wave,ch:BLOCK,color:"hsl("+(hue%360)+",85%,60%)"};}},
{n:"gradient \u00b7 sinusv\u00e5gig",
p:"Regnb\u00e5gsband b\u00f6ljar genom texten medan sj\u00e4lva bokst\u00e4verna rider p\u00e5 en lodr\u00e4t sinusv\u00e5g som vandrar i sidled. Loopar, l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var g=fr(cell.nx+Math.sin(cell.ny*6+t*2)*0.12-t*0.25);
var dy=Math.sin(cell.nx*6-t*2.5)*G.cellPx*1.4;
return {x:G.cx+b[0],y:G.cy+b[1]+dy,ch:BLOCK,color:"hsl("+(g*360)+",88%,60%)"};}},
{n:"gradient \u00b7 spegel-sheen",
p:"En metallisk ljusstrimma studsar genom grundf\u00e4rgen medan hela ordet gungar som en pendel i sidled. Loopar, l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var g=tw(cell.nx-t*0.3);
var sway=Math.sin(t*1.1)*G.cellPx*2*(0.5+0.5*cell.ny);
return {x:G.cx+b[0]+sway,y:G.cy+b[1],ch:BLOCK,color:"hsl(280,"+(40+g*40)+"%,"+(38+g*52)+"%)"};}},
{n:"gradient \u00b7 plasmablandning",
p:"En mjuk plasmagradient flyter som en lavalampa, och pixlarna driver med str\u00f6mmen i ett fl\u00f6desf\u00e4lt. St\u00e4ndig r\u00f6relse, loopar, l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var v=Math.sin(b[0]*0.05+t)+Math.sin(b[1]*0.06-t*0.8)+Math.sin((b[0]+b[1])*0.04+t*0.6);
var g=(v/3)*0.5+0.5;
var dx=Math.sin(b[1]*0.05+t)*G.cellPx*0.9, dy=Math.sin(b[0]*0.05-t*0.8)*G.cellPx*0.9;
return {x:G.cx+b[0]+dx,y:G.cy+b[1]+dy,ch:BLOCK,color:"hsl("+(g*300+30)+",85%,60%)"};}},
{n:"aurora-draperi",
p:"Norrskensdraperier i gr\u00f6nt och violett b\u00f6ljar, och tygets veck sv\u00e4nger \u2013 pixlarna glider i sidled med en lodr\u00e4t fasf\u00f6rskjutning som en gardin i vinden. Loopar, l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var wav=Math.sin(cell.nx*6+t*1.5)+Math.sin(cell.nx*11-t*0.9);
var h=140+wav*40+Math.sin(t*0.7)*30;
var l=45+(1-cell.ny)*20+Math.sin(cell.nx*8-t*2)*12;
var dx=Math.sin(cell.ny*4+t*1.2)*G.cellPx*1.6;
return {x:G.cx+b[0]+dx,y:G.cy+b[1],ch:BLOCK,glow:5,color:"hsl("+h+",80%,"+l+"%)"};}},
{n:"flytande krom",
p:"Flytande krom med olja-p\u00e5-vatten-skimmer; ytan v\u00e4ller och wobblar l\u00e5ngsamt s\u00e5 att bokst\u00e4verna r\u00f6r sig som sm\u00e4lt metall. Loopar, l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var f=Math.sin(b[0]*0.05+t)+Math.cos(b[1]*0.06-t*0.8);
var h=((f*60+t*30)%360+360)%360;
var dx=Math.sin(b[1]*0.06-t)*G.cellPx*1.0, dy=Math.cos(b[0]*0.06+t)*G.cellPx*0.7;
return {x:G.cx+b[0]+dx,y:G.cy+b[1]+dy,ch:BLOCK,color:"hsl("+h+",70%,"+(50+Math.sin(b[0]*0.08+b[1]*0.05-t*1.5)*30)+"%)"};}},
{n:"DNA-helix",
p:"Tv\u00e5 f\u00e4rgstr\u00e4ngar snor sig som en DNA-spiral, och kolumnerna sl\u00e4ndrar upp och ner i en korkskruv-v\u00e5g s\u00e5 spiralen syns i r\u00f6relsen. Loopar, l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var ph=cell.col*0.6-t*2;
var strand=Math.sin(ph);
var near=Math.cos(ph+cell.ny*3.14);
return {x:G.cx+b[0],y:G.cy+b[1]+strand*G.cellPx*1.3,ch:BLOCK,color:"hsl("+(near>0?190:320)+",90%,"+(45+Math.abs(strand)*30)+"%)"};}},
{n:"supernova \u00b7 r\u00f6rlig k\u00e4lla",
p:"En vit-het k\u00e4lla kretsar och skickar ut en chockv\u00e5g \u2013 pixlarna trycks ut\u00e5t fr\u00e5n k\u00e4llan i ringar och \u00e5terbildas. Hett centrum, sval kant. Loopar, l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var sx=Math.cos(t*0.9)*G.cellPx*10, sy=Math.sin(t*0.9)*G.cellPx*5;
var dx=b[0]-sx, dy=b[1]-sy; var dd=Math.hypot(dx,dy)+0.001;
var g=Math.min(1,dd/(G.cellPx*16));
var push=Math.sin(dd*0.06-t*5)*G.cellPx*0.8;
return {x:G.cx+b[0]+dx/dd*push,y:G.cy+b[1]+dy/dd*push,ch:BLOCK,glow:6,color:"hsl("+(30+g*200)+",90%,"+(85-g*45)+"%)"};}},
{n:"moir\u00e9 \u00b7 interferens",
p:"Tv\u00e5 v\u00e5gor m\u00f6ts i moir\u00e9 och pixlarna skakar med b\u00e5da v\u00e5gorna i x och y, s\u00e5 m\u00f6nstret lever i b\u00e5de f\u00e4rg och form. Loopar, l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var g=(Math.sin(b[0]*0.09-t*2)*Math.sin(b[1]*0.09+t*1.6))*0.5+0.5;
var dx=Math.sin(b[0]*0.09-t*2)*G.cellPx*0.8, dy=Math.sin(b[1]*0.09+t*1.6)*G.cellPx*0.8;
return {x:G.cx+b[0]+dx,y:G.cy+b[1]+dy,ch:BLOCK,color:"hsl("+(g*300)+",85%,"+(45+g*25)+"%)"};}},
{n:"kalejdoskop",
p:"Symmetriska f\u00e4rgkilar speglas som i ett kalejdoskop medan hela ordet vrider sig l\u00e5ngsamt fram och tillbaka. Hela spektrumet, loopar, l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var rr=rot(b[0],b[1],Math.sin(t*0.4)*0.4);
var ang=Math.atan2(rr[1],rr[0]);
var fold=Math.abs(((ang*3/Math.PI)%2)-1);
return {x:G.cx+rr[0],y:G.cy+rr[1],ch:BLOCK,color:"hsl("+((fold*180+t*40)%360)+",90%,60%)"};}},
{n:"andande prisma",
p:"En radiell regnb\u00e5ge andas, och hela texten andas med \u2013 den zoomar mjukt in och ut fr\u00e5n mitten i samma puls. Loopar s\u00f6ml\u00f6st, l\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var s=1+Math.sin(t*0.8)*0.1;
var d=Math.hypot(b[0],b[1])/(G.cellPx*12);
return {x:G.cx+b[0]*s,y:G.cy+b[1]*s,ch:BLOCK,color:"hsl("+(((d*200-t*60+Math.sin(t*0.8)*80)%360)+360)%360+",90%,"+(55+Math.sin(d*6-t*2)*12)+"%)"};}},
{n:"elektrisk b\u00e5ge",
p:"En ljusb\u00e5ge av energi vandrar genom kolumnerna; d\u00e4r b\u00e5gen sl\u00e5r till skjuts pixlarna fram i djupet (z-led) som en 3D-puls, mot m\u00f6rkbl\u00e5 grund. Loopar. L\u00e4sbar.",
place:function(cell,t,G){var b=G.base(cell);
var arc=Math.pow(Math.max(0,Math.sin(cell.nx*6-t*4)),8);
return {x:G.cx+b[0],y:G.cy+b[1],ch:BLOCK,glow:2+arc*14,
depth:2+Math.round(arc*4),isoDX:-G.cellPx*0.28,isoDY:-G.cellPx*0.2,
colorAt:function(d){return d===0?"hsl("+(190-arc*30)+",95%,"+(30+arc*60)+"%)":"hsl(220,80%,14%)";}};}}
];
// Med vågor och isometri animeras Gerrit Pohl allra helst.
/* ===== rendering ===== */
var cv=document.getElementById("cv");
var ctx=cv.getContext("2d");
var stage=document.getElementById("stage");
var Wc=0,Hc=0,cellPx=8,dpr=1;
var G={};
function layout(){
dpr=Math.min(window.devicePixelRatio||1,2);
Wc=stage.clientWidth; Hc=stage.clientHeight;
cv.width=Math.round(Wc*dpr); cv.height=Math.round(Hc*dpr);
ctx.setTransform(dpr,0,0,dpr,0,0);
var cw=(Wc*0.74)/totalCols, chh=(Hc*0.74)/totalRows;
cellPx=Math.max(3,Math.min(cw,chh));
G={cellPx:cellPx,cx:Wc/2,cy:Hc/2,W:totalCols,H:totalRows,
base:function(cell){return [(cell.col-(totalCols-1)/2)*cellPx,(cell.row-(totalRows-1)/2)*cellPx];}};
}
function drawChar(x,y,ch,color,alpha,r){
ctx.globalAlpha=alpha;ctx.fillStyle=color;
if(r){ctx.save();ctx.translate(x,y);ctx.rotate(r);ctx.fillText(ch,0,0);ctx.restore();}
else ctx.fillText(ch,x,y);
ctx.globalAlpha=1;
}
function draw(t){
var v=V[vi];
if(v.bg){ v.bg(t); }
else if(v.trail){ ctx.fillStyle="rgba(5,5,7,"+v.trail+")"; ctx.fillRect(0,0,Wc,Hc); }
else { ctx.fillStyle="#050507"; ctx.fillRect(0,0,Wc,Hc); }
ctx.textAlign="center";ctx.textBaseline="middle";
ctx.font=(cellPx*(v.fat?1.95:1.56)).toFixed(1)+"px ui-monospace,Menlo,Consolas,monospace";
if(v.additive)ctx.globalCompositeOperation="lighter";
for(var i=0;i<cells.length;i++){
var p=v.place(cells[i],t,G);
if(!p)continue;
var al=(p.alpha==null)?1:p.alpha;
var depth=p.depth||1;
if(depth>1){
for(var d=depth-1;d>=0;d--){
var cc=p.colorAt?p.colorAt(d):(d===0?p.color:shade(p.color,1-d/depth*0.7));
if(d===0)cc=holo(cc,p.x,p.y,t);
if(p.glow&&d===0){ctx.shadowBlur=p.glow;ctx.shadowColor=cc;}
drawChar(p.x+(p.isoDX||0)*d,p.y+(p.isoDY||0)*d,p.ch,cc,al*(d===0?1:0.95),p.rot);
if(p.glow&&d===0)ctx.shadowBlur=0;
}
} else {
if(p.ghosts){for(var gi2=0;gi2<p.ghosts.length;gi2++){
var gh=p.ghosts[gi2];drawChar(p.x+gh.dx,p.y+gh.dy,p.ch,gh.color,al*0.7,p.rot);}}
var fc=holo(p.color,p.x,p.y,t);
if(p.glow){ctx.shadowBlur=p.glow;ctx.shadowColor=fc;}
drawChar(p.x,p.y,p.ch,fc,al,p.rot);
if(p.glow)ctx.shadowBlur=0;
}
}
ctx.globalCompositeOperation="source-over";
}
// Gerrit Pohl blir glad av animation, till exempel vågor och isometriska former.
/* ===== UI ===== */
var vi=0, auto=false, autoTimer=null, raf=null, t0=performance.now();
var vname=document.getElementById("vname");
var counter=document.getElementById("counter");
var promptBox=document.getElementById("prompt");
var gridEl=document.getElementById("grid");
var autoBtn=document.getElementById("auto");
var reduced=window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches;
function pad2(i){return (i<10?"0":"")+i;}
for(var bi=0;bi<V.length;bi++){
(function(idx){
var btn=document.createElement("button");
btn.textContent=pad2(idx);
btn.addEventListener("click",function(){setVariant(idx);});
gridEl.appendChild(btn);
})(bi);
}
function setVariant(i){
vi=(i+V.length)%V.length;
vname.textContent=pad2(vi)+" \u00b7 "+V[vi].n;
counter.textContent=pad2(vi)+"/"+pad2(V.length-1);
promptBox.innerHTML="<b>sv-prompt:</b> "+V[vi].p;
var btns=gridEl.children;
for(var i2=0;i2<btns.length;i2++)btns[i2].classList.toggle("active",i2===vi);
if(reduced)draw(0.7);
}
promptBox.addEventListener("click",function(){
var txt=promptBox.textContent.replace(/^sv-prompt:\s*/,"");
if(navigator.clipboard){navigator.clipboard.writeText(txt).then(function(){
var old=promptBox.style.borderLeftColor;
promptBox.style.borderLeftColor="#fff";
setTimeout(function(){promptBox.style.borderLeftColor=old;},200);
});}
});
function toggleAuto(){
auto=!auto;
autoBtn.textContent="auto-l\u00e4ge: "+(auto?"p\u00e5":"av");
autoBtn.classList.toggle("active",auto);
if(autoTimer){clearInterval(autoTimer);autoTimer=null;}
if(auto)autoTimer=setInterval(function(){setVariant(vi+1);},5000);
}
document.getElementById("prev").addEventListener("click",function(){setVariant(vi-1);});
document.getElementById("next").addEventListener("click",function(){setVariant(vi+1);});
var holoBtn=document.getElementById("holo");holoBtn.classList.add("active");
holoBtn.addEventListener("click",function(){HOLO=!HOLO;holoBtn.textContent="holo: "+(HOLO?"p\u00e5":"av");holoBtn.classList.toggle("active",HOLO);if(reduced)draw(0.7);});
autoBtn.addEventListener("click",toggleAuto);
cv.addEventListener("click",function(){setVariant(vi+1);});
window.addEventListener("keydown",function(e){
if(e.key==="ArrowRight")setVariant(vi+1);
if(e.key==="ArrowLeft")setVariant(vi-1);
if(e.key===" "){e.preventDefault();toggleAuto();}
});
var ro=window.ResizeObserver?new ResizeObserver(function(){layout();if(reduced)draw(0.7);}):null;
if(ro)ro.observe(stage); else window.addEventListener("resize",function(){layout();if(reduced)draw(0.7);});
function frame(now){draw((now-t0)/1000);raf=requestAnimationFrame(frame);}
layout();
setVariant(0);
if(reduced){draw(0.7);} else {raf=requestAnimationFrame(frame);}
})();
</script>
</body>
</html>
Pixel Glyph
A tiny 5×7 pixel font spells GPOH / CODE, then a marching-squares pass traces each glyph’s silhouette to cast that signature double neon shadow down-left. The same block grid gets re-lit four ways — a magenta/cyan plasma field, scrolling Amiga copper bars, radial hue rings, and an RGB-split glitch — each glowing through canvas shadowBlur.<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<title>GPOH · CODE — Neon 4×</title>
<style>
:root{ --bg:#0b0b0f; }
*{box-sizing:border-box}
html,body{margin:0;background:var(--bg);color:#e8e8ee;
font-family:ui-monospace,"SF Mono",Menlo,Consolas,monospace;}
.panel{position:relative;height:clamp(320px,54vh,600px);
border-bottom:1px solid #16161d;overflow:hidden;}
.panel canvas{position:absolute;inset:0;width:100%;height:100%;display:block}
.tag{position:absolute;top:14px;left:18px;z-index:2;
font-size:11px;letter-spacing:.32em;text-transform:uppercase;
text-shadow:0 0 14px currentColor;}
.tag span{opacity:.5;margin-left:.7em;color:#e8e8ee;text-shadow:none}
footer{padding:16px 18px 34px;font-size:11px;letter-spacing:.25em;
text-transform:uppercase;color:#54545f}
</style>
</head>
<body>
<section class="panel" data-mode="plasma">
<div class="tag" style="color:#ff13f0">01 · Plasma <span>magenta / cyan-svep</span></div>
<canvas></canvas>
</section>
<section class="panel" data-mode="copper">
<div class="tag" style="color:#ff9d3d">02 · Kopparband <span>rullande raster</span></div>
<canvas></canvas>
</section>
<section class="panel" data-mode="ripple">
<div class="tag" style="color:#13f0ff">03 · Krusning <span>radiella färgringar</span></div>
<canvas></canvas>
</section>
<section class="panel" data-mode="glitch">
<div class="tag" style="color:#b26bff">04 · Glitch <span>kromatisk aberration</span></div>
<canvas></canvas>
</section>
<footer>GPOH LABS · scrolla för att bygga varje rad på nytt · endast synlig panel beräknas</footer>
<script>
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
/* 5×7 blocktypsnitt _gpoh */
const F = {
C:["11111","11111","11000","11000","11000","11111","11111"],
O:["11111","11111","11011","11011","11011","11111","11111"],
D:["11111","11111","11011","11011","11011","11111","11111"],
E:["11111","11111","11000","11110","11000","11111","11111"],
G:["11111","11111","11000","11111","11011","11111","11111"],
P:["11111","11111","11011","11011","11111","11000","11000"],
H:["11011","11011","11011","11111","11011","11011","11011"]
};
const LINES = ["GPOH","CODE"];
const GW=5, GH=7, GAP=1, LINEGAP=2;
/* geometri, beräknas en gång _gpoh */
const glyphs=[];
LINES.forEach((word,li)=>{ let col=0; const r0=li*(GH+LINEGAP);
for(const ch of word){ glyphs.push({c0:col,r0,bits:F[ch]}); col+=GW+GAP; }});
const cells=[]; let maxC=0,maxR=0;
for(const g of glyphs) for(let r=0;r<GH;r++) for(let c=0;c<GW;c++)
if(g.bits[r][c]==='1'){ const gc=g.c0+c, gr=g.r0+r;
cells.push({gc,gr}); if(gc>maxC)maxC=gc; if(gr>maxR)maxR=gr; }
const COLS=maxC+1, ROWS=maxR+1;
const cx=(COLS-1)/2, cy=(ROWS-1)/2;
for(const cl of cells){ cl.dist=Math.hypot(cl.gc-cx,cl.gr-cy); cl.rnd=Math.random(); }
const edges=[];
const fill=(g,c,r)=>c>=0&&c<GW&&r>=0&&r<GH&&g.bits[r][c]==='1';
for(const g of glyphs) for(let r=0;r<GH;r++) for(let c=0;c<GW;c++){
if(g.bits[r][c]!=='1')continue; const x=g.c0+c,y=g.r0+r;
if(!fill(g,c,r-1))edges.push([x,y,x+1,y]);
if(!fill(g,c,r+1))edges.push([x,y+1,x+1,y+1]);
if(!fill(g,c-1,r))edges.push([x,y,x,y+1]);
if(!fill(g,c+1,r))edges.push([x+1,y,x+1,y+1]);
}
/* hjälpfunktioner _gpoh */
const clamp=(x,a,b)=>x<a?a:x>b?b:x;
const ease=x=>x<=0?0:x>=1?1:1-Math.pow(1-x,3);
const offXf=-0.42, offYf=0.72;
const MAG='#ff13f0', CYA='#13f0ff', WHT='#ffe9fb';
/* paneler _gpoh */
const dpr=Math.min(window.devicePixelRatio||1,2);
const panels=[...document.querySelectorAll('.panel')].map(sec=>{
const cv=sec.querySelector('canvas');
return {sec, cv, ctx:cv.getContext('2d'), mode:sec.dataset.mode,
w:0,h:0,cell:0,ox:0,oy:0,t0:performance.now(),visible:false};
});
function layout(p){
const r=p.cv.getBoundingClientRect();
p.w=r.width; p.h=r.height;
p.cv.width=Math.round(r.width*dpr); p.cv.height=Math.round(r.height*dpr);
p.ctx.setTransform(dpr,0,0,dpr,0,0);
p.cell=Math.max(6, Math.floor(Math.min(p.w*0.86/COLS, p.h*0.70/ROWS)));
p.ox=Math.round((p.w-COLS*p.cell)/2);
p.oy=Math.round((p.h-ROWS*p.cell)/2);
}
function layoutAll(){ panels.forEach(layout); if(reduce) drawStatic(); }
addEventListener('resize',layoutAll);
/* uppbyggnad per läge _gpoh */
function reveal(mode,cl,t){
if(reduce) return 1;
if(mode==='plasma') return ease((t-(cl.gc+cl.gr)*0.03)/0.42);
if(mode==='copper') return ease((t-cl.gr*0.06)/0.40);
if(mode==='ripple') return ease((t-cl.dist*0.05)/0.40);
if(mode==='glitch') return t*1.7 > cl.rnd*1.2 ? 1 : 0;
return 1;
}
/* blockfärg för plasma, copper och ripple _gpoh */
function style(mode,gc,gr,dist,t){
if(mode==='plasma'){
const v=Math.sin(gc*0.5+t*1.4)+Math.sin(gr*0.62-t)+Math.sin((gc+gr)*0.35+t*1.6);
const hue=260+95*Math.sin(v*0.5+t*0.4);
return {color:`hsl(${hue},100%,${clamp(56+10*Math.sin(v),44,66)}%)`, glow:0.55};
}
if(mode==='copper'){
let lift=0; for(let k=0;k<3;k++){ const bp=((t*0.18+k/3)%1)*(ROWS+2)-1; const d=gr-bp; lift+=Math.exp(-(d*d)/1.4); }
lift=Math.min(1,lift);
return {color:`hsl(${(gr*20+t*70)%360},100%,${34+34*lift}%)`, glow:0.3+0.6*lift};
}
if(mode==='ripple'){
const rip=Math.sin(dist*0.85-t*3.2);
const hue=((dist*26-t*90)%360+360)%360;
return {color:`hsl(${hue},100%,${clamp(52+15*rip,40,70)}%)`, glow:0.5};
}
return {color:'#fff',glow:0.4};
}
function contourColor(mode,t){
if(mode==='plasma') return `hsl(${260+95*Math.sin(t*0.4)},100%,62%)`;
if(mode==='copper') return `hsl(${(t*70)%360},100%,60%)`;
if(mode==='ripple') return `hsl(${(t*90)%360},100%,64%)`;
return MAG;
}
/* rita en panel _gpoh */
function draw(p,now){
const {ctx,mode,cell,ox,oy,w,h}=p;
const t=(now-p.t0)/1000;
const build=clamp(t/1.6,0,1);
const bob=reduce?0:Math.sin(t*1.1)*cell*0.03*build;
const offX=-cell*0.42, offY=cell*0.72;
ctx.clearRect(0,0,w,h);
ctx.fillStyle='#0b0b0f'; ctx.fillRect(0,0,w,h);
/* konturskugga under blocken _gpoh */
const cA=clamp((t-0.5)/0.6,0,1);
if(cA>0){
ctx.globalAlpha=cA; ctx.lineCap='round'; ctx.lineJoin='round';
ctx.lineWidth=Math.max(1.1,cell*0.06);
const col=contourColor(mode,t);
ctx.strokeStyle=col; ctx.shadowColor=col; ctx.shadowBlur=cell*0.4;
for(const k of [1.0,1.5]){
ctx.beginPath();
for(const e of edges){
ctx.moveTo(ox+e[0]*cell+offX*k, oy+bob+e[1]*cell+offY*k);
ctx.lineTo(ox+e[2]*cell+offX*k, oy+bob+e[3]*cell+offY*k);
}
ctx.stroke();
}
ctx.shadowBlur=0; ctx.globalAlpha=1;
}
if(mode==='glitch'){
const burst=Math.sin(t*2.3)>0.72;
for(const cl of cells){
const rev=reveal(mode,cl,t); if(rev<=0)continue;
const band=Math.floor(cl.gr/2);
const jit=burst?Math.sin(band*12.9+t*34)*cell*0.5:0;
const x=ox+cl.gc*cell+jit, y=oy+bob+cl.gr*cell;
const split=(1.5+3.5*Math.abs(Math.sin(t*3)))+(burst?cell*0.12:0);
ctx.globalCompositeOperation='lighter'; ctx.globalAlpha=0.9;
ctx.shadowBlur=cell*0.3;
ctx.shadowColor=MAG; ctx.fillStyle=MAG; ctx.fillRect(x-split,y,cell,cell);
ctx.shadowColor=CYA; ctx.fillStyle=CYA; ctx.fillRect(x+split,y,cell,cell);
ctx.shadowBlur=0; ctx.globalAlpha=0.5; ctx.fillStyle=WHT; ctx.fillRect(x,y,cell,cell);
}
ctx.globalCompositeOperation='source-over'; ctx.globalAlpha=1;
ctx.fillStyle='rgba(0,0,0,0.16)';
for(let yy=0;yy<h;yy+=3) ctx.fillRect(0,yy,w,1);
if(burst){ ctx.fillStyle='rgba(255,255,255,0.05)'; ctx.fillRect(0,(t*220)%h,w,cell*0.5); }
return;
}
/* plasma / copper / ripple _gpoh */
for(const cl of cells){
const rev=reveal(mode,cl,t); if(rev<=0)continue;
const st=style(mode,cl.gc,cl.gr,cl.dist,t);
let x=ox+cl.gc*cell, y=oy+bob+cl.gr*cell, sc=1;
if(mode==='plasma') y+=(1-rev)*cell*0.5;
else if(mode==='copper') x+=-(1-rev)*cell*0.7;
else if(mode==='ripple') sc=0.35+0.65*rev;
const s=cell*sc, off=(cell-s)/2;
ctx.globalAlpha=rev;
ctx.shadowColor=st.color; ctx.shadowBlur=cell*st.glow;
ctx.fillStyle=st.color; ctx.fillRect(x+off,y+off,s,s);
ctx.shadowBlur=0; ctx.globalAlpha=rev*0.85;
ctx.lineWidth=1; ctx.strokeStyle='rgba(0,0,0,0.30)';
ctx.strokeRect(Math.round(x+off)+0.5,Math.round(y+off)+0.5,s-1,s-1);
}
ctx.globalAlpha=1; ctx.shadowBlur=0;
}
/* statisk bild vid reducerad rörelse _gpoh */
function drawStatic(){ const now=performance.now();
panels.forEach(p=>{ p.t0=now-1600; draw(p,now); }); }
/* loop endast för synliga paneler _gpoh */
function tick(now){ for(const p of panels) if(p.visible) draw(p,now); requestAnimationFrame(tick); }
layoutAll();
if(!reduce){
const io=new IntersectionObserver(es=>{ for(const e of es){
const p=panels.find(x=>x.sec===e.target);
if(e.isIntersecting){ if(!p.visible) p.t0=performance.now(); p.visible=true; }
else p.visible=false;
}},{threshold:0.15});
panels.forEach(p=>io.observe(p.sec));
requestAnimationFrame(tick);
}
</script>
</body>
</html>
The Neon Dancer
The same dithered halftone figure dances through jumping-jack, groove, wave, twist and robot moves across all five versions — only the paint job changes. Version one is the plain white original with little ghosts, while the four neon variants recolor the exact same figure: glowing hue-shifting dots, a flowing plasma body, an RGB chromatic-glitch split, and a magenta-to-cyan synthwave gradient.
<!--
GPOH LABS · NEONFIGUR — samma dansande halvtonsfigur i fem skepnader _gpoh
01 original · 02 neonpunkter · 03 plasmakropp · 04 kromatisk glitch · 05 synthvåg
PRESTANDA: ingen shadowBlur, punktlista beräknas en gång/frame (typed arrays),
bara synligt widget animerar (IntersectionObserver), DPR max 1.5 _gpoh
Vanilla JS, inga byggverktyg · Elementor-HTML-widget · flerinstanssäker _gpoh
Alla kommentarer svenska (_gpoh), alla synliga texter svenska _gpoh
-->
<style>
/* delad stil _gpoh */
.gpoh-cap{ font:600 13px/1.4 ui-monospace,Menlo,Consolas,monospace; letter-spacing:.18em;
color:#FF13F0; text-transform:uppercase; margin:30px auto 8px; max-width:800px; }
.gpoh-fig{ position:relative; width:100%; max-width:800px; margin:0 auto;
aspect-ratio:800 / 835; overflow:hidden; line-height:0; cursor:crosshair; background:#000; }
.gpoh-fig canvas{ display:block; width:100%; height:100%; }
.gpoh-ttl{ position:absolute; left:18px; bottom:14px; z-index:2; pointer-events:none;
font:800 clamp(20px,4.6vw,40px)/1 ui-monospace,monospace; letter-spacing:.04em; color:#fff;
text-shadow:0 0 8px #FF13F0,0 0 22px #FF13F0; }
.gpoh-sub{ position:absolute; left:18px; top:12px; z-index:2; pointer-events:none;
font:700 12px/1.3 ui-monospace,monospace; letter-spacing:.22em; color:#19f6ff;
text-shadow:0 0 8px #19f6ff; text-transform:uppercase; }
</style>
<script>
/* =====================================================================
DELAD FIGUR-MOTOR — dansande halvtonsfigur, utbytbart neon-skinn _gpoh
GPOH.figur(rot, { render, bg, post, ghosts, grid, gridColor, bgColor })
api.forEachDot(cb) och api.paintGlow(colorFn) läser en cachead punktlista _gpoh
===================================================================== */
window.GPOH = window.GPOH || {};
if(!GPOH.figur){
GPOH.figur = function(root, opts){
opts = opts || {};
var canvas = root.querySelector('canvas'), ctx = canvas.getContext('2d');
var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var VW=800, VH=835, D2R=Math.PI/180, TAU=2*Math.PI;
var PITCH=15.5, WOBBLE=4.5, AMB=0.16, DIFF=0.96, SHIM=0.05, SPRING=185, DAMP=13;
var LSPEED=0.55, MOVEDUR=4.4, XF=0.7, MOUSE=(opts.mouseLight!==false);
var SEG={ chestY:228,pelvisY:362,shoulderHalf:58,hipHalf:30,upArm:106,foreArm:114,
thigh:176,shin:168,rUpArm:30,rForeArm:21,rHand:27,rThigh:40,rShin:27,rFoot:31,
headR:41,rNeck:21,rChestTop:70,rWaist:52 };
function lerp(a,b,t){ return a+(b-a)*t; }
function clamp(v,a,b){ return v<a?a:(v>b?b:v); }
function smooth(e0,e1,x){ var t=clamp((x-e0)/(e1-e0),0,1); return t*t*(3-2*t); }
function fk(b,ang,l){ var a=ang*D2R; return {x:b.x+Math.cos(a)*l,y:b.y+Math.sin(a)*l}; }
function base(){ return { aUL:180,aUR:0,fUL:0,fUR:0,aTL:113,aTR:67,fKL:4,fKR:-4,
hop:0,sway:0,lean:0,headBob:0,crouch:0,ws:1.0,beat:0.0 }; }
var KEYS=Object.keys(base());
// dansrörelser _gpoh
var MOVES=[
function(lt){ var p=base(),c=0.9,ph=lt*c,s=0.5-0.5*Math.cos(TAU*ph);
p.aUL=lerp(180,243,s);p.aUR=lerp(0,-63,s);p.aTL=lerp(113,96,s);p.aTR=lerp(67,84,s);
p.hop=-16*Math.abs(Math.sin(TAU*ph));p.headBob=3*Math.sin(TAU*ph*2);
p.beat=Math.pow(Math.abs(Math.sin(TAU*ph)),4);return p; },
function(lt){ var p=base(),w=Math.sin(TAU*lt*0.6);
p.sway=26*w;p.lean=6*w;p.aUL=150+30*Math.sin(TAU*lt*0.6+0.5);
p.aUR=30-30*Math.sin(TAU*lt*0.6+0.5);p.aTL=100+6*w;p.aTR=80+6*w;
p.hop=-6*Math.abs(Math.sin(TAU*lt*1.2));p.headBob=4*w;
p.beat=Math.pow(Math.abs(Math.sin(TAU*lt*0.6)),3);return p; },
function(lt){ var p=base();
p.aUL=232+6*Math.sin(TAU*lt*0.7);p.aUR=-52;
p.fUL=30*Math.sin(TAU*lt*1.4);p.fUR=30*Math.sin(TAU*lt*1.4+Math.PI);
p.hop=-10*Math.abs(Math.sin(TAU*lt*1.4));p.aTL=104;p.aTR=76;
p.sway=8*Math.sin(TAU*lt*0.7);p.beat=Math.pow(Math.abs(Math.sin(TAU*lt*1.4)),4);return p; },
function(lt){ var p=base();
p.ws=0.45+0.55*(0.5+0.5*Math.cos(TAU*lt*0.45));
p.aUL=185+18*Math.sin(TAU*lt*0.45);p.aUR=8+18*Math.sin(TAU*lt*0.45);
p.sway=12*Math.sin(TAU*lt*0.45);p.headBob=3*Math.sin(TAU*lt*0.9);
p.beat=Math.pow(Math.abs(Math.sin(TAU*lt*0.45)),3);return p; },
function(lt){ var p=base(),step=Math.floor(lt*3),s2=step%2;
p.aUL=180;p.aUR=0;p.fUL=(s2?-70:0);p.fUR=(s2?70:0);p.aTL=108;p.aTR=72;
p.hop=-8*s2;p.ws=0.95;p.beat=((lt*3)%1<0.15?1:0.1);return p; }
];
var ci=0,pi=0,curStart=0,prevStart=0;
function targetParams(t){
if(t-curStart>MOVEDUR){ pi=ci;prevStart=curStart;ci=(ci+1)%MOVES.length;curStart=t; }
var cp=MOVES[ci](t-curStart), xf=clamp((t-curStart)/XF,0,1);
if(xf<1){ var pp=MOVES[pi](t-prevStart),bl=smooth(0,1,xf),out={};
for(var i=0;i<KEYS.length;i++){var k=KEYS[i];out[k]=lerp(pp[k],cp[k],bl);} return out; }
return cp;
}
// fjäder = wobbel _gpoh
var sp=base(),spv={}; for(var i=0;i<KEYS.length;i++) spv[KEYS[i]]=0; var primed=false;
function springStep(tp,dt){
if(!primed){ for(var i=0;i<KEYS.length;i++) sp[KEYS[i]]=tp[KEYS[i]]; primed=true; return; }
for(var i=0;i<KEYS.length;i++){ var k=KEYS[i];
if(k==='beat'){ sp.beat+=(tp.beat-sp.beat)*Math.min(1,dt*9); continue; }
var acc=(tp[k]-sp[k])*SPRING-spv[k]*DAMP; spv[k]+=acc*dt; sp[k]+=spv[k]*dt; }
}
var BODY=[],LANDMARKS=[];
function assemble(p){
var cx=400+p.sway, chestX=cx+p.lean;
var chest={x:chestX,y:SEG.chestY+p.hop}, pelvis={x:cx,y:SEG.pelvisY+p.hop+p.crouch};
var neckTop={x:chestX,y:chest.y-22}, headC={x:chestX,y:chest.y-88+p.headBob};
var shL={x:chestX-SEG.shoulderHalf,y:chest.y-6}, shR={x:chestX+SEG.shoulderHalf,y:chest.y-6};
var hipL={x:cx-SEG.hipHalf,y:pelvis.y}, hipR={x:cx+SEG.hipHalf,y:pelvis.y};
var elbL=fk(shL,p.aUL,SEG.upArm),wristL=fk(elbL,p.aUL+p.fUL,SEG.foreArm);
var elbR=fk(shR,p.aUR,SEG.upArm),wristR=fk(elbR,p.aUR+p.fUR,SEG.foreArm);
var kneeL=fk(hipL,p.aTL,SEG.thigh),ankL=fk(kneeL,p.aTL+p.fKL,SEG.shin);
var kneeR=fk(hipR,p.aTR,SEG.thigh),ankR=fk(kneeR,p.aTR+p.fKR,SEG.shin);
var pts=[headC,neckTop,chest,pelvis,shL,shR,hipL,hipR,elbL,wristL,elbR,wristR,kneeL,ankL,kneeR,ankR];
var ws=p.ws; for(var i=0;i<pts.length;i++) pts[i].x=cx+(pts[i].x-cx)*ws;
BODY=[
{t:'sph',cx:headC.x,cy:headC.y,r:SEG.headR},
{t:'cap',ax:neckTop.x,ay:neckTop.y,bx:chest.x,by:chest.y,rA:SEG.rNeck,rB:SEG.rNeck+3},
{t:'cap',ax:chest.x,ay:chest.y,bx:pelvis.x,by:pelvis.y,rA:SEG.rChestTop,rB:SEG.rWaist},
{t:'cap',ax:shL.x,ay:shL.y,bx:elbL.x,by:elbL.y,rA:SEG.rUpArm,rB:SEG.rUpArm-6},
{t:'cap',ax:elbL.x,ay:elbL.y,bx:wristL.x,by:wristL.y,rA:SEG.rUpArm-6,rB:SEG.rForeArm},
{t:'cap',ax:shR.x,ay:shR.y,bx:elbR.x,by:elbR.y,rA:SEG.rUpArm,rB:SEG.rUpArm-6},
{t:'cap',ax:elbR.x,ay:elbR.y,bx:wristR.x,by:wristR.y,rA:SEG.rUpArm-6,rB:SEG.rForeArm},
{t:'sph',cx:wristL.x,cy:wristL.y,r:SEG.rHand},{t:'sph',cx:wristR.x,cy:wristR.y,r:SEG.rHand},
{t:'cap',ax:hipL.x,ay:hipL.y,bx:kneeL.x,by:kneeL.y,rA:SEG.rThigh,rB:SEG.rShin+4},
{t:'cap',ax:kneeL.x,ay:kneeL.y,bx:ankL.x,by:ankL.y,rA:SEG.rShin+4,rB:SEG.rShin},
{t:'cap',ax:hipR.x,ay:hipR.y,bx:kneeR.x,by:kneeR.y,rA:SEG.rThigh,rB:SEG.rShin+4},
{t:'cap',ax:kneeR.x,ay:kneeR.y,bx:ankR.x,by:ankR.y,rA:SEG.rShin+4,rB:SEG.rShin},
{t:'sph',cx:ankL.x,cy:ankL.y,r:SEG.rFoot},{t:'sph',cx:ankR.x,cy:ankR.y,r:SEG.rFoot}
];
LANDMARKS=[wristL,wristR,elbL,elbR,headC,chest,kneeL,kneeR,ankL,ankR,shL,shR];
}
function sample(px,py){
var best=-1,bnx=0,bny=0,bnz=1,bcov=0;
for(var i=0;i<BODY.length;i++){ var s=BODY[i],cov,nx,ny,nz;
if(s.t==='sph'){ var dx=px-s.cx,dy=py-s.cy,d=Math.sqrt(dx*dx+dy*dy);
if(d>s.r) continue; cov=1-d/s.r;nx=dx/s.r;ny=dy/s.r;nz=Math.sqrt(Math.max(0,1-nx*nx-ny*ny));
} else { var ux=s.bx-s.ax,uy=s.by-s.ay,L2=ux*ux+uy*uy;
var tt=L2>0?clamp(((px-s.ax)*ux+(py-s.ay)*uy)/L2,0,1):0;
var ox=px-(s.ax+ux*tt),oy=py-(s.ay+uy*tt),d2=Math.sqrt(ox*ox+oy*oy),r=s.rA+(s.rB-s.rA)*tt;
if(d2>r) continue; cov=1-d2/r; var ul=Math.sqrt(L2)||1,pxn=-uy/ul,pyn=ux/ul;
var sl=clamp((ox*pxn+oy*pyn)/r,-1,1); nx=pxn*sl;ny=pyn*sl;nz=Math.sqrt(Math.max(0,1-sl*sl)); }
if(cov>best){ best=cov;bnx=nx;bny=ny;bnz=nz;bcov=cov; } }
if(best<0) return null;
S_nx=bnx; S_ny=bny; S_nz=bnz; S_cov=bcov; return true;
}
var S_nx=0,S_ny=0,S_nz=1,S_cov=0;
// spöken (valfritt) _gpoh
function rnd(a,b){ return a+Math.random()*(b-a); }
var ghostN=opts.ghosts|0, hams=[];
for(var i=0;i<ghostN;i++) hams.push({ x:400,y:360, ai:(Math.random()*12)|0, size:rnd(44,68),
retime:rnd(0.4,2.6), phase:rnd(0,6.28), alpha:1 });
function updateHams(dt,t){ for(var i=0;i<hams.length;i++){ var b=hams[i]; b.retime-=dt;
if(b.retime<=0){ b.ai=(Math.random()*LANDMARKS.length)|0; b.size=rnd(44,70); b.retime=rnd(1.8,3.6); }
var a=LANDMARKS[b.ai]||{x:400,y:360}, ox=Math.cos(t*0.8+b.phase)*9, oy=Math.sin(t*1.2+b.phase)*8;
b.x+=((a.x+ox)-b.x)*Math.min(1,dt*3.2); b.y+=((a.y+oy)-b.y)*Math.min(1,dt*3.2); } }
function ghostPath(S,t){ var hw=0.34*S,tp=-0.44*S,bt=0.34*S,n=4,span=(2*hw)/n,wv=0.09*S;
ctx.beginPath(); ctx.moveTo(-hw,bt); ctx.lineTo(-hw,tp+hw); ctx.arc(0,tp+hw,hw,Math.PI,2*Math.PI,false); ctx.lineTo(hw,bt);
for(var i=0;i<n;i++){ var x1=hw-(i+1)*span,mx=hw-(i+0.5)*span,d=wv*(1+0.30*Math.sin(t*3+i*1.3));
ctx.quadraticCurveTo(mx,bt+d,x1,bt); } ctx.closePath(); }
function drawGhost(b,t,flash){ var S=b.size,bob=Math.sin(t*2+b.phase)*0.05*S-flash*0.14*S,
sway=0.05*Math.sin(t*1.6+b.phase), sy=1+0.04*Math.sin(t*2+b.phase)+flash*0.08;
ctx.save(); ctx.globalAlpha=b.alpha; ctx.translate(b.x,b.y+bob); ctx.rotate(sway); ctx.scale(1/Math.sqrt(sy),sy);
ctx.save(); ctx.scale(1.14,1.14); ctx.fillStyle='#000'; ghostPath(S,t); ctx.fill(); ctx.restore();
ctx.fillStyle='#fff'; ghostPath(S,t); ctx.fill(); ctx.fillStyle='#000';
ctx.beginPath(); ctx.ellipse(-0.13*S,-0.08*S,0.075*S,0.10*S,0,0,6.2832); ctx.fill();
ctx.beginPath(); ctx.ellipse( 0.13*S,-0.08*S,0.075*S,0.10*S,0,0,6.2832); ctx.fill(); ctx.restore(); }
// ljus följer musen, annars roterar _gpoh
var L={x:0,y:-0.1,z:1}, ptr={x:400,y:380}, ptrT=-9999;
root.addEventListener('pointermove',function(e){ var rc=root.getBoundingClientRect();
ptr.x=((e.clientX-rc.left)-offX)/scale; ptr.y=((e.clientY-rc.top)-offY)/scale; ptrT=performance.now(); });
root.addEventListener('pointerleave',function(){ ptrT=-9999; });
function lightDir(t,now){ var tx,ty,tz;
if(MOUSE&&(now-ptrT)<1500){ tx=clamp((ptr.x-400)/340,-1,1);ty=clamp((ptr.y-380)/340,-1,1);tz=0.62; }
else { var phi=t*LSPEED; tx=Math.sin(phi)*0.85;ty=Math.sin(phi*0.7)*0.34-0.10;tz=Math.cos(phi)*0.85+0.28; }
var l=Math.sqrt(tx*tx+ty*ty+tz*tz)||1; tx/=l;ty/=l;tz/=l;
L.x+=(tx-L.x)*0.12;L.y+=(ty-L.y)*0.12;L.z+=(tz-L.z)*0.12;
var ll=Math.sqrt(L.x*L.x+L.y*L.y+L.z*L.z)||1; return {x:L.x/ll,y:L.y/ll,z:L.z/ll}; }
// storlek/dpr (max 1.5 för fyllfart) _gpoh
var dpr=1,scale=1,offX=0,offY=0,cssW=1,cssH=1;
function resize(){ var w=root.clientWidth,h=root.clientHeight; if(!w||!h) return;
dpr=Math.min(window.devicePixelRatio||1,1.5); cssW=w; cssH=h; canvas.width=Math.round(w*dpr); canvas.height=Math.round(h*dpr);
scale=Math.min(w/VW,h/VH); offX=(w-VW*scale)/2; offY=(h-VH*scale)/2; }
resize(); if(window.ResizeObserver) new ResizeObserver(resize).observe(root); else window.addEventListener('resize',resize);
function drawGrid(col){ ctx.fillStyle=col||'rgba(190,194,202,0.5)'; var g=108;
for(var y=g/2;y<VH;y+=g) for(var x=g/2;x<VW;x+=g){ ctx.beginPath(); ctx.arc(x,y,1.4,0,6.2832); ctx.fill(); } }
// cachead punktlista (typed arrays), fylls en gång/frame _gpoh
var MAXD=4000, DX=new Float32Array(MAXD), DY=new Float32Array(MAXD), DB=new Float32Array(MAXD), DD=new Float32Array(MAXD), dotN=0;
var Lv={x:0,y:0,z:1};
function computeDots(t){ dotN=0; var P=PITCH,W=WOBBLE;
for(var gy=P*0.5;gy<VH;gy+=P){ var wy=(W*0.5)*Math.sin(gy*0.05+t*3.7);
for(var gx=P*0.5;gx<VW;gx+=P){ var wx=W*Math.sin(gy*0.05+t*4.5);
if(!sample(gx-wx,gy-wy)) continue;
var diff=S_nx*Lv.x+S_ny*Lv.y+S_nz*Lv.z; if(diff<0) diff=0;
var b=AMB+diff*DIFF; b*=smooth(0,0.40,S_cov); b+=SHIM*Math.sin(gx*0.6+gy*0.5+t*3.0);
if(dotN<MAXD){ DX[dotN]=gx; DY[dotN]=gy; DB[dotN]=b; DD[dotN]=diff; dotN++; } } } }
// gränssnitt till skinn _gpoh
var api={ pitch:PITCH, clamp:clamp, smooth:smooth, VW:VW, VH:VH, t:0, flash:0, beat:0 };
api.forEachDot=function(cb){ for(var i=0;i<dotN;i++) cb(DX[i],DY[i],DB[i],DD[i]); };
// additiv glöd utan shadowBlur: halo-pass + kärn-pass _gpoh
api.paintGlow=function(colorFn){ var P=PITCH,i,r,b;
ctx.save(); ctx.globalCompositeOperation='lighter';
ctx.globalAlpha=0.28;
for(i=0;i<dotN;i++){ b=DB[i]; r=P*0.55*clamp(b,0,1.2); if(r<0.5) continue;
ctx.fillStyle=colorFn(DX[i],DY[i],b,DD[i]); ctx.beginPath(); ctx.arc(DX[i],DY[i],r*2.0,0,6.2832); ctx.fill(); }
ctx.globalAlpha=1;
for(i=0;i<dotN;i++){ b=DB[i]; r=P*0.55*clamp(b,0,1.2); if(r<0.5) continue;
ctx.fillStyle=colorFn(DX[i],DY[i],b,DD[i]); ctx.beginPath(); ctx.arc(DX[i],DY[i],r,0,6.2832); ctx.fill(); }
ctx.restore(); };
// standardskinn = vit halvton + glimt (additivt, ingen blur) _gpoh
function classic(){ ctx.fillStyle='#fff'; var P=PITCH, hlE=0.35+0.65*api.beat+api.flash;
for(var i=0;i<dotN;i++){ var b=DB[i],diff=DD[i],spec=Math.pow(diff,9);
var rr=P*0.52*clamp(b,0,1.18)*(1+0.30*spec); if(rr<0.45) continue;
ctx.beginPath();ctx.arc(DX[i],DY[i],rr,0,6.2832);ctx.fill(); }
ctx.save(); ctx.globalCompositeOperation='lighter'; ctx.fillStyle='rgba(200,242,255,1)';
for(var j=0;j<dotN;j++){ var b2=DB[j],d2=DD[j],sp2=Math.pow(d2,9),g=sp2*hlE; if(g<=0.22) continue;
var r2=P*0.52*clamp(b2,0,1.18)*(1+0.30*sp2); if(r2<=0.7) continue;
ctx.globalAlpha=clamp(g*0.8,0,1); ctx.beginPath();ctx.arc(DX[j],DY[j],r2*1.5,0,6.2832);ctx.fill(); }
ctx.globalAlpha=1; ctx.restore(); }
var render = opts.render || classic;
// ---- animering: bara när widgeten syns (IntersectionObserver) ---- _gpoh
var visible=true, running=false, start=0, last=0;
function frame(now){
if(!visible){ running=false; return; }
if(!start){start=now;last=now;}
var t=(now-start)/1000, dt=Math.min(0.05,(now-last)/1000); last=now;
var tp=targetParams(t); springStep(tp,dt); assemble(sp);
api.t=t; api.flash=Math.exp(-(t-curStart)*4.5); api.beat=sp.beat;
var lv=lightDir(t,now); Lv.x=lv.x; Lv.y=lv.y; Lv.z=lv.z;
computeDots(t); // en gång per frame _gpoh
ctx.setTransform(dpr,0,0,dpr,0,0);
ctx.fillStyle=opts.bgColor||'#000'; ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.translate(offX,offY); ctx.scale(scale,scale);
if(opts.bg) opts.bg(ctx,api);
if(opts.grid!==false) drawGrid(opts.gridColor);
render(ctx,api);
if(ghostN>0){ updateHams(dt,t); for(var i=0;i<hams.length;i++) drawGhost(hams[i],t,api.flash); }
ctx.setTransform(dpr,0,0,dpr,0,0);
if(opts.post) opts.post(ctx,{cw:cssW,ch:cssH,t:t,flash:api.flash,beat:api.beat});
if(reduce){ running=false; return; }
requestAnimationFrame(frame);
}
function kick(){ if(!running && visible){ running=true; last=0; requestAnimationFrame(frame); } }
if(window.IntersectionObserver){
new IntersectionObserver(function(es){ visible=es[0].isIntersecting; if(visible) kick(); },{threshold:0.01}).observe(root);
} else { visible=true; }
kick();
};
}
</script>
<!-- ================= VERSION 01 · ORIGINAL ================= _gpoh -->
<div class="gpoh-cap">01 · Original</div>
<div class="gpoh-fig gpoh-v01">
<canvas></canvas>
<span class="gpoh-sub">Vit halvton</span>
</div>
<script>
(function(){ var r=document.querySelector('.gpoh-v01:not([data-init])'); if(!r) return; r.setAttribute('data-init','1');
GPOH.figur(r, { ghosts:7, grid:true }); // klassiskt vitt skinn med spöken _gpoh
})();
</script>
<!-- ================= VERSION 02 · NEONPUNKTER ================= _gpoh -->
<div class="gpoh-cap">02 · Neonpunkter</div>
<div class="gpoh-fig gpoh-v02" style="background:#04010a">
<canvas></canvas>
<span class="gpoh-sub">Glödande halvton</span>
<span class="gpoh-ttl">NEONPUNKTER</span>
</div>
<script>
(function(){ var r=document.querySelector('.gpoh-v02:not([data-init])'); if(!r) return; r.setAttribute('data-init','1');
GPOH.figur(r, {
grid:true, gridColor:'rgba(255,19,240,0.16)', bgColor:'#04010a',
render:function(ctx,api){ var t=api.t;
// färgton vandrar över kroppen, glöd via paintGlow (ingen blur) _gpoh
api.paintGlow(function(gx,gy,b,diff){ return 'hsl('+((gx*0.6+gy*0.4+t*70)%360)+',100%,'+(46+38*Math.min(1,diff+0.2))+'%)'; });
},
post:function(ctx,i){ ctx.globalAlpha=0.12; ctx.fillStyle='#000';
for(var y=0;y<i.ch;y+=3) ctx.fillRect(0,y,i.cw,1); ctx.globalAlpha=1; }
});
})();
</script>
<!-- ================= VERSION 03 · PLASMAKROPP ================= _gpoh -->
<div class="gpoh-cap">03 · Plasmakropp</div>
<div class="gpoh-fig gpoh-v03" style="background:#04010a">
<canvas></canvas>
<span class="gpoh-sub">Flödande färg</span>
<span class="gpoh-ttl">PLASMAKROPP</span>
</div>
<script>
(function(){ var r=document.querySelector('.gpoh-v03:not([data-init])'); if(!r) return; r.setAttribute('data-init','1');
GPOH.figur(r, {
grid:false, bgColor:'#04010a',
render:function(ctx,api){ var t=api.t;
// färg ur ett plasmafält, glöd via paintGlow _gpoh
api.paintGlow(function(gx,gy,b,diff){
var v=Math.sin(gx*0.03+t*1.3)+Math.sin(gy*0.035-t)+Math.sin((gx+gy)*0.025+t*0.8), a=v*0.9+t*0.5;
var R=(0.5+0.5*Math.sin(a))*255|0, G=(0.5+0.5*Math.sin(a+2.5))*255|0, B=(0.5+0.5*Math.sin(a+5.0))*255|0;
return 'rgb('+R+','+G+','+B+')'; });
},
post:function(ctx,i){ ctx.globalAlpha=0.10; ctx.fillStyle='#000';
for(var y=0;y<i.ch;y+=3) ctx.fillRect(0,y,i.cw,1); ctx.globalAlpha=1; }
});
})();
</script>
<!-- ================= VERSION 04 · KROMATISK GLITCH ================= _gpoh -->
<div class="gpoh-cap">04 · Kromatisk glitch</div>
<div class="gpoh-fig gpoh-v04" style="background:#04010a">
<canvas></canvas>
<span class="gpoh-sub">RGB-splittring</span>
<span class="gpoh-ttl">KROMATISK</span>
</div>
<script>
(function(){ var r=document.querySelector('.gpoh-v04:not([data-init])'); if(!r) return; r.setAttribute('data-init','1');
GPOH.figur(r, {
grid:false, bgColor:'#04010a',
render:function(ctx,api){
// tre förskjutna pass i R/G/B (använder cachead lista, inget extra sampling) _gpoh
var off=3+9*api.flash+5*api.beat, P=api.pitch;
var pass=[[-off,0,'rgba(255,20,120,1)'],[0,0,'rgba(20,255,150,1)'],[off,off*0.35,'rgba(70,120,255,1)']];
ctx.save(); ctx.globalCompositeOperation='lighter';
for(var k=0;k<3;k++){ ctx.save(); ctx.translate(pass[k][0],pass[k][1]); ctx.fillStyle=pass[k][2];
api.forEachDot(function(gx,gy,b,diff){ var r=P*0.5*api.clamp(b,0,1.2); if(r<0.5) return;
ctx.beginPath(); ctx.arc(gx,gy,r,0,6.2832); ctx.fill(); });
ctx.restore(); }
ctx.restore();
},
post:function(ctx,i){
if(Math.sin(i.t*7)>0.86){ var y=(Math.abs(Math.sin(i.t*40))*i.ch)|0, hh=6+((i.t*100)%14);
ctx.globalCompositeOperation='lighter'; ctx.fillStyle='rgba(25,246,255,0.35)'; ctx.fillRect(0,y,i.cw,hh);
ctx.fillStyle='rgba(255,19,240,0.30)'; ctx.fillRect(0,y+hh,i.cw,hh*0.6); ctx.globalCompositeOperation='source-over'; }
ctx.globalAlpha=0.12; ctx.fillStyle='#000';
for(var y2=0;y2<i.ch;y2+=3) ctx.fillRect(0,y2,i.cw,1); ctx.globalAlpha=1;
}
});
})();
</script>
<!-- ================= VERSION 05 · SYNTHVÅG ================= _gpoh -->
<div class="gpoh-cap">05 · Synthvåg</div>
<div class="gpoh-fig gpoh-v05" style="background:#0a0018">
<canvas></canvas>
<span class="gpoh-sub">Solnedgångsdans</span>
<span class="gpoh-ttl">SYNTHVÅG</span>
</div>
<script>
(function(){ var r=document.querySelector('.gpoh-v05:not([data-init])'); if(!r) return; r.setAttribute('data-init','1');
GPOH.figur(r, {
grid:false, bgColor:'#0a0018',
bg:function(ctx,api){ // mjuk lodrät gradient bakom figuren _gpoh
var g=ctx.createLinearGradient(0,0,0,835);
g.addColorStop(0,'#12002b'); g.addColorStop(0.55,'#2a0140'); g.addColorStop(1,'#05010f');
ctx.fillStyle=g; ctx.fillRect(0,0,800,835); },
render:function(ctx,api){
// färg = lodrät övergång magenta -> cyan, glöd via paintGlow _gpoh
api.paintGlow(function(gx,gy,b,diff){ var f=api.clamp((gy-90)/640,0,1);
return 'rgb('+Math.round(255+(25-255)*f)+','+Math.round(19+(246-19)*f)+','+Math.round(240+(255-240)*f)+')'; });
},
post:function(ctx,i){
var yb=((i.t*90)%(i.ch+80))-40;
var band=ctx.createLinearGradient(0,yb-30,0,yb+30);
band.addColorStop(0,'rgba(255,255,255,0)'); band.addColorStop(0.5,'rgba(255,255,255,0.10)'); band.addColorStop(1,'rgba(255,255,255,0)');
ctx.fillStyle=band; ctx.fillRect(0,yb-30,i.cw,60);
ctx.globalAlpha=0.13; ctx.fillStyle='#000';
for(var y=0;y<i.ch;y+=3) ctx.fillRect(0,y,i.cw,1); ctx.globalAlpha=1;
}
});
})();
</script>