7581d519f206
fix lock rune canvas — cyclic string scroll matching onboarding mechanic, 30% density
- time
- 2026-04-28 05:32 UTC · 6 months ago
- machine
- koh2
- parent
- 26c99f63f576
↓ peach-browser-7581d519.tar.gzatlas at this save
files changed1 file
src/popup/components/LockRuneCanvas.tsx+45 −53src/popup/components/LockRuneCanvas.tsx+45 −53
@@ -1,13 +1,26 @@11 import { onCleanup, onMount } from "solid-js";22 33 const FONT_SIZE = 15;4-const LINE_H = FONT_SIZE * 2.8;5-const CHAR_W = FONT_SIZE * 1.7;6-const DENSITY = 0.30;4+const LINE_HEIGHT = FONT_SIZE * 2.3;5+const LETTER_WIDTH = FONT_SIZE * 1.35;6+const LETTER_SPACING = FONT_SIZE * 0.18;77 const SPEEDS = [5, 7, 4];88 const GLOW_COUNT = 2;99 const FALLBACK_ACCENT = "#F49C70";1010 11+// 30% of string positions are runes; the rest are null (skipped in draw).12+// Same cyclic-scroll mechanic as RuneCanvas — no isolated-character weirdness.13+const RUNE_STRING_LEN = 80;14+const DENSITY = 0.30;15+const RUNE_STRING: (string | null)[] = Array.from(16+ { length: RUNE_STRING_LEN },17+ (_, i) => {18+ const hash = (i * 2654435761) >>> 0;19+ if ((hash % 100) >= DENSITY * 100) return null;20+ return String.fromCodePoint(0xe000 + (hash % 256));21+ },22+);23+1124 function readAccentHex(): string {1225 const raw = getComputedStyle(document.documentElement)1326 .getPropertyValue("--peach-accent")@@ -35,9 +48,7 @@3548 3649 setAccent(readAccentHex());3750 38- const accentObserver = new MutationObserver(() => {39- setAccent(readAccentHex());40- });51+ const accentObserver = new MutationObserver(() => setAccent(readAccentHex()));4152 accentObserver.observe(document.documentElement, {4253 attributes: true,4354 attributeFilter: ["style"],@@ -48,23 +59,11 @@4859 canvas.width = W;4960 canvas.height = H;5061 51- const NUM_ROWS = Math.ceil(H / LINE_H) + 1;52- const CHARS_PER_ROW = Math.ceil(W / CHAR_W) + 2;62+ const CELL_W = LETTER_WIDTH + LETTER_SPACING;63+ const SYMBOLS_PER_ROW = Math.ceil(W / CELL_W) + 4;64+ const NUM_ROWS = Math.floor(H / LINE_HEIGHT) + 1;65+ const TOTAL_STRING_W = RUNE_STRING_LEN * CELL_W || 1;5366 54- // Deterministic sparse grid — 30% of positions occupied55- const runeString: (string | null)[] = Array.from(56- { length: NUM_ROWS * CHARS_PER_ROW },57- (_, i) => {58- const hash = (i * 2654435761) >>> 0;59- if ((hash % 100) >= DENSITY * 100) return null;60- return String.fromCodePoint(0xe000 + (hash % 256));61- },62- );63-64- const offsets = Array.from({ length: NUM_ROWS }, () =>65- Math.random() * CHARS_PER_ROW * CHAR_W,66- );67-6867 interface Glow {6968 row: number;7069 col: number;@@ -74,24 +73,21 @@7473 age: number;7574 }7675 77- function spawnGlow(): Glow {78- let row = 0, col = 0, attempts = 0;79- do {80- row = Math.floor(Math.random() * NUM_ROWS);81- col = Math.floor(Math.random() * CHARS_PER_ROW);82- attempts++;83- } while (!runeString[row * CHARS_PER_ROW + col] && attempts < 20);84- return {85- row, col,86- phase: Math.random(),87- speed: 0.3 + Math.random() * 0.3,88- lifetime: 3000 + Math.random() * 4000,89- age: 0,90- };91- }76+ const spawn = (): Glow => ({77+ row: Math.floor(Math.random() * NUM_ROWS),78+ col: Math.floor(Math.random() * SYMBOLS_PER_ROW),79+ phase: Math.random(),80+ speed: 0.3 + Math.random() * 0.3,81+ lifetime: 3000 + Math.random() * 4000,82+ age: 0,83+ });9284 93- const glows: Glow[] = Array.from({ length: GLOW_COUNT }, spawnGlow);85+ const glows: Glow[] = Array.from({ length: GLOW_COUNT }, spawn);9486 87+ const offsets: number[] = Array.from({ length: NUM_ROWS }, () =>88+ Math.random() * TOTAL_STRING_W,89+ );90+9591 let lastTime = performance.now();9692 let raf = 0;9793 let running = false;@@ -113,23 +109,24 @@113109 const env = Math.sin((g.age / g.lifetime) * Math.PI);114110 const pulse = Math.sin(g.phase * Math.PI * 2) * 0.5 + 0.5;115111 glowMap.set(`${g.row},${g.col}`, Math.max(0, env) * pulse * 0.60);116- if (g.age >= g.lifetime) Object.assign(g, spawnGlow());112+ if (g.age >= g.lifetime) Object.assign(g, spawn());117113 }118114 119115 const [ar, ag, ab] = accentRgb;120116 121117 for (let row = 0; row < NUM_ROWS; row++) {122118 const speed = SPEEDS[row % SPEEDS.length];123- const totalW = CHARS_PER_ROW * CHAR_W;124- offsets[row] = (offsets[row] + (speed * dt) / 1000) % totalW;125- const y = row * LINE_H + FONT_SIZE + 12;119+ offsets[row] = (offsets[row] + (speed * dt) / 1000) % TOTAL_STRING_W;120+ const y = row * LINE_HEIGHT + FONT_SIZE + 12;126121 127- for (let col = 0; col < CHARS_PER_ROW; col++) {128- const char = runeString[row * CHARS_PER_ROW + col];129- if (!char) continue;122+ for (let col = 0; col < SYMBOLS_PER_ROW; col++) {123+ const x = col * CELL_W - (offsets[row] % CELL_W);124+ if (x < -CELL_W || x > W + CELL_W) continue;130125 131- const x = col * CHAR_W - offsets[row] + CHAR_W;132- if (x < -CHAR_W || x > W + CHAR_W) continue;126+ const baseIdx = Math.floor(offsets[row] / CELL_W);127+ const strIdx = (((baseIdx + col) % RUNE_STRING_LEN) + RUNE_STRING_LEN) % RUNE_STRING_LEN;128+ const char = RUNE_STRING[strIdx];129+ if (!char) continue; // sparse position — blank133130 134131 const glowA = glowMap.get(`${row},${col}`) ?? 0;135132 @@ -178,10 +175,5 @@178175 });179176 });180177 181- return (182- <canvas183- ref={canvas}184- class="peach-lock-rune-canvas"185- />186- );178+ return <canvas ref={canvas} class="peach-lock-rune-canvas" />;187179 }