diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-25 22:13:32 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-25 22:13:32 +0300 |
| commit | a5cb9f7ad4b2bcb7ac367854147f661ab83c5ec1 (patch) | |
| tree | 96db490ed72dec49c4bc2aba8539fd57f3246f47 /internal/generator/templates/themes | |
| parent | cc9c69ce748416ad1d9b28592ec463434023d63c (diff) | |
can swap themes dynamicallyv0.12.0
Diffstat (limited to 'internal/generator/templates/themes')
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 — <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}}">← Newer</a>{{end}} - {{if .NextPage}}<a href="{{.NextPage}}">Older →</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 — <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}}">← Newer</a>{{end}} - {{if .NextPage}}<a href="{{.NextPage}}">Older →</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 — <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}}">← NEWER</a>{{end}} - {{if .NextPage}}<a href="{{.NextPage}}">OLDER →</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 — <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}}">← Newer</a>{{end}} - {{if .NextPage}}<a href="{{.NextPage}}">Older →</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 — <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}}">← Newer</a>{{end}} - {{if .NextPage}}<a href="{{.NextPage}}">Older →</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></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:\></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">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}}"><-- NEWER</a>{{end}} - {{if .NextPage}}<a href="{{.NextPage}}">OLDER --></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}}"><-- NEWER</a>{{end}} - {{if .NextPage}}<a href="{{.NextPage}}">OLDER --></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 — 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 — <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}}">← NEWER TRANSMISSIONS</a>{{end}} - {{if .NextPage}}<a href="{{.NextPage}}">OLDER TRANSMISSIONS →</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 — <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}}">← Newer</a>{{end}} - {{if .NextPage}}<a href="{{.NextPage}}">Older →</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 — <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}}">← Newer</a>{{end}} - {{if .NextPage}}<a href="{{.NextPage}}">Older →</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 — <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}}">← Newer</a>{{end}} - {{if .NextPage}}<a href="{{.NextPage}}">Older →</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}}"><-- NEWER</a>{{end}} - {{if .NextPage}}<a href="{{.NextPage}}">OLDER --></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 — <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}}">← NEWER</a>{{end}} - {{if .NextPage}}<a href="{{.NextPage}}">OLDER →</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}}"><-- NEWER</a>{{end}} - {{if .NextPage}}<a href="{{.NextPage}}">OLDER --></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 — <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}}">← Newer</a>{{end}} - {{if .NextPage}}<a href="{{.NextPage}}">Older →</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 — <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}}">← NEWER</a>{{end}} - {{if .NextPage}}<a href="{{.NextPage}}">OLDER →</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">> ./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">> 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}}"><-- NEWER</a>{{end}} - {{if .NextPage}}<a href="{{.NextPage}}">OLDER --></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 — <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}}">← Newer</a>{{end}} - {{if .NextPage}}<a href="{{.NextPage}}">Older →</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 — <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}}">← Newer</a>{{end}} - {{if .NextPage}}<a href="{{.NextPage}}">Older →</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> |
