summaryrefslogtreecommitdiff
path: root/internal/generator/templates/themes
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-25 22:13:32 +0300
committerPaul Buetow <paul@buetow.org>2026-04-25 22:13:32 +0300
commita5cb9f7ad4b2bcb7ac367854147f661ab83c5ec1 (patch)
tree96db490ed72dec49c4bc2aba8539fd57f3246f47 /internal/generator/templates/themes
parentcc9c69ce748416ad1d9b28592ec463434023d63c (diff)
can swap themes dynamicallyv0.12.0
Diffstat (limited to 'internal/generator/templates/themes')
-rw-r--r--internal/generator/templates/themes/aurora.tmpl359
-rw-r--r--internal/generator/templates/themes/aurora/meta.json7
-rw-r--r--internal/generator/templates/themes/aurora/theme.css94
-rw-r--r--internal/generator/templates/themes/aurora/theme.js200
-rw-r--r--internal/generator/templates/themes/biomech.tmpl217
-rw-r--r--internal/generator/templates/themes/biomech/meta.json7
-rw-r--r--internal/generator/templates/themes/biomech/theme.css52
-rw-r--r--internal/generator/templates/themes/biomech/theme.js98
-rw-r--r--internal/generator/templates/themes/brutalist.tmpl260
-rw-r--r--internal/generator/templates/themes/brutalist/meta.json7
-rw-r--r--internal/generator/templates/themes/brutalist/theme.css65
-rw-r--r--internal/generator/templates/themes/brutalist/theme.js132
-rw-r--r--internal/generator/templates/themes/cathedral/meta.json7
-rw-r--r--internal/generator/templates/themes/cathedral/theme.css79
-rw-r--r--internal/generator/templates/themes/cathedral/theme.js (renamed from internal/generator/templates/themes/cathedral.tmpl)154
-rw-r--r--internal/generator/templates/themes/cosmos/meta.json7
-rw-r--r--internal/generator/templates/themes/cosmos/theme.css81
-rw-r--r--internal/generator/templates/themes/cosmos/theme.js (renamed from internal/generator/templates/themes/cosmos.tmpl)152
-rw-r--r--internal/generator/templates/themes/dos.tmpl302
-rw-r--r--internal/generator/templates/themes/dos/meta.json7
-rw-r--r--internal/generator/templates/themes/dos/theme.css79
-rw-r--r--internal/generator/templates/themes/dos/theme.js157
-rw-r--r--internal/generator/templates/themes/matrix/meta.json7
-rw-r--r--internal/generator/templates/themes/matrix/theme.css76
-rw-r--r--internal/generator/templates/themes/matrix/theme.js (renamed from internal/generator/templates/themes/matrix.tmpl)149
-rw-r--r--internal/generator/templates/themes/neon.tmpl348
-rw-r--r--internal/generator/templates/themes/neon/meta.json7
-rw-r--r--internal/generator/templates/themes/neon/theme.css94
-rw-r--r--internal/generator/templates/themes/neon/theme.js155
-rw-r--r--internal/generator/templates/themes/noir/meta.json7
-rw-r--r--internal/generator/templates/themes/noir/theme.css77
-rw-r--r--internal/generator/templates/themes/noir/theme.js (renamed from internal/generator/templates/themes/noir.tmpl)152
-rw-r--r--internal/generator/templates/themes/ocean/meta.json7
-rw-r--r--internal/generator/templates/themes/ocean/theme.css69
-rw-r--r--internal/generator/templates/themes/ocean/theme.js (renamed from internal/generator/templates/themes/ocean.tmpl)139
-rw-r--r--internal/generator/templates/themes/plasma/meta.json7
-rw-r--r--internal/generator/templates/themes/plasma/theme.css78
-rw-r--r--internal/generator/templates/themes/plasma/theme.js (renamed from internal/generator/templates/themes/plasma.tmpl)148
-rw-r--r--internal/generator/templates/themes/retro/meta.json7
-rw-r--r--internal/generator/templates/themes/retro/theme.css79
-rw-r--r--internal/generator/templates/themes/retro/theme.js (renamed from internal/generator/templates/themes/retro.tmpl)148
-rw-r--r--internal/generator/templates/themes/retrofuture.tmpl374
-rw-r--r--internal/generator/templates/themes/retrofuture/meta.json7
-rw-r--r--internal/generator/templates/themes/retrofuture/theme.css105
-rw-r--r--internal/generator/templates/themes/retrofuture/theme.js193
-rw-r--r--internal/generator/templates/themes/spaceage/meta.json7
-rw-r--r--internal/generator/templates/themes/spaceage/theme.css80
-rw-r--r--internal/generator/templates/themes/spaceage/theme.js (renamed from internal/generator/templates/themes/spaceage.tmpl)151
-rw-r--r--internal/generator/templates/themes/surveillance.tmpl224
-rw-r--r--internal/generator/templates/themes/surveillance/meta.json7
-rw-r--r--internal/generator/templates/themes/surveillance/theme.css50
-rw-r--r--internal/generator/templates/themes/surveillance/theme.js107
-rw-r--r--internal/generator/templates/themes/synthwave/meta.json7
-rw-r--r--internal/generator/templates/themes/synthwave/theme.css85
-rw-r--r--internal/generator/templates/themes/synthwave/theme.js (renamed from internal/generator/templates/themes/synthwave.tmpl)158
-rw-r--r--internal/generator/templates/themes/terminal.tmpl275
-rw-r--r--internal/generator/templates/themes/terminal/meta.json7
-rw-r--r--internal/generator/templates/themes/terminal/theme.css70
-rw-r--r--internal/generator/templates/themes/terminal/theme.js141
-rw-r--r--internal/generator/templates/themes/tropicale/meta.json7
-rw-r--r--internal/generator/templates/themes/tropicale/theme.css81
-rw-r--r--internal/generator/templates/themes/tropicale/theme.js (renamed from internal/generator/templates/themes/tropicale.tmpl)152
-rw-r--r--internal/generator/templates/themes/volcano/meta.json7
-rw-r--r--internal/generator/templates/themes/volcano/theme.css69
-rw-r--r--internal/generator/templates/themes/volcano/theme.js (renamed from internal/generator/templates/themes/volcano.tmpl)139
65 files changed, 2812 insertions, 3968 deletions
diff --git a/internal/generator/templates/themes/aurora.tmpl b/internal/generator/templates/themes/aurora.tmpl
deleted file mode 100644
index e71c576..0000000
--- a/internal/generator/templates/themes/aurora.tmpl
+++ /dev/null
@@ -1,359 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>snonux.foo ✦ AURORA</title>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
- <style>
- :root { --green:#00ffb3; --teal:#00cfe8; --purple:#c084fc; --navy:#050d1a; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:'Segoe UI',system-ui,sans-serif; background:var(--navy);
- color:#e0f8f0; overflow:hidden; height:100vh; }
- #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
- .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
- header { padding:16px 28px; background:rgba(5,13,26,0.78); backdrop-filter:blur(14px);
- border-bottom:1px solid rgba(0,255,179,0.25); display:flex; align-items:center; justify-content:space-between; }
- .logo { display:flex; align-items:center; gap:14px; }
- .logo-mark { font-size:2rem; font-weight:800; background:linear-gradient(90deg,var(--green),var(--teal));
- -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
- .logo-title h1 { font-size:1.5rem; font-weight:700; color:#e0f8f0; letter-spacing:1px; }
- .logo-title .subtitle { font-size:0.75rem; color:rgba(224,248,240,0.55); margin-top:2px; }
- .logo-title .subtitle a { color:var(--green); text-decoration:none; }
- .logo-title .subtitle a:hover { text-shadow:0 0 8px var(--green); }
- .transmit-btn { border:1px solid var(--teal); color:var(--teal); padding:9px 20px;
- border-radius:20px; text-decoration:none; font-size:0.85rem; transition:all 0.2s; }
- .transmit-btn:hover { background:var(--teal); color:var(--navy); }
- a.header-feed-link { color:var(--green); }
- a.header-feed-link:hover { color:var(--teal); text-shadow:0 0 8px var(--green); }
- .nav-hints { background:rgba(5,13,26,0.6); border-bottom:1px solid rgba(0,255,179,0.15);
- color:rgba(224,248,240,0.45); padding:5px 28px; display:flex; gap:18px;
- font-size:0.68rem; flex-wrap:wrap; }
- .nav-hints kbd { background:rgba(0,255,179,0.1); border:1px solid rgba(0,255,179,0.35);
- color:var(--green); border-radius:3px; padding:0 5px; margin:0 2px; }
- .content { flex:1; overflow-y:auto; padding:20px 28px;
- scrollbar-width:thin; scrollbar-color:var(--green) var(--navy); }
- .page-nav { display:flex; justify-content:center; margin:14px 0; }
- .page-nav a { border:1px solid var(--teal); color:var(--teal); padding:8px 20px;
- border-radius:20px; text-decoration:none; font-size:0.82rem; letter-spacing:1px; }
- .page-nav a:hover { background:var(--teal); color:var(--navy); }
- .page-nav-footer { flex-shrink:0; padding:8px 28px; display:flex; justify-content:center;
- background:rgba(5,13,26,0.78); backdrop-filter:blur(14px);
- border-top:1px solid rgba(0,255,179,0.25); }
- .post { background:rgba(5,20,35,0.72); border:1px solid rgba(0,255,179,0.2); border-radius:10px;
- padding:20px; margin-bottom:14px; cursor:pointer;
- transition:all 0.25s; backdrop-filter:blur(6px); }
- .post:hover { border-color:var(--green); box-shadow:0 0 20px rgba(0,255,179,0.2); transform:translateY(-2px); }
- .post-active { border-color:var(--purple) !important; background:rgba(15,5,40,0.9) !important;
- box-shadow:0 0 24px rgba(192,132,252,0.35),inset 3px 0 0 var(--purple) !important; }
- .post-header { display:flex; justify-content:space-between; margin-bottom:12px; font-size:0.88rem; }
- .post-time { color:var(--teal); font-family:monospace; font-size:0.8rem; }
- .post-text { line-height:1.65; font-size:0.95rem; }
- .post-text a { color:var(--green); text-decoration:none; }
- .post-text a:hover { text-shadow:0 0 8px var(--green); }
- .post-audio { width:100%; margin-top:10px; }
- .post-modal { display:none; position:fixed; inset:0; z-index:100;
- background:rgba(5,13,26,0.95); backdrop-filter:blur(20px);
- overflow-y:auto; padding:40px 20px; }
- .post-modal.active { display:block; }
- .modal-inner { max-width:760px; margin:0 auto; background:rgba(5,20,40,0.97);
- border:1px solid var(--green); border-radius:12px;
- box-shadow:0 0 60px rgba(0,255,179,0.25); padding:40px; }
- .modal-close { float:right; background:none; border:none; color:var(--teal);
- font-size:0.9rem; cursor:pointer; letter-spacing:1px; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 18px;} .content{padding:14px 18px;} }
- .splash-overlay.splash-aurora {
- background: radial-gradient(ellipse 120% 80% at 50% 120%, rgba(0,207,232,0.12) 0%, transparent 45%),
- radial-gradient(ellipse 90% 60% at 20% 20%, rgba(192,132,252,0.08) 0%, transparent 50%),
- linear-gradient(165deg, #030811 0%, var(--navy) 35%, #0a1628 70%, #050d1a 100%);
- background-size: 100% 100%, 100% 100%, 200% 200%;
- animation: splashAuroraShift 14s ease-in-out infinite;
- }
- @keyframes splashAuroraShift { 0%,100%{background-position:0% 0%, 0% 0%, 0% 50%} 50%{background-position:0% 0%, 0% 0%, 100% 50%} }
- .splash-aurora .splash-aurora-stars {
- position:absolute; inset:0; pointer-events:none; z-index:0; opacity:0.45;
- background-image:
- radial-gradient(1px 1px at 8% 12%, rgba(255,255,255,0.7) 50%, transparent 51%),
- radial-gradient(1px 1px at 22% 28%, rgba(255,255,255,0.5) 50%, transparent 51%),
- radial-gradient(1px 1px at 78% 18%, rgba(255,255,255,0.6) 50%, transparent 51%),
- radial-gradient(1px 1px at 92% 35%, rgba(255,255,255,0.45) 50%, transparent 51%),
- radial-gradient(1px 1px at 45% 8%, rgba(224,248,240,0.5) 50%, transparent 51%),
- radial-gradient(1px 1px at 65% 42%, rgba(255,255,255,0.4) 50%, transparent 51%);
- }
- .splash-aurora .splash-aurora-glow {
- position:absolute; left:50%; bottom:-5vh; transform:translateX(-50%); width:140%; height:55vh;
- pointer-events:none; z-index:0; opacity:0.55;
- background: radial-gradient(ellipse 75% 55% at 50% 100%, rgba(0,255,179,0.22) 0%, transparent 62%),
- radial-gradient(ellipse 55% 40% at 35% 95%, rgba(0,207,232,0.12) 0%, transparent 55%),
- radial-gradient(ellipse 50% 38% at 70% 92%, rgba(192,132,252,0.14) 0%, transparent 55%);
- filter: blur(0.5px);
- }
- .splash-aurora .splash-title {
- font-size:clamp(1.5rem,4.8vw,2.15rem); font-weight:700; letter-spacing:0.04em;
- background: linear-gradient(120deg, #e8fff4 0%, var(--green) 45%, var(--teal) 78%, #e0e8ff 100%);
- -webkit-background-clip:text; -webkit-text-fill-color:transparent;
- filter: drop-shadow(0 0 28px rgba(0,255,179,0.35));
- }
- .splash-aurora .splash-tag {
- margin-top:0.5rem; font-size:0.74rem; letter-spacing:0.28em; text-transform:uppercase;
- color:rgba(0,207,232,0.92); text-shadow:0 0 20px rgba(0,255,179,0.4);
- }
- .splash-aurora .splash-hint { color:rgba(224,248,240,0.88); margin-top:1.1rem; }
- .splash-aurora .splash-inner { position:relative; z-index:2; }
-{{template "navSharedCSSInner"}}
- </style>
-</head>
-<body>
- {{template "splashGate"}}
- <div id="splash-overlay" class="splash-overlay splash-aurora" role="dialog" aria-modal="true" aria-label="Open microblog" tabindex="-1">
- <canvas class="splash-gl-canvas" id="splash-gl-canvas" aria-hidden="true"></canvas>
- <div class="splash-aurora-stars" aria-hidden="true"></div>
- <div class="splash-aurora-glow" aria-hidden="true"></div>
- <div class="splash-inner">
- <div class="splash-title">snonux.foo</div>
- <div class="splash-tag">Aurora uplink</div>
- <div class="splash-hint">Click or Enter to open the feed</div>
- </div>
- </div>
- <script>
- (function(){
- if(document.documentElement.classList.contains('sno-splash-skip'))return;
- var cv=document.getElementById('splash-gl-canvas');
- if(!cv||typeof THREE==='undefined')return;
- var raf,ren,sc,ca,clock,ribbons=[],SEG=48;
- var cols=[0x00ffb3,0x00cfe8,0xc084fc,0x48e8d0,0xa855f7];
- var yPos=[2,5.5,9,12.5,16], zPos=[-18,-14,-10,-7,-4];
- function cleanup(){
- window.removeEventListener('resize',sz);
- if(raf)cancelAnimationFrame(raf);raf=null;
- ribbons.forEach(function(rb){rb.geo.dispose();rb.mesh.material.dispose();});
- ribbons=[];
- if(ren){ren.dispose();ren=null;}
- window._snonuxSplashWebGLCleanup=null;
- }
- window._snonuxSplashWebGLCleanup=cleanup;
- function sz(){
- var w=cv.clientWidth||2,h=cv.clientHeight||2;
- if(ren)ren.setSize(w,h,false);
- if(ca){ca.aspect=w/h;ca.updateProjectionMatrix();}
- }
- ren=new THREE.WebGLRenderer({canvas:cv,antialias:true,alpha:true});
- ren.setClearColor(0,0);ren.setPixelRatio(Math.min(window.devicePixelRatio||1,2));
- sc=new THREE.Scene();
- ca=new THREE.PerspectiveCamera(52,1,0.1,120);
- ca.position.set(0,10,26);ca.lookAt(0,8,-6);
- clock=new THREE.Clock();
- for(var r=0;r<5;r++){
- var geo=new THREE.PlaneGeometry(100,7,SEG,1);
- var mat=new THREE.MeshBasicMaterial({
- color:cols[r],transparent:true,opacity:0.26+r*0.02,
- side:THREE.DoubleSide,blending:THREE.AdditiveBlending,depthWrite:false
- });
- var mesh=new THREE.Mesh(geo,mat);
- mesh.position.set(0,yPos[r],zPos[r]);
- sc.add(mesh);
- ribbons.push({mesh:mesh,geo:geo,freq:0.55+0.12*r,phase:r*1.15,amp:2.4+0.2*r});
- }
- function loop(){
- raf=requestAnimationFrame(loop);
- var t=clock.getElapsedTime();
- for(var i=0;i<ribbons.length;i++){
- var rb=ribbons[i],pos=rb.geo.attributes.position;
- for(var v=0;v<pos.count;v++){
- if(pos.getY(v)>0){
- var x=pos.getX(v);
- pos.setY(v,rb.amp*Math.sin(t*rb.freq+x*0.065+rb.phase)
- +rb.amp*0.38*Math.cos(t*rb.freq*0.72+x*0.042));
- }
- }
- pos.needsUpdate=true;
- }
- ren.render(sc,ca);
- }
- sz();window.addEventListener('resize',sz);
- raf=requestAnimationFrame(loop);
- })();
- </script>
- <canvas id="three-canvas"></canvas>
- <div class="overlay">
- <header>
- <div class="logo">
- <span class="logo-mark">SN</span>
- <div class="logo-title">
- <h1>snonux.foo</h1>
- <p class="subtitle">microblog &mdash; <a href="https://foo.zone">foo.zone</a> is the real blog</p>
- <p class="logo-host">Served by NetBSD on a Raspberry Pi 3</p>
- </div>
- </div>
- <div class="nav">
- <a href="atom.xml" class="header-feed-link" rel="alternate" title="Atom feed" type="application/atom+xml">Atom feed</a>
- <a href="https://foo.zone/about" class="transmit-btn">Transmit</a>
- </div>
- </header>
- {{template "navhints" .}}
- <div class="content" id="post-content">
- {{range $i, $post := .Posts}}
- <div class="post" id="post-{{$post.ID}}" data-index="{{$i}}">
- <div class="post-header">
- <div><strong>@snonux</strong></div>
- <div class="post-time">{{$post.FormattedTime}}</div>
- </div>
- <div class="post-text">{{$post.ContentHTML}}</div>
- </div>
- {{end}}
- </div>
- {{if or .PrevPage .NextPage}}
- <footer class="page-nav-footer" aria-label="Pagination">
- <div class="page-nav page-nav-dual">
- {{if .PrevPage}}<a href="{{.PrevPage}}">&larr; Newer</a>{{end}}
- {{if .NextPage}}<a href="{{.NextPage}}">Older &rarr;</a>{{end}}
- </div>
- </footer>
- {{end}}
- </div>
- {{template "navmodal" .}}
- <script>
- // Aurora WebGL: six wide ribbon meshes whose top-row vertices are animated
- // with overlapping sine waves, rendered with additive blending so they glow
- // against the dark navy sky like real aurora curtains.
- (function() {
- var _wild = false, _snoTOffset = 0, _snoLastT = 0;
- var RIBBON_COUNT = 6;
- var SEG_W = 60; // horizontal segments per ribbon
- var ribbonColors = [0x00ffb3, 0x00cfe8, 0xc084fc, 0x00ffb3, 0x48e8d0, 0xa855f7];
- var ribbonY = [-10, -4, 2, 8, 14, 20];
- var ribbonZ = [-40, -30, -22, -15, -10, -5];
- var ribbonFreq = [0.6, 0.9, 0.7, 1.1, 0.5, 0.8];
- var ribbonPhase = [0.0, 1.2, 2.4, 0.8, 3.1, 1.7];
- var ribbonAmp = [3.0, 2.5, 2.0, 3.5, 2.2, 2.8];
-
- var scene, camera, renderer, clock;
- var ribbons = [];
-
- function initThree() {
- scene = new THREE.Scene();
- scene.background = new THREE.Color(0x050d1a);
- scene.fog = new THREE.Fog(0x050d1a, 40, 120);
-
- camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 200);
- camera.position.set(0, 5, 30);
- camera.lookAt(0, 5, 0);
-
- renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('three-canvas'), antialias: true });
- renderer.setSize(window.innerWidth, window.innerHeight);
- renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
-
- clock = new THREE.Clock();
-
- for (var r = 0; r < RIBBON_COUNT; r++) {
- // Wide shallow plane; we animate the top row of vertices
- var geo = new THREE.PlaneGeometry(120, 8, SEG_W, 1);
- var mat = new THREE.MeshBasicMaterial({
- color: ribbonColors[r], transparent: true, opacity: 0.32,
- side: THREE.DoubleSide, blending: THREE.AdditiveBlending, depthWrite: false
- });
- var mesh = new THREE.Mesh(geo, mat);
- mesh.position.set(0, ribbonY[r], ribbonZ[r]);
- scene.add(mesh);
- ribbons.push({ mesh: mesh, geo: geo, freq: ribbonFreq[r],
- phase: ribbonPhase[r], amp: ribbonAmp[r] });
- }
-
- window.addEventListener('resize', onResize);
- animate();
- }
-
- function onResize() {
- camera.aspect = window.innerWidth / window.innerHeight;
- camera.updateProjectionMatrix();
- renderer.setSize(window.innerWidth, window.innerHeight);
- }
-
- function animate() {
- requestAnimationFrame(animate);
- var realT = clock.getElapsedTime();
- // Wild mode: accelerate time 18× so waves churn much faster
- _snoTOffset += (realT - _snoLastT) * (_wild ? 18 : 0);
- _snoLastT = realT;
- var t = realT + _snoTOffset;
-
- var ampMult = _wild ? 3.2 : 1;
- // Camera sways left/right and bobs up/down in wild mode
- camera.position.x = _wild ? Math.sin(t * 0.28) * 10 : 0;
- camera.position.y = _wild ? 5 + Math.cos(t * 0.19) * 4 : 5;
-
- for (var r = 0; r < ribbons.length; r++) {
- var rb = ribbons[r];
- var pos = rb.geo.attributes.position;
- var count = pos.count;
- // In wild mode ribbons also drift vertically so they cross and tangle
- var yDrift = _wild ? Math.sin(t * rb.freq * 0.4 + r * 1.1) * 6 : 0;
- rb.mesh.position.y = ribbonY[r] + yDrift;
- // PlaneGeometry vertices: (SEG_W+1)*2 total; top row is every other vertex
- for (var i = 0; i < count; i++) {
- var x = pos.getX(i);
- // Only animate top row (y > 0 in local space) for the waving top edge
- if (pos.getY(i) > 0) {
- pos.setY(i, rb.amp * ampMult * Math.sin(t * rb.freq + x * 0.08 + rb.phase)
- + rb.amp * ampMult * 0.4 * Math.cos(t * rb.freq * 0.7 + x * 0.05));
- }
- }
- pos.needsUpdate = true;
- }
- renderer.render(scene, camera);
- }
-
- initThree();
-
- // Aurora nav/wild effects — snow burst on navigate, blizzard storm on wild
- window.snonuxOpenEffect = function() {
- var modal = document.getElementById('post-modal');
- if (modal) { modal.classList.add('sno-modal-zoom'); setTimeout(function() { modal.classList.remove('sno-modal-zoom'); }, 400); }
- // Frost shimmer — aurora-colored radial
- var d = document.createElement('div');
- d.style.cssText = 'position:fixed;inset:0;z-index:997;pointer-events:none;background:radial-gradient(ellipse at center,rgba(0,255,179,0.14) 0%,rgba(192,132,252,0.1) 55%,transparent 80%);transition:opacity 0.3s';
- document.body.appendChild(d);
- setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 340); }, 15);
- };
- window.snonuxCloseEffect = function() {
- var d = document.createElement('div');
- d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(0,207,232,0.12);transition:opacity 0.2s';
- document.body.appendChild(d);
- setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 230); }, 15);
- };
- window.snonuxScrollEffect = function(dir) {
- var isDown = dir === 'down';
- var thick = _wild ? '14px' : '5px';
- var d = document.createElement('div');
- d.style.cssText = 'position:fixed;left:0;right:0;height:' + thick + ';z-index:9000;pointer-events:none;' +
- 'background:linear-gradient(90deg,transparent,rgba(0,207,232,0.9),rgba(120,200,100,0.9),rgba(0,207,232,0.9),transparent);' +
- (isDown ? 'top:0;' : 'bottom:0;') +
- 'transition:transform 0.32s ease,opacity 0.32s ease;';
- document.body.appendChild(d);
- setTimeout(function() { d.style.transform = isDown ? 'translateY(100vh)' : 'translateY(-100vh)'; d.style.opacity='0'; }, 16);
- setTimeout(function() { d.remove(); }, 400);
- };
- window.snonuxWildToggle = function() {
- _wild = !_wild;
- var b = document.getElementById('sno-wild-badge');
- if (b) b.classList.toggle('sno-wild-on', _wild);
- };
- window.snonuxNavEffect = function() {
- // Snow burst — CSS snowflakes scatter from cursor
- var ov = document.querySelector('.overlay');
- if (ov) { ov.classList.add('sno-fx-shake'); setTimeout(function() { ov.classList.remove('sno-fx-shake'); }, 380); }
- // Frost flash
- var d = document.createElement('div');
- d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:radial-gradient(ellipse at center,rgba(0,255,179,0.18) 0%,rgba(192,132,252,0.1) 60%,transparent 100%);transition:opacity 0.22s';
- document.body.appendChild(d);
- setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 250); }, 30);
- };
- window.snonuxPageEffect = function() {
- var ov = document.querySelector('.overlay');
- if (ov) { ov.classList.add('sno-fx-zoom'); setTimeout(function() { ov.classList.remove('sno-fx-zoom'); }, 330); }
- };
- })();
- </script>
- {{template "navscript" .}}
-</body>
-</html>
diff --git a/internal/generator/templates/themes/aurora/meta.json b/internal/generator/templates/themes/aurora/meta.json
new file mode 100644
index 0000000..b0b2cf5
--- /dev/null
+++ b/internal/generator/templates/themes/aurora/meta.json
@@ -0,0 +1,7 @@
+{
+ "title": "snonux.foo ✦ AURORA",
+ "header_html": "\u003cdiv class=\"logo\"\u003e\n \u003cspan class=\"logo-mark\"\u003eSN\u003c/span\u003e\n \u003cdiv class=\"logo-title\"\u003e\n \u003ch1\u003esnonux.foo\u003c/h1\u003e\n \u003cp class=\"subtitle\"\u003emicroblog \u0026mdash; \u003ca href=\"https://foo.zone\"\u003efoo.zone\u003c/a\u003e is the real blog\u003c/p\u003e\n \u003cp class=\"logo-host\"\u003eServed by NetBSD on a Raspberry Pi 3\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"nav\"\u003e\n \u003ca href=\"atom.xml\" class=\"header-feed-link\" rel=\"alternate\" title=\"Atom feed\" type=\"application/atom+xml\"\u003eAtom feed\u003c/a\u003e\n \u003ca href=\"https://foo.zone/about\" class=\"transmit-btn\"\u003eTransmit\u003c/a\u003e\n \u003c/div\u003e",
+ "splash_inner_html": "\u003ccanvas class=\"splash-gl-canvas\" id=\"splash-gl-canvas\" aria-hidden=\"true\"\u003e\u003c/canvas\u003e\n \u003cdiv class=\"splash-aurora-stars\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-aurora-glow\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-inner\"\u003e\n \u003cdiv class=\"splash-title\"\u003esnonux.foo\u003c/div\u003e\n \u003cdiv class=\"splash-tag\"\u003eAurora uplink\u003c/div\u003e\n \u003cdiv class=\"splash-hint\"\u003eClick or Enter to open the feed\u003c/div\u003e\n \u003c/div\u003e",
+ "prev_page_text": "\u0026larr; Newer",
+ "next_page_text": "Older \u0026rarr;"
+}
diff --git a/internal/generator/templates/themes/aurora/theme.css b/internal/generator/templates/themes/aurora/theme.css
new file mode 100644
index 0000000..325624c
--- /dev/null
+++ b/internal/generator/templates/themes/aurora/theme.css
@@ -0,0 +1,94 @@
+ :root { --green:#00ffb3; --teal:#00cfe8; --purple:#c084fc; --navy:#050d1a; }
+ * { margin:0; padding:0; box-sizing:border-box; }
+ body { font-family:'Segoe UI',system-ui,sans-serif; background:var(--navy);
+ color:#e0f8f0; overflow:hidden; height:100vh; }
+ #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
+ .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
+ header { padding:16px 28px; background:rgba(5,13,26,0.78); backdrop-filter:blur(14px);
+ border-bottom:1px solid rgba(0,255,179,0.25); display:flex; align-items:center; justify-content:space-between; }
+ .logo { display:flex; align-items:center; gap:14px; }
+ .logo-mark { font-size:2rem; font-weight:800; background:linear-gradient(90deg,var(--green),var(--teal));
+ -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
+ .logo-title h1 { font-size:1.5rem; font-weight:700; color:#e0f8f0; letter-spacing:1px; }
+ .logo-title .subtitle { font-size:0.75rem; color:rgba(224,248,240,0.55); margin-top:2px; }
+ .logo-title .subtitle a { color:var(--green); text-decoration:none; }
+ .logo-title .subtitle a:hover { text-shadow:0 0 8px var(--green); }
+ .transmit-btn { border:1px solid var(--teal); color:var(--teal); padding:9px 20px;
+ border-radius:20px; text-decoration:none; font-size:0.85rem; transition:all 0.2s; }
+ .transmit-btn:hover { background:var(--teal); color:var(--navy); }
+ a.header-feed-link { color:var(--green); }
+ a.header-feed-link:hover { color:var(--teal); text-shadow:0 0 8px var(--green); }
+ .nav-hints { background:rgba(5,13,26,0.6); border-bottom:1px solid rgba(0,255,179,0.15);
+ color:rgba(224,248,240,0.45); padding:5px 28px; display:flex; gap:18px;
+ font-size:0.68rem; flex-wrap:wrap; }
+ .nav-hints kbd { background:rgba(0,255,179,0.1); border:1px solid rgba(0,255,179,0.35);
+ color:var(--green); border-radius:3px; padding:0 5px; margin:0 2px; }
+ .content { flex:1; overflow-y:auto; padding:20px 28px;
+ scrollbar-width:thin; scrollbar-color:var(--green) var(--navy); }
+ .page-nav { display:flex; justify-content:center; margin:14px 0; }
+ .page-nav a { border:1px solid var(--teal); color:var(--teal); padding:8px 20px;
+ border-radius:20px; text-decoration:none; font-size:0.82rem; letter-spacing:1px; }
+ .page-nav a:hover { background:var(--teal); color:var(--navy); }
+ .page-nav-footer { flex-shrink:0; padding:8px 28px; display:flex; justify-content:center;
+ background:rgba(5,13,26,0.78); backdrop-filter:blur(14px);
+ border-top:1px solid rgba(0,255,179,0.25); }
+ .post { background:rgba(5,20,35,0.72); border:1px solid rgba(0,255,179,0.2); border-radius:10px;
+ padding:20px; margin-bottom:14px; cursor:pointer;
+ transition:all 0.25s; backdrop-filter:blur(6px); }
+ .post:hover { border-color:var(--green); box-shadow:0 0 20px rgba(0,255,179,0.2); transform:translateY(-2px); }
+ .post-active { border-color:var(--purple) !important; background:rgba(15,5,40,0.9) !important;
+ box-shadow:0 0 24px rgba(192,132,252,0.35),inset 3px 0 0 var(--purple) !important; }
+ .post-header { display:flex; justify-content:space-between; margin-bottom:12px; font-size:0.88rem; }
+ .post-time { color:var(--teal); font-family:monospace; font-size:0.8rem; }
+ .post-text { line-height:1.65; font-size:0.95rem; }
+ .post-text a { color:var(--green); text-decoration:none; }
+ .post-text a:hover { text-shadow:0 0 8px var(--green); }
+ .post-audio { width:100%; margin-top:10px; }
+ .post-modal { display:none; position:fixed; inset:0; z-index:100;
+ background:rgba(5,13,26,0.95); backdrop-filter:blur(20px);
+ overflow-y:auto; padding:40px 20px; }
+ .post-modal.active { display:block; }
+ .modal-inner { max-width:760px; margin:0 auto; background:rgba(5,20,40,0.97);
+ border:1px solid var(--green); border-radius:12px;
+ box-shadow:0 0 60px rgba(0,255,179,0.25); padding:40px; }
+ .modal-close { float:right; background:none; border:none; color:var(--teal);
+ font-size:0.9rem; cursor:pointer; letter-spacing:1px; }
+ @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 18px;} .content{padding:14px 18px;} }
+ [data-sno-theme="aurora"] .splash-overlay {
+ background: radial-gradient(ellipse 120% 80% at 50% 120%, rgba(0,207,232,0.12) 0%, transparent 45%),
+ radial-gradient(ellipse 90% 60% at 20% 20%, rgba(192,132,252,0.08) 0%, transparent 50%),
+ linear-gradient(165deg, #030811 0%, var(--navy) 35%, #0a1628 70%, #050d1a 100%);
+ background-size: 100% 100%, 100% 100%, 200% 200%;
+ animation: splashAuroraShift 14s ease-in-out infinite;
+ }
+ @keyframes splashAuroraShift { 0%,100%{background-position:0% 0%, 0% 0%, 0% 50%} 50%{background-position:0% 0%, 0% 0%, 100% 50%} }
+ [data-sno-theme="aurora"] [data-sno-theme="aurora"]-stars {
+ position:absolute; inset:0; pointer-events:none; z-index:0; opacity:0.45;
+ background-image:
+ radial-gradient(1px 1px at 8% 12%, rgba(255,255,255,0.7) 50%, transparent 51%),
+ radial-gradient(1px 1px at 22% 28%, rgba(255,255,255,0.5) 50%, transparent 51%),
+ radial-gradient(1px 1px at 78% 18%, rgba(255,255,255,0.6) 50%, transparent 51%),
+ radial-gradient(1px 1px at 92% 35%, rgba(255,255,255,0.45) 50%, transparent 51%),
+ radial-gradient(1px 1px at 45% 8%, rgba(224,248,240,0.5) 50%, transparent 51%),
+ radial-gradient(1px 1px at 65% 42%, rgba(255,255,255,0.4) 50%, transparent 51%);
+ }
+ [data-sno-theme="aurora"] [data-sno-theme="aurora"]-glow {
+ position:absolute; left:50%; bottom:-5vh; transform:translateX(-50%); width:140%; height:55vh;
+ pointer-events:none; z-index:0; opacity:0.55;
+ background: radial-gradient(ellipse 75% 55% at 50% 100%, rgba(0,255,179,0.22) 0%, transparent 62%),
+ radial-gradient(ellipse 55% 40% at 35% 95%, rgba(0,207,232,0.12) 0%, transparent 55%),
+ radial-gradient(ellipse 50% 38% at 70% 92%, rgba(192,132,252,0.14) 0%, transparent 55%);
+ filter: blur(0.5px);
+ }
+ [data-sno-theme="aurora"] .splash-title {
+ font-size:clamp(1.5rem,4.8vw,2.15rem); font-weight:700; letter-spacing:0.04em;
+ background: linear-gradient(120deg, #e8fff4 0%, var(--green) 45%, var(--teal) 78%, #e0e8ff 100%);
+ -webkit-background-clip:text; -webkit-text-fill-color:transparent;
+ filter: drop-shadow(0 0 28px rgba(0,255,179,0.35));
+ }
+ [data-sno-theme="aurora"] .splash-tag {
+ margin-top:0.5rem; font-size:0.74rem; letter-spacing:0.28em; text-transform:uppercase;
+ color:rgba(0,207,232,0.92); text-shadow:0 0 20px rgba(0,255,179,0.4);
+ }
+ [data-sno-theme="aurora"] .splash-hint { color:rgba(224,248,240,0.88); margin-top:1.1rem; }
+ [data-sno-theme="aurora"] .splash-inner { position:relative; z-index:2; }
diff --git a/internal/generator/templates/themes/aurora/theme.js b/internal/generator/templates/themes/aurora/theme.js
new file mode 100644
index 0000000..624a4a7
--- /dev/null
+++ b/internal/generator/templates/themes/aurora/theme.js
@@ -0,0 +1,200 @@
+
+ (function(){
+ if(document.documentElement.classList.contains('sno-splash-skip'))return;
+ var cv=document.getElementById('splash-gl-canvas');
+ if(!cv||typeof THREE==='undefined')return;
+ var raf,ren,sc,ca,clock,ribbons=[],SEG=48;
+ var cols=[0x00ffb3,0x00cfe8,0xc084fc,0x48e8d0,0xa855f7];
+ var yPos=[2,5.5,9,12.5,16], zPos=[-18,-14,-10,-7,-4];
+ function cleanup(){
+ window.removeEventListener('resize',sz);
+ if(raf)cancelAnimationFrame(raf);raf=null;
+ ribbons.forEach(function(rb){rb.geo.dispose();rb.mesh.material.dispose();});
+ ribbons=[];
+ if(ren){ren.dispose();ren=null;}
+ window._snonuxSplashWebGLCleanup=null;
+ }
+ window._snonuxSplashWebGLCleanup=cleanup;
+ function sz(){
+ var w=cv.clientWidth||2,h=cv.clientHeight||2;
+ if(ren)ren.setSize(w,h,false);
+ if(ca){ca.aspect=w/h;ca.updateProjectionMatrix();}
+ }
+ ren=new THREE.WebGLRenderer({canvas:cv,antialias:true,alpha:true});
+ ren.setClearColor(0,0);ren.setPixelRatio(Math.min(window.devicePixelRatio||1,2));
+ sc=new THREE.Scene();
+ ca=new THREE.PerspectiveCamera(52,1,0.1,120);
+ ca.position.set(0,10,26);ca.lookAt(0,8,-6);
+ clock=new THREE.Clock();
+ for(var r=0;r<5;r++){
+ var geo=new THREE.PlaneGeometry(100,7,SEG,1);
+ var mat=new THREE.MeshBasicMaterial({
+ color:cols[r],transparent:true,opacity:0.26+r*0.02,
+ side:THREE.DoubleSide,blending:THREE.AdditiveBlending,depthWrite:false
+ });
+ var mesh=new THREE.Mesh(geo,mat);
+ mesh.position.set(0,yPos[r],zPos[r]);
+ sc.add(mesh);
+ ribbons.push({mesh:mesh,geo:geo,freq:0.55+0.12*r,phase:r*1.15,amp:2.4+0.2*r});
+ }
+ function loop(){
+ raf=requestAnimationFrame(loop);
+ var t=clock.getElapsedTime();
+ for(var i=0;i<ribbons.length;i++){
+ var rb=ribbons[i],pos=rb.geo.attributes.position;
+ for(var v=0;v<pos.count;v++){
+ if(pos.getY(v)>0){
+ var x=pos.getX(v);
+ pos.setY(v,rb.amp*Math.sin(t*rb.freq+x*0.065+rb.phase)
+ +rb.amp*0.38*Math.cos(t*rb.freq*0.72+x*0.042));
+ }
+ }
+ pos.needsUpdate=true;
+ }
+ ren.render(sc,ca);
+ }
+ sz();window.addEventListener('resize',sz);
+ raf=requestAnimationFrame(loop);
+ })();
+
+
+ // Aurora WebGL: six wide ribbon meshes whose top-row vertices are animated
+ // with overlapping sine waves, rendered with additive blending so they glow
+ // against the dark navy sky like real aurora curtains.
+ (function() {
+ var _wild = false, _snoTOffset = 0, _snoLastT = 0;
+ var RIBBON_COUNT = 6;
+ var SEG_W = 60; // horizontal segments per ribbon
+ var ribbonColors = [0x00ffb3, 0x00cfe8, 0xc084fc, 0x00ffb3, 0x48e8d0, 0xa855f7];
+ var ribbonY = [-10, -4, 2, 8, 14, 20];
+ var ribbonZ = [-40, -30, -22, -15, -10, -5];
+ var ribbonFreq = [0.6, 0.9, 0.7, 1.1, 0.5, 0.8];
+ var ribbonPhase = [0.0, 1.2, 2.4, 0.8, 3.1, 1.7];
+ var ribbonAmp = [3.0, 2.5, 2.0, 3.5, 2.2, 2.8];
+
+ var scene, camera, renderer, clock;
+ var ribbons = [];
+
+ function initThree() {
+ scene = new THREE.Scene();
+ scene.background = new THREE.Color(0x050d1a);
+ scene.fog = new THREE.Fog(0x050d1a, 40, 120);
+
+ camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 200);
+ camera.position.set(0, 5, 30);
+ camera.lookAt(0, 5, 0);
+
+ renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('three-canvas'), antialias: true });
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
+
+ clock = new THREE.Clock();
+
+ for (var r = 0; r < RIBBON_COUNT; r++) {
+ // Wide shallow plane; we animate the top row of vertices
+ var geo = new THREE.PlaneGeometry(120, 8, SEG_W, 1);
+ var mat = new THREE.MeshBasicMaterial({
+ color: ribbonColors[r], transparent: true, opacity: 0.32,
+ side: THREE.DoubleSide, blending: THREE.AdditiveBlending, depthWrite: false
+ });
+ var mesh = new THREE.Mesh(geo, mat);
+ mesh.position.set(0, ribbonY[r], ribbonZ[r]);
+ scene.add(mesh);
+ ribbons.push({ mesh: mesh, geo: geo, freq: ribbonFreq[r],
+ phase: ribbonPhase[r], amp: ribbonAmp[r] });
+ }
+
+ window.addEventListener('resize', onResize);
+ animate();
+ }
+
+ function onResize() {
+ camera.aspect = window.innerWidth / window.innerHeight;
+ camera.updateProjectionMatrix();
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ }
+
+ function animate() {
+ requestAnimationFrame(animate);
+ var realT = clock.getElapsedTime();
+ // Wild mode: accelerate time 18× so waves churn much faster
+ _snoTOffset += (realT - _snoLastT) * (_wild ? 18 : 0);
+ _snoLastT = realT;
+ var t = realT + _snoTOffset;
+
+ var ampMult = _wild ? 3.2 : 1;
+ // Camera sways left/right and bobs up/down in wild mode
+ camera.position.x = _wild ? Math.sin(t * 0.28) * 10 : 0;
+ camera.position.y = _wild ? 5 + Math.cos(t * 0.19) * 4 : 5;
+
+ for (var r = 0; r < ribbons.length; r++) {
+ var rb = ribbons[r];
+ var pos = rb.geo.attributes.position;
+ var count = pos.count;
+ // In wild mode ribbons also drift vertically so they cross and tangle
+ var yDrift = _wild ? Math.sin(t * rb.freq * 0.4 + r * 1.1) * 6 : 0;
+ rb.mesh.position.y = ribbonY[r] + yDrift;
+ // PlaneGeometry vertices: (SEG_W+1)*2 total; top row is every other vertex
+ for (var i = 0; i < count; i++) {
+ var x = pos.getX(i);
+ // Only animate top row (y > 0 in local space) for the waving top edge
+ if (pos.getY(i) > 0) {
+ pos.setY(i, rb.amp * ampMult * Math.sin(t * rb.freq + x * 0.08 + rb.phase)
+ + rb.amp * ampMult * 0.4 * Math.cos(t * rb.freq * 0.7 + x * 0.05));
+ }
+ }
+ pos.needsUpdate = true;
+ }
+ renderer.render(scene, camera);
+ }
+
+ initThree();
+
+ // Aurora nav/wild effects — snow burst on navigate, blizzard storm on wild
+ window.snonuxOpenEffect = function() {
+ var modal = document.getElementById('post-modal');
+ if (modal) { modal.classList.add('sno-modal-zoom'); setTimeout(function() { modal.classList.remove('sno-modal-zoom'); }, 400); }
+ // Frost shimmer — aurora-colored radial
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:997;pointer-events:none;background:radial-gradient(ellipse at center,rgba(0,255,179,0.14) 0%,rgba(192,132,252,0.1) 55%,transparent 80%);transition:opacity 0.3s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 340); }, 15);
+ };
+ window.snonuxCloseEffect = function() {
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(0,207,232,0.12);transition:opacity 0.2s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 230); }, 15);
+ };
+ window.snonuxScrollEffect = function(dir) {
+ var isDown = dir === 'down';
+ var thick = _wild ? '14px' : '5px';
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;left:0;right:0;height:' + thick + ';z-index:9000;pointer-events:none;' +
+ 'background:linear-gradient(90deg,transparent,rgba(0,207,232,0.9),rgba(120,200,100,0.9),rgba(0,207,232,0.9),transparent);' +
+ (isDown ? 'top:0;' : 'bottom:0;') +
+ 'transition:transform 0.32s ease,opacity 0.32s ease;';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.transform = isDown ? 'translateY(100vh)' : 'translateY(-100vh)'; d.style.opacity='0'; }, 16);
+ setTimeout(function() { d.remove(); }, 400);
+ };
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ };
+ window.snonuxNavEffect = function() {
+ // Snow burst — CSS snowflakes scatter from cursor
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-shake'); setTimeout(function() { ov.classList.remove('sno-fx-shake'); }, 380); }
+ // Frost flash
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:radial-gradient(ellipse at center,rgba(0,255,179,0.18) 0%,rgba(192,132,252,0.1) 60%,transparent 100%);transition:opacity 0.22s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 250); }, 30);
+ };
+ window.snonuxPageEffect = function() {
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-zoom'); setTimeout(function() { ov.classList.remove('sno-fx-zoom'); }, 330); }
+ };
+ })();
diff --git a/internal/generator/templates/themes/biomech.tmpl b/internal/generator/templates/themes/biomech.tmpl
deleted file mode 100644
index 9773d96..0000000
--- a/internal/generator/templates/themes/biomech.tmpl
+++ /dev/null
@@ -1,217 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>snonux.foo // BIOMECH</title>
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
- <link href="https://fonts.googleapis.com/css2?family=Oxanium:wght@400;600;700&family=IBM+Plex+Mono:wght@400;700&display=swap" rel="stylesheet">
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
- <style>
- :root { --bone:#d0c7bb; --flesh:#803f5d; --vein:#f55b7d; --acid:#93ffd8; --steel:#2d3642; --bg:#09070d; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:'Oxanium',system-ui,sans-serif; background:var(--bg); color:var(--bone); overflow:hidden; height:100vh; }
- #three-canvas { position:fixed; inset:0; width:100%; height:100%; z-index:1; }
- .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
- header { padding:16px 26px; background:rgba(9,7,13,0.84); backdrop-filter:blur(10px); border-bottom:1px solid rgba(147,255,216,0.16); display:flex; align-items:center; justify-content:space-between; }
- .logo { display:flex; align-items:center; gap:14px; }
- .logo-mark { font-size:1.8rem; color:var(--acid); text-shadow:0 0 18px rgba(147,255,216,0.24); }
- .logo-title h1 { font-size:1.42rem; color:var(--bone); letter-spacing:0.08em; }
- .logo-title .subtitle { font-size:0.74rem; color:rgba(208,199,187,0.56); margin-top:2px; }
- .logo-title .subtitle a { color:var(--acid); text-decoration:none; }
- .logo-title .subtitle a:hover { color:#dffff6; }
- .transmit-btn { border:1px solid rgba(147,255,216,0.22); color:var(--acid); padding:8px 16px; text-decoration:none; font-size:0.78rem; letter-spacing:0.2em; text-transform:uppercase; transition:all 0.18s; }
- .transmit-btn:hover { background:rgba(147,255,216,0.1); }
- a.header-feed-link { color:rgba(208,199,187,0.68); }
- a.header-feed-link:hover { color:var(--acid); }
- .nav-hints { background:rgba(12,10,18,0.74); border-bottom:1px solid rgba(147,255,216,0.08); color:rgba(208,199,187,0.44); padding:5px 26px; display:flex; gap:18px; font-size:0.66rem; letter-spacing:0.08em; flex-wrap:wrap; }
- .nav-hints kbd { background:rgba(128,63,93,0.14); border:1px solid rgba(147,255,216,0.18); color:var(--acid); padding:0 5px; margin:0 2px; }
- .content { flex:1; overflow-y:auto; padding:20px 26px; scrollbar-width:thin; scrollbar-color:#6d4a69 #120d16; }
- .page-nav { display:flex; justify-content:center; margin:14px 0; }
- .page-nav a { border:1px solid rgba(147,255,216,0.18); color:var(--acid); padding:8px 18px; text-decoration:none; font-size:0.78rem; letter-spacing:0.2em; text-transform:uppercase; }
- .page-nav a:hover { background:rgba(147,255,216,0.08); }
- .page-nav-footer { flex-shrink:0; padding:8px 26px; display:flex; justify-content:center; background:rgba(9,7,13,0.84); backdrop-filter:blur(10px); border-top:1px solid rgba(147,255,216,0.16); }
- .post { background:linear-gradient(180deg, rgba(33,20,31,0.9), rgba(12,9,18,0.92)); border:1px solid rgba(147,255,216,0.08); padding:18px; margin-bottom:13px; cursor:pointer; box-shadow:0 16px 38px rgba(0,0,0,0.28); transition:border-color 0.18s, box-shadow 0.18s, transform 0.18s; }
- .post:hover { border-color:rgba(147,255,216,0.22); transform:translateY(-1px); }
- .post-active { border-color:rgba(245,91,125,0.28) !important; background:linear-gradient(180deg, rgba(46,18,34,0.94), rgba(13,9,17,0.95)) !important;
- box-shadow:0 0 0 1px rgba(147,255,216,0.08), 0 18px 42px rgba(0,0,0,0.42), inset 4px 0 0 var(--vein) !important; }
- .post-header { display:flex; justify-content:space-between; margin-bottom:10px; font-size:0.84rem; }
- .post-header strong { color:var(--acid); }
- .post-time { color:rgba(208,199,187,0.58); font-family:'IBM Plex Mono',monospace; }
- .post-text { line-height:1.7; font-size:0.92rem; }
- .post-text a { color:var(--acid); text-decoration:none; border-bottom:1px solid rgba(147,255,216,0.18); }
- .post-image { margin-top:10px; border:1px solid rgba(147,255,216,0.1); filter:saturate(0.9) hue-rotate(-14deg) contrast(1.06); }
- .post-audio { width:100%; margin-top:10px; filter:hue-rotate(-14deg); }
- .post-modal { display:none; position:fixed; inset:0; z-index:100; overflow-y:auto; padding:40px 20px; }
- .post-modal.active { display:block; }
- .modal-inner { max-width:760px; margin:0 auto; background:rgba(11,9,16,0.98); border:1px solid rgba(147,255,216,0.18); padding:34px; box-shadow:0 22px 76px rgba(0,0,0,0.72); }
- .modal-close { float:right; background:none; border:none; color:var(--acid); font-family:'IBM Plex Mono',monospace; font-size:0.78rem; cursor:pointer; letter-spacing:0.18em; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 16px;} .content{padding:14px 16px;} .modal-inner{padding:24px 16px;} }
- .splash-overlay.splash-biomech {
- background:
- radial-gradient(circle at 50% 22%, rgba(245,91,125,0.14) 0%, transparent 28%),
- radial-gradient(circle at 50% 80%, rgba(147,255,216,0.08) 0%, transparent 42%),
- linear-gradient(180deg, #100b14 0%, #050407 100%);
- }
- .splash-biomech .splash-pod { position:absolute; left:50%; top:10vh; width:min(34vw,220px); height:min(46vw,290px); transform:translateX(-50%); border-radius:48% 48% 42% 42% / 54% 54% 38% 38%;
- background:radial-gradient(circle at 50% 35%, rgba(147,255,216,0.18) 0%, rgba(147,255,216,0.06) 28%, rgba(128,63,93,0.38) 62%, rgba(12,9,18,0.8) 100%);
- box-shadow:0 0 42px rgba(245,91,125,0.14); opacity:0.72; z-index:1; }
- .splash-biomech .splash-title { font-size:clamp(1.55rem,5vw,2.1rem); color:var(--bone); letter-spacing:0.12em; }
- .splash-biomech .splash-tag { color:var(--acid); letter-spacing:0.22em; }
- .splash-biomech .splash-hint { color:rgba(208,199,187,0.78); }
- .splash-biomech .splash-inner { text-shadow:0 2px 22px rgba(0,0,0,0.95); }
-{{template "navSharedCSSInner"}}
- </style>
-</head>
-<body>
- {{template "splashGate"}}
- <div id="splash-overlay" class="splash-overlay splash-biomech" role="dialog" aria-modal="true" aria-label="Open microblog" tabindex="-1">
- <canvas class="splash-gl-canvas" id="splash-gl-canvas" aria-hidden="true"></canvas>
- <div class="splash-pod" aria-hidden="true"></div>
- <div class="splash-inner">
- <div class="splash-title">snonux.foo</div>
- <div class="splash-tag">Containment Membrane</div>
- <div class="splash-hint">Click or Enter to breach the shell</div>
- </div>
- </div>
- <script>
- (function(){
- if(document.documentElement.classList.contains('sno-splash-skip'))return;
- var cv=document.getElementById('splash-gl-canvas');
- if(!cv||typeof THREE==='undefined')return;
- var raf,ren,sc,ca,clock,core;
- function cleanup(){window.removeEventListener('resize',sz);if(raf)cancelAnimationFrame(raf);raf=null;if(ren)ren.dispose();ren=null;window._snonuxSplashWebGLCleanup=null;}
- window._snonuxSplashWebGLCleanup=cleanup;
- function sz(){var w=cv.clientWidth||2,h=cv.clientHeight||2;if(ren)ren.setSize(w,h,false);if(ca){ca.aspect=w/h;ca.updateProjectionMatrix();}}
- ren=new THREE.WebGLRenderer({canvas:cv,antialias:true,alpha:true});ren.setClearColor(0,0);ren.setPixelRatio(Math.min(window.devicePixelRatio||1,2));
- sc=new THREE.Scene();ca=new THREE.PerspectiveCamera(48,1,0.1,60);ca.position.z=9;clock=new THREE.Clock();
- core=new THREE.Mesh(new THREE.SphereGeometry(1.2,24,24),new THREE.MeshBasicMaterial({color:0xf55b7d,transparent:true,opacity:0.76})); sc.add(core);
- var shell=new THREE.Mesh(new THREE.TorusKnotGeometry(2.4,0.36,80,14),new THREE.MeshBasicMaterial({color:0x93ffd8,wireframe:true,transparent:true,opacity:0.42})); sc.add(shell); shell.userData.rot=0.006;
- sz();window.addEventListener('resize',sz);
- function loop(){ raf=requestAnimationFrame(loop); var t=clock.getElapsedTime(); shell.rotation.x=t*0.2; shell.rotation.y=t*0.3; core.scale.setScalar(1+Math.sin(t*3.2)*0.08); ren.render(sc,ca); }
- raf=requestAnimationFrame(loop);
- })();
- </script>
- <canvas id="three-canvas"></canvas>
- <div class="overlay">
- <header>
- <div class="logo">
- <span class="logo-mark">SN</span>
- <div class="logo-title">
- <h1>snonux.foo</h1>
- <p class="subtitle">microblog &mdash; <a href="https://foo.zone">foo.zone</a> is the real blog</p>
- <p class="logo-host">Served by NetBSD on a Raspberry Pi 3</p>
- </div>
- </div>
- <div class="nav">
- <a href="atom.xml" class="header-feed-link" rel="alternate" title="Atom feed" type="application/atom+xml">Atom feed</a>
- <a href="https://foo.zone/about" class="transmit-btn">Anatomy</a>
- </div>
- </header>
- {{template "navhints" .}}
- <div class="content" id="post-content">
- {{range $i, $post := .Posts}}
- <div class="post" id="post-{{$post.ID}}" data-index="{{$i}}">
- <div class="post-header">
- <div><strong>@snonux</strong></div>
- <div class="post-time">{{$post.FormattedTime}}</div>
- </div>
- <div class="post-text">{{$post.ContentHTML}}</div>
- </div>
- {{end}}
- </div>
- {{if or .PrevPage .NextPage}}
- <footer class="page-nav-footer" aria-label="Pagination">
- <div class="page-nav page-nav-dual">
- {{if .PrevPage}}<a href="{{.PrevPage}}">&larr; Newer</a>{{end}}
- {{if .NextPage}}<a href="{{.NextPage}}">Older &rarr;</a>{{end}}
- </div>
- </footer>
- {{end}}
- </div>
- {{template "navmodal" .}}
- <script>
- (function() {
- var _wild = false, _snoTOffset = 0, _snoLastT = 0;
- var scene, camera, renderer, clock, core, shellA, shellB, orbiters = [];
-
- function initThree() {
- scene = new THREE.Scene();
- scene.background = new THREE.Color(0x09070d);
- scene.fog = new THREE.Fog(0x09070d, 18, 120);
- camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 220);
- camera.position.set(0, 6, 26);
- renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('three-canvas'), antialias: true });
- renderer.setSize(window.innerWidth, window.innerHeight);
- renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
- clock = new THREE.Clock();
- scene.add(new THREE.AmbientLight(0x553a47, 0.45));
- var coreLight = new THREE.PointLight(0xf55b7d, 1.6, 80); coreLight.position.set(0,0,0); scene.add(coreLight);
-
- core = new THREE.Mesh(new THREE.SphereGeometry(4.2, 36, 36), new THREE.MeshPhongMaterial({ color:0x803f5d, emissive:0xf55b7d, emissiveIntensity:0.52, shininess:90 }));
- shellA = new THREE.Mesh(new THREE.TorusKnotGeometry(7.4, 0.45, 180, 24, 2, 5), new THREE.MeshBasicMaterial({ color:0x93ffd8, wireframe:true, transparent:true, opacity:0.34 }));
- shellB = new THREE.Mesh(new THREE.TorusKnotGeometry(5.9, 0.28, 160, 16, 3, 7), new THREE.MeshBasicMaterial({ color:0xd0c7bb, wireframe:true, transparent:true, opacity:0.18 }));
- scene.add(core); scene.add(shellA); scene.add(shellB);
- for (var i = 0; i < 9; i++) {
- var orb = new THREE.Mesh(new THREE.SphereGeometry(0.55 + Math.random() * 0.45, 14, 14), new THREE.MeshPhongMaterial({ color: i % 2 === 0 ? 0x93ffd8 : 0xf55b7d, emissive: i % 2 === 0 ? 0x24473b : 0x5b1f32, emissiveIntensity:0.45 }));
- orb.userData.radius = 11 + Math.random() * 8;
- orb.userData.speed = 0.2 + Math.random() * 0.5;
- orb.userData.phase = Math.random() * Math.PI * 2;
- orbiters.push(orb); scene.add(orb);
- }
- window.addEventListener('resize', onResize);
- animate();
- }
-
- function onResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }
-
- function animate() {
- requestAnimationFrame(animate);
- var realT = clock.getElapsedTime();
- _snoTOffset += (realT - _snoLastT) * (_wild ? 11 : 0);
- _snoLastT = realT;
- var t = realT + _snoTOffset;
- core.scale.setScalar(1 + Math.sin(t * (_wild ? 6 : 1.8)) * (_wild ? 0.22 : 0.08));
- shellA.rotation.x = t * (_wild ? 0.9 : 0.25); shellA.rotation.y = t * (_wild ? 1.2 : 0.32);
- shellB.rotation.y = -t * (_wild ? 1.1 : 0.22); shellB.rotation.z = t * (_wild ? 0.8 : 0.18);
- shellA.material.opacity = _wild ? 0.54 : 0.34;
- for (var i = 0; i < orbiters.length; i++) {
- var o = orbiters[i], a = t * o.userData.speed + o.userData.phase;
- o.position.set(Math.cos(a) * o.userData.radius, Math.sin(a * 1.4) * 4, Math.sin(a) * o.userData.radius * 0.7);
- }
- camera.position.x = Math.sin(realT * (_wild ? 1.8 : 0.35)) * (_wild ? 3.2 : 1.1);
- camera.position.y = 6 + Math.sin(realT * (_wild ? 1.2 : 0.28)) * (_wild ? 1.8 : 0.4);
- camera.lookAt(0, 0, 0);
- renderer.render(scene, camera);
- }
-
- initThree();
-
- function flash(css, ms) {
- var d=document.createElement('div');
- d.style.cssText='position:fixed;inset:0;z-index:998;pointer-events:none;'+css+';transition:opacity '+(ms||220)+'ms';
- document.body.appendChild(d);
- setTimeout(function(){d.style.opacity='0';setTimeout(function(){d.remove();},ms||220);},25);
- }
- window.snonuxOpenEffect = function() {
- var modal=document.getElementById('post-modal');
- if(modal){modal.classList.add('sno-modal-expand');setTimeout(function(){modal.classList.remove('sno-modal-expand');},400);}
- flash('background:radial-gradient(circle at center,rgba(245,91,125,0.16),transparent 70%)',240);
- };
- window.snonuxCloseEffect = function(){ flash('background:rgba(0,0,0,0.3)',160); };
- window.snonuxNavEffect = function(){ flash('background:linear-gradient(90deg,transparent,rgba(147,255,216,0.1),transparent)',160); };
- window.snonuxPageEffect = function(){ flash('background:radial-gradient(circle at center,rgba(147,255,216,0.12),transparent 72%)',220); };
- window.snonuxScrollEffect = function(dir){
- var d=document.createElement('div');
- d.style.cssText='position:fixed;'+(dir==='down'?'top:0;':'bottom:0;')+'left:0;right:0;height:'+(_wild?'16px':'6px')+';z-index:9000;pointer-events:none;background:linear-gradient(90deg,transparent,rgba(245,91,125,0.8),rgba(147,255,216,0.7),transparent);transition:transform 0.32s ease,opacity 0.32s ease;';
- document.body.appendChild(d);
- setTimeout(function(){d.style.transform=dir==='down'?'translateY(100vh)':'translateY(-100vh)';d.style.opacity='0';},16);
- setTimeout(function(){d.remove();},380);
- };
- window.snonuxWildToggle = function(){ _wild=!_wild; var b=document.getElementById('sno-wild-badge'); if(b)b.classList.toggle('sno-wild-on',_wild); };
- })();
- </script>
- {{template "navscript" .}}
-</body>
-</html>
diff --git a/internal/generator/templates/themes/biomech/meta.json b/internal/generator/templates/themes/biomech/meta.json
new file mode 100644
index 0000000..0ba749c
--- /dev/null
+++ b/internal/generator/templates/themes/biomech/meta.json
@@ -0,0 +1,7 @@
+{
+ "title": "snonux.foo // BIOMECH",
+ "header_html": "\u003cdiv class=\"logo\"\u003e\n \u003cspan class=\"logo-mark\"\u003eSN\u003c/span\u003e\n \u003cdiv class=\"logo-title\"\u003e\n \u003ch1\u003esnonux.foo\u003c/h1\u003e\n \u003cp class=\"subtitle\"\u003emicroblog \u0026mdash; \u003ca href=\"https://foo.zone\"\u003efoo.zone\u003c/a\u003e is the real blog\u003c/p\u003e\n \u003cp class=\"logo-host\"\u003eServed by NetBSD on a Raspberry Pi 3\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"nav\"\u003e\n \u003ca href=\"atom.xml\" class=\"header-feed-link\" rel=\"alternate\" title=\"Atom feed\" type=\"application/atom+xml\"\u003eAtom feed\u003c/a\u003e\n \u003ca href=\"https://foo.zone/about\" class=\"transmit-btn\"\u003eAnatomy\u003c/a\u003e\n \u003c/div\u003e",
+ "splash_inner_html": "\u003ccanvas class=\"splash-gl-canvas\" id=\"splash-gl-canvas\" aria-hidden=\"true\"\u003e\u003c/canvas\u003e\n \u003cdiv class=\"splash-pod\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-inner\"\u003e\n \u003cdiv class=\"splash-title\"\u003esnonux.foo\u003c/div\u003e\n \u003cdiv class=\"splash-tag\"\u003eContainment Membrane\u003c/div\u003e\n \u003cdiv class=\"splash-hint\"\u003eClick or Enter to breach the shell\u003c/div\u003e\n \u003c/div\u003e",
+ "prev_page_text": "\u0026larr; Newer",
+ "next_page_text": "Older \u0026rarr;"
+}
diff --git a/internal/generator/templates/themes/biomech/theme.css b/internal/generator/templates/themes/biomech/theme.css
new file mode 100644
index 0000000..d43ee0b
--- /dev/null
+++ b/internal/generator/templates/themes/biomech/theme.css
@@ -0,0 +1,52 @@
+ :root { --bone:#d0c7bb; --flesh:#803f5d; --vein:#f55b7d; --acid:#93ffd8; --steel:#2d3642; --bg:#09070d; }
+ * { margin:0; padding:0; box-sizing:border-box; }
+ body { font-family:'Oxanium',system-ui,sans-serif; background:var(--bg); color:var(--bone); overflow:hidden; height:100vh; }
+ #three-canvas { position:fixed; inset:0; width:100%; height:100%; z-index:1; }
+ .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
+ header { padding:16px 26px; background:rgba(9,7,13,0.84); backdrop-filter:blur(10px); border-bottom:1px solid rgba(147,255,216,0.16); display:flex; align-items:center; justify-content:space-between; }
+ .logo { display:flex; align-items:center; gap:14px; }
+ .logo-mark { font-size:1.8rem; color:var(--acid); text-shadow:0 0 18px rgba(147,255,216,0.24); }
+ .logo-title h1 { font-size:1.42rem; color:var(--bone); letter-spacing:0.08em; }
+ .logo-title .subtitle { font-size:0.74rem; color:rgba(208,199,187,0.56); margin-top:2px; }
+ .logo-title .subtitle a { color:var(--acid); text-decoration:none; }
+ .logo-title .subtitle a:hover { color:#dffff6; }
+ .transmit-btn { border:1px solid rgba(147,255,216,0.22); color:var(--acid); padding:8px 16px; text-decoration:none; font-size:0.78rem; letter-spacing:0.2em; text-transform:uppercase; transition:all 0.18s; }
+ .transmit-btn:hover { background:rgba(147,255,216,0.1); }
+ a.header-feed-link { color:rgba(208,199,187,0.68); }
+ a.header-feed-link:hover { color:var(--acid); }
+ .nav-hints { background:rgba(12,10,18,0.74); border-bottom:1px solid rgba(147,255,216,0.08); color:rgba(208,199,187,0.44); padding:5px 26px; display:flex; gap:18px; font-size:0.66rem; letter-spacing:0.08em; flex-wrap:wrap; }
+ .nav-hints kbd { background:rgba(128,63,93,0.14); border:1px solid rgba(147,255,216,0.18); color:var(--acid); padding:0 5px; margin:0 2px; }
+ .content { flex:1; overflow-y:auto; padding:20px 26px; scrollbar-width:thin; scrollbar-color:#6d4a69 #120d16; }
+ .page-nav { display:flex; justify-content:center; margin:14px 0; }
+ .page-nav a { border:1px solid rgba(147,255,216,0.18); color:var(--acid); padding:8px 18px; text-decoration:none; font-size:0.78rem; letter-spacing:0.2em; text-transform:uppercase; }
+ .page-nav a:hover { background:rgba(147,255,216,0.08); }
+ .page-nav-footer { flex-shrink:0; padding:8px 26px; display:flex; justify-content:center; background:rgba(9,7,13,0.84); backdrop-filter:blur(10px); border-top:1px solid rgba(147,255,216,0.16); }
+ .post { background:linear-gradient(180deg, rgba(33,20,31,0.9), rgba(12,9,18,0.92)); border:1px solid rgba(147,255,216,0.08); padding:18px; margin-bottom:13px; cursor:pointer; box-shadow:0 16px 38px rgba(0,0,0,0.28); transition:border-color 0.18s, box-shadow 0.18s, transform 0.18s; }
+ .post:hover { border-color:rgba(147,255,216,0.22); transform:translateY(-1px); }
+ .post-active { border-color:rgba(245,91,125,0.28) !important; background:linear-gradient(180deg, rgba(46,18,34,0.94), rgba(13,9,17,0.95)) !important;
+ box-shadow:0 0 0 1px rgba(147,255,216,0.08), 0 18px 42px rgba(0,0,0,0.42), inset 4px 0 0 var(--vein) !important; }
+ .post-header { display:flex; justify-content:space-between; margin-bottom:10px; font-size:0.84rem; }
+ .post-header strong { color:var(--acid); }
+ .post-time { color:rgba(208,199,187,0.58); font-family:'IBM Plex Mono',monospace; }
+ .post-text { line-height:1.7; font-size:0.92rem; }
+ .post-text a { color:var(--acid); text-decoration:none; border-bottom:1px solid rgba(147,255,216,0.18); }
+ .post-image { margin-top:10px; border:1px solid rgba(147,255,216,0.1); filter:saturate(0.9) hue-rotate(-14deg) contrast(1.06); }
+ .post-audio { width:100%; margin-top:10px; filter:hue-rotate(-14deg); }
+ .post-modal { display:none; position:fixed; inset:0; z-index:100; overflow-y:auto; padding:40px 20px; }
+ .post-modal.active { display:block; }
+ .modal-inner { max-width:760px; margin:0 auto; background:rgba(11,9,16,0.98); border:1px solid rgba(147,255,216,0.18); padding:34px; box-shadow:0 22px 76px rgba(0,0,0,0.72); }
+ .modal-close { float:right; background:none; border:none; color:var(--acid); font-family:'IBM Plex Mono',monospace; font-size:0.78rem; cursor:pointer; letter-spacing:0.18em; }
+ @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 16px;} .content{padding:14px 16px;} .modal-inner{padding:24px 16px;} }
+ [data-sno-theme="biomech"] .splash-overlay {
+ background:
+ radial-gradient(circle at 50% 22%, rgba(245,91,125,0.14) 0%, transparent 28%),
+ radial-gradient(circle at 50% 80%, rgba(147,255,216,0.08) 0%, transparent 42%),
+ linear-gradient(180deg, #100b14 0%, #050407 100%);
+ }
+ [data-sno-theme="biomech"] .splash-pod { position:absolute; left:50%; top:10vh; width:min(34vw,220px); height:min(46vw,290px); transform:translateX(-50%); border-radius:48% 48% 42% 42% / 54% 54% 38% 38%;
+ background:radial-gradient(circle at 50% 35%, rgba(147,255,216,0.18) 0%, rgba(147,255,216,0.06) 28%, rgba(128,63,93,0.38) 62%, rgba(12,9,18,0.8) 100%);
+ box-shadow:0 0 42px rgba(245,91,125,0.14); opacity:0.72; z-index:1; }
+ [data-sno-theme="biomech"] .splash-title { font-size:clamp(1.55rem,5vw,2.1rem); color:var(--bone); letter-spacing:0.12em; }
+ [data-sno-theme="biomech"] .splash-tag { color:var(--acid); letter-spacing:0.22em; }
+ [data-sno-theme="biomech"] .splash-hint { color:rgba(208,199,187,0.78); }
+ [data-sno-theme="biomech"] .splash-inner { text-shadow:0 2px 22px rgba(0,0,0,0.95); }
diff --git a/internal/generator/templates/themes/biomech/theme.js b/internal/generator/templates/themes/biomech/theme.js
new file mode 100644
index 0000000..5e4d3e1
--- /dev/null
+++ b/internal/generator/templates/themes/biomech/theme.js
@@ -0,0 +1,98 @@
+
+ (function(){
+ if(document.documentElement.classList.contains('sno-splash-skip'))return;
+ var cv=document.getElementById('splash-gl-canvas');
+ if(!cv||typeof THREE==='undefined')return;
+ var raf,ren,sc,ca,clock,core;
+ function cleanup(){window.removeEventListener('resize',sz);if(raf)cancelAnimationFrame(raf);raf=null;if(ren)ren.dispose();ren=null;window._snonuxSplashWebGLCleanup=null;}
+ window._snonuxSplashWebGLCleanup=cleanup;
+ function sz(){var w=cv.clientWidth||2,h=cv.clientHeight||2;if(ren)ren.setSize(w,h,false);if(ca){ca.aspect=w/h;ca.updateProjectionMatrix();}}
+ ren=new THREE.WebGLRenderer({canvas:cv,antialias:true,alpha:true});ren.setClearColor(0,0);ren.setPixelRatio(Math.min(window.devicePixelRatio||1,2));
+ sc=new THREE.Scene();ca=new THREE.PerspectiveCamera(48,1,0.1,60);ca.position.z=9;clock=new THREE.Clock();
+ core=new THREE.Mesh(new THREE.SphereGeometry(1.2,24,24),new THREE.MeshBasicMaterial({color:0xf55b7d,transparent:true,opacity:0.76})); sc.add(core);
+ var shell=new THREE.Mesh(new THREE.TorusKnotGeometry(2.4,0.36,80,14),new THREE.MeshBasicMaterial({color:0x93ffd8,wireframe:true,transparent:true,opacity:0.42})); sc.add(shell); shell.userData.rot=0.006;
+ sz();window.addEventListener('resize',sz);
+ function loop(){ raf=requestAnimationFrame(loop); var t=clock.getElapsedTime(); shell.rotation.x=t*0.2; shell.rotation.y=t*0.3; core.scale.setScalar(1+Math.sin(t*3.2)*0.08); ren.render(sc,ca); }
+ raf=requestAnimationFrame(loop);
+ })();
+
+
+ (function() {
+ var _wild = false, _snoTOffset = 0, _snoLastT = 0;
+ var scene, camera, renderer, clock, core, shellA, shellB, orbiters = [];
+
+ function initThree() {
+ scene = new THREE.Scene();
+ scene.background = new THREE.Color(0x09070d);
+ scene.fog = new THREE.Fog(0x09070d, 18, 120);
+ camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 220);
+ camera.position.set(0, 6, 26);
+ renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('three-canvas'), antialias: true });
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
+ clock = new THREE.Clock();
+ scene.add(new THREE.AmbientLight(0x553a47, 0.45));
+ var coreLight = new THREE.PointLight(0xf55b7d, 1.6, 80); coreLight.position.set(0,0,0); scene.add(coreLight);
+
+ core = new THREE.Mesh(new THREE.SphereGeometry(4.2, 36, 36), new THREE.MeshPhongMaterial({ color:0x803f5d, emissive:0xf55b7d, emissiveIntensity:0.52, shininess:90 }));
+ shellA = new THREE.Mesh(new THREE.TorusKnotGeometry(7.4, 0.45, 180, 24, 2, 5), new THREE.MeshBasicMaterial({ color:0x93ffd8, wireframe:true, transparent:true, opacity:0.34 }));
+ shellB = new THREE.Mesh(new THREE.TorusKnotGeometry(5.9, 0.28, 160, 16, 3, 7), new THREE.MeshBasicMaterial({ color:0xd0c7bb, wireframe:true, transparent:true, opacity:0.18 }));
+ scene.add(core); scene.add(shellA); scene.add(shellB);
+ for (var i = 0; i < 9; i++) {
+ var orb = new THREE.Mesh(new THREE.SphereGeometry(0.55 + Math.random() * 0.45, 14, 14), new THREE.MeshPhongMaterial({ color: i % 2 === 0 ? 0x93ffd8 : 0xf55b7d, emissive: i % 2 === 0 ? 0x24473b : 0x5b1f32, emissiveIntensity:0.45 }));
+ orb.userData.radius = 11 + Math.random() * 8;
+ orb.userData.speed = 0.2 + Math.random() * 0.5;
+ orb.userData.phase = Math.random() * Math.PI * 2;
+ orbiters.push(orb); scene.add(orb);
+ }
+ window.addEventListener('resize', onResize);
+ animate();
+ }
+
+ function onResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }
+
+ function animate() {
+ requestAnimationFrame(animate);
+ var realT = clock.getElapsedTime();
+ _snoTOffset += (realT - _snoLastT) * (_wild ? 11 : 0);
+ _snoLastT = realT;
+ var t = realT + _snoTOffset;
+ core.scale.setScalar(1 + Math.sin(t * (_wild ? 6 : 1.8)) * (_wild ? 0.22 : 0.08));
+ shellA.rotation.x = t * (_wild ? 0.9 : 0.25); shellA.rotation.y = t * (_wild ? 1.2 : 0.32);
+ shellB.rotation.y = -t * (_wild ? 1.1 : 0.22); shellB.rotation.z = t * (_wild ? 0.8 : 0.18);
+ shellA.material.opacity = _wild ? 0.54 : 0.34;
+ for (var i = 0; i < orbiters.length; i++) {
+ var o = orbiters[i], a = t * o.userData.speed + o.userData.phase;
+ o.position.set(Math.cos(a) * o.userData.radius, Math.sin(a * 1.4) * 4, Math.sin(a) * o.userData.radius * 0.7);
+ }
+ camera.position.x = Math.sin(realT * (_wild ? 1.8 : 0.35)) * (_wild ? 3.2 : 1.1);
+ camera.position.y = 6 + Math.sin(realT * (_wild ? 1.2 : 0.28)) * (_wild ? 1.8 : 0.4);
+ camera.lookAt(0, 0, 0);
+ renderer.render(scene, camera);
+ }
+
+ initThree();
+
+ function flash(css, ms) {
+ var d=document.createElement('div');
+ d.style.cssText='position:fixed;inset:0;z-index:998;pointer-events:none;'+css+';transition:opacity '+(ms||220)+'ms';
+ document.body.appendChild(d);
+ setTimeout(function(){d.style.opacity='0';setTimeout(function(){d.remove();},ms||220);},25);
+ }
+ window.snonuxOpenEffect = function() {
+ var modal=document.getElementById('post-modal');
+ if(modal){modal.classList.add('sno-modal-expand');setTimeout(function(){modal.classList.remove('sno-modal-expand');},400);}
+ flash('background:radial-gradient(circle at center,rgba(245,91,125,0.16),transparent 70%)',240);
+ };
+ window.snonuxCloseEffect = function(){ flash('background:rgba(0,0,0,0.3)',160); };
+ window.snonuxNavEffect = function(){ flash('background:linear-gradient(90deg,transparent,rgba(147,255,216,0.1),transparent)',160); };
+ window.snonuxPageEffect = function(){ flash('background:radial-gradient(circle at center,rgba(147,255,216,0.12),transparent 72%)',220); };
+ window.snonuxScrollEffect = function(dir){
+ var d=document.createElement('div');
+ d.style.cssText='position:fixed;'+(dir==='down'?'top:0;':'bottom:0;')+'left:0;right:0;height:'+(_wild?'16px':'6px')+';z-index:9000;pointer-events:none;background:linear-gradient(90deg,transparent,rgba(245,91,125,0.8),rgba(147,255,216,0.7),transparent);transition:transform 0.32s ease,opacity 0.32s ease;';
+ document.body.appendChild(d);
+ setTimeout(function(){d.style.transform=dir==='down'?'translateY(100vh)':'translateY(-100vh)';d.style.opacity='0';},16);
+ setTimeout(function(){d.remove();},380);
+ };
+ window.snonuxWildToggle = function(){ _wild=!_wild; var b=document.getElementById('sno-wild-badge'); if(b)b.classList.toggle('sno-wild-on',_wild); };
+ })();
diff --git a/internal/generator/templates/themes/brutalist.tmpl b/internal/generator/templates/themes/brutalist.tmpl
deleted file mode 100644
index 9a02b25..0000000
--- a/internal/generator/templates/themes/brutalist.tmpl
+++ /dev/null
@@ -1,260 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>SNONUX.FOO</title>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
- <style>
- :root { --red:#ff2200; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:Impact,'Arial Narrow',Arial,sans-serif;
- background:#000; color:#fff; overflow:hidden; height:100vh; }
- #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
- .overlay { height:100vh; display:flex; flex-direction:column; position:relative; z-index:10; }
- header { padding:14px 24px; background:#000; border-bottom:4px solid #fff;
- display:flex; align-items:center; justify-content:space-between; }
- .logo { display:flex; align-items:center; gap:16px; }
- .logo-mark { font-size:2.8rem; color:var(--red); line-height:1; }
- .logo-title h1 { font-size:2rem; color:#fff; letter-spacing:0; line-height:1; }
- .logo-title .subtitle { font-size:0.78rem; color:#888; margin-top:3px;
- font-family:'Courier New',monospace; }
- .logo-title .subtitle a { color:var(--red); text-decoration:none; }
- .logo-title .subtitle a:hover { text-decoration:underline; }
- .transmit-btn { border:3px solid var(--red); color:var(--red); padding:10px 20px;
- border-radius:0; text-decoration:none; font-family:Impact; font-size:1.05rem;
- letter-spacing:2px; transition:all 0.1s; }
- .transmit-btn:hover { background:var(--red); color:#000; }
- a.header-feed-link { color:#aaa; font-family:'Courier New',monospace; font-size:0.78rem; }
- a.header-feed-link:hover { color:var(--red); }
- .nav-hints { background:#111; border-bottom:2px solid #333; color:#888;
- padding:5px 24px; display:flex; gap:18px; font-family:'Courier New',monospace;
- font-size:0.7rem; flex-wrap:wrap; }
- .nav-hints kbd { background:#000; border:1px solid #555; color:#fff;
- border-radius:0; padding:0 5px; margin:0 2px; font-size:0.7rem; }
- .content { flex:1; overflow-y:auto; padding:20px 24px;
- scrollbar-width:thin; scrollbar-color:#fff #000; }
- .page-nav { display:flex; justify-content:center; margin:14px 0; }
- .page-nav a { border:3px solid #fff; color:#fff; padding:9px 22px;
- border-radius:0; text-decoration:none; font-family:Impact;
- font-size:1rem; letter-spacing:2px; }
- .page-nav a:hover { background:#fff; color:#000; }
- .page-nav-footer { flex-shrink:0; padding:6px 24px; display:flex; justify-content:center;
- background:#000; border-top:4px solid #fff; }
- .post { background:#000; border:3px solid #fff; border-radius:0;
- padding:20px 22px; margin-bottom:14px; cursor:pointer;
- transition:border-color 0.1s,background 0.1s; }
- .post:hover { border-color:var(--red); }
- .post-active { border-color:var(--red) !important; background:#0d0000 !important;
- border-left-width:8px !important; box-shadow:none !important; }
- .post-header { display:flex; justify-content:space-between; margin-bottom:12px; }
- .post-time { color:#aaa; font-family:'Courier New',monospace; font-size:0.82rem; }
- .post-text { font-family:'Arial',sans-serif; font-size:1rem; line-height:1.5; }
- .post-text a { color:var(--red); text-decoration:underline; }
- .post-audio { width:100%; margin-top:10px; }
- .post-modal { display:none; position:fixed; inset:0; z-index:100;
- background:rgba(0,0,0,0.98); overflow-y:auto; padding:40px 20px; }
- .post-modal.active { display:block; }
- .modal-inner { max-width:780px; margin:0 auto; background:#000;
- border:4px solid #fff; border-radius:0; padding:38px;
- box-shadow:8px 8px 0 var(--red); }
- .modal-close { float:right; background:none; border:none; color:var(--red);
- font-family:Impact; font-size:1.3rem; cursor:pointer; letter-spacing:2px; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:10px 16px;} .logo-mark{font-size:2rem;} }
- .splash-overlay.splash-brutalist { background:#000; }
- .splash-brutalist .splash-frame {
- border:6px solid #fff; padding:clamp(1.5rem,5vw,2.5rem) clamp(1.25rem,4vw,2rem);
- box-shadow: 12px 12px 0 var(--red); animation: splashBrutalJolt 3s steps(2,end) infinite;
- }
- @keyframes splashBrutalJolt { 0%,100% { transform: translate(0,0); } 50% { transform: translate(2px,-2px); } }
- .splash-brutalist .splash-title { font-family:Impact,sans-serif; font-size:clamp(1.8rem,6vw,2.8rem); color:#fff; }
- .splash-brutalist .splash-tag { font-family:'Courier New',monospace; color:var(--red); }
- .splash-brutalist .splash-hint { font-family:'Courier New',monospace; color:#c8c8c8; }
- .splash-brutalist .splash-inner { text-shadow: 0 0 12px #000, 0 2px 8px #000; }
-{{template "navSharedCSSInner"}}
- </style>
-</head>
-<body>
- {{template "splashGate"}}
- <div id="splash-overlay" class="splash-overlay splash-brutalist" role="dialog" aria-modal="true" aria-label="Open microblog" tabindex="-1">
- <canvas class="splash-gl-canvas" id="splash-gl-canvas" aria-hidden="true"></canvas>
- <div class="splash-inner splash-frame">
- <div class="splash-title">SNONUX.FOO</div>
- <div class="splash-tag">Brutalist theme</div>
- <div class="splash-hint">[ CLICK OR ENTER TO TRANSMIT ]</div>
- </div>
- </div>
- <script>
- (function(){
- if(document.documentElement.classList.contains('sno-splash-skip'))return;
- var cv=document.getElementById('splash-gl-canvas');
- if(!cv||typeof THREE==='undefined')return;
- var raf,ren,sc,ca,g=new THREE.Group(),t0=performance.now();
- function cleanup(){window.removeEventListener('resize',sz);if(raf)cancelAnimationFrame(raf);raf=null;if(ren)ren.dispose();ren=null;window._snonuxSplashWebGLCleanup=null;}
- window._snonuxSplashWebGLCleanup=cleanup;
- function sz(){var w=cv.clientWidth||2,h=cv.clientHeight||2;if(ren)ren.setSize(w,h,false);if(ca){ca.aspect=w/h;ca.updateProjectionMatrix();}}
- ren=new THREE.WebGLRenderer({canvas:cv,antialias:true,alpha:true});ren.setClearColor(0,0);ren.setPixelRatio(Math.min(window.devicePixelRatio||1,2));
- sc=new THREE.Scene();ca=new THREE.PerspectiveCamera(50,1,0.1,80);ca.position.z=8;
- var b1=new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(3.4,2.4,2.4)),new THREE.LineBasicMaterial({color:0xffffff}));
- var b2=new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(2.2,1.6,1.6)),new THREE.LineBasicMaterial({color:0xff2200}));
- b2.position.set(0.3,0.2,0.5);g.add(b1);g.add(b2);sc.add(g);sz();window.addEventListener('resize',sz);
- function loop(now){raf=requestAnimationFrame(loop);var t=(now-t0)*0.001;g.rotation.x=t*0.51;g.rotation.y=t*0.73;ren.render(sc,ca);}
- raf=requestAnimationFrame(loop);
- })();
- </script>
- <canvas id="three-canvas"></canvas>
- <div class="overlay">
- <header>
- <div class="logo">
- <span class="logo-mark">SN</span>
- <div class="logo-title">
- <h1>SNONUX.FOO</h1>
- <p class="subtitle">MICROBLOG &mdash; <a href="https://foo.zone">FOO.ZONE</a> IS THE REAL BLOG</p>
- <p class="logo-host">Served by NetBSD on a Raspberry Pi 3</p>
- </div>
- </div>
- <div class="nav">
- <a href="atom.xml" class="header-feed-link" rel="alternate" title="Atom feed" type="application/atom+xml">Atom feed</a>
- <a href="https://foo.zone/about" class="transmit-btn">TRANSMIT</a>
- </div>
- </header>
- {{template "navhints" .}}
- <div class="content" id="post-content">
- {{range $i, $post := .Posts}}
- <div class="post" id="post-{{$post.ID}}" data-index="{{$i}}">
- <div class="post-header">
- <div><strong>@SNONUX</strong></div>
- <div class="post-time">{{$post.FormattedTime}}</div>
- </div>
- <div class="post-text">{{$post.ContentHTML}}</div>
- </div>
- {{end}}
- </div>
- {{if or .PrevPage .NextPage}}
- <footer class="page-nav-footer" aria-label="Pagination">
- <div class="page-nav page-nav-dual">
- {{if .PrevPage}}<a href="{{.PrevPage}}">&larr; NEWER</a>{{end}}
- {{if .NextPage}}<a href="{{.NextPage}}">OLDER &rarr;</a>{{end}}
- </div>
- </footer>
- {{end}}
- </div>
- {{template "navmodal" .}}
- <script>
- // Brutalist WebGL: harsh slowly-rotating boxes — solid white and wireframe red.
- // No fog, no softness. Pure geometric violence against the black void.
- (function() {
- var _wild = false;
- var scene, camera, renderer, clock;
- var boxes = [];
-
- function initThree() {
- scene = new THREE.Scene();
- scene.background = new THREE.Color(0x000000);
-
- camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 200);
- camera.position.set(0, 0, 40);
-
- renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('three-canvas'), antialias: false });
- renderer.setSize(window.innerWidth, window.innerHeight);
- renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
- clock = new THREE.Clock();
-
- // Box configurations: [size, posX, posY, posZ, rotSpeedX, rotSpeedY, wireframe, color]
- var configs = [
- [10, 0, 0, 0, 0.002, 0.005, false, 0xffffff],
- [6, 18, -6, -8, 0.004, 0.003, true, 0xff2200],
- [7, -16, 5, -10, 0.003, 0.006, true, 0xff2200],
- [5, 8, 12, -5, 0.006, 0.002, false, 0xff2200],
- [4, -10,-10, -3, 0.005, 0.004, false, 0xffffff],
- ];
-
- configs.forEach(function(c) {
- var geo = new THREE.BoxGeometry(c[0], c[0], c[0]);
- var mat = new THREE.MeshBasicMaterial({ color: c[7], wireframe: c[6] });
- var mesh = new THREE.Mesh(geo, mat);
- mesh.position.set(c[1], c[2], c[3]);
- boxes.push({ mesh: mesh, rx: c[4], ry: c[5] });
- scene.add(mesh);
- });
-
- window.addEventListener('resize', onResize);
- animate();
- }
-
- function onResize() {
- camera.aspect = window.innerWidth / window.innerHeight;
- camera.updateProjectionMatrix();
- renderer.setSize(window.innerWidth, window.innerHeight);
- }
-
- function animate() {
- requestAnimationFrame(animate);
- var sm = _wild ? 15 : 1;
- boxes.forEach(function(b) {
- b.mesh.rotation.x += b.rx * sm;
- b.mesh.rotation.y += b.ry * sm;
- // Wild mode: random jitter on positions
- if (_wild) { b.mesh.position.x += (Math.random()-0.5)*0.4; b.mesh.position.y += (Math.random()-0.5)*0.4; }
- });
- renderer.render(scene, camera);
- }
-
- initThree();
-
- // Brutalist nav/wild effects — violent shake on navigate, geometric chaos on wild
- window.snonuxOpenEffect = function() {
- // Expand violently from nothing — pure brutalist impact
- var modal = document.getElementById('post-modal');
- if (modal) { modal.classList.add('sno-modal-expand'); setTimeout(function() { modal.classList.remove('sno-modal-expand'); }, 420); }
- var d = document.createElement('div');
- d.style.cssText = 'position:fixed;inset:0;z-index:997;pointer-events:none;background:rgba(255,34,0,0.22);transition:opacity 0.14s';
- document.body.appendChild(d);
- setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 170); }, 15);
- };
- window.snonuxCloseEffect = function() {
- var ov = document.querySelector('.overlay');
- if (ov) { ov.classList.add('sno-fx-shake'); setTimeout(function() { ov.classList.remove('sno-fx-shake'); }, 360); }
- };
- window.snonuxScrollEffect = function(dir) {
- var isDown = dir === 'down';
- var thick = _wild ? '14px' : '5px';
- var d = document.createElement('div');
- // Brutalist: harsh black-and-white hard edge
- d.style.cssText = 'position:fixed;left:0;right:0;height:' + thick + ';z-index:9000;pointer-events:none;' +
- 'background:linear-gradient(90deg,rgba(0,0,0,0.95),rgba(255,255,255,0.95),rgba(0,0,0,0.95));' +
- (isDown ? 'top:0;' : 'bottom:0;') +
- 'transition:transform 0.25s ease,opacity 0.25s ease;';
- document.body.appendChild(d);
- setTimeout(function() { d.style.transform = isDown ? 'translateY(100vh)' : 'translateY(-100vh)'; d.style.opacity='0'; }, 16);
- setTimeout(function() { d.remove(); }, 320);
- };
- window.snonuxWildToggle = function() {
- _wild = !_wild;
- var b = document.getElementById('sno-wild-badge');
- if (b) b.classList.toggle('sno-wild-on', _wild);
- };
- window.snonuxNavEffect = function() {
- // Violent double shake + red flash
- var ov = document.querySelector('.overlay');
- if (ov) {
- ov.classList.add('sno-fx-shake');
- setTimeout(function() { ov.classList.remove('sno-fx-shake'); setTimeout(function() { ov.classList.add('sno-fx-shake'); setTimeout(function() { ov.classList.remove('sno-fx-shake'); }, 380); }, 50); }, 400);
- }
- var d = document.createElement('div');
- d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(255,34,0,0.28);transition:opacity 0.18s';
- document.body.appendChild(d);
- setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 200); }, 25);
- };
- window.snonuxPageEffect = function() {
- // Color inversion flash
- var d = document.createElement('div');
- d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:#fff;mix-blend-mode:difference;transition:opacity 0.15s';
- document.body.appendChild(d);
- setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 180); }, 20);
- };
- })();
- </script>
- {{template "navscript" .}}
-</body>
-</html>
diff --git a/internal/generator/templates/themes/brutalist/meta.json b/internal/generator/templates/themes/brutalist/meta.json
new file mode 100644
index 0000000..b6dee75
--- /dev/null
+++ b/internal/generator/templates/themes/brutalist/meta.json
@@ -0,0 +1,7 @@
+{
+ "title": "SNONUX.FOO",
+ "header_html": "\u003cdiv class=\"logo\"\u003e\n \u003cspan class=\"logo-mark\"\u003eSN\u003c/span\u003e\n \u003cdiv class=\"logo-title\"\u003e\n \u003ch1\u003eSNONUX.FOO\u003c/h1\u003e\n \u003cp class=\"subtitle\"\u003eMICROBLOG \u0026mdash; \u003ca href=\"https://foo.zone\"\u003eFOO.ZONE\u003c/a\u003e IS THE REAL BLOG\u003c/p\u003e\n \u003cp class=\"logo-host\"\u003eServed by NetBSD on a Raspberry Pi 3\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"nav\"\u003e\n \u003ca href=\"atom.xml\" class=\"header-feed-link\" rel=\"alternate\" title=\"Atom feed\" type=\"application/atom+xml\"\u003eAtom feed\u003c/a\u003e\n \u003ca href=\"https://foo.zone/about\" class=\"transmit-btn\"\u003eTRANSMIT\u003c/a\u003e\n \u003c/div\u003e",
+ "splash_inner_html": "\u003ccanvas class=\"splash-gl-canvas\" id=\"splash-gl-canvas\" aria-hidden=\"true\"\u003e\u003c/canvas\u003e\n \u003cdiv class=\"splash-inner splash-frame\"\u003e\n \u003cdiv class=\"splash-title\"\u003eSNONUX.FOO\u003c/div\u003e\n \u003cdiv class=\"splash-tag\"\u003eBrutalist theme\u003c/div\u003e\n \u003cdiv class=\"splash-hint\"\u003e[ CLICK OR ENTER TO TRANSMIT ]\u003c/div\u003e\n \u003c/div\u003e",
+ "prev_page_text": "\u0026larr; NEWER",
+ "next_page_text": "OLDER \u0026rarr;"
+}
diff --git a/internal/generator/templates/themes/brutalist/theme.css b/internal/generator/templates/themes/brutalist/theme.css
new file mode 100644
index 0000000..4c8dac5
--- /dev/null
+++ b/internal/generator/templates/themes/brutalist/theme.css
@@ -0,0 +1,65 @@
+ :root { --red:#ff2200; }
+ * { margin:0; padding:0; box-sizing:border-box; }
+ body { font-family:Impact,'Arial Narrow',Arial,sans-serif;
+ background:#000; color:#fff; overflow:hidden; height:100vh; }
+ #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
+ .overlay { height:100vh; display:flex; flex-direction:column; position:relative; z-index:10; }
+ header { padding:14px 24px; background:#000; border-bottom:4px solid #fff;
+ display:flex; align-items:center; justify-content:space-between; }
+ .logo { display:flex; align-items:center; gap:16px; }
+ .logo-mark { font-size:2.8rem; color:var(--red); line-height:1; }
+ .logo-title h1 { font-size:2rem; color:#fff; letter-spacing:0; line-height:1; }
+ .logo-title .subtitle { font-size:0.78rem; color:#888; margin-top:3px;
+ font-family:'Courier New',monospace; }
+ .logo-title .subtitle a { color:var(--red); text-decoration:none; }
+ .logo-title .subtitle a:hover { text-decoration:underline; }
+ .transmit-btn { border:3px solid var(--red); color:var(--red); padding:10px 20px;
+ border-radius:0; text-decoration:none; font-family:Impact; font-size:1.05rem;
+ letter-spacing:2px; transition:all 0.1s; }
+ .transmit-btn:hover { background:var(--red); color:#000; }
+ a.header-feed-link { color:#aaa; font-family:'Courier New',monospace; font-size:0.78rem; }
+ a.header-feed-link:hover { color:var(--red); }
+ .nav-hints { background:#111; border-bottom:2px solid #333; color:#888;
+ padding:5px 24px; display:flex; gap:18px; font-family:'Courier New',monospace;
+ font-size:0.7rem; flex-wrap:wrap; }
+ .nav-hints kbd { background:#000; border:1px solid #555; color:#fff;
+ border-radius:0; padding:0 5px; margin:0 2px; font-size:0.7rem; }
+ .content { flex:1; overflow-y:auto; padding:20px 24px;
+ scrollbar-width:thin; scrollbar-color:#fff #000; }
+ .page-nav { display:flex; justify-content:center; margin:14px 0; }
+ .page-nav a { border:3px solid #fff; color:#fff; padding:9px 22px;
+ border-radius:0; text-decoration:none; font-family:Impact;
+ font-size:1rem; letter-spacing:2px; }
+ .page-nav a:hover { background:#fff; color:#000; }
+ .page-nav-footer { flex-shrink:0; padding:6px 24px; display:flex; justify-content:center;
+ background:#000; border-top:4px solid #fff; }
+ .post { background:#000; border:3px solid #fff; border-radius:0;
+ padding:20px 22px; margin-bottom:14px; cursor:pointer;
+ transition:border-color 0.1s,background 0.1s; }
+ .post:hover { border-color:var(--red); }
+ .post-active { border-color:var(--red) !important; background:#0d0000 !important;
+ border-left-width:8px !important; box-shadow:none !important; }
+ .post-header { display:flex; justify-content:space-between; margin-bottom:12px; }
+ .post-time { color:#aaa; font-family:'Courier New',monospace; font-size:0.82rem; }
+ .post-text { font-family:'Arial',sans-serif; font-size:1rem; line-height:1.5; }
+ .post-text a { color:var(--red); text-decoration:underline; }
+ .post-audio { width:100%; margin-top:10px; }
+ .post-modal { display:none; position:fixed; inset:0; z-index:100;
+ background:rgba(0,0,0,0.98); overflow-y:auto; padding:40px 20px; }
+ .post-modal.active { display:block; }
+ .modal-inner { max-width:780px; margin:0 auto; background:#000;
+ border:4px solid #fff; border-radius:0; padding:38px;
+ box-shadow:8px 8px 0 var(--red); }
+ .modal-close { float:right; background:none; border:none; color:var(--red);
+ font-family:Impact; font-size:1.3rem; cursor:pointer; letter-spacing:2px; }
+ @media(max-width:640px) { .nav-hints{display:none;} header{padding:10px 16px;} .logo-mark{font-size:2rem;} }
+ [data-sno-theme="brutalist"] .splash-overlay { background:#000; }
+ [data-sno-theme="brutalist"] .splash-frame {
+ border:6px solid #fff; padding:clamp(1.5rem,5vw,2.5rem) clamp(1.25rem,4vw,2rem);
+ box-shadow: 12px 12px 0 var(--red); animation: splashBrutalJolt 3s steps(2,end) infinite;
+ }
+ @keyframes splashBrutalJolt { 0%,100% { transform: translate(0,0); } 50% { transform: translate(2px,-2px); } }
+ [data-sno-theme="brutalist"] .splash-title { font-family:Impact,sans-serif; font-size:clamp(1.8rem,6vw,2.8rem); color:#fff; }
+ [data-sno-theme="brutalist"] .splash-tag { font-family:'Courier New',monospace; color:var(--red); }
+ [data-sno-theme="brutalist"] .splash-hint { font-family:'Courier New',monospace; color:#c8c8c8; }
+ [data-sno-theme="brutalist"] .splash-inner { text-shadow: 0 0 12px #000, 0 2px 8px #000; }
diff --git a/internal/generator/templates/themes/brutalist/theme.js b/internal/generator/templates/themes/brutalist/theme.js
new file mode 100644
index 0000000..227763a
--- /dev/null
+++ b/internal/generator/templates/themes/brutalist/theme.js
@@ -0,0 +1,132 @@
+
+ (function(){
+ if(document.documentElement.classList.contains('sno-splash-skip'))return;
+ var cv=document.getElementById('splash-gl-canvas');
+ if(!cv||typeof THREE==='undefined')return;
+ var raf,ren,sc,ca,g=new THREE.Group(),t0=performance.now();
+ function cleanup(){window.removeEventListener('resize',sz);if(raf)cancelAnimationFrame(raf);raf=null;if(ren)ren.dispose();ren=null;window._snonuxSplashWebGLCleanup=null;}
+ window._snonuxSplashWebGLCleanup=cleanup;
+ function sz(){var w=cv.clientWidth||2,h=cv.clientHeight||2;if(ren)ren.setSize(w,h,false);if(ca){ca.aspect=w/h;ca.updateProjectionMatrix();}}
+ ren=new THREE.WebGLRenderer({canvas:cv,antialias:true,alpha:true});ren.setClearColor(0,0);ren.setPixelRatio(Math.min(window.devicePixelRatio||1,2));
+ sc=new THREE.Scene();ca=new THREE.PerspectiveCamera(50,1,0.1,80);ca.position.z=8;
+ var b1=new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(3.4,2.4,2.4)),new THREE.LineBasicMaterial({color:0xffffff}));
+ var b2=new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(2.2,1.6,1.6)),new THREE.LineBasicMaterial({color:0xff2200}));
+ b2.position.set(0.3,0.2,0.5);g.add(b1);g.add(b2);sc.add(g);sz();window.addEventListener('resize',sz);
+ function loop(now){raf=requestAnimationFrame(loop);var t=(now-t0)*0.001;g.rotation.x=t*0.51;g.rotation.y=t*0.73;ren.render(sc,ca);}
+ raf=requestAnimationFrame(loop);
+ })();
+
+
+ // Brutalist WebGL: harsh slowly-rotating boxes — solid white and wireframe red.
+ // No fog, no softness. Pure geometric violence against the black void.
+ (function() {
+ var _wild = false;
+ var scene, camera, renderer, clock;
+ var boxes = [];
+
+ function initThree() {
+ scene = new THREE.Scene();
+ scene.background = new THREE.Color(0x000000);
+
+ camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 200);
+ camera.position.set(0, 0, 40);
+
+ renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('three-canvas'), antialias: false });
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
+ clock = new THREE.Clock();
+
+ // Box configurations: [size, posX, posY, posZ, rotSpeedX, rotSpeedY, wireframe, color]
+ var configs = [
+ [10, 0, 0, 0, 0.002, 0.005, false, 0xffffff],
+ [6, 18, -6, -8, 0.004, 0.003, true, 0xff2200],
+ [7, -16, 5, -10, 0.003, 0.006, true, 0xff2200],
+ [5, 8, 12, -5, 0.006, 0.002, false, 0xff2200],
+ [4, -10,-10, -3, 0.005, 0.004, false, 0xffffff],
+ ];
+
+ configs.forEach(function(c) {
+ var geo = new THREE.BoxGeometry(c[0], c[0], c[0]);
+ var mat = new THREE.MeshBasicMaterial({ color: c[7], wireframe: c[6] });
+ var mesh = new THREE.Mesh(geo, mat);
+ mesh.position.set(c[1], c[2], c[3]);
+ boxes.push({ mesh: mesh, rx: c[4], ry: c[5] });
+ scene.add(mesh);
+ });
+
+ window.addEventListener('resize', onResize);
+ animate();
+ }
+
+ function onResize() {
+ camera.aspect = window.innerWidth / window.innerHeight;
+ camera.updateProjectionMatrix();
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ }
+
+ function animate() {
+ requestAnimationFrame(animate);
+ var sm = _wild ? 15 : 1;
+ boxes.forEach(function(b) {
+ b.mesh.rotation.x += b.rx * sm;
+ b.mesh.rotation.y += b.ry * sm;
+ // Wild mode: random jitter on positions
+ if (_wild) { b.mesh.position.x += (Math.random()-0.5)*0.4; b.mesh.position.y += (Math.random()-0.5)*0.4; }
+ });
+ renderer.render(scene, camera);
+ }
+
+ initThree();
+
+ // Brutalist nav/wild effects — violent shake on navigate, geometric chaos on wild
+ window.snonuxOpenEffect = function() {
+ // Expand violently from nothing — pure brutalist impact
+ var modal = document.getElementById('post-modal');
+ if (modal) { modal.classList.add('sno-modal-expand'); setTimeout(function() { modal.classList.remove('sno-modal-expand'); }, 420); }
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:997;pointer-events:none;background:rgba(255,34,0,0.22);transition:opacity 0.14s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 170); }, 15);
+ };
+ window.snonuxCloseEffect = function() {
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-shake'); setTimeout(function() { ov.classList.remove('sno-fx-shake'); }, 360); }
+ };
+ window.snonuxScrollEffect = function(dir) {
+ var isDown = dir === 'down';
+ var thick = _wild ? '14px' : '5px';
+ var d = document.createElement('div');
+ // Brutalist: harsh black-and-white hard edge
+ d.style.cssText = 'position:fixed;left:0;right:0;height:' + thick + ';z-index:9000;pointer-events:none;' +
+ 'background:linear-gradient(90deg,rgba(0,0,0,0.95),rgba(255,255,255,0.95),rgba(0,0,0,0.95));' +
+ (isDown ? 'top:0;' : 'bottom:0;') +
+ 'transition:transform 0.25s ease,opacity 0.25s ease;';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.transform = isDown ? 'translateY(100vh)' : 'translateY(-100vh)'; d.style.opacity='0'; }, 16);
+ setTimeout(function() { d.remove(); }, 320);
+ };
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ };
+ window.snonuxNavEffect = function() {
+ // Violent double shake + red flash
+ var ov = document.querySelector('.overlay');
+ if (ov) {
+ ov.classList.add('sno-fx-shake');
+ setTimeout(function() { ov.classList.remove('sno-fx-shake'); setTimeout(function() { ov.classList.add('sno-fx-shake'); setTimeout(function() { ov.classList.remove('sno-fx-shake'); }, 380); }, 50); }, 400);
+ }
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(255,34,0,0.28);transition:opacity 0.18s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 200); }, 25);
+ };
+ window.snonuxPageEffect = function() {
+ // Color inversion flash
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:#fff;mix-blend-mode:difference;transition:opacity 0.15s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 180); }, 20);
+ };
+ })();
diff --git a/internal/generator/templates/themes/cathedral/meta.json b/internal/generator/templates/themes/cathedral/meta.json
new file mode 100644
index 0000000..f4a81db
--- /dev/null
+++ b/internal/generator/templates/themes/cathedral/meta.json
@@ -0,0 +1,7 @@
+{
+ "title": "snonux.foo // CATHEDRAL",
+ "header_html": "\u003cdiv class=\"logo\"\u003e\n \u003cspan class=\"logo-mark\"\u003eSN\u003c/span\u003e\n \u003cdiv class=\"logo-title\"\u003e\n \u003ch1\u003esnonux.foo\u003c/h1\u003e\n \u003cp class=\"subtitle\"\u003emicroblog \u0026mdash; \u003ca href=\"https://foo.zone\"\u003efoo.zone\u003c/a\u003e is the real blog\u003c/p\u003e\n \u003cp class=\"logo-host\"\u003eServed by NetBSD on a Raspberry Pi 3\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"nav\"\u003e\n \u003ca href=\"atom.xml\" class=\"header-feed-link\" rel=\"alternate\" title=\"Atom feed\" type=\"application/atom+xml\"\u003eAtom feed\u003c/a\u003e\n \u003ca href=\"https://foo.zone/about\" class=\"transmit-btn\"\u003eReliquary\u003c/a\u003e\n \u003c/div\u003e",
+ "splash_inner_html": "\u003ccanvas class=\"splash-gl-canvas\" id=\"splash-gl-canvas\" aria-hidden=\"true\"\u003e\u003c/canvas\u003e\n \u003cdiv class=\"splash-rose\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-pipes\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-incense\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-inner\"\u003e\n \u003cdiv class=\"splash-title\"\u003esnonux.foo\u003c/div\u003e\n \u003cdiv class=\"splash-tag\"\u003eRitual Engine\u003c/div\u003e\n \u003cdiv class=\"splash-hint\"\u003eClick or Enter to cross the nave\u003c/div\u003e\n \u003c/div\u003e",
+ "prev_page_text": "\u0026larr; Newer",
+ "next_page_text": "Older \u0026rarr;"
+}
diff --git a/internal/generator/templates/themes/cathedral/theme.css b/internal/generator/templates/themes/cathedral/theme.css
new file mode 100644
index 0000000..5dc6456
--- /dev/null
+++ b/internal/generator/templates/themes/cathedral/theme.css
@@ -0,0 +1,79 @@
+ :root { --gold:#e0c47f; --violet:#6f4fae; --ruby:#8e2f49; --glass:#7bc2ff; --stone:#110f16; --chalk:#f0e8d9; }
+ * { margin:0; padding:0; box-sizing:border-box; }
+ body { font-family:'Spectral',serif; background:#0f0d14; color:var(--chalk); overflow:hidden; height:100vh; }
+ body::before { content:''; position:fixed; inset:0; z-index:998; pointer-events:none;
+ background:
+ radial-gradient(circle at 50% 4%, rgba(224,196,127,0.1) 0%, transparent 24%),
+ linear-gradient(90deg, rgba(123,194,255,0.05), transparent 18%, transparent 82%, rgba(142,47,73,0.06));
+ mix-blend-mode:screen; opacity:0.8; }
+ #three-canvas { position:fixed; inset:0; width:100%; height:100%; z-index:1; }
+ .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
+ header { padding:16px 28px; background:rgba(11,10,16,0.84); backdrop-filter:blur(10px); border-bottom:1px solid rgba(224,196,127,0.18); display:flex; align-items:center; justify-content:space-between; }
+ .logo { display:flex; align-items:center; gap:14px; }
+ .logo-mark { font-family:'Cinzel',serif; font-size:1.9rem; color:var(--gold); text-shadow:0 0 14px rgba(224,196,127,0.22); }
+ .logo-mark::after { content:'✢'; margin-left:8px; color:#fff3c8; text-shadow:0 0 12px rgba(224,196,127,0.6); }
+ .logo-title h1 { font-family:'Cinzel',serif; font-size:1.5rem; letter-spacing:0.1em; color:var(--chalk); }
+ .logo-title .subtitle { font-size:0.8rem; color:rgba(240,232,217,0.6); margin-top:2px; }
+ .logo-title .subtitle a { color:var(--gold); text-decoration:none; }
+ .logo-title .subtitle a:hover { color:#fff3c8; }
+ .transmit-btn { border:1px solid rgba(224,196,127,0.28); color:var(--gold); padding:8px 16px; text-decoration:none; font-size:0.8rem; letter-spacing:0.2em; text-transform:uppercase; transition:all 0.18s; }
+ .transmit-btn:hover { background:rgba(224,196,127,0.12); border-color:rgba(224,196,127,0.52); }
+ a.header-feed-link { color:rgba(224,196,127,0.84); }
+ a.header-feed-link:hover { color:#fff3c8; }
+ .nav-hints { background:rgba(17,14,22,0.72); border-bottom:1px solid rgba(224,196,127,0.08); color:rgba(240,232,217,0.48); padding:5px 28px; display:flex; gap:18px; font-size:0.68rem; letter-spacing:0.08em; flex-wrap:wrap; }
+ .nav-hints kbd { background:rgba(111,79,174,0.16); border:1px solid rgba(224,196,127,0.2); color:var(--gold); padding:0 5px; margin:0 2px; }
+ .content { flex:1; overflow-y:auto; padding:20px 28px; scrollbar-width:thin; scrollbar-color:#7e6231 #18131d; }
+ .page-nav { display:flex; justify-content:center; margin:14px 0; }
+ .page-nav a { border:1px solid rgba(224,196,127,0.2); color:var(--gold); padding:8px 18px; text-decoration:none; font-size:0.8rem; letter-spacing:0.16em; text-transform:uppercase; }
+ .page-nav a:hover { background:rgba(224,196,127,0.08); }
+ .page-nav-footer { flex-shrink:0; padding:8px 28px; display:flex; justify-content:center; background:rgba(11,10,16,0.84); backdrop-filter:blur(10px); border-top:1px solid rgba(224,196,127,0.18); }
+ .post { position:relative; background:
+ linear-gradient(180deg, rgba(30,19,30,0.93), rgba(13,10,17,0.95)),
+ radial-gradient(circle at 14% 0%, rgba(123,194,255,0.08), transparent 28%);
+ border:1px solid rgba(224,196,127,0.08); padding:20px; margin-bottom:14px; cursor:pointer;
+ box-shadow:0 16px 38px rgba(0,0,0,0.28); transition:border-color 0.2s, box-shadow 0.2s, transform 0.2s; }
+ .post::before { content:''; position:absolute; inset:0; pointer-events:none; background:linear-gradient(120deg, rgba(123,194,255,0.05), transparent 36%, rgba(142,47,73,0.06) 68%, transparent); }
+ .post:hover { border-color:rgba(224,196,127,0.24); transform:translateY(-1px); box-shadow:0 22px 42px rgba(0,0,0,0.42); }
+ .post-active { border-color:rgba(224,196,127,0.36) !important;
+ background:linear-gradient(180deg, rgba(42,19,33,0.96), rgba(15,10,18,0.96)) !important;
+ box-shadow:0 0 0 1px rgba(224,196,127,0.12), 0 22px 44px rgba(0,0,0,0.46), inset 4px 0 0 var(--gold) !important; }
+ .post-header { display:flex; justify-content:space-between; margin-bottom:12px; font-size:0.88rem; }
+ .post-header strong { color:var(--gold); font-family:'Cinzel',serif; }
+ .post-time { color:rgba(240,232,217,0.58); }
+ .post-text { line-height:1.72; font-size:1rem; }
+ .post-text a { color:#cfe2ff; text-decoration:none; border-bottom:1px solid rgba(207,226,255,0.22); }
+ .post-text a:hover { border-color:rgba(207,226,255,0.72); }
+ .post-image { margin-top:10px; border:1px solid rgba(224,196,127,0.12); filter:saturate(0.9) contrast(1.06); }
+ .post-audio { width:100%; margin-top:10px; filter:sepia(0.12) contrast(0.92); }
+ .post-modal { display:none; position:fixed; inset:0; z-index:100; overflow-y:auto; padding:40px 20px; }
+ .post-modal.active { display:block; }
+ .modal-inner { max-width:800px; margin:0 auto; background:rgba(15,11,18,0.98); border:1px solid rgba(224,196,127,0.2); padding:38px; box-shadow:0 28px 84px rgba(0,0,0,0.72); }
+ .modal-close { float:right; background:none; border:none; color:var(--gold); font-family:'Cinzel',serif; font-size:0.8rem; cursor:pointer; letter-spacing:0.14em; }
+ @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 16px;} .content{padding:14px 16px;} .modal-inner{padding:24px 16px;} }
+ [data-sno-theme="cathedral"] .splash-overlay {
+ background:
+ radial-gradient(circle at 50% 18%, rgba(224,196,127,0.18) 0%, transparent 30%),
+ linear-gradient(180deg, #17111b 0%, #09080d 100%);
+ }
+ [data-sno-theme="cathedral"] .splash-rose { position:absolute; top:6vh; left:50%; width:min(36vw,240px); height:min(36vw,240px); transform:translateX(-50%); border-radius:50%;
+ background:
+ radial-gradient(circle at center, rgba(255,244,200,0.86) 0 6%, rgba(224,196,127,0.78) 6% 12%, rgba(111,79,174,0.84) 12% 20%, rgba(123,194,255,0.78) 20% 28%, rgba(142,47,73,0.8) 28% 38%, rgba(224,196,127,0.2) 38% 42%, transparent 42%),
+ conic-gradient(from 0deg, rgba(123,194,255,0.68), rgba(142,47,73,0.84), rgba(224,196,127,0.74), rgba(111,79,174,0.74), rgba(123,194,255,0.68));
+ box-shadow:0 0 72px rgba(224,196,127,0.22); opacity:0.78; z-index:1; animation:cathedralRoseSpin 24s linear infinite; }
+ @keyframes cathedralRoseSpin { to { transform:translateX(-50%) rotate(360deg); } }
+ [data-sno-theme="cathedral"] .splash-pipes { position:absolute; inset:auto 0 0 0; height:42vh; z-index:1;
+ background:
+ linear-gradient(90deg,
+ transparent 0 6%, rgba(12,11,18,0.96) 6% 10%, transparent 10% 14%, rgba(12,11,18,0.96) 14% 18%, transparent 18% 22%, rgba(12,11,18,0.96) 22% 26%,
+ transparent 26% 74%, rgba(12,11,18,0.96) 74% 78%, transparent 78% 82%, rgba(12,11,18,0.96) 82% 86%, transparent 86% 90%, rgba(12,11,18,0.96) 90% 94%, transparent 94%);
+ opacity:0.94; }
+ [data-sno-theme="cathedral"] .splash-incense { position:absolute; inset:0; z-index:1;
+ background:
+ radial-gradient(circle at 34% 72%, rgba(255,255,255,0.05) 0%, transparent 26%),
+ radial-gradient(circle at 68% 58%, rgba(255,255,255,0.04) 0%, transparent 26%);
+ animation:cathedralSmoke 8s ease-in-out infinite alternate; }
+ @keyframes cathedralSmoke { from { transform:translateY(0) scale(1); } to { transform:translateY(-2%) scale(1.05); } }
+ [data-sno-theme="cathedral"] .splash-title { font-family:'Cinzel',serif; font-size:clamp(1.7rem,5vw,2.5rem); color:#fff4d2; letter-spacing:0.08em; }
+ [data-sno-theme="cathedral"] .splash-tag { color:var(--gold); letter-spacing:0.26em; }
+ [data-sno-theme="cathedral"] .splash-hint { color:rgba(240,232,217,0.82); }
+ [data-sno-theme="cathedral"] .splash-inner { text-shadow:0 2px 28px rgba(0,0,0,0.94); }
diff --git a/internal/generator/templates/themes/cathedral.tmpl b/internal/generator/templates/themes/cathedral/theme.js
index fb519ce..6247200 100644
--- a/internal/generator/templates/themes/cathedral.tmpl
+++ b/internal/generator/templates/themes/cathedral/theme.js
@@ -1,110 +1,4 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>snonux.foo // CATHEDRAL</title>
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
- <link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@500;700&family=Spectral:wght@400;600&display=swap" rel="stylesheet">
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
- <style>
- :root { --gold:#e0c47f; --violet:#6f4fae; --ruby:#8e2f49; --glass:#7bc2ff; --stone:#110f16; --chalk:#f0e8d9; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:'Spectral',serif; background:#0f0d14; color:var(--chalk); overflow:hidden; height:100vh; }
- body::before { content:''; position:fixed; inset:0; z-index:998; pointer-events:none;
- background:
- radial-gradient(circle at 50% 4%, rgba(224,196,127,0.1) 0%, transparent 24%),
- linear-gradient(90deg, rgba(123,194,255,0.05), transparent 18%, transparent 82%, rgba(142,47,73,0.06));
- mix-blend-mode:screen; opacity:0.8; }
- #three-canvas { position:fixed; inset:0; width:100%; height:100%; z-index:1; }
- .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
- header { padding:16px 28px; background:rgba(11,10,16,0.84); backdrop-filter:blur(10px); border-bottom:1px solid rgba(224,196,127,0.18); display:flex; align-items:center; justify-content:space-between; }
- .logo { display:flex; align-items:center; gap:14px; }
- .logo-mark { font-family:'Cinzel',serif; font-size:1.9rem; color:var(--gold); text-shadow:0 0 14px rgba(224,196,127,0.22); }
- .logo-mark::after { content:'✢'; margin-left:8px; color:#fff3c8; text-shadow:0 0 12px rgba(224,196,127,0.6); }
- .logo-title h1 { font-family:'Cinzel',serif; font-size:1.5rem; letter-spacing:0.1em; color:var(--chalk); }
- .logo-title .subtitle { font-size:0.8rem; color:rgba(240,232,217,0.6); margin-top:2px; }
- .logo-title .subtitle a { color:var(--gold); text-decoration:none; }
- .logo-title .subtitle a:hover { color:#fff3c8; }
- .transmit-btn { border:1px solid rgba(224,196,127,0.28); color:var(--gold); padding:8px 16px; text-decoration:none; font-size:0.8rem; letter-spacing:0.2em; text-transform:uppercase; transition:all 0.18s; }
- .transmit-btn:hover { background:rgba(224,196,127,0.12); border-color:rgba(224,196,127,0.52); }
- a.header-feed-link { color:rgba(224,196,127,0.84); }
- a.header-feed-link:hover { color:#fff3c8; }
- .nav-hints { background:rgba(17,14,22,0.72); border-bottom:1px solid rgba(224,196,127,0.08); color:rgba(240,232,217,0.48); padding:5px 28px; display:flex; gap:18px; font-size:0.68rem; letter-spacing:0.08em; flex-wrap:wrap; }
- .nav-hints kbd { background:rgba(111,79,174,0.16); border:1px solid rgba(224,196,127,0.2); color:var(--gold); padding:0 5px; margin:0 2px; }
- .content { flex:1; overflow-y:auto; padding:20px 28px; scrollbar-width:thin; scrollbar-color:#7e6231 #18131d; }
- .page-nav { display:flex; justify-content:center; margin:14px 0; }
- .page-nav a { border:1px solid rgba(224,196,127,0.2); color:var(--gold); padding:8px 18px; text-decoration:none; font-size:0.8rem; letter-spacing:0.16em; text-transform:uppercase; }
- .page-nav a:hover { background:rgba(224,196,127,0.08); }
- .page-nav-footer { flex-shrink:0; padding:8px 28px; display:flex; justify-content:center; background:rgba(11,10,16,0.84); backdrop-filter:blur(10px); border-top:1px solid rgba(224,196,127,0.18); }
- .post { position:relative; background:
- linear-gradient(180deg, rgba(30,19,30,0.93), rgba(13,10,17,0.95)),
- radial-gradient(circle at 14% 0%, rgba(123,194,255,0.08), transparent 28%);
- border:1px solid rgba(224,196,127,0.08); padding:20px; margin-bottom:14px; cursor:pointer;
- box-shadow:0 16px 38px rgba(0,0,0,0.28); transition:border-color 0.2s, box-shadow 0.2s, transform 0.2s; }
- .post::before { content:''; position:absolute; inset:0; pointer-events:none; background:linear-gradient(120deg, rgba(123,194,255,0.05), transparent 36%, rgba(142,47,73,0.06) 68%, transparent); }
- .post:hover { border-color:rgba(224,196,127,0.24); transform:translateY(-1px); box-shadow:0 22px 42px rgba(0,0,0,0.42); }
- .post-active { border-color:rgba(224,196,127,0.36) !important;
- background:linear-gradient(180deg, rgba(42,19,33,0.96), rgba(15,10,18,0.96)) !important;
- box-shadow:0 0 0 1px rgba(224,196,127,0.12), 0 22px 44px rgba(0,0,0,0.46), inset 4px 0 0 var(--gold) !important; }
- .post-header { display:flex; justify-content:space-between; margin-bottom:12px; font-size:0.88rem; }
- .post-header strong { color:var(--gold); font-family:'Cinzel',serif; }
- .post-time { color:rgba(240,232,217,0.58); }
- .post-text { line-height:1.72; font-size:1rem; }
- .post-text a { color:#cfe2ff; text-decoration:none; border-bottom:1px solid rgba(207,226,255,0.22); }
- .post-text a:hover { border-color:rgba(207,226,255,0.72); }
- .post-image { margin-top:10px; border:1px solid rgba(224,196,127,0.12); filter:saturate(0.9) contrast(1.06); }
- .post-audio { width:100%; margin-top:10px; filter:sepia(0.12) contrast(0.92); }
- .post-modal { display:none; position:fixed; inset:0; z-index:100; overflow-y:auto; padding:40px 20px; }
- .post-modal.active { display:block; }
- .modal-inner { max-width:800px; margin:0 auto; background:rgba(15,11,18,0.98); border:1px solid rgba(224,196,127,0.2); padding:38px; box-shadow:0 28px 84px rgba(0,0,0,0.72); }
- .modal-close { float:right; background:none; border:none; color:var(--gold); font-family:'Cinzel',serif; font-size:0.8rem; cursor:pointer; letter-spacing:0.14em; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 16px;} .content{padding:14px 16px;} .modal-inner{padding:24px 16px;} }
- .splash-overlay.splash-cathedral {
- background:
- radial-gradient(circle at 50% 18%, rgba(224,196,127,0.18) 0%, transparent 30%),
- linear-gradient(180deg, #17111b 0%, #09080d 100%);
- }
- .splash-cathedral .splash-rose { position:absolute; top:6vh; left:50%; width:min(36vw,240px); height:min(36vw,240px); transform:translateX(-50%); border-radius:50%;
- background:
- radial-gradient(circle at center, rgba(255,244,200,0.86) 0 6%, rgba(224,196,127,0.78) 6% 12%, rgba(111,79,174,0.84) 12% 20%, rgba(123,194,255,0.78) 20% 28%, rgba(142,47,73,0.8) 28% 38%, rgba(224,196,127,0.2) 38% 42%, transparent 42%),
- conic-gradient(from 0deg, rgba(123,194,255,0.68), rgba(142,47,73,0.84), rgba(224,196,127,0.74), rgba(111,79,174,0.74), rgba(123,194,255,0.68));
- box-shadow:0 0 72px rgba(224,196,127,0.22); opacity:0.78; z-index:1; animation:cathedralRoseSpin 24s linear infinite; }
- @keyframes cathedralRoseSpin { to { transform:translateX(-50%) rotate(360deg); } }
- .splash-cathedral .splash-pipes { position:absolute; inset:auto 0 0 0; height:42vh; z-index:1;
- background:
- linear-gradient(90deg,
- transparent 0 6%, rgba(12,11,18,0.96) 6% 10%, transparent 10% 14%, rgba(12,11,18,0.96) 14% 18%, transparent 18% 22%, rgba(12,11,18,0.96) 22% 26%,
- transparent 26% 74%, rgba(12,11,18,0.96) 74% 78%, transparent 78% 82%, rgba(12,11,18,0.96) 82% 86%, transparent 86% 90%, rgba(12,11,18,0.96) 90% 94%, transparent 94%);
- opacity:0.94; }
- .splash-cathedral .splash-incense { position:absolute; inset:0; z-index:1;
- background:
- radial-gradient(circle at 34% 72%, rgba(255,255,255,0.05) 0%, transparent 26%),
- radial-gradient(circle at 68% 58%, rgba(255,255,255,0.04) 0%, transparent 26%);
- animation:cathedralSmoke 8s ease-in-out infinite alternate; }
- @keyframes cathedralSmoke { from { transform:translateY(0) scale(1); } to { transform:translateY(-2%) scale(1.05); } }
- .splash-cathedral .splash-title { font-family:'Cinzel',serif; font-size:clamp(1.7rem,5vw,2.5rem); color:#fff4d2; letter-spacing:0.08em; }
- .splash-cathedral .splash-tag { color:var(--gold); letter-spacing:0.26em; }
- .splash-cathedral .splash-hint { color:rgba(240,232,217,0.82); }
- .splash-cathedral .splash-inner { text-shadow:0 2px 28px rgba(0,0,0,0.94); }
-{{template "navSharedCSSInner"}}
- </style>
-</head>
-<body>
- {{template "splashGate"}}
- <div id="splash-overlay" class="splash-overlay splash-cathedral" role="dialog" aria-modal="true" aria-label="Open microblog" tabindex="-1">
- <canvas class="splash-gl-canvas" id="splash-gl-canvas" aria-hidden="true"></canvas>
- <div class="splash-rose" aria-hidden="true"></div>
- <div class="splash-pipes" aria-hidden="true"></div>
- <div class="splash-incense" aria-hidden="true"></div>
- <div class="splash-inner">
- <div class="splash-title">snonux.foo</div>
- <div class="splash-tag">Ritual Engine</div>
- <div class="splash-hint">Click or Enter to cross the nave</div>
- </div>
- </div>
- <script>
+
(function(){
if(document.documentElement.classList.contains('sno-splash-skip'))return;
var cv=document.getElementById('splash-gl-canvas');
@@ -129,46 +23,8 @@
pos.needsUpdate=true; rose.rotation.z=t*0.08; beam.material.opacity=0.08+Math.sin(t*1.5)*0.02; ren.render(sc,ca); }
raf=requestAnimationFrame(loop);
})();
- </script>
- <canvas id="three-canvas"></canvas>
- <div class="overlay">
- <header>
- <div class="logo">
- <span class="logo-mark">SN</span>
- <div class="logo-title">
- <h1>snonux.foo</h1>
- <p class="subtitle">microblog &mdash; <a href="https://foo.zone">foo.zone</a> is the real blog</p>
- <p class="logo-host">Served by NetBSD on a Raspberry Pi 3</p>
- </div>
- </div>
- <div class="nav">
- <a href="atom.xml" class="header-feed-link" rel="alternate" title="Atom feed" type="application/atom+xml">Atom feed</a>
- <a href="https://foo.zone/about" class="transmit-btn">Reliquary</a>
- </div>
- </header>
- {{template "navhints" .}}
- <div class="content" id="post-content">
- {{range $i, $post := .Posts}}
- <div class="post" id="post-{{$post.ID}}" data-index="{{$i}}">
- <div class="post-header">
- <div><strong>@snonux</strong></div>
- <div class="post-time">{{$post.FormattedTime}}</div>
- </div>
- <div class="post-text">{{$post.ContentHTML}}</div>
- </div>
- {{end}}
- </div>
- {{if or .PrevPage .NextPage}}
- <footer class="page-nav-footer" aria-label="Pagination">
- <div class="page-nav page-nav-dual">
- {{if .PrevPage}}<a href="{{.PrevPage}}">&larr; Newer</a>{{end}}
- {{if .NextPage}}<a href="{{.NextPage}}">Older &rarr;</a>{{end}}
- </div>
- </footer>
- {{end}}
- </div>
- {{template "navmodal" .}}
- <script>
+
+
(function() {
var _wild = false, _snoTOffset = 0, _snoLastT = 0;
var scene, camera, renderer, clock, dust, embers, beams = [], candles = [], rose, halo, chandelier, pipes = [];
@@ -297,7 +153,3 @@
if (b) b.classList.toggle('sno-wild-on', _wild);
};
})();
- </script>
- {{template "navscript" .}}
-</body>
-</html>
diff --git a/internal/generator/templates/themes/cosmos/meta.json b/internal/generator/templates/themes/cosmos/meta.json
new file mode 100644
index 0000000..7f7b880
--- /dev/null
+++ b/internal/generator/templates/themes/cosmos/meta.json
@@ -0,0 +1,7 @@
+{
+ "title": "snonux.foo ✧ COSMOS",
+ "header_html": "\u003cdiv class=\"logo\"\u003e\n \u003cspan class=\"logo-mark\"\u003eSN\u003c/span\u003e\n \u003cdiv class=\"logo-title\"\u003e\n \u003ch1\u003esnonux.foo\u003c/h1\u003e\n \u003cp class=\"subtitle\"\u003emicroblog \u0026mdash; \u003ca href=\"https://foo.zone\"\u003efoo.zone\u003c/a\u003e is the real blog\u003c/p\u003e\n \u003cp class=\"logo-host\"\u003eServed by NetBSD on a Raspberry Pi 3\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"nav\"\u003e\n \u003ca href=\"atom.xml\" class=\"header-feed-link\" rel=\"alternate\" title=\"Atom feed\" type=\"application/atom+xml\"\u003eAtom feed\u003c/a\u003e\n \u003ca href=\"https://foo.zone/about\" class=\"transmit-btn\"\u003eTransmit\u003c/a\u003e\n \u003c/div\u003e",
+ "splash_inner_html": "\u003ccanvas class=\"splash-gl-canvas\" id=\"splash-gl-canvas\" aria-hidden=\"true\"\u003e\u003c/canvas\u003e\n \u003cdiv class=\"splash-stars\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-inner\"\u003e\n \u003cdiv class=\"splash-orbit\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-title\"\u003esnonux.foo\u003c/div\u003e\n \u003cdiv class=\"splash-tag\"\u003eCosmos gate\u003c/div\u003e\n \u003cdiv class=\"splash-hint\"\u003eEngage — click or Enter\u003c/div\u003e\n \u003c/div\u003e",
+ "prev_page_text": "\u0026larr; Newer",
+ "next_page_text": "Older \u0026rarr;"
+}
diff --git a/internal/generator/templates/themes/cosmos/theme.css b/internal/generator/templates/themes/cosmos/theme.css
new file mode 100644
index 0000000..eff1267
--- /dev/null
+++ b/internal/generator/templates/themes/cosmos/theme.css
@@ -0,0 +1,81 @@
+ :root { --gold:#ffd166; --purple:#9b5de5; --blue:#4cc9f0; --bg:#020214; }
+ * { margin:0; padding:0; box-sizing:border-box; }
+ body { font-family:'Segoe UI',system-ui,sans-serif; background:var(--bg);
+ color:#d4e8ff; overflow:hidden; height:100vh; }
+ #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
+ .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
+ header { padding:16px 28px; background:rgba(2,2,20,0.78); backdrop-filter:blur(14px);
+ border-bottom:1px solid rgba(255,209,102,0.2); display:flex; align-items:center; justify-content:space-between; }
+ .logo { display:flex; align-items:center; gap:14px; }
+ .logo-mark { font-size:2rem; font-weight:800;
+ background:linear-gradient(90deg,var(--gold),var(--purple));
+ -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
+ .logo-title h1 { font-size:1.5rem; font-weight:700; color:#d4e8ff; }
+ .logo-title .subtitle { font-size:0.75rem; color:rgba(212,232,255,0.5); margin-top:2px; }
+ .logo-title .subtitle a { color:var(--gold); text-decoration:none; }
+ .logo-title .subtitle a:hover { text-shadow:0 0 8px var(--gold); }
+ .transmit-btn { border:1px solid var(--gold); color:var(--gold); padding:9px 20px;
+ border-radius:20px; text-decoration:none; font-size:0.85rem; transition:all 0.2s; }
+ .transmit-btn:hover { background:var(--gold); color:var(--bg); }
+ a.header-feed-link { color:var(--blue); }
+ a.header-feed-link:hover { color:var(--gold); }
+ .nav-hints { background:rgba(2,2,20,0.6); border-bottom:1px solid rgba(255,209,102,0.12);
+ color:rgba(212,232,255,0.4); padding:5px 28px; display:flex; gap:18px;
+ font-size:0.68rem; flex-wrap:wrap; }
+ .nav-hints kbd { background:rgba(255,209,102,0.1); border:1px solid rgba(255,209,102,0.3);
+ color:var(--gold); border-radius:3px; padding:0 5px; margin:0 2px; }
+ .content { flex:1; overflow-y:auto; padding:20px 28px;
+ scrollbar-width:thin; scrollbar-color:var(--purple) var(--bg); }
+ .page-nav { display:flex; justify-content:center; margin:14px 0; }
+ .page-nav a { border:1px solid var(--purple); color:var(--purple); padding:8px 20px;
+ border-radius:20px; text-decoration:none; font-size:0.82rem; }
+ .page-nav a:hover { background:var(--purple); color:#fff; }
+ .page-nav-footer { flex-shrink:0; padding:8px 28px; display:flex; justify-content:center;
+ background:rgba(2,2,20,0.78); backdrop-filter:blur(14px);
+ border-top:1px solid rgba(255,209,102,0.2); }
+ .post { background:rgba(5,5,30,0.72); border:1px solid rgba(155,93,229,0.22); border-radius:10px;
+ padding:20px; margin-bottom:14px; cursor:pointer;
+ transition:all 0.25s; backdrop-filter:blur(6px); }
+ .post:hover { border-color:var(--gold); box-shadow:0 0 22px rgba(255,209,102,0.18); transform:translateY(-2px); }
+ .post-active { border-color:var(--gold) !important; background:rgba(10,5,35,0.9) !important;
+ box-shadow:0 0 28px rgba(255,209,102,0.35),inset 3px 0 0 var(--gold) !important; }
+ .post-header { display:flex; justify-content:space-between; margin-bottom:12px; font-size:0.88rem; }
+ .post-time { color:var(--blue); font-family:monospace; font-size:0.8rem; }
+ .post-text { line-height:1.65; font-size:0.95rem; }
+ .post-text a { color:var(--blue); text-decoration:none; }
+ .post-text a:hover { text-shadow:0 0 8px var(--blue); }
+ .post-audio { width:100%; margin-top:10px; }
+ .post-modal { display:none; position:fixed; inset:0; z-index:100;
+ overflow-y:auto; padding:40px 20px; }
+ .post-modal.active { display:block; }
+ .modal-inner { max-width:760px; margin:0 auto; background:rgba(5,5,30,0.92);
+ border:1px solid var(--gold); border-radius:12px;
+ box-shadow:0 0 60px rgba(255,209,102,0.25); padding:40px; backdrop-filter:blur(16px); }
+ .modal-close { float:right; background:none; border:none; color:var(--gold);
+ font-size:0.9rem; cursor:pointer; letter-spacing:1px; }
+ @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 18px;} .content{padding:14px 18px;} }
+ [data-sno-theme="cosmos"] .splash-overlay { background: radial-gradient(ellipse 100% 80% at 50% 100%, rgba(155,93,229,0.2) 0%, transparent 55%), var(--bg); }
+ [data-sno-theme="cosmos"] .splash-stars {
+ position:absolute; inset:0; pointer-events:none; opacity:0.5;
+ background-image: radial-gradient(1px 1px at 20% 30%, rgba(255,255,255,0.9), transparent),
+ radial-gradient(1px 1px at 80% 20%, rgba(255,209,102,0.8), transparent),
+ radial-gradient(1px 1px at 40% 70%, rgba(76,201,240,0.7), transparent),
+ radial-gradient(1px 1px at 65% 55%, rgba(255,255,255,0.6), transparent);
+ background-size: 100% 100%;
+ animation: splashTwinkle 4s ease-in-out infinite alternate;
+ }
+ @keyframes splashTwinkle { from { opacity:0.35; } to { opacity:0.65; } }
+ [data-sno-theme="cosmos"] .splash-inner { position:relative; z-index:1; }
+ [data-sno-theme="cosmos"] .splash-orbit {
+ width:72px; height:72px; margin:0 auto 1rem; border:2px solid rgba(255,209,102,0.5);
+ border-radius:50%; animation: splashOrbitSpin 12s linear infinite;
+ box-shadow: 0 0 30px rgba(155,93,229,0.4);
+ }
+ @keyframes splashOrbitSpin { to { transform: rotate(360deg); } }
+ [data-sno-theme="cosmos"] .splash-title { font-size:clamp(1.45rem,4.5vw,2rem); color:#d4e8ff; }
+ [data-sno-theme="cosmos"] .splash-tag {
+ background:linear-gradient(90deg,var(--gold),var(--purple));
+ -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
+ [data-sno-theme="cosmos"] .splash-hint { color:rgba(212,232,255,0.88); }
+ [data-sno-theme="cosmos"] .splash-stars { z-index:1; }
+ [data-sno-theme="cosmos"] .splash-inner { text-shadow: 0 2px 20px rgba(0,0,0,0.85); }
diff --git a/internal/generator/templates/themes/cosmos.tmpl b/internal/generator/templates/themes/cosmos/theme.js
index 594796c..be51bd7 100644
--- a/internal/generator/templates/themes/cosmos.tmpl
+++ b/internal/generator/templates/themes/cosmos/theme.js
@@ -1,108 +1,4 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>snonux.foo ✧ COSMOS</title>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
- <style>
- :root { --gold:#ffd166; --purple:#9b5de5; --blue:#4cc9f0; --bg:#020214; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:'Segoe UI',system-ui,sans-serif; background:var(--bg);
- color:#d4e8ff; overflow:hidden; height:100vh; }
- #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
- .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
- header { padding:16px 28px; background:rgba(2,2,20,0.78); backdrop-filter:blur(14px);
- border-bottom:1px solid rgba(255,209,102,0.2); display:flex; align-items:center; justify-content:space-between; }
- .logo { display:flex; align-items:center; gap:14px; }
- .logo-mark { font-size:2rem; font-weight:800;
- background:linear-gradient(90deg,var(--gold),var(--purple));
- -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
- .logo-title h1 { font-size:1.5rem; font-weight:700; color:#d4e8ff; }
- .logo-title .subtitle { font-size:0.75rem; color:rgba(212,232,255,0.5); margin-top:2px; }
- .logo-title .subtitle a { color:var(--gold); text-decoration:none; }
- .logo-title .subtitle a:hover { text-shadow:0 0 8px var(--gold); }
- .transmit-btn { border:1px solid var(--gold); color:var(--gold); padding:9px 20px;
- border-radius:20px; text-decoration:none; font-size:0.85rem; transition:all 0.2s; }
- .transmit-btn:hover { background:var(--gold); color:var(--bg); }
- a.header-feed-link { color:var(--blue); }
- a.header-feed-link:hover { color:var(--gold); }
- .nav-hints { background:rgba(2,2,20,0.6); border-bottom:1px solid rgba(255,209,102,0.12);
- color:rgba(212,232,255,0.4); padding:5px 28px; display:flex; gap:18px;
- font-size:0.68rem; flex-wrap:wrap; }
- .nav-hints kbd { background:rgba(255,209,102,0.1); border:1px solid rgba(255,209,102,0.3);
- color:var(--gold); border-radius:3px; padding:0 5px; margin:0 2px; }
- .content { flex:1; overflow-y:auto; padding:20px 28px;
- scrollbar-width:thin; scrollbar-color:var(--purple) var(--bg); }
- .page-nav { display:flex; justify-content:center; margin:14px 0; }
- .page-nav a { border:1px solid var(--purple); color:var(--purple); padding:8px 20px;
- border-radius:20px; text-decoration:none; font-size:0.82rem; }
- .page-nav a:hover { background:var(--purple); color:#fff; }
- .page-nav-footer { flex-shrink:0; padding:8px 28px; display:flex; justify-content:center;
- background:rgba(2,2,20,0.78); backdrop-filter:blur(14px);
- border-top:1px solid rgba(255,209,102,0.2); }
- .post { background:rgba(5,5,30,0.72); border:1px solid rgba(155,93,229,0.22); border-radius:10px;
- padding:20px; margin-bottom:14px; cursor:pointer;
- transition:all 0.25s; backdrop-filter:blur(6px); }
- .post:hover { border-color:var(--gold); box-shadow:0 0 22px rgba(255,209,102,0.18); transform:translateY(-2px); }
- .post-active { border-color:var(--gold) !important; background:rgba(10,5,35,0.9) !important;
- box-shadow:0 0 28px rgba(255,209,102,0.35),inset 3px 0 0 var(--gold) !important; }
- .post-header { display:flex; justify-content:space-between; margin-bottom:12px; font-size:0.88rem; }
- .post-time { color:var(--blue); font-family:monospace; font-size:0.8rem; }
- .post-text { line-height:1.65; font-size:0.95rem; }
- .post-text a { color:var(--blue); text-decoration:none; }
- .post-text a:hover { text-shadow:0 0 8px var(--blue); }
- .post-audio { width:100%; margin-top:10px; }
- .post-modal { display:none; position:fixed; inset:0; z-index:100;
- overflow-y:auto; padding:40px 20px; }
- .post-modal.active { display:block; }
- .modal-inner { max-width:760px; margin:0 auto; background:rgba(5,5,30,0.92);
- border:1px solid var(--gold); border-radius:12px;
- box-shadow:0 0 60px rgba(255,209,102,0.25); padding:40px; backdrop-filter:blur(16px); }
- .modal-close { float:right; background:none; border:none; color:var(--gold);
- font-size:0.9rem; cursor:pointer; letter-spacing:1px; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 18px;} .content{padding:14px 18px;} }
- .splash-overlay.splash-cosmos { background: radial-gradient(ellipse 100% 80% at 50% 100%, rgba(155,93,229,0.2) 0%, transparent 55%), var(--bg); }
- .splash-cosmos .splash-stars {
- position:absolute; inset:0; pointer-events:none; opacity:0.5;
- background-image: radial-gradient(1px 1px at 20% 30%, rgba(255,255,255,0.9), transparent),
- radial-gradient(1px 1px at 80% 20%, rgba(255,209,102,0.8), transparent),
- radial-gradient(1px 1px at 40% 70%, rgba(76,201,240,0.7), transparent),
- radial-gradient(1px 1px at 65% 55%, rgba(255,255,255,0.6), transparent);
- background-size: 100% 100%;
- animation: splashTwinkle 4s ease-in-out infinite alternate;
- }
- @keyframes splashTwinkle { from { opacity:0.35; } to { opacity:0.65; } }
- .splash-cosmos .splash-inner { position:relative; z-index:1; }
- .splash-cosmos .splash-orbit {
- width:72px; height:72px; margin:0 auto 1rem; border:2px solid rgba(255,209,102,0.5);
- border-radius:50%; animation: splashOrbitSpin 12s linear infinite;
- box-shadow: 0 0 30px rgba(155,93,229,0.4);
- }
- @keyframes splashOrbitSpin { to { transform: rotate(360deg); } }
- .splash-cosmos .splash-title { font-size:clamp(1.45rem,4.5vw,2rem); color:#d4e8ff; }
- .splash-cosmos .splash-tag {
- background:linear-gradient(90deg,var(--gold),var(--purple));
- -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
- .splash-cosmos .splash-hint { color:rgba(212,232,255,0.88); }
- .splash-cosmos .splash-stars { z-index:1; }
- .splash-cosmos .splash-inner { text-shadow: 0 2px 20px rgba(0,0,0,0.85); }
-{{template "navSharedCSSInner"}}
- </style>
-</head>
-<body>
- {{template "splashGate"}}
- <div id="splash-overlay" class="splash-overlay splash-cosmos" role="dialog" aria-modal="true" aria-label="Open microblog" tabindex="-1">
- <canvas class="splash-gl-canvas" id="splash-gl-canvas" aria-hidden="true"></canvas>
- <div class="splash-stars" aria-hidden="true"></div>
- <div class="splash-inner">
- <div class="splash-orbit" aria-hidden="true"></div>
- <div class="splash-title">snonux.foo</div>
- <div class="splash-tag">Cosmos gate</div>
- <div class="splash-hint">Engage — click or Enter</div>
- </div>
- </div>
- <script>
+
(function(){
if(document.documentElement.classList.contains('sno-splash-skip'))return;
var cv=document.getElementById('splash-gl-canvas');
@@ -121,46 +17,8 @@
moon.position.x=2.6*Math.cos(t*0.7);moon.position.z=2.6*Math.sin(t*0.7);ren.render(sc,ca);}
raf=requestAnimationFrame(loop);
})();
- </script>
- <canvas id="three-canvas"></canvas>
- <div class="overlay">
- <header>
- <div class="logo">
- <span class="logo-mark">SN</span>
- <div class="logo-title">
- <h1>snonux.foo</h1>
- <p class="subtitle">microblog &mdash; <a href="https://foo.zone">foo.zone</a> is the real blog</p>
- <p class="logo-host">Served by NetBSD on a Raspberry Pi 3</p>
- </div>
- </div>
- <div class="nav">
- <a href="atom.xml" class="header-feed-link" rel="alternate" title="Atom feed" type="application/atom+xml">Atom feed</a>
- <a href="https://foo.zone/about" class="transmit-btn">Transmit</a>
- </div>
- </header>
- {{template "navhints" .}}
- <div class="content" id="post-content">
- {{range $i, $post := .Posts}}
- <div class="post" id="post-{{$post.ID}}" data-index="{{$i}}">
- <div class="post-header">
- <div><strong>@snonux</strong></div>
- <div class="post-time">{{$post.FormattedTime}}</div>
- </div>
- <div class="post-text">{{$post.ContentHTML}}</div>
- </div>
- {{end}}
- </div>
- {{if or .PrevPage .NextPage}}
- <footer class="page-nav-footer" aria-label="Pagination">
- <div class="page-nav page-nav-dual">
- {{if .PrevPage}}<a href="{{.PrevPage}}">&larr; Newer</a>{{end}}
- {{if .NextPage}}<a href="{{.NextPage}}">Older &rarr;</a>{{end}}
- </div>
- </footer>
- {{end}}
- </div>
- {{template "navmodal" .}}
- <script>
+
+
// Cosmos WebGL: ringed planet, swirling nebula blobs, asteroid belt, and stars.
// The planet sits at lower-right and slowly rotates; asteroids orbit it;
// nebula clouds drift with additive blending for a deep-space glow.
@@ -379,7 +237,3 @@
setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 230); }, 20);
};
})();
- </script>
- {{template "navscript" .}}
-</body>
-</html>
diff --git a/internal/generator/templates/themes/dos.tmpl b/internal/generator/templates/themes/dos.tmpl
deleted file mode 100644
index 622978c..0000000
--- a/internal/generator/templates/themes/dos.tmpl
+++ /dev/null
@@ -1,302 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>SNONUX.FOO - DOS</title>
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
- <link href="https://fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet">
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
- <style>
- :root { --dos-blue:#0000aa; --dos-lblue:#5555ff; --dos-white:#aaaaaa;
- --dos-bwhite:#ffffff; --dos-yellow:#ffff55; --dos-cyan:#55ffff;
- --dos-red:#ff5555; --dos-bg:#000088; --dos-black:#000000; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:'VT323','Courier New',monospace; background:var(--dos-blue);
- color:var(--dos-bwhite); overflow:hidden; height:100vh; font-size:18px; }
- #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
- .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
- header { padding:8px 20px; background:var(--dos-white); color:var(--dos-blue);
- display:flex; align-items:center; justify-content:space-between; }
- .logo { display:flex; align-items:center; gap:12px; }
- .logo-mark { font-size:1.8rem; color:var(--dos-blue); font-weight:bold; }
- .logo-title h1 { font-size:1.4rem; color:var(--dos-blue); font-weight:normal; letter-spacing:2px; }
- .logo-title .subtitle { font-size:0.85rem; color:var(--dos-blue); margin-top:1px; }
- .logo-title .subtitle a { color:var(--dos-blue); text-decoration:underline; }
- .logo-title .subtitle a:hover { color:var(--dos-black); }
- .transmit-btn { border:2px solid var(--dos-blue); color:var(--dos-blue); padding:4px 14px;
- text-decoration:none; font-size:1rem; letter-spacing:1px;
- transition:all 0.1s; }
- .transmit-btn:hover { background:var(--dos-blue); color:var(--dos-bwhite); }
- a.header-feed-link { color:var(--dos-blue); }
- a.header-feed-link:hover { color:var(--dos-black); }
- .nav-hints { background:var(--dos-blue); border-bottom:2px solid var(--dos-lblue);
- color:var(--dos-cyan); padding:4px 20px; display:flex; gap:16px;
- font-size:0.85rem; flex-wrap:wrap; }
- .nav-hints kbd { background:var(--dos-black); border:1px solid var(--dos-lblue);
- color:var(--dos-yellow); padding:0 5px; margin:0 2px; }
- .content { flex:1; overflow-y:auto; padding:12px 20px;
- scrollbar-width:thin; scrollbar-color:var(--dos-lblue) var(--dos-blue); }
- .page-nav { display:flex; justify-content:center; margin:10px 0; }
- .page-nav a { border:2px solid var(--dos-lblue); color:var(--dos-yellow); padding:6px 18px;
- text-decoration:none; font-size:1rem; letter-spacing:1px; }
- .page-nav a:hover { background:var(--dos-lblue); color:var(--dos-bwhite); }
- .page-nav-footer { flex-shrink:0; padding:6px 20px; display:flex; justify-content:center;
- background:var(--dos-white); color:var(--dos-blue); }
- .page-nav-footer .page-nav a { border-color:var(--dos-blue); color:var(--dos-blue); }
- .page-nav-footer .page-nav a:hover { background:var(--dos-blue); color:var(--dos-bwhite); }
- .post { background:var(--dos-black); border:2px solid var(--dos-lblue);
- padding:12px 14px; margin-bottom:8px; cursor:pointer;
- transition:border-color 0.1s; }
- .post:hover { border-color:var(--dos-yellow);
- box-shadow:0 0 0 1px var(--dos-yellow); }
- .post-active { border-color:var(--dos-yellow) !important;
- background:rgba(0,0,170,0.3) !important;
- box-shadow:0 0 0 2px var(--dos-yellow),inset 3px 0 0 var(--dos-yellow) !important; }
- .post-header { display:flex; justify-content:space-between; margin-bottom:8px; font-size:1rem; }
- .post-header strong { color:var(--dos-yellow); }
- .post-time { color:var(--dos-cyan); font-size:0.95rem; }
- .post-text { line-height:1.5; font-size:1.05rem; }
- .post-text a { color:var(--dos-cyan); text-decoration:underline; }
- .post-text a:hover { color:var(--dos-yellow); }
- .post-image { max-width:100%; margin-top:8px; border:2px solid var(--dos-lblue); }
- .post-audio { width:100%; margin-top:8px; }
- .post-modal { display:none; position:fixed; inset:0; z-index:100;
- background:rgba(0,0,0,0.95); overflow-y:auto; padding:40px 20px; }
- .post-modal.active { display:block; }
- .modal-inner { max-width:740px; margin:0 auto; background:var(--dos-black);
- border:2px solid var(--dos-yellow); padding:24px;
- box-shadow:0 0 20px rgba(85,85,255,0.4); }
- .modal-close { float:right; background:var(--dos-white); border:2px outset var(--dos-bwhite);
- color:var(--dos-blue); font-family:'VT323','Courier New',monospace;
- font-size:1rem; cursor:pointer; padding:2px 8px; }
- .modal-close:hover { background:var(--dos-blue); color:var(--dos-bwhite);
- border-style:inset; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:6px 12px;} .content{padding:8px 12px;} }
- .splash-overlay.splash-dos { background:var(--dos-blue); font-family:'VT323','Courier New',monospace; }
- .splash-dos .splash-inner { position:relative; z-index:1; }
- .splash-dos .splash-title {
- font-size:clamp(1.4rem,4.5vw,2rem); color:var(--dos-bwhite);
- letter-spacing:0.15em;
- animation: splashDosBlink 1s step-end infinite;
- }
- @keyframes splashDosBlink { 0%,100%{border-right:0.6em solid var(--dos-bwhite)} 50%{border-right:0.6em solid transparent} }
- .splash-dos .splash-tag { color:var(--dos-yellow); letter-spacing:0.15em; }
- .splash-dos .splash-hint { color:var(--dos-cyan); }
- .splash-dos .splash-inner {
- background:var(--dos-black); border:2px solid var(--dos-lblue);
- text-shadow:none; box-shadow:4px 4px 0 rgba(0,0,0,0.5);
- }
-{{template "navSharedCSSInner"}}
- </style>
-</head>
-<body>
- {{template "splashGate"}}
- <div id="splash-overlay" class="splash-overlay splash-dos" role="dialog" aria-modal="true" aria-label="Open microblog" tabindex="-1">
- <canvas class="splash-gl-canvas" id="splash-gl-canvas" aria-hidden="true"></canvas>
- <div class="splash-inner">
- <div class="splash-title">C:\SNONUX&gt;</div>
- <div class="splash-tag">MS-DOS v6.22</div>
- <div class="splash-hint">Press any key to continue...</div>
- </div>
- </div>
- <script>
- (function(){
- if(document.documentElement.classList.contains('sno-splash-skip'))return;
- var cv=document.getElementById('splash-gl-canvas');
- if(!cv||typeof THREE==='undefined')return;
- var raf,ren,sc,ca,drops=[],t0=performance.now();
- function cleanup(){window.removeEventListener('resize',sz);if(raf)cancelAnimationFrame(raf);raf=null;if(ren)ren.dispose();ren=null;window._snonuxSplashWebGLCleanup=null;}
- window._snonuxSplashWebGLCleanup=cleanup;
- function sz(){var w=cv.clientWidth||2,h=cv.clientHeight||2;if(ren)ren.setSize(w,h,false);if(ca){ca.aspect=w/h;ca.updateProjectionMatrix();}}
- ren=new THREE.WebGLRenderer({canvas:cv,antialias:false,alpha:true});ren.setClearColor(0,0);ren.setPixelRatio(1);
- sc=new THREE.Scene();ca=new THREE.PerspectiveCamera(50,1,0.1,80);ca.position.z=20;
- var geo=new THREE.PlaneGeometry(0.22,0.32);
- for(var i=0;i<60;i++){
- var mat=new THREE.MeshBasicMaterial({color:0x55ff55,transparent:true,opacity:0.3+Math.random()*0.4});
- var m=new THREE.Mesh(geo,mat);
- m.position.set((Math.random()-0.5)*28, Math.random()*22-11, (Math.random()-0.5)*5);
- m.userData.speed=0.5+Math.random()*1.5;
- sc.add(m); drops.push(m);
- }
- sz();window.addEventListener('resize',sz);
- function loop(now){raf=requestAnimationFrame(loop);
- for(var i=0;i<drops.length;i++){
- drops[i].position.y-=drops[i].userData.speed*0.06;
- if(drops[i].position.y<-12) drops[i].position.y=12;
- }
- ren.render(sc,ca);}
- raf=requestAnimationFrame(loop);
- })();
- </script>
- <canvas id="three-canvas"></canvas>
- <div class="overlay">
- <header>
- <div class="logo">
- <span class="logo-mark">C:\&gt;</span>
- <div class="logo-title">
- <h1>SNONUX.FOO</h1>
- <p class="subtitle">MICROBLOG &mdash; <a href="https://foo.zone">FOO.ZONE</a> IS THE REAL BLOG</p>
- <p class="logo-host">Served by NetBSD on a Raspberry Pi 3</p>
- </div>
- </div>
- <div class="nav">
- <a href="atom.xml" class="header-feed-link" rel="alternate" title="Atom feed" type="application/atom+xml">Atom feed</a>
- <a href="https://foo.zone/about" class="transmit-btn">ABOUT</a>
- </div>
- </header>
- {{template "navhints" .}}
- <div class="content" id="post-content">
- {{range $i, $post := .Posts}}
- <div class="post" id="post-{{$post.ID}}" data-index="{{$i}}">
- <div class="post-header">
- <div><strong>@SNONUX</strong></div>
- <div class="post-time">{{$post.FormattedTime}}</div>
- </div>
- <div class="post-text">{{$post.ContentHTML}}</div>
- </div>
- {{end}}
- </div>
- {{if or .PrevPage .NextPage}}
- <footer class="page-nav-footer" aria-label="Pagination">
- <div class="page-nav page-nav-dual">
- {{if .PrevPage}}<a href="{{.PrevPage}}">&lt;-- NEWER</a>{{end}}
- {{if .NextPage}}<a href="{{.NextPage}}">OLDER --&gt;</a>{{end}}
- </div>
- </footer>
- {{end}}
- </div>
- {{template "navmodal" .}}
- <script>
- (function() {
- var _wild = false, _snoTOffset = 0, _snoLastT = 0;
- var scene, camera, renderer, clock;
- var columns = [];
-
- function initThree() {
- scene = new THREE.Scene();
- scene.background = new THREE.Color(0x000088);
-
- camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 200);
- camera.position.set(0, 0, 40);
-
- renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('three-canvas'), antialias: false });
- renderer.setSize(window.innerWidth, window.innerHeight);
- renderer.setPixelRatio(1);
- clock = new THREE.Clock();
-
- var geo = new THREE.PlaneGeometry(0.35, 0.5);
-
- for (var c = 0; c < 30; c++) {
- var col = [];
- var x = (c - 15) * 2.2;
- var speed = 1.5 + Math.random() * 3;
- var startY = Math.random() * 60 - 30;
- for (var r = 0; r < 8; r++) {
- var brightness = 1.0 - (r / 8) * 0.7;
- var color = new THREE.Color(brightness * 0.33, brightness, brightness * 0.33);
- var mat = new THREE.MeshBasicMaterial({ color: color, transparent: true, opacity: brightness * 0.5 });
- var mesh = new THREE.Mesh(geo, mat);
- mesh.position.set(x, startY - r * 0.7, 0);
- scene.add(mesh);
- col.push({ mesh: mesh, offset: r * 0.7 });
- }
- columns.push({ chars: col, x: x, speed: speed, y: startY });
- }
-
- window.addEventListener('resize', onResize);
- animate();
- }
-
- function onResize() {
- camera.aspect = window.innerWidth / window.innerHeight;
- camera.updateProjectionMatrix();
- renderer.setSize(window.innerWidth, window.innerHeight);
- }
-
- function animate() {
- requestAnimationFrame(animate);
- var realT = clock.getElapsedTime();
- _snoTOffset += (realT - _snoLastT) * (_wild ? 9 : 0);
- _snoLastT = realT;
- var t = realT + _snoTOffset;
- for (var c = 0; c < columns.length; c++) {
- var col = columns[c];
- var y = col.y - t * col.speed;
- y = ((y % 60) + 60) % 60 - 30;
- for (var r = 0; r < col.chars.length; r++) {
- col.chars[r].mesh.position.y = y - col.chars[r].offset;
- // Wild: each char jitters horizontally like corrupted RAM
- if (_wild) { col.chars[r].mesh.position.x = col.x + (Math.random() - 0.5) * 2.5; }
- else { col.chars[r].mesh.position.x = col.x; }
- }
- }
- // Wild: camera lunges forward/back and sways like a CRT meltdown
- if (_wild) {
- camera.position.z = 40 + Math.sin(realT * 0.41) * 14;
- camera.position.x = Math.sin(realT * 0.37) * 8;
- camera.fov = 60 + Math.sin(realT * 0.53) * 16;
- camera.updateProjectionMatrix();
- } else {
- camera.position.z = 40;
- camera.position.x = 0;
- if (camera.fov !== 60) { camera.fov = 60; camera.updateProjectionMatrix(); }
- }
- renderer.render(scene, camera);
- }
-
- initThree();
-
- // DOS nav/wild effects — CRT glitch on navigate, system crash rain on wild
- window.snonuxOpenEffect = function() {
- // Slide in like a dialog box appearing on DOS screen
- var modal = document.getElementById('post-modal');
- if (modal) { modal.classList.add('sno-modal-slide'); setTimeout(function() { modal.classList.remove('sno-modal-slide'); }, 360); }
- // CRT scan flash from top
- var d = document.createElement('div');
- d.style.cssText = 'position:fixed;top:0;left:0;right:0;height:4px;z-index:997;pointer-events:none;background:rgba(85,255,255,0.7);box-shadow:0 0 8px rgba(85,255,255,0.5);transition:top 0.28s linear,opacity 0.1s 0.28s';
- document.body.appendChild(d);
- setTimeout(function() { d.style.top='100vh'; setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 120); }, 280); }, 15);
- };
- window.snonuxCloseEffect = function() {
- var ov = document.querySelector('.overlay');
- if (ov) { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); }, 280); }
- };
- window.snonuxScrollEffect = function(dir) {
- var isDown = dir === 'down';
- var thick = _wild ? '14px' : '5px';
- var d = document.createElement('div');
- // DOS/CRT: grey-to-white scanline
- d.style.cssText = 'position:fixed;left:0;right:0;height:' + thick + ';z-index:9000;pointer-events:none;' +
- 'background:linear-gradient(90deg,transparent,rgba(180,180,180,0.9),rgba(255,255,255,0.9),rgba(180,180,180,0.9),transparent);' +
- (isDown ? 'top:0;' : 'bottom:0;') +
- 'transition:transform 0.28s ease,opacity 0.28s ease;';
- document.body.appendChild(d);
- setTimeout(function() { d.style.transform = isDown ? 'translateY(100vh)' : 'translateY(-100vh)'; d.style.opacity='0'; }, 16);
- setTimeout(function() { d.remove(); }, 360);
- };
- window.snonuxWildToggle = function() {
- _wild = !_wild;
- var b = document.getElementById('sno-wild-badge');
- if (b) b.classList.toggle('sno-wild-on', _wild);
- };
- window.snonuxNavEffect = function() {
- // CRT horizontal glitch
- var ov = document.querySelector('.overlay');
- if (ov) { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); }, 300); }
- var d = document.createElement('div');
- d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(85,255,85,0.12);transition:opacity 0.15s';
- document.body.appendChild(d);
- setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 180); }, 25);
- };
- window.snonuxPageEffect = function() {
- // System crash — scanline strobe
- var ov = document.querySelector('.overlay');
- if (ov) { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); setTimeout(function() { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); }, 280); }, 40); }, 310); }
- };
- })();
- </script>
- {{template "navscript" .}}
-</body>
-</html>
diff --git a/internal/generator/templates/themes/dos/meta.json b/internal/generator/templates/themes/dos/meta.json
new file mode 100644
index 0000000..fd59dd3
--- /dev/null
+++ b/internal/generator/templates/themes/dos/meta.json
@@ -0,0 +1,7 @@
+{
+ "title": "SNONUX.FOO - DOS",
+ "header_html": "\u003cdiv class=\"logo\"\u003e\n \u003cspan class=\"logo-mark\"\u003eC:\\\u0026gt;\u003c/span\u003e\n \u003cdiv class=\"logo-title\"\u003e\n \u003ch1\u003eSNONUX.FOO\u003c/h1\u003e\n \u003cp class=\"subtitle\"\u003eMICROBLOG \u0026mdash; \u003ca href=\"https://foo.zone\"\u003eFOO.ZONE\u003c/a\u003e IS THE REAL BLOG\u003c/p\u003e\n \u003cp class=\"logo-host\"\u003eServed by NetBSD on a Raspberry Pi 3\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"nav\"\u003e\n \u003ca href=\"atom.xml\" class=\"header-feed-link\" rel=\"alternate\" title=\"Atom feed\" type=\"application/atom+xml\"\u003eAtom feed\u003c/a\u003e\n \u003ca href=\"https://foo.zone/about\" class=\"transmit-btn\"\u003eABOUT\u003c/a\u003e\n \u003c/div\u003e",
+ "splash_inner_html": "\u003ccanvas class=\"splash-gl-canvas\" id=\"splash-gl-canvas\" aria-hidden=\"true\"\u003e\u003c/canvas\u003e\n \u003cdiv class=\"splash-inner\"\u003e\n \u003cdiv class=\"splash-title\"\u003eC:\\SNONUX\u0026gt;\u003c/div\u003e\n \u003cdiv class=\"splash-tag\"\u003eMS-DOS v6.22\u003c/div\u003e\n \u003cdiv class=\"splash-hint\"\u003ePress any key to continue...\u003c/div\u003e\n \u003c/div\u003e",
+ "prev_page_text": "\u0026lt;-- NEWER",
+ "next_page_text": "OLDER --\u0026gt;"
+}
diff --git a/internal/generator/templates/themes/dos/theme.css b/internal/generator/templates/themes/dos/theme.css
new file mode 100644
index 0000000..ee06438
--- /dev/null
+++ b/internal/generator/templates/themes/dos/theme.css
@@ -0,0 +1,79 @@
+ :root { --dos-blue:#0000aa; --dos-lblue:#5555ff; --dos-white:#aaaaaa;
+ --dos-bwhite:#ffffff; --dos-yellow:#ffff55; --dos-cyan:#55ffff;
+ --dos-red:#ff5555; --dos-bg:#000088; --dos-black:#000000; }
+ * { margin:0; padding:0; box-sizing:border-box; }
+ body { font-family:'VT323','Courier New',monospace; background:var(--dos-blue);
+ color:var(--dos-bwhite); overflow:hidden; height:100vh; font-size:18px; }
+ #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
+ .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
+ header { padding:8px 20px; background:var(--dos-white); color:var(--dos-blue);
+ display:flex; align-items:center; justify-content:space-between; }
+ .logo { display:flex; align-items:center; gap:12px; }
+ .logo-mark { font-size:1.8rem; color:var(--dos-blue); font-weight:bold; }
+ .logo-title h1 { font-size:1.4rem; color:var(--dos-blue); font-weight:normal; letter-spacing:2px; }
+ .logo-title .subtitle { font-size:0.85rem; color:var(--dos-blue); margin-top:1px; }
+ .logo-title .subtitle a { color:var(--dos-blue); text-decoration:underline; }
+ .logo-title .subtitle a:hover { color:var(--dos-black); }
+ .transmit-btn { border:2px solid var(--dos-blue); color:var(--dos-blue); padding:4px 14px;
+ text-decoration:none; font-size:1rem; letter-spacing:1px;
+ transition:all 0.1s; }
+ .transmit-btn:hover { background:var(--dos-blue); color:var(--dos-bwhite); }
+ a.header-feed-link { color:var(--dos-blue); }
+ a.header-feed-link:hover { color:var(--dos-black); }
+ .nav-hints { background:var(--dos-blue); border-bottom:2px solid var(--dos-lblue);
+ color:var(--dos-cyan); padding:4px 20px; display:flex; gap:16px;
+ font-size:0.85rem; flex-wrap:wrap; }
+ .nav-hints kbd { background:var(--dos-black); border:1px solid var(--dos-lblue);
+ color:var(--dos-yellow); padding:0 5px; margin:0 2px; }
+ .content { flex:1; overflow-y:auto; padding:12px 20px;
+ scrollbar-width:thin; scrollbar-color:var(--dos-lblue) var(--dos-blue); }
+ .page-nav { display:flex; justify-content:center; margin:10px 0; }
+ .page-nav a { border:2px solid var(--dos-lblue); color:var(--dos-yellow); padding:6px 18px;
+ text-decoration:none; font-size:1rem; letter-spacing:1px; }
+ .page-nav a:hover { background:var(--dos-lblue); color:var(--dos-bwhite); }
+ .page-nav-footer { flex-shrink:0; padding:6px 20px; display:flex; justify-content:center;
+ background:var(--dos-white); color:var(--dos-blue); }
+ .page-nav-footer .page-nav a { border-color:var(--dos-blue); color:var(--dos-blue); }
+ .page-nav-footer .page-nav a:hover { background:var(--dos-blue); color:var(--dos-bwhite); }
+ .post { background:var(--dos-black); border:2px solid var(--dos-lblue);
+ padding:12px 14px; margin-bottom:8px; cursor:pointer;
+ transition:border-color 0.1s; }
+ .post:hover { border-color:var(--dos-yellow);
+ box-shadow:0 0 0 1px var(--dos-yellow); }
+ .post-active { border-color:var(--dos-yellow) !important;
+ background:rgba(0,0,170,0.3) !important;
+ box-shadow:0 0 0 2px var(--dos-yellow),inset 3px 0 0 var(--dos-yellow) !important; }
+ .post-header { display:flex; justify-content:space-between; margin-bottom:8px; font-size:1rem; }
+ .post-header strong { color:var(--dos-yellow); }
+ .post-time { color:var(--dos-cyan); font-size:0.95rem; }
+ .post-text { line-height:1.5; font-size:1.05rem; }
+ .post-text a { color:var(--dos-cyan); text-decoration:underline; }
+ .post-text a:hover { color:var(--dos-yellow); }
+ .post-image { max-width:100%; margin-top:8px; border:2px solid var(--dos-lblue); }
+ .post-audio { width:100%; margin-top:8px; }
+ .post-modal { display:none; position:fixed; inset:0; z-index:100;
+ background:rgba(0,0,0,0.95); overflow-y:auto; padding:40px 20px; }
+ .post-modal.active { display:block; }
+ .modal-inner { max-width:740px; margin:0 auto; background:var(--dos-black);
+ border:2px solid var(--dos-yellow); padding:24px;
+ box-shadow:0 0 20px rgba(85,85,255,0.4); }
+ .modal-close { float:right; background:var(--dos-white); border:2px outset var(--dos-bwhite);
+ color:var(--dos-blue); font-family:'VT323','Courier New',monospace;
+ font-size:1rem; cursor:pointer; padding:2px 8px; }
+ .modal-close:hover { background:var(--dos-blue); color:var(--dos-bwhite);
+ border-style:inset; }
+ @media(max-width:640px) { .nav-hints{display:none;} header{padding:6px 12px;} .content{padding:8px 12px;} }
+ [data-sno-theme="dos"] .splash-overlay { background:var(--dos-blue); font-family:'VT323','Courier New',monospace; }
+ [data-sno-theme="dos"] .splash-inner { position:relative; z-index:1; }
+ [data-sno-theme="dos"] .splash-title {
+ font-size:clamp(1.4rem,4.5vw,2rem); color:var(--dos-bwhite);
+ letter-spacing:0.15em;
+ animation: splashDosBlink 1s step-end infinite;
+ }
+ @keyframes splashDosBlink { 0%,100%{border-right:0.6em solid var(--dos-bwhite)} 50%{border-right:0.6em solid transparent} }
+ [data-sno-theme="dos"] .splash-tag { color:var(--dos-yellow); letter-spacing:0.15em; }
+ [data-sno-theme="dos"] .splash-hint { color:var(--dos-cyan); }
+ [data-sno-theme="dos"] .splash-inner {
+ background:var(--dos-black); border:2px solid var(--dos-lblue);
+ text-shadow:none; box-shadow:4px 4px 0 rgba(0,0,0,0.5);
+ }
diff --git a/internal/generator/templates/themes/dos/theme.js b/internal/generator/templates/themes/dos/theme.js
new file mode 100644
index 0000000..1d913ac
--- /dev/null
+++ b/internal/generator/templates/themes/dos/theme.js
@@ -0,0 +1,157 @@
+
+ (function(){
+ if(document.documentElement.classList.contains('sno-splash-skip'))return;
+ var cv=document.getElementById('splash-gl-canvas');
+ if(!cv||typeof THREE==='undefined')return;
+ var raf,ren,sc,ca,drops=[],t0=performance.now();
+ function cleanup(){window.removeEventListener('resize',sz);if(raf)cancelAnimationFrame(raf);raf=null;if(ren)ren.dispose();ren=null;window._snonuxSplashWebGLCleanup=null;}
+ window._snonuxSplashWebGLCleanup=cleanup;
+ function sz(){var w=cv.clientWidth||2,h=cv.clientHeight||2;if(ren)ren.setSize(w,h,false);if(ca){ca.aspect=w/h;ca.updateProjectionMatrix();}}
+ ren=new THREE.WebGLRenderer({canvas:cv,antialias:false,alpha:true});ren.setClearColor(0,0);ren.setPixelRatio(1);
+ sc=new THREE.Scene();ca=new THREE.PerspectiveCamera(50,1,0.1,80);ca.position.z=20;
+ var geo=new THREE.PlaneGeometry(0.22,0.32);
+ for(var i=0;i<60;i++){
+ var mat=new THREE.MeshBasicMaterial({color:0x55ff55,transparent:true,opacity:0.3+Math.random()*0.4});
+ var m=new THREE.Mesh(geo,mat);
+ m.position.set((Math.random()-0.5)*28, Math.random()*22-11, (Math.random()-0.5)*5);
+ m.userData.speed=0.5+Math.random()*1.5;
+ sc.add(m); drops.push(m);
+ }
+ sz();window.addEventListener('resize',sz);
+ function loop(now){raf=requestAnimationFrame(loop);
+ for(var i=0;i<drops.length;i++){
+ drops[i].position.y-=drops[i].userData.speed*0.06;
+ if(drops[i].position.y<-12) drops[i].position.y=12;
+ }
+ ren.render(sc,ca);}
+ raf=requestAnimationFrame(loop);
+ })();
+
+
+ (function() {
+ var _wild = false, _snoTOffset = 0, _snoLastT = 0;
+ var scene, camera, renderer, clock;
+ var columns = [];
+
+ function initThree() {
+ scene = new THREE.Scene();
+ scene.background = new THREE.Color(0x000088);
+
+ camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 200);
+ camera.position.set(0, 0, 40);
+
+ renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('three-canvas'), antialias: false });
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ renderer.setPixelRatio(1);
+ clock = new THREE.Clock();
+
+ var geo = new THREE.PlaneGeometry(0.35, 0.5);
+
+ for (var c = 0; c < 30; c++) {
+ var col = [];
+ var x = (c - 15) * 2.2;
+ var speed = 1.5 + Math.random() * 3;
+ var startY = Math.random() * 60 - 30;
+ for (var r = 0; r < 8; r++) {
+ var brightness = 1.0 - (r / 8) * 0.7;
+ var color = new THREE.Color(brightness * 0.33, brightness, brightness * 0.33);
+ var mat = new THREE.MeshBasicMaterial({ color: color, transparent: true, opacity: brightness * 0.5 });
+ var mesh = new THREE.Mesh(geo, mat);
+ mesh.position.set(x, startY - r * 0.7, 0);
+ scene.add(mesh);
+ col.push({ mesh: mesh, offset: r * 0.7 });
+ }
+ columns.push({ chars: col, x: x, speed: speed, y: startY });
+ }
+
+ window.addEventListener('resize', onResize);
+ animate();
+ }
+
+ function onResize() {
+ camera.aspect = window.innerWidth / window.innerHeight;
+ camera.updateProjectionMatrix();
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ }
+
+ function animate() {
+ requestAnimationFrame(animate);
+ var realT = clock.getElapsedTime();
+ _snoTOffset += (realT - _snoLastT) * (_wild ? 9 : 0);
+ _snoLastT = realT;
+ var t = realT + _snoTOffset;
+ for (var c = 0; c < columns.length; c++) {
+ var col = columns[c];
+ var y = col.y - t * col.speed;
+ y = ((y % 60) + 60) % 60 - 30;
+ for (var r = 0; r < col.chars.length; r++) {
+ col.chars[r].mesh.position.y = y - col.chars[r].offset;
+ // Wild: each char jitters horizontally like corrupted RAM
+ if (_wild) { col.chars[r].mesh.position.x = col.x + (Math.random() - 0.5) * 2.5; }
+ else { col.chars[r].mesh.position.x = col.x; }
+ }
+ }
+ // Wild: camera lunges forward/back and sways like a CRT meltdown
+ if (_wild) {
+ camera.position.z = 40 + Math.sin(realT * 0.41) * 14;
+ camera.position.x = Math.sin(realT * 0.37) * 8;
+ camera.fov = 60 + Math.sin(realT * 0.53) * 16;
+ camera.updateProjectionMatrix();
+ } else {
+ camera.position.z = 40;
+ camera.position.x = 0;
+ if (camera.fov !== 60) { camera.fov = 60; camera.updateProjectionMatrix(); }
+ }
+ renderer.render(scene, camera);
+ }
+
+ initThree();
+
+ // DOS nav/wild effects — CRT glitch on navigate, system crash rain on wild
+ window.snonuxOpenEffect = function() {
+ // Slide in like a dialog box appearing on DOS screen
+ var modal = document.getElementById('post-modal');
+ if (modal) { modal.classList.add('sno-modal-slide'); setTimeout(function() { modal.classList.remove('sno-modal-slide'); }, 360); }
+ // CRT scan flash from top
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;top:0;left:0;right:0;height:4px;z-index:997;pointer-events:none;background:rgba(85,255,255,0.7);box-shadow:0 0 8px rgba(85,255,255,0.5);transition:top 0.28s linear,opacity 0.1s 0.28s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.top='100vh'; setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 120); }, 280); }, 15);
+ };
+ window.snonuxCloseEffect = function() {
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); }, 280); }
+ };
+ window.snonuxScrollEffect = function(dir) {
+ var isDown = dir === 'down';
+ var thick = _wild ? '14px' : '5px';
+ var d = document.createElement('div');
+ // DOS/CRT: grey-to-white scanline
+ d.style.cssText = 'position:fixed;left:0;right:0;height:' + thick + ';z-index:9000;pointer-events:none;' +
+ 'background:linear-gradient(90deg,transparent,rgba(180,180,180,0.9),rgba(255,255,255,0.9),rgba(180,180,180,0.9),transparent);' +
+ (isDown ? 'top:0;' : 'bottom:0;') +
+ 'transition:transform 0.28s ease,opacity 0.28s ease;';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.transform = isDown ? 'translateY(100vh)' : 'translateY(-100vh)'; d.style.opacity='0'; }, 16);
+ setTimeout(function() { d.remove(); }, 360);
+ };
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ };
+ window.snonuxNavEffect = function() {
+ // CRT horizontal glitch
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); }, 300); }
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(85,255,85,0.12);transition:opacity 0.15s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 180); }, 25);
+ };
+ window.snonuxPageEffect = function() {
+ // System crash — scanline strobe
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); setTimeout(function() { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); }, 280); }, 40); }, 310); }
+ };
+ })();
diff --git a/internal/generator/templates/themes/matrix/meta.json b/internal/generator/templates/themes/matrix/meta.json
new file mode 100644
index 0000000..ac07e29
--- /dev/null
+++ b/internal/generator/templates/themes/matrix/meta.json
@@ -0,0 +1,7 @@
+{
+ "title": "snonux.foo // MATRIX",
+ "header_html": "\u003cdiv class=\"logo\"\u003e\n \u003cspan class=\"logo-mark\"\u003eSN\u003c/span\u003e\n \u003cdiv class=\"logo-title\"\u003e\n \u003ch1\u003eSNONUX.FOO\u003c/h1\u003e\n \u003cp class=\"subtitle\"\u003eMICROBLOG / \u003ca href=\"https://foo.zone\"\u003eFOO.ZONE\u003c/a\u003e IS THE REAL BLOG\u003c/p\u003e\n \u003cp class=\"logo-host\"\u003eServed by NetBSD on a Raspberry Pi 3\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"nav\"\u003e\n \u003ca href=\"atom.xml\" class=\"header-feed-link\" rel=\"alternate\" title=\"Atom feed\" type=\"application/atom+xml\"\u003eatom.xml\u003c/a\u003e\n \u003ca href=\"https://foo.zone/about\" class=\"transmit-btn\"\u003eTRANSMIT\u003c/a\u003e\n \u003c/div\u003e",
+ "splash_inner_html": "\u003ccanvas class=\"splash-gl-canvas\" id=\"splash-gl-canvas\" aria-hidden=\"true\"\u003e\u003c/canvas\u003e\n \u003cdiv class=\"splash-rain\" aria-hidden=\"true\"\u003e01001110 01000101 01001111\n10101010 11001100 00110011\n01110011 01101110 01101111\n11001010 10100101 01011010\u003c/div\u003e\n \u003cdiv class=\"splash-inner\"\u003e\n \u003cdiv class=\"splash-title\"\u003eSNONUX.FOO\u003c/div\u003e\n \u003cdiv class=\"splash-tag\"\u003eFollow the signal\u003c/div\u003e\n \u003cdiv class=\"splash-hint\"\u003ewake up — click or enter\u003c/div\u003e\n \u003c/div\u003e",
+ "prev_page_text": "\u0026lt;-- NEWER",
+ "next_page_text": "OLDER --\u0026gt;"
+}
diff --git a/internal/generator/templates/themes/matrix/theme.css b/internal/generator/templates/themes/matrix/theme.css
new file mode 100644
index 0000000..8254d45
--- /dev/null
+++ b/internal/generator/templates/themes/matrix/theme.css
@@ -0,0 +1,76 @@
+ :root { --g:#00ff41; --g2:#008f11; --g3:#003b00; --bg:#000; }
+ * { margin:0; padding:0; box-sizing:border-box; }
+ body { font-family:'Courier New',Courier,monospace; background:var(--bg); color:var(--g);
+ overflow:hidden; height:100vh; }
+ /* scanline overlay sits above WebGL */
+ body::before { content:''; position:fixed; inset:0; z-index:999; pointer-events:none;
+ background:repeating-linear-gradient(0deg,transparent,transparent 3px,
+ rgba(0,0,0,0.08) 3px,rgba(0,0,0,0.08) 4px); }
+ @keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} }
+ /* WebGL background canvas */
+ #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
+ .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
+ header { padding:12px 24px; background:#000; border-bottom:1px solid var(--g2);
+ display:flex; align-items:center; justify-content:space-between; }
+ .logo { display:flex; align-items:center; gap:14px; }
+ .logo-mark { font-size:1.8rem; color:var(--g); text-shadow:0 0 18px var(--g); letter-spacing:3px; }
+ /* blinking cursor after logo mark */
+ .logo-mark::after { content:'_'; animation:blink 1.2s step-start infinite; }
+ .logo-title h1 { font-size:1.2rem; color:var(--g); text-shadow:0 0 10px var(--g);
+ letter-spacing:4px; font-weight:normal; }
+ .logo-title .subtitle { font-size:0.72rem; color:var(--g2); margin-top:2px; letter-spacing:1px; }
+ .logo-title .subtitle a { color:var(--g); text-decoration:none; }
+ .logo-title .subtitle a:hover { text-shadow:0 0 6px var(--g); }
+ .transmit-btn { border:1px solid var(--g2); color:var(--g); padding:8px 18px;
+ text-decoration:none; font-size:0.82rem; letter-spacing:2px;
+ transition:all 0.1s; }
+ .transmit-btn:hover { background:var(--g); color:var(--bg); }
+ a.header-feed-link { color:var(--g2); }
+ a.header-feed-link:hover { color:var(--g); text-shadow:0 0 8px var(--g); }
+ .nav-hints { background:#000; border-bottom:1px solid var(--g3); color:var(--g2);
+ padding:4px 24px; display:flex; gap:18px; font-size:0.68rem; flex-wrap:wrap; }
+ .nav-hints kbd { background:transparent; border:1px solid var(--g3); color:var(--g);
+ padding:0 5px; font-size:0.68rem; margin:0 2px; }
+ .content { flex:1; overflow-y:auto; padding:14px 24px;
+ scrollbar-width:thin; scrollbar-color:var(--g2) var(--bg); }
+ .page-nav { display:flex; justify-content:center; margin:12px 0; }
+ .page-nav a { border:1px solid var(--g2); color:var(--g); padding:7px 20px;
+ text-decoration:none; font-size:0.82rem; letter-spacing:2px; }
+ .page-nav a:hover { background:var(--g); color:var(--bg); }
+ .page-nav-footer { flex-shrink:0; padding:6px 24px; display:flex; justify-content:center;
+ background:#000; border-top:1px solid var(--g2); }
+ .post { background:#000; border:1px solid var(--g3); padding:16px 18px;
+ margin-bottom:10px; cursor:pointer; transition:border-color 0.15s; }
+ .post:hover { border-color:var(--g2); box-shadow:0 0 8px rgba(0,255,65,0.2); }
+ .post-active { border-color:var(--g) !important; background:rgba(0,255,65,0.03) !important;
+ box-shadow:0 0 14px rgba(0,255,65,0.3),inset 3px 0 0 var(--g) !important; }
+ .post-header { display:flex; justify-content:space-between; margin-bottom:10px; font-size:0.85rem; }
+ .post-time { color:var(--g2); font-size:0.78rem; }
+ .post-text { line-height:1.6; font-size:0.88rem; }
+ .post-text a { color:var(--g); text-decoration:underline; }
+ .post-image { max-width:100%; margin-top:10px; border:1px solid var(--g3); }
+ .post-audio { width:100%; margin-top:10px; }
+ .post-modal { display:none; position:fixed; inset:0; z-index:100;
+ background:rgba(0,0,0,0.98); overflow-y:auto; padding:40px 20px; }
+ .post-modal.active { display:block; }
+ .modal-inner { max-width:740px; margin:0 auto; background:#000;
+ border:1px solid var(--g); padding:36px;
+ box-shadow:0 0 40px rgba(0,255,65,0.25); }
+ .modal-close { float:right; background:none; border:none; color:var(--g2);
+ font-family:monospace; font-size:0.9rem; cursor:pointer; letter-spacing:2px; }
+ @media(max-width:640px) { .nav-hints{display:none;} header{padding:10px 16px;} .content{padding:10px 16px;} }
+ [data-sno-theme="matrix"] .splash-overlay { background: #000; font-family:'Courier New',monospace; }
+ [data-sno-theme="matrix"] .splash-rain {
+ position:absolute; inset:0; overflow:hidden; pointer-events:none; opacity:0.35; z-index:1;
+ font-size:11px; line-height:14px; color:var(--g2); text-align:left; padding:8px;
+ white-space:pre; animation: splashMatrixScroll 16s linear infinite;
+ }
+ @keyframes splashMatrixScroll { to { transform: translateY(-24px); } }
+ [data-sno-theme="matrix"] .splash-title {
+ position:relative; z-index:1; font-size:clamp(1.1rem,3.5vw,1.5rem); color:var(--g);
+ text-shadow:0 0 20px var(--g); letter-spacing:0.35em;
+ animation: splashMatrixGlow 1.8s ease-in-out infinite alternate;
+ }
+ @keyframes splashMatrixGlow { from { opacity:0.85; } to { opacity:1; text-shadow:0 0 28px var(--g); } }
+ [data-sno-theme="matrix"] .splash-tag { position:relative; z-index:1; color:rgba(0,255,65,0.88); }
+ [data-sno-theme="matrix"] .splash-hint { position:relative; z-index:1; color:rgba(0,255,65,0.82); }
diff --git a/internal/generator/templates/themes/matrix.tmpl b/internal/generator/templates/themes/matrix/theme.js
index 40d74c1..7ca694f 100644
--- a/internal/generator/templates/themes/matrix.tmpl
+++ b/internal/generator/templates/themes/matrix/theme.js
@@ -1,105 +1,4 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>snonux.foo // MATRIX</title>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
- <style>
- :root { --g:#00ff41; --g2:#008f11; --g3:#003b00; --bg:#000; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:'Courier New',Courier,monospace; background:var(--bg); color:var(--g);
- overflow:hidden; height:100vh; }
- /* scanline overlay sits above WebGL */
- body::before { content:''; position:fixed; inset:0; z-index:999; pointer-events:none;
- background:repeating-linear-gradient(0deg,transparent,transparent 3px,
- rgba(0,0,0,0.08) 3px,rgba(0,0,0,0.08) 4px); }
- @keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} }
- /* WebGL background canvas */
- #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
- .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
- header { padding:12px 24px; background:#000; border-bottom:1px solid var(--g2);
- display:flex; align-items:center; justify-content:space-between; }
- .logo { display:flex; align-items:center; gap:14px; }
- .logo-mark { font-size:1.8rem; color:var(--g); text-shadow:0 0 18px var(--g); letter-spacing:3px; }
- /* blinking cursor after logo mark */
- .logo-mark::after { content:'_'; animation:blink 1.2s step-start infinite; }
- .logo-title h1 { font-size:1.2rem; color:var(--g); text-shadow:0 0 10px var(--g);
- letter-spacing:4px; font-weight:normal; }
- .logo-title .subtitle { font-size:0.72rem; color:var(--g2); margin-top:2px; letter-spacing:1px; }
- .logo-title .subtitle a { color:var(--g); text-decoration:none; }
- .logo-title .subtitle a:hover { text-shadow:0 0 6px var(--g); }
- .transmit-btn { border:1px solid var(--g2); color:var(--g); padding:8px 18px;
- text-decoration:none; font-size:0.82rem; letter-spacing:2px;
- transition:all 0.1s; }
- .transmit-btn:hover { background:var(--g); color:var(--bg); }
- a.header-feed-link { color:var(--g2); }
- a.header-feed-link:hover { color:var(--g); text-shadow:0 0 8px var(--g); }
- .nav-hints { background:#000; border-bottom:1px solid var(--g3); color:var(--g2);
- padding:4px 24px; display:flex; gap:18px; font-size:0.68rem; flex-wrap:wrap; }
- .nav-hints kbd { background:transparent; border:1px solid var(--g3); color:var(--g);
- padding:0 5px; font-size:0.68rem; margin:0 2px; }
- .content { flex:1; overflow-y:auto; padding:14px 24px;
- scrollbar-width:thin; scrollbar-color:var(--g2) var(--bg); }
- .page-nav { display:flex; justify-content:center; margin:12px 0; }
- .page-nav a { border:1px solid var(--g2); color:var(--g); padding:7px 20px;
- text-decoration:none; font-size:0.82rem; letter-spacing:2px; }
- .page-nav a:hover { background:var(--g); color:var(--bg); }
- .page-nav-footer { flex-shrink:0; padding:6px 24px; display:flex; justify-content:center;
- background:#000; border-top:1px solid var(--g2); }
- .post { background:#000; border:1px solid var(--g3); padding:16px 18px;
- margin-bottom:10px; cursor:pointer; transition:border-color 0.15s; }
- .post:hover { border-color:var(--g2); box-shadow:0 0 8px rgba(0,255,65,0.2); }
- .post-active { border-color:var(--g) !important; background:rgba(0,255,65,0.03) !important;
- box-shadow:0 0 14px rgba(0,255,65,0.3),inset 3px 0 0 var(--g) !important; }
- .post-header { display:flex; justify-content:space-between; margin-bottom:10px; font-size:0.85rem; }
- .post-time { color:var(--g2); font-size:0.78rem; }
- .post-text { line-height:1.6; font-size:0.88rem; }
- .post-text a { color:var(--g); text-decoration:underline; }
- .post-image { max-width:100%; margin-top:10px; border:1px solid var(--g3); }
- .post-audio { width:100%; margin-top:10px; }
- .post-modal { display:none; position:fixed; inset:0; z-index:100;
- background:rgba(0,0,0,0.98); overflow-y:auto; padding:40px 20px; }
- .post-modal.active { display:block; }
- .modal-inner { max-width:740px; margin:0 auto; background:#000;
- border:1px solid var(--g); padding:36px;
- box-shadow:0 0 40px rgba(0,255,65,0.25); }
- .modal-close { float:right; background:none; border:none; color:var(--g2);
- font-family:monospace; font-size:0.9rem; cursor:pointer; letter-spacing:2px; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:10px 16px;} .content{padding:10px 16px;} }
- .splash-overlay.splash-matrix { background: #000; font-family:'Courier New',monospace; }
- .splash-matrix .splash-rain {
- position:absolute; inset:0; overflow:hidden; pointer-events:none; opacity:0.35; z-index:1;
- font-size:11px; line-height:14px; color:var(--g2); text-align:left; padding:8px;
- white-space:pre; animation: splashMatrixScroll 16s linear infinite;
- }
- @keyframes splashMatrixScroll { to { transform: translateY(-24px); } }
- .splash-matrix .splash-title {
- position:relative; z-index:1; font-size:clamp(1.1rem,3.5vw,1.5rem); color:var(--g);
- text-shadow:0 0 20px var(--g); letter-spacing:0.35em;
- animation: splashMatrixGlow 1.8s ease-in-out infinite alternate;
- }
- @keyframes splashMatrixGlow { from { opacity:0.85; } to { opacity:1; text-shadow:0 0 28px var(--g); } }
- .splash-matrix .splash-tag { position:relative; z-index:1; color:rgba(0,255,65,0.88); }
- .splash-matrix .splash-hint { position:relative; z-index:1; color:rgba(0,255,65,0.82); }
-{{template "navSharedCSSInner"}}
- </style>
-</head>
-<body>
- {{template "splashGate"}}
- <div id="splash-overlay" class="splash-overlay splash-matrix" role="dialog" aria-modal="true" aria-label="Open microblog" tabindex="-1">
- <canvas class="splash-gl-canvas" id="splash-gl-canvas" aria-hidden="true"></canvas>
- <div class="splash-rain" aria-hidden="true">01001110 01000101 01001111
-10101010 11001100 00110011
-01110011 01101110 01101111
-11001010 10100101 01011010</div>
- <div class="splash-inner">
- <div class="splash-title">SNONUX.FOO</div>
- <div class="splash-tag">Follow the signal</div>
- <div class="splash-hint">wake up — click or enter</div>
- </div>
- </div>
- <script>
+
(function(){
if(document.documentElement.classList.contains('sno-splash-skip'))return;
var cv=document.getElementById('splash-gl-canvas');
@@ -119,46 +18,8 @@
pos.needsUpdate=true;pts.rotation.y=t*0.15;ren.render(sc,ca);}
raf=requestAnimationFrame(loop);
})();
- </script>
- <canvas id="three-canvas"></canvas>
- <div class="overlay">
- <header>
- <div class="logo">
- <span class="logo-mark">SN</span>
- <div class="logo-title">
- <h1>SNONUX.FOO</h1>
- <p class="subtitle">MICROBLOG / <a href="https://foo.zone">FOO.ZONE</a> IS THE REAL BLOG</p>
- <p class="logo-host">Served by NetBSD on a Raspberry Pi 3</p>
- </div>
- </div>
- <div class="nav">
- <a href="atom.xml" class="header-feed-link" rel="alternate" title="Atom feed" type="application/atom+xml">atom.xml</a>
- <a href="https://foo.zone/about" class="transmit-btn">TRANSMIT</a>
- </div>
- </header>
- {{template "navhints" .}}
- <div class="content" id="post-content">
- {{range $i, $post := .Posts}}
- <div class="post" id="post-{{$post.ID}}" data-index="{{$i}}">
- <div class="post-header">
- <div><strong>@snonux</strong></div>
- <div class="post-time">{{$post.FormattedTime}}</div>
- </div>
- <div class="post-text">{{$post.ContentHTML}}</div>
- </div>
- {{end}}
- </div>
- {{if or .PrevPage .NextPage}}
- <footer class="page-nav-footer" aria-label="Pagination">
- <div class="page-nav page-nav-dual">
- {{if .PrevPage}}<a href="{{.PrevPage}}">&lt;-- NEWER</a>{{end}}
- {{if .NextPage}}<a href="{{.NextPage}}">OLDER --&gt;</a>{{end}}
- </div>
- </footer>
- {{end}}
- </div>
- {{template "navmodal" .}}
- <script>
+
+
// Matrix WebGL scene: 80 columns of falling particles with per-vertex colour.
// Each column has a "head" that falls at a random speed; particles near the head
// are bright green and fade to near-black further behind, simulating digital rain.
@@ -309,7 +170,3 @@
if (ov) { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); }, 320); }
};
})();
- </script>
- {{template "navscript" .}}
-</body>
-</html>
diff --git a/internal/generator/templates/themes/neon.tmpl b/internal/generator/templates/themes/neon.tmpl
deleted file mode 100644
index bf8bd89..0000000
--- a/internal/generator/templates/themes/neon.tmpl
+++ /dev/null
@@ -1,348 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>snonux.foo • NEON NEXUS</title>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
- <style>
- @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700&display=swap');
- :root { --neon-cyan:#00f5ff; --neon-magenta:#ff00cc; --neon-yellow:#ffe700; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:'Orbitron',sans-serif; background:#0b001a; color:#e0f8ff; overflow:hidden; height:100vh; }
- #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
- .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
- header { padding:16px 30px; display:flex; align-items:center; justify-content:space-between;
- background:rgba(11,0,26,0.8); backdrop-filter:blur(12px);
- border-bottom:2px solid rgba(255,231,0,0.3); }
- .logo { display:flex; align-items:center; gap:12px; }
- #sn-logo { flex-shrink:0; }
- .logo-title h1 { font-size:2rem; font-weight:700; letter-spacing:-3px; text-shadow:0 0 25px var(--neon-cyan); }
- .logo-title .subtitle { font-size:0.68rem; opacity:0.6; letter-spacing:1px; margin-top:2px; }
- .logo-title .subtitle a { color:var(--neon-cyan); text-decoration:none; }
- .logo-title .subtitle a:hover { text-shadow:0 0 8px var(--neon-cyan); }
- .nav { gap:16px; }
- a.header-feed-link { color:var(--neon-cyan); text-shadow:0 0 8px rgba(0,245,255,0.35); }
- .transmit-btn { background:transparent; border:3px solid var(--neon-yellow); color:var(--neon-yellow);
- padding:12px 28px; border-radius:9999px; font-weight:600; letter-spacing:1px;
- display:flex; align-items:center; gap:10px; box-shadow:0 0 30px var(--neon-yellow);
- transition:all 0.3s; text-decoration:none; font-family:'Orbitron',sans-serif; font-size:0.9rem; }
- .transmit-btn:hover { background:var(--neon-yellow); color:#0b001a; transform:scale(1.08); }
- .content { flex:1; padding:30px; overflow-y:auto; scrollbar-width:thin; scrollbar-color:#ffe700 #1a0033; }
- .page-nav { display:flex; justify-content:center; margin:18px 0; }
- .page-nav a { background:transparent; border:2px solid var(--neon-cyan); color:var(--neon-cyan);
- padding:10px 28px; border-radius:9999px; font-size:0.85rem; letter-spacing:2px;
- text-decoration:none; transition:all 0.3s; }
- .page-nav a:hover { background:var(--neon-cyan); color:#0b001a; }
- .page-nav-footer { flex-shrink:0; padding:8px 30px; display:flex; justify-content:center;
- background:rgba(11,0,26,0.8); backdrop-filter:blur(12px);
- border-top:2px solid rgba(255,231,0,0.3); }
- .post { background:rgba(20,5,45,0.9); border:2px solid transparent;
- border-image:linear-gradient(45deg,var(--neon-cyan),var(--neon-magenta)) 1;
- border-radius:24px; padding:28px; margin-bottom:28px;
- box-shadow:0 0 35px rgba(0,245,255,0.5);
- transition:all 0.4s cubic-bezier(0.23,1,0.32,1); cursor:pointer; }
- .post:hover { transform:translateY(-8px) rotate(1deg); box-shadow:0 0 50px rgba(255,231,0,0.6); }
- .post-active { border-image:none !important; border-color:var(--neon-yellow) !important;
- background:rgba(40,20,70,0.97) !important;
- box-shadow:0 0 0 2px var(--neon-yellow),0 0 30px rgba(255,231,0,0.7),
- 0 0 70px rgba(255,231,0,0.35),inset 4px 0 0 var(--neon-yellow) !important;
- transform:translateY(-6px) scale(1.012); }
- .post-header { display:flex; justify-content:space-between; margin-bottom:18px; font-size:0.95rem; }
- .post-time { font-family:monospace; color:var(--neon-yellow); text-shadow:0 0 12px var(--neon-yellow); }
- .post-text { font-size:1.1rem; line-height:1.55; }
- .post-text a { color:var(--neon-cyan); text-decoration:none; }
- .post-text a:hover { text-shadow:0 0 8px var(--neon-cyan); }
- .post-image { max-width:100%; border-radius:12px; margin-top:12px; }
- .post-audio { width:100%; margin-top:12px; }
- .nav-hints { display:flex; gap:20px; justify-content:center; align-items:center;
- padding:6px 20px; background:rgba(11,0,26,0.7);
- border-bottom:1px solid rgba(0,245,255,0.15);
- font-size:0.68rem; letter-spacing:1.5px; color:rgba(224,248,255,0.5); flex-wrap:wrap; }
- .nav-hints kbd { display:inline-block; background:rgba(0,245,255,0.1);
- border:1px solid rgba(0,245,255,0.35); border-radius:4px; padding:1px 5px;
- color:var(--neon-cyan); font-family:monospace; font-size:0.72rem; margin:0 2px; }
- .post-modal { display:none; position:fixed; inset:0; z-index:100;
- background:rgba(11,0,26,0.95); backdrop-filter:blur(16px);
- overflow-y:auto; padding:40px; }
- .post-modal.active { display:block; }
- .modal-inner { max-width:800px; margin:0 auto; background:rgba(20,5,45,0.98);
- border:2px solid transparent;
- border-image:linear-gradient(45deg,var(--neon-yellow),var(--neon-magenta)) 1;
- border-radius:24px; padding:40px; box-shadow:0 0 80px rgba(255,231,0,0.4); }
- .modal-close { float:right; background:none; border:none; color:var(--neon-cyan);
- font-size:1.4rem; cursor:pointer; font-family:'Orbitron',sans-serif; }
- @media(max-width:640px) {
- .logo-title h1 { font-size:1.6rem; } #sn-logo { width:44px; height:44px; }
- .post { padding:22px; margin-bottom:22px; } .content { padding:20px; }
- header { padding:14px 20px; } .transmit-btn { padding:9px 16px; font-size:0.8rem; }
- .nav-hints { display:none; } .modal-inner { padding:24px 16px; }
- }
- .splash-overlay.splash-neon {
- background: radial-gradient(ellipse 120% 80% at 50% 35%, rgba(0,245,255,0.14) 0%, transparent 55%),
- radial-gradient(ellipse 90% 55% at 75% 85%, rgba(255,0,204,0.12) 0%, transparent 50%),
- #0b001a;
- }
- .splash-neon .splash-deco {
- width:100px; height:100px; margin:0 auto 1.25rem; border-radius:50%;
- border:3px solid var(--neon-cyan); box-shadow:0 0 36px var(--neon-cyan), inset 0 0 26px rgba(0,245,255,0.15);
- animation: splashNeonSpin 5s linear infinite;
- }
- @keyframes splashNeonSpin { to { transform: rotate(360deg); } }
- .splash-neon .splash-title {
- font-size: clamp(1.5rem, 5vw, 2.35rem);
- animation: splashNeonPulse 2s ease-in-out infinite alternate;
- }
- @keyframes splashNeonPulse {
- from { text-shadow: 0 0 12px var(--neon-cyan), 0 0 24px rgba(255,0,204,0.4); }
- to { text-shadow: 0 0 26px var(--neon-cyan), 0 0 48px var(--neon-magenta); }
- }
- .splash-neon .splash-tag { color: var(--neon-yellow); }
- .splash-neon .splash-hint { color: rgba(224,248,255,0.9); font-family: 'Orbitron', sans-serif; }
- .splash-neon .splash-inner { text-shadow: 0 2px 24px rgba(0,0,0,0.85), 0 0 40px rgba(11,0,26,0.9); }
-{{template "navSharedCSSInner"}}
- </style>
-</head>
-<body>
- {{template "splashGate"}}
- <div id="splash-overlay" class="splash-overlay splash-neon" role="dialog" aria-modal="true" aria-label="Open microblog" tabindex="-1">
- <canvas class="splash-gl-canvas" id="splash-gl-canvas" aria-hidden="true"></canvas>
- <div class="splash-inner">
- <div class="splash-deco" aria-hidden="true"></div>
- <div class="splash-title">snonux.foo</div>
- <div class="splash-tag">Neon Nexus</div>
- <div class="splash-hint">Click or Enter &mdash; establish link</div>
- </div>
- </div>
- <script>
- (function(){
- if(document.documentElement.classList.contains('sno-splash-skip'))return;
- var cv=document.getElementById('splash-gl-canvas');
- if(!cv||typeof THREE==='undefined')return;
- var raf,ren,sc,ca,g=new THREE.Group(),t0=performance.now();
- function cleanup(){window.removeEventListener('resize',sz);if(raf)cancelAnimationFrame(raf);raf=null;if(ren){ren.dispose();}ren=null;window._snonuxSplashWebGLCleanup=null;}
- window._snonuxSplashWebGLCleanup=cleanup;
- function sz(){var w=cv.clientWidth||2,h=cv.clientHeight||2;if(ren)ren.setSize(w,h,false);if(ca){ca.aspect=w/h;ca.updateProjectionMatrix();}}
- ren=new THREE.WebGLRenderer({canvas:cv,antialias:true,alpha:true});
- ren.setClearColor(0,0);ren.setPixelRatio(Math.min(window.devicePixelRatio||1,2));
- sc=new THREE.Scene();ca=new THREE.PerspectiveCamera(52,1,0.1,100);ca.position.set(0,0.4,9);
- var cols=[0x00f5ff,0xff00cc,0xffe700],i,m;
- for(i=0;i<3;i++){m=new THREE.Mesh(new THREE.TorusGeometry(1.55+i*0.48,0.055,8,48),new THREE.MeshBasicMaterial({color:cols[i],transparent:true,opacity:0.92}));m.rotation.x=Math.PI/2;m.userData.sp=0.01+i*0.004;g.add(m);}
- g.add(new THREE.Mesh(new THREE.SphereGeometry(0.52,20,20),new THREE.MeshBasicMaterial({color:0xffe700,transparent:true,opacity:0.95})));
- sc.add(g);sz();window.addEventListener('resize',sz);
- function loop(now){raf=requestAnimationFrame(loop);var t=(now-t0)*0.001;g.rotation.y=t*0.42;g.rotation.x=Math.sin(t*0.65)*0.12;g.children.forEach(function(c){if(c.userData.sp)c.rotation.z+=c.userData.sp;});ren.render(sc,ca);}
- raf=requestAnimationFrame(loop);
- })();
- </script>
- <canvas id="three-canvas"></canvas>
- <div class="overlay">
- <header>
- <div class="logo">
- <svg id="sn-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56 56" width="56" height="56" aria-label="snonux logo">
- <defs>
- <linearGradient id="sn-grad" x1="0" y1="0" x2="1" y2="1">
- <stop offset="0%" stop-color="#ffe700"/><stop offset="100%" stop-color="#ff00cc"/>
- </linearGradient>
- <radialGradient id="sn-bg" cx="40%" cy="35%" r="70%">
- <stop offset="0%" stop-color="#2d0060"/><stop offset="100%" stop-color="#0b001a"/>
- </radialGradient>
- <filter id="sn-gc" x="-60%" y="-60%" width="220%" height="220%">
- <feGaussianBlur stdDeviation="2.5" result="b"/>
- <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
- </filter>
- <filter id="sn-gm" x="-60%" y="-60%" width="220%" height="220%">
- <feGaussianBlur stdDeviation="2.5" result="b"/>
- <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
- </filter>
- <filter id="sn-gh" x="-20%" y="-20%" width="140%" height="140%">
- <feGaussianBlur stdDeviation="3" result="b"/>
- <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
- </filter>
- </defs>
- <polygon points="55,28 41.5,51.4 14.5,51.4 1,28 14.5,4.6 41.5,4.6"
- fill="none" stroke="#ffe700" stroke-width="5" opacity="0.18" filter="url(#sn-gh)"/>
- <polygon points="55,28 41.5,51.4 14.5,51.4 1,28 14.5,4.6 41.5,4.6"
- fill="url(#sn-bg)" stroke="url(#sn-grad)" stroke-width="1.8"/>
- <line x1="34" y1="12" x2="22" y2="44" stroke="#ffe700" stroke-width="0.9" opacity="0.75"/>
- <rect x="32.5" y="10.5" width="3" height="3" transform="rotate(45 34 12)" fill="#ffe700" opacity="0.8"/>
- <rect x="20.5" y="42.5" width="3" height="3" transform="rotate(45 22 44)" fill="#ffe700" opacity="0.8"/>
- <text x="9" y="37" font-family="Orbitron,monospace" font-weight="700" font-size="20"
- fill="#00f5ff" filter="url(#sn-gc)">S</text>
- <text x="28" y="37" font-family="Orbitron,monospace" font-weight="700" font-size="20"
- fill="#ff00cc" filter="url(#sn-gm)">N</text>
- </svg>
- <div class="logo-title">
- <h1>snonux.foo</h1>
- <p class="subtitle">microblog &mdash; <a href="https://foo.zone">foo.zone</a> is the real blog</p>
- <p class="logo-host">Served by NetBSD on a Raspberry Pi 3</p>
- </div>
- </div>
- <div class="nav">
- <a href="atom.xml" class="header-feed-link" rel="alternate" title="Atom feed" type="application/atom+xml">Atom feed</a>
- <a href="https://foo.zone/about" class="transmit-btn">
- <i class="fa-solid fa-feather-pointed"></i> TRANSMIT TO NEXUS
- </a>
- </div>
- </header>
- {{template "navhints" .}}
- <div class="content" id="post-content">
- {{range $i, $post := .Posts}}
- <div class="post" id="post-{{$post.ID}}" data-index="{{$i}}">
- <div class="post-header">
- <div><strong>@snonux</strong></div>
- <div class="post-time">{{$post.FormattedTime}}</div>
- </div>
- <div class="post-text">{{$post.ContentHTML}}</div>
- </div>
- {{end}}
- </div>
- {{if or .PrevPage .NextPage}}
- <footer class="page-nav-footer" aria-label="Pagination">
- <div class="page-nav page-nav-dual">
- {{if .PrevPage}}<a href="{{.PrevPage}}">&larr; NEWER TRANSMISSIONS</a>{{end}}
- {{if .NextPage}}<a href="{{.NextPage}}">OLDER TRANSMISSIONS &rarr;</a>{{end}}
- </div>
- </footer>
- {{end}}
- </div>
- {{template "navmodal" .}}
- <script>
- // Three.js neon nexus scene — central orb, orbiting rings, particle field.
- let scene, camera, renderer, centralSphere, rings = [], particles;
- function initThree() {
- const canvas = document.getElementById('three-canvas');
- renderer = new THREE.WebGLRenderer({ canvas, antialias:true, alpha:true });
- renderer.setSize(window.innerWidth, window.innerHeight);
- renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
- scene = new THREE.Scene();
- scene.fog = new THREE.Fog(0x0b001a, 15, 80);
- camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 200);
- camera.position.set(0, 12, 35);
- scene.add(new THREE.AmbientLight(0x00f5ff, 0.8));
- const coreLight = new THREE.PointLight(0xff00cc, 4, 100);
- coreLight.position.set(0,0,0); scene.add(coreLight);
- centralSphere = new THREE.Mesh(new THREE.SphereGeometry(6,64,64),
- new THREE.MeshPhongMaterial({color:0x00f5ff,emissive:0xff00cc,emissiveIntensity:1.8,
- shininess:100,transparent:true,opacity:0.95}));
- scene.add(centralSphere);
- scene.add(new THREE.Mesh(new THREE.SphereGeometry(4.5,64,64),
- new THREE.MeshBasicMaterial({color:0x00f5ff,transparent:true,opacity:0.4,blending:THREE.AdditiveBlending})));
- const rc=[0x00f5ff,0xff00cc,0x00f5ff,0xffe700];
- for(let i=0;i<14;i++){
- const ring=new THREE.Mesh(new THREE.TorusGeometry(12+i*2.2,0.35,32,128),
- new THREE.MeshPhongMaterial({color:rc[i%4],emissive:rc[i%4],emissiveIntensity:2.5,
- shininess:80,transparent:true,opacity:0.9,side:THREE.DoubleSide}));
- ring.rotation.x=Math.random()*Math.PI;
- ring.userData={speed:0.008+i*0.003,axisTilt:Math.random()*0.6};
- scene.add(ring); rings.push(ring);
- }
- const pCount=2200,pos=new Float32Array(pCount*3),col=new Float32Array(pCount*3);
- for(let i=0;i<pCount*3;i+=3){
- const r=30+Math.random()*40,t=Math.random()*Math.PI*2,p=Math.acos(2*Math.random()-1);
- pos[i]=r*Math.sin(p)*Math.cos(t);pos[i+1]=r*Math.sin(p)*Math.sin(t);pos[i+2]=r*Math.cos(p);
- const c=new THREE.Color().setHSL(Math.random()>0.5?0.55:0.8,1,1);
- col[i]=c.r;col[i+1]=c.g;col[i+2]=c.b;
- }
- const pg=new THREE.BufferGeometry();
- pg.setAttribute('position',new THREE.BufferAttribute(pos,3));
- pg.setAttribute('color',new THREE.BufferAttribute(col,3));
- particles=new THREE.Points(pg,new THREE.PointsMaterial(
- {size:0.22,vertexColors:true,transparent:true,opacity:0.9,blending:THREE.AdditiveBlending}));
- scene.add(particles);
- let mouseX=0;
- window.addEventListener('mousemove',e=>{mouseX=(e.clientX/window.innerWidth)*2-1;});
- (function animate(){
- requestAnimationFrame(animate);
- const time=Date.now()*0.0004;
- camera.position.x=Math.sin(time)*35+mouseX*6;
- camera.position.z=Math.cos(time)*35+10;
- camera.lookAt(0,4,0);
- centralSphere.rotation.y+=0.003;
- rings.forEach((ring,i)=>{
- ring.rotation.y+=ring.userData.speed;
- ring.rotation.x=Math.sin(time*1.5+i)*ring.userData.axisTilt;
- });
- particles.rotation.y+=window._snoNeonWild ? 0.012 : 0.0008;
- renderer.render(scene,camera);
- })();
- }
- window.addEventListener('resize',()=>{
- if(!camera||!renderer) return;
- camera.aspect=window.innerWidth/window.innerHeight;
- camera.updateProjectionMatrix();
- renderer.setSize(window.innerWidth,window.innerHeight);
- });
- window.onload=initThree;
- </script>
- <script>
- // Neon nav/wild effects — lightning flash on navigate, ring frenzy on wild
- (function() {
- function flash(color, ms) {
- var d = document.createElement('div');
- d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:' + color + ';transition:opacity ' + (ms||180) + 'ms';
- document.body.appendChild(d);
- setTimeout(function() { d.style.opacity = '0'; setTimeout(function() { d.remove(); }, ms || 180); }, 30);
- }
- function fxOverlay(cls, ms) {
- var ov = document.querySelector('.overlay');
- if (!ov) return;
- ov.classList.add(cls);
- setTimeout(function() { ov.classList.remove(cls); }, ms || 380);
- }
- var _wild = false;
- window.snonuxOpenEffect = function(post) {
- // Modal burst from center with lightning ring
- var modal = document.getElementById('post-modal');
- if (modal) { modal.classList.add('sno-modal-expand'); setTimeout(function() { modal.classList.remove('sno-modal-expand'); }, 420); }
- // Cyan ring pulse radiating outward
- var ring = document.createElement('div');
- var r = post ? post.getBoundingClientRect() : {left: window.innerWidth/2, top: window.innerHeight/2};
- ring.style.cssText = 'position:fixed;top:' + (r.top+20) + 'px;left:' + (r.left+20) + 'px;z-index:997;pointer-events:none;width:10px;height:10px;border-radius:50%;border:3px solid rgba(0,245,255,0.9);transition:all 0.38s ease,opacity 0.38s';
- document.body.appendChild(ring);
- setTimeout(function() { ring.style.transform='scale(35)'; ring.style.opacity='0'; setTimeout(function() { ring.remove(); }, 420); }, 15);
- };
- window.snonuxCloseEffect = function() {
- var d = document.createElement('div');
- d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(255,0,204,0.12);transition:opacity 0.18s';
- document.body.appendChild(d);
- setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 200); }, 15);
- };
- window.snonuxNavEffect = function() {
- flash('rgba(0,245,255,0.22)', 160);
- fxOverlay('sno-fx-shake', 380);
- };
- window.snonuxPageEffect = function() {
- flash('rgba(255,231,0,0.18)', 140);
- fxOverlay('sno-fx-zoom', 320);
- };
- window.snonuxScrollEffect = function(dir) {
- var isDown = dir === 'down';
- var thick = _wild ? '14px' : '5px';
- var d = document.createElement('div');
- d.style.cssText = 'position:fixed;left:0;right:0;height:' + thick + ';z-index:9000;pointer-events:none;' +
- 'background:linear-gradient(90deg,transparent,rgba(0,245,255,0.9),rgba(255,0,204,0.9),transparent);' +
- (isDown ? 'top:0;' : 'bottom:0;') +
- 'transition:transform 0.3s ease,opacity 0.3s ease;';
- document.body.appendChild(d);
- setTimeout(function() { d.style.transform = isDown ? 'translateY(100vh)' : 'translateY(-100vh)'; d.style.opacity='0'; }, 16);
- setTimeout(function() { d.remove(); }, 380);
- };
- window.snonuxWildToggle = function() {
- _wild = !_wild;
- var b = document.getElementById('sno-wild-badge');
- if (b) b.classList.toggle('sno-wild-on', _wild);
- // Speed up all rings and particles when wild
- if (rings && rings.length) {
- rings.forEach(function(r, i) {
- r.userData.speed = _wild ? (0.008 + i * 0.003) * 14 : 0.008 + i * 0.003;
- });
- }
- // Store wild state for particle rotation boost in animate loop
- window._snoNeonWild = _wild;
- };
- })();
- </script>
- {{template "navscript" .}}
-</body>
-</html>
diff --git a/internal/generator/templates/themes/neon/meta.json b/internal/generator/templates/themes/neon/meta.json
new file mode 100644
index 0000000..1ea8510
--- /dev/null
+++ b/internal/generator/templates/themes/neon/meta.json
@@ -0,0 +1,7 @@
+{
+ "title": "snonux.foo • NEON NEXUS",
+ "header_html": "\u003cdiv class=\"logo\"\u003e\n \u003csvg id=\"sn-logo\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 56 56\" width=\"56\" height=\"56\" aria-label=\"snonux logo\"\u003e\n \u003cdefs\u003e\n \u003clinearGradient id=\"sn-grad\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\"\u003e\n \u003cstop offset=\"0%\" stop-color=\"#ffe700\"/\u003e\u003cstop offset=\"100%\" stop-color=\"#ff00cc\"/\u003e\n \u003c/linearGradient\u003e\n \u003cradialGradient id=\"sn-bg\" cx=\"40%\" cy=\"35%\" r=\"70%\"\u003e\n \u003cstop offset=\"0%\" stop-color=\"#2d0060\"/\u003e\u003cstop offset=\"100%\" stop-color=\"#0b001a\"/\u003e\n \u003c/radialGradient\u003e\n \u003cfilter id=\"sn-gc\" x=\"-60%\" y=\"-60%\" width=\"220%\" height=\"220%\"\u003e\n \u003cfeGaussianBlur stdDeviation=\"2.5\" result=\"b\"/\u003e\n \u003cfeMerge\u003e\u003cfeMergeNode in=\"b\"/\u003e\u003cfeMergeNode in=\"SourceGraphic\"/\u003e\u003c/feMerge\u003e\n \u003c/filter\u003e\n \u003cfilter id=\"sn-gm\" x=\"-60%\" y=\"-60%\" width=\"220%\" height=\"220%\"\u003e\n \u003cfeGaussianBlur stdDeviation=\"2.5\" result=\"b\"/\u003e\n \u003cfeMerge\u003e\u003cfeMergeNode in=\"b\"/\u003e\u003cfeMergeNode in=\"SourceGraphic\"/\u003e\u003c/feMerge\u003e\n \u003c/filter\u003e\n \u003cfilter id=\"sn-gh\" x=\"-20%\" y=\"-20%\" width=\"140%\" height=\"140%\"\u003e\n \u003cfeGaussianBlur stdDeviation=\"3\" result=\"b\"/\u003e\n \u003cfeMerge\u003e\u003cfeMergeNode in=\"b\"/\u003e\u003cfeMergeNode in=\"SourceGraphic\"/\u003e\u003c/feMerge\u003e\n \u003c/filter\u003e\n \u003c/defs\u003e\n \u003cpolygon points=\"55,28 41.5,51.4 14.5,51.4 1,28 14.5,4.6 41.5,4.6\"\n fill=\"none\" stroke=\"#ffe700\" stroke-width=\"5\" opacity=\"0.18\" filter=\"url(#sn-gh)\"/\u003e\n \u003cpolygon points=\"55,28 41.5,51.4 14.5,51.4 1,28 14.5,4.6 41.5,4.6\"\n fill=\"url(#sn-bg)\" stroke=\"url(#sn-grad)\" stroke-width=\"1.8\"/\u003e\n \u003cline x1=\"34\" y1=\"12\" x2=\"22\" y2=\"44\" stroke=\"#ffe700\" stroke-width=\"0.9\" opacity=\"0.75\"/\u003e\n \u003crect x=\"32.5\" y=\"10.5\" width=\"3\" height=\"3\" transform=\"rotate(45 34 12)\" fill=\"#ffe700\" opacity=\"0.8\"/\u003e\n \u003crect x=\"20.5\" y=\"42.5\" width=\"3\" height=\"3\" transform=\"rotate(45 22 44)\" fill=\"#ffe700\" opacity=\"0.8\"/\u003e\n \u003ctext x=\"9\" y=\"37\" font-family=\"Orbitron,monospace\" font-weight=\"700\" font-size=\"20\"\n fill=\"#00f5ff\" filter=\"url(#sn-gc)\"\u003eS\u003c/text\u003e\n \u003ctext x=\"28\" y=\"37\" font-family=\"Orbitron,monospace\" font-weight=\"700\" font-size=\"20\"\n fill=\"#ff00cc\" filter=\"url(#sn-gm)\"\u003eN\u003c/text\u003e\n \u003c/svg\u003e\n \u003cdiv class=\"logo-title\"\u003e\n \u003ch1\u003esnonux.foo\u003c/h1\u003e\n \u003cp class=\"subtitle\"\u003emicroblog \u0026mdash; \u003ca href=\"https://foo.zone\"\u003efoo.zone\u003c/a\u003e is the real blog\u003c/p\u003e\n \u003cp class=\"logo-host\"\u003eServed by NetBSD on a Raspberry Pi 3\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"nav\"\u003e\n \u003ca href=\"atom.xml\" class=\"header-feed-link\" rel=\"alternate\" title=\"Atom feed\" type=\"application/atom+xml\"\u003eAtom feed\u003c/a\u003e\n \u003ca href=\"https://foo.zone/about\" class=\"transmit-btn\"\u003e\n \u003ci class=\"fa-solid fa-feather-pointed\"\u003e\u003c/i\u003e TRANSMIT TO NEXUS\n \u003c/a\u003e\n \u003c/div\u003e",
+ "splash_inner_html": "\u003ccanvas class=\"splash-gl-canvas\" id=\"splash-gl-canvas\" aria-hidden=\"true\"\u003e\u003c/canvas\u003e\n \u003cdiv class=\"splash-inner\"\u003e\n \u003cdiv class=\"splash-deco\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-title\"\u003esnonux.foo\u003c/div\u003e\n \u003cdiv class=\"splash-tag\"\u003eNeon Nexus\u003c/div\u003e\n \u003cdiv class=\"splash-hint\"\u003eClick or Enter \u0026mdash; establish link\u003c/div\u003e\n \u003c/div\u003e",
+ "prev_page_text": "\u0026larr; NEWER TRANSMISSIONS",
+ "next_page_text": "OLDER TRANSMISSIONS \u0026rarr;"
+}
diff --git a/internal/generator/templates/themes/neon/theme.css b/internal/generator/templates/themes/neon/theme.css
new file mode 100644
index 0000000..1db90e8
--- /dev/null
+++ b/internal/generator/templates/themes/neon/theme.css
@@ -0,0 +1,94 @@
+ @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700&display=swap');
+ :root { --neon-cyan:#00f5ff; --neon-magenta:#ff00cc; --neon-yellow:#ffe700; }
+ * { margin:0; padding:0; box-sizing:border-box; }
+ body { font-family:'Orbitron',sans-serif; background:#0b001a; color:#e0f8ff; overflow:hidden; height:100vh; }
+ #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
+ .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
+ header { padding:16px 30px; display:flex; align-items:center; justify-content:space-between;
+ background:rgba(11,0,26,0.8); backdrop-filter:blur(12px);
+ border-bottom:2px solid rgba(255,231,0,0.3); }
+ .logo { display:flex; align-items:center; gap:12px; }
+ #sn-logo { flex-shrink:0; }
+ .logo-title h1 { font-size:2rem; font-weight:700; letter-spacing:-3px; text-shadow:0 0 25px var(--neon-cyan); }
+ .logo-title .subtitle { font-size:0.68rem; opacity:0.6; letter-spacing:1px; margin-top:2px; }
+ .logo-title .subtitle a { color:var(--neon-cyan); text-decoration:none; }
+ .logo-title .subtitle a:hover { text-shadow:0 0 8px var(--neon-cyan); }
+ .nav { gap:16px; }
+ a.header-feed-link { color:var(--neon-cyan); text-shadow:0 0 8px rgba(0,245,255,0.35); }
+ .transmit-btn { background:transparent; border:3px solid var(--neon-yellow); color:var(--neon-yellow);
+ padding:12px 28px; border-radius:9999px; font-weight:600; letter-spacing:1px;
+ display:flex; align-items:center; gap:10px; box-shadow:0 0 30px var(--neon-yellow);
+ transition:all 0.3s; text-decoration:none; font-family:'Orbitron',sans-serif; font-size:0.9rem; }
+ .transmit-btn:hover { background:var(--neon-yellow); color:#0b001a; transform:scale(1.08); }
+ .content { flex:1; padding:30px; overflow-y:auto; scrollbar-width:thin; scrollbar-color:#ffe700 #1a0033; }
+ .page-nav { display:flex; justify-content:center; margin:18px 0; }
+ .page-nav a { background:transparent; border:2px solid var(--neon-cyan); color:var(--neon-cyan);
+ padding:10px 28px; border-radius:9999px; font-size:0.85rem; letter-spacing:2px;
+ text-decoration:none; transition:all 0.3s; }
+ .page-nav a:hover { background:var(--neon-cyan); color:#0b001a; }
+ .page-nav-footer { flex-shrink:0; padding:8px 30px; display:flex; justify-content:center;
+ background:rgba(11,0,26,0.8); backdrop-filter:blur(12px);
+ border-top:2px solid rgba(255,231,0,0.3); }
+ .post { background:rgba(20,5,45,0.9); border:2px solid transparent;
+ border-image:linear-gradient(45deg,var(--neon-cyan),var(--neon-magenta)) 1;
+ border-radius:24px; padding:28px; margin-bottom:28px;
+ box-shadow:0 0 35px rgba(0,245,255,0.5);
+ transition:all 0.4s cubic-bezier(0.23,1,0.32,1); cursor:pointer; }
+ .post:hover { transform:translateY(-8px) rotate(1deg); box-shadow:0 0 50px rgba(255,231,0,0.6); }
+ .post-active { border-image:none !important; border-color:var(--neon-yellow) !important;
+ background:rgba(40,20,70,0.97) !important;
+ box-shadow:0 0 0 2px var(--neon-yellow),0 0 30px rgba(255,231,0,0.7),
+ 0 0 70px rgba(255,231,0,0.35),inset 4px 0 0 var(--neon-yellow) !important;
+ transform:translateY(-6px) scale(1.012); }
+ .post-header { display:flex; justify-content:space-between; margin-bottom:18px; font-size:0.95rem; }
+ .post-time { font-family:monospace; color:var(--neon-yellow); text-shadow:0 0 12px var(--neon-yellow); }
+ .post-text { font-size:1.1rem; line-height:1.55; }
+ .post-text a { color:var(--neon-cyan); text-decoration:none; }
+ .post-text a:hover { text-shadow:0 0 8px var(--neon-cyan); }
+ .post-image { max-width:100%; border-radius:12px; margin-top:12px; }
+ .post-audio { width:100%; margin-top:12px; }
+ .nav-hints { display:flex; gap:20px; justify-content:center; align-items:center;
+ padding:6px 20px; background:rgba(11,0,26,0.7);
+ border-bottom:1px solid rgba(0,245,255,0.15);
+ font-size:0.68rem; letter-spacing:1.5px; color:rgba(224,248,255,0.5); flex-wrap:wrap; }
+ .nav-hints kbd { display:inline-block; background:rgba(0,245,255,0.1);
+ border:1px solid rgba(0,245,255,0.35); border-radius:4px; padding:1px 5px;
+ color:var(--neon-cyan); font-family:monospace; font-size:0.72rem; margin:0 2px; }
+ .post-modal { display:none; position:fixed; inset:0; z-index:100;
+ background:rgba(11,0,26,0.95); backdrop-filter:blur(16px);
+ overflow-y:auto; padding:40px; }
+ .post-modal.active { display:block; }
+ .modal-inner { max-width:800px; margin:0 auto; background:rgba(20,5,45,0.98);
+ border:2px solid transparent;
+ border-image:linear-gradient(45deg,var(--neon-yellow),var(--neon-magenta)) 1;
+ border-radius:24px; padding:40px; box-shadow:0 0 80px rgba(255,231,0,0.4); }
+ .modal-close { float:right; background:none; border:none; color:var(--neon-cyan);
+ font-size:1.4rem; cursor:pointer; font-family:'Orbitron',sans-serif; }
+ @media(max-width:640px) {
+ .logo-title h1 { font-size:1.6rem; } #sn-logo { width:44px; height:44px; }
+ .post { padding:22px; margin-bottom:22px; } .content { padding:20px; }
+ header { padding:14px 20px; } .transmit-btn { padding:9px 16px; font-size:0.8rem; }
+ .nav-hints { display:none; } .modal-inner { padding:24px 16px; }
+ }
+ [data-sno-theme="neon"] .splash-overlay {
+ background: radial-gradient(ellipse 120% 80% at 50% 35%, rgba(0,245,255,0.14) 0%, transparent 55%),
+ radial-gradient(ellipse 90% 55% at 75% 85%, rgba(255,0,204,0.12) 0%, transparent 50%),
+ #0b001a;
+ }
+ [data-sno-theme="neon"] .splash-deco {
+ width:100px; height:100px; margin:0 auto 1.25rem; border-radius:50%;
+ border:3px solid var(--neon-cyan); box-shadow:0 0 36px var(--neon-cyan), inset 0 0 26px rgba(0,245,255,0.15);
+ animation: splashNeonSpin 5s linear infinite;
+ }
+ @keyframes splashNeonSpin { to { transform: rotate(360deg); } }
+ [data-sno-theme="neon"] .splash-title {
+ font-size: clamp(1.5rem, 5vw, 2.35rem);
+ animation: splashNeonPulse 2s ease-in-out infinite alternate;
+ }
+ @keyframes splashNeonPulse {
+ from { text-shadow: 0 0 12px var(--neon-cyan), 0 0 24px rgba(255,0,204,0.4); }
+ to { text-shadow: 0 0 26px var(--neon-cyan), 0 0 48px var(--neon-magenta); }
+ }
+ [data-sno-theme="neon"] .splash-tag { color: var(--neon-yellow); }
+ [data-sno-theme="neon"] .splash-hint { color: rgba(224,248,255,0.9); font-family: 'Orbitron', sans-serif; }
+ [data-sno-theme="neon"] .splash-inner { text-shadow: 0 2px 24px rgba(0,0,0,0.85), 0 0 40px rgba(11,0,26,0.9); }
diff --git a/internal/generator/templates/themes/neon/theme.js b/internal/generator/templates/themes/neon/theme.js
new file mode 100644
index 0000000..5247c76
--- /dev/null
+++ b/internal/generator/templates/themes/neon/theme.js
@@ -0,0 +1,155 @@
+
+ (function(){
+ if(document.documentElement.classList.contains('sno-splash-skip'))return;
+ var cv=document.getElementById('splash-gl-canvas');
+ if(!cv||typeof THREE==='undefined')return;
+ var raf,ren,sc,ca,g=new THREE.Group(),t0=performance.now();
+ function cleanup(){window.removeEventListener('resize',sz);if(raf)cancelAnimationFrame(raf);raf=null;if(ren){ren.dispose();}ren=null;window._snonuxSplashWebGLCleanup=null;}
+ window._snonuxSplashWebGLCleanup=cleanup;
+ function sz(){var w=cv.clientWidth||2,h=cv.clientHeight||2;if(ren)ren.setSize(w,h,false);if(ca){ca.aspect=w/h;ca.updateProjectionMatrix();}}
+ ren=new THREE.WebGLRenderer({canvas:cv,antialias:true,alpha:true});
+ ren.setClearColor(0,0);ren.setPixelRatio(Math.min(window.devicePixelRatio||1,2));
+ sc=new THREE.Scene();ca=new THREE.PerspectiveCamera(52,1,0.1,100);ca.position.set(0,0.4,9);
+ var cols=[0x00f5ff,0xff00cc,0xffe700],i,m;
+ for(i=0;i<3;i++){m=new THREE.Mesh(new THREE.TorusGeometry(1.55+i*0.48,0.055,8,48),new THREE.MeshBasicMaterial({color:cols[i],transparent:true,opacity:0.92}));m.rotation.x=Math.PI/2;m.userData.sp=0.01+i*0.004;g.add(m);}
+ g.add(new THREE.Mesh(new THREE.SphereGeometry(0.52,20,20),new THREE.MeshBasicMaterial({color:0xffe700,transparent:true,opacity:0.95})));
+ sc.add(g);sz();window.addEventListener('resize',sz);
+ function loop(now){raf=requestAnimationFrame(loop);var t=(now-t0)*0.001;g.rotation.y=t*0.42;g.rotation.x=Math.sin(t*0.65)*0.12;g.children.forEach(function(c){if(c.userData.sp)c.rotation.z+=c.userData.sp;});ren.render(sc,ca);}
+ raf=requestAnimationFrame(loop);
+ })();
+
+
+ // Three.js neon nexus scene — central orb, orbiting rings, particle field.
+ let scene, camera, renderer, centralSphere, rings = [], particles;
+ function initThree() {
+ const canvas = document.getElementById('three-canvas');
+ renderer = new THREE.WebGLRenderer({ canvas, antialias:true, alpha:true });
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
+ scene = new THREE.Scene();
+ scene.fog = new THREE.Fog(0x0b001a, 15, 80);
+ camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 200);
+ camera.position.set(0, 12, 35);
+ scene.add(new THREE.AmbientLight(0x00f5ff, 0.8));
+ const coreLight = new THREE.PointLight(0xff00cc, 4, 100);
+ coreLight.position.set(0,0,0); scene.add(coreLight);
+ centralSphere = new THREE.Mesh(new THREE.SphereGeometry(6,64,64),
+ new THREE.MeshPhongMaterial({color:0x00f5ff,emissive:0xff00cc,emissiveIntensity:1.8,
+ shininess:100,transparent:true,opacity:0.95}));
+ scene.add(centralSphere);
+ scene.add(new THREE.Mesh(new THREE.SphereGeometry(4.5,64,64),
+ new THREE.MeshBasicMaterial({color:0x00f5ff,transparent:true,opacity:0.4,blending:THREE.AdditiveBlending})));
+ const rc=[0x00f5ff,0xff00cc,0x00f5ff,0xffe700];
+ for(let i=0;i<14;i++){
+ const ring=new THREE.Mesh(new THREE.TorusGeometry(12+i*2.2,0.35,32,128),
+ new THREE.MeshPhongMaterial({color:rc[i%4],emissive:rc[i%4],emissiveIntensity:2.5,
+ shininess:80,transparent:true,opacity:0.9,side:THREE.DoubleSide}));
+ ring.rotation.x=Math.random()*Math.PI;
+ ring.userData={speed:0.008+i*0.003,axisTilt:Math.random()*0.6};
+ scene.add(ring); rings.push(ring);
+ }
+ const pCount=2200,pos=new Float32Array(pCount*3),col=new Float32Array(pCount*3);
+ for(let i=0;i<pCount*3;i+=3){
+ const r=30+Math.random()*40,t=Math.random()*Math.PI*2,p=Math.acos(2*Math.random()-1);
+ pos[i]=r*Math.sin(p)*Math.cos(t);pos[i+1]=r*Math.sin(p)*Math.sin(t);pos[i+2]=r*Math.cos(p);
+ const c=new THREE.Color().setHSL(Math.random()>0.5?0.55:0.8,1,1);
+ col[i]=c.r;col[i+1]=c.g;col[i+2]=c.b;
+ }
+ const pg=new THREE.BufferGeometry();
+ pg.setAttribute('position',new THREE.BufferAttribute(pos,3));
+ pg.setAttribute('color',new THREE.BufferAttribute(col,3));
+ particles=new THREE.Points(pg,new THREE.PointsMaterial(
+ {size:0.22,vertexColors:true,transparent:true,opacity:0.9,blending:THREE.AdditiveBlending}));
+ scene.add(particles);
+ let mouseX=0;
+ window.addEventListener('mousemove',e=>{mouseX=(e.clientX/window.innerWidth)*2-1;});
+ (function animate(){
+ requestAnimationFrame(animate);
+ const time=Date.now()*0.0004;
+ camera.position.x=Math.sin(time)*35+mouseX*6;
+ camera.position.z=Math.cos(time)*35+10;
+ camera.lookAt(0,4,0);
+ centralSphere.rotation.y+=0.003;
+ rings.forEach((ring,i)=>{
+ ring.rotation.y+=ring.userData.speed;
+ ring.rotation.x=Math.sin(time*1.5+i)*ring.userData.axisTilt;
+ });
+ particles.rotation.y+=window._snoNeonWild ? 0.012 : 0.0008;
+ renderer.render(scene,camera);
+ })();
+ }
+ window.addEventListener('resize',()=>{
+ if(!camera||!renderer) return;
+ camera.aspect=window.innerWidth/window.innerHeight;
+ camera.updateProjectionMatrix();
+ renderer.setSize(window.innerWidth,window.innerHeight);
+ });
+ window.onload=initThree;
+
+
+ // Neon nav/wild effects — lightning flash on navigate, ring frenzy on wild
+ (function() {
+ function flash(color, ms) {
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:' + color + ';transition:opacity ' + (ms||180) + 'ms';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity = '0'; setTimeout(function() { d.remove(); }, ms || 180); }, 30);
+ }
+ function fxOverlay(cls, ms) {
+ var ov = document.querySelector('.overlay');
+ if (!ov) return;
+ ov.classList.add(cls);
+ setTimeout(function() { ov.classList.remove(cls); }, ms || 380);
+ }
+ var _wild = false;
+ window.snonuxOpenEffect = function(post) {
+ // Modal burst from center with lightning ring
+ var modal = document.getElementById('post-modal');
+ if (modal) { modal.classList.add('sno-modal-expand'); setTimeout(function() { modal.classList.remove('sno-modal-expand'); }, 420); }
+ // Cyan ring pulse radiating outward
+ var ring = document.createElement('div');
+ var r = post ? post.getBoundingClientRect() : {left: window.innerWidth/2, top: window.innerHeight/2};
+ ring.style.cssText = 'position:fixed;top:' + (r.top+20) + 'px;left:' + (r.left+20) + 'px;z-index:997;pointer-events:none;width:10px;height:10px;border-radius:50%;border:3px solid rgba(0,245,255,0.9);transition:all 0.38s ease,opacity 0.38s';
+ document.body.appendChild(ring);
+ setTimeout(function() { ring.style.transform='scale(35)'; ring.style.opacity='0'; setTimeout(function() { ring.remove(); }, 420); }, 15);
+ };
+ window.snonuxCloseEffect = function() {
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(255,0,204,0.12);transition:opacity 0.18s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 200); }, 15);
+ };
+ window.snonuxNavEffect = function() {
+ flash('rgba(0,245,255,0.22)', 160);
+ fxOverlay('sno-fx-shake', 380);
+ };
+ window.snonuxPageEffect = function() {
+ flash('rgba(255,231,0,0.18)', 140);
+ fxOverlay('sno-fx-zoom', 320);
+ };
+ window.snonuxScrollEffect = function(dir) {
+ var isDown = dir === 'down';
+ var thick = _wild ? '14px' : '5px';
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;left:0;right:0;height:' + thick + ';z-index:9000;pointer-events:none;' +
+ 'background:linear-gradient(90deg,transparent,rgba(0,245,255,0.9),rgba(255,0,204,0.9),transparent);' +
+ (isDown ? 'top:0;' : 'bottom:0;') +
+ 'transition:transform 0.3s ease,opacity 0.3s ease;';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.transform = isDown ? 'translateY(100vh)' : 'translateY(-100vh)'; d.style.opacity='0'; }, 16);
+ setTimeout(function() { d.remove(); }, 380);
+ };
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ // Speed up all rings and particles when wild
+ if (rings && rings.length) {
+ rings.forEach(function(r, i) {
+ r.userData.speed = _wild ? (0.008 + i * 0.003) * 14 : 0.008 + i * 0.003;
+ });
+ }
+ // Store wild state for particle rotation boost in animate loop
+ window._snoNeonWild = _wild;
+ };
+ })();
diff --git a/internal/generator/templates/themes/noir/meta.json b/internal/generator/templates/themes/noir/meta.json
new file mode 100644
index 0000000..2adf2ce
--- /dev/null
+++ b/internal/generator/templates/themes/noir/meta.json
@@ -0,0 +1,7 @@
+{
+ "title": "snonux.foo // NOIR",
+ "header_html": "\u003cdiv class=\"logo\"\u003e\n \u003cspan class=\"logo-mark\"\u003eSN\u003c/span\u003e\n \u003cdiv class=\"logo-title\"\u003e\n \u003ch1\u003esnonux.foo\u003c/h1\u003e\n \u003cp class=\"subtitle\"\u003emicroblog \u0026mdash; \u003ca href=\"https://foo.zone\"\u003efoo.zone\u003c/a\u003e is the real blog\u003c/p\u003e\n \u003cp class=\"logo-host\"\u003eServed by NetBSD on a Raspberry Pi 3\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"nav\"\u003e\n \u003ca href=\"atom.xml\" class=\"header-feed-link\" rel=\"alternate\" title=\"Atom feed\" type=\"application/atom+xml\"\u003eAtom feed\u003c/a\u003e\n \u003ca href=\"https://foo.zone/about\" class=\"transmit-btn\"\u003eCase File\u003c/a\u003e\n \u003c/div\u003e",
+ "splash_inner_html": "\u003ccanvas class=\"splash-gl-canvas\" id=\"splash-gl-canvas\" aria-hidden=\"true\"\u003e\u003c/canvas\u003e\n \u003cdiv class=\"splash-blinds\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-city\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-sign\" aria-hidden=\"true\"\u003eVacancy\u003c/div\u003e\n \u003cdiv class=\"splash-inner\"\u003e\n \u003cdiv class=\"splash-title\"\u003esnonux.foo\u003c/div\u003e\n \u003cdiv class=\"splash-tag\"\u003eMidnight Edition\u003c/div\u003e\n \u003cdiv class=\"splash-hint\"\u003eClick or Enter to step under the streetlamp\u003c/div\u003e\n \u003c/div\u003e",
+ "prev_page_text": "\u0026larr; Newer",
+ "next_page_text": "Older \u0026rarr;"
+}
diff --git a/internal/generator/templates/themes/noir/theme.css b/internal/generator/templates/themes/noir/theme.css
new file mode 100644
index 0000000..6fe8cdd
--- /dev/null
+++ b/internal/generator/templates/themes/noir/theme.css
@@ -0,0 +1,77 @@
+ :root { --fog:#0b0b0b; --ink:#d8d1c4; --silver:#a4a09a; --street:#161616; --lamp:#f0ead6; --blood:#a9372b; }
+ * { margin:0; padding:0; box-sizing:border-box; }
+ body { font-family:'IBM Plex Mono','Courier New',monospace; background:#050505; color:var(--ink); overflow:hidden; height:100vh; }
+ body::before { content:''; position:fixed; inset:0; z-index:999; pointer-events:none;
+ background:
+ radial-gradient(circle at 50% 50%, rgba(255,255,255,0.05), transparent 60%),
+ repeating-linear-gradient(0deg, rgba(255,255,255,0.015), rgba(255,255,255,0.015) 1px, transparent 1px, transparent 3px);
+ mix-blend-mode:screen; opacity:0.28; }
+ body::after { content:''; position:fixed; inset:0; z-index:998; pointer-events:none;
+ background:
+ linear-gradient(90deg, rgba(255,255,255,0.03), transparent 22%, transparent 78%, rgba(255,255,255,0.03)),
+ radial-gradient(circle at 50% 110%, rgba(255,255,255,0.06) 0%, transparent 35%);
+ mix-blend-mode:screen; opacity:0.42; }
+ #three-canvas { position:fixed; inset:0; width:100%; height:100%; z-index:1; }
+ .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
+ header { padding:16px 26px; background:rgba(5,5,5,0.82); backdrop-filter:blur(10px);
+ border-bottom:1px solid rgba(240,234,214,0.16); display:flex; align-items:center; justify-content:space-between; }
+ .logo { display:flex; align-items:center; gap:14px; }
+ .logo-mark { font-family:'Playfair Display',serif; font-size:2rem; color:var(--lamp); letter-spacing:0.04em; }
+ .logo-mark::after { content:'•'; color:var(--blood); margin-left:8px; text-shadow:0 0 10px rgba(169,55,43,0.7); }
+ .logo-title h1 { font-family:'Playfair Display',serif; font-size:1.6rem; letter-spacing:0.08em; color:var(--lamp); }
+ .logo-title .subtitle { font-size:0.72rem; color:rgba(216,209,196,0.58); margin-top:3px; }
+ .logo-title .subtitle a { color:var(--silver); text-decoration:none; }
+ .logo-title .subtitle a:hover { color:var(--lamp); }
+ .transmit-btn { border:1px solid rgba(240,234,214,0.25); color:var(--lamp); padding:9px 16px;
+ text-decoration:none; font-size:0.78rem; letter-spacing:0.26em; text-transform:uppercase;
+ transition:background 0.18s,color 0.18s,border-color 0.18s; }
+ .transmit-btn:hover { background:var(--lamp); color:#050505; border-color:var(--lamp); }
+ a.header-feed-link { color:var(--silver); }
+ a.header-feed-link:hover { color:var(--lamp); }
+ .nav-hints { background:rgba(7,7,7,0.72); border-bottom:1px solid rgba(240,234,214,0.08); color:rgba(216,209,196,0.4);
+ padding:5px 26px; display:flex; gap:18px; font-size:0.67rem; letter-spacing:0.08em; flex-wrap:wrap; }
+ .nav-hints kbd { background:#111; border:1px solid rgba(240,234,214,0.18); color:var(--lamp); padding:0 5px; margin:0 2px; }
+ .content { flex:1; overflow-y:auto; padding:20px 26px; scrollbar-width:thin; scrollbar-color:#5a5a5a #121212; }
+ .page-nav { display:flex; justify-content:center; margin:14px 0; }
+ .page-nav a { border:1px solid rgba(240,234,214,0.18); color:var(--lamp); padding:8px 18px; text-decoration:none; font-size:0.78rem; letter-spacing:0.22em; text-transform:uppercase; }
+ .page-nav a:hover { background:rgba(240,234,214,0.08); }
+ .page-nav-footer { flex-shrink:0; padding:8px 26px; display:flex; justify-content:center;
+ background:rgba(5,5,5,0.82); backdrop-filter:blur(10px); border-top:1px solid rgba(240,234,214,0.16); }
+ .post { background:linear-gradient(180deg, rgba(16,16,16,0.94), rgba(8,8,8,0.92)); border:1px solid rgba(255,255,255,0.06);
+ padding:20px; margin-bottom:14px; cursor:pointer; box-shadow:0 10px 28px rgba(0,0,0,0.32); transition:border-color 0.2s,transform 0.2s,box-shadow 0.2s; }
+ .post:hover { border-color:rgba(240,234,214,0.18); transform:translateY(-1px); box-shadow:0 18px 34px rgba(0,0,0,0.42); }
+ .post-active { border-color:rgba(240,234,214,0.35) !important; background:linear-gradient(180deg, rgba(24,24,24,0.96), rgba(10,10,10,0.95)) !important;
+ box-shadow:0 0 0 1px rgba(240,234,214,0.12), 0 18px 38px rgba(0,0,0,0.5), inset 4px 0 0 var(--lamp) !important; }
+ .post-header { display:flex; justify-content:space-between; margin-bottom:12px; font-size:0.84rem; }
+ .post-header strong { color:var(--lamp); }
+ .post-time { color:var(--silver); }
+ .post-text { line-height:1.72; font-size:0.92rem; color:var(--ink); }
+ .post-text a { color:var(--lamp); text-decoration:none; border-bottom:1px solid rgba(240,234,214,0.18); }
+ .post-text a:hover { border-color:rgba(240,234,214,0.55); }
+ .post-image { margin-top:10px; border:1px solid rgba(255,255,255,0.08); filter:grayscale(1) contrast(1.06); }
+ .post-audio { width:100%; margin-top:10px; filter:grayscale(1) contrast(0.9); }
+ .post-modal { display:none; position:fixed; inset:0; z-index:100; overflow-y:auto; padding:40px 20px; }
+ .post-modal.active { display:block; }
+ .modal-inner { max-width:760px; margin:0 auto; background:rgba(10,10,10,0.98); border:1px solid rgba(240,234,214,0.22);
+ padding:38px; box-shadow:0 28px 80px rgba(0,0,0,0.72); }
+ .modal-close { float:right; background:none; border:none; color:var(--lamp); font-family:'IBM Plex Mono',monospace; font-size:0.82rem; cursor:pointer; letter-spacing:0.2em; }
+ @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 16px;} .content{padding:14px 16px;} .modal-inner{padding:24px 16px;} }
+ [data-sno-theme="noir"] .splash-overlay {
+ background:
+ radial-gradient(ellipse 40% 65% at 52% 24%, rgba(240,234,214,0.2) 0%, rgba(240,234,214,0.06) 26%, transparent 58%),
+ linear-gradient(180deg, #080808 0%, #020202 100%);
+ }
+ [data-sno-theme="noir"] .splash-blinds { position:absolute; inset:0; background:repeating-linear-gradient(180deg, rgba(0,0,0,0.82) 0 22px, rgba(255,255,255,0.03) 22px 24px); opacity:0.34; z-index:1; }
+ [data-sno-theme="noir"] .splash-city { position:absolute; left:0; right:0; bottom:0; height:28vh; z-index:1;
+ background:
+ linear-gradient(90deg, transparent 0 6%, #060606 6% 12%, transparent 12% 16%, #090909 16% 24%, transparent 24% 29%, #050505 29% 38%, transparent 38% 42%, #0a0a0a 42% 53%, transparent 53% 58%, #060606 58% 68%, transparent 68% 73%, #0a0a0a 73% 82%, transparent 82% 87%, #080808 87% 96%, transparent 96%),
+ linear-gradient(180deg, transparent, rgba(0,0,0,0.9));
+ opacity:0.86; }
+ [data-sno-theme="noir"] .splash-sign { position:absolute; right:18%; top:22%; width:96px; height:28px; border:1px solid rgba(169,55,43,0.5); color:#ffd7d1; display:flex; align-items:center; justify-content:center;
+ font-size:0.62rem; letter-spacing:0.26em; text-transform:uppercase; background:rgba(169,55,43,0.14); box-shadow:0 0 16px rgba(169,55,43,0.34), inset 0 0 12px rgba(169,55,43,0.22); z-index:1;
+ animation:noirSignFlicker 2.7s steps(2) infinite; }
+ @keyframes noirSignFlicker { 0%,100%{opacity:0.92} 8%{opacity:0.25} 10%{opacity:0.96} 52%{opacity:0.62} 54%{opacity:0.95} }
+ [data-sno-theme="noir"] .splash-title { font-family:'Playfair Display',serif; font-size:clamp(1.7rem,5vw,2.5rem); color:var(--lamp); letter-spacing:0.08em; }
+ [data-sno-theme="noir"] .splash-tag { color:var(--silver); letter-spacing:0.24em; }
+ [data-sno-theme="noir"] .splash-hint { color:rgba(216,209,196,0.78); }
+ [data-sno-theme="noir"] .splash-inner { text-shadow:0 3px 22px rgba(0,0,0,0.95); }
diff --git a/internal/generator/templates/themes/noir.tmpl b/internal/generator/templates/themes/noir/theme.js
index 2d08979..eeff487 100644
--- a/internal/generator/templates/themes/noir.tmpl
+++ b/internal/generator/templates/themes/noir/theme.js
@@ -1,108 +1,4 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>snonux.foo // NOIR</title>
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
- <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;700&family=Playfair+Display:wght@600;700&display=swap" rel="stylesheet">
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
- <style>
- :root { --fog:#0b0b0b; --ink:#d8d1c4; --silver:#a4a09a; --street:#161616; --lamp:#f0ead6; --blood:#a9372b; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:'IBM Plex Mono','Courier New',monospace; background:#050505; color:var(--ink); overflow:hidden; height:100vh; }
- body::before { content:''; position:fixed; inset:0; z-index:999; pointer-events:none;
- background:
- radial-gradient(circle at 50% 50%, rgba(255,255,255,0.05), transparent 60%),
- repeating-linear-gradient(0deg, rgba(255,255,255,0.015), rgba(255,255,255,0.015) 1px, transparent 1px, transparent 3px);
- mix-blend-mode:screen; opacity:0.28; }
- body::after { content:''; position:fixed; inset:0; z-index:998; pointer-events:none;
- background:
- linear-gradient(90deg, rgba(255,255,255,0.03), transparent 22%, transparent 78%, rgba(255,255,255,0.03)),
- radial-gradient(circle at 50% 110%, rgba(255,255,255,0.06) 0%, transparent 35%);
- mix-blend-mode:screen; opacity:0.42; }
- #three-canvas { position:fixed; inset:0; width:100%; height:100%; z-index:1; }
- .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
- header { padding:16px 26px; background:rgba(5,5,5,0.82); backdrop-filter:blur(10px);
- border-bottom:1px solid rgba(240,234,214,0.16); display:flex; align-items:center; justify-content:space-between; }
- .logo { display:flex; align-items:center; gap:14px; }
- .logo-mark { font-family:'Playfair Display',serif; font-size:2rem; color:var(--lamp); letter-spacing:0.04em; }
- .logo-mark::after { content:'•'; color:var(--blood); margin-left:8px; text-shadow:0 0 10px rgba(169,55,43,0.7); }
- .logo-title h1 { font-family:'Playfair Display',serif; font-size:1.6rem; letter-spacing:0.08em; color:var(--lamp); }
- .logo-title .subtitle { font-size:0.72rem; color:rgba(216,209,196,0.58); margin-top:3px; }
- .logo-title .subtitle a { color:var(--silver); text-decoration:none; }
- .logo-title .subtitle a:hover { color:var(--lamp); }
- .transmit-btn { border:1px solid rgba(240,234,214,0.25); color:var(--lamp); padding:9px 16px;
- text-decoration:none; font-size:0.78rem; letter-spacing:0.26em; text-transform:uppercase;
- transition:background 0.18s,color 0.18s,border-color 0.18s; }
- .transmit-btn:hover { background:var(--lamp); color:#050505; border-color:var(--lamp); }
- a.header-feed-link { color:var(--silver); }
- a.header-feed-link:hover { color:var(--lamp); }
- .nav-hints { background:rgba(7,7,7,0.72); border-bottom:1px solid rgba(240,234,214,0.08); color:rgba(216,209,196,0.4);
- padding:5px 26px; display:flex; gap:18px; font-size:0.67rem; letter-spacing:0.08em; flex-wrap:wrap; }
- .nav-hints kbd { background:#111; border:1px solid rgba(240,234,214,0.18); color:var(--lamp); padding:0 5px; margin:0 2px; }
- .content { flex:1; overflow-y:auto; padding:20px 26px; scrollbar-width:thin; scrollbar-color:#5a5a5a #121212; }
- .page-nav { display:flex; justify-content:center; margin:14px 0; }
- .page-nav a { border:1px solid rgba(240,234,214,0.18); color:var(--lamp); padding:8px 18px; text-decoration:none; font-size:0.78rem; letter-spacing:0.22em; text-transform:uppercase; }
- .page-nav a:hover { background:rgba(240,234,214,0.08); }
- .page-nav-footer { flex-shrink:0; padding:8px 26px; display:flex; justify-content:center;
- background:rgba(5,5,5,0.82); backdrop-filter:blur(10px); border-top:1px solid rgba(240,234,214,0.16); }
- .post { background:linear-gradient(180deg, rgba(16,16,16,0.94), rgba(8,8,8,0.92)); border:1px solid rgba(255,255,255,0.06);
- padding:20px; margin-bottom:14px; cursor:pointer; box-shadow:0 10px 28px rgba(0,0,0,0.32); transition:border-color 0.2s,transform 0.2s,box-shadow 0.2s; }
- .post:hover { border-color:rgba(240,234,214,0.18); transform:translateY(-1px); box-shadow:0 18px 34px rgba(0,0,0,0.42); }
- .post-active { border-color:rgba(240,234,214,0.35) !important; background:linear-gradient(180deg, rgba(24,24,24,0.96), rgba(10,10,10,0.95)) !important;
- box-shadow:0 0 0 1px rgba(240,234,214,0.12), 0 18px 38px rgba(0,0,0,0.5), inset 4px 0 0 var(--lamp) !important; }
- .post-header { display:flex; justify-content:space-between; margin-bottom:12px; font-size:0.84rem; }
- .post-header strong { color:var(--lamp); }
- .post-time { color:var(--silver); }
- .post-text { line-height:1.72; font-size:0.92rem; color:var(--ink); }
- .post-text a { color:var(--lamp); text-decoration:none; border-bottom:1px solid rgba(240,234,214,0.18); }
- .post-text a:hover { border-color:rgba(240,234,214,0.55); }
- .post-image { margin-top:10px; border:1px solid rgba(255,255,255,0.08); filter:grayscale(1) contrast(1.06); }
- .post-audio { width:100%; margin-top:10px; filter:grayscale(1) contrast(0.9); }
- .post-modal { display:none; position:fixed; inset:0; z-index:100; overflow-y:auto; padding:40px 20px; }
- .post-modal.active { display:block; }
- .modal-inner { max-width:760px; margin:0 auto; background:rgba(10,10,10,0.98); border:1px solid rgba(240,234,214,0.22);
- padding:38px; box-shadow:0 28px 80px rgba(0,0,0,0.72); }
- .modal-close { float:right; background:none; border:none; color:var(--lamp); font-family:'IBM Plex Mono',monospace; font-size:0.82rem; cursor:pointer; letter-spacing:0.2em; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 16px;} .content{padding:14px 16px;} .modal-inner{padding:24px 16px;} }
- .splash-overlay.splash-noir {
- background:
- radial-gradient(ellipse 40% 65% at 52% 24%, rgba(240,234,214,0.2) 0%, rgba(240,234,214,0.06) 26%, transparent 58%),
- linear-gradient(180deg, #080808 0%, #020202 100%);
- }
- .splash-noir .splash-blinds { position:absolute; inset:0; background:repeating-linear-gradient(180deg, rgba(0,0,0,0.82) 0 22px, rgba(255,255,255,0.03) 22px 24px); opacity:0.34; z-index:1; }
- .splash-noir .splash-city { position:absolute; left:0; right:0; bottom:0; height:28vh; z-index:1;
- background:
- linear-gradient(90deg, transparent 0 6%, #060606 6% 12%, transparent 12% 16%, #090909 16% 24%, transparent 24% 29%, #050505 29% 38%, transparent 38% 42%, #0a0a0a 42% 53%, transparent 53% 58%, #060606 58% 68%, transparent 68% 73%, #0a0a0a 73% 82%, transparent 82% 87%, #080808 87% 96%, transparent 96%),
- linear-gradient(180deg, transparent, rgba(0,0,0,0.9));
- opacity:0.86; }
- .splash-noir .splash-sign { position:absolute; right:18%; top:22%; width:96px; height:28px; border:1px solid rgba(169,55,43,0.5); color:#ffd7d1; display:flex; align-items:center; justify-content:center;
- font-size:0.62rem; letter-spacing:0.26em; text-transform:uppercase; background:rgba(169,55,43,0.14); box-shadow:0 0 16px rgba(169,55,43,0.34), inset 0 0 12px rgba(169,55,43,0.22); z-index:1;
- animation:noirSignFlicker 2.7s steps(2) infinite; }
- @keyframes noirSignFlicker { 0%,100%{opacity:0.92} 8%{opacity:0.25} 10%{opacity:0.96} 52%{opacity:0.62} 54%{opacity:0.95} }
- .splash-noir .splash-title { font-family:'Playfair Display',serif; font-size:clamp(1.7rem,5vw,2.5rem); color:var(--lamp); letter-spacing:0.08em; }
- .splash-noir .splash-tag { color:var(--silver); letter-spacing:0.24em; }
- .splash-noir .splash-hint { color:rgba(216,209,196,0.78); }
- .splash-noir .splash-inner { text-shadow:0 3px 22px rgba(0,0,0,0.95); }
-{{template "navSharedCSSInner"}}
- </style>
-</head>
-<body>
- {{template "splashGate"}}
- <div id="splash-overlay" class="splash-overlay splash-noir" role="dialog" aria-modal="true" aria-label="Open microblog" tabindex="-1">
- <canvas class="splash-gl-canvas" id="splash-gl-canvas" aria-hidden="true"></canvas>
- <div class="splash-blinds" aria-hidden="true"></div>
- <div class="splash-city" aria-hidden="true"></div>
- <div class="splash-sign" aria-hidden="true">Vacancy</div>
- <div class="splash-inner">
- <div class="splash-title">snonux.foo</div>
- <div class="splash-tag">Midnight Edition</div>
- <div class="splash-hint">Click or Enter to step under the streetlamp</div>
- </div>
- </div>
- <script>
+
(function(){
if(document.documentElement.classList.contains('sno-splash-skip'))return;
var cv=document.getElementById('splash-gl-canvas');
@@ -130,46 +26,8 @@
pos.needsUpdate=true; glow.scale.setScalar(1+Math.sin(t*2.3)*0.05); cone.material.opacity=0.1+Math.sin(t*1.8)*0.03; ren.render(sc,ca); }
raf=requestAnimationFrame(loop);
})();
- </script>
- <canvas id="three-canvas"></canvas>
- <div class="overlay">
- <header>
- <div class="logo">
- <span class="logo-mark">SN</span>
- <div class="logo-title">
- <h1>snonux.foo</h1>
- <p class="subtitle">microblog &mdash; <a href="https://foo.zone">foo.zone</a> is the real blog</p>
- <p class="logo-host">Served by NetBSD on a Raspberry Pi 3</p>
- </div>
- </div>
- <div class="nav">
- <a href="atom.xml" class="header-feed-link" rel="alternate" title="Atom feed" type="application/atom+xml">Atom feed</a>
- <a href="https://foo.zone/about" class="transmit-btn">Case File</a>
- </div>
- </header>
- {{template "navhints" .}}
- <div class="content" id="post-content">
- {{range $i, $post := .Posts}}
- <div class="post" id="post-{{$post.ID}}" data-index="{{$i}}">
- <div class="post-header">
- <div><strong>@snonux</strong></div>
- <div class="post-time">{{$post.FormattedTime}}</div>
- </div>
- <div class="post-text">{{$post.ContentHTML}}</div>
- </div>
- {{end}}
- </div>
- {{if or .PrevPage .NextPage}}
- <footer class="page-nav-footer" aria-label="Pagination">
- <div class="page-nav page-nav-dual">
- {{if .PrevPage}}<a href="{{.PrevPage}}">&larr; Newer</a>{{end}}
- {{if .NextPage}}<a href="{{.NextPage}}">Older &rarr;</a>{{end}}
- </div>
- </footer>
- {{end}}
- </div>
- {{template "navmodal" .}}
- <script>
+
+
(function() {
var _wild = false, _snoTOffset = 0, _snoLastT = 0;
var scene, camera, renderer, clock, rain, leftSweep, rightSweep, street, buildings = [], fogPlanes = [], signPlane, lampHalo;
@@ -302,7 +160,3 @@
if (b) b.classList.toggle('sno-wild-on', _wild);
};
})();
- </script>
- {{template "navscript" .}}
-</body>
-</html>
diff --git a/internal/generator/templates/themes/ocean/meta.json b/internal/generator/templates/themes/ocean/meta.json
new file mode 100644
index 0000000..2b4c2b2
--- /dev/null
+++ b/internal/generator/templates/themes/ocean/meta.json
@@ -0,0 +1,7 @@
+{
+ "title": "snonux.foo ~ OCEAN",
+ "header_html": "\u003cdiv class=\"logo\"\u003e\n \u003cspan class=\"logo-mark\"\u003eSN\u003c/span\u003e\n \u003cdiv class=\"logo-title\"\u003e\n \u003ch1\u003esnonux.foo\u003c/h1\u003e\n \u003cp class=\"subtitle\"\u003emicroblog \u0026mdash; \u003ca href=\"https://foo.zone\"\u003efoo.zone\u003c/a\u003e is the real blog\u003c/p\u003e\n \u003cp class=\"logo-host\"\u003eServed by NetBSD on a Raspberry Pi 3\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"nav\"\u003e\n \u003ca href=\"atom.xml\" class=\"header-feed-link\" rel=\"alternate\" title=\"Atom feed\" type=\"application/atom+xml\"\u003eAtom feed\u003c/a\u003e\n \u003ca href=\"https://foo.zone/about\" class=\"transmit-btn\"\u003eTransmit\u003c/a\u003e\n \u003c/div\u003e",
+ "splash_inner_html": "\u003ccanvas class=\"splash-gl-canvas\" id=\"splash-gl-canvas\" aria-hidden=\"true\"\u003e\u003c/canvas\u003e\n \u003cdiv class=\"splash-inner\"\u003e\n \u003cdiv class=\"splash-wave\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-title\"\u003esnonux.foo\u003c/div\u003e\n \u003cdiv class=\"splash-tag\"\u003eDeep channel\u003c/div\u003e\n \u003cdiv class=\"splash-hint\"\u003eSurface — click or Enter\u003c/div\u003e\n \u003c/div\u003e",
+ "prev_page_text": "\u0026larr; Newer",
+ "next_page_text": "Older \u0026rarr;"
+}
diff --git a/internal/generator/templates/themes/ocean/theme.css b/internal/generator/templates/themes/ocean/theme.css
new file mode 100644
index 0000000..4a6e582
--- /dev/null
+++ b/internal/generator/templates/themes/ocean/theme.css
@@ -0,0 +1,69 @@
+ :root { --teal:#00b4d8; --aqua:#48cae4; --deep:#023e8a; --navy:#03045e; --foam:#caf0f8; }
+ * { margin:0; padding:0; box-sizing:border-box; }
+ body { font-family:'Segoe UI',system-ui,sans-serif; background:var(--navy);
+ color:var(--foam); overflow:hidden; height:100vh; }
+ #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
+ .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
+ header { padding:16px 28px; background:rgba(3,4,94,0.82); backdrop-filter:blur(12px);
+ border-bottom:1px solid rgba(0,180,216,0.3); display:flex; align-items:center; justify-content:space-between; }
+ .logo { display:flex; align-items:center; gap:14px; }
+ .logo-mark { font-size:2rem; font-weight:800; color:var(--aqua); text-shadow:0 0 16px var(--teal); }
+ .logo-title h1 { font-size:1.5rem; font-weight:700; color:var(--foam); letter-spacing:1px; }
+ .logo-title .subtitle { font-size:0.75rem; color:rgba(202,240,248,0.55); margin-top:2px; }
+ .logo-title .subtitle a { color:var(--aqua); text-decoration:none; }
+ .logo-title .subtitle a:hover { text-shadow:0 0 8px var(--teal); }
+ .transmit-btn { border:1px solid var(--teal); color:var(--teal); padding:9px 20px;
+ border-radius:20px; text-decoration:none; font-size:0.85rem; transition:all 0.2s; }
+ .transmit-btn:hover { background:var(--teal); color:var(--navy); }
+ a.header-feed-link { color:var(--aqua); }
+ a.header-feed-link:hover { color:var(--foam); }
+ .nav-hints { background:rgba(3,4,94,0.65); border-bottom:1px solid rgba(0,180,216,0.18);
+ color:rgba(202,240,248,0.45); padding:5px 28px; display:flex; gap:18px;
+ font-size:0.68rem; flex-wrap:wrap; }
+ .nav-hints kbd { background:rgba(0,180,216,0.12); border:1px solid rgba(0,180,216,0.35);
+ color:var(--aqua); border-radius:3px; padding:0 5px; margin:0 2px; }
+ .content { flex:1; overflow-y:auto; padding:20px 28px;
+ scrollbar-width:thin; scrollbar-color:var(--teal) var(--navy); }
+ .page-nav { display:flex; justify-content:center; margin:14px 0; }
+ .page-nav a { border:1px solid var(--deep); color:var(--aqua); padding:8px 20px;
+ border-radius:20px; text-decoration:none; font-size:0.82rem; }
+ .page-nav a:hover { background:var(--teal); color:var(--navy); }
+ .page-nav-footer { flex-shrink:0; padding:8px 28px; display:flex; justify-content:center;
+ background:rgba(3,4,94,0.82); backdrop-filter:blur(12px);
+ border-top:1px solid rgba(0,180,216,0.3); }
+ .post { background:rgba(3,4,94,0.55); border:1px solid rgba(0,180,216,0.22); border-radius:10px;
+ padding:20px; margin-bottom:14px; cursor:pointer;
+ transition:all 0.25s; backdrop-filter:blur(6px); }
+ .post:hover { border-color:var(--teal); box-shadow:0 4px 24px rgba(0,180,216,0.22); transform:translateY(-2px); }
+ .post-active { border-color:var(--aqua) !important; background:rgba(0,100,150,0.55) !important;
+ box-shadow:0 0 22px rgba(72,202,228,0.35),inset 3px 0 0 var(--aqua) !important; }
+ .post-header { display:flex; justify-content:space-between; margin-bottom:12px; font-size:0.88rem; }
+ .post-time { color:var(--teal); font-family:monospace; font-size:0.8rem; }
+ .post-text { line-height:1.65; font-size:0.95rem; }
+ .post-text a { color:var(--aqua); text-decoration:none; }
+ .post-text a:hover { text-shadow:0 0 8px var(--teal); }
+ .post-audio { width:100%; margin-top:10px; }
+ .post-modal { display:none; position:fixed; inset:0; z-index:100;
+ overflow-y:auto; padding:40px 20px; }
+ .post-modal.active { display:block; }
+ .modal-inner { max-width:760px; margin:0 auto; background:rgba(2,30,80,0.92);
+ border:1px solid var(--teal); border-radius:12px; backdrop-filter:blur(16px);
+ box-shadow:0 0 60px rgba(0,180,216,0.3); padding:40px; }
+ .modal-close { float:right; background:none; border:none; color:var(--teal);
+ font-size:0.9rem; cursor:pointer; letter-spacing:1px; }
+ @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 18px;} .content{padding:14px 18px;} }
+ [data-sno-theme="ocean"] .splash-overlay {
+ background: linear-gradient(180deg, var(--navy) 0%, var(--deep) 45%, #001a3d 100%);
+ }
+ [data-sno-theme="ocean"] .splash-wave {
+ width:min(320px,88vw); height:14px; margin:0 auto 1.2rem; border-radius:50%;
+ background: radial-gradient(ellipse at 50% 0%, var(--aqua), transparent 70%);
+ opacity:0.7; animation: splashWaveBob 2.8s ease-in-out infinite;
+ box-shadow: 0 8px 40px rgba(0,180,216,0.35);
+ }
+ @keyframes splashWaveBob { 0%,100%{ transform: translateY(0) scaleX(1); } 50%{ transform: translateY(-6px) scaleX(1.05); } }
+ [data-sno-theme="ocean"] .splash-title { font-size:clamp(1.45rem,4.5vw,2rem); color:var(--foam);
+ text-shadow:0 0 18px var(--teal); }
+ [data-sno-theme="ocean"] .splash-tag { color:var(--aqua); letter-spacing:0.2em; }
+ [data-sno-theme="ocean"] .splash-hint { color:rgba(202,240,248,0.88); }
+ [data-sno-theme="ocean"] .splash-inner { text-shadow: 0 2px 16px rgba(3,4,94,0.9); }
diff --git a/internal/generator/templates/themes/ocean.tmpl b/internal/generator/templates/themes/ocean/theme.js
index 8422398..58d2deb 100644
--- a/internal/generator/templates/themes/ocean.tmpl
+++ b/internal/generator/templates/themes/ocean/theme.js
@@ -1,95 +1,4 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>snonux.foo ~ OCEAN</title>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
- <style>
- :root { --teal:#00b4d8; --aqua:#48cae4; --deep:#023e8a; --navy:#03045e; --foam:#caf0f8; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:'Segoe UI',system-ui,sans-serif; background:var(--navy);
- color:var(--foam); overflow:hidden; height:100vh; }
- #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
- .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
- header { padding:16px 28px; background:rgba(3,4,94,0.82); backdrop-filter:blur(12px);
- border-bottom:1px solid rgba(0,180,216,0.3); display:flex; align-items:center; justify-content:space-between; }
- .logo { display:flex; align-items:center; gap:14px; }
- .logo-mark { font-size:2rem; font-weight:800; color:var(--aqua); text-shadow:0 0 16px var(--teal); }
- .logo-title h1 { font-size:1.5rem; font-weight:700; color:var(--foam); letter-spacing:1px; }
- .logo-title .subtitle { font-size:0.75rem; color:rgba(202,240,248,0.55); margin-top:2px; }
- .logo-title .subtitle a { color:var(--aqua); text-decoration:none; }
- .logo-title .subtitle a:hover { text-shadow:0 0 8px var(--teal); }
- .transmit-btn { border:1px solid var(--teal); color:var(--teal); padding:9px 20px;
- border-radius:20px; text-decoration:none; font-size:0.85rem; transition:all 0.2s; }
- .transmit-btn:hover { background:var(--teal); color:var(--navy); }
- a.header-feed-link { color:var(--aqua); }
- a.header-feed-link:hover { color:var(--foam); }
- .nav-hints { background:rgba(3,4,94,0.65); border-bottom:1px solid rgba(0,180,216,0.18);
- color:rgba(202,240,248,0.45); padding:5px 28px; display:flex; gap:18px;
- font-size:0.68rem; flex-wrap:wrap; }
- .nav-hints kbd { background:rgba(0,180,216,0.12); border:1px solid rgba(0,180,216,0.35);
- color:var(--aqua); border-radius:3px; padding:0 5px; margin:0 2px; }
- .content { flex:1; overflow-y:auto; padding:20px 28px;
- scrollbar-width:thin; scrollbar-color:var(--teal) var(--navy); }
- .page-nav { display:flex; justify-content:center; margin:14px 0; }
- .page-nav a { border:1px solid var(--deep); color:var(--aqua); padding:8px 20px;
- border-radius:20px; text-decoration:none; font-size:0.82rem; }
- .page-nav a:hover { background:var(--teal); color:var(--navy); }
- .page-nav-footer { flex-shrink:0; padding:8px 28px; display:flex; justify-content:center;
- background:rgba(3,4,94,0.82); backdrop-filter:blur(12px);
- border-top:1px solid rgba(0,180,216,0.3); }
- .post { background:rgba(3,4,94,0.55); border:1px solid rgba(0,180,216,0.22); border-radius:10px;
- padding:20px; margin-bottom:14px; cursor:pointer;
- transition:all 0.25s; backdrop-filter:blur(6px); }
- .post:hover { border-color:var(--teal); box-shadow:0 4px 24px rgba(0,180,216,0.22); transform:translateY(-2px); }
- .post-active { border-color:var(--aqua) !important; background:rgba(0,100,150,0.55) !important;
- box-shadow:0 0 22px rgba(72,202,228,0.35),inset 3px 0 0 var(--aqua) !important; }
- .post-header { display:flex; justify-content:space-between; margin-bottom:12px; font-size:0.88rem; }
- .post-time { color:var(--teal); font-family:monospace; font-size:0.8rem; }
- .post-text { line-height:1.65; font-size:0.95rem; }
- .post-text a { color:var(--aqua); text-decoration:none; }
- .post-text a:hover { text-shadow:0 0 8px var(--teal); }
- .post-audio { width:100%; margin-top:10px; }
- .post-modal { display:none; position:fixed; inset:0; z-index:100;
- overflow-y:auto; padding:40px 20px; }
- .post-modal.active { display:block; }
- .modal-inner { max-width:760px; margin:0 auto; background:rgba(2,30,80,0.92);
- border:1px solid var(--teal); border-radius:12px; backdrop-filter:blur(16px);
- box-shadow:0 0 60px rgba(0,180,216,0.3); padding:40px; }
- .modal-close { float:right; background:none; border:none; color:var(--teal);
- font-size:0.9rem; cursor:pointer; letter-spacing:1px; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 18px;} .content{padding:14px 18px;} }
- .splash-overlay.splash-ocean {
- background: linear-gradient(180deg, var(--navy) 0%, var(--deep) 45%, #001a3d 100%);
- }
- .splash-ocean .splash-wave {
- width:min(320px,88vw); height:14px; margin:0 auto 1.2rem; border-radius:50%;
- background: radial-gradient(ellipse at 50% 0%, var(--aqua), transparent 70%);
- opacity:0.7; animation: splashWaveBob 2.8s ease-in-out infinite;
- box-shadow: 0 8px 40px rgba(0,180,216,0.35);
- }
- @keyframes splashWaveBob { 0%,100%{ transform: translateY(0) scaleX(1); } 50%{ transform: translateY(-6px) scaleX(1.05); } }
- .splash-ocean .splash-title { font-size:clamp(1.45rem,4.5vw,2rem); color:var(--foam);
- text-shadow:0 0 18px var(--teal); }
- .splash-ocean .splash-tag { color:var(--aqua); letter-spacing:0.2em; }
- .splash-ocean .splash-hint { color:rgba(202,240,248,0.88); }
- .splash-ocean .splash-inner { text-shadow: 0 2px 16px rgba(3,4,94,0.9); }
-{{template "navSharedCSSInner"}}
- </style>
-</head>
-<body>
- {{template "splashGate"}}
- <div id="splash-overlay" class="splash-overlay splash-ocean" role="dialog" aria-modal="true" aria-label="Open microblog" tabindex="-1">
- <canvas class="splash-gl-canvas" id="splash-gl-canvas" aria-hidden="true"></canvas>
- <div class="splash-inner">
- <div class="splash-wave" aria-hidden="true"></div>
- <div class="splash-title">snonux.foo</div>
- <div class="splash-tag">Deep channel</div>
- <div class="splash-hint">Surface — click or Enter</div>
- </div>
- </div>
- <script>
+
(function(){
if(document.documentElement.classList.contains('sno-splash-skip'))return;
var cv=document.getElementById('splash-gl-canvas');
@@ -108,46 +17,8 @@
g.children.forEach(function(c){if(c.userData.dy){c.position.y+=Math.sin(t*2+c.userData.x)*0.008;c.position.x=c.userData.x+Math.sin(t+c.userData.y0)*0.15;}});ren.render(sc,ca);}
raf=requestAnimationFrame(loop);
})();
- </script>
- <canvas id="three-canvas"></canvas>
- <div class="overlay">
- <header>
- <div class="logo">
- <span class="logo-mark">SN</span>
- <div class="logo-title">
- <h1>snonux.foo</h1>
- <p class="subtitle">microblog &mdash; <a href="https://foo.zone">foo.zone</a> is the real blog</p>
- <p class="logo-host">Served by NetBSD on a Raspberry Pi 3</p>
- </div>
- </div>
- <div class="nav">
- <a href="atom.xml" class="header-feed-link" rel="alternate" title="Atom feed" type="application/atom+xml">Atom feed</a>
- <a href="https://foo.zone/about" class="transmit-btn">Transmit</a>
- </div>
- </header>
- {{template "navhints" .}}
- <div class="content" id="post-content">
- {{range $i, $post := .Posts}}
- <div class="post" id="post-{{$post.ID}}" data-index="{{$i}}">
- <div class="post-header">
- <div><strong>@snonux</strong></div>
- <div class="post-time">{{$post.FormattedTime}}</div>
- </div>
- <div class="post-text">{{$post.ContentHTML}}</div>
- </div>
- {{end}}
- </div>
- {{if or .PrevPage .NextPage}}
- <footer class="page-nav-footer" aria-label="Pagination">
- <div class="page-nav page-nav-dual">
- {{if .PrevPage}}<a href="{{.PrevPage}}">&larr; Newer</a>{{end}}
- {{if .NextPage}}<a href="{{.NextPage}}">Older &rarr;</a>{{end}}
- </div>
- </footer>
- {{end}}
- </div>
- {{template "navmodal" .}}
- <script>
+
+
// Ocean WebGL: dramatic wave surface + sea rock spires + bioluminescent
// jellyfish + rising bubbles + a slow whale cruising the deep.
(function() {
@@ -381,7 +252,3 @@
if (ov) { ov.classList.add('sno-fx-zoom'); setTimeout(function() { ov.classList.remove('sno-fx-zoom'); }, 330); }
};
})();
- </script>
- {{template "navscript" .}}
-</body>
-</html>
diff --git a/internal/generator/templates/themes/plasma/meta.json b/internal/generator/templates/themes/plasma/meta.json
new file mode 100644
index 0000000..42d2d7c
--- /dev/null
+++ b/internal/generator/templates/themes/plasma/meta.json
@@ -0,0 +1,7 @@
+{
+ "title": "snonux.foo ◈ PLASMA",
+ "header_html": "\u003cdiv class=\"logo\"\u003e\n \u003cspan class=\"logo-mark\"\u003eSN\u003c/span\u003e\n \u003cdiv class=\"logo-title\"\u003e\n \u003ch1\u003esnonux.foo\u003c/h1\u003e\n \u003cp class=\"subtitle\"\u003emicroblog \u0026mdash; \u003ca href=\"https://foo.zone\"\u003efoo.zone\u003c/a\u003e is the real blog\u003c/p\u003e\n \u003cp class=\"logo-host\"\u003eServed by NetBSD on a Raspberry Pi 3\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"nav\"\u003e\n \u003ca href=\"atom.xml\" class=\"header-feed-link\" rel=\"alternate\" title=\"Atom feed\" type=\"application/atom+xml\"\u003eAtom feed\u003c/a\u003e\n \u003ca href=\"https://foo.zone/about\" class=\"transmit-btn\"\u003eTransmit\u003c/a\u003e\n \u003c/div\u003e",
+ "splash_inner_html": "\u003ccanvas class=\"splash-gl-canvas\" id=\"splash-gl-canvas\" aria-hidden=\"true\"\u003e\u003c/canvas\u003e\n \u003cdiv class=\"splash-blobs\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-inner\"\u003e\n \u003cdiv class=\"splash-title\"\u003esnonux.foo\u003c/div\u003e\n \u003cdiv class=\"splash-tag\"\u003ePlasma lock\u003c/div\u003e\n \u003cdiv class=\"splash-hint\"\u003eMerge — click or Enter\u003c/div\u003e\n \u003c/div\u003e",
+ "prev_page_text": "\u0026larr; Newer",
+ "next_page_text": "Older \u0026rarr;"
+}
diff --git a/internal/generator/templates/themes/plasma/theme.css b/internal/generator/templates/themes/plasma/theme.css
new file mode 100644
index 0000000..cc36513
--- /dev/null
+++ b/internal/generator/templates/themes/plasma/theme.css
@@ -0,0 +1,78 @@
+ :root { --cyan:#00f0ff; --magenta:#ff00e0; --yellow:#ffee00; --bg:#050008; }
+ * { margin:0; padding:0; box-sizing:border-box; }
+ body { font-family:'Segoe UI',system-ui,sans-serif; background:var(--bg);
+ color:#e8e0ff; overflow:hidden; height:100vh; }
+ #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
+ .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
+ header { padding:16px 28px; background:rgba(5,0,8,0.8); backdrop-filter:blur(14px);
+ border-bottom:1px solid rgba(0,240,255,0.2); display:flex; align-items:center; justify-content:space-between; }
+ .logo { display:flex; align-items:center; gap:14px; }
+ .logo-mark { font-size:2rem; font-weight:800;
+ background:linear-gradient(90deg,var(--cyan),var(--magenta));
+ -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
+ .logo-title h1 { font-size:1.5rem; font-weight:700; color:#e8e0ff; }
+ .logo-title .subtitle { font-size:0.75rem; color:rgba(232,224,255,0.5); margin-top:2px; }
+ .logo-title .subtitle a { color:var(--cyan); text-decoration:none; }
+ .logo-title .subtitle a:hover { text-shadow:0 0 8px var(--cyan); }
+ .transmit-btn { border:1px solid var(--magenta); color:var(--magenta); padding:9px 20px;
+ border-radius:20px; text-decoration:none; font-size:0.85rem; transition:all 0.2s; }
+ .transmit-btn:hover { background:var(--magenta); color:var(--bg); }
+ a.header-feed-link { color:var(--cyan); }
+ a.header-feed-link:hover { color:var(--magenta); }
+ .nav-hints { background:rgba(5,0,8,0.65); border-bottom:1px solid rgba(0,240,255,0.12);
+ color:rgba(232,224,255,0.4); padding:5px 28px; display:flex; gap:18px;
+ font-size:0.68rem; flex-wrap:wrap; }
+ .nav-hints kbd { background:rgba(0,240,255,0.1); border:1px solid rgba(0,240,255,0.3);
+ color:var(--cyan); border-radius:3px; padding:0 5px; margin:0 2px; }
+ .content { flex:1; overflow-y:auto; padding:20px 28px;
+ scrollbar-width:thin; scrollbar-color:var(--magenta) var(--bg); }
+ .page-nav { display:flex; justify-content:center; margin:14px 0; }
+ .page-nav a { border:1px solid var(--cyan); color:var(--cyan); padding:8px 20px;
+ border-radius:20px; text-decoration:none; font-size:0.82rem; }
+ .page-nav a:hover { background:var(--cyan); color:var(--bg); }
+ .page-nav-footer { flex-shrink:0; padding:8px 28px; display:flex; justify-content:center;
+ background:rgba(5,0,8,0.8); backdrop-filter:blur(14px);
+ border-top:1px solid rgba(0,240,255,0.2); }
+ .post { background:rgba(10,0,20,0.75); border:1px solid rgba(0,240,255,0.18); border-radius:10px;
+ padding:20px; margin-bottom:14px; cursor:pointer;
+ transition:all 0.25s; backdrop-filter:blur(6px); }
+ .post:hover { border-color:var(--cyan); box-shadow:0 0 20px rgba(0,240,255,0.2); transform:translateY(-2px); }
+ .post-active { border-color:var(--magenta) !important; background:rgba(20,0,30,0.9) !important;
+ box-shadow:0 0 24px rgba(255,0,224,0.35),inset 3px 0 0 var(--magenta) !important; }
+ .post-header { display:flex; justify-content:space-between; margin-bottom:12px; font-size:0.88rem; }
+ .post-time { color:var(--cyan); font-family:monospace; font-size:0.8rem; }
+ .post-text { line-height:1.65; font-size:0.95rem; }
+ .post-text a { color:var(--cyan); text-decoration:none; }
+ .post-text a:hover { text-shadow:0 0 8px var(--cyan); }
+ .post-audio { width:100%; margin-top:10px; }
+ .post-modal { display:none; position:fixed; inset:0; z-index:100;
+ background:rgba(5,0,8,0.96); backdrop-filter:blur(20px);
+ overflow-y:auto; padding:40px 20px; }
+ .post-modal.active { display:block; }
+ .modal-inner { max-width:760px; margin:0 auto; background:rgba(10,0,25,0.98);
+ border:1px solid var(--magenta); border-radius:12px;
+ box-shadow:0 0 60px rgba(255,0,224,0.25); padding:40px; }
+ .modal-close { float:right; background:none; border:none; color:var(--cyan);
+ font-size:0.9rem; cursor:pointer; letter-spacing:1px; }
+ @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 18px;} .content{padding:14px 18px;} }
+ [data-sno-theme="plasma"] .splash-overlay { background: var(--bg); overflow:hidden; }
+ [data-sno-theme="plasma"] .splash-blobs {
+ position:absolute; width:140%; height:140%; left:-20%; top:-20%; pointer-events:none;
+ background:
+ radial-gradient(ellipse at 30% 40%, rgba(0,240,255,0.25) 0%, transparent 45%),
+ radial-gradient(ellipse at 70% 60%, rgba(255,0,224,0.22) 0%, transparent 50%),
+ radial-gradient(ellipse at 50% 80%, rgba(255,238,0,0.12) 0%, transparent 40%);
+ animation: splashPlasmaDrift 10s ease-in-out infinite alternate;
+ filter: blur(2px);
+ }
+ @keyframes splashPlasmaDrift {
+ from { transform: translate(0,0) rotate(0deg); }
+ to { transform: translate(-4%,3%) rotate(8deg); }
+ }
+ [data-sno-theme="plasma"] .splash-inner { position:relative; z-index:1; }
+ [data-sno-theme="plasma"] .splash-title { font-size:clamp(1.45rem,4.5vw,2rem); color:#e8e0ff;
+ text-shadow:0 0 24px var(--cyan), 0 0 48px rgba(255,0,224,0.35); }
+ [data-sno-theme="plasma"] .splash-tag { color:var(--magenta); letter-spacing:0.18em; }
+ [data-sno-theme="plasma"] .splash-hint { color:rgba(232,224,255,0.86); }
+ [data-sno-theme="plasma"] .splash-blobs { z-index:1; }
+ [data-sno-theme="plasma"] .splash-inner { text-shadow: 0 2px 22px rgba(0,0,0,0.9); }
diff --git a/internal/generator/templates/themes/plasma.tmpl b/internal/generator/templates/themes/plasma/theme.js
index 332d07f..f6912b7 100644
--- a/internal/generator/templates/themes/plasma.tmpl
+++ b/internal/generator/templates/themes/plasma/theme.js
@@ -1,104 +1,4 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>snonux.foo ◈ PLASMA</title>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
- <style>
- :root { --cyan:#00f0ff; --magenta:#ff00e0; --yellow:#ffee00; --bg:#050008; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:'Segoe UI',system-ui,sans-serif; background:var(--bg);
- color:#e8e0ff; overflow:hidden; height:100vh; }
- #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
- .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
- header { padding:16px 28px; background:rgba(5,0,8,0.8); backdrop-filter:blur(14px);
- border-bottom:1px solid rgba(0,240,255,0.2); display:flex; align-items:center; justify-content:space-between; }
- .logo { display:flex; align-items:center; gap:14px; }
- .logo-mark { font-size:2rem; font-weight:800;
- background:linear-gradient(90deg,var(--cyan),var(--magenta));
- -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
- .logo-title h1 { font-size:1.5rem; font-weight:700; color:#e8e0ff; }
- .logo-title .subtitle { font-size:0.75rem; color:rgba(232,224,255,0.5); margin-top:2px; }
- .logo-title .subtitle a { color:var(--cyan); text-decoration:none; }
- .logo-title .subtitle a:hover { text-shadow:0 0 8px var(--cyan); }
- .transmit-btn { border:1px solid var(--magenta); color:var(--magenta); padding:9px 20px;
- border-radius:20px; text-decoration:none; font-size:0.85rem; transition:all 0.2s; }
- .transmit-btn:hover { background:var(--magenta); color:var(--bg); }
- a.header-feed-link { color:var(--cyan); }
- a.header-feed-link:hover { color:var(--magenta); }
- .nav-hints { background:rgba(5,0,8,0.65); border-bottom:1px solid rgba(0,240,255,0.12);
- color:rgba(232,224,255,0.4); padding:5px 28px; display:flex; gap:18px;
- font-size:0.68rem; flex-wrap:wrap; }
- .nav-hints kbd { background:rgba(0,240,255,0.1); border:1px solid rgba(0,240,255,0.3);
- color:var(--cyan); border-radius:3px; padding:0 5px; margin:0 2px; }
- .content { flex:1; overflow-y:auto; padding:20px 28px;
- scrollbar-width:thin; scrollbar-color:var(--magenta) var(--bg); }
- .page-nav { display:flex; justify-content:center; margin:14px 0; }
- .page-nav a { border:1px solid var(--cyan); color:var(--cyan); padding:8px 20px;
- border-radius:20px; text-decoration:none; font-size:0.82rem; }
- .page-nav a:hover { background:var(--cyan); color:var(--bg); }
- .page-nav-footer { flex-shrink:0; padding:8px 28px; display:flex; justify-content:center;
- background:rgba(5,0,8,0.8); backdrop-filter:blur(14px);
- border-top:1px solid rgba(0,240,255,0.2); }
- .post { background:rgba(10,0,20,0.75); border:1px solid rgba(0,240,255,0.18); border-radius:10px;
- padding:20px; margin-bottom:14px; cursor:pointer;
- transition:all 0.25s; backdrop-filter:blur(6px); }
- .post:hover { border-color:var(--cyan); box-shadow:0 0 20px rgba(0,240,255,0.2); transform:translateY(-2px); }
- .post-active { border-color:var(--magenta) !important; background:rgba(20,0,30,0.9) !important;
- box-shadow:0 0 24px rgba(255,0,224,0.35),inset 3px 0 0 var(--magenta) !important; }
- .post-header { display:flex; justify-content:space-between; margin-bottom:12px; font-size:0.88rem; }
- .post-time { color:var(--cyan); font-family:monospace; font-size:0.8rem; }
- .post-text { line-height:1.65; font-size:0.95rem; }
- .post-text a { color:var(--cyan); text-decoration:none; }
- .post-text a:hover { text-shadow:0 0 8px var(--cyan); }
- .post-audio { width:100%; margin-top:10px; }
- .post-modal { display:none; position:fixed; inset:0; z-index:100;
- background:rgba(5,0,8,0.96); backdrop-filter:blur(20px);
- overflow-y:auto; padding:40px 20px; }
- .post-modal.active { display:block; }
- .modal-inner { max-width:760px; margin:0 auto; background:rgba(10,0,25,0.98);
- border:1px solid var(--magenta); border-radius:12px;
- box-shadow:0 0 60px rgba(255,0,224,0.25); padding:40px; }
- .modal-close { float:right; background:none; border:none; color:var(--cyan);
- font-size:0.9rem; cursor:pointer; letter-spacing:1px; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 18px;} .content{padding:14px 18px;} }
- .splash-overlay.splash-plasma { background: var(--bg); overflow:hidden; }
- .splash-plasma .splash-blobs {
- position:absolute; width:140%; height:140%; left:-20%; top:-20%; pointer-events:none;
- background:
- radial-gradient(ellipse at 30% 40%, rgba(0,240,255,0.25) 0%, transparent 45%),
- radial-gradient(ellipse at 70% 60%, rgba(255,0,224,0.22) 0%, transparent 50%),
- radial-gradient(ellipse at 50% 80%, rgba(255,238,0,0.12) 0%, transparent 40%);
- animation: splashPlasmaDrift 10s ease-in-out infinite alternate;
- filter: blur(2px);
- }
- @keyframes splashPlasmaDrift {
- from { transform: translate(0,0) rotate(0deg); }
- to { transform: translate(-4%,3%) rotate(8deg); }
- }
- .splash-plasma .splash-inner { position:relative; z-index:1; }
- .splash-plasma .splash-title { font-size:clamp(1.45rem,4.5vw,2rem); color:#e8e0ff;
- text-shadow:0 0 24px var(--cyan), 0 0 48px rgba(255,0,224,0.35); }
- .splash-plasma .splash-tag { color:var(--magenta); letter-spacing:0.18em; }
- .splash-plasma .splash-hint { color:rgba(232,224,255,0.86); }
- .splash-plasma .splash-blobs { z-index:1; }
- .splash-plasma .splash-inner { text-shadow: 0 2px 22px rgba(0,0,0,0.9); }
-{{template "navSharedCSSInner"}}
- </style>
-</head>
-<body>
- {{template "splashGate"}}
- <div id="splash-overlay" class="splash-overlay splash-plasma" role="dialog" aria-modal="true" aria-label="Open microblog" tabindex="-1">
- <canvas class="splash-gl-canvas" id="splash-gl-canvas" aria-hidden="true"></canvas>
- <div class="splash-blobs" aria-hidden="true"></div>
- <div class="splash-inner">
- <div class="splash-title">snonux.foo</div>
- <div class="splash-tag">Plasma lock</div>
- <div class="splash-hint">Merge — click or Enter</div>
- </div>
- </div>
- <script>
+
(function(){
if(document.documentElement.classList.contains('sno-splash-skip'))return;
var cv=document.getElementById('splash-gl-canvas');
@@ -117,46 +17,8 @@
ren.render(sc,ca);}
raf=requestAnimationFrame(loop);
})();
- </script>
- <canvas id="three-canvas"></canvas>
- <div class="overlay">
- <header>
- <div class="logo">
- <span class="logo-mark">SN</span>
- <div class="logo-title">
- <h1>snonux.foo</h1>
- <p class="subtitle">microblog &mdash; <a href="https://foo.zone">foo.zone</a> is the real blog</p>
- <p class="logo-host">Served by NetBSD on a Raspberry Pi 3</p>
- </div>
- </div>
- <div class="nav">
- <a href="atom.xml" class="header-feed-link" rel="alternate" title="Atom feed" type="application/atom+xml">Atom feed</a>
- <a href="https://foo.zone/about" class="transmit-btn">Transmit</a>
- </div>
- </header>
- {{template "navhints" .}}
- <div class="content" id="post-content">
- {{range $i, $post := .Posts}}
- <div class="post" id="post-{{$post.ID}}" data-index="{{$i}}">
- <div class="post-header">
- <div><strong>@snonux</strong></div>
- <div class="post-time">{{$post.FormattedTime}}</div>
- </div>
- <div class="post-text">{{$post.ContentHTML}}</div>
- </div>
- {{end}}
- </div>
- {{if or .PrevPage .NextPage}}
- <footer class="page-nav-footer" aria-label="Pagination">
- <div class="page-nav page-nav-dual">
- {{if .PrevPage}}<a href="{{.PrevPage}}">&larr; Newer</a>{{end}}
- {{if .NextPage}}<a href="{{.NextPage}}">Older &rarr;</a>{{end}}
- </div>
- </footer>
- {{end}}
- </div>
- {{template "navmodal" .}}
- <script>
+
+
// Plasma WebGL: 12 large translucent spheres drifting on independent sine
// paths with additive blending — overlapping blobs mix colours and pulse
// like a lava lamp or plasma ball. Dark bg, cyan/magenta/yellow palette.
@@ -300,7 +162,3 @@
if (ov) { ov.classList.add('sno-fx-zoom'); setTimeout(function() { ov.classList.remove('sno-fx-zoom'); }, 330); }
};
})();
- </script>
- {{template "navscript" .}}
-</body>
-</html>
diff --git a/internal/generator/templates/themes/retro/meta.json b/internal/generator/templates/themes/retro/meta.json
new file mode 100644
index 0000000..8a14c24
--- /dev/null
+++ b/internal/generator/templates/themes/retro/meta.json
@@ -0,0 +1,7 @@
+{
+ "title": "SNONUX.FOO // RETRO",
+ "header_html": "\u003cdiv class=\"logo\"\u003e\n \u003cspan class=\"logo-mark\"\u003e[SN]\u003c/span\u003e\n \u003cdiv class=\"logo-title\"\u003e\n \u003ch1\u003eSNONUX.FOO\u003c/h1\u003e\n \u003cp class=\"subtitle\"\u003eMICROBLOG / \u003ca href=\"https://foo.zone\"\u003eFOO.ZONE\u003c/a\u003e IS THE REAL BLOG\u003c/p\u003e\n \u003cp class=\"logo-host\"\u003eServed by NetBSD on a Raspberry Pi 3\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"nav\"\u003e\n \u003ca href=\"atom.xml\" class=\"header-feed-link\" rel=\"alternate\" title=\"Atom feed\" type=\"application/atom+xml\"\u003eAtom feed\u003c/a\u003e\n \u003ca href=\"https://foo.zone/about\" class=\"transmit-btn\"\u003eTRANSMIT\u003c/a\u003e\n \u003c/div\u003e",
+ "splash_inner_html": "\u003ccanvas class=\"splash-gl-canvas\" id=\"splash-gl-canvas\" aria-hidden=\"true\"\u003e\u003c/canvas\u003e\n \u003cdiv class=\"splash-inner\"\u003e\n \u003cdiv class=\"splash-title\"\u003e*** SNONUX BBS ***\u003c/div\u003e\n \u003cdiv class=\"splash-tag\"\u003eAmber phosphor mode\u003c/div\u003e\n \u003cdiv class=\"splash-hint\"\u003ePress Enter or click to connect\u003c/div\u003e\n \u003c/div\u003e",
+ "prev_page_text": "\u0026lt;-- NEWER",
+ "next_page_text": "OLDER --\u0026gt;"
+}
diff --git a/internal/generator/templates/themes/retro/theme.css b/internal/generator/templates/themes/retro/theme.css
new file mode 100644
index 0000000..cf0e235
--- /dev/null
+++ b/internal/generator/templates/themes/retro/theme.css
@@ -0,0 +1,79 @@
+ :root { --amber:#ffb000; --dim:#7a5200; --bg:#0a0800; --bg2:#050300; }
+ * { margin:0; padding:0; box-sizing:border-box; }
+ body { font-family:'Courier New',Courier,monospace; background:var(--bg); color:var(--amber);
+ overflow:hidden; height:100vh; }
+ /* Phosphor scanlines overlay — sits above WebGL */
+ body::before { content:''; position:fixed; inset:0; z-index:999; pointer-events:none;
+ background:repeating-linear-gradient(0deg,transparent,transparent 2px,
+ rgba(0,0,0,0.15) 2px,rgba(0,0,0,0.15) 4px); }
+ /* Subtle glow flicker */
+ @keyframes amber-flicker { 0%,100%{opacity:1} 94%{opacity:0.98} 96%{opacity:0.93} }
+ body { animation:amber-flicker 11s infinite; }
+ /* WebGL background canvas */
+ #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
+ .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
+ header { padding:12px 24px; background:var(--bg2); border-bottom:2px solid var(--amber);
+ display:flex; align-items:center; justify-content:space-between; }
+ .logo { display:flex; align-items:center; gap:14px; }
+ .logo-mark { font-size:1.6rem; color:var(--amber); text-shadow:0 0 14px var(--amber);
+ letter-spacing:2px; }
+ .logo-title h1 { font-size:1.2rem; color:var(--amber); text-shadow:0 0 10px var(--amber);
+ letter-spacing:4px; font-weight:normal; }
+ .logo-title .subtitle { font-size:0.72rem; color:var(--dim); margin-top:2px; }
+ .logo-title .subtitle a { color:var(--amber); text-decoration:none; }
+ .logo-title .subtitle a:hover { text-shadow:0 0 6px var(--amber); }
+ .transmit-btn { border:1px solid var(--amber); color:var(--amber); padding:8px 18px;
+ text-decoration:none; font-size:0.82rem; letter-spacing:2px;
+ transition:all 0.1s; }
+ .transmit-btn:hover { background:var(--amber); color:var(--bg); }
+ a.header-feed-link { color:var(--dim); }
+ a.header-feed-link:hover { color:var(--amber); text-shadow:0 0 6px var(--amber); }
+ .nav-hints { background:var(--bg2); border-bottom:1px solid var(--dim); color:var(--dim);
+ padding:4px 24px; display:flex; gap:18px; font-size:0.68rem; flex-wrap:wrap; }
+ .nav-hints kbd { background:transparent; border:1px solid var(--dim); color:var(--amber);
+ padding:0 5px; font-size:0.68rem; margin:0 2px; }
+ .content { flex:1; overflow-y:auto; padding:14px 24px;
+ scrollbar-width:thin; scrollbar-color:var(--dim) var(--bg); }
+ .page-nav { display:flex; justify-content:center; margin:12px 0; }
+ .page-nav a { border:1px solid var(--dim); color:var(--amber); padding:7px 20px;
+ text-decoration:none; font-size:0.82rem; letter-spacing:2px; }
+ .page-nav a:hover { background:var(--amber); color:var(--bg); border-color:var(--amber); }
+ .page-nav-footer { flex-shrink:0; padding:6px 24px; display:flex; justify-content:center;
+ background:var(--bg2); border-top:2px solid var(--amber); }
+ .post { background:var(--bg); border:1px solid var(--dim); padding:16px 18px;
+ margin-bottom:10px; cursor:pointer; transition:border-color 0.15s; }
+ .post:hover { border-color:var(--amber); box-shadow:0 0 8px rgba(255,176,0,0.25); }
+ .post-active { border-color:var(--amber) !important;
+ background:rgba(255,176,0,0.04) !important;
+ box-shadow:0 0 14px rgba(255,176,0,0.3),inset 3px 0 0 var(--amber) !important; }
+ .post-header { display:flex; justify-content:space-between; margin-bottom:10px; font-size:0.85rem; }
+ .post-time { color:var(--dim); font-size:0.78rem; }
+ .post-text { line-height:1.6; font-size:0.88rem; }
+ .post-text a { color:var(--amber); text-decoration:underline; }
+ .post-image { max-width:100%; margin-top:10px; border:1px solid var(--dim);
+ filter:sepia(60%) hue-rotate(-10deg); }
+ .post-audio { width:100%; margin-top:10px; }
+ .post-modal { display:none; position:fixed; inset:0; z-index:100;
+ background:rgba(0,0,0,0.97); overflow-y:auto; padding:40px 20px; }
+ .post-modal.active { display:block; }
+ .modal-inner { max-width:740px; margin:0 auto; background:var(--bg);
+ border:1px solid var(--amber); padding:36px;
+ box-shadow:0 0 40px rgba(255,176,0,0.2); }
+ .modal-close { float:right; background:none; border:none; color:var(--dim);
+ font-family:monospace; font-size:0.9rem; cursor:pointer; letter-spacing:2px; }
+ @media(max-width:640px) { .nav-hints{display:none;} header{padding:10px 16px;} .content{padding:10px 16px;} }
+ [data-sno-theme="retro"] .splash-overlay { background: var(--bg); font-family:'Courier New',monospace; }
+ [data-sno-theme="retro"]::after {
+ content:''; position:absolute; inset:0; pointer-events:none; opacity:0.35;
+ background: repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,0,0,0.2) 2px, rgba(0,0,0,0.2) 4px);
+ }
+ [data-sno-theme="retro"] .splash-inner { position:relative; z-index:1; }
+ [data-sno-theme="retro"] .splash-title {
+ font-size:clamp(1.15rem,3.8vw,1.55rem); color:var(--amber);
+ text-shadow:0 0 14px var(--amber); letter-spacing:0.3em;
+ animation: splashRetroFlicker 4s ease-in-out infinite;
+ }
+ @keyframes splashRetroFlicker { 0%,100%{opacity:1} 50%{opacity:0.92} }
+ [data-sno-theme="retro"] .splash-tag { color:#d4a020; }
+ [data-sno-theme="retro"] .splash-hint { color:#c99528; }
+ [data-sno-theme="retro"] .splash-inner { text-shadow: 0 0 10px #000, 0 2px 8px #000; }
diff --git a/internal/generator/templates/themes/retro.tmpl b/internal/generator/templates/themes/retro/theme.js
index d6487c2..69d72b1 100644
--- a/internal/generator/templates/themes/retro.tmpl
+++ b/internal/generator/templates/themes/retro/theme.js
@@ -1,104 +1,4 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>SNONUX.FOO // RETRO</title>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
- <style>
- :root { --amber:#ffb000; --dim:#7a5200; --bg:#0a0800; --bg2:#050300; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:'Courier New',Courier,monospace; background:var(--bg); color:var(--amber);
- overflow:hidden; height:100vh; }
- /* Phosphor scanlines overlay — sits above WebGL */
- body::before { content:''; position:fixed; inset:0; z-index:999; pointer-events:none;
- background:repeating-linear-gradient(0deg,transparent,transparent 2px,
- rgba(0,0,0,0.15) 2px,rgba(0,0,0,0.15) 4px); }
- /* Subtle glow flicker */
- @keyframes amber-flicker { 0%,100%{opacity:1} 94%{opacity:0.98} 96%{opacity:0.93} }
- body { animation:amber-flicker 11s infinite; }
- /* WebGL background canvas */
- #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
- .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
- header { padding:12px 24px; background:var(--bg2); border-bottom:2px solid var(--amber);
- display:flex; align-items:center; justify-content:space-between; }
- .logo { display:flex; align-items:center; gap:14px; }
- .logo-mark { font-size:1.6rem; color:var(--amber); text-shadow:0 0 14px var(--amber);
- letter-spacing:2px; }
- .logo-title h1 { font-size:1.2rem; color:var(--amber); text-shadow:0 0 10px var(--amber);
- letter-spacing:4px; font-weight:normal; }
- .logo-title .subtitle { font-size:0.72rem; color:var(--dim); margin-top:2px; }
- .logo-title .subtitle a { color:var(--amber); text-decoration:none; }
- .logo-title .subtitle a:hover { text-shadow:0 0 6px var(--amber); }
- .transmit-btn { border:1px solid var(--amber); color:var(--amber); padding:8px 18px;
- text-decoration:none; font-size:0.82rem; letter-spacing:2px;
- transition:all 0.1s; }
- .transmit-btn:hover { background:var(--amber); color:var(--bg); }
- a.header-feed-link { color:var(--dim); }
- a.header-feed-link:hover { color:var(--amber); text-shadow:0 0 6px var(--amber); }
- .nav-hints { background:var(--bg2); border-bottom:1px solid var(--dim); color:var(--dim);
- padding:4px 24px; display:flex; gap:18px; font-size:0.68rem; flex-wrap:wrap; }
- .nav-hints kbd { background:transparent; border:1px solid var(--dim); color:var(--amber);
- padding:0 5px; font-size:0.68rem; margin:0 2px; }
- .content { flex:1; overflow-y:auto; padding:14px 24px;
- scrollbar-width:thin; scrollbar-color:var(--dim) var(--bg); }
- .page-nav { display:flex; justify-content:center; margin:12px 0; }
- .page-nav a { border:1px solid var(--dim); color:var(--amber); padding:7px 20px;
- text-decoration:none; font-size:0.82rem; letter-spacing:2px; }
- .page-nav a:hover { background:var(--amber); color:var(--bg); border-color:var(--amber); }
- .page-nav-footer { flex-shrink:0; padding:6px 24px; display:flex; justify-content:center;
- background:var(--bg2); border-top:2px solid var(--amber); }
- .post { background:var(--bg); border:1px solid var(--dim); padding:16px 18px;
- margin-bottom:10px; cursor:pointer; transition:border-color 0.15s; }
- .post:hover { border-color:var(--amber); box-shadow:0 0 8px rgba(255,176,0,0.25); }
- .post-active { border-color:var(--amber) !important;
- background:rgba(255,176,0,0.04) !important;
- box-shadow:0 0 14px rgba(255,176,0,0.3),inset 3px 0 0 var(--amber) !important; }
- .post-header { display:flex; justify-content:space-between; margin-bottom:10px; font-size:0.85rem; }
- .post-time { color:var(--dim); font-size:0.78rem; }
- .post-text { line-height:1.6; font-size:0.88rem; }
- .post-text a { color:var(--amber); text-decoration:underline; }
- .post-image { max-width:100%; margin-top:10px; border:1px solid var(--dim);
- filter:sepia(60%) hue-rotate(-10deg); }
- .post-audio { width:100%; margin-top:10px; }
- .post-modal { display:none; position:fixed; inset:0; z-index:100;
- background:rgba(0,0,0,0.97); overflow-y:auto; padding:40px 20px; }
- .post-modal.active { display:block; }
- .modal-inner { max-width:740px; margin:0 auto; background:var(--bg);
- border:1px solid var(--amber); padding:36px;
- box-shadow:0 0 40px rgba(255,176,0,0.2); }
- .modal-close { float:right; background:none; border:none; color:var(--dim);
- font-family:monospace; font-size:0.9rem; cursor:pointer; letter-spacing:2px; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:10px 16px;} .content{padding:10px 16px;} }
- .splash-overlay.splash-retro { background: var(--bg); font-family:'Courier New',monospace; }
- .splash-retro::after {
- content:''; position:absolute; inset:0; pointer-events:none; opacity:0.35;
- background: repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,0,0,0.2) 2px, rgba(0,0,0,0.2) 4px);
- }
- .splash-retro .splash-inner { position:relative; z-index:1; }
- .splash-retro .splash-title {
- font-size:clamp(1.15rem,3.8vw,1.55rem); color:var(--amber);
- text-shadow:0 0 14px var(--amber); letter-spacing:0.3em;
- animation: splashRetroFlicker 4s ease-in-out infinite;
- }
- @keyframes splashRetroFlicker { 0%,100%{opacity:1} 50%{opacity:0.92} }
- .splash-retro .splash-tag { color:#d4a020; }
- .splash-retro .splash-hint { color:#c99528; }
- .splash-retro .splash-inner { text-shadow: 0 0 10px #000, 0 2px 8px #000; }
-{{template "navSharedCSSInner"}}
- </style>
-</head>
-<body>
- {{template "splashGate"}}
- <div id="splash-overlay" class="splash-overlay splash-retro" role="dialog" aria-modal="true" aria-label="Open microblog" tabindex="-1">
- <canvas class="splash-gl-canvas" id="splash-gl-canvas" aria-hidden="true"></canvas>
- <div class="splash-inner">
- <div class="splash-title">*** SNONUX BBS ***</div>
- <div class="splash-tag">Amber phosphor mode</div>
- <div class="splash-hint">Press Enter or click to connect</div>
- </div>
- </div>
- <script>
+
(function(){
if(document.documentElement.classList.contains('sno-splash-skip'))return;
var cv=document.getElementById('splash-gl-canvas');
@@ -115,46 +15,8 @@
function loop(now){raf=requestAnimationFrame(loop);var t=(now-t0)*0.001;g.rotation.x=t*0.44;g.rotation.y=t*0.71;oc.rotation.z=t*0.9;ren.render(sc,ca);}
raf=requestAnimationFrame(loop);
})();
- </script>
- <canvas id="three-canvas"></canvas>
- <div class="overlay">
- <header>
- <div class="logo">
- <span class="logo-mark">[SN]</span>
- <div class="logo-title">
- <h1>SNONUX.FOO</h1>
- <p class="subtitle">MICROBLOG / <a href="https://foo.zone">FOO.ZONE</a> IS THE REAL BLOG</p>
- <p class="logo-host">Served by NetBSD on a Raspberry Pi 3</p>
- </div>
- </div>
- <div class="nav">
- <a href="atom.xml" class="header-feed-link" rel="alternate" title="Atom feed" type="application/atom+xml">Atom feed</a>
- <a href="https://foo.zone/about" class="transmit-btn">TRANSMIT</a>
- </div>
- </header>
- {{template "navhints" .}}
- <div class="content" id="post-content">
- {{range $i, $post := .Posts}}
- <div class="post" id="post-{{$post.ID}}" data-index="{{$i}}">
- <div class="post-header">
- <div><strong>@SNONUX</strong></div>
- <div class="post-time">{{$post.FormattedTime}}</div>
- </div>
- <div class="post-text">{{$post.ContentHTML}}</div>
- </div>
- {{end}}
- </div>
- {{if or .PrevPage .NextPage}}
- <footer class="page-nav-footer" aria-label="Pagination">
- <div class="page-nav page-nav-dual">
- {{if .PrevPage}}<a href="{{.PrevPage}}">&lt;-- NEWER</a>{{end}}
- {{if .NextPage}}<a href="{{.NextPage}}">OLDER --&gt;</a>{{end}}
- </div>
- </footer>
- {{end}}
- </div>
- {{template "navmodal" .}}
- <script>
+
+
// Retro WebGL scene: amber demo-scene cube + orbiting octahedrons + star particles.
// Evokes classic 80s/90s PC demo aesthetics with amber phosphor colours.
(function() {
@@ -303,7 +165,3 @@
setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 200); }, 20);
};
})();
- </script>
- {{template "navscript" .}}
-</body>
-</html>
diff --git a/internal/generator/templates/themes/retrofuture.tmpl b/internal/generator/templates/themes/retrofuture.tmpl
deleted file mode 100644
index f1b1fb7..0000000
--- a/internal/generator/templates/themes/retrofuture.tmpl
+++ /dev/null
@@ -1,374 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>snonux.foo ◈ RETROFUTURE</title>
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Share+Tech+Mono&display=swap" rel="stylesheet">
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
- <style>
- :root { --pink:#ff6b9d; --purple:#00d9c0; --orange:#ff8c42; --bg:#0a0121; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:'Share Tech Mono',monospace; background:var(--bg);
- color:#f0efe4; overflow:hidden; height:100vh; }
- #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
- .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
- header { padding:16px 28px; background:rgba(10,1,33,0.85); backdrop-filter:blur(12px);
- border-bottom:2px solid var(--pink); display:flex; align-items:center; justify-content:space-between; }
- .logo { display:flex; align-items:center; gap:14px; }
- .logo-mark { font-size:1.8rem; font-family:'Orbitron',sans-serif; color:var(--purple);
- text-shadow:0 0 12px var(--purple),0 0 28px rgba(0,217,192,0.4); }
- .logo-title h1 { font-size:1.7rem; font-family:'Orbitron',sans-serif; color:#f0efe4;
- letter-spacing:3px; text-shadow:0 0 8px rgba(255,255,255,0.2); }
- .logo-title .subtitle { font-size:0.7rem; color:rgba(240,239,228,0.55); margin-top:2px;
- font-family:'Share Tech Mono',monospace; }
- .logo-title .subtitle a { color:var(--pink); text-decoration:none; }
- .logo-title .subtitle a:hover { text-shadow:0 0 8px var(--pink); }
- .logo-title .logo-host { font-size:0.65rem; color:rgba(0,217,192,0.6); margin-top:2px;
- font-family:'Share Tech Mono',monospace; }
- .transmit-btn { border:2px solid var(--orange); color:var(--orange); padding:10px 22px;
- border-radius:22px; text-decoration:none; letter-spacing:1px;
- font-size:0.88rem; font-family:'Orbitron',sans-serif; transition:all 0.2s; }
- .transmit-btn:hover { background:var(--orange); color:var(--bg); box-shadow:0 0 18px rgba(255,140,66,0.5); }
- a.header-feed-link { color:var(--pink); font-family:'Share Tech Mono',monospace; }
- .nav-hints { background:rgba(10,1,33,0.75); border-bottom:1px solid rgba(0,217,192,0.25);
- color:rgba(240,239,228,0.45); padding:5px 20px; display:flex; gap:18px;
- font-size:0.68rem; font-family:'Share Tech Mono',monospace; flex-wrap:wrap; }
- .nav-hints kbd { background:rgba(0,217,192,0.12); border:1px solid rgba(0,217,192,0.45);
- color:var(--purple); border-radius:3px; padding:0 5px; margin:0 2px; font-size:0.7rem; }
- .content { flex:1; overflow-y:auto; padding:22px 28px;
- scrollbar-width:thin; scrollbar-color:var(--purple) var(--bg); }
- .page-nav { display:flex; justify-content:center; margin:14px 0; }
- .page-nav a { border:2px solid var(--pink); color:var(--pink); padding:8px 22px;
- border-radius:22px; text-decoration:none; letter-spacing:2px; font-size:0.82rem;
- font-family:'Orbitron',sans-serif; transition:all 0.2s; }
- .page-nav a:hover { background:var(--pink); color:var(--bg); }
- .page-nav-footer { flex-shrink:0; padding:8px 28px; display:flex; justify-content:center;
- background:rgba(10,1,33,0.82); backdrop-filter:blur(10px);
- border-top:2px solid var(--pink); }
- .post { background:rgba(20,10,55,0.85); border:1px solid rgba(0,217,192,0.3);
- border-radius:12px; padding:22px; margin-bottom:18px; cursor:pointer; transition:all 0.25s;
- box-shadow:0 2px 16px rgba(0,0,0,0.4); }
- .post:hover { border-color:var(--pink); box-shadow:0 0 22px rgba(255,107,157,0.3),0 4px 24px rgba(0,0,0,0.5); transform:translateY(-3px); }
- .post-active { border-color:var(--orange) !important; background:rgba(30,15,60,0.96) !important;
- box-shadow:0 0 22px rgba(255,140,66,0.4),inset 3px 0 0 var(--orange) !important; }
- .post-header { display:flex; justify-content:space-between; margin-bottom:14px; }
- .post-time { color:var(--orange); font-family:'Share Tech Mono',monospace; font-size:0.85rem; }
- .post-text { line-height:1.6; font-size:0.95rem; font-family:'Share Tech Mono',monospace; }
- .post-text a { color:var(--pink); text-decoration:none; }
- .post-audio { width:100%; margin-top:10px; }
- .post-modal { display:none; position:fixed; inset:0; z-index:100;
- background:rgba(10,1,33,0.96); overflow-y:auto; padding:40px 20px; }
- .post-modal.active { display:block; }
- .modal-inner { max-width:780px; margin:0 auto; background:rgba(20,10,55,0.98);
- border:2px solid var(--pink); border-radius:12px;
- box-shadow:0 0 60px rgba(255,107,157,0.25); padding:38px; }
- .modal-close { float:right; background:none; border:none; color:var(--orange);
- font-family:'Orbitron',sans-serif; font-size:0.9rem; cursor:pointer; letter-spacing:2px; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 18px;} }
- .splash-overlay.splash-retrofuture {
- background: radial-gradient(ellipse at 50% 60%, #1a0840 0%, var(--bg) 55%, #050010 100%);
- }
- .splash-retrofuture .splash-starburst {
- position:absolute; inset:0; pointer-events:none; z-index:1;
- }
- .splash-retrofuture .splash-starburst span {
- position:absolute; top:50%; left:50%; width:3px; height:3px;
- border-radius:50%; background:var(--pink); opacity:0.5;
- box-shadow:0 0 6px var(--pink);
- animation: starTwinkle 3s ease-in-out infinite;
- }
- @keyframes starTwinkle {
- 0%,100%{opacity:0.3;transform:scale(1);}
- 50%{opacity:0.8;transform:scale(1.4);}
- }
- .splash-retrofuture .splash-atomic {
- width:min(100px,22vw); height:min(100px,22vw); margin:0 auto 1rem;
- border-radius:50%; border:3px solid var(--purple);
- box-shadow:0 0 20px var(--purple),0 0 40px rgba(0,217,192,0.3),inset 0 0 20px rgba(0,217,192,0.15);
- position:relative; animation:splashAtomicPulse 2.5s ease-in-out infinite alternate;
- }
- .splash-retrofuture .splash-atomic::before,
- .splash-retrofuture .splash-atomic::after {
- content:''; position:absolute; border:2px solid var(--purple); border-radius:50%;
- top:50%; left:50%; transform:translate(-50%,-50%);
- }
- .splash-retrofuture .splash-atomic::before {
- width:160%; height:30%; opacity:0.7;
- box-shadow:0 0 10px var(--purple);
- }
- .splash-retrofuture .splash-atomic::after {
- width:30%; height:160%; opacity:0.7;
- box-shadow:0 0 10px var(--purple);
- }
- @keyframes splashAtomicPulse {
- from{transform:scale(0.95);box-shadow:0 0 15px var(--purple),0 0 30px rgba(0,217,192,0.2),inset 0 0 15px rgba(0,217,192,0.1);}
- to{transform:scale(1.05);box-shadow:0 0 25px var(--purple),0 0 50px rgba(0,217,192,0.4),inset 0 0 25px rgba(0,217,192,0.2);}
- }
- .splash-retrofuture .splash-title {
- font-family:'Orbitron',sans-serif; font-size:clamp(1.4rem,4.5vw,2rem);
- color:#f0efe4; letter-spacing:4px; text-shadow:0 0 20px rgba(255,107,157,0.6);
- }
- .splash-retrofuture .splash-tag { font-family:'Share Tech Mono',monospace; color:var(--purple); }
- .splash-retrofuture .splash-hint { font-family:'Share Tech Mono',monospace; color:rgba(240,239,228,0.8); }
- .splash-retrofuture .splash-inner { text-shadow:0 2px 24px rgba(10,1,33,0.9); }
-{{template "navSharedCSSInner"}}
- </style>
-</head>
-<body>
- {{template "splashGate"}}
- <div id="splash-overlay" class="splash-overlay splash-retrofuture" role="dialog" aria-modal="true" aria-label="Open microblog" tabindex="-1">
- <canvas class="splash-gl-canvas" id="splash-gl-canvas" aria-hidden="true"></canvas>
- <div class="splash-starburst" aria-hidden="true">
- <span style="top:15%;left:20%;animation-delay:0s"></span>
- <span style="top:25%;left:75%;animation-delay:0.4s"></span>
- <span style="top:60%;left:10%;animation-delay:0.8s"></span>
- <span style="top:70%;left:85%;animation-delay:1.2s"></span>
- <span style="top:40%;left:50%;animation-delay:1.6s"></span>
- <span style="top:80%;left:35%;animation-delay:0.2s"></span>
- <span style="top:10%;left:60%;animation-delay:1.0s"></span>
- <span style="top:55%;left:90%;animation-delay:0.6s"></span>
- </div>
- <div class="splash-inner">
- <div class="splash-atomic" aria-hidden="true"></div>
- <div class="splash-title">snonux.foo</div>
- <div class="splash-tag">Atomic age uplink</div>
- <div class="splash-hint">Click or Enter to tune in</div>
- </div>
- </div>
- <script>
- (function(){
- if(document.documentElement.classList.contains('sno-splash-skip'))return;
- var cv=document.getElementById('splash-gl-canvas');
- if(!cv||typeof THREE==='undefined')return;
- var raf,ren,sc,ca,g=new THREE.Group(),t0=performance.now();
- function cleanup(){window.removeEventListener('resize',sz);if(raf)cancelAnimationFrame(raf);raf=null;if(ren)ren.dispose();ren=null;window._snonuxSplashWebGLCleanup=null;}
- window._snonuxSplashWebGLCleanup=cleanup;
- function sz(){var w=cv.clientWidth||2,h=cv.clientHeight||2;if(ren)ren.setSize(w,h,false);if(ca){ca.aspect=w/h;ca.updateProjectionMatrix();}}
- ren=new THREE.WebGLRenderer({canvas:cv,antialias:true,alpha:true});ren.setClearColor(0,0);ren.setPixelRatio(Math.min(window.devicePixelRatio||1,2));
- sc=new THREE.Scene();ca=new THREE.PerspectiveCamera(58,1,0.1,120);ca.position.set(0,1.2,7);
- var atomic=new THREE.Mesh(new THREE.SphereGeometry(1.35,28,28),new THREE.MeshBasicMaterial({color:0x00d9c0,transparent:true,opacity:0.9}));
- atomic.position.y=0;g.add(atomic);
- var ringH=new THREE.Mesh(new THREE.TorusGeometry(2.1,0.06,8,64),new THREE.MeshBasicMaterial({color:0xff6b9d,transparent:true,opacity:0.8}));
- g.add(ringH);
- var ringV=new THREE.Mesh(new THREE.TorusGeometry(2.1,0.06,8,64),new THREE.MeshBasicMaterial({color:0xff6b9d,transparent:true,opacity:0.8}));
- ringV.rotation.x=Math.PI/2;g.add(ringV);
- var gr=new THREE.Mesh(new THREE.PlaneGeometry(28,28,20,20),new THREE.MeshBasicMaterial({color:0x00d9c0,wireframe:true,transparent:true,opacity:0.25}));
- gr.rotation.x=-Math.PI/2.4;gr.position.y=-2.8;g.add(gr);
- sc.add(g);sz();window.addEventListener('resize',sz);
- function loop(now){raf=requestAnimationFrame(loop);var t=(now-t0)*0.001;g.rotation.y=Math.sin(t*0.3)*0.12;atomic.position.y=Math.sin(t*1.1)*0.12;ringH.scale.setScalar(1+Math.sin(t*2.2)*0.06);ringV.scale.setScalar(1+Math.cos(t*2.2)*0.06);ren.render(sc,ca);}
- raf=requestAnimationFrame(loop);
- })();
- </script>
- <canvas id="three-canvas"></canvas>
- <div class="overlay">
- <header>
- <div class="logo">
- <span class="logo-mark">SN</span>
- <div class="logo-title">
- <h1>snonux.foo</h1>
- <p class="subtitle">microblog &mdash; <a href="https://foo.zone">foo.zone</a> is the real blog</p>
- <p class="logo-host">Served by NetBSD on a Raspberry Pi 3</p>
- </div>
- </div>
- <div class="nav">
- <a href="atom.xml" class="header-feed-link" rel="alternate" title="Atom feed" type="application/atom+xml">Atom feed</a>
- <a href="https://foo.zone/about" class="transmit-btn">TRANSMIT TO NEXUS</a>
- </div>
- </header>
- {{template "navhints" .}}
- <div class="content" id="post-content">
- {{range $i, $post := .Posts}}
- <div class="post" id="post-{{$post.ID}}" data-index="{{$i}}">
- <div class="post-header">
- <div><strong>@snonux</strong></div>
- <div class="post-time">{{$post.FormattedTime}}</div>
- </div>
- <div class="post-text">{{$post.ContentHTML}}</div>
- </div>
- {{end}}
- </div>
- {{if or .PrevPage .NextPage}}
- <footer class="page-nav-footer" aria-label="Pagination">
- <div class="page-nav page-nav-dual">
- {{if .PrevPage}}<a href="{{.PrevPage}}">&larr; NEWER</a>{{end}}
- {{if .NextPage}}<a href="{{.NextPage}}">OLDER &rarr;</a>{{end}}
- </div>
- </footer>
- {{end}}
- </div>
- {{template "navmodal" .}}
- <script>
- // Retrofuture WebGL: atomic orb with crossed electron rings, receding teal grid floor,
- // chrome metallic star particles, and a slow drifting camera orbit.
- (function() {
- var _wild = false, _snoTOffset = 0, _snoLastT = 0;
- var scene, camera, renderer, clock;
- var atomic, ringH, ringV, stars;
-
- function initThree() {
- scene = new THREE.Scene();
- scene.background = new THREE.Color(0x0a0121);
- scene.fog = new THREE.Fog(0x0a0121, 60, 180);
-
- camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 300);
- camera.position.set(0, 10, 45);
- camera.lookAt(0, -5, -10);
-
- renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('three-canvas'), antialias: true });
- renderer.setSize(window.innerWidth, window.innerHeight);
- renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
- clock = new THREE.Clock();
-
- // Glowing atomic teal orb
- atomic = new THREE.Mesh(
- new THREE.SphereGeometry(12, 32, 16),
- new THREE.MeshBasicMaterial({ color: 0x00d9c0 })
- );
- atomic.position.set(0, -8, -55);
- scene.add(atomic);
-
- // Horizontal electron ring — pink
- ringH = new THREE.Mesh(
- new THREE.TorusGeometry(15, 0.15, 8, 64),
- new THREE.MeshBasicMaterial({ color: 0xff6b9d })
- );
- ringH.position.copy(atomic.position);
- scene.add(ringH);
-
- // Vertical electron ring — coral
- ringV = new THREE.Mesh(
- new THREE.TorusGeometry(15, 0.15, 8, 64),
- new THREE.MeshBasicMaterial({ color: 0xff8c42 })
- );
- ringV.position.copy(atomic.position);
- ringV.rotation.x = Math.PI / 2;
- scene.add(ringV);
-
- // Receding grid floor — teal
- var grid = new THREE.GridHelper(200, 40, 0x00d9c0, 0x1a0840);
- grid.position.set(0, -18, -30);
- scene.add(grid);
-
- // 1200 chrome star particles scattered in a sphere shell
- var starPos = new Float32Array(1200 * 3);
- for (var j = 0; j < 1200 * 3; j += 3) {
- var r = 80 + Math.random() * 40;
- var theta = Math.random() * Math.PI * 2;
- var phi = Math.acos(2 * Math.random() - 1);
- starPos[j] = r * Math.sin(phi) * Math.cos(theta);
- starPos[j+1] = r * Math.sin(phi) * Math.sin(theta);
- starPos[j+2] = r * Math.cos(phi);
- }
- var starGeo = new THREE.BufferGeometry();
- starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3));
- scene.add(new THREE.Points(starGeo, new THREE.PointsMaterial({
- color: 0xffd700, size: 0.22, transparent: true, opacity: 0.8
- })));
-
- window.addEventListener('resize', onResize);
- animate();
- }
-
- function onResize() {
- camera.aspect = window.innerWidth / window.innerHeight;
- camera.updateProjectionMatrix();
- renderer.setSize(window.innerWidth, window.innerHeight);
- }
-
- function animate() {
- requestAnimationFrame(animate);
- var realT = clock.getElapsedTime();
- _snoTOffset += (realT - _snoLastT) * (_wild ? 9 : 0);
- _snoLastT = realT;
- var t = realT + _snoTOffset;
- // Atomic orb pulses; wild meltdown mode makes it throb intensely
- var pulse = _wild ? 1 + 0.1 * Math.sin(t * 12) : 1 + 0.015 * Math.sin(t * 1.5);
- atomic.scale.setScalar(pulse);
- ringH.rotation.z = t * 0.4;
- ringV.rotation.z = -t * 0.3;
- // Rings tilt chaotically in wild — full 3D tumble
- if (_wild) {
- ringH.rotation.x = Math.sin(realT * 2.3) * 0.8;
- ringV.rotation.y = Math.cos(realT * 1.9) * 0.9;
- } else {
- ringH.rotation.x = 0;
- ringV.rotation.y = 0;
- }
- // Camera: meltdown spiral in wild, slow orbit otherwise
- if (_wild) {
- camera.position.x = Math.sin(realT * 0.36) * 16;
- camera.position.y = 10 + Math.sin(realT * 0.29) * 10;
- camera.position.z = 45 + Math.sin(realT * 0.22) * 16;
- camera.fov = 60 + Math.sin(realT * 0.47) * 18;
- camera.updateProjectionMatrix();
- } else {
- camera.position.x = Math.sin(t * 0.07) * 5;
- camera.position.y = 10 + Math.sin(t * 0.05) * 2;
- camera.position.z = 45;
- if (camera.fov !== 60) { camera.fov = 60; camera.updateProjectionMatrix(); }
- }
- camera.lookAt(0, -5, -10);
- renderer.render(scene, camera);
- }
-
- initThree();
-
- // Retrofuture nav/wild effects — atomic pulse on navigate, meltdown on wild
- window.snonuxOpenEffect = function(post) {
- // Atomic rings expand outward from post — zoom into modal
- var modal = document.getElementById('post-modal');
- if (modal) { modal.classList.add('sno-modal-zoom'); setTimeout(function() { modal.classList.remove('sno-modal-zoom'); }, 400); }
- var r = post ? post.getBoundingClientRect() : {left: window.innerWidth/2, top: window.innerHeight/2, width: 0, height: 0};
- [0, 80, 160].forEach(function(delay) {
- var ring = document.createElement('div');
- ring.style.cssText = 'position:fixed;top:' + (r.top+r.height/2-6) + 'px;left:' + (r.left+r.width/2-6) + 'px;z-index:997;pointer-events:none;width:12px;height:12px;border-radius:50%;border:2px solid rgba(0,217,192,0.7);transition:all 0.42s ease,opacity 0.42s';
- document.body.appendChild(ring);
- setTimeout(function() { ring.style.transform='scale(25)'; ring.style.opacity='0'; setTimeout(function() { ring.remove(); }, 460); }, delay + 15);
- });
- };
- window.snonuxCloseEffect = function() {
- var d = document.createElement('div');
- d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(0,217,192,0.1);transition:opacity 0.2s';
- document.body.appendChild(d);
- setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 230); }, 15);
- };
- window.snonuxScrollEffect = function(dir) {
- var isDown = dir === 'down';
- var thick = _wild ? '14px' : '5px';
- var d = document.createElement('div');
- // Retrofuture: atomic orange-gold sweep
- d.style.cssText = 'position:fixed;left:0;right:0;height:' + thick + ';z-index:9000;pointer-events:none;' +
- 'background:linear-gradient(90deg,transparent,rgba(255,140,0,0.9),rgba(255,80,0,0.9),rgba(255,140,0,0.9),transparent);' +
- (isDown ? 'top:0;' : 'bottom:0;') +
- 'transition:transform 0.3s ease,opacity 0.3s ease;';
- document.body.appendChild(d);
- setTimeout(function() { d.style.transform = isDown ? 'translateY(100vh)' : 'translateY(-100vh)'; d.style.opacity='0'; }, 16);
- setTimeout(function() { d.remove(); }, 380);
- };
- window.snonuxWildToggle = function() {
- _wild = !_wild;
- var b = document.getElementById('sno-wild-badge');
- if (b) b.classList.toggle('sno-wild-on', _wild);
- };
- window.snonuxNavEffect = function() {
- // Atomic pulse — rings expand briefly as CSS overlay
- var d = document.createElement('div');
- d.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) scale(0.1);z-index:998;pointer-events:none;width:100vmax;height:100vmax;border-radius:50%;border:3px solid rgba(0,217,192,0.7);transition:transform 0.3s ease,opacity 0.3s';
- document.body.appendChild(d);
- setTimeout(function() { d.style.transform='translate(-50%,-50%) scale(1.2)'; d.style.opacity='0'; setTimeout(function() { d.remove(); }, 330); }, 15);
- };
- window.snonuxPageEffect = function() {
- var ov = document.querySelector('.overlay');
- if (ov) { ov.classList.add('sno-fx-zoom'); setTimeout(function() { ov.classList.remove('sno-fx-zoom'); }, 330); }
- var d = document.createElement('div');
- d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(0,217,192,0.15);transition:opacity 0.2s';
- document.body.appendChild(d);
- setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 230); }, 20);
- };
- })();
- </script>
- {{template "navscript" .}}
-</body>
-</html> \ No newline at end of file
diff --git a/internal/generator/templates/themes/retrofuture/meta.json b/internal/generator/templates/themes/retrofuture/meta.json
new file mode 100644
index 0000000..e41fdc5
--- /dev/null
+++ b/internal/generator/templates/themes/retrofuture/meta.json
@@ -0,0 +1,7 @@
+{
+ "title": "snonux.foo ◈ RETROFUTURE",
+ "header_html": "\u003cdiv class=\"logo\"\u003e\n \u003cspan class=\"logo-mark\"\u003eSN\u003c/span\u003e\n \u003cdiv class=\"logo-title\"\u003e\n \u003ch1\u003esnonux.foo\u003c/h1\u003e\n \u003cp class=\"subtitle\"\u003emicroblog \u0026mdash; \u003ca href=\"https://foo.zone\"\u003efoo.zone\u003c/a\u003e is the real blog\u003c/p\u003e\n \u003cp class=\"logo-host\"\u003eServed by NetBSD on a Raspberry Pi 3\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"nav\"\u003e\n \u003ca href=\"atom.xml\" class=\"header-feed-link\" rel=\"alternate\" title=\"Atom feed\" type=\"application/atom+xml\"\u003eAtom feed\u003c/a\u003e\n \u003ca href=\"https://foo.zone/about\" class=\"transmit-btn\"\u003eTRANSMIT TO NEXUS\u003c/a\u003e\n \u003c/div\u003e",
+ "splash_inner_html": "\u003ccanvas class=\"splash-gl-canvas\" id=\"splash-gl-canvas\" aria-hidden=\"true\"\u003e\u003c/canvas\u003e\n \u003cdiv class=\"splash-starburst\" aria-hidden=\"true\"\u003e\n \u003cspan style=\"top:15%;left:20%;animation-delay:0s\"\u003e\u003c/span\u003e\n \u003cspan style=\"top:25%;left:75%;animation-delay:0.4s\"\u003e\u003c/span\u003e\n \u003cspan style=\"top:60%;left:10%;animation-delay:0.8s\"\u003e\u003c/span\u003e\n \u003cspan style=\"top:70%;left:85%;animation-delay:1.2s\"\u003e\u003c/span\u003e\n \u003cspan style=\"top:40%;left:50%;animation-delay:1.6s\"\u003e\u003c/span\u003e\n \u003cspan style=\"top:80%;left:35%;animation-delay:0.2s\"\u003e\u003c/span\u003e\n \u003cspan style=\"top:10%;left:60%;animation-delay:1.0s\"\u003e\u003c/span\u003e\n \u003cspan style=\"top:55%;left:90%;animation-delay:0.6s\"\u003e\u003c/span\u003e\n \u003c/div\u003e\n \u003cdiv class=\"splash-inner\"\u003e\n \u003cdiv class=\"splash-atomic\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-title\"\u003esnonux.foo\u003c/div\u003e\n \u003cdiv class=\"splash-tag\"\u003eAtomic age uplink\u003c/div\u003e\n \u003cdiv class=\"splash-hint\"\u003eClick or Enter to tune in\u003c/div\u003e\n \u003c/div\u003e",
+ "prev_page_text": "\u0026larr; NEWER",
+ "next_page_text": "OLDER \u0026rarr;"
+}
diff --git a/internal/generator/templates/themes/retrofuture/theme.css b/internal/generator/templates/themes/retrofuture/theme.css
new file mode 100644
index 0000000..6cc010f
--- /dev/null
+++ b/internal/generator/templates/themes/retrofuture/theme.css
@@ -0,0 +1,105 @@
+ :root { --pink:#ff6b9d; --purple:#00d9c0; --orange:#ff8c42; --bg:#0a0121; }
+ * { margin:0; padding:0; box-sizing:border-box; }
+ body { font-family:'Share Tech Mono',monospace; background:var(--bg);
+ color:#f0efe4; overflow:hidden; height:100vh; }
+ #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
+ .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
+ header { padding:16px 28px; background:rgba(10,1,33,0.85); backdrop-filter:blur(12px);
+ border-bottom:2px solid var(--pink); display:flex; align-items:center; justify-content:space-between; }
+ .logo { display:flex; align-items:center; gap:14px; }
+ .logo-mark { font-size:1.8rem; font-family:'Orbitron',sans-serif; color:var(--purple);
+ text-shadow:0 0 12px var(--purple),0 0 28px rgba(0,217,192,0.4); }
+ .logo-title h1 { font-size:1.7rem; font-family:'Orbitron',sans-serif; color:#f0efe4;
+ letter-spacing:3px; text-shadow:0 0 8px rgba(255,255,255,0.2); }
+ .logo-title .subtitle { font-size:0.7rem; color:rgba(240,239,228,0.55); margin-top:2px;
+ font-family:'Share Tech Mono',monospace; }
+ .logo-title .subtitle a { color:var(--pink); text-decoration:none; }
+ .logo-title .subtitle a:hover { text-shadow:0 0 8px var(--pink); }
+ .logo-title .logo-host { font-size:0.65rem; color:rgba(0,217,192,0.6); margin-top:2px;
+ font-family:'Share Tech Mono',monospace; }
+ .transmit-btn { border:2px solid var(--orange); color:var(--orange); padding:10px 22px;
+ border-radius:22px; text-decoration:none; letter-spacing:1px;
+ font-size:0.88rem; font-family:'Orbitron',sans-serif; transition:all 0.2s; }
+ .transmit-btn:hover { background:var(--orange); color:var(--bg); box-shadow:0 0 18px rgba(255,140,66,0.5); }
+ a.header-feed-link { color:var(--pink); font-family:'Share Tech Mono',monospace; }
+ .nav-hints { background:rgba(10,1,33,0.75); border-bottom:1px solid rgba(0,217,192,0.25);
+ color:rgba(240,239,228,0.45); padding:5px 20px; display:flex; gap:18px;
+ font-size:0.68rem; font-family:'Share Tech Mono',monospace; flex-wrap:wrap; }
+ .nav-hints kbd { background:rgba(0,217,192,0.12); border:1px solid rgba(0,217,192,0.45);
+ color:var(--purple); border-radius:3px; padding:0 5px; margin:0 2px; font-size:0.7rem; }
+ .content { flex:1; overflow-y:auto; padding:22px 28px;
+ scrollbar-width:thin; scrollbar-color:var(--purple) var(--bg); }
+ .page-nav { display:flex; justify-content:center; margin:14px 0; }
+ .page-nav a { border:2px solid var(--pink); color:var(--pink); padding:8px 22px;
+ border-radius:22px; text-decoration:none; letter-spacing:2px; font-size:0.82rem;
+ font-family:'Orbitron',sans-serif; transition:all 0.2s; }
+ .page-nav a:hover { background:var(--pink); color:var(--bg); }
+ .page-nav-footer { flex-shrink:0; padding:8px 28px; display:flex; justify-content:center;
+ background:rgba(10,1,33,0.82); backdrop-filter:blur(10px);
+ border-top:2px solid var(--pink); }
+ .post { background:rgba(20,10,55,0.85); border:1px solid rgba(0,217,192,0.3);
+ border-radius:12px; padding:22px; margin-bottom:18px; cursor:pointer; transition:all 0.25s;
+ box-shadow:0 2px 16px rgba(0,0,0,0.4); }
+ .post:hover { border-color:var(--pink); box-shadow:0 0 22px rgba(255,107,157,0.3),0 4px 24px rgba(0,0,0,0.5); transform:translateY(-3px); }
+ .post-active { border-color:var(--orange) !important; background:rgba(30,15,60,0.96) !important;
+ box-shadow:0 0 22px rgba(255,140,66,0.4),inset 3px 0 0 var(--orange) !important; }
+ .post-header { display:flex; justify-content:space-between; margin-bottom:14px; }
+ .post-time { color:var(--orange); font-family:'Share Tech Mono',monospace; font-size:0.85rem; }
+ .post-text { line-height:1.6; font-size:0.95rem; font-family:'Share Tech Mono',monospace; }
+ .post-text a { color:var(--pink); text-decoration:none; }
+ .post-audio { width:100%; margin-top:10px; }
+ .post-modal { display:none; position:fixed; inset:0; z-index:100;
+ background:rgba(10,1,33,0.96); overflow-y:auto; padding:40px 20px; }
+ .post-modal.active { display:block; }
+ .modal-inner { max-width:780px; margin:0 auto; background:rgba(20,10,55,0.98);
+ border:2px solid var(--pink); border-radius:12px;
+ box-shadow:0 0 60px rgba(255,107,157,0.25); padding:38px; }
+ .modal-close { float:right; background:none; border:none; color:var(--orange);
+ font-family:'Orbitron',sans-serif; font-size:0.9rem; cursor:pointer; letter-spacing:2px; }
+ @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 18px;} }
+ [data-sno-theme="retrofuture"] .splash-overlay {
+ background: radial-gradient(ellipse at 50% 60%, #1a0840 0%, var(--bg) 55%, #050010 100%);
+ }
+ [data-sno-theme="retrofuture"] .splash-starburst {
+ position:absolute; inset:0; pointer-events:none; z-index:1;
+ }
+ [data-sno-theme="retrofuture"] .splash-starburst span {
+ position:absolute; top:50%; left:50%; width:3px; height:3px;
+ border-radius:50%; background:var(--pink); opacity:0.5;
+ box-shadow:0 0 6px var(--pink);
+ animation: starTwinkle 3s ease-in-out infinite;
+ }
+ @keyframes starTwinkle {
+ 0%,100%{opacity:0.3;transform:scale(1);}
+ 50%{opacity:0.8;transform:scale(1.4);}
+ }
+ [data-sno-theme="retrofuture"] .splash-atomic {
+ width:min(100px,22vw); height:min(100px,22vw); margin:0 auto 1rem;
+ border-radius:50%; border:3px solid var(--purple);
+ box-shadow:0 0 20px var(--purple),0 0 40px rgba(0,217,192,0.3),inset 0 0 20px rgba(0,217,192,0.15);
+ position:relative; animation:splashAtomicPulse 2.5s ease-in-out infinite alternate;
+ }
+ [data-sno-theme="retrofuture"] .splash-atomic::before,
+ [data-sno-theme="retrofuture"] .splash-atomic::after {
+ content:''; position:absolute; border:2px solid var(--purple); border-radius:50%;
+ top:50%; left:50%; transform:translate(-50%,-50%);
+ }
+ [data-sno-theme="retrofuture"] .splash-atomic::before {
+ width:160%; height:30%; opacity:0.7;
+ box-shadow:0 0 10px var(--purple);
+ }
+ [data-sno-theme="retrofuture"] .splash-atomic::after {
+ width:30%; height:160%; opacity:0.7;
+ box-shadow:0 0 10px var(--purple);
+ }
+ @keyframes splashAtomicPulse {
+ from{transform:scale(0.95);box-shadow:0 0 15px var(--purple),0 0 30px rgba(0,217,192,0.2),inset 0 0 15px rgba(0,217,192,0.1);}
+ to{transform:scale(1.05);box-shadow:0 0 25px var(--purple),0 0 50px rgba(0,217,192,0.4),inset 0 0 25px rgba(0,217,192,0.2);}
+ }
+ [data-sno-theme="retrofuture"] .splash-title {
+ font-family:'Orbitron',sans-serif; font-size:clamp(1.4rem,4.5vw,2rem);
+ color:#f0efe4; letter-spacing:4px; text-shadow:0 0 20px rgba(255,107,157,0.6);
+ }
+ [data-sno-theme="retrofuture"] .splash-tag { font-family:'Share Tech Mono',monospace; color:var(--purple); }
+ [data-sno-theme="retrofuture"] .splash-hint { font-family:'Share Tech Mono',monospace; color:rgba(240,239,228,0.8); }
+ [data-sno-theme="retrofuture"] .splash-inner { text-shadow:0 2px 24px rgba(10,1,33,0.9); }
diff --git a/internal/generator/templates/themes/retrofuture/theme.js b/internal/generator/templates/themes/retrofuture/theme.js
new file mode 100644
index 0000000..057de32
--- /dev/null
+++ b/internal/generator/templates/themes/retrofuture/theme.js
@@ -0,0 +1,193 @@
+
+ (function(){
+ if(document.documentElement.classList.contains('sno-splash-skip'))return;
+ var cv=document.getElementById('splash-gl-canvas');
+ if(!cv||typeof THREE==='undefined')return;
+ var raf,ren,sc,ca,g=new THREE.Group(),t0=performance.now();
+ function cleanup(){window.removeEventListener('resize',sz);if(raf)cancelAnimationFrame(raf);raf=null;if(ren)ren.dispose();ren=null;window._snonuxSplashWebGLCleanup=null;}
+ window._snonuxSplashWebGLCleanup=cleanup;
+ function sz(){var w=cv.clientWidth||2,h=cv.clientHeight||2;if(ren)ren.setSize(w,h,false);if(ca){ca.aspect=w/h;ca.updateProjectionMatrix();}}
+ ren=new THREE.WebGLRenderer({canvas:cv,antialias:true,alpha:true});ren.setClearColor(0,0);ren.setPixelRatio(Math.min(window.devicePixelRatio||1,2));
+ sc=new THREE.Scene();ca=new THREE.PerspectiveCamera(58,1,0.1,120);ca.position.set(0,1.2,7);
+ var atomic=new THREE.Mesh(new THREE.SphereGeometry(1.35,28,28),new THREE.MeshBasicMaterial({color:0x00d9c0,transparent:true,opacity:0.9}));
+ atomic.position.y=0;g.add(atomic);
+ var ringH=new THREE.Mesh(new THREE.TorusGeometry(2.1,0.06,8,64),new THREE.MeshBasicMaterial({color:0xff6b9d,transparent:true,opacity:0.8}));
+ g.add(ringH);
+ var ringV=new THREE.Mesh(new THREE.TorusGeometry(2.1,0.06,8,64),new THREE.MeshBasicMaterial({color:0xff6b9d,transparent:true,opacity:0.8}));
+ ringV.rotation.x=Math.PI/2;g.add(ringV);
+ var gr=new THREE.Mesh(new THREE.PlaneGeometry(28,28,20,20),new THREE.MeshBasicMaterial({color:0x00d9c0,wireframe:true,transparent:true,opacity:0.25}));
+ gr.rotation.x=-Math.PI/2.4;gr.position.y=-2.8;g.add(gr);
+ sc.add(g);sz();window.addEventListener('resize',sz);
+ function loop(now){raf=requestAnimationFrame(loop);var t=(now-t0)*0.001;g.rotation.y=Math.sin(t*0.3)*0.12;atomic.position.y=Math.sin(t*1.1)*0.12;ringH.scale.setScalar(1+Math.sin(t*2.2)*0.06);ringV.scale.setScalar(1+Math.cos(t*2.2)*0.06);ren.render(sc,ca);}
+ raf=requestAnimationFrame(loop);
+ })();
+
+
+ // Retrofuture WebGL: atomic orb with crossed electron rings, receding teal grid floor,
+ // chrome metallic star particles, and a slow drifting camera orbit.
+ (function() {
+ var _wild = false, _snoTOffset = 0, _snoLastT = 0;
+ var scene, camera, renderer, clock;
+ var atomic, ringH, ringV, stars;
+
+ function initThree() {
+ scene = new THREE.Scene();
+ scene.background = new THREE.Color(0x0a0121);
+ scene.fog = new THREE.Fog(0x0a0121, 60, 180);
+
+ camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 300);
+ camera.position.set(0, 10, 45);
+ camera.lookAt(0, -5, -10);
+
+ renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('three-canvas'), antialias: true });
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
+ clock = new THREE.Clock();
+
+ // Glowing atomic teal orb
+ atomic = new THREE.Mesh(
+ new THREE.SphereGeometry(12, 32, 16),
+ new THREE.MeshBasicMaterial({ color: 0x00d9c0 })
+ );
+ atomic.position.set(0, -8, -55);
+ scene.add(atomic);
+
+ // Horizontal electron ring — pink
+ ringH = new THREE.Mesh(
+ new THREE.TorusGeometry(15, 0.15, 8, 64),
+ new THREE.MeshBasicMaterial({ color: 0xff6b9d })
+ );
+ ringH.position.copy(atomic.position);
+ scene.add(ringH);
+
+ // Vertical electron ring — coral
+ ringV = new THREE.Mesh(
+ new THREE.TorusGeometry(15, 0.15, 8, 64),
+ new THREE.MeshBasicMaterial({ color: 0xff8c42 })
+ );
+ ringV.position.copy(atomic.position);
+ ringV.rotation.x = Math.PI / 2;
+ scene.add(ringV);
+
+ // Receding grid floor — teal
+ var grid = new THREE.GridHelper(200, 40, 0x00d9c0, 0x1a0840);
+ grid.position.set(0, -18, -30);
+ scene.add(grid);
+
+ // 1200 chrome star particles scattered in a sphere shell
+ var starPos = new Float32Array(1200 * 3);
+ for (var j = 0; j < 1200 * 3; j += 3) {
+ var r = 80 + Math.random() * 40;
+ var theta = Math.random() * Math.PI * 2;
+ var phi = Math.acos(2 * Math.random() - 1);
+ starPos[j] = r * Math.sin(phi) * Math.cos(theta);
+ starPos[j+1] = r * Math.sin(phi) * Math.sin(theta);
+ starPos[j+2] = r * Math.cos(phi);
+ }
+ var starGeo = new THREE.BufferGeometry();
+ starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3));
+ scene.add(new THREE.Points(starGeo, new THREE.PointsMaterial({
+ color: 0xffd700, size: 0.22, transparent: true, opacity: 0.8
+ })));
+
+ window.addEventListener('resize', onResize);
+ animate();
+ }
+
+ function onResize() {
+ camera.aspect = window.innerWidth / window.innerHeight;
+ camera.updateProjectionMatrix();
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ }
+
+ function animate() {
+ requestAnimationFrame(animate);
+ var realT = clock.getElapsedTime();
+ _snoTOffset += (realT - _snoLastT) * (_wild ? 9 : 0);
+ _snoLastT = realT;
+ var t = realT + _snoTOffset;
+ // Atomic orb pulses; wild meltdown mode makes it throb intensely
+ var pulse = _wild ? 1 + 0.1 * Math.sin(t * 12) : 1 + 0.015 * Math.sin(t * 1.5);
+ atomic.scale.setScalar(pulse);
+ ringH.rotation.z = t * 0.4;
+ ringV.rotation.z = -t * 0.3;
+ // Rings tilt chaotically in wild — full 3D tumble
+ if (_wild) {
+ ringH.rotation.x = Math.sin(realT * 2.3) * 0.8;
+ ringV.rotation.y = Math.cos(realT * 1.9) * 0.9;
+ } else {
+ ringH.rotation.x = 0;
+ ringV.rotation.y = 0;
+ }
+ // Camera: meltdown spiral in wild, slow orbit otherwise
+ if (_wild) {
+ camera.position.x = Math.sin(realT * 0.36) * 16;
+ camera.position.y = 10 + Math.sin(realT * 0.29) * 10;
+ camera.position.z = 45 + Math.sin(realT * 0.22) * 16;
+ camera.fov = 60 + Math.sin(realT * 0.47) * 18;
+ camera.updateProjectionMatrix();
+ } else {
+ camera.position.x = Math.sin(t * 0.07) * 5;
+ camera.position.y = 10 + Math.sin(t * 0.05) * 2;
+ camera.position.z = 45;
+ if (camera.fov !== 60) { camera.fov = 60; camera.updateProjectionMatrix(); }
+ }
+ camera.lookAt(0, -5, -10);
+ renderer.render(scene, camera);
+ }
+
+ initThree();
+
+ // Retrofuture nav/wild effects — atomic pulse on navigate, meltdown on wild
+ window.snonuxOpenEffect = function(post) {
+ // Atomic rings expand outward from post — zoom into modal
+ var modal = document.getElementById('post-modal');
+ if (modal) { modal.classList.add('sno-modal-zoom'); setTimeout(function() { modal.classList.remove('sno-modal-zoom'); }, 400); }
+ var r = post ? post.getBoundingClientRect() : {left: window.innerWidth/2, top: window.innerHeight/2, width: 0, height: 0};
+ [0, 80, 160].forEach(function(delay) {
+ var ring = document.createElement('div');
+ ring.style.cssText = 'position:fixed;top:' + (r.top+r.height/2-6) + 'px;left:' + (r.left+r.width/2-6) + 'px;z-index:997;pointer-events:none;width:12px;height:12px;border-radius:50%;border:2px solid rgba(0,217,192,0.7);transition:all 0.42s ease,opacity 0.42s';
+ document.body.appendChild(ring);
+ setTimeout(function() { ring.style.transform='scale(25)'; ring.style.opacity='0'; setTimeout(function() { ring.remove(); }, 460); }, delay + 15);
+ });
+ };
+ window.snonuxCloseEffect = function() {
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(0,217,192,0.1);transition:opacity 0.2s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 230); }, 15);
+ };
+ window.snonuxScrollEffect = function(dir) {
+ var isDown = dir === 'down';
+ var thick = _wild ? '14px' : '5px';
+ var d = document.createElement('div');
+ // Retrofuture: atomic orange-gold sweep
+ d.style.cssText = 'position:fixed;left:0;right:0;height:' + thick + ';z-index:9000;pointer-events:none;' +
+ 'background:linear-gradient(90deg,transparent,rgba(255,140,0,0.9),rgba(255,80,0,0.9),rgba(255,140,0,0.9),transparent);' +
+ (isDown ? 'top:0;' : 'bottom:0;') +
+ 'transition:transform 0.3s ease,opacity 0.3s ease;';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.transform = isDown ? 'translateY(100vh)' : 'translateY(-100vh)'; d.style.opacity='0'; }, 16);
+ setTimeout(function() { d.remove(); }, 380);
+ };
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ };
+ window.snonuxNavEffect = function() {
+ // Atomic pulse — rings expand briefly as CSS overlay
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) scale(0.1);z-index:998;pointer-events:none;width:100vmax;height:100vmax;border-radius:50%;border:3px solid rgba(0,217,192,0.7);transition:transform 0.3s ease,opacity 0.3s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.transform='translate(-50%,-50%) scale(1.2)'; d.style.opacity='0'; setTimeout(function() { d.remove(); }, 330); }, 15);
+ };
+ window.snonuxPageEffect = function() {
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-zoom'); setTimeout(function() { ov.classList.remove('sno-fx-zoom'); }, 330); }
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(0,217,192,0.15);transition:opacity 0.2s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 230); }, 20);
+ };
+ })();
diff --git a/internal/generator/templates/themes/spaceage/meta.json b/internal/generator/templates/themes/spaceage/meta.json
new file mode 100644
index 0000000..008bc8a
--- /dev/null
+++ b/internal/generator/templates/themes/spaceage/meta.json
@@ -0,0 +1,7 @@
+{
+ "title": "SNONUX.FOO // SPACE AGE",
+ "header_html": "\u003cdiv class=\"logo\"\u003e\n \u003cspan class=\"logo-mark\"\u003e[SN]\u003c/span\u003e\n \u003cdiv class=\"logo-title\"\u003e\n \u003ch1\u003eSNONUX.FOO\u003c/h1\u003e\n \u003cp class=\"subtitle\"\u003eMICROBLOG / \u003ca href=\"https://foo.zone\"\u003eFOO.ZONE\u003c/a\u003e IS THE REAL BLOG\u003c/p\u003e\n \u003cp class=\"logo-host\"\u003eServed by NetBSD on a Raspberry Pi 3\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"nav\"\u003e\n \u003ca href=\"atom.xml\" class=\"header-feed-link\" rel=\"alternate\" title=\"Atom feed\" type=\"application/atom+xml\"\u003eAtom feed\u003c/a\u003e\n \u003ca href=\"https://foo.zone/about\" class=\"transmit-btn\"\u003eTRANSMIT\u003c/a\u003e\n \u003c/div\u003e",
+ "splash_inner_html": "\u003ccanvas class=\"splash-gl-canvas\" id=\"splash-gl-canvas\" aria-hidden=\"true\"\u003e\u003c/canvas\u003e\n \u003cdiv class=\"splash-inner\"\u003e\n \u003cdiv class=\"splash-title\"\u003eSTARBASE SNONUX\u003c/div\u003e\n \u003cdiv class=\"splash-tag\"\u003eOrbital uplink established\u003c/div\u003e\n \u003cdiv class=\"splash-hint\"\u003ePress Enter or click to dock\u003c/div\u003e\n \u003c/div\u003e",
+ "prev_page_text": "\u0026lt;-- NEWER",
+ "next_page_text": "OLDER --\u0026gt;"
+}
diff --git a/internal/generator/templates/themes/spaceage/theme.css b/internal/generator/templates/themes/spaceage/theme.css
new file mode 100644
index 0000000..b659f55
--- /dev/null
+++ b/internal/generator/templates/themes/spaceage/theme.css
@@ -0,0 +1,80 @@
+ :root { --teal:#00e8e8; --dim:#1a4455; --red:#ff3320; --silver:#c8d8e0; --bg:#030a0f; --bg2:#020608; }
+ * { margin:0; padding:0; box-sizing:border-box; }
+ body { font-family:'Space Mono','Courier New',monospace; background:var(--bg); color:var(--silver);
+ overflow:hidden; height:100vh; }
+ /* Subtle horizontal scanlines — lighter than retro, cleaner space feel */
+ body::before { content:''; position:fixed; inset:0; z-index:999; pointer-events:none;
+ background:repeating-linear-gradient(0deg,transparent,transparent 3px,
+ rgba(0,0,0,0.08) 3px,rgba(0,0,0,0.08) 4px); }
+ #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
+ .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
+ header { padding:12px 24px; background:rgba(2,6,8,0.88); backdrop-filter:blur(8px);
+ border-bottom:2px solid var(--teal);
+ display:flex; align-items:center; justify-content:space-between; }
+ .logo { display:flex; align-items:center; gap:14px; }
+ .logo-mark { font-size:1.5rem; color:var(--teal); text-shadow:0 0 18px var(--teal);
+ letter-spacing:3px; font-weight:700; }
+ .logo-title h1 { font-size:1.1rem; color:var(--teal); text-shadow:0 0 12px var(--teal);
+ letter-spacing:5px; font-weight:700; }
+ .logo-title .subtitle { font-size:0.68rem; color:var(--dim); margin-top:3px; letter-spacing:1px; }
+ .logo-title .subtitle a { color:var(--teal); text-decoration:none; opacity:0.8; }
+ .logo-title .subtitle a:hover { opacity:1; text-shadow:0 0 6px var(--teal); }
+ /* HAL eye: red dot beside the transmit button */
+ .transmit-btn { position:relative; border:1px solid var(--teal); color:var(--teal); padding:8px 18px;
+ text-decoration:none; font-size:0.78rem; letter-spacing:3px;
+ transition:all 0.15s; }
+ .transmit-btn::before { content:'●'; color:var(--red); text-shadow:0 0 8px var(--red);
+ position:absolute; left:-18px; top:50%; transform:translateY(-50%);
+ font-size:0.65rem; animation:hal-blink 4s ease-in-out infinite; }
+ @keyframes hal-blink { 0%,90%,100%{opacity:1} 95%{opacity:0.2} }
+ .transmit-btn:hover { background:var(--teal); color:var(--bg); }
+ a.header-feed-link { color:var(--dim); font-size:0.78rem; letter-spacing:1px; }
+ a.header-feed-link:hover { color:var(--teal); }
+ .nav-hints { background:rgba(2,6,8,0.82); border-bottom:1px solid var(--dim);
+ color:var(--dim); padding:4px 24px; display:flex; gap:18px;
+ font-size:0.66rem; flex-wrap:wrap; letter-spacing:1px; }
+ .nav-hints kbd { background:transparent; border:1px solid var(--dim); color:var(--teal);
+ padding:0 5px; font-size:0.66rem; margin:0 2px; }
+ .content { flex:1; overflow-y:auto; padding:16px 24px;
+ scrollbar-width:thin; scrollbar-color:var(--dim) var(--bg); }
+ .page-nav { display:flex; justify-content:center; margin:12px 0; }
+ .page-nav a { border:1px solid var(--dim); color:var(--teal); padding:7px 22px;
+ text-decoration:none; font-size:0.78rem; letter-spacing:3px; }
+ .page-nav a:hover { background:var(--teal); color:var(--bg); border-color:var(--teal); }
+ .page-nav-footer { flex-shrink:0; padding:6px 24px; display:flex; justify-content:center;
+ background:rgba(2,6,8,0.88); backdrop-filter:blur(8px); border-top:2px solid var(--teal); }
+ .post { background:rgba(3,10,15,0.82); border:1px solid var(--dim); padding:16px 18px;
+ margin-bottom:10px; cursor:pointer; transition:border-color 0.2s, box-shadow 0.2s; }
+ .post:hover { border-color:var(--teal); box-shadow:0 0 12px rgba(0,232,232,0.18); }
+ .post-active { border-color:var(--teal) !important;
+ background:rgba(0,232,232,0.04) !important;
+ box-shadow:0 0 18px rgba(0,232,232,0.28),inset 3px 0 0 var(--teal) !important; }
+ .post-header { display:flex; justify-content:space-between; margin-bottom:10px; font-size:0.82rem; }
+ .post-time { color:var(--dim); font-size:0.75rem; letter-spacing:1px; }
+ .post-text { line-height:1.65; font-size:0.86rem; color:var(--silver); }
+ .post-text a { color:var(--teal); text-decoration:underline; }
+ .post-image { max-width:100%; margin-top:10px; border:1px solid var(--dim);
+ filter:saturate(0.85) brightness(0.92); }
+ .post-audio { width:100%; margin-top:10px; }
+ .post-modal { display:none; position:fixed; inset:0; z-index:100;
+ background:rgba(2,6,8,0.97); overflow-y:auto; padding:40px 20px; }
+ .post-modal.active { display:block; }
+ .modal-inner { max-width:740px; margin:0 auto; background:var(--bg);
+ border:1px solid var(--teal); padding:36px;
+ box-shadow:0 0 50px rgba(0,232,232,0.18); }
+ .modal-close { float:right; background:none; border:none; color:var(--dim);
+ font-family:'Space Mono',monospace; font-size:0.85rem; cursor:pointer; letter-spacing:3px; }
+ @media(max-width:640px) { .nav-hints{display:none;} header{padding:10px 16px;} .content{padding:10px 16px;} }
+ /* Splash screen: space age orbital */
+ [data-sno-theme="spaceage"] .splash-overlay { background:var(--bg); }
+ [data-sno-theme="spaceage"] .splash-inner { position:relative; z-index:1; }
+ [data-sno-theme="spaceage"] .splash-title {
+ font-family:'Space Mono',monospace; font-weight:700;
+ font-size:clamp(1.1rem,3.5vw,1.6rem); color:var(--teal);
+ text-shadow:0 0 20px var(--teal),0 0 40px rgba(0,232,232,0.4);
+ letter-spacing:0.35em;
+ animation: spaceagePulse 3s ease-in-out infinite;
+ }
+ @keyframes spaceagePulse { 0%,100%{text-shadow:0 0 20px var(--teal),0 0 40px rgba(0,232,232,0.4)} 50%{text-shadow:0 0 30px var(--teal),0 0 60px rgba(0,232,232,0.6)} }
+ [data-sno-theme="spaceage"] .splash-tag { color:var(--silver); letter-spacing:2px; font-family:'Space Mono',monospace; font-size:0.78rem; }
+ [data-sno-theme="spaceage"] .splash-hint { color:var(--dim); letter-spacing:2px; font-family:'Space Mono',monospace; font-size:0.72rem; }
diff --git a/internal/generator/templates/themes/spaceage.tmpl b/internal/generator/templates/themes/spaceage/theme.js
index a71f057..4bcad10 100644
--- a/internal/generator/templates/themes/spaceage.tmpl
+++ b/internal/generator/templates/themes/spaceage/theme.js
@@ -1,107 +1,4 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>SNONUX.FOO // SPACE AGE</title>
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
- <style>
- :root { --teal:#00e8e8; --dim:#1a4455; --red:#ff3320; --silver:#c8d8e0; --bg:#030a0f; --bg2:#020608; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:'Space Mono','Courier New',monospace; background:var(--bg); color:var(--silver);
- overflow:hidden; height:100vh; }
- /* Subtle horizontal scanlines — lighter than retro, cleaner space feel */
- body::before { content:''; position:fixed; inset:0; z-index:999; pointer-events:none;
- background:repeating-linear-gradient(0deg,transparent,transparent 3px,
- rgba(0,0,0,0.08) 3px,rgba(0,0,0,0.08) 4px); }
- #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
- .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
- header { padding:12px 24px; background:rgba(2,6,8,0.88); backdrop-filter:blur(8px);
- border-bottom:2px solid var(--teal);
- display:flex; align-items:center; justify-content:space-between; }
- .logo { display:flex; align-items:center; gap:14px; }
- .logo-mark { font-size:1.5rem; color:var(--teal); text-shadow:0 0 18px var(--teal);
- letter-spacing:3px; font-weight:700; }
- .logo-title h1 { font-size:1.1rem; color:var(--teal); text-shadow:0 0 12px var(--teal);
- letter-spacing:5px; font-weight:700; }
- .logo-title .subtitle { font-size:0.68rem; color:var(--dim); margin-top:3px; letter-spacing:1px; }
- .logo-title .subtitle a { color:var(--teal); text-decoration:none; opacity:0.8; }
- .logo-title .subtitle a:hover { opacity:1; text-shadow:0 0 6px var(--teal); }
- /* HAL eye: red dot beside the transmit button */
- .transmit-btn { position:relative; border:1px solid var(--teal); color:var(--teal); padding:8px 18px;
- text-decoration:none; font-size:0.78rem; letter-spacing:3px;
- transition:all 0.15s; }
- .transmit-btn::before { content:'●'; color:var(--red); text-shadow:0 0 8px var(--red);
- position:absolute; left:-18px; top:50%; transform:translateY(-50%);
- font-size:0.65rem; animation:hal-blink 4s ease-in-out infinite; }
- @keyframes hal-blink { 0%,90%,100%{opacity:1} 95%{opacity:0.2} }
- .transmit-btn:hover { background:var(--teal); color:var(--bg); }
- a.header-feed-link { color:var(--dim); font-size:0.78rem; letter-spacing:1px; }
- a.header-feed-link:hover { color:var(--teal); }
- .nav-hints { background:rgba(2,6,8,0.82); border-bottom:1px solid var(--dim);
- color:var(--dim); padding:4px 24px; display:flex; gap:18px;
- font-size:0.66rem; flex-wrap:wrap; letter-spacing:1px; }
- .nav-hints kbd { background:transparent; border:1px solid var(--dim); color:var(--teal);
- padding:0 5px; font-size:0.66rem; margin:0 2px; }
- .content { flex:1; overflow-y:auto; padding:16px 24px;
- scrollbar-width:thin; scrollbar-color:var(--dim) var(--bg); }
- .page-nav { display:flex; justify-content:center; margin:12px 0; }
- .page-nav a { border:1px solid var(--dim); color:var(--teal); padding:7px 22px;
- text-decoration:none; font-size:0.78rem; letter-spacing:3px; }
- .page-nav a:hover { background:var(--teal); color:var(--bg); border-color:var(--teal); }
- .page-nav-footer { flex-shrink:0; padding:6px 24px; display:flex; justify-content:center;
- background:rgba(2,6,8,0.88); backdrop-filter:blur(8px); border-top:2px solid var(--teal); }
- .post { background:rgba(3,10,15,0.82); border:1px solid var(--dim); padding:16px 18px;
- margin-bottom:10px; cursor:pointer; transition:border-color 0.2s, box-shadow 0.2s; }
- .post:hover { border-color:var(--teal); box-shadow:0 0 12px rgba(0,232,232,0.18); }
- .post-active { border-color:var(--teal) !important;
- background:rgba(0,232,232,0.04) !important;
- box-shadow:0 0 18px rgba(0,232,232,0.28),inset 3px 0 0 var(--teal) !important; }
- .post-header { display:flex; justify-content:space-between; margin-bottom:10px; font-size:0.82rem; }
- .post-time { color:var(--dim); font-size:0.75rem; letter-spacing:1px; }
- .post-text { line-height:1.65; font-size:0.86rem; color:var(--silver); }
- .post-text a { color:var(--teal); text-decoration:underline; }
- .post-image { max-width:100%; margin-top:10px; border:1px solid var(--dim);
- filter:saturate(0.85) brightness(0.92); }
- .post-audio { width:100%; margin-top:10px; }
- .post-modal { display:none; position:fixed; inset:0; z-index:100;
- background:rgba(2,6,8,0.97); overflow-y:auto; padding:40px 20px; }
- .post-modal.active { display:block; }
- .modal-inner { max-width:740px; margin:0 auto; background:var(--bg);
- border:1px solid var(--teal); padding:36px;
- box-shadow:0 0 50px rgba(0,232,232,0.18); }
- .modal-close { float:right; background:none; border:none; color:var(--dim);
- font-family:'Space Mono',monospace; font-size:0.85rem; cursor:pointer; letter-spacing:3px; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:10px 16px;} .content{padding:10px 16px;} }
- /* Splash screen: space age orbital */
- .splash-overlay.splash-spaceage { background:var(--bg); }
- .splash-spaceage .splash-inner { position:relative; z-index:1; }
- .splash-spaceage .splash-title {
- font-family:'Space Mono',monospace; font-weight:700;
- font-size:clamp(1.1rem,3.5vw,1.6rem); color:var(--teal);
- text-shadow:0 0 20px var(--teal),0 0 40px rgba(0,232,232,0.4);
- letter-spacing:0.35em;
- animation: spaceagePulse 3s ease-in-out infinite;
- }
- @keyframes spaceagePulse { 0%,100%{text-shadow:0 0 20px var(--teal),0 0 40px rgba(0,232,232,0.4)} 50%{text-shadow:0 0 30px var(--teal),0 0 60px rgba(0,232,232,0.6)} }
- .splash-spaceage .splash-tag { color:var(--silver); letter-spacing:2px; font-family:'Space Mono',monospace; font-size:0.78rem; }
- .splash-spaceage .splash-hint { color:var(--dim); letter-spacing:2px; font-family:'Space Mono',monospace; font-size:0.72rem; }
-{{template "navSharedCSSInner"}}
- </style>
-</head>
-<body>
- {{template "splashGate"}}
- <div id="splash-overlay" class="splash-overlay splash-spaceage" role="dialog" aria-modal="true" aria-label="Open microblog" tabindex="-1">
- <canvas class="splash-gl-canvas" id="splash-gl-canvas" aria-hidden="true"></canvas>
- <div class="splash-inner">
- <div class="splash-title">STARBASE SNONUX</div>
- <div class="splash-tag">Orbital uplink established</div>
- <div class="splash-hint">Press Enter or click to dock</div>
- </div>
- </div>
- <script>
+
// Splash WebGL: slowly rotating torus (space station ring) + star field.
(function(){
if(document.documentElement.classList.contains('sno-splash-skip'))return;
@@ -123,46 +20,8 @@
function loop(now){raf=requestAnimationFrame(loop);var t=(now-t0)*0.001;g.rotation.x=t*0.28;g.rotation.y=t*0.45;hub.rotation.z=t*1.1;ren.render(sc,ca);}
raf=requestAnimationFrame(loop);
})();
- </script>
- <canvas id="three-canvas"></canvas>
- <div class="overlay">
- <header>
- <div class="logo">
- <span class="logo-mark">[SN]</span>
- <div class="logo-title">
- <h1>SNONUX.FOO</h1>
- <p class="subtitle">MICROBLOG / <a href="https://foo.zone">FOO.ZONE</a> IS THE REAL BLOG</p>
- <p class="logo-host">Served by NetBSD on a Raspberry Pi 3</p>
- </div>
- </div>
- <div class="nav">
- <a href="atom.xml" class="header-feed-link" rel="alternate" title="Atom feed" type="application/atom+xml">Atom feed</a>
- <a href="https://foo.zone/about" class="transmit-btn">TRANSMIT</a>
- </div>
- </header>
- {{template "navhints" .}}
- <div class="content" id="post-content">
- {{range $i, $post := .Posts}}
- <div class="post" id="post-{{$post.ID}}" data-index="{{$i}}">
- <div class="post-header">
- <div><strong>@SNONUX</strong></div>
- <div class="post-time">{{$post.FormattedTime}}</div>
- </div>
- <div class="post-text">{{$post.ContentHTML}}</div>
- </div>
- {{end}}
- </div>
- {{if or .PrevPage .NextPage}}
- <footer class="page-nav-footer" aria-label="Pagination">
- <div class="page-nav page-nav-dual">
- {{if .PrevPage}}<a href="{{.PrevPage}}">&lt;-- NEWER</a>{{end}}
- {{if .NextPage}}<a href="{{.NextPage}}">OLDER --&gt;</a>{{end}}
- </div>
- </footer>
- {{end}}
- </div>
- {{template "navmodal" .}}
- <script>
+
+
// Space Age WebGL: toroidal space station ring + three satellite pods orbiting it
// + a slowly rotating planet sphere + dense star field. Teal wireframe throughout.
(function() {
@@ -344,7 +203,3 @@
setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 230); }, 20);
};
})();
- </script>
- {{template "navscript" .}}
-</body>
-</html>
diff --git a/internal/generator/templates/themes/surveillance.tmpl b/internal/generator/templates/themes/surveillance.tmpl
deleted file mode 100644
index 5d0df8d..0000000
--- a/internal/generator/templates/themes/surveillance.tmpl
+++ /dev/null
@@ -1,224 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>snonux.foo // SURVEILLANCE</title>
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
- <link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&display=swap" rel="stylesheet">
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
- <style>
- :root { --phosphor:#bcffd4; --green:#63f3a8; --grey:#88a197; --alert:#ff4d5c; --bg:#09100d; --panel:#101916; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:'Share Tech Mono','Courier New',monospace; background:var(--bg); color:var(--phosphor); overflow:hidden; height:100vh; }
- body::before { content:''; position:fixed; inset:0; z-index:999; pointer-events:none; background:repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(188,255,212,0.035) 2px, rgba(188,255,212,0.035) 3px); opacity:0.72; }
- #three-canvas { position:fixed; inset:0; width:100%; height:100%; z-index:1; }
- .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
- header { padding:14px 24px; background:rgba(9,16,13,0.84); backdrop-filter:blur(8px); border-bottom:1px solid rgba(99,243,168,0.18); display:flex; align-items:center; justify-content:space-between; }
- .logo { display:flex; align-items:center; gap:14px; }
- .logo-mark { font-size:1.55rem; color:var(--green); }
- .logo-title h1 { font-size:1.15rem; color:var(--green); letter-spacing:0.24em; }
- .logo-title .subtitle { font-size:0.72rem; color:rgba(188,255,212,0.5); margin-top:2px; }
- .logo-title .subtitle a { color:var(--grey); text-decoration:none; }
- .logo-title .subtitle a:hover { color:var(--green); }
- .transmit-btn { border:1px solid rgba(99,243,168,0.22); color:var(--green); padding:8px 14px; text-decoration:none; font-size:0.76rem; letter-spacing:0.24em; text-transform:uppercase; transition:all 0.18s; }
- .transmit-btn:hover { background:rgba(99,243,168,0.12); }
- a.header-feed-link { color:var(--grey); }
- a.header-feed-link:hover { color:var(--green); }
- .nav-hints { background:rgba(10,18,14,0.74); border-bottom:1px solid rgba(99,243,168,0.08); color:rgba(188,255,212,0.42); padding:5px 24px; display:flex; gap:18px; font-size:0.66rem; flex-wrap:wrap; }
- .nav-hints kbd { background:#0c1511; border:1px solid rgba(99,243,168,0.2); color:var(--green); padding:0 5px; margin:0 2px; }
- .content { flex:1; overflow-y:auto; padding:18px 24px; scrollbar-width:thin; scrollbar-color:#4b8d68 #0d1512; }
- .page-nav { display:flex; justify-content:center; margin:12px 0; }
- .page-nav a { border:1px solid rgba(99,243,168,0.2); color:var(--green); padding:7px 16px; text-decoration:none; font-size:0.76rem; letter-spacing:0.22em; text-transform:uppercase; }
- .page-nav a:hover { background:rgba(99,243,168,0.08); }
- .page-nav-footer { flex-shrink:0; padding:8px 24px; display:flex; justify-content:center; background:rgba(9,16,13,0.84); backdrop-filter:blur(8px); border-top:1px solid rgba(99,243,168,0.18); }
- .post { background:linear-gradient(180deg, rgba(16,25,22,0.92), rgba(9,15,12,0.92)); border:1px solid rgba(99,243,168,0.08); padding:18px; margin-bottom:12px; cursor:pointer; position:relative; transition:border-color 0.18s, box-shadow 0.18s; }
- .post::after { content:''; position:absolute; inset:8px; border:1px solid rgba(99,243,168,0.06); pointer-events:none; }
- .post:hover { border-color:rgba(99,243,168,0.22); box-shadow:0 0 18px rgba(99,243,168,0.1); }
- .post-active { border-color:rgba(99,243,168,0.34) !important; background:linear-gradient(180deg, rgba(10,25,18,0.96), rgba(7,14,10,0.95)) !important;
- box-shadow:0 0 0 1px rgba(99,243,168,0.1), 0 16px 34px rgba(0,0,0,0.32), inset 4px 0 0 var(--green) !important; }
- .post-header { display:flex; justify-content:space-between; margin-bottom:10px; font-size:0.8rem; }
- .post-header strong, .post-time { color:var(--green); }
- .post-text { line-height:1.68; font-size:0.9rem; color:var(--phosphor); }
- .post-text a { color:var(--green); text-decoration:none; border-bottom:1px solid rgba(99,243,168,0.18); }
- .post-image { margin-top:10px; border:1px solid rgba(99,243,168,0.1); filter:saturate(0.6) contrast(1.12) hue-rotate(-16deg); }
- .post-audio { width:100%; margin-top:10px; filter:grayscale(0.7); }
- .post-modal { display:none; position:fixed; inset:0; z-index:100; overflow-y:auto; padding:40px 20px; }
- .post-modal.active { display:block; }
- .modal-inner { max-width:760px; margin:0 auto; background:rgba(8,14,11,0.98); border:1px solid rgba(99,243,168,0.2); padding:34px; box-shadow:0 20px 72px rgba(0,0,0,0.72); }
- .modal-close { float:right; background:none; border:none; color:var(--green); font-family:'Share Tech Mono',monospace; font-size:0.76rem; cursor:pointer; letter-spacing:0.2em; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 16px;} .content{padding:14px 16px;} .modal-inner{padding:24px 16px;} }
- .splash-overlay.splash-surveillance {
- background:
- radial-gradient(circle at 50% 22%, rgba(99,243,168,0.12) 0%, transparent 34%),
- linear-gradient(180deg, #09100d 0%, #050907 100%);
- }
- .splash-surveillance .splash-grid { position:absolute; inset:0; background:linear-gradient(rgba(99,243,168,0.06) 1px, transparent 1px), linear-gradient(90deg, rgba(99,243,168,0.06) 1px, transparent 1px); background-size:40px 40px; opacity:0.28; }
- .splash-surveillance .splash-title { font-size:clamp(1.45rem,4.8vw,2rem); color:var(--green); letter-spacing:0.28em; }
- .splash-surveillance .splash-tag { color:var(--grey); letter-spacing:0.22em; }
- .splash-surveillance .splash-hint { color:rgba(188,255,212,0.76); }
- .splash-surveillance .splash-inner { text-shadow:0 0 18px rgba(99,243,168,0.28); }
-{{template "navSharedCSSInner"}}
- </style>
-</head>
-<body>
- {{template "splashGate"}}
- <div id="splash-overlay" class="splash-overlay splash-surveillance" role="dialog" aria-modal="true" aria-label="Open microblog" tabindex="-1">
- <canvas class="splash-gl-canvas" id="splash-gl-canvas" aria-hidden="true"></canvas>
- <div class="splash-grid" aria-hidden="true"></div>
- <div class="splash-inner">
- <div class="splash-title">snonux.foo</div>
- <div class="splash-tag">Camera Mesh Online</div>
- <div class="splash-hint">Click or Enter to access the feed</div>
- </div>
- </div>
- <script>
- (function(){
- if(document.documentElement.classList.contains('sno-splash-skip'))return;
- var cv=document.getElementById('splash-gl-canvas');
- if(!cv||typeof THREE==='undefined')return;
- var raf,ren,sc,ca,clock,rings=[];
- function cleanup(){window.removeEventListener('resize',sz);if(raf)cancelAnimationFrame(raf);raf=null;if(ren)ren.dispose();ren=null;window._snonuxSplashWebGLCleanup=null;}
- window._snonuxSplashWebGLCleanup=cleanup;
- function sz(){var w=cv.clientWidth||2,h=cv.clientHeight||2;if(ren)ren.setSize(w,h,false);if(ca){ca.aspect=w/h;ca.updateProjectionMatrix();}}
- ren=new THREE.WebGLRenderer({canvas:cv,antialias:true,alpha:true});ren.setClearColor(0,0);ren.setPixelRatio(Math.min(window.devicePixelRatio||1,2));
- sc=new THREE.Scene();ca=new THREE.PerspectiveCamera(50,1,0.1,60);ca.position.z=10;clock=new THREE.Clock();
- for(var i=0;i<3;i++){ var r=new THREE.Mesh(new THREE.TorusGeometry(1.4+i*0.5,0.04,8,48),new THREE.MeshBasicMaterial({color:0x63f3a8,transparent:true,opacity:0.68-i*0.1})); sc.add(r); rings.push(r);}
- var iris=new THREE.Mesh(new THREE.CircleGeometry(0.4,24),new THREE.MeshBasicMaterial({color:0xbcffd4,transparent:true,opacity:0.8})); sc.add(iris);
- sz();window.addEventListener('resize',sz);
- function loop(){ raf=requestAnimationFrame(loop); var t=clock.getElapsedTime(); for(var i=0;i<rings.length;i++){ rings[i].rotation.z=t*(0.4+i*0.3); rings[i].scale.setScalar(1+Math.sin(t*2+i)*0.03); } ren.render(sc,ca); }
- raf=requestAnimationFrame(loop);
- })();
- </script>
- <canvas id="three-canvas"></canvas>
- <div class="overlay">
- <header>
- <div class="logo">
- <span class="logo-mark">SN</span>
- <div class="logo-title">
- <h1>snonux.foo</h1>
- <p class="subtitle">microblog &mdash; <a href="https://foo.zone">foo.zone</a> is the real blog</p>
- <p class="logo-host">Served by NetBSD on a Raspberry Pi 3</p>
- </div>
- </div>
- <div class="nav">
- <a href="atom.xml" class="header-feed-link" rel="alternate" title="Atom feed" type="application/atom+xml">Atom feed</a>
- <a href="https://foo.zone/about" class="transmit-btn">Operator</a>
- </div>
- </header>
- {{template "navhints" .}}
- <div class="content" id="post-content">
- {{range $i, $post := .Posts}}
- <div class="post" id="post-{{$post.ID}}" data-index="{{$i}}">
- <div class="post-header">
- <div><strong>@snonux</strong></div>
- <div class="post-time">{{$post.FormattedTime}}</div>
- </div>
- <div class="post-text">{{$post.ContentHTML}}</div>
- </div>
- {{end}}
- </div>
- {{if or .PrevPage .NextPage}}
- <footer class="page-nav-footer" aria-label="Pagination">
- <div class="page-nav page-nav-dual">
- {{if .PrevPage}}<a href="{{.PrevPage}}">&larr; Newer</a>{{end}}
- {{if .NextPage}}<a href="{{.NextPage}}">Older &rarr;</a>{{end}}
- </div>
- </footer>
- {{end}}
- </div>
- {{template "navmodal" .}}
- <script>
- (function() {
- var _wild = false, _snoTOffset = 0, _snoLastT = 0;
- var scene, camera, renderer, clock, nodes = [], trackers = [], rain;
-
- function initThree() {
- scene = new THREE.Scene();
- scene.background = new THREE.Color(0x09100d);
- scene.fog = new THREE.Fog(0x09100d, 20, 120);
- camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 200);
- camera.position.set(0, 6, 28);
- renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('three-canvas'), antialias: true });
- renderer.setSize(window.innerWidth, window.innerHeight);
- renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
- clock = new THREE.Clock();
- scene.add(new THREE.AmbientLight(0x35634f, 0.55));
-
- for (var i = 0; i < 12; i++) {
- var s = new THREE.Mesh(new THREE.PlaneGeometry(7, 4.2), new THREE.MeshBasicMaterial({ color:0x15221c, transparent:true, opacity:0.92, side:THREE.DoubleSide }));
- s.position.set((i % 4 - 1.5) * 11, 8 - Math.floor(i / 4) * 6, -10 - Math.floor(i / 4) * 8);
- scene.add(s); nodes.push(s);
- var box = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.PlaneGeometry(6.4, 3.6)), new THREE.LineBasicMaterial({ color:0x63f3a8, transparent:true, opacity:0.5 }));
- box.position.copy(s.position); box.position.z += 0.04; scene.add(box); trackers.push(box);
- }
-
- var rp = new Float32Array(1500 * 3);
- for (i = 0; i < rp.length; i += 3) { rp[i]=(Math.random()-0.5)*70; rp[i+1]=(Math.random()-0.5)*40; rp[i+2]=-80+Math.random()*90; }
- var rg = new THREE.BufferGeometry(); rg.setAttribute('position', new THREE.BufferAttribute(rp, 3));
- rain = new THREE.Points(rg, new THREE.PointsMaterial({ color:0xbcffd4, size:0.08, transparent:true, opacity:0.2 }));
- scene.add(rain);
- window.addEventListener('resize', onResize);
- animate();
- }
-
- function onResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }
-
- function animate() {
- requestAnimationFrame(animate);
- var realT = clock.getElapsedTime();
- _snoTOffset += (realT - _snoLastT) * (_wild ? 9 : 0);
- _snoLastT = realT;
- var t = realT + _snoTOffset;
- for (var i = 0; i < nodes.length; i++) {
- nodes[i].material.opacity = (_wild ? 0.58 : 0.9) - ((i % 4) * 0.06);
- trackers[i].rotation.z = Math.sin(t * 0.7 + i) * (_wild ? 0.12 : 0.03);
- trackers[i].material.opacity = _wild ? 0.84 : 0.5;
- }
- var pos = rain.geometry.attributes.position;
- for (i = 0; i < pos.count; i++) { var y = pos.getY(i) - (_wild ? 0.22 : 0.08); pos.setY(i, y < -20 ? 20 : y); }
- pos.needsUpdate = true;
- camera.position.x = Math.sin(realT * (_wild ? 1.6 : 0.3)) * (_wild ? 2.8 : 0.7);
- camera.position.y = 6 + Math.cos(realT * 0.4) * (_wild ? 1.1 : 0.3);
- camera.lookAt(0, 0, -20);
- renderer.render(scene, camera);
- }
-
- initThree();
-
- function overlay(css, ms) {
- var d = document.createElement('div');
- d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;' + css + ';transition:opacity ' + (ms || 200) + 'ms';
- document.body.appendChild(d);
- setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, ms || 200); }, 25);
- }
- window.snonuxOpenEffect = function(post) {
- var modal = document.getElementById('post-modal');
- if (modal) { modal.classList.add('sno-modal-slide'); setTimeout(function() { modal.classList.remove('sno-modal-slide'); }, 340); }
- var r = post ? post.getBoundingClientRect() : { left: innerWidth*0.5, top: innerHeight*0.5, width: 0, height: 0 };
- var box = document.createElement('div');
- box.style.cssText = 'position:fixed;left:' + (r.left-6) + 'px;top:' + (r.top-6) + 'px;width:' + (r.width+12) + 'px;height:' + (r.height+12) + 'px;border:1px solid rgba(99,243,168,0.8);z-index:997;pointer-events:none;transition:transform 0.32s ease,opacity 0.32s ease;';
- document.body.appendChild(box);
- setTimeout(function() { box.style.transform='scale(1.18)'; box.style.opacity='0'; setTimeout(function() { box.remove(); }, 360); }, 18);
- };
- window.snonuxCloseEffect = function() { overlay('background:rgba(0,0,0,0.32)', 160); };
- window.snonuxNavEffect = function() { overlay('background:linear-gradient(90deg,transparent,rgba(99,243,168,0.08),transparent)', 160); };
- window.snonuxPageEffect = function() { overlay('background:radial-gradient(circle at center,rgba(255,77,92,0.12),transparent 68%)', 220); };
- window.snonuxScrollEffect = function(dir) {
- var d = document.createElement('div');
- d.style.cssText = 'position:fixed;' + (dir === 'down' ? 'top:0;' : 'bottom:0;') + 'left:0;right:0;height:' + (_wild ? '16px' : '6px') + ';z-index:9000;pointer-events:none;background:linear-gradient(90deg,transparent,rgba(99,243,168,0.82),transparent);transition:transform 0.28s ease,opacity 0.28s ease;';
- document.body.appendChild(d);
- setTimeout(function() { d.style.transform = dir === 'down' ? 'translateY(100vh)' : 'translateY(-100vh)'; d.style.opacity='0'; }, 16);
- setTimeout(function() { d.remove(); }, 340);
- };
- window.snonuxWildToggle = function() {
- _wild = !_wild;
- var b = document.getElementById('sno-wild-badge');
- if (b) b.classList.toggle('sno-wild-on', _wild);
- };
- })();
- </script>
- {{template "navscript" .}}
-</body>
-</html>
diff --git a/internal/generator/templates/themes/surveillance/meta.json b/internal/generator/templates/themes/surveillance/meta.json
new file mode 100644
index 0000000..7e948f9
--- /dev/null
+++ b/internal/generator/templates/themes/surveillance/meta.json
@@ -0,0 +1,7 @@
+{
+ "title": "snonux.foo // SURVEILLANCE",
+ "header_html": "\u003cdiv class=\"logo\"\u003e\n \u003cspan class=\"logo-mark\"\u003eSN\u003c/span\u003e\n \u003cdiv class=\"logo-title\"\u003e\n \u003ch1\u003esnonux.foo\u003c/h1\u003e\n \u003cp class=\"subtitle\"\u003emicroblog \u0026mdash; \u003ca href=\"https://foo.zone\"\u003efoo.zone\u003c/a\u003e is the real blog\u003c/p\u003e\n \u003cp class=\"logo-host\"\u003eServed by NetBSD on a Raspberry Pi 3\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"nav\"\u003e\n \u003ca href=\"atom.xml\" class=\"header-feed-link\" rel=\"alternate\" title=\"Atom feed\" type=\"application/atom+xml\"\u003eAtom feed\u003c/a\u003e\n \u003ca href=\"https://foo.zone/about\" class=\"transmit-btn\"\u003eOperator\u003c/a\u003e\n \u003c/div\u003e",
+ "splash_inner_html": "\u003ccanvas class=\"splash-gl-canvas\" id=\"splash-gl-canvas\" aria-hidden=\"true\"\u003e\u003c/canvas\u003e\n \u003cdiv class=\"splash-grid\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-inner\"\u003e\n \u003cdiv class=\"splash-title\"\u003esnonux.foo\u003c/div\u003e\n \u003cdiv class=\"splash-tag\"\u003eCamera Mesh Online\u003c/div\u003e\n \u003cdiv class=\"splash-hint\"\u003eClick or Enter to access the feed\u003c/div\u003e\n \u003c/div\u003e",
+ "prev_page_text": "\u0026larr; Newer",
+ "next_page_text": "Older \u0026rarr;"
+}
diff --git a/internal/generator/templates/themes/surveillance/theme.css b/internal/generator/templates/themes/surveillance/theme.css
new file mode 100644
index 0000000..cd54913
--- /dev/null
+++ b/internal/generator/templates/themes/surveillance/theme.css
@@ -0,0 +1,50 @@
+ :root { --phosphor:#bcffd4; --green:#63f3a8; --grey:#88a197; --alert:#ff4d5c; --bg:#09100d; --panel:#101916; }
+ * { margin:0; padding:0; box-sizing:border-box; }
+ body { font-family:'Share Tech Mono','Courier New',monospace; background:var(--bg); color:var(--phosphor); overflow:hidden; height:100vh; }
+ body::before { content:''; position:fixed; inset:0; z-index:999; pointer-events:none; background:repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(188,255,212,0.035) 2px, rgba(188,255,212,0.035) 3px); opacity:0.72; }
+ #three-canvas { position:fixed; inset:0; width:100%; height:100%; z-index:1; }
+ .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
+ header { padding:14px 24px; background:rgba(9,16,13,0.84); backdrop-filter:blur(8px); border-bottom:1px solid rgba(99,243,168,0.18); display:flex; align-items:center; justify-content:space-between; }
+ .logo { display:flex; align-items:center; gap:14px; }
+ .logo-mark { font-size:1.55rem; color:var(--green); }
+ .logo-title h1 { font-size:1.15rem; color:var(--green); letter-spacing:0.24em; }
+ .logo-title .subtitle { font-size:0.72rem; color:rgba(188,255,212,0.5); margin-top:2px; }
+ .logo-title .subtitle a { color:var(--grey); text-decoration:none; }
+ .logo-title .subtitle a:hover { color:var(--green); }
+ .transmit-btn { border:1px solid rgba(99,243,168,0.22); color:var(--green); padding:8px 14px; text-decoration:none; font-size:0.76rem; letter-spacing:0.24em; text-transform:uppercase; transition:all 0.18s; }
+ .transmit-btn:hover { background:rgba(99,243,168,0.12); }
+ a.header-feed-link { color:var(--grey); }
+ a.header-feed-link:hover { color:var(--green); }
+ .nav-hints { background:rgba(10,18,14,0.74); border-bottom:1px solid rgba(99,243,168,0.08); color:rgba(188,255,212,0.42); padding:5px 24px; display:flex; gap:18px; font-size:0.66rem; flex-wrap:wrap; }
+ .nav-hints kbd { background:#0c1511; border:1px solid rgba(99,243,168,0.2); color:var(--green); padding:0 5px; margin:0 2px; }
+ .content { flex:1; overflow-y:auto; padding:18px 24px; scrollbar-width:thin; scrollbar-color:#4b8d68 #0d1512; }
+ .page-nav { display:flex; justify-content:center; margin:12px 0; }
+ .page-nav a { border:1px solid rgba(99,243,168,0.2); color:var(--green); padding:7px 16px; text-decoration:none; font-size:0.76rem; letter-spacing:0.22em; text-transform:uppercase; }
+ .page-nav a:hover { background:rgba(99,243,168,0.08); }
+ .page-nav-footer { flex-shrink:0; padding:8px 24px; display:flex; justify-content:center; background:rgba(9,16,13,0.84); backdrop-filter:blur(8px); border-top:1px solid rgba(99,243,168,0.18); }
+ .post { background:linear-gradient(180deg, rgba(16,25,22,0.92), rgba(9,15,12,0.92)); border:1px solid rgba(99,243,168,0.08); padding:18px; margin-bottom:12px; cursor:pointer; position:relative; transition:border-color 0.18s, box-shadow 0.18s; }
+ .post::after { content:''; position:absolute; inset:8px; border:1px solid rgba(99,243,168,0.06); pointer-events:none; }
+ .post:hover { border-color:rgba(99,243,168,0.22); box-shadow:0 0 18px rgba(99,243,168,0.1); }
+ .post-active { border-color:rgba(99,243,168,0.34) !important; background:linear-gradient(180deg, rgba(10,25,18,0.96), rgba(7,14,10,0.95)) !important;
+ box-shadow:0 0 0 1px rgba(99,243,168,0.1), 0 16px 34px rgba(0,0,0,0.32), inset 4px 0 0 var(--green) !important; }
+ .post-header { display:flex; justify-content:space-between; margin-bottom:10px; font-size:0.8rem; }
+ .post-header strong, .post-time { color:var(--green); }
+ .post-text { line-height:1.68; font-size:0.9rem; color:var(--phosphor); }
+ .post-text a { color:var(--green); text-decoration:none; border-bottom:1px solid rgba(99,243,168,0.18); }
+ .post-image { margin-top:10px; border:1px solid rgba(99,243,168,0.1); filter:saturate(0.6) contrast(1.12) hue-rotate(-16deg); }
+ .post-audio { width:100%; margin-top:10px; filter:grayscale(0.7); }
+ .post-modal { display:none; position:fixed; inset:0; z-index:100; overflow-y:auto; padding:40px 20px; }
+ .post-modal.active { display:block; }
+ .modal-inner { max-width:760px; margin:0 auto; background:rgba(8,14,11,0.98); border:1px solid rgba(99,243,168,0.2); padding:34px; box-shadow:0 20px 72px rgba(0,0,0,0.72); }
+ .modal-close { float:right; background:none; border:none; color:var(--green); font-family:'Share Tech Mono',monospace; font-size:0.76rem; cursor:pointer; letter-spacing:0.2em; }
+ @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 16px;} .content{padding:14px 16px;} .modal-inner{padding:24px 16px;} }
+ [data-sno-theme="surveillance"] .splash-overlay {
+ background:
+ radial-gradient(circle at 50% 22%, rgba(99,243,168,0.12) 0%, transparent 34%),
+ linear-gradient(180deg, #09100d 0%, #050907 100%);
+ }
+ [data-sno-theme="surveillance"] .splash-grid { position:absolute; inset:0; background:linear-gradient(rgba(99,243,168,0.06) 1px, transparent 1px), linear-gradient(90deg, rgba(99,243,168,0.06) 1px, transparent 1px); background-size:40px 40px; opacity:0.28; }
+ [data-sno-theme="surveillance"] .splash-title { font-size:clamp(1.45rem,4.8vw,2rem); color:var(--green); letter-spacing:0.28em; }
+ [data-sno-theme="surveillance"] .splash-tag { color:var(--grey); letter-spacing:0.22em; }
+ [data-sno-theme="surveillance"] .splash-hint { color:rgba(188,255,212,0.76); }
+ [data-sno-theme="surveillance"] .splash-inner { text-shadow:0 0 18px rgba(99,243,168,0.28); }
diff --git a/internal/generator/templates/themes/surveillance/theme.js b/internal/generator/templates/themes/surveillance/theme.js
new file mode 100644
index 0000000..e0e7474
--- /dev/null
+++ b/internal/generator/templates/themes/surveillance/theme.js
@@ -0,0 +1,107 @@
+
+ (function(){
+ if(document.documentElement.classList.contains('sno-splash-skip'))return;
+ var cv=document.getElementById('splash-gl-canvas');
+ if(!cv||typeof THREE==='undefined')return;
+ var raf,ren,sc,ca,clock,rings=[];
+ function cleanup(){window.removeEventListener('resize',sz);if(raf)cancelAnimationFrame(raf);raf=null;if(ren)ren.dispose();ren=null;window._snonuxSplashWebGLCleanup=null;}
+ window._snonuxSplashWebGLCleanup=cleanup;
+ function sz(){var w=cv.clientWidth||2,h=cv.clientHeight||2;if(ren)ren.setSize(w,h,false);if(ca){ca.aspect=w/h;ca.updateProjectionMatrix();}}
+ ren=new THREE.WebGLRenderer({canvas:cv,antialias:true,alpha:true});ren.setClearColor(0,0);ren.setPixelRatio(Math.min(window.devicePixelRatio||1,2));
+ sc=new THREE.Scene();ca=new THREE.PerspectiveCamera(50,1,0.1,60);ca.position.z=10;clock=new THREE.Clock();
+ for(var i=0;i<3;i++){ var r=new THREE.Mesh(new THREE.TorusGeometry(1.4+i*0.5,0.04,8,48),new THREE.MeshBasicMaterial({color:0x63f3a8,transparent:true,opacity:0.68-i*0.1})); sc.add(r); rings.push(r);}
+ var iris=new THREE.Mesh(new THREE.CircleGeometry(0.4,24),new THREE.MeshBasicMaterial({color:0xbcffd4,transparent:true,opacity:0.8})); sc.add(iris);
+ sz();window.addEventListener('resize',sz);
+ function loop(){ raf=requestAnimationFrame(loop); var t=clock.getElapsedTime(); for(var i=0;i<rings.length;i++){ rings[i].rotation.z=t*(0.4+i*0.3); rings[i].scale.setScalar(1+Math.sin(t*2+i)*0.03); } ren.render(sc,ca); }
+ raf=requestAnimationFrame(loop);
+ })();
+
+
+ (function() {
+ var _wild = false, _snoTOffset = 0, _snoLastT = 0;
+ var scene, camera, renderer, clock, nodes = [], trackers = [], rain;
+
+ function initThree() {
+ scene = new THREE.Scene();
+ scene.background = new THREE.Color(0x09100d);
+ scene.fog = new THREE.Fog(0x09100d, 20, 120);
+ camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 200);
+ camera.position.set(0, 6, 28);
+ renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('three-canvas'), antialias: true });
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
+ clock = new THREE.Clock();
+ scene.add(new THREE.AmbientLight(0x35634f, 0.55));
+
+ for (var i = 0; i < 12; i++) {
+ var s = new THREE.Mesh(new THREE.PlaneGeometry(7, 4.2), new THREE.MeshBasicMaterial({ color:0x15221c, transparent:true, opacity:0.92, side:THREE.DoubleSide }));
+ s.position.set((i % 4 - 1.5) * 11, 8 - Math.floor(i / 4) * 6, -10 - Math.floor(i / 4) * 8);
+ scene.add(s); nodes.push(s);
+ var box = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.PlaneGeometry(6.4, 3.6)), new THREE.LineBasicMaterial({ color:0x63f3a8, transparent:true, opacity:0.5 }));
+ box.position.copy(s.position); box.position.z += 0.04; scene.add(box); trackers.push(box);
+ }
+
+ var rp = new Float32Array(1500 * 3);
+ for (i = 0; i < rp.length; i += 3) { rp[i]=(Math.random()-0.5)*70; rp[i+1]=(Math.random()-0.5)*40; rp[i+2]=-80+Math.random()*90; }
+ var rg = new THREE.BufferGeometry(); rg.setAttribute('position', new THREE.BufferAttribute(rp, 3));
+ rain = new THREE.Points(rg, new THREE.PointsMaterial({ color:0xbcffd4, size:0.08, transparent:true, opacity:0.2 }));
+ scene.add(rain);
+ window.addEventListener('resize', onResize);
+ animate();
+ }
+
+ function onResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }
+
+ function animate() {
+ requestAnimationFrame(animate);
+ var realT = clock.getElapsedTime();
+ _snoTOffset += (realT - _snoLastT) * (_wild ? 9 : 0);
+ _snoLastT = realT;
+ var t = realT + _snoTOffset;
+ for (var i = 0; i < nodes.length; i++) {
+ nodes[i].material.opacity = (_wild ? 0.58 : 0.9) - ((i % 4) * 0.06);
+ trackers[i].rotation.z = Math.sin(t * 0.7 + i) * (_wild ? 0.12 : 0.03);
+ trackers[i].material.opacity = _wild ? 0.84 : 0.5;
+ }
+ var pos = rain.geometry.attributes.position;
+ for (i = 0; i < pos.count; i++) { var y = pos.getY(i) - (_wild ? 0.22 : 0.08); pos.setY(i, y < -20 ? 20 : y); }
+ pos.needsUpdate = true;
+ camera.position.x = Math.sin(realT * (_wild ? 1.6 : 0.3)) * (_wild ? 2.8 : 0.7);
+ camera.position.y = 6 + Math.cos(realT * 0.4) * (_wild ? 1.1 : 0.3);
+ camera.lookAt(0, 0, -20);
+ renderer.render(scene, camera);
+ }
+
+ initThree();
+
+ function overlay(css, ms) {
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;' + css + ';transition:opacity ' + (ms || 200) + 'ms';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, ms || 200); }, 25);
+ }
+ window.snonuxOpenEffect = function(post) {
+ var modal = document.getElementById('post-modal');
+ if (modal) { modal.classList.add('sno-modal-slide'); setTimeout(function() { modal.classList.remove('sno-modal-slide'); }, 340); }
+ var r = post ? post.getBoundingClientRect() : { left: innerWidth*0.5, top: innerHeight*0.5, width: 0, height: 0 };
+ var box = document.createElement('div');
+ box.style.cssText = 'position:fixed;left:' + (r.left-6) + 'px;top:' + (r.top-6) + 'px;width:' + (r.width+12) + 'px;height:' + (r.height+12) + 'px;border:1px solid rgba(99,243,168,0.8);z-index:997;pointer-events:none;transition:transform 0.32s ease,opacity 0.32s ease;';
+ document.body.appendChild(box);
+ setTimeout(function() { box.style.transform='scale(1.18)'; box.style.opacity='0'; setTimeout(function() { box.remove(); }, 360); }, 18);
+ };
+ window.snonuxCloseEffect = function() { overlay('background:rgba(0,0,0,0.32)', 160); };
+ window.snonuxNavEffect = function() { overlay('background:linear-gradient(90deg,transparent,rgba(99,243,168,0.08),transparent)', 160); };
+ window.snonuxPageEffect = function() { overlay('background:radial-gradient(circle at center,rgba(255,77,92,0.12),transparent 68%)', 220); };
+ window.snonuxScrollEffect = function(dir) {
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;' + (dir === 'down' ? 'top:0;' : 'bottom:0;') + 'left:0;right:0;height:' + (_wild ? '16px' : '6px') + ';z-index:9000;pointer-events:none;background:linear-gradient(90deg,transparent,rgba(99,243,168,0.82),transparent);transition:transform 0.28s ease,opacity 0.28s ease;';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.transform = dir === 'down' ? 'translateY(100vh)' : 'translateY(-100vh)'; d.style.opacity='0'; }, 16);
+ setTimeout(function() { d.remove(); }, 340);
+ };
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ };
+ })();
diff --git a/internal/generator/templates/themes/synthwave/meta.json b/internal/generator/templates/themes/synthwave/meta.json
new file mode 100644
index 0000000..f92f49f
--- /dev/null
+++ b/internal/generator/templates/themes/synthwave/meta.json
@@ -0,0 +1,7 @@
+{
+ "title": "snonux.foo ⊕ SYNTHWAVE",
+ "header_html": "\u003cdiv class=\"logo\"\u003e\n \u003cspan class=\"logo-mark\"\u003eSN\u003c/span\u003e\n \u003cdiv class=\"logo-title\"\u003e\n \u003ch1\u003esnonux.foo\u003c/h1\u003e\n \u003cp class=\"subtitle\"\u003emicroblog \u0026mdash; \u003ca href=\"https://foo.zone\"\u003efoo.zone\u003c/a\u003e is the real blog\u003c/p\u003e\n \u003cp class=\"logo-host\"\u003eServed by NetBSD on a Raspberry Pi 3\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"nav\"\u003e\n \u003ca href=\"atom.xml\" class=\"header-feed-link\" rel=\"alternate\" title=\"Atom feed\" type=\"application/atom+xml\"\u003eAtom feed\u003c/a\u003e\n \u003ca href=\"https://foo.zone/about\" class=\"transmit-btn\"\u003eTRANSMIT TO NEXUS\u003c/a\u003e\n \u003c/div\u003e",
+ "splash_inner_html": "\u003ccanvas class=\"splash-gl-canvas\" id=\"splash-gl-canvas\" aria-hidden=\"true\"\u003e\u003c/canvas\u003e\n \u003cdiv class=\"splash-grid\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-inner\"\u003e\n \u003cdiv class=\"splash-sun\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-title\"\u003esnonux.foo\u003c/div\u003e\n \u003cdiv class=\"splash-tag\"\u003eSynthwave uplink\u003c/div\u003e\n \u003cdiv class=\"splash-hint\"\u003eClick or Enter to ride the grid\u003c/div\u003e\n \u003c/div\u003e",
+ "prev_page_text": "\u0026larr; NEWER",
+ "next_page_text": "OLDER \u0026rarr;"
+}
diff --git a/internal/generator/templates/themes/synthwave/theme.css b/internal/generator/templates/themes/synthwave/theme.css
new file mode 100644
index 0000000..0195e5f
--- /dev/null
+++ b/internal/generator/templates/themes/synthwave/theme.css
@@ -0,0 +1,85 @@
+ :root { --pink:#ff2d78; --purple:#bf3fff; --orange:#ff6b2b; --bg:#0d0221; }
+ * { margin:0; padding:0; box-sizing:border-box; }
+ body { font-family:'Russo One','Arial Black',sans-serif; background:var(--bg);
+ color:#fff; overflow:hidden; height:100vh; }
+ #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
+ .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
+ header { padding:16px 28px; background:rgba(13,2,33,0.82); backdrop-filter:blur(10px);
+ border-bottom:2px solid var(--pink); display:flex; align-items:center; justify-content:space-between; }
+ .logo { display:flex; align-items:center; gap:14px; }
+ .logo-mark { font-size:1.8rem; background:linear-gradient(90deg,var(--pink),var(--purple));
+ -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
+ .logo-title h1 { font-size:1.7rem; background:linear-gradient(90deg,var(--pink),var(--orange));
+ -webkit-background-clip:text; -webkit-text-fill-color:transparent; letter-spacing:2px; }
+ .logo-title .subtitle { font-size:0.7rem; color:rgba(255,255,255,0.55); margin-top:2px;
+ font-family:'Share Tech Mono',monospace; }
+ .logo-title .subtitle a { color:var(--pink); text-decoration:none; }
+ .logo-title .subtitle a:hover { text-shadow:0 0 8px var(--pink); }
+ .transmit-btn { border:2px solid var(--orange); color:var(--orange); padding:10px 22px;
+ border-radius:4px; text-decoration:none; letter-spacing:1px;
+ font-size:0.88rem; transition:all 0.2s; }
+ .transmit-btn:hover { background:var(--orange); color:var(--bg); }
+ a.header-feed-link { color:var(--pink); font-family:'Share Tech Mono',monospace; }
+ .nav-hints { background:rgba(13,2,33,0.75); border-bottom:1px solid rgba(255,45,120,0.3);
+ color:rgba(255,255,255,0.45); padding:5px 20px; display:flex; gap:18px;
+ font-size:0.68rem; font-family:'Share Tech Mono',monospace; flex-wrap:wrap; }
+ .nav-hints kbd { background:rgba(255,45,120,0.15); border:1px solid rgba(255,45,120,0.4);
+ color:var(--pink); border-radius:3px; padding:0 5px; margin:0 2px; font-size:0.7rem; }
+ .content { flex:1; overflow-y:auto; padding:22px 28px;
+ scrollbar-width:thin; scrollbar-color:var(--pink) var(--bg); }
+ .page-nav { display:flex; justify-content:center; margin:14px 0; }
+ .page-nav a { border:2px solid var(--purple); color:var(--purple); padding:8px 22px;
+ border-radius:4px; text-decoration:none; letter-spacing:2px; font-size:0.82rem; }
+ .page-nav a:hover { background:var(--purple); color:#fff; }
+ .page-nav-footer { flex-shrink:0; padding:8px 28px; display:flex; justify-content:center;
+ background:rgba(13,2,33,0.82); backdrop-filter:blur(10px);
+ border-top:2px solid var(--pink); }
+ .post { background:rgba(20,5,50,0.85); border:1px solid var(--purple); border-radius:6px;
+ padding:22px; margin-bottom:18px; cursor:pointer; transition:all 0.25s; }
+ .post:hover { border-color:var(--pink); box-shadow:0 0 22px rgba(255,45,120,0.35); transform:translateY(-3px); }
+ .post-active { border-color:var(--orange) !important; background:rgba(30,8,60,0.96) !important;
+ box-shadow:0 0 22px rgba(255,107,43,0.45),inset 3px 0 0 var(--orange) !important; }
+ .post-header { display:flex; justify-content:space-between; margin-bottom:14px; }
+ .post-time { color:var(--orange); font-family:'Share Tech Mono',monospace; font-size:0.85rem; }
+ .post-text { line-height:1.6; font-size:0.95rem; font-family:'Share Tech Mono',monospace; }
+ .post-text a { color:var(--pink); text-decoration:none; }
+ .post-audio { width:100%; margin-top:10px; }
+ .post-modal { display:none; position:fixed; inset:0; z-index:100;
+ background:rgba(13,2,33,0.96); overflow-y:auto; padding:40px 20px; }
+ .post-modal.active { display:block; }
+ .modal-inner { max-width:780px; margin:0 auto; background:rgba(20,5,50,0.98);
+ border:2px solid var(--pink); border-radius:6px;
+ box-shadow:0 0 60px rgba(255,45,120,0.35); padding:38px; }
+ .modal-close { float:right; background:none; border:none; color:var(--orange);
+ font-family:'Russo One',sans-serif; font-size:0.9rem; cursor:pointer; letter-spacing:2px; }
+ @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 18px;} }
+ [data-sno-theme="synthwave"] .splash-overlay {
+ background: linear-gradient(180deg, #2a0a3e 0%, var(--bg) 38%, #1a0630 100%);
+ }
+ [data-sno-theme="synthwave"] .splash-grid {
+ position:absolute; inset:0; opacity:0.35; pointer-events:none; z-index:1;
+ background: linear-gradient(90deg, rgba(255,45,120,0.08) 1px, transparent 1px) 0 0 / 48px 48px,
+ linear-gradient(rgba(191,63,255,0.06) 1px, transparent 1px) 0 0 / 48px 48px;
+ transform: perspective(280px) rotateX(68deg) scale(2.2);
+ transform-origin: 50% 85%;
+ animation: splashGridDrift 10s linear infinite;
+ }
+ @keyframes splashGridDrift { to { background-position: 48px 48px, 0 96px; } }
+ [data-sno-theme="synthwave"] .splash-sun {
+ width:min(140px,35vw); height:min(140px,35vw); margin:0 auto 1rem; border-radius:50%;
+ background: radial-gradient(circle, var(--orange) 0%, var(--pink) 45%, transparent 70%);
+ box-shadow: 0 0 60px var(--pink), 0 0 100px var(--orange);
+ animation: splashSunPulse 2.5s ease-in-out infinite alternate;
+ }
+ @keyframes splashSunPulse {
+ from { transform: scale(0.95); opacity: 0.85; }
+ to { transform: scale(1.05); opacity: 1; }
+ }
+ [data-sno-theme="synthwave"] .splash-title {
+ font-family:'Russo One',sans-serif; font-size:clamp(1.5rem,5vw,2.2rem);
+ background: linear-gradient(90deg,var(--pink),var(--orange));
+ -webkit-background-clip:text; -webkit-text-fill-color:transparent;
+ }
+ [data-sno-theme="synthwave"] .splash-tag { font-family:'Share Tech Mono',monospace; color:var(--purple); }
+ [data-sno-theme="synthwave"] .splash-hint { font-family:'Share Tech Mono',monospace; color:rgba(255,255,255,0.88); }
+ [data-sno-theme="synthwave"] .splash-inner { text-shadow: 0 2px 20px rgba(13,2,33,0.95); }
diff --git a/internal/generator/templates/themes/synthwave.tmpl b/internal/generator/templates/themes/synthwave/theme.js
index f07103e..7be585e 100644
--- a/internal/generator/templates/themes/synthwave.tmpl
+++ b/internal/generator/templates/themes/synthwave/theme.js
@@ -1,114 +1,4 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>snonux.foo ⊕ SYNTHWAVE</title>
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link href="https://fonts.googleapis.com/css2?family=Russo+One&family=Share+Tech+Mono&display=swap" rel="stylesheet">
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
- <style>
- :root { --pink:#ff2d78; --purple:#bf3fff; --orange:#ff6b2b; --bg:#0d0221; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:'Russo One','Arial Black',sans-serif; background:var(--bg);
- color:#fff; overflow:hidden; height:100vh; }
- #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
- .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
- header { padding:16px 28px; background:rgba(13,2,33,0.82); backdrop-filter:blur(10px);
- border-bottom:2px solid var(--pink); display:flex; align-items:center; justify-content:space-between; }
- .logo { display:flex; align-items:center; gap:14px; }
- .logo-mark { font-size:1.8rem; background:linear-gradient(90deg,var(--pink),var(--purple));
- -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
- .logo-title h1 { font-size:1.7rem; background:linear-gradient(90deg,var(--pink),var(--orange));
- -webkit-background-clip:text; -webkit-text-fill-color:transparent; letter-spacing:2px; }
- .logo-title .subtitle { font-size:0.7rem; color:rgba(255,255,255,0.55); margin-top:2px;
- font-family:'Share Tech Mono',monospace; }
- .logo-title .subtitle a { color:var(--pink); text-decoration:none; }
- .logo-title .subtitle a:hover { text-shadow:0 0 8px var(--pink); }
- .transmit-btn { border:2px solid var(--orange); color:var(--orange); padding:10px 22px;
- border-radius:4px; text-decoration:none; letter-spacing:1px;
- font-size:0.88rem; transition:all 0.2s; }
- .transmit-btn:hover { background:var(--orange); color:var(--bg); }
- a.header-feed-link { color:var(--pink); font-family:'Share Tech Mono',monospace; }
- .nav-hints { background:rgba(13,2,33,0.75); border-bottom:1px solid rgba(255,45,120,0.3);
- color:rgba(255,255,255,0.45); padding:5px 20px; display:flex; gap:18px;
- font-size:0.68rem; font-family:'Share Tech Mono',monospace; flex-wrap:wrap; }
- .nav-hints kbd { background:rgba(255,45,120,0.15); border:1px solid rgba(255,45,120,0.4);
- color:var(--pink); border-radius:3px; padding:0 5px; margin:0 2px; font-size:0.7rem; }
- .content { flex:1; overflow-y:auto; padding:22px 28px;
- scrollbar-width:thin; scrollbar-color:var(--pink) var(--bg); }
- .page-nav { display:flex; justify-content:center; margin:14px 0; }
- .page-nav a { border:2px solid var(--purple); color:var(--purple); padding:8px 22px;
- border-radius:4px; text-decoration:none; letter-spacing:2px; font-size:0.82rem; }
- .page-nav a:hover { background:var(--purple); color:#fff; }
- .page-nav-footer { flex-shrink:0; padding:8px 28px; display:flex; justify-content:center;
- background:rgba(13,2,33,0.82); backdrop-filter:blur(10px);
- border-top:2px solid var(--pink); }
- .post { background:rgba(20,5,50,0.85); border:1px solid var(--purple); border-radius:6px;
- padding:22px; margin-bottom:18px; cursor:pointer; transition:all 0.25s; }
- .post:hover { border-color:var(--pink); box-shadow:0 0 22px rgba(255,45,120,0.35); transform:translateY(-3px); }
- .post-active { border-color:var(--orange) !important; background:rgba(30,8,60,0.96) !important;
- box-shadow:0 0 22px rgba(255,107,43,0.45),inset 3px 0 0 var(--orange) !important; }
- .post-header { display:flex; justify-content:space-between; margin-bottom:14px; }
- .post-time { color:var(--orange); font-family:'Share Tech Mono',monospace; font-size:0.85rem; }
- .post-text { line-height:1.6; font-size:0.95rem; font-family:'Share Tech Mono',monospace; }
- .post-text a { color:var(--pink); text-decoration:none; }
- .post-audio { width:100%; margin-top:10px; }
- .post-modal { display:none; position:fixed; inset:0; z-index:100;
- background:rgba(13,2,33,0.96); overflow-y:auto; padding:40px 20px; }
- .post-modal.active { display:block; }
- .modal-inner { max-width:780px; margin:0 auto; background:rgba(20,5,50,0.98);
- border:2px solid var(--pink); border-radius:6px;
- box-shadow:0 0 60px rgba(255,45,120,0.35); padding:38px; }
- .modal-close { float:right; background:none; border:none; color:var(--orange);
- font-family:'Russo One',sans-serif; font-size:0.9rem; cursor:pointer; letter-spacing:2px; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 18px;} }
- .splash-overlay.splash-synthwave {
- background: linear-gradient(180deg, #2a0a3e 0%, var(--bg) 38%, #1a0630 100%);
- }
- .splash-synthwave .splash-grid {
- position:absolute; inset:0; opacity:0.35; pointer-events:none; z-index:1;
- background: linear-gradient(90deg, rgba(255,45,120,0.08) 1px, transparent 1px) 0 0 / 48px 48px,
- linear-gradient(rgba(191,63,255,0.06) 1px, transparent 1px) 0 0 / 48px 48px;
- transform: perspective(280px) rotateX(68deg) scale(2.2);
- transform-origin: 50% 85%;
- animation: splashGridDrift 10s linear infinite;
- }
- @keyframes splashGridDrift { to { background-position: 48px 48px, 0 96px; } }
- .splash-synthwave .splash-sun {
- width:min(140px,35vw); height:min(140px,35vw); margin:0 auto 1rem; border-radius:50%;
- background: radial-gradient(circle, var(--orange) 0%, var(--pink) 45%, transparent 70%);
- box-shadow: 0 0 60px var(--pink), 0 0 100px var(--orange);
- animation: splashSunPulse 2.5s ease-in-out infinite alternate;
- }
- @keyframes splashSunPulse {
- from { transform: scale(0.95); opacity: 0.85; }
- to { transform: scale(1.05); opacity: 1; }
- }
- .splash-synthwave .splash-title {
- font-family:'Russo One',sans-serif; font-size:clamp(1.5rem,5vw,2.2rem);
- background: linear-gradient(90deg,var(--pink),var(--orange));
- -webkit-background-clip:text; -webkit-text-fill-color:transparent;
- }
- .splash-synthwave .splash-tag { font-family:'Share Tech Mono',monospace; color:var(--purple); }
- .splash-synthwave .splash-hint { font-family:'Share Tech Mono',monospace; color:rgba(255,255,255,0.88); }
- .splash-synthwave .splash-inner { text-shadow: 0 2px 20px rgba(13,2,33,0.95); }
-{{template "navSharedCSSInner"}}
- </style>
-</head>
-<body>
- {{template "splashGate"}}
- <div id="splash-overlay" class="splash-overlay splash-synthwave" role="dialog" aria-modal="true" aria-label="Open microblog" tabindex="-1">
- <canvas class="splash-gl-canvas" id="splash-gl-canvas" aria-hidden="true"></canvas>
- <div class="splash-grid" aria-hidden="true"></div>
- <div class="splash-inner">
- <div class="splash-sun" aria-hidden="true"></div>
- <div class="splash-title">snonux.foo</div>
- <div class="splash-tag">Synthwave uplink</div>
- <div class="splash-hint">Click or Enter to ride the grid</div>
- </div>
- </div>
- <script>
+
(function(){
if(document.documentElement.classList.contains('sno-splash-skip'))return;
var cv=document.getElementById('splash-gl-canvas');
@@ -127,46 +17,8 @@
function loop(now){raf=requestAnimationFrame(loop);var t=(now-t0)*0.001;g.rotation.y=Math.sin(t*0.35)*0.08;sun.position.y=2.1+Math.sin(t*1.2)*0.08;sun.scale.setScalar(1+Math.sin(t*2)*0.04);ren.render(sc,ca);}
raf=requestAnimationFrame(loop);
})();
- </script>
- <canvas id="three-canvas"></canvas>
- <div class="overlay">
- <header>
- <div class="logo">
- <span class="logo-mark">SN</span>
- <div class="logo-title">
- <h1>snonux.foo</h1>
- <p class="subtitle">microblog &mdash; <a href="https://foo.zone">foo.zone</a> is the real blog</p>
- <p class="logo-host">Served by NetBSD on a Raspberry Pi 3</p>
- </div>
- </div>
- <div class="nav">
- <a href="atom.xml" class="header-feed-link" rel="alternate" title="Atom feed" type="application/atom+xml">Atom feed</a>
- <a href="https://foo.zone/about" class="transmit-btn">TRANSMIT TO NEXUS</a>
- </div>
- </header>
- {{template "navhints" .}}
- <div class="content" id="post-content">
- {{range $i, $post := .Posts}}
- <div class="post" id="post-{{$post.ID}}" data-index="{{$i}}">
- <div class="post-header">
- <div><strong>@snonux</strong></div>
- <div class="post-time">{{$post.FormattedTime}}</div>
- </div>
- <div class="post-text">{{$post.ContentHTML}}</div>
- </div>
- {{end}}
- </div>
- {{if or .PrevPage .NextPage}}
- <footer class="page-nav-footer" aria-label="Pagination">
- <div class="page-nav page-nav-dual">
- {{if .PrevPage}}<a href="{{.PrevPage}}">&larr; NEWER</a>{{end}}
- {{if .NextPage}}<a href="{{.NextPage}}">OLDER &rarr;</a>{{end}}
- </div>
- </footer>
- {{end}}
- </div>
- {{template "navmodal" .}}
- <script>
+
+
// Synthwave WebGL: glowing sunset sphere with horizontal scan-line rings,
// a receding grid floor, and pink star particles. Replaces CSS sky/grid.
(function() {
@@ -320,7 +172,3 @@
setTimeout(function() { d.style.transform='scaleY(1.4)'; d.style.opacity='0'; setTimeout(function() { d.remove(); }, 250); }, 20);
};
})();
- </script>
- {{template "navscript" .}}
-</body>
-</html>
diff --git a/internal/generator/templates/themes/terminal.tmpl b/internal/generator/templates/themes/terminal.tmpl
deleted file mode 100644
index 167f08d..0000000
--- a/internal/generator/templates/themes/terminal.tmpl
+++ /dev/null
@@ -1,275 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>snonux.foo // TERMINAL</title>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
- <style>
- :root { --p:#33ff33; --dim:#1a7a1a; --bg:#0a0a0a; --bg2:#050505; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:'Courier New',Courier,monospace; background:var(--bg); color:var(--p);
- overflow:hidden; height:100vh; position:relative; }
- /* CRT scanlines sit above the WebGL canvas */
- body::before { content:''; position:fixed; inset:0; z-index:999; pointer-events:none;
- background:repeating-linear-gradient(0deg,transparent,transparent 2px,
- rgba(0,0,0,0.12) 2px,rgba(0,0,0,0.12) 4px); }
- /* Subtle screen flicker */
- @keyframes flicker { 0%,100%{opacity:1} 93%{opacity:0.97} 95%{opacity:0.91} 97%{opacity:0.98} }
- body { animation:flicker 9s infinite; }
- /* WebGL background canvas — fills the viewport behind everything */
- #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
- .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
- header { padding:12px 24px; background:var(--bg2); border-bottom:2px solid var(--p);
- display:flex; align-items:center; justify-content:space-between; }
- .logo { display:flex; align-items:center; gap:14px; }
- .logo-mark { font-size:1.6rem; color:var(--p); text-shadow:0 0 14px var(--p); letter-spacing:2px; }
- .logo-title h1 { font-size:1.3rem; color:var(--p); text-shadow:0 0 10px var(--p);
- letter-spacing:3px; font-weight:normal; }
- .logo-title .subtitle { font-size:0.72rem; color:var(--dim); margin-top:2px; }
- .logo-title .subtitle a { color:var(--p); text-decoration:none; }
- .logo-title .subtitle a:hover { text-shadow:0 0 6px var(--p); }
- .nav a.transmit-btn { border:1px solid var(--p); color:var(--p); padding:8px 18px;
- border-radius:0; text-decoration:none; letter-spacing:2px; font-size:0.85rem;
- transition:all 0.2s; }
- .nav a.transmit-btn:hover { background:var(--p); color:var(--bg); }
- a.header-feed-link { color:var(--dim); }
- a.header-feed-link:hover { color:var(--p); }
- .nav-hints { background:var(--bg2); border-bottom:1px solid var(--dim); color:var(--dim);
- padding:5px 24px; display:flex; gap:18px; font-size:0.68rem; flex-wrap:wrap; }
- .nav-hints kbd { background:transparent; border:1px solid var(--dim); color:var(--p);
- border-radius:0; padding:0 5px; font-size:0.7rem; margin:0 2px; }
- .content { flex:1; overflow-y:auto; padding:16px 24px;
- scrollbar-width:thin; scrollbar-color:var(--dim) var(--bg); }
- .page-nav { display:flex; justify-content:center; margin:14px 0; }
- .page-nav a { border:1px solid var(--dim); color:var(--p); padding:7px 20px;
- border-radius:0; text-decoration:none; letter-spacing:2px; font-size:0.82rem; }
- .page-nav a:hover { background:var(--p); color:var(--bg); border-color:var(--p); }
- .page-nav-footer { flex-shrink:0; padding:6px 24px; display:flex; justify-content:center;
- background:var(--bg2); border-top:2px solid var(--p); }
- .post { background:var(--bg); border:1px solid var(--dim); border-radius:0;
- padding:18px 20px; margin-bottom:12px; cursor:pointer; transition:border-color 0.15s; }
- .post:hover { border-color:var(--p); box-shadow:0 0 8px rgba(51,255,51,0.3); }
- .post-active { border-color:var(--p) !important; background:rgba(51,255,51,0.04) !important;
- box-shadow:0 0 14px rgba(51,255,51,0.3),inset 3px 0 0 var(--p) !important; }
- .post-header { display:flex; justify-content:space-between; margin-bottom:12px; font-size:0.88rem; }
- .post-time { color:var(--dim); font-size:0.82rem; }
- .post-text { line-height:1.6; font-size:0.92rem; }
- .post-text a { color:var(--p); text-decoration:underline; }
- .post-image { max-width:100%; margin-top:10px; border:1px solid var(--dim); }
- .post-audio { width:100%; margin-top:10px; }
- .post-modal { display:none; position:fixed; inset:0; z-index:100;
- background:rgba(0,0,0,0.97); overflow-y:auto; padding:40px 20px; }
- .post-modal.active { display:block; }
- .modal-inner { max-width:760px; margin:0 auto; background:var(--bg);
- border:1px solid var(--p); border-radius:0;
- box-shadow:0 0 40px rgba(51,255,51,0.25); padding:36px; }
- .modal-close { float:right; background:none; border:none; color:var(--p);
- font-family:monospace; font-size:0.9rem; cursor:pointer; letter-spacing:2px; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:10px 16px;} .content{padding:12px 16px;} }
- .splash-overlay.splash-terminal { background: var(--bg); font-family:'Courier New',monospace; }
- .splash-terminal .splash-prompt { text-align:left; font-size:0.9rem; color:rgba(51,255,51,0.78); margin-bottom:0.5rem; }
- .splash-terminal .splash-title { font-size:clamp(1.2rem,4vw,1.65rem); color:var(--p);
- text-shadow:0 0 12px var(--p); letter-spacing:0.15em; }
- .splash-terminal .splash-cursor::after { content:'█'; animation: splashTermBlink 1s step-end infinite; color:var(--p); }
- @keyframes splashTermBlink { 0%,100%{opacity:1} 50%{opacity:0} }
- .splash-terminal .splash-tag { color:rgba(51,255,51,0.85); letter-spacing:0.25em; }
- .splash-terminal .splash-hint { color:rgba(51,255,51,0.8); }
- .splash-terminal .splash-inner { text-shadow: 0 0 8px #000, 0 2px 12px #000; }
-{{template "navSharedCSSInner"}}
- </style>
-</head>
-<body>
- {{template "splashGate"}}
- <div id="splash-overlay" class="splash-overlay splash-terminal" role="dialog" aria-modal="true" aria-label="Open microblog" tabindex="-1">
- <canvas class="splash-gl-canvas" id="splash-gl-canvas" aria-hidden="true"></canvas>
- <div class="splash-inner">
- <div class="splash-prompt">&gt; ./snonux --boot</div>
- <div class="splash-title splash-cursor">LINK ESTABLISHED</div>
- <div class="splash-tag">TERMINAL SESSION</div>
- <div class="splash-hint">[ click / enter to continue ]</div>
- </div>
- </div>
- <script>
- (function(){
- if(document.documentElement.classList.contains('sno-splash-skip'))return;
- var cv=document.getElementById('splash-gl-canvas');
- if(!cv||typeof THREE==='undefined')return;
- var raf,ren,sc,ca,m,t0=performance.now();
- function cleanup(){window.removeEventListener('resize',sz);if(raf)cancelAnimationFrame(raf);raf=null;if(ren)ren.dispose();ren=null;window._snonuxSplashWebGLCleanup=null;}
- window._snonuxSplashWebGLCleanup=cleanup;
- function sz(){var w=cv.clientWidth||2,h=cv.clientHeight||2;if(ren)ren.setSize(w,h,false);if(ca){ca.aspect=w/h;ca.updateProjectionMatrix();}}
- ren=new THREE.WebGLRenderer({canvas:cv,antialias:true,alpha:true});ren.setClearColor(0,0);ren.setPixelRatio(Math.min(window.devicePixelRatio||1,2));
- sc=new THREE.Scene();ca=new THREE.PerspectiveCamera(48,1,0.1,60);ca.position.z=7;
- m=new THREE.Mesh(new THREE.IcosahedronGeometry(2.3,1),new THREE.MeshBasicMaterial({color:0x33ff33,wireframe:true,transparent:true,opacity:0.88}));
- sc.add(m);sz();window.addEventListener('resize',sz);
- function loop(now){raf=requestAnimationFrame(loop);var t=(now-t0)*0.001;m.rotation.x=t*0.62;m.rotation.y=t*0.88;ren.render(sc,ca);}
- raf=requestAnimationFrame(loop);
- })();
- </script>
- <canvas id="three-canvas"></canvas>
- <div class="overlay">
- <header>
- <div class="logo">
- <span class="logo-mark">[SN]</span>
- <div class="logo-title">
- <h1>snonux.foo</h1>
- <p class="subtitle">microblog / <a href="https://foo.zone">foo.zone</a> is the real blog</p>
- <p class="logo-host">Served by NetBSD on a Raspberry Pi 3</p>
- </div>
- </div>
- <div class="nav">
- <a href="atom.xml" class="header-feed-link" rel="alternate" title="Atom feed" type="application/atom+xml">atom.xml</a>
- <a href="https://foo.zone/about" class="transmit-btn">&gt; TRANSMIT</a>
- </div>
- </header>
- {{template "navhints" .}}
- <div class="content" id="post-content">
- {{range $i, $post := .Posts}}
- <div class="post" id="post-{{$post.ID}}" data-index="{{$i}}">
- <div class="post-header">
- <div><strong>@snonux</strong></div>
- <div class="post-time">{{$post.FormattedTime}}</div>
- </div>
- <div class="post-text">{{$post.ContentHTML}}</div>
- </div>
- {{end}}
- </div>
- {{if or .PrevPage .NextPage}}
- <footer class="page-nav-footer" aria-label="Pagination">
- <div class="page-nav page-nav-dual">
- {{if .PrevPage}}<a href="{{.PrevPage}}">&lt;-- NEWER</a>{{end}}
- {{if .NextPage}}<a href="{{.NextPage}}">OLDER --&gt;</a>{{end}}
- </div>
- </footer>
- {{end}}
- </div>
- {{template "navmodal" .}}
- <script>
- // Terminal WebGL scene: phosphor-green icosahedron wireframe + torus particle ring.
- // The scene sits behind the CRT scanline overlay (z-index:999) and the UI (z-index:10).
- (function() {
- var _wild = false, _snoTOffset = 0, _snoLastT = 0;
- var scene, camera, renderer, icosa, particles;
- var clock = new THREE.Clock();
-
- function initThree() {
- // Scene with pure-black background and distance fog
- scene = new THREE.Scene();
- scene.background = new THREE.Color(0x000000);
- scene.fog = new THREE.Fog(0x000000, 20, 80);
-
- // Perspective camera positioned in front of the orb
- camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 200);
- camera.position.set(0, 0, 30);
-
- renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('three-canvas'), antialias: true });
- renderer.setSize(window.innerWidth, window.innerHeight);
- renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
-
- // Large green phosphor wireframe icosahedron — the central CRT orb
- var icoGeo = new THREE.IcosahedronGeometry(8, 2);
- var icoMat = new THREE.MeshBasicMaterial({ color: 0x33ff33, wireframe: true });
- icosa = new THREE.Mesh(icoGeo, icoMat);
- scene.add(icosa);
-
- // 400 dim particles arranged on a torus path around the icosahedron
- var torusGeo = new THREE.TorusGeometry(14, 3, 16, 100);
- var positions = torusGeo.attributes.position;
- var ptGeo = new THREE.BufferGeometry();
- var pts = new Float32Array(400 * 3);
- for (var i = 0; i < 400; i++) {
- // Sample vertices from the torus geometry to place particles on its surface
- var idx = Math.floor(Math.random() * positions.count);
- pts[i * 3] = positions.getX(idx);
- pts[i * 3 + 1] = positions.getY(idx);
- pts[i * 3 + 2] = positions.getZ(idx);
- }
- ptGeo.setAttribute('position', new THREE.BufferAttribute(pts, 3));
- var ptMat = new THREE.PointsMaterial({ color: 0x1a7a1a, size: 0.18 });
- particles = new THREE.Points(ptGeo, ptMat);
- scene.add(particles);
-
- window.addEventListener('resize', onResize);
- animate();
- }
-
- function onResize() {
- camera.aspect = window.innerWidth / window.innerHeight;
- camera.updateProjectionMatrix();
- renderer.setSize(window.innerWidth, window.innerHeight);
- }
-
- function animate() {
- requestAnimationFrame(animate);
- var realT = clock.getElapsedTime();
- _snoTOffset += (realT - _snoLastT) * (_wild ? 11 : 0);
- _snoLastT = realT;
- var t = realT + _snoTOffset;
- // Slow multi-axis rotation; wild mode overloads the phosphor orb
- icosa.rotation.x = t * 0.12;
- icosa.rotation.y = t * 0.18;
- icosa.rotation.z = t * 0.07;
- // Counter-rotate particles for visual contrast
- particles.rotation.y = -t * 0.08;
- particles.rotation.x = t * 0.04;
- renderer.render(scene, camera);
- }
-
- initThree();
-
- // Terminal nav/wild effects — cursor glitch on navigate, buffer overflow on wild
- window.snonuxOpenEffect = function() {
- // Slide in like terminal output being printed
- var modal = document.getElementById('post-modal');
- if (modal) { modal.classList.add('sno-modal-slide'); setTimeout(function() { modal.classList.remove('sno-modal-slide'); }, 360); }
- // Phosphor scan from top to bottom
- var scan = document.createElement('div');
- scan.style.cssText = 'position:fixed;top:0;left:0;right:0;height:2px;z-index:997;pointer-events:none;background:rgba(51,255,51,0.6);box-shadow:0 0 8px rgba(51,255,51,0.4);transition:top 0.3s linear,opacity 0.1s 0.3s';
- document.body.appendChild(scan);
- setTimeout(function() { scan.style.top='100vh'; setTimeout(function() { scan.style.opacity='0'; setTimeout(function() { scan.remove(); }, 120); }, 300); }, 15);
- };
- window.snonuxCloseEffect = function() {
- var d = document.createElement('div');
- d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(51,255,51,0.1);transition:opacity 0.18s';
- document.body.appendChild(d);
- setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 200); }, 15);
- document.body.style.animationDuration = '9s';
- };
- window.snonuxScrollEffect = function(dir) {
- var isDown = dir === 'down';
- var thick = _wild ? '14px' : '5px';
- var d = document.createElement('div');
- // Terminal: phosphor green scan
- d.style.cssText = 'position:fixed;left:0;right:0;height:' + thick + ';z-index:9000;pointer-events:none;' +
- 'background:linear-gradient(90deg,transparent,rgba(57,255,20,0.9),rgba(20,200,10,0.9),rgba(57,255,20,0.9),transparent);' +
- (isDown ? 'top:0;' : 'bottom:0;') +
- 'transition:transform 0.28s ease,opacity 0.28s ease;';
- document.body.appendChild(d);
- setTimeout(function() { d.style.transform = isDown ? 'translateY(100vh)' : 'translateY(-100vh)'; d.style.opacity='0'; }, 16);
- setTimeout(function() { d.remove(); }, 360);
- };
- window.snonuxWildToggle = function() {
- _wild = !_wild;
- var b = document.getElementById('sno-wild-badge');
- if (b) b.classList.toggle('sno-wild-on', _wild);
- // Toggle intense scanline strobe in wild mode
- document.body.style.animationDuration = _wild ? '0.4s' : '9s';
- };
- window.snonuxNavEffect = function() {
- var ov = document.querySelector('.overlay');
- if (ov) { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); }, 300); }
- var d = document.createElement('div');
- d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(51,255,51,0.13);transition:opacity 0.18s';
- document.body.appendChild(d);
- setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 210); }, 25);
- };
- window.snonuxPageEffect = function() {
- var ov = document.querySelector('.overlay');
- if (ov) { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); setTimeout(function() { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); }, 280); }, 35); }, 300); }
- };
- })();
- </script>
- {{template "navscript" .}}
-</body>
-</html>
diff --git a/internal/generator/templates/themes/terminal/meta.json b/internal/generator/templates/themes/terminal/meta.json
new file mode 100644
index 0000000..8fbc7ed
--- /dev/null
+++ b/internal/generator/templates/themes/terminal/meta.json
@@ -0,0 +1,7 @@
+{
+ "title": "snonux.foo // TERMINAL",
+ "header_html": "\u003cdiv class=\"logo\"\u003e\n \u003cspan class=\"logo-mark\"\u003e[SN]\u003c/span\u003e\n \u003cdiv class=\"logo-title\"\u003e\n \u003ch1\u003esnonux.foo\u003c/h1\u003e\n \u003cp class=\"subtitle\"\u003emicroblog / \u003ca href=\"https://foo.zone\"\u003efoo.zone\u003c/a\u003e is the real blog\u003c/p\u003e\n \u003cp class=\"logo-host\"\u003eServed by NetBSD on a Raspberry Pi 3\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"nav\"\u003e\n \u003ca href=\"atom.xml\" class=\"header-feed-link\" rel=\"alternate\" title=\"Atom feed\" type=\"application/atom+xml\"\u003eatom.xml\u003c/a\u003e\n \u003ca href=\"https://foo.zone/about\" class=\"transmit-btn\"\u003e\u0026gt; TRANSMIT\u003c/a\u003e\n \u003c/div\u003e",
+ "splash_inner_html": "\u003ccanvas class=\"splash-gl-canvas\" id=\"splash-gl-canvas\" aria-hidden=\"true\"\u003e\u003c/canvas\u003e\n \u003cdiv class=\"splash-inner\"\u003e\n \u003cdiv class=\"splash-prompt\"\u003e\u0026gt; ./snonux --boot\u003c/div\u003e\n \u003cdiv class=\"splash-title splash-cursor\"\u003eLINK ESTABLISHED\u003c/div\u003e\n \u003cdiv class=\"splash-tag\"\u003eTERMINAL SESSION\u003c/div\u003e\n \u003cdiv class=\"splash-hint\"\u003e[ click / enter to continue ]\u003c/div\u003e\n \u003c/div\u003e",
+ "prev_page_text": "\u0026lt;-- NEWER",
+ "next_page_text": "OLDER --\u0026gt;"
+}
diff --git a/internal/generator/templates/themes/terminal/theme.css b/internal/generator/templates/themes/terminal/theme.css
new file mode 100644
index 0000000..afd4489
--- /dev/null
+++ b/internal/generator/templates/themes/terminal/theme.css
@@ -0,0 +1,70 @@
+ :root { --p:#33ff33; --dim:#1a7a1a; --bg:#0a0a0a; --bg2:#050505; }
+ * { margin:0; padding:0; box-sizing:border-box; }
+ body { font-family:'Courier New',Courier,monospace; background:var(--bg); color:var(--p);
+ overflow:hidden; height:100vh; position:relative; }
+ /* CRT scanlines sit above the WebGL canvas */
+ body::before { content:''; position:fixed; inset:0; z-index:999; pointer-events:none;
+ background:repeating-linear-gradient(0deg,transparent,transparent 2px,
+ rgba(0,0,0,0.12) 2px,rgba(0,0,0,0.12) 4px); }
+ /* Subtle screen flicker */
+ @keyframes flicker { 0%,100%{opacity:1} 93%{opacity:0.97} 95%{opacity:0.91} 97%{opacity:0.98} }
+ body { animation:flicker 9s infinite; }
+ /* WebGL background canvas — fills the viewport behind everything */
+ #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
+ .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
+ header { padding:12px 24px; background:var(--bg2); border-bottom:2px solid var(--p);
+ display:flex; align-items:center; justify-content:space-between; }
+ .logo { display:flex; align-items:center; gap:14px; }
+ .logo-mark { font-size:1.6rem; color:var(--p); text-shadow:0 0 14px var(--p); letter-spacing:2px; }
+ .logo-title h1 { font-size:1.3rem; color:var(--p); text-shadow:0 0 10px var(--p);
+ letter-spacing:3px; font-weight:normal; }
+ .logo-title .subtitle { font-size:0.72rem; color:var(--dim); margin-top:2px; }
+ .logo-title .subtitle a { color:var(--p); text-decoration:none; }
+ .logo-title .subtitle a:hover { text-shadow:0 0 6px var(--p); }
+ .nav a.transmit-btn { border:1px solid var(--p); color:var(--p); padding:8px 18px;
+ border-radius:0; text-decoration:none; letter-spacing:2px; font-size:0.85rem;
+ transition:all 0.2s; }
+ .nav a.transmit-btn:hover { background:var(--p); color:var(--bg); }
+ a.header-feed-link { color:var(--dim); }
+ a.header-feed-link:hover { color:var(--p); }
+ .nav-hints { background:var(--bg2); border-bottom:1px solid var(--dim); color:var(--dim);
+ padding:5px 24px; display:flex; gap:18px; font-size:0.68rem; flex-wrap:wrap; }
+ .nav-hints kbd { background:transparent; border:1px solid var(--dim); color:var(--p);
+ border-radius:0; padding:0 5px; font-size:0.7rem; margin:0 2px; }
+ .content { flex:1; overflow-y:auto; padding:16px 24px;
+ scrollbar-width:thin; scrollbar-color:var(--dim) var(--bg); }
+ .page-nav { display:flex; justify-content:center; margin:14px 0; }
+ .page-nav a { border:1px solid var(--dim); color:var(--p); padding:7px 20px;
+ border-radius:0; text-decoration:none; letter-spacing:2px; font-size:0.82rem; }
+ .page-nav a:hover { background:var(--p); color:var(--bg); border-color:var(--p); }
+ .page-nav-footer { flex-shrink:0; padding:6px 24px; display:flex; justify-content:center;
+ background:var(--bg2); border-top:2px solid var(--p); }
+ .post { background:var(--bg); border:1px solid var(--dim); border-radius:0;
+ padding:18px 20px; margin-bottom:12px; cursor:pointer; transition:border-color 0.15s; }
+ .post:hover { border-color:var(--p); box-shadow:0 0 8px rgba(51,255,51,0.3); }
+ .post-active { border-color:var(--p) !important; background:rgba(51,255,51,0.04) !important;
+ box-shadow:0 0 14px rgba(51,255,51,0.3),inset 3px 0 0 var(--p) !important; }
+ .post-header { display:flex; justify-content:space-between; margin-bottom:12px; font-size:0.88rem; }
+ .post-time { color:var(--dim); font-size:0.82rem; }
+ .post-text { line-height:1.6; font-size:0.92rem; }
+ .post-text a { color:var(--p); text-decoration:underline; }
+ .post-image { max-width:100%; margin-top:10px; border:1px solid var(--dim); }
+ .post-audio { width:100%; margin-top:10px; }
+ .post-modal { display:none; position:fixed; inset:0; z-index:100;
+ background:rgba(0,0,0,0.97); overflow-y:auto; padding:40px 20px; }
+ .post-modal.active { display:block; }
+ .modal-inner { max-width:760px; margin:0 auto; background:var(--bg);
+ border:1px solid var(--p); border-radius:0;
+ box-shadow:0 0 40px rgba(51,255,51,0.25); padding:36px; }
+ .modal-close { float:right; background:none; border:none; color:var(--p);
+ font-family:monospace; font-size:0.9rem; cursor:pointer; letter-spacing:2px; }
+ @media(max-width:640px) { .nav-hints{display:none;} header{padding:10px 16px;} .content{padding:12px 16px;} }
+ [data-sno-theme="terminal"] .splash-overlay { background: var(--bg); font-family:'Courier New',monospace; }
+ [data-sno-theme="terminal"] .splash-prompt { text-align:left; font-size:0.9rem; color:rgba(51,255,51,0.78); margin-bottom:0.5rem; }
+ [data-sno-theme="terminal"] .splash-title { font-size:clamp(1.2rem,4vw,1.65rem); color:var(--p);
+ text-shadow:0 0 12px var(--p); letter-spacing:0.15em; }
+ [data-sno-theme="terminal"] .splash-cursor::after { content:'█'; animation: splashTermBlink 1s step-end infinite; color:var(--p); }
+ @keyframes splashTermBlink { 0%,100%{opacity:1} 50%{opacity:0} }
+ [data-sno-theme="terminal"] .splash-tag { color:rgba(51,255,51,0.85); letter-spacing:0.25em; }
+ [data-sno-theme="terminal"] .splash-hint { color:rgba(51,255,51,0.8); }
+ [data-sno-theme="terminal"] .splash-inner { text-shadow: 0 0 8px #000, 0 2px 12px #000; }
diff --git a/internal/generator/templates/themes/terminal/theme.js b/internal/generator/templates/themes/terminal/theme.js
new file mode 100644
index 0000000..4400a7e
--- /dev/null
+++ b/internal/generator/templates/themes/terminal/theme.js
@@ -0,0 +1,141 @@
+
+ (function(){
+ if(document.documentElement.classList.contains('sno-splash-skip'))return;
+ var cv=document.getElementById('splash-gl-canvas');
+ if(!cv||typeof THREE==='undefined')return;
+ var raf,ren,sc,ca,m,t0=performance.now();
+ function cleanup(){window.removeEventListener('resize',sz);if(raf)cancelAnimationFrame(raf);raf=null;if(ren)ren.dispose();ren=null;window._snonuxSplashWebGLCleanup=null;}
+ window._snonuxSplashWebGLCleanup=cleanup;
+ function sz(){var w=cv.clientWidth||2,h=cv.clientHeight||2;if(ren)ren.setSize(w,h,false);if(ca){ca.aspect=w/h;ca.updateProjectionMatrix();}}
+ ren=new THREE.WebGLRenderer({canvas:cv,antialias:true,alpha:true});ren.setClearColor(0,0);ren.setPixelRatio(Math.min(window.devicePixelRatio||1,2));
+ sc=new THREE.Scene();ca=new THREE.PerspectiveCamera(48,1,0.1,60);ca.position.z=7;
+ m=new THREE.Mesh(new THREE.IcosahedronGeometry(2.3,1),new THREE.MeshBasicMaterial({color:0x33ff33,wireframe:true,transparent:true,opacity:0.88}));
+ sc.add(m);sz();window.addEventListener('resize',sz);
+ function loop(now){raf=requestAnimationFrame(loop);var t=(now-t0)*0.001;m.rotation.x=t*0.62;m.rotation.y=t*0.88;ren.render(sc,ca);}
+ raf=requestAnimationFrame(loop);
+ })();
+
+
+ // Terminal WebGL scene: phosphor-green icosahedron wireframe + torus particle ring.
+ // The scene sits behind the CRT scanline overlay (z-index:999) and the UI (z-index:10).
+ (function() {
+ var _wild = false, _snoTOffset = 0, _snoLastT = 0;
+ var scene, camera, renderer, icosa, particles;
+ var clock = new THREE.Clock();
+
+ function initThree() {
+ // Scene with pure-black background and distance fog
+ scene = new THREE.Scene();
+ scene.background = new THREE.Color(0x000000);
+ scene.fog = new THREE.Fog(0x000000, 20, 80);
+
+ // Perspective camera positioned in front of the orb
+ camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 200);
+ camera.position.set(0, 0, 30);
+
+ renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('three-canvas'), antialias: true });
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
+
+ // Large green phosphor wireframe icosahedron — the central CRT orb
+ var icoGeo = new THREE.IcosahedronGeometry(8, 2);
+ var icoMat = new THREE.MeshBasicMaterial({ color: 0x33ff33, wireframe: true });
+ icosa = new THREE.Mesh(icoGeo, icoMat);
+ scene.add(icosa);
+
+ // 400 dim particles arranged on a torus path around the icosahedron
+ var torusGeo = new THREE.TorusGeometry(14, 3, 16, 100);
+ var positions = torusGeo.attributes.position;
+ var ptGeo = new THREE.BufferGeometry();
+ var pts = new Float32Array(400 * 3);
+ for (var i = 0; i < 400; i++) {
+ // Sample vertices from the torus geometry to place particles on its surface
+ var idx = Math.floor(Math.random() * positions.count);
+ pts[i * 3] = positions.getX(idx);
+ pts[i * 3 + 1] = positions.getY(idx);
+ pts[i * 3 + 2] = positions.getZ(idx);
+ }
+ ptGeo.setAttribute('position', new THREE.BufferAttribute(pts, 3));
+ var ptMat = new THREE.PointsMaterial({ color: 0x1a7a1a, size: 0.18 });
+ particles = new THREE.Points(ptGeo, ptMat);
+ scene.add(particles);
+
+ window.addEventListener('resize', onResize);
+ animate();
+ }
+
+ function onResize() {
+ camera.aspect = window.innerWidth / window.innerHeight;
+ camera.updateProjectionMatrix();
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ }
+
+ function animate() {
+ requestAnimationFrame(animate);
+ var realT = clock.getElapsedTime();
+ _snoTOffset += (realT - _snoLastT) * (_wild ? 11 : 0);
+ _snoLastT = realT;
+ var t = realT + _snoTOffset;
+ // Slow multi-axis rotation; wild mode overloads the phosphor orb
+ icosa.rotation.x = t * 0.12;
+ icosa.rotation.y = t * 0.18;
+ icosa.rotation.z = t * 0.07;
+ // Counter-rotate particles for visual contrast
+ particles.rotation.y = -t * 0.08;
+ particles.rotation.x = t * 0.04;
+ renderer.render(scene, camera);
+ }
+
+ initThree();
+
+ // Terminal nav/wild effects — cursor glitch on navigate, buffer overflow on wild
+ window.snonuxOpenEffect = function() {
+ // Slide in like terminal output being printed
+ var modal = document.getElementById('post-modal');
+ if (modal) { modal.classList.add('sno-modal-slide'); setTimeout(function() { modal.classList.remove('sno-modal-slide'); }, 360); }
+ // Phosphor scan from top to bottom
+ var scan = document.createElement('div');
+ scan.style.cssText = 'position:fixed;top:0;left:0;right:0;height:2px;z-index:997;pointer-events:none;background:rgba(51,255,51,0.6);box-shadow:0 0 8px rgba(51,255,51,0.4);transition:top 0.3s linear,opacity 0.1s 0.3s';
+ document.body.appendChild(scan);
+ setTimeout(function() { scan.style.top='100vh'; setTimeout(function() { scan.style.opacity='0'; setTimeout(function() { scan.remove(); }, 120); }, 300); }, 15);
+ };
+ window.snonuxCloseEffect = function() {
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(51,255,51,0.1);transition:opacity 0.18s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 200); }, 15);
+ document.body.style.animationDuration = '9s';
+ };
+ window.snonuxScrollEffect = function(dir) {
+ var isDown = dir === 'down';
+ var thick = _wild ? '14px' : '5px';
+ var d = document.createElement('div');
+ // Terminal: phosphor green scan
+ d.style.cssText = 'position:fixed;left:0;right:0;height:' + thick + ';z-index:9000;pointer-events:none;' +
+ 'background:linear-gradient(90deg,transparent,rgba(57,255,20,0.9),rgba(20,200,10,0.9),rgba(57,255,20,0.9),transparent);' +
+ (isDown ? 'top:0;' : 'bottom:0;') +
+ 'transition:transform 0.28s ease,opacity 0.28s ease;';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.transform = isDown ? 'translateY(100vh)' : 'translateY(-100vh)'; d.style.opacity='0'; }, 16);
+ setTimeout(function() { d.remove(); }, 360);
+ };
+ window.snonuxWildToggle = function() {
+ _wild = !_wild;
+ var b = document.getElementById('sno-wild-badge');
+ if (b) b.classList.toggle('sno-wild-on', _wild);
+ // Toggle intense scanline strobe in wild mode
+ document.body.style.animationDuration = _wild ? '0.4s' : '9s';
+ };
+ window.snonuxNavEffect = function() {
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); }, 300); }
+ var d = document.createElement('div');
+ d.style.cssText = 'position:fixed;inset:0;z-index:998;pointer-events:none;background:rgba(51,255,51,0.13);transition:opacity 0.18s';
+ document.body.appendChild(d);
+ setTimeout(function() { d.style.opacity='0'; setTimeout(function() { d.remove(); }, 210); }, 25);
+ };
+ window.snonuxPageEffect = function() {
+ var ov = document.querySelector('.overlay');
+ if (ov) { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); setTimeout(function() { ov.classList.add('sno-fx-glitch'); setTimeout(function() { ov.classList.remove('sno-fx-glitch'); }, 280); }, 35); }, 300); }
+ };
+ })();
diff --git a/internal/generator/templates/themes/tropicale/meta.json b/internal/generator/templates/themes/tropicale/meta.json
new file mode 100644
index 0000000..292b1ed
--- /dev/null
+++ b/internal/generator/templates/themes/tropicale/meta.json
@@ -0,0 +1,7 @@
+{
+ "title": "snonux.foo ~ TROPICALE",
+ "header_html": "\u003cdiv class=\"logo\"\u003e\n \u003cspan class=\"logo-mark\"\u003eSN\u003c/span\u003e\n \u003cdiv class=\"logo-title\"\u003e\n \u003ch1\u003esnonux.foo\u003c/h1\u003e\n \u003cp class=\"subtitle\"\u003emicroblog \u0026mdash; \u003ca href=\"https://foo.zone\"\u003efoo.zone\u003c/a\u003e is the real blog\u003c/p\u003e\n \u003cp class=\"logo-host\"\u003eServed by NetBSD on a Raspberry Pi 3\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"nav\"\u003e\n \u003ca href=\"atom.xml\" class=\"header-feed-link\" rel=\"alternate\" title=\"Atom feed\" type=\"application/atom+xml\"\u003eAtom feed\u003c/a\u003e\n \u003ca href=\"https://foo.zone/about\" class=\"transmit-btn\"\u003eTransmit\u003c/a\u003e\n \u003c/div\u003e",
+ "splash_inner_html": "\u003ccanvas class=\"splash-gl-canvas\" id=\"splash-gl-canvas\" aria-hidden=\"true\"\u003e\u003c/canvas\u003e\n \u003cdiv class=\"splash-sun-glow\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-wave-bar\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-inner\"\u003e\n \u003cdiv class=\"splash-title\"\u003esnonux.foo\u003c/div\u003e\n \u003cdiv class=\"splash-tag\"\u003eIsland transmission\u003c/div\u003e\n \u003cdiv class=\"splash-hint\"\u003eRide the wave — click or Enter\u003c/div\u003e\n \u003c/div\u003e",
+ "prev_page_text": "\u0026larr; Newer",
+ "next_page_text": "Older \u0026rarr;"
+}
diff --git a/internal/generator/templates/themes/tropicale/theme.css b/internal/generator/templates/themes/tropicale/theme.css
new file mode 100644
index 0000000..cc93504
--- /dev/null
+++ b/internal/generator/templates/themes/tropicale/theme.css
@@ -0,0 +1,81 @@
+ :root { --sand:#e8c97a; --sky:#38c9d8; --lagoon:#0e7490; --sun:#fbbf24; --coral:#f97316; --dusk:#0a1e2e; --cream:#fef9e7; }
+ * { margin:0; padding:0; box-sizing:border-box; }
+ body { font-family:'Segoe UI',system-ui,sans-serif; background:var(--dusk);
+ color:var(--cream); overflow:hidden; height:100vh; }
+ #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
+ .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
+ header { padding:16px 28px; background:rgba(10,30,46,0.82); backdrop-filter:blur(12px);
+ border-bottom:1px solid rgba(56,201,216,0.3); display:flex; align-items:center; justify-content:space-between; }
+ .logo { display:flex; align-items:center; gap:14px; }
+ .logo-mark { font-size:2rem; font-weight:800; color:var(--sun); text-shadow:0 0 18px rgba(251,191,36,0.7); }
+ .logo-title h1 { font-size:1.5rem; font-weight:700; color:var(--cream); letter-spacing:1px; }
+ .logo-title .subtitle { font-size:0.75rem; color:rgba(254,249,231,0.55); margin-top:2px; }
+ .logo-title .subtitle a { color:var(--sky); text-decoration:none; }
+ .logo-title .subtitle a:hover { text-shadow:0 0 8px var(--sky); }
+ .transmit-btn { border:1px solid var(--sky); color:var(--sky); padding:9px 20px;
+ border-radius:20px; text-decoration:none; font-size:0.85rem; transition:all 0.2s; }
+ .transmit-btn:hover { background:var(--sky); color:var(--dusk); }
+ a.header-feed-link { color:var(--sand); }
+ a.header-feed-link:hover { color:var(--cream); }
+ .nav-hints { background:rgba(10,30,46,0.65); border-bottom:1px solid rgba(56,201,216,0.18);
+ color:rgba(254,249,231,0.45); padding:5px 28px; display:flex; gap:18px;
+ font-size:0.68rem; flex-wrap:wrap; }
+ .nav-hints kbd { background:rgba(56,201,216,0.12); border:1px solid rgba(56,201,216,0.35);
+ color:var(--sky); border-radius:3px; padding:0 5px; margin:0 2px; }
+ .content { flex:1; overflow-y:auto; padding:20px 28px;
+ scrollbar-width:thin; scrollbar-color:var(--sky) var(--dusk); }
+ .page-nav { display:flex; justify-content:center; margin:14px 0; }
+ .page-nav a { border:1px solid var(--lagoon); color:var(--sky); padding:8px 20px;
+ border-radius:20px; text-decoration:none; font-size:0.82rem; }
+ .page-nav a:hover { background:var(--sky); color:var(--dusk); }
+ .page-nav-footer { flex-shrink:0; padding:8px 28px; display:flex; justify-content:center;
+ background:rgba(10,30,46,0.82); backdrop-filter:blur(12px);
+ border-top:1px solid rgba(56,201,216,0.3); }
+ .post { background:rgba(10,40,60,0.55); border:1px solid rgba(56,201,216,0.22); border-radius:10px;
+ padding:20px; margin-bottom:14px; cursor:pointer;
+ transition:all 0.25s; backdrop-filter:blur(6px); }
+ .post:hover { border-color:var(--sky); box-shadow:0 4px 24px rgba(56,201,216,0.22); transform:translateY(-2px); }
+ .post-active { border-color:var(--coral) !important; background:rgba(60,20,10,0.55) !important;
+ box-shadow:0 0 22px rgba(249,115,22,0.35),inset 3px 0 0 var(--coral) !important; }
+ .post-header { display:flex; justify-content:space-between; margin-bottom:12px; font-size:0.88rem; }
+ .post-time { color:var(--sand); font-family:monospace; font-size:0.8rem; }
+ .post-text { line-height:1.65; font-size:0.95rem; }
+ .post-text a { color:var(--sky); text-decoration:none; }
+ .post-text a:hover { text-shadow:0 0 8px var(--sky); }
+ .post-audio { width:100%; margin-top:10px; }
+ .post-modal { display:none; position:fixed; inset:0; z-index:100;
+ overflow-y:auto; padding:40px 20px; }
+ .post-modal.active { display:block; }
+ .modal-inner { max-width:760px; margin:0 auto; background:rgba(10,25,40,0.94);
+ border:1px solid var(--sky); border-radius:12px; backdrop-filter:blur(16px);
+ box-shadow:0 0 60px rgba(56,201,216,0.3); padding:40px; }
+ .modal-close { float:right; background:none; border:none; color:var(--sky);
+ font-size:0.9rem; cursor:pointer; letter-spacing:1px; }
+ @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 18px;} .content{padding:14px 18px;} }
+ /* Splash screen — tropical sunset gradient with radial sun glow */
+ [data-sno-theme="tropicale"] .splash-overlay {
+ background:
+ radial-gradient(ellipse 55% 40% at 75% 35%, rgba(251,191,36,0.22) 0%, transparent 60%),
+ linear-gradient(175deg, #0a1e2e 0%, #0e3a50 30%, #1a5c70 55%, #0e3a50 80%, #0a1e2e 100%);
+ }
+ [data-sno-theme="tropicale"] .splash-sun-glow {
+ position:absolute; right:22%; top:18%; width:clamp(80px,14vw,130px); height:clamp(80px,14vw,130px);
+ border-radius:50%; pointer-events:none; z-index:0;
+ background:radial-gradient(circle, rgba(251,191,36,0.9) 30%, rgba(249,115,22,0.5) 60%, transparent 80%);
+ box-shadow:0 0 60px 30px rgba(251,191,36,0.35);
+ animation: splashSunPulse 3.5s ease-in-out infinite;
+ }
+ @keyframes splashSunPulse { 0%,100%{ opacity:0.85; transform:scale(1); } 50%{ opacity:1; transform:scale(1.06); } }
+ [data-sno-theme="tropicale"] .splash-wave-bar {
+ position:absolute; bottom:0; left:0; right:0; height:18px;
+ background: linear-gradient(90deg, transparent, rgba(56,201,216,0.6), rgba(232,201,122,0.4), rgba(56,201,216,0.6), transparent);
+ animation: splashWaveSweep 2.8s ease-in-out infinite;
+ }
+ @keyframes splashWaveSweep { 0%,100%{ transform:scaleX(1) translateY(0); } 50%{ transform:scaleX(1.04) translateY(-3px); } }
+ [data-sno-theme="tropicale"] .splash-title {
+ font-size:clamp(1.45rem,4.5vw,2rem); color:var(--cream);
+ text-shadow:0 0 24px rgba(251,191,36,0.55);
+ }
+ [data-sno-theme="tropicale"] .splash-tag { color:var(--sky); letter-spacing:0.2em; }
+ [data-sno-theme="tropicale"] .splash-hint { color:rgba(254,249,231,0.88); }
+ [data-sno-theme="tropicale"] .splash-inner { position:relative; z-index:2; text-shadow:0 2px 16px rgba(10,30,46,0.9); }
diff --git a/internal/generator/templates/themes/tropicale.tmpl b/internal/generator/templates/themes/tropicale/theme.js
index 920498b..7f7b600 100644
--- a/internal/generator/templates/themes/tropicale.tmpl
+++ b/internal/generator/templates/themes/tropicale/theme.js
@@ -1,108 +1,4 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>snonux.foo ~ TROPICALE</title>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
- <style>
- :root { --sand:#e8c97a; --sky:#38c9d8; --lagoon:#0e7490; --sun:#fbbf24; --coral:#f97316; --dusk:#0a1e2e; --cream:#fef9e7; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:'Segoe UI',system-ui,sans-serif; background:var(--dusk);
- color:var(--cream); overflow:hidden; height:100vh; }
- #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
- .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
- header { padding:16px 28px; background:rgba(10,30,46,0.82); backdrop-filter:blur(12px);
- border-bottom:1px solid rgba(56,201,216,0.3); display:flex; align-items:center; justify-content:space-between; }
- .logo { display:flex; align-items:center; gap:14px; }
- .logo-mark { font-size:2rem; font-weight:800; color:var(--sun); text-shadow:0 0 18px rgba(251,191,36,0.7); }
- .logo-title h1 { font-size:1.5rem; font-weight:700; color:var(--cream); letter-spacing:1px; }
- .logo-title .subtitle { font-size:0.75rem; color:rgba(254,249,231,0.55); margin-top:2px; }
- .logo-title .subtitle a { color:var(--sky); text-decoration:none; }
- .logo-title .subtitle a:hover { text-shadow:0 0 8px var(--sky); }
- .transmit-btn { border:1px solid var(--sky); color:var(--sky); padding:9px 20px;
- border-radius:20px; text-decoration:none; font-size:0.85rem; transition:all 0.2s; }
- .transmit-btn:hover { background:var(--sky); color:var(--dusk); }
- a.header-feed-link { color:var(--sand); }
- a.header-feed-link:hover { color:var(--cream); }
- .nav-hints { background:rgba(10,30,46,0.65); border-bottom:1px solid rgba(56,201,216,0.18);
- color:rgba(254,249,231,0.45); padding:5px 28px; display:flex; gap:18px;
- font-size:0.68rem; flex-wrap:wrap; }
- .nav-hints kbd { background:rgba(56,201,216,0.12); border:1px solid rgba(56,201,216,0.35);
- color:var(--sky); border-radius:3px; padding:0 5px; margin:0 2px; }
- .content { flex:1; overflow-y:auto; padding:20px 28px;
- scrollbar-width:thin; scrollbar-color:var(--sky) var(--dusk); }
- .page-nav { display:flex; justify-content:center; margin:14px 0; }
- .page-nav a { border:1px solid var(--lagoon); color:var(--sky); padding:8px 20px;
- border-radius:20px; text-decoration:none; font-size:0.82rem; }
- .page-nav a:hover { background:var(--sky); color:var(--dusk); }
- .page-nav-footer { flex-shrink:0; padding:8px 28px; display:flex; justify-content:center;
- background:rgba(10,30,46,0.82); backdrop-filter:blur(12px);
- border-top:1px solid rgba(56,201,216,0.3); }
- .post { background:rgba(10,40,60,0.55); border:1px solid rgba(56,201,216,0.22); border-radius:10px;
- padding:20px; margin-bottom:14px; cursor:pointer;
- transition:all 0.25s; backdrop-filter:blur(6px); }
- .post:hover { border-color:var(--sky); box-shadow:0 4px 24px rgba(56,201,216,0.22); transform:translateY(-2px); }
- .post-active { border-color:var(--coral) !important; background:rgba(60,20,10,0.55) !important;
- box-shadow:0 0 22px rgba(249,115,22,0.35),inset 3px 0 0 var(--coral) !important; }
- .post-header { display:flex; justify-content:space-between; margin-bottom:12px; font-size:0.88rem; }
- .post-time { color:var(--sand); font-family:monospace; font-size:0.8rem; }
- .post-text { line-height:1.65; font-size:0.95rem; }
- .post-text a { color:var(--sky); text-decoration:none; }
- .post-text a:hover { text-shadow:0 0 8px var(--sky); }
- .post-audio { width:100%; margin-top:10px; }
- .post-modal { display:none; position:fixed; inset:0; z-index:100;
- overflow-y:auto; padding:40px 20px; }
- .post-modal.active { display:block; }
- .modal-inner { max-width:760px; margin:0 auto; background:rgba(10,25,40,0.94);
- border:1px solid var(--sky); border-radius:12px; backdrop-filter:blur(16px);
- box-shadow:0 0 60px rgba(56,201,216,0.3); padding:40px; }
- .modal-close { float:right; background:none; border:none; color:var(--sky);
- font-size:0.9rem; cursor:pointer; letter-spacing:1px; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 18px;} .content{padding:14px 18px;} }
- /* Splash screen — tropical sunset gradient with radial sun glow */
- .splash-overlay.splash-tropicale {
- background:
- radial-gradient(ellipse 55% 40% at 75% 35%, rgba(251,191,36,0.22) 0%, transparent 60%),
- linear-gradient(175deg, #0a1e2e 0%, #0e3a50 30%, #1a5c70 55%, #0e3a50 80%, #0a1e2e 100%);
- }
- .splash-tropicale .splash-sun-glow {
- position:absolute; right:22%; top:18%; width:clamp(80px,14vw,130px); height:clamp(80px,14vw,130px);
- border-radius:50%; pointer-events:none; z-index:0;
- background:radial-gradient(circle, rgba(251,191,36,0.9) 30%, rgba(249,115,22,0.5) 60%, transparent 80%);
- box-shadow:0 0 60px 30px rgba(251,191,36,0.35);
- animation: splashSunPulse 3.5s ease-in-out infinite;
- }
- @keyframes splashSunPulse { 0%,100%{ opacity:0.85; transform:scale(1); } 50%{ opacity:1; transform:scale(1.06); } }
- .splash-tropicale .splash-wave-bar {
- position:absolute; bottom:0; left:0; right:0; height:18px;
- background: linear-gradient(90deg, transparent, rgba(56,201,216,0.6), rgba(232,201,122,0.4), rgba(56,201,216,0.6), transparent);
- animation: splashWaveSweep 2.8s ease-in-out infinite;
- }
- @keyframes splashWaveSweep { 0%,100%{ transform:scaleX(1) translateY(0); } 50%{ transform:scaleX(1.04) translateY(-3px); } }
- .splash-tropicale .splash-title {
- font-size:clamp(1.45rem,4.5vw,2rem); color:var(--cream);
- text-shadow:0 0 24px rgba(251,191,36,0.55);
- }
- .splash-tropicale .splash-tag { color:var(--sky); letter-spacing:0.2em; }
- .splash-tropicale .splash-hint { color:rgba(254,249,231,0.88); }
- .splash-tropicale .splash-inner { position:relative; z-index:2; text-shadow:0 2px 16px rgba(10,30,46,0.9); }
-{{template "navSharedCSSInner"}}
- </style>
-</head>
-<body>
- {{template "splashGate"}}
- <div id="splash-overlay" class="splash-overlay splash-tropicale" role="dialog" aria-modal="true" aria-label="Open microblog" tabindex="-1">
- <canvas class="splash-gl-canvas" id="splash-gl-canvas" aria-hidden="true"></canvas>
- <div class="splash-sun-glow" aria-hidden="true"></div>
- <div class="splash-wave-bar" aria-hidden="true"></div>
- <div class="splash-inner">
- <div class="splash-title">snonux.foo</div>
- <div class="splash-tag">Island transmission</div>
- <div class="splash-hint">Ride the wave — click or Enter</div>
- </div>
- </div>
- <script>
+
(function(){
if(document.documentElement.classList.contains('sno-splash-skip'))return;
var cv=document.getElementById('splash-gl-canvas');
@@ -154,46 +50,8 @@
ren.render(sc,ca);}
raf=requestAnimationFrame(loop);
})();
- </script>
- <canvas id="three-canvas"></canvas>
- <div class="overlay">
- <header>
- <div class="logo">
- <span class="logo-mark">SN</span>
- <div class="logo-title">
- <h1>snonux.foo</h1>
- <p class="subtitle">microblog &mdash; <a href="https://foo.zone">foo.zone</a> is the real blog</p>
- <p class="logo-host">Served by NetBSD on a Raspberry Pi 3</p>
- </div>
- </div>
- <div class="nav">
- <a href="atom.xml" class="header-feed-link" rel="alternate" title="Atom feed" type="application/atom+xml">Atom feed</a>
- <a href="https://foo.zone/about" class="transmit-btn">Transmit</a>
- </div>
- </header>
- {{template "navhints" .}}
- <div class="content" id="post-content">
- {{range $i, $post := .Posts}}
- <div class="post" id="post-{{$post.ID}}" data-index="{{$i}}">
- <div class="post-header">
- <div><strong>@snonux</strong></div>
- <div class="post-time">{{$post.FormattedTime}}</div>
- </div>
- <div class="post-text">{{$post.ContentHTML}}</div>
- </div>
- {{end}}
- </div>
- {{if or .PrevPage .NextPage}}
- <footer class="page-nav-footer" aria-label="Pagination">
- <div class="page-nav page-nav-dual">
- {{if .PrevPage}}<a href="{{.PrevPage}}">&larr; Newer</a>{{end}}
- {{if .NextPage}}<a href="{{.NextPage}}">Older &rarr;</a>{{end}}
- </div>
- </footer>
- {{end}}
- </div>
- {{template "navmodal" .}}
- <script>
+
+
// Tropicale WebGL: tropical sunset beach — rolling ocean waves, a glowing sun
// sinking toward the horizon, drifting seagulls, a palm tree silhouette on
// the shore, and golden sparkle particles on the water surface.
@@ -569,7 +427,3 @@
if (ov) { ov.classList.add('sno-fx-zoom'); setTimeout(function() { ov.classList.remove('sno-fx-zoom'); }, 330); }
};
})();
- </script>
- {{template "navscript" .}}
-</body>
-</html>
diff --git a/internal/generator/templates/themes/volcano/meta.json b/internal/generator/templates/themes/volcano/meta.json
new file mode 100644
index 0000000..2998f64
--- /dev/null
+++ b/internal/generator/templates/themes/volcano/meta.json
@@ -0,0 +1,7 @@
+{
+ "title": "snonux.foo ▲ VOLCANO",
+ "header_html": "\u003cdiv class=\"logo\"\u003e\n \u003cspan class=\"logo-mark\"\u003eSN\u003c/span\u003e\n \u003cdiv class=\"logo-title\"\u003e\n \u003ch1\u003esnonux.foo\u003c/h1\u003e\n \u003cp class=\"subtitle\"\u003emicroblog \u0026mdash; \u003ca href=\"https://foo.zone\"\u003efoo.zone\u003c/a\u003e is the real blog\u003c/p\u003e\n \u003cp class=\"logo-host\"\u003eServed by NetBSD on a Raspberry Pi 3\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"nav\"\u003e\n \u003ca href=\"atom.xml\" class=\"header-feed-link\" rel=\"alternate\" title=\"Atom feed\" type=\"application/atom+xml\"\u003eAtom feed\u003c/a\u003e\n \u003ca href=\"https://foo.zone/about\" class=\"transmit-btn\"\u003eTransmit\u003c/a\u003e\n \u003c/div\u003e",
+ "splash_inner_html": "\u003ccanvas class=\"splash-gl-canvas\" id=\"splash-gl-canvas\" aria-hidden=\"true\"\u003e\u003c/canvas\u003e\n \u003cdiv class=\"splash-inner\"\u003e\n \u003cdiv class=\"splash-ember\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n \u003cdiv class=\"splash-title\"\u003esnonux.foo\u003c/div\u003e\n \u003cdiv class=\"splash-tag\"\u003eVolcano vent\u003c/div\u003e\n \u003cdiv class=\"splash-hint\"\u003eErupt into feed — click or Enter\u003c/div\u003e\n \u003c/div\u003e",
+ "prev_page_text": "\u0026larr; Newer",
+ "next_page_text": "Older \u0026rarr;"
+}
diff --git a/internal/generator/templates/themes/volcano/theme.css b/internal/generator/templates/themes/volcano/theme.css
new file mode 100644
index 0000000..6bf109a
--- /dev/null
+++ b/internal/generator/templates/themes/volcano/theme.css
@@ -0,0 +1,69 @@
+ :root { --lava:#ff4400; --ember:#ff8c00; --hot:#ffcc00; --bg:#0d0802; }
+ * { margin:0; padding:0; box-sizing:border-box; }
+ body { font-family:'Segoe UI',system-ui,sans-serif; background:var(--bg);
+ color:#ffe8cc; overflow:hidden; height:100vh; }
+ #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
+ .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
+ header { padding:16px 28px; background:rgba(13,8,2,0.82); backdrop-filter:blur(12px);
+ border-bottom:1px solid rgba(255,68,0,0.3); display:flex; align-items:center; justify-content:space-between; }
+ .logo { display:flex; align-items:center; gap:14px; }
+ .logo-mark { font-size:2rem; font-weight:800; color:var(--ember); text-shadow:0 0 16px var(--lava); }
+ .logo-title h1 { font-size:1.5rem; font-weight:700; color:#ffe8cc; }
+ .logo-title .subtitle { font-size:0.75rem; color:rgba(255,232,204,0.5); margin-top:2px; }
+ .logo-title .subtitle a { color:var(--ember); text-decoration:none; }
+ .logo-title .subtitle a:hover { text-shadow:0 0 8px var(--lava); }
+ .transmit-btn { border:1px solid var(--lava); color:var(--lava); padding:9px 20px;
+ border-radius:4px; text-decoration:none; font-size:0.85rem; transition:all 0.2s; }
+ .transmit-btn:hover { background:var(--lava); color:var(--bg); }
+ a.header-feed-link { color:var(--ember); }
+ a.header-feed-link:hover { color:var(--hot); text-shadow:0 0 8px var(--lava); }
+ .nav-hints { background:rgba(13,8,2,0.7); border-bottom:1px solid rgba(255,68,0,0.15);
+ color:rgba(255,232,204,0.4); padding:5px 28px; display:flex; gap:18px;
+ font-size:0.68rem; flex-wrap:wrap; }
+ .nav-hints kbd { background:rgba(255,68,0,0.12); border:1px solid rgba(255,68,0,0.35);
+ color:var(--ember); border-radius:3px; padding:0 5px; margin:0 2px; }
+ .content { flex:1; overflow-y:auto; padding:20px 28px;
+ scrollbar-width:thin; scrollbar-color:var(--lava) var(--bg); }
+ .page-nav { display:flex; justify-content:center; margin:14px 0; }
+ .page-nav a { border:1px solid var(--ember); color:var(--ember); padding:8px 20px;
+ border-radius:4px; text-decoration:none; font-size:0.82rem; }
+ .page-nav a:hover { background:var(--lava); color:var(--bg); }
+ .page-nav-footer { flex-shrink:0; padding:7px 28px; display:flex; justify-content:center;
+ background:rgba(13,8,2,0.82); backdrop-filter:blur(12px);
+ border-top:1px solid rgba(255,68,0,0.3); }
+ .post { background:rgba(20,8,2,0.72); border:1px solid rgba(255,68,0,0.2); border-radius:8px;
+ padding:20px; margin-bottom:14px; cursor:pointer;
+ transition:all 0.25s; backdrop-filter:blur(4px); }
+ .post:hover { border-color:var(--ember); box-shadow:0 0 20px rgba(255,68,0,0.25); transform:translateY(-2px); }
+ .post-active { border-color:var(--hot) !important; background:rgba(30,8,2,0.9) !important;
+ box-shadow:0 0 24px rgba(255,140,0,0.4),inset 3px 0 0 var(--hot) !important; }
+ .post-header { display:flex; justify-content:space-between; margin-bottom:12px; font-size:0.88rem; }
+ .post-time { color:var(--ember); font-family:monospace; font-size:0.8rem; }
+ .post-text { line-height:1.65; font-size:0.95rem; }
+ .post-text a { color:var(--ember); text-decoration:none; }
+ .post-text a:hover { text-shadow:0 0 8px var(--lava); }
+ .post-audio { width:100%; margin-top:10px; }
+ .post-modal { display:none; position:fixed; inset:0; z-index:100;
+ overflow-y:auto; padding:40px 20px; }
+ .post-modal.active { display:block; }
+ .modal-inner { max-width:760px; margin:0 auto; background:rgba(20,8,2,0.92);
+ border:1px solid var(--lava); border-radius:10px; backdrop-filter:blur(16px);
+ box-shadow:0 0 60px rgba(255,68,0,0.3); padding:40px; }
+ .modal-close { float:right; background:none; border:none; color:var(--ember);
+ font-size:0.9rem; cursor:pointer; letter-spacing:1px; }
+ @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 18px;} .content{padding:14px 18px;} }
+ [data-sno-theme="volcano"] .splash-overlay {
+ background: radial-gradient(ellipse 80% 60% at 50% 100%, rgba(255,68,0,0.35) 0%, transparent 50%), var(--bg);
+ }
+ [data-sno-theme="volcano"] .splash-ember {
+ width:min(200px,55vw); height:4px; margin:0 auto 1.3rem; border-radius:2px;
+ background: linear-gradient(90deg, transparent, var(--lava), var(--hot), var(--ember), transparent);
+ animation: splashEmberPulse 1.6s ease-in-out infinite alternate;
+ box-shadow: 0 0 20px var(--lava), 0 6px 30px rgba(255,68,0,0.4);
+ }
+ @keyframes splashEmberPulse { from { opacity:0.6; transform: scaleX(0.9); } to { opacity:1; transform: scaleX(1); } }
+ [data-sno-theme="volcano"] .splash-title { font-size:clamp(1.45rem,4.5vw,2rem); color:#ffe8cc;
+ text-shadow:0 0 20px var(--lava); }
+ [data-sno-theme="volcano"] .splash-tag { color:var(--ember); letter-spacing:0.15em; }
+ [data-sno-theme="volcano"] .splash-hint { color:rgba(255,232,204,0.88); }
+ [data-sno-theme="volcano"] .splash-inner { text-shadow: 0 2px 18px rgba(0,0,0,0.85); }
diff --git a/internal/generator/templates/themes/volcano.tmpl b/internal/generator/templates/themes/volcano/theme.js
index 982eed8..41de88a 100644
--- a/internal/generator/templates/themes/volcano.tmpl
+++ b/internal/generator/templates/themes/volcano/theme.js
@@ -1,95 +1,4 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>snonux.foo ▲ VOLCANO</title>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
- <style>
- :root { --lava:#ff4400; --ember:#ff8c00; --hot:#ffcc00; --bg:#0d0802; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:'Segoe UI',system-ui,sans-serif; background:var(--bg);
- color:#ffe8cc; overflow:hidden; height:100vh; }
- #three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
- .overlay { position:relative; z-index:10; height:100vh; display:flex; flex-direction:column; }
- header { padding:16px 28px; background:rgba(13,8,2,0.82); backdrop-filter:blur(12px);
- border-bottom:1px solid rgba(255,68,0,0.3); display:flex; align-items:center; justify-content:space-between; }
- .logo { display:flex; align-items:center; gap:14px; }
- .logo-mark { font-size:2rem; font-weight:800; color:var(--ember); text-shadow:0 0 16px var(--lava); }
- .logo-title h1 { font-size:1.5rem; font-weight:700; color:#ffe8cc; }
- .logo-title .subtitle { font-size:0.75rem; color:rgba(255,232,204,0.5); margin-top:2px; }
- .logo-title .subtitle a { color:var(--ember); text-decoration:none; }
- .logo-title .subtitle a:hover { text-shadow:0 0 8px var(--lava); }
- .transmit-btn { border:1px solid var(--lava); color:var(--lava); padding:9px 20px;
- border-radius:4px; text-decoration:none; font-size:0.85rem; transition:all 0.2s; }
- .transmit-btn:hover { background:var(--lava); color:var(--bg); }
- a.header-feed-link { color:var(--ember); }
- a.header-feed-link:hover { color:var(--hot); text-shadow:0 0 8px var(--lava); }
- .nav-hints { background:rgba(13,8,2,0.7); border-bottom:1px solid rgba(255,68,0,0.15);
- color:rgba(255,232,204,0.4); padding:5px 28px; display:flex; gap:18px;
- font-size:0.68rem; flex-wrap:wrap; }
- .nav-hints kbd { background:rgba(255,68,0,0.12); border:1px solid rgba(255,68,0,0.35);
- color:var(--ember); border-radius:3px; padding:0 5px; margin:0 2px; }
- .content { flex:1; overflow-y:auto; padding:20px 28px;
- scrollbar-width:thin; scrollbar-color:var(--lava) var(--bg); }
- .page-nav { display:flex; justify-content:center; margin:14px 0; }
- .page-nav a { border:1px solid var(--ember); color:var(--ember); padding:8px 20px;
- border-radius:4px; text-decoration:none; font-size:0.82rem; }
- .page-nav a:hover { background:var(--lava); color:var(--bg); }
- .page-nav-footer { flex-shrink:0; padding:7px 28px; display:flex; justify-content:center;
- background:rgba(13,8,2,0.82); backdrop-filter:blur(12px);
- border-top:1px solid rgba(255,68,0,0.3); }
- .post { background:rgba(20,8,2,0.72); border:1px solid rgba(255,68,0,0.2); border-radius:8px;
- padding:20px; margin-bottom:14px; cursor:pointer;
- transition:all 0.25s; backdrop-filter:blur(4px); }
- .post:hover { border-color:var(--ember); box-shadow:0 0 20px rgba(255,68,0,0.25); transform:translateY(-2px); }
- .post-active { border-color:var(--hot) !important; background:rgba(30,8,2,0.9) !important;
- box-shadow:0 0 24px rgba(255,140,0,0.4),inset 3px 0 0 var(--hot) !important; }
- .post-header { display:flex; justify-content:space-between; margin-bottom:12px; font-size:0.88rem; }
- .post-time { color:var(--ember); font-family:monospace; font-size:0.8rem; }
- .post-text { line-height:1.65; font-size:0.95rem; }
- .post-text a { color:var(--ember); text-decoration:none; }
- .post-text a:hover { text-shadow:0 0 8px var(--lava); }
- .post-audio { width:100%; margin-top:10px; }
- .post-modal { display:none; position:fixed; inset:0; z-index:100;
- overflow-y:auto; padding:40px 20px; }
- .post-modal.active { display:block; }
- .modal-inner { max-width:760px; margin:0 auto; background:rgba(20,8,2,0.92);
- border:1px solid var(--lava); border-radius:10px; backdrop-filter:blur(16px);
- box-shadow:0 0 60px rgba(255,68,0,0.3); padding:40px; }
- .modal-close { float:right; background:none; border:none; color:var(--ember);
- font-size:0.9rem; cursor:pointer; letter-spacing:1px; }
- @media(max-width:640px) { .nav-hints{display:none;} header{padding:12px 18px;} .content{padding:14px 18px;} }
- .splash-overlay.splash-volcano {
- background: radial-gradient(ellipse 80% 60% at 50% 100%, rgba(255,68,0,0.35) 0%, transparent 50%), var(--bg);
- }
- .splash-volcano .splash-ember {
- width:min(200px,55vw); height:4px; margin:0 auto 1.3rem; border-radius:2px;
- background: linear-gradient(90deg, transparent, var(--lava), var(--hot), var(--ember), transparent);
- animation: splashEmberPulse 1.6s ease-in-out infinite alternate;
- box-shadow: 0 0 20px var(--lava), 0 6px 30px rgba(255,68,0,0.4);
- }
- @keyframes splashEmberPulse { from { opacity:0.6; transform: scaleX(0.9); } to { opacity:1; transform: scaleX(1); } }
- .splash-volcano .splash-title { font-size:clamp(1.45rem,4.5vw,2rem); color:#ffe8cc;
- text-shadow:0 0 20px var(--lava); }
- .splash-volcano .splash-tag { color:var(--ember); letter-spacing:0.15em; }
- .splash-volcano .splash-hint { color:rgba(255,232,204,0.88); }
- .splash-volcano .splash-inner { text-shadow: 0 2px 18px rgba(0,0,0,0.85); }
-{{template "navSharedCSSInner"}}
- </style>
-</head>
-<body>
- {{template "splashGate"}}
- <div id="splash-overlay" class="splash-overlay splash-volcano" role="dialog" aria-modal="true" aria-label="Open microblog" tabindex="-1">
- <canvas class="splash-gl-canvas" id="splash-gl-canvas" aria-hidden="true"></canvas>
- <div class="splash-inner">
- <div class="splash-ember" aria-hidden="true"></div>
- <div class="splash-title">snonux.foo</div>
- <div class="splash-tag">Volcano vent</div>
- <div class="splash-hint">Erupt into feed — click or Enter</div>
- </div>
- </div>
- <script>
+
(function(){
if(document.documentElement.classList.contains('sno-splash-skip'))return;
var cv=document.getElementById('splash-gl-canvas');
@@ -111,46 +20,8 @@
pos.needsUpdate=true;cone.rotation.y=t*0.25;ren.render(sc,ca);}
raf=requestAnimationFrame(loop);
})();
- </script>
- <canvas id="three-canvas"></canvas>
- <div class="overlay">
- <header>
- <div class="logo">
- <span class="logo-mark">SN</span>
- <div class="logo-title">
- <h1>snonux.foo</h1>
- <p class="subtitle">microblog &mdash; <a href="https://foo.zone">foo.zone</a> is the real blog</p>
- <p class="logo-host">Served by NetBSD on a Raspberry Pi 3</p>
- </div>
- </div>
- <div class="nav">
- <a href="atom.xml" class="header-feed-link" rel="alternate" title="Atom feed" type="application/atom+xml">Atom feed</a>
- <a href="https://foo.zone/about" class="transmit-btn">Transmit</a>
- </div>
- </header>
- {{template "navhints" .}}
- <div class="content" id="post-content">
- {{range $i, $post := .Posts}}
- <div class="post" id="post-{{$post.ID}}" data-index="{{$i}}">
- <div class="post-header">
- <div><strong>@snonux</strong></div>
- <div class="post-time">{{$post.FormattedTime}}</div>
- </div>
- <div class="post-text">{{$post.ContentHTML}}</div>
- </div>
- {{end}}
- </div>
- {{if or .PrevPage .NextPage}}
- <footer class="page-nav-footer" aria-label="Pagination">
- <div class="page-nav page-nav-dual">
- {{if .PrevPage}}<a href="{{.PrevPage}}">&larr; Newer</a>{{end}}
- {{if .NextPage}}<a href="{{.NextPage}}">Older &rarr;</a>{{end}}
- </div>
- </footer>
- {{end}}
- </div>
- {{template "navmodal" .}}
- <script>
+
+
// Volcano WebGL: glowing lava floor, molten rock boulders, smoke plumes,
// underground furnace glow sphere, and 3000 rising ember particles.
(function() {
@@ -394,7 +265,3 @@
if (ov) { ov.classList.add('sno-fx-zoom'); setTimeout(function() { ov.classList.remove('sno-fx-zoom'); }, 330); }
};
})();
- </script>
- {{template "navscript" .}}
-</body>
-</html>