- ChatWidget: remove illegal seeds, real localStorage per-handle chat, honest bot replies about market/forum/funds - ForumBoard: wire to real forumState (loadForum/addThread/vote), kill fake stats and illegal seed posts - Home page: privacy features list reflects reality, footer links real - Links: kill all alert() calls, replace fake onions with real clearnet privacy resources + internal route grid - Support: per-coin copied state, env-driven addresses, real BTC addr - Inner circle: wire to AccountContext, tier system from LUX balance, remove hardcoded admin/shadow credentials and fake trading signals - Drop box: real sealed-note localStorage system, honest about no anonymous upload capability, real file picker with receipt - Messages: fully functional per-handle localStorage chat, AI-style contextual bot replies, clear history, honest about local storage - Wallets: pivot from fake PayPal accounts to Digital Access Passes, wire Buy Now to cart via ShopProduct interface - Testimonials: wire submit form to localStorage, interactive star rating 1-10, display submitted reviews above the fold - Raffle: use real merchant BTC address, real per-handle entry storage, honest LUX-only prize disclaimer, fix 0x address - Drops/Lotto: real number picker 1-49 with Quick Pick, ticket submission, match display against drawn numbers, demo disclaimer - Sanctuary: real 4-4-6-2 breathing timer, meditation passage with timer, candle-lighting with localStorage notes - Game: full playable Void Pong with canvas physics, CPU AI, scoring, rally counter, localStorage high score - Security analysis: honest architecture breakdown with real grades, layer-by-layer analysis, practical OPSEC guide, fiction banner - Trust: compute real scores from actual localStorage data (LUX, USD, forum posts, testimonials), FAQ accordion Made-with: Cursor
289 lines
11 KiB
TypeScript
289 lines
11 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect, useRef, useCallback } from "react";
|
|
import Link from "next/link";
|
|
import ThemedLayout from "@/components/layouts/ThemedLayout";
|
|
|
|
const BREATH_PHASES = [
|
|
{ label: "Inhale", duration: 4, color: "#d4af37" },
|
|
{ label: "Hold", duration: 4, color: "#a07820" },
|
|
{ label: "Exhale", duration: 6, color: "#7a5c10" },
|
|
{ label: "Rest", duration: 2, color: "#4a3c00" },
|
|
];
|
|
const TOTAL_CYCLE = BREATH_PHASES.reduce((s, p) => s + p.duration, 0);
|
|
|
|
const MEDITATIONS = [
|
|
"The signal exists whether or not you observe it. Observe it.",
|
|
"Your handle is not your identity. Your actions are.",
|
|
"Anonymity is not invisibility — it is the deliberate curation of what is seen.",
|
|
"Every encrypted packet is a sealed letter to yourself across time.",
|
|
"You are one hop in a circuit that spans the planet.",
|
|
"In the void between nodes, there is no latency. Only intention.",
|
|
"Trust no one node. Trust the protocol.",
|
|
"The archive remembers. The relay forgets. Choose which you are.",
|
|
];
|
|
|
|
const CANDLE_KEY = "cyberlux-candles-v1";
|
|
type CandleEntry = { ts: number; note?: string };
|
|
|
|
function loadCandles(): CandleEntry[] {
|
|
if (typeof window === "undefined") return [];
|
|
try { return JSON.parse(localStorage.getItem(CANDLE_KEY) ?? "[]") as CandleEntry[]; }
|
|
catch { return []; }
|
|
}
|
|
|
|
function addCandle(note?: string) {
|
|
const candles = loadCandles();
|
|
candles.push({ ts: Date.now(), note });
|
|
if (typeof window !== "undefined") {
|
|
localStorage.setItem(CANDLE_KEY, JSON.stringify(candles.slice(-50)));
|
|
}
|
|
return candles.length + 1;
|
|
}
|
|
|
|
export default function SanctuaryPage() {
|
|
const [tab, setTab] = useState<"breathe" | "meditate" | "candle">("breathe");
|
|
|
|
// Breathing exercise
|
|
const [breathing, setBreathing] = useState(false);
|
|
const [phase, setPhase] = useState(0);
|
|
const [phaseTime, setPhaseTime] = useState(0);
|
|
const breathRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
|
|
// Meditation
|
|
const [meditationIdx, setMeditationIdx] = useState(0);
|
|
const [meditationSeconds, setMeditationSeconds] = useState(0);
|
|
const [meditating, setMeditating] = useState(false);
|
|
const medRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
|
|
// Candle
|
|
const [candleNote, setCandleNote] = useState("");
|
|
const [candleLit, setCandleLit] = useState(false);
|
|
const [candleCount, setCandleCount] = useState(0);
|
|
|
|
useEffect(() => {
|
|
setCandleCount(loadCandles().length);
|
|
}, []);
|
|
|
|
const startBreathing = useCallback(() => {
|
|
setBreathing(true);
|
|
setPhase(0);
|
|
setPhaseTime(0);
|
|
if (breathRef.current) clearInterval(breathRef.current);
|
|
let elapsed = 0;
|
|
breathRef.current = setInterval(() => {
|
|
elapsed++;
|
|
let acc = 0;
|
|
for (let i = 0; i < BREATH_PHASES.length; i++) {
|
|
acc += BREATH_PHASES[i]!.duration;
|
|
if (elapsed % TOTAL_CYCLE < acc - (BREATH_PHASES[i]!.duration - 1) + (BREATH_PHASES[i]!.duration - 1)) {
|
|
const cyclePos = elapsed % TOTAL_CYCLE;
|
|
let phaseAcc = 0;
|
|
for (let j = 0; j < BREATH_PHASES.length; j++) {
|
|
if (cyclePos < phaseAcc + BREATH_PHASES[j]!.duration) {
|
|
setPhase(j);
|
|
setPhaseTime(cyclePos - phaseAcc);
|
|
break;
|
|
}
|
|
phaseAcc += BREATH_PHASES[j]!.duration;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}, 1000);
|
|
}, []);
|
|
|
|
const stopBreathing = useCallback(() => {
|
|
setBreathing(false);
|
|
if (breathRef.current) clearInterval(breathRef.current);
|
|
}, []);
|
|
|
|
useEffect(() => () => { if (breathRef.current) clearInterval(breathRef.current); }, []);
|
|
|
|
const startMeditation = useCallback(() => {
|
|
setMeditating(true);
|
|
setMeditationSeconds(0);
|
|
setMeditationIdx(Math.floor(Math.random() * MEDITATIONS.length));
|
|
if (medRef.current) clearInterval(medRef.current);
|
|
medRef.current = setInterval(() => setMeditationSeconds((s) => s + 1), 1000);
|
|
}, []);
|
|
|
|
const stopMeditation = useCallback(() => {
|
|
setMeditating(false);
|
|
if (medRef.current) clearInterval(medRef.current);
|
|
}, []);
|
|
|
|
useEffect(() => () => { if (medRef.current) clearInterval(medRef.current); }, []);
|
|
|
|
const lightCandle = () => {
|
|
const count = addCandle(candleNote.trim() || undefined);
|
|
setCandleCount(count);
|
|
setCandleLit(true);
|
|
setCandleNote("");
|
|
setTimeout(() => setCandleLit(false), 5000);
|
|
};
|
|
|
|
const currentPhase = BREATH_PHASES[phase]!;
|
|
const circleScale = (() => {
|
|
if (!breathing) return 0.5;
|
|
const progress = phaseTime / currentPhase.duration;
|
|
if (phase === 0) return 0.5 + progress * 0.5;
|
|
if (phase === 1) return 1;
|
|
if (phase === 2) return 1 - progress * 0.5;
|
|
return 0.5;
|
|
})();
|
|
|
|
const formatMedSec = (s: number) =>
|
|
`${Math.floor(s / 60).toString().padStart(2, "0")}:${(s % 60).toString().padStart(2, "0")}`;
|
|
|
|
return (
|
|
<ThemedLayout theme="mystic" title="The Sanctuary">
|
|
<div className="mx-auto max-w-2xl px-4 py-12 text-center">
|
|
<h1 className="mb-2 font-serif text-4xl italic text-[#d4af37]">The Sanctuary</h1>
|
|
<p className="mb-10 text-sm text-[#d4af37]/60">A place of quiet. No tracking. No noise.</p>
|
|
|
|
{/* Tabs */}
|
|
<div className="mb-8 flex justify-center gap-1 rounded-full bg-black/30 p-1 border border-[#d4af37]/15">
|
|
{(["breathe", "meditate", "candle"] as const).map((t) => (
|
|
<button
|
|
key={t}
|
|
type="button"
|
|
onClick={() => setTab(t)}
|
|
className={`flex-1 rounded-full py-2 text-xs font-bold uppercase tracking-wider transition-all ${
|
|
tab === t ? "bg-[#d4af37]/20 text-[#d4af37]" : "text-[#d4af37]/40 hover:text-[#d4af37]/70"
|
|
}`}
|
|
>
|
|
{t === "breathe" ? "Breathing" : t === "meditate" ? "Meditation" : "Candles"}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Breathing */}
|
|
{tab === "breathe" && (
|
|
<div className="space-y-8">
|
|
<p className="text-sm text-[#d4af37]/70">
|
|
4-4-6-2 breathing pattern. Calms the nervous system in under two minutes.
|
|
</p>
|
|
<div className="relative flex h-48 w-48 mx-auto items-center justify-center">
|
|
<div
|
|
className="absolute inset-0 rounded-full border-2 border-[#d4af37]/30 transition-transform duration-1000"
|
|
style={{ transform: `scale(${circleScale})`, backgroundColor: `${currentPhase.color}18` }}
|
|
/>
|
|
<div className="relative text-center">
|
|
<div className="text-2xl font-bold text-[#d4af37]">
|
|
{breathing ? currentPhase.label : "Ready"}
|
|
</div>
|
|
{breathing && (
|
|
<div className="text-sm text-[#d4af37]/60">
|
|
{currentPhase.duration - phaseTime}s
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="flex justify-center gap-3">
|
|
{BREATH_PHASES.map((p, i) => (
|
|
<div
|
|
key={p.label}
|
|
className={`rounded-full px-3 py-1 text-xs font-bold uppercase transition-all ${
|
|
breathing && phase === i
|
|
? "bg-[#d4af37]/20 text-[#d4af37]"
|
|
: "text-[#d4af37]/30"
|
|
}`}
|
|
>
|
|
{p.label} {p.duration}s
|
|
</div>
|
|
))}
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={breathing ? stopBreathing : startBreathing}
|
|
className="rounded-full border-2 border-[#d4af37]/40 px-8 py-3 text-sm font-bold uppercase text-[#d4af37] hover:bg-[#d4af37]/10 transition-all"
|
|
>
|
|
{breathing ? "Stop" : "Begin Session"}
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{/* Meditation */}
|
|
{tab === "meditate" && (
|
|
<div className="space-y-8">
|
|
<div className="min-h-[5rem] flex items-center justify-center">
|
|
<p className="max-w-md text-xl italic leading-relaxed text-[#d4af37]/80">
|
|
"{MEDITATIONS[meditationIdx]}"
|
|
</p>
|
|
</div>
|
|
{meditating && (
|
|
<div className="text-3xl font-mono text-[#d4af37]">{formatMedSec(meditationSeconds)}</div>
|
|
)}
|
|
<div className="flex justify-center gap-3">
|
|
<button
|
|
type="button"
|
|
onClick={meditating ? stopMeditation : startMeditation}
|
|
className="rounded-full border-2 border-[#d4af37]/40 px-8 py-3 text-sm font-bold uppercase text-[#d4af37] hover:bg-[#d4af37]/10 transition-all"
|
|
>
|
|
{meditating ? "End Session" : "Begin Meditation"}
|
|
</button>
|
|
{!meditating && (
|
|
<button
|
|
type="button"
|
|
onClick={() => setMeditationIdx((i) => (i + 1) % MEDITATIONS.length)}
|
|
className="rounded-full border border-[#d4af37]/20 px-5 py-3 text-xs text-[#d4af37]/50 hover:text-[#d4af37]/80 transition-all"
|
|
>
|
|
Next passage
|
|
</button>
|
|
)}
|
|
</div>
|
|
{!meditating && meditationSeconds > 0 && (
|
|
<p className="text-sm text-[#d4af37]/50">
|
|
Session: {formatMedSec(meditationSeconds)}
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Candle */}
|
|
{tab === "candle" && (
|
|
<div className="space-y-6">
|
|
<p className="text-sm text-[#d4af37]/60">
|
|
Light a candle. Add an optional note. Stored only in this browser.{" "}
|
|
<span className="text-[#d4af37]/40">{candleCount} lit so far.</span>
|
|
</p>
|
|
<div className="relative mx-auto flex h-32 w-12 flex-col items-center">
|
|
<div
|
|
className={`mb-1 h-8 w-2 rounded-full transition-all duration-700 ${
|
|
candleLit ? "bg-amber-300 shadow-[0_0_20px_#fbbf24,0_0_40px_#fbbf24] animate-pulse" : "bg-[#d4af37]/20"
|
|
}`}
|
|
/>
|
|
<div className="h-20 w-10 rounded-b bg-gradient-to-b from-[#d4af37]/30 to-[#d4af37]/10 border border-[#d4af37]/20" />
|
|
</div>
|
|
<textarea
|
|
value={candleNote}
|
|
onChange={(e) => setCandleNote(e.target.value)}
|
|
rows={3}
|
|
maxLength={200}
|
|
placeholder="Optional note for this candle… (stored locally, not sent anywhere)"
|
|
className="w-full rounded-xl border border-[#d4af37]/20 bg-black/30 px-4 py-3 text-sm text-[#d4af37]/80 focus:border-[#d4af37]/40 focus:outline-none resize-none placeholder:text-[#d4af37]/30"
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={lightCandle}
|
|
className="rounded-full border-2 border-[#d4af37]/40 px-8 py-3 text-sm font-bold uppercase text-[#d4af37] hover:bg-[#d4af37]/10 transition-all"
|
|
>
|
|
{candleLit ? "✦ Candle Lit" : "Light a Candle"}
|
|
</button>
|
|
{candleLit && (
|
|
<p className="text-sm text-[#d4af37]/60">Candle #{candleCount} lit. It burns in this browser.</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
<div className="mt-16 flex justify-center gap-6 text-xs text-[#d4af37]/30">
|
|
<Link href="/" className="hover:text-[#d4af37]/60">Hub</Link>
|
|
<Link href="/vault" className="hover:text-[#d4af37]/60">Vault</Link>
|
|
<Link href="/mixer" className="hover:text-[#d4af37]/60">Mixer</Link>
|
|
</div>
|
|
</div>
|
|
</ThemedLayout>
|
|
);
|
|
}
|