SIMON Master Console Rebuild
True command center for SIMON + WEB360
This rebuild gives you a cleaner production spine: interactive tree, live viewer, guarded editor, dynamic graphics, architecture map, and note storage. It is designed to sit beside your existing
console.php
and become the stronger replacement path.
Dashboard
Tree
Viewer
System Map
File Registry
Notes
Files
15,334
Folders
408
Scanned Size
3.84 GB
PHP Files
886
Editable Text Files
12,595
File viewer
guarded to /htdocs
/infinity/signup.php
<?php declare(strict_types=1); /** * /infinity/signup.php — Surreal Space/Heaven Signup (FULL FILE) * - Back button to /infinity/ * - Email signup (basic): Display Name + Email + Terms * - Optional password section (hidden by default; enable later) * - “Continue with Apple / Google” (UI-only; wire handlers later) * * REQUIREMENTS: * - Uses /infinity/_inc/app.php * - Fully self-contained (no page_header()/page_footer()). */ require __DIR__ . '/_inc/app.php'; $brand = $INFINITY['brand']['name'] ?? 'Infinity'; $tag = $INFINITY['brand']['tagline'] ?? 'The Marketplace • The Sanctuary • The Living Archive'; function csrf_token_demo(): string { // Replace with real CSRF in your auth system. return bin2hex(random_bytes(16)); } $csrf = csrf_token_demo(); $bgPoster = '/infinity/media/sun.jpg'; $bgVideo = '/infinity/media/sun.mp4'; ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" /> <title><?= h($brand) ?> — Sign up</title> <meta name="theme-color" content="#070b16" /> <meta name="description" content="Create your Infinity account — a surreal sanctuary for memory, legacy, and light." /> <style> :root{ --bg0:#050812; --bg1:#070b16; --text:#eaf2ff; --muted:#a9b6d9; --glass: rgba(255,255,255,.07); --glass2: rgba(255,255,255,.035); --edge: rgba(255,255,255,.16); --cyan:#6ae0ff; --indigo:#7b62ff; --pink:#ff4fd6; --sun:#ffb86b; --radius: 22px; --shadow: 0 18px 60px rgba(0,0,0,.55); } *{ box-sizing:border-box; } html,body{ height:100%; width:100%; } body{ margin:0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial; color: var(--text); background: radial-gradient(1200px 800px at 50% 30%, rgba(123,98,255,.18), transparent 60%), radial-gradient(1000px 700px at 70% 60%, rgba(106,224,255,.12), transparent 58%), linear-gradient(180deg, var(--bg0), var(--bg1)); overflow:hidden; } /* Background layer */ .bg{ position:fixed; inset:0; z-index:0; pointer-events:none; overflow:hidden; } .bg video, .bg .poster{ position:absolute; inset:-2%; width:104%; height:104%; object-fit:cover; filter: saturate(1.15) contrast(1.07) brightness(.86); transform: scale(1.02); } .bg .poster{ display:none; background:center/cover no-repeat; } .bg:before{ content:""; position:absolute; inset:0; background: radial-gradient(900px 650px at 50% 22%, rgba(255,255,255,.08), transparent 60%), radial-gradient(900px 700px at 35% 55%, rgba(106,224,255,.10), transparent 55%), radial-gradient(900px 700px at 75% 65%, rgba(255,79,214,.08), transparent 60%), linear-gradient(180deg, rgba(5,8,18,.42), rgba(7,11,22,.84)); } .grain{ position:absolute; inset:0; background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.22'/%3E%3C/svg%3E"); opacity:.08; mix-blend-mode: overlay; } /* One canvas for ALL stars (prevents “two square images”) */ canvas#fx{ position:fixed; inset:0; z-index:1; pointer-events:none; } /* Layout */ .wrap{ position:relative; z-index:2; height:100%; display:grid; place-items:center; padding: clamp(18px, 3vw, 38px); } .shell{ width: min(980px, 92vw); display:grid; grid-template-columns: 1.08fr .92fr; gap: 18px; align-items: stretch; } .card{ border-radius: calc(var(--radius) + 10px); background: linear-gradient(180deg, rgba(255,255,255,.08), rgba(255,255,255,.03)); border: 1px solid rgba(255,255,255,.14); box-shadow: var(--shadow); backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px); overflow:hidden; min-height: min(640px, 78vh); } .left{ position:relative; padding: 22px; } .right{ position:relative; padding: 22px; display:grid; grid-template-rows: auto 1fr auto; gap: 14px; } /* Top row */ .toprow{ display:flex; align-items:center; justify-content:space-between; gap: 12px; margin-bottom: 16px; } .back{ display:inline-flex; align-items:center; gap: 10px; padding: 10px 12px; border-radius: 999px; border: 1px solid rgba(255,255,255,.14); background: rgba(0,0,0,.22); color: rgba(234,242,255,.92); text-decoration:none; letter-spacing:.08em; text-transform: uppercase; font-size: 12px; transition: transform .16s ease, border-color .16s ease, background .16s ease; user-select:none; } .back:hover{ transform: translateY(-1px); border-color: rgba(106,224,255,.22); background: rgba(0,0,0,.30); } .back i{ font-style: normal; opacity:.85; } .sig{ display:flex; flex-direction:column; align-items:flex-end; gap: 4px; min-width: 0; } .sig b{ letter-spacing:.18em; text-transform: uppercase; font-size: 12px; color: rgba(234,242,255,.84); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width: 52ch; } .sig small{ color: rgba(169,182,217,.78); letter-spacing:.08em; text-transform: uppercase; font-size: 11px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width: 52ch; } /* Title */ .title{ margin-top: 12px; margin-bottom: 8px; font-size: clamp(24px, 3.1vw, 40px); letter-spacing: -.02em; font-weight: 880; color: rgba(234,242,255,.94); text-shadow: 0 0 18px rgba(255,255,255,.08), 0 0 48px rgba(106,224,255,.16), 0 0 90px rgba(123,98,255,.14); } .subtitle{ margin:0 0 18px 0; color: rgba(169,182,217,.90); line-height: 1.55; font-size: 14px; max-width: 62ch; } .divider{ height:1px; background: linear-gradient(90deg, transparent, rgba(255,255,255,.20), rgba(106,224,255,.22), transparent); opacity:.9; margin: 16px 0; } /* Form */ form{ margin:0; } .field{ display:grid; gap: 8px; margin-top: 12px; } label{ font-size: 12px; letter-spacing:.12em; text-transform: uppercase; color: rgba(169,182,217,.82); } input{ width:100%; padding: 12px 12px; border-radius: 16px; border: 1px solid rgba(255,255,255,.14); background: rgba(0,0,0,.22); color: rgba(234,242,255,.92); outline:none; transition: border-color .16s ease, background .16s ease; } input:focus{ border-color: rgba(106,224,255,.32); background: rgba(0,0,0,.30); } .row{ display:flex; justify-content:space-between; align-items:center; gap: 12px; margin-top: 12px; flex-wrap: wrap; } .check{ display:flex; align-items:flex-start; gap: 10px; color: rgba(169,182,217,.92); font-size: 13px; user-select:none; max-width: 72ch; } .check input{ width:16px; height:16px; margin-top:2px; accent-color: rgba(106,224,255,.95); } .link{ color: rgba(234,242,255,.92); text-decoration:none; border-bottom: 1px dashed rgba(255,255,255,.18); padding-bottom: 2px; font-size: 13px; } .link:hover{ border-bottom-color: rgba(106,224,255,.30); } .btn{ width:100%; margin-top: 14px; padding: 12px 14px; border-radius: 16px; border: 1px solid rgba(255,255,255,.14); background: linear-gradient(135deg, rgba(106,224,255,.12), rgba(255,255,255,.02)); color: rgba(234,242,255,.92); font-weight: 900; letter-spacing:.12em; text-transform: uppercase; cursor:pointer; transition: transform .16s ease, border-color .16s ease, background .16s ease; } .btn:hover{ transform: translateY(-1px); border-color: rgba(106,224,255,.26); background: linear-gradient(135deg, rgba(123,98,255,.12), rgba(255,255,255,.02)); } .btn:disabled{ opacity:.55; cursor:not-allowed; transform:none; } .or{ display:flex; align-items:center; gap: 12px; margin: 16px 0 8px; color: rgba(169,182,217,.80); font-size: 12px; letter-spacing:.14em; text-transform: uppercase; } .or:before, .or:after{ content:""; flex: 1; height:1px; background: linear-gradient(90deg, transparent, rgba(255,255,255,.16), transparent); } .oauth{ display:grid; gap: 10px; margin-top: 10px; } .oauth a{ display:flex; align-items:center; justify-content:space-between; gap: 12px; padding: 12px 12px; border-radius: 16px; border: 1px solid rgba(255,255,255,.14); background: rgba(0,0,0,.18); color: rgba(234,242,255,.92); text-decoration:none; transition: transform .16s ease, border-color .16s ease, background .16s ease; } .oauth a:hover{ transform: translateY(-1px); border-color: rgba(106,224,255,.24); background: rgba(0,0,0,.28); } .oauth .lefty{ display:flex; align-items:center; gap: 10px; min-width: 0; } .oauth .badge{ width: 36px; height: 36px; border-radius: 14px; border: 1px solid rgba(255,255,255,.14); background: linear-gradient(135deg, rgba(255,255,255,.06), rgba(255,255,255,.02)); display:grid; place-items:center; box-shadow: 0 14px 40px rgba(0,0,0,.35); flex: 0 0 auto; } .oauth .txt{ display:flex; flex-direction:column; gap: 2px; min-width: 0; } .oauth .txt b{ font-size: 13px; letter-spacing:.08em; text-transform: uppercase; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .oauth .txt small{ font-size: 12px; color: rgba(169,182,217,.82); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } /* Halo: divine portal glow (NOT a star tile) */ .halo{ height: 200px; border-radius: calc(var(--radius) + 14px); border: 1px solid rgba(255,255,255,.12); background: radial-gradient(220px 150px at 50% 35%, rgba(255,255,255,.10), transparent 60%), radial-gradient(320px 220px at 50% 60%, rgba(106,224,255,.12), transparent 65%), radial-gradient(380px 280px at 50% 72%, rgba(123,98,255,.10), transparent 70%), linear-gradient(180deg, rgba(0,0,0,.12), rgba(0,0,0,.04)); box-shadow: 0 18px 70px rgba(0,0,0,.55); overflow:hidden; position:relative; } .halo:before{ content:""; position:absolute; inset:-40%; background: radial-gradient(circle at 30% 35%, rgba(106,224,255,.30), transparent 55%), radial-gradient(circle at 70% 65%, rgba(123,98,255,.24), transparent 60%), radial-gradient(circle at 50% 50%, rgba(255,255,255,.12), transparent 58%); transform: rotate(18deg); animation: haloPulse 2.6s ease-in-out infinite; mix-blend-mode: screen; } .halo:after{ content:""; position:absolute; inset:0; background: linear-gradient(120deg, transparent 0%, rgba(255,255,255,.22) 30%, transparent 60%); transform: translateX(-140%); opacity:.28; animation: haloSheen 3.8s ease-in-out infinite; } @keyframes haloPulse{ 0%,100%{ opacity:.62; transform:scale(1) rotate(18deg); } 50%{ opacity:1; transform:scale(1.08) rotate(18deg); } } @keyframes haloSheen{ 0%{ transform:translateX(-140%); } 45%{ transform:translateX(140%); } 100%{ transform:translateX(140%); } } .right h3{ margin:0; font-size: 12px; letter-spacing:.14em; text-transform: uppercase; color: rgba(234,242,255,.84); } .right p{ margin: 6px 0 0 0; color: rgba(169,182,217,.86); font-size: 13px; line-height: 1.45; } .pillrow{ display:flex; flex-wrap:wrap; gap: 10px; margin-top: 10px; } .pill{ border: 1px solid rgba(255,255,255,.12); background: rgba(0,0,0,.16); color: rgba(234,242,255,.88); padding: 8px 10px; border-radius: 999px; font-size: 12px; letter-spacing:.06em; text-transform: uppercase; } .fineprint{ border-top: 1px solid rgba(255,255,255,.10); padding-top: 12px; color: rgba(169,182,217,.84); font-size: 12px; line-height: 1.45; } .mini{ display:flex; gap: 10px; align-items:center; justify-content:space-between; border: 1px solid rgba(255,255,255,.12); background: rgba(0,0,0,.14); border-radius: 16px; padding: 10px 12px; margin-top: 10px; } .mini b{ font-size: 12px; letter-spacing:.12em; text-transform: uppercase; color: rgba(234,242,255,.86); } .mini small{ font-size: 12px; color: rgba(169,182,217,.82); } .tinybtn{ border: 1px solid rgba(255,255,255,.14); background: rgba(255,255,255,.06); color: rgba(234,242,255,.90); padding: 8px 10px; border-radius: 999px; text-decoration:none; letter-spacing:.10em; text-transform: uppercase; font-size: 11px; transition: transform .16s ease, border-color .16s ease, background .16s ease; cursor:pointer; user-select:none; } .tinybtn:hover{ transform: translateY(-1px); border-color: rgba(106,224,255,.24); background: rgba(255,255,255,.08); } /* Optional password section (hidden by default) */ .pw{ display:none; margin-top: 12px; padding-top: 12px; border-top: 1px solid rgba(255,255,255,.10); } .pw.on{ display:block; } /* Status */ .status{ margin-top: 12px; border-radius: 16px; border: 1px solid rgba(255,255,255,.12); background: rgba(0,0,0,.18); padding: 10px 12px; color: rgba(169,182,217,.90); font-size: 13px; line-height: 1.45; display:none; } .status.on{ display:block; } @media (max-width: 920px){ .shell{ grid-template-columns: 1fr; } .card{ min-height: auto; } } @media (max-width: 520px){ .left, .right{ padding: 18px; } .sig{ display:none; } } @media (prefers-reduced-motion: reduce){ .halo:before, .halo:after{ animation:none !important; } } </style> </head> <body> <div class="bg" aria-hidden="true"> <video id="bgvid" autoplay muted loop playsinline preload="metadata" poster="<?= h($bgPoster) ?>"> <source src="<?= h($bgVideo) ?>" type="video/mp4" /> </video> <div class="poster" id="poster" style="background-image:url('<?= h($bgPoster) ?>')"></div> <div class="grain"></div> </div> <canvas id="fx"></canvas> <div class="wrap"> <div class="shell" role="main" aria-label="Infinity signup"> <section class="card left" aria-label="Signup form"> <div class="toprow"> <a class="back" href="/infinity/" aria-label="Back to Infinity"><i>←</i> Back</a> <div class="sig" aria-label="Brand"> <b><?= h($brand) ?></b> <small><?= h($tag) ?></small> </div> </div> <div class="title">Create your account</div> <p class="subtitle"> Basic is best: a display name, an email, and consent. Apple/Google can provide identity automatically. </p> <div class="divider" aria-hidden="true"></div> <form id="signupForm" method="post" action="/infinity/signup.php" autocomplete="on" novalidate> <input type="hidden" name="csrf" value="<?= h($csrf) ?>" /> <div class="field"> <label for="display_name">Display name</label> <input id="display_name" name="display_name" type="text" maxlength="60" placeholder="How you want to be seen" required /> </div> <div class="field"> <label for="email">Email</label> <input id="email" name="email" type="email" maxlength="190" placeholder="you@domain.com" required /> </div> <div class="pw" id="pwBox" aria-label="Optional password section"> <div class="field"> <label for="password">Password (optional)</label> <input id="password" name="password" type="password" minlength="8" placeholder="Enable later if you want passwords" /> </div> </div> <div class="row"> <label class="check" for="terms"> <input id="terms" name="terms" type="checkbox" required /> <span> I accept the <a class="link" href="/infinity/terms.php">Terms</a> and <a class="link" href="/infinity/privacy.php">Privacy</a>. </span> </label> <button class="tinybtn" type="button" id="togglePw" aria-controls="pwBox" aria-expanded="false"> Add password </button> </div> <button class="btn" type="submit" id="submitBtn">Create account</button> <div class="status" id="status" role="status" aria-live="polite"></div> <div class="or">Or continue with</div> <div class="oauth" aria-label="OAuth options"> <a href="#" id="oauthApple" aria-label="Continue with Apple (UI only)"> <span class="lefty"> <span class="badge" aria-hidden="true"> <!-- Apple icon --> <svg width="18" height="18" viewBox="0 0 24 24" fill="none"> <path d="M16.7 13.7c0 3 2.6 4 2.6 4s-2 5.6-4.7 5.6c-1.2 0-1.7-.8-3-.8s-1.9.8-3 .8C3 23.3 1 17.9 1 14.4 1 11 3 8.7 5.4 8.7c1.2 0 2.2.8 3 .8s2-.9 3.4-.9c.5 0 2.2.1 3.2 1.6-2.7 1.5-2.3 3.9-2.3 3.9Z" fill="rgba(234,242,255,.92)"/> <path d="M15.2 3.2c-.8 1-2.1 1.7-3.3 1.6-.1-1.3.5-2.5 1.2-3.4.8-1 2.2-1.7 3.3-1.7.1 1.3-.5 2.6-1.2 3.5Z" fill="rgba(234,242,255,.70)"/> </svg> </span> <span class="txt"> <b>Apple</b> <small>Uses Apple ID (email may be masked)</small> </span> </span> <span aria-hidden="true">→</span> </a> <a href="#" id="oauthGoogle" aria-label="Continue with Google (UI only)"> <span class="lefty"> <span class="badge" aria-hidden="true"> <!-- Google-ish ring --> <svg width="18" height="18" viewBox="0 0 24 24" fill="none"> <path d="M21.6 12.3c0-.7-.1-1.3-.2-1.9H12v3.6h5.4c-.2 1-.9 2-1.9 2.6v2.4h3.1c1.8-1.7 3-4.2 3-6.7Z" fill="rgba(234,242,255,.92)"/> <path d="M12 22c2.6 0 4.8-.9 6.4-2.4l-3.1-2.4c-.9.6-2 .9-3.3.9-2.5 0-4.6-1.7-5.4-4H3.4v2.5C5 19.7 8.2 22 12 22Z" fill="rgba(234,242,255,.70)"/> <path d="M6.6 14.1c-.2-.6-.4-1.3-.4-2s.1-1.4.4-2V7.6H3.4C2.8 8.8 2.4 10.4 2.4 12s.4 3.2 1 4.4l3.2-2.3Z" fill="rgba(234,242,255,.62)"/> <path d="M12 5.9c1.4 0 2.6.5 3.6 1.4l2.7-2.7C16.8 3.1 14.6 2 12 2 8.2 2 5 4.3 3.4 7.6l3.2 2.5c.8-2.3 2.9-4.2 5.4-4.2Z" fill="rgba(234,242,255,.78)"/> </svg> </span> <span class="txt"> <b>Google</b> <small>Uses Google account (verified email)</small> </span> </span> <span aria-hidden="true">→</span> </a> </div> <div style="margin-top:14px; color:rgba(169,182,217,.85); font-size:13px;"> Already have an account? <a class="link" href="/infinity/login.php">Log in</a> </div> </form> </section> <aside class="card right" aria-label="Heaven panel"> <div class="halo" aria-hidden="true"></div> <div> <h3>What’s in the halo?</h3> <p> A soft “gateway” glow—no boxes, no tiles. The stars live behind everything on a single full-screen canvas. </p> <div class="pillrow" aria-label="Features"> <span class="pill">Consent-first</span> <span class="pill">Living archive</span> <span class="pill">Legacy</span> <span class="pill">Sanctuary</span> </div> <div class="mini" aria-label="Quick note"> <div> <b>Apple/Google</b><br/> <small>Email may be masked; name may be partial.</small> </div> <button class="tinybtn" type="button" id="whyBtn" aria-expanded="false" aria-controls="whyBox">Why?</button> </div> <div class="status" id="whyBox"> Apple/Google usually provide a verified identity and email (sometimes masked). We still ask once for a display name and acceptance of terms. </div> </div> <div class="fineprint"> Infinity preserves memory and intent with care. It does not claim consciousness transfer or resurrection. </div> </aside> </div> </div> <script> (() => { const bgvid = document.getElementById('bgvid'); const poster = document.getElementById('poster'); if (bgvid) { const showPoster = () => { if (poster) poster.style.display = 'block'; }; bgvid.addEventListener('error', showPoster); bgvid.addEventListener('stalled', showPoster); bgvid.addEventListener('suspend', showPoster); } // UI-only OAuth buttons const status = document.getElementById('status'); const setStatus = (msg) => { if (!status) return; status.textContent = msg; status.classList.add('on'); }; document.getElementById('oauthApple')?.addEventListener('click', (e) => { e.preventDefault(); setStatus('Apple sign-in is UI-only here. Wire it to your auth handler when ready.'); }); document.getElementById('oauthGoogle')?.addEventListener('click', (e) => { e.preventDefault(); setStatus('Google sign-in is UI-only here. Wire it to your auth handler when ready.'); }); // Optional password toggle const pwBox = document.getElementById('pwBox'); const togglePw = document.getElementById('togglePw'); togglePw?.addEventListener('click', () => { const on = !pwBox.classList.contains('on'); pwBox.classList.toggle('on', on); togglePw.setAttribute('aria-expanded', on ? 'true' : 'false'); togglePw.textContent = on ? 'No password' : 'Add password'; if (on) document.getElementById('password')?.focus(); }); // “Why?” toggle const whyBtn = document.getElementById('whyBtn'); const whyBox = document.getElementById('whyBox'); whyBtn?.addEventListener('click', () => { const on = !whyBox.classList.contains('on'); whyBox.classList.toggle('on', on); whyBtn.setAttribute('aria-expanded', on ? 'true' : 'false'); }); // Basic client-side validation message (does not replace server validation) const form = document.getElementById('signupForm'); const submitBtn = document.getElementById('submitBtn'); form?.addEventListener('submit', (e) => { const dn = document.getElementById('display_name'); const em = document.getElementById('email'); const terms = document.getElementById('terms'); const ok = (dn?.value || '').trim().length >= 2 && (em?.value || '').includes('@') && !!terms?.checked; if (!ok) { e.preventDefault(); setStatus('Please add a display name, a valid email, and accept Terms/Privacy.'); try { submitBtn?.classList.remove('shake'); void submitBtn?.offsetWidth; } catch(_) {} } }); // Fullscreen starfield canvas (single layer; no squares/tiles) const canvas = document.getElementById('fx'); const ctx = canvas.getContext('2d', { alpha:true }); const DPR = Math.min(2, window.devicePixelRatio || 1); const state = { w: innerWidth, h: innerHeight, stars: [], mx: innerWidth/2, my: innerHeight/2, t: 0, running: true }; function resize(){ state.w = innerWidth; state.h = innerHeight; canvas.width = Math.floor(state.w * DPR); canvas.height = Math.floor(state.h * DPR); canvas.style.width = state.w + 'px'; canvas.style.height = state.h + 'px'; ctx.setTransform(DPR,0,0,DPR,0,0); seed(); } addEventListener('resize', resize, {passive:true}); addEventListener('pointermove', (e)=>{ state.mx=e.clientX; state.my=e.clientY; }, {passive:true}); const rnd=(a,b)=>a+Math.random()*(b-a); function seed(){ const target = Math.floor(Math.min(220, Math.max(110, state.w/10))); state.stars.length = 0; for (let i=0;i<target;i++){ state.stars.push({ x: Math.random()*state.w, y: Math.random()*state.h, r: rnd(.6, 2.0), a: rnd(.07, .55), vx: rnd(-.06,.06), vy: rnd(-.04,.04), tw: rnd(.002,.012), ph: Math.random()*Math.PI*2 }); } } // Reduce draw cost when tab hidden document.addEventListener('visibilitychange', () => { state.running = !document.hidden; if (state.running) requestAnimationFrame(draw); }); function draw(){ if (!state.running) return; const w = state.w, h = state.h; ctx.clearRect(0,0,w,h); state.t += 1; // subtle energy fog const gx = state.mx/w, gy = state.my/h; const fog = ctx.createRadialGradient( w*(0.25+gx*0.15), h*(0.25+gy*0.15), 40, w*(0.5+gx*0.10), h*(0.45+gy*0.10), 420 ); fog.addColorStop(0, `rgba(106,224,255,0.06)`); fog.addColorStop(0.45, `rgba(123,98,255,0.04)`); fog.addColorStop(1, `rgba(255,79,214,0)`); ctx.fillStyle = fog; ctx.fillRect(0,0,w,h); // stars const dx = (state.mx - w/2) * 0.00002; const dy = (state.my - h/2) * 0.00002; for (const s of state.stars){ s.ph += s.tw; s.x += s.vx + dx; s.y += s.vy + dy; if (s.x < -10) s.x = w+10; if (s.x > w+10) s.x = -10; if (s.y < -10) s.y = h+10; if (s.y > h+10) s.y = -10; const tw = (Math.sin(s.ph)*0.5+0.5); const a = s.a*(0.65 + 0.7*tw); ctx.beginPath(); ctx.arc(s.x, s.y, s.r*(0.85+tw*0.35), 0, Math.PI*2); ctx.fillStyle = `rgba(234,242,255,${a})`; ctx.fill(); } requestAnimationFrame(draw); } resize(); requestAnimationFrame(draw); })(); </script> </body> </html>
Save file
Quick jump
open a path
Open