0% found this document useful (0 votes)
13 views8 pages

!doctype HTML 3

GhostTalks is an anonymous confession box web application that allows users to submit and explore confessions while maintaining anonymity. The interface includes features for posting confessions, searching, and filtering by categories, as well as an admin panel for moderation. The application is designed to run in the browser using local storage, ensuring user data remains private and secure.

Uploaded by

ibkoladipo.ipi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
13 views8 pages

!doctype HTML 3

GhostTalks is an anonymous confession box web application that allows users to submit and explore confessions while maintaining anonymity. The interface includes features for posting confessions, searching, and filtering by categories, as well as an admin panel for moderation. The application is designed to run in the browser using local storage, ensuring user data remains private and secure.

Uploaded by

ibkoladipo.ipi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 8

<!

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>

You might also like