<!
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>GhostTalks — Anonymous Confession Box</title>
<style>
:root {
--bg: #0b0f14;
--card: #121721;
--card-2: #0f1420;
--text: #e8edf5;
--muted: #a9b2c3;
--brand: #6aa7ff;
--accent: #89f7fe;
--danger: #ff6b6b;
--success: #00d97e;
--warning: #ffd166;
--shadow: 0 10px 30px rgba(0,0,0,.35);
--radius: 16px;
}
* { box-sizing: border-box; }
body { margin:0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe
UI, Roboto, Ubuntu, Cantarell, Noto Sans, 'Helvetica Neue', Arial; background:
linear-gradient(120deg, #0a0f16, #0b1220 55%, #0a0e18); color: var(--text); }
.container { max-width: 980px; margin: 24px auto; padding: 0 16px; }
header { display:flex; align-items:center; justify-content:space-between;
gap:12px; margin-bottom:18px; }
.brand { display:flex; align-items:center; gap:10px; font-weight:800; letter-
spacing:0.3px; }
.logo { width:40px; height:40px; border-radius:12px; display:grid; place-
items:center; background: radial-gradient(120% 120% at 10% 10%, var(--accent),
var(--brand) 55%, transparent 56%), linear-gradient(160deg, #172235, #0d1422); box-
shadow: var(--shadow); }
.logo span { font-size:20px; }
.pill { padding:8px 12px; border-radius:999px; background:#0f1522; border:1px
solid #1c2740; color:var(--muted); font-size:12px; }
.row { display:flex; flex-wrap:wrap; gap:16px; }
.grow { flex: 1 1 360px; }
.card { background: linear-gradient(180deg, var(--card), var(--card-2));
border: 1px solid #1a2338; border-radius: var(--radius); box-shadow: var(--shadow);
}
.card .head { padding:14px 16px; border-bottom: 1px solid #1a2338;
display:flex; align-items:center; justify-content:space-between; gap:10px; }
.card .body { padding:16px; }
textarea, select, input[type="text"] {
width:100%; background:#0f1524; color:var(--text); border:1px solid #1b2743;
border-radius:12px; padding:12px 14px; outline:none; transition:.2s border;
}
textarea:focus, select:focus, input[type="text"]:focus { border-color: var(--
brand); }
textarea { min-height: 110px; resize: vertical; }
.btn { appearance:none; border:1px solid #243253; background:#12203a;
color:var(--text); padding:10px 14px; border-radius:12px; cursor:pointer; font-
weight:650; transition:.2s transform,.2s background,.2s border,.2s opacity; }
.btn:hover { transform: translateY(-1px); }
.[Link] { background: linear-gradient(135deg, #1b2c4e, #1a2a48); border-
color:#2a3b66; }
.[Link] { background: linear-gradient(135deg, #193d4a, #13323f); border-
color:#225e77; }
.[Link] { background: linear-gradient(135deg, #3e1b1b, #331313); border-
color:#5e2626; }
.[Link] { background: transparent; border-color:#223055; }
.hint { color: var(--muted); font-size:12px; }
.stack { display:flex; gap:10px; flex-wrap:wrap; align-items:center; }
.space { height: 12px; }
.tabs { display:flex; gap:6px; flex-wrap:wrap; }
.tab { padding:6px 10px; border-radius: 999px; border:1px solid #223055;
background:#0f1524; color:var(--muted); cursor:pointer; font-size:12px; }
.[Link] { color: var(--text); background:#172037; border-color:#2c3c66; }
.searchbar { display:flex; gap:10px; }
.searchbar input { flex:1; }
.conf-list { display:grid; grid-template-columns: repeat(auto-fill,
minmax(260px, 1fr)); gap:14px; }
.conf { background: #0f1524; border:1px solid #1b2743; border-radius:14px;
padding:14px; display:flex; flex-direction:column; gap:10px; position:relative; }
.conf .cat { display:inline-block; padding:6px 10px; border-radius:999px;
background:#14203a; border:1px solid #233258; font-size:12px; color:#a9c4ff; }
.conf .time { color:var(--muted); font-size:12px; }
.conf .text { white-space: pre-wrap; line-height:1.45; }
.conf .actions { display:flex; gap:8px; margin-top:6px; }
.small { font-size:12px; color:var(--muted); }
.admin-banner { display:flex; align-items:center; justify-content:space-
between; gap:8px; padding:10px 12px; border-radius:12px; border:1px dashed #35508d;
background:#0e1a33; }
.flag { color: #ffd166; }
.like { color: #89f7fe; }
.report { color: #ffadad; }
.ok { color: var(--success); }
.empty { text-align:center; color:var(--muted); padding:30px 10px; }
#rateHint { display:none; }
@media (max-width: 560px) {
.conf { padding:14px; } /* tiny mobile tweak */
.container { padding: 0 12px; }
}
</style>
</head>
<body>
<div class="container">
<header>
<a href="[Link]" class="btn accent">View Confessions Only</a>
<div class="brand">
<div class="logo"><span>👻</span></div>
<div>
<div style="font-size:18px">GhostTalks</div>
<div class="hint">Anonymous Confession Box • Local-first prototype</div>
</div>
</div>
<div class="stack">
<span class="pill" id="adminState">User Mode</span>
<button class="btn ghost" id="adminBtn">Admin</button>
</div>
</header>
<div class="row">
<!-- Write card -->
<div class="card grow">
<div class="head">
<strong>Drop a confession</strong>
<span class="hint">No one would know, even Me. Feel Free, confess
lol.</span>
</div>
<div class="body">
<div class="space"></div>
<label class="hint">Category</label>
<select id="category"></select>
<div class="space"></div>
<label class="hint">Your confession</label>
<textarea id="confText" placeholder="Say it and disappear like a
ghost..."></textarea>
<div class="space"></div>
<div class="stack">
<button class="btn brand" id="postBtn">Post Anonymously</button>
<span class="hint" id="rateHint">Unlimited posts</span>
</div>
</div>
</div>
<!-- Filter/Search card -->
<div class="card grow">
<div class="head">
<strong>Explore</strong>
<div class="tabs" id="tabs"></div>
</div>
<div class="body">
<div class="searchbar">
<input id="search" type="text" placeholder="Search text or category..."
/>
<button class="btn" id="clearSearch">Clear</button>
</div>
<div class="space"></div>
<div class="small">Tips: sort by Latest, Top, or Flagged (reports). Use
Admin to moderate.</div>
</div>
</div>
</div>
<!-- List -->
<div class="space"></div>
<div class="card">
<div class="head"><strong>Confessions</strong><span class="small"
id="count"></span></div>
<div class="body">
<div id="list" class="conf-list"></div>
<div id="empty" class="empty" style="display:none">No confessions yet. Be
the first 👀</div>
</div>
</div>
<!-- Admin panel -->
<div class="space"></div>
<div class="card" id="adminPanel" style="display:none">
<div class="head"><strong>Admin Panel</strong><span class="small">Local-only
(PIN protected). For real deployments, use a backend.</span></div>
<div class="body">
<div class="admin-banner">
<div>
<div><strong>Status:</strong> <span class="ok">Unlocked</span></div>
<div class="small">Delete items, clear all, or change PIN.</div>
</div>
<div class="stack">
<button class="btn danger" id="wipeAll">Wipe All</button>
<button class="btn" id="changePin">Change PIN</button>
</div>
</div>
</div>
</div>
<footer style="text-align:center; margin:20px 0; color:var(--muted)">
Built by you • Runs in your browser (localStorage) • v1.0
</footer>
</div>
<script>
// ====== Storage & Constants ======
const LS_KEY = 'ghosttalks_confessions_v1';
const PIN_KEY = 'ghosttalks_admin_pin';
const DEFAULT_PIN = '4321';
const DEFAULT_CATEGORIES = [
'General', 'Crush/Heart', 'School/Teachers', 'Family', 'Friends/Drama', 'Random
Rant', 'Confession'
];
const BAD_WORDS = [
'idiot','stupid','dumb','bastard','fool','nonsense','kill','die'
];
// ====== State ======
let confessions = loadConfessions();
let admin = false;
let currentTab = 'latest';
let query = '';
// ====== Helpers ======
function uid(){ return [Link]().toString(36).slice(2) +
[Link]().toString(36); }
function save(){ [Link](LS_KEY, [Link](confessions)); }
function loadConfessions(){ try{ return [Link]([Link](LS_KEY)
|| '[]'); }catch(e){ return []; } }
function getPin(){ return [Link](PIN_KEY) || DEFAULT_PIN; }
function setPin(p){ [Link](PIN_KEY, p); }
function timeAgo(ts){
const s = [Link](([Link]()-ts)/1000);
if(s < 60) return `${s}s ago`;
const m = [Link](s/60); if(m < 60) return `${m}m ago`;
const h = [Link](m/60); if(h < 24) return `${h}h ago`;
const d = [Link](h/24); if(d < 7) return `${d}d ago`;
const w = [Link](d/7); if(w < 5) return `${w}w ago`;
return new Date(ts).toLocaleDateString();
}
function cleanText(t){
let x = [Link]();
for(const w of BAD_WORDS){ x = [Link](new RegExp(`\\b${w}\\b`, 'gi'),
'***'); }
return x;
}
// ====== UI Elements ======
const categoryEl = [Link]('category');
const textEl = [Link]('confText');
const postBtn = [Link]('postBtn');
const tabsEl = [Link]('tabs');
const listEl = [Link]('list');
const emptyEl = [Link]('empty');
const countEl = [Link]('count');
const searchEl = [Link]('search');
const clearSearchBtn = [Link]('clearSearch');
const adminBtn = [Link]('adminBtn');
const adminPanel = [Link]('adminPanel');
const adminState = [Link]('adminState');
const wipeAllBtn = [Link]('wipeAll');
const changePinBtn = [Link]('changePin');
// ====== Init Categories ======
DEFAULT_CATEGORIES.forEach(c => {
const o = [Link]('option');
[Link] = c; [Link] = c; [Link](o);
});
// ====== Init Tabs ======
const TABS = [
{id:'latest', label:'Latest'},
{id:'top', label:'Top'},
{id:'flagged', label:'Flagged'},
];
[Link](t => {
const b = [Link]('button');
[Link] = 'tab' + ([Link] === currentTab ? ' active' : '');
[Link] = [Link];
[Link]('click', () => { currentTab = [Link]; render(); });
[Link](b);
});
[Link]('input', (e)=>{ query =
[Link]().toLowerCase(); render(); });
[Link]('click', ()=>{ [Link]=''; query='';
render(); });
[Link]('click', () => {
const txt = cleanText([Link]);
if(!txt) return;
const cat = [Link] || DEFAULT_CATEGORIES[0];
const item = { id: uid(), text: txt, category: cat, likes:0, disliked:0,
comments:[], createdAt: [Link](), liked:false, disliked:false, reports:0 };
[Link](item);
save();
[Link] = '';
render();
[Link]({ top: 0, behavior: 'smooth' });
});
[Link]('click', ()=>{
if(!admin){
const pin = prompt('Enter admin PIN');
if(pin === getPin()){ admin = true; [Link]='block';
[Link]='Admin Mode'; render(); }
else { alert('Wrong PIN'); }
} else {
admin = false; [Link]='none'; [Link]='User
Mode'; render();
}
});
[Link]('click', ()=>{
if(confirm("Delete ALL confessions? This can't be undone.")){
confessions = []; save(); render();
}
});
[Link]('click', ()=>{
const cur = prompt('Current PIN');
if(cur !== getPin()) return alert('Wrong PIN');
const np = prompt('New PIN (4-8 digits)', '');
if(!np || [Link] < 4 || [Link] > 8 || /\D/.test(np)) return
alert('Invalid PIN. Use 4-8 digits.');
setPin(np); alert('PIN updated.');
});
function filtered(){
let arr = [Link]();
if(query){ arr = [Link](c => [Link]().includes(query) ||
[Link]().includes(query)); }
if(currentTab === 'latest') [Link]((a,b)=> [Link] - [Link]);
if(currentTab === 'top') [Link]((a,b)=> ([Link] - [Link]) || ([Link] -
[Link]));
if(currentTab === 'flagged') arr = [Link](c => [Link] > 0);
return arr;
}
function render(){
[...[Link]].forEach(btn => {
[Link]('active', [Link]() === currentTab);
});
const arr = filtered();
[Link] = `${[Link]} total • ${[Link]} shown`;
[Link]='';
[Link] = [Link] ? 'none' : 'block';
for(const c of arr){
const card = [Link]('div');
[Link] = 'conf';
const top = [Link]('div');
[Link] = 'stack';
const cat = [Link]('span'); [Link]='cat';
[Link]=[Link]; [Link](cat);
const time = [Link]('span'); [Link]='time';
[Link] = '• ' + timeAgo([Link]); [Link](time);
[Link](top);
const text = [Link]('div'); [Link]='text';
[Link] = [Link]; [Link](text);
const actions = [Link]('div'); [Link]='actions';
// Like button
const like = [Link]('button'); [Link]='btn';
[Link] = `👍 <span class="small like">${[Link]}</span>`;
[Link] = [Link];
[Link]('click', ()=>{
if(![Link]){ [Link]++; [Link]=true; save(); render(); }
});
[Link](like);
// Dislike button
const dislike = [Link]('button'); [Link]='btn';
[Link] = `👎 <span class="small">${[Link]}</span>`;
[Link] = [Link];
[Link]('click', ()=>{
if(![Link]){ [Link]=true; save(); render(); }
});
[Link](dislike);
// Report button
const report = [Link]('button'); [Link]='btn';
[Link] = `⚠️ Report`;
[Link]('click', ()=>{ [Link]++; save(); render(); });
[Link](report);
// Admin delete
if(admin){
const del = [Link]('button'); [Link]='btn danger';
[Link] = 'Delete';
[Link]('click', ()=>{
if(confirm('Delete this confession?')){ confessions =
[Link](x=>[Link]!==[Link]); save(); render(); }
});
[Link](del);
}
[Link](actions);
[Link](card);
}
}
render();
</script>
</body>
</html>