add everything

This commit is contained in:
Koolant
2026-04-17 12:29:41 -04:00
commit 4c1cfe6847
437 changed files with 11939 additions and 0 deletions

320
js/pages/Home.js Normal file
View File

@@ -0,0 +1,320 @@
import { fetchEditors, fetchList, fetchLeaderboard, fetchPacks } from '../content.js';
import { store } from '../main.js';
export default {
name: 'HomePage',
template: `
<main v-if="loading" class="surface" style="display:flex;align-items:center;justify-content:center;min-height:100vh;">
<p style="font-family:monospace;color:#6b7a8d;letter-spacing:0.2em;">LOADING...</p>
</main>
<main v-else class="home">
<div class="home-noise"></div>
<!-- ── Hero ── -->
<section class="home-hero">
<!--<div class="home-hero-grid"></div>-->
<div class="home-hero-scroll">
<div
class="home-hero-track"
:style="{ width: backgroundLevels.length * 300 + 'px' }"
>
<div
v-for="(bg, i) in backgroundLevels"
:key="i"
class="home-hero-tile"
:style="{ backgroundImage: \`url('\${bg}')\` }"
></div>
<!-- duplicate for seamless loop -->
<div
v-for="(bg, i) in backgroundLevels"
:key="'dup-' + i"
class="home-hero-tile"
:style="{ backgroundImage: \`url('\${bg}')\` }"
></div>
</div>
</div>
<div class="home-hero-content">
<!--<div class="home-eyebrow">
<span class="home-dot home-dot-pulse"></span>
<span>GEOMETRY DASH</span>
</div>-->
<h1 class="home-title">
<!--<span class="home-title-line">john evil</span>
<span class="home-title-line home-title-accent">EVIL DEMONLIST</span>
<span class="home-title-line">by [JE] john evil</span>-->
<img src="./tsl_logo_wName.png">
</h1>
<p class="home-sub">geometry dash; evil dash</p>
<div class="home-actions">
<router-link to="/list" class="home-btn home-btn-primary">Browse the List</router-link>
<router-link to="/leaderboard" class="home-btn home-btn-ghost">Leaderboard</router-link>
</div>
</div>
<!--<div class="home-deco">
<div class="home-ring home-ring-1"></div>
<div class="home-ring home-ring-2"></div>
<div class="home-ring home-ring-3"></div>
</div>-->
</section>
<!-- ── Stats Bar ── -->
<section class="home-stats-bar">
<div class="home-stats-inner">
<div class="home-stat-item" v-for="(stat, i) in stats" :key="i">
<span class="home-stat-value">{{ stat.value }}</span>
<span class="home-stat-label">{{ stat.label }}</span>
</div>
</div>
</section>
<!-- ── Members Spotlight ── -->
<section class="home-members-section">
<div class="home-section-header">
<h2 class="home-section-title">john evil</h2>
</div>
<div class="home-spotlight" v-if="enrichedEditors.length">
<div class="home-spotlight-bg" :style="spotlightBgStyle"></div>
<div class="home-spotlight-tint"></div>
<!-- Pill nav -->
<div class="home-pills">
<button
v-for="(editor, i) in enrichedEditors"
:key="editor.name"
class="home-pill"
:class="{ 'home-pill-active': i === activeIdx }"
@click="setActive(i)"
>
<img
class="home-pill-icon"
:src="'/assets/icons/' + editor.name + '.png'"
:alt="editor.name"
@error="onIconError"
/>
<span>{{ editor.name }}</span>
</button>
</div>
<!-- Card -->
<transition name="home-card-fade" mode="out-in">
<div class="home-card" :key="activeIdx" v-if="activeEditor">
<div class="home-card-left">
<a
v-if="activeEditor.link"
:href="activeEditor.link"
target="_blank"
rel="noopener noreferrer"
class="home-yt-link"
>
<img
class="home-yt-avatar"
:src="'/assets/icons/' + activeEditor.name + '.png'"
:alt="activeEditor.name"
@error="onIconError"
/>
<span class="home-yt-badge">
<svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14">
<path d="M23.5 6.2a3 3 0 0 0-2.1-2.1C19.5 3.5 12 3.5 12 3.5s-7.5 0-9.4.6a3 3 0 0 0-2.1 2.1C0 8.1 0 12 0 12s0 3.9.5 5.8a3 3 0 0 0 2.1 2.1c1.9.6 9.4.6 9.4.6s7.5 0 9.4-.6a3 3 0 0 0 2.1-2.1C24 15.9 24 12 24 12s0-3.9-.5-5.8zM9.8 15.5V8.5l6.3 3.5-6.3 3.5z"/>
</svg>
YouTube
</span>
</a>
<div v-else class="home-icon-wrap">
<img
:src="'/assets/icons/' + activeEditor.name + '.png'"
:alt="activeEditor.name"
@error="onIconError"
/>
</div>
<h3 class="home-card-name">{{ activeEditor.name }}</h3>
<p class="home-card-role">{{ activeEditor.role || 'List Member' }}</p>
</div>
<div class="home-card-right">
<div class="home-cstats">
<div class="home-cstat">
<span class="home-cstat-val">{{ activeEditor.completions + activeEditor.verifications ?? '—' }}</span>
<span class="home-cstat-key">Total Completions</span>
</div>
<div class="home-cstat">
<span class="home-cstat-val">{{ activeEditor.verifications ?? '—' }}</span>
<span class="home-cstat-key">First Completions</span>
</div>
<div class="home-cstat">
<span class="home-cstat-val">{{ activeEditor.rank != null ? '#' + activeEditor.rank : '—' }}</span>
<span class="home-cstat-key">Leaderboard Rank</span>
</div>
<div class="home-cstat" v-if="activeEditor.score != null">
<span class="home-cstat-val">{{ activeEditor.score }}</span>
<span class="home-cstat-key">List Score</span>
</div>
</div>
<div class="home-hardest" v-if="activeEditor.hardest">
<span class="home-hardest-label">HARDEST DEMON</span>
<span class="home-hardest-name">{{ activeEditor.hardest.level }}</span>
<span class="home-hardest-rank" v-if="activeEditor.hardest.rank">#{{ activeEditor.hardest.rank }} on the list</span>
</div>
</div>
</div>
</transition>
<button class="home-nav home-nav-left" @click="prev" aria-label="Previous">&#8592;</button>
<button class="home-nav home-nav-right" @click="next" aria-label="Next">&#8594;</button>
</div>
</section>
<!-- ── Fun Facts ── -->
<section class="home-facts">
<div class="home-facts-grid">
<div class="home-fact-card" v-for="(fact, i) in funFacts" :key="i">
<span class="home-fact-icon">{{ fact.icon }}</span>
<p class="home-fact-text">{{ fact.text }}</p>
</div>
</div>
</section>
<!-- ── Footer CTA ── -->
<section class="home-footer-cta">
<h2 class="home-footer-heading">↓ view the list ↓</h2>
<router-link to="/list" class="home-btn home-btn-primary home-btn-lg">yeah View the List</router-link>
</section>
</main>
`,
data: () => ({
store,
rawEditors: [],
leaderboardMap: {},
enrichedEditors: [],
loading: true,
activeIdx: 0,
stats: [
{ value: '—', label: 'Demons Ranked' },
{ value: '—', label: 'Total Completions' },
{ value: '—', label: 'Active Members' },
{ value: '—', label: 'Level Packs' },
],
funFacts: [
{ icon: '🔥', text: 'i got molested when i was 7' },
{ icon: '🤤', text: 'zorpikgmd is our one and only goongod and we are all his bellyslaves' },
{ icon: '💀', text: 'we are evil. john ai vs jew bot incident 3/23/2026 never forget ✊' },
],
backgroundLevels: [],
}),
watch: {
'store.listType'() {
resetHome(this);
}
},
computed: {
activeEditor() {
return this.enrichedEditors[this.activeIdx] || null;
},
spotlightBgStyle() {
const ed = this.activeEditor;
if (!ed || !ed.hardest) return {};
const p = ed.hardest.path
? `/assets/levels/${ed.hardest.path}.png`
: '/assets/levels/default.png';
return { backgroundImage: `url('${p}')` };
},
},
async mounted() {
await resetHome(this);
},
methods: {
setActive(i) { this.activeIdx = i; },
next() { this.activeIdx = (this.activeIdx + 1) % this.enrichedEditors.length; },
prev() { this.activeIdx = (this.activeIdx - 1 + this.enrichedEditors.length) % this.enrichedEditors.length; },
onIconError(e) { e.target.src = '/assets/icons/default.png'; },
},
};
export async function resetHome(ctx) {
console.log("evil");
ctx.loading = true;
try {
const [rawEditors, listData, [leaderboard]] = await Promise.all([
fetchEditors(),
fetchList(),
fetchLeaderboard(),
]);
ctx.rawEditors = (rawEditors || []).map((e) =>
typeof e === 'string' ? { name: e } : e
);
// Build leaderboard lookup keyed by lowercase name
const leaderboardMap = {};
if (leaderboard && Array.isArray(leaderboard)) {
leaderboard.forEach((entry, idx) => {
leaderboardMap[entry.user.toLowerCase()] = { ...entry, rank: idx + 1 };
});
}
ctx.leaderboardMap = leaderboardMap;
// Stats from list
if (listData) {
const valid = listData.filter(([, rank, level]) => rank !== null && level !== null);
const totalRecords = valid.reduce((s, [,, lv]) => s + (lv?.records?.length ?? 0), 0);
ctx.stats[0].value = valid.length;
ctx.stats[1].value = totalRecords + valid.length;
ctx.stats[2].value = ctx.rawEditors.length;
}
if (listData) {
const valid = listData.filter(([, rank, level]) => rank !== null && level !== null);
// pick random levels (like 2030 so it looks full)
const shuffled = valid.sort(() => 0.5 - Math.random());
ctx.backgroundLevels = shuffled.slice(0, 25).map(([, , lv]) => {
return `/assets/levels/${lv.path || lv.name}.png`;
});
}
// Enrich each editor from leaderboard
ctx.enrichedEditors = ctx.rawEditors.map((editor) => {
const lb = leaderboardMap[editor.name.toLowerCase()];
if (!lb) return { ...editor };
const completions = lb.completed?.length ?? 0;
const verifications = lb.verified?.length ?? 0;
const allFinished = [...(lb.verified || []), ...(lb.completed || [])];
let hardest = null;
if (allFinished.length > 0) {
const best = allFinished.reduce((p, c) => c.rank < p.rank ? c : p);
hardest = { level: best.level, rank: best.rank, path: best.path, link: best.link };
}
return { ...editor, completions, verifications, rank: lb.rank, score: lb.total, hardest };
});
// Pack count
try {
const packs = await fetchPacks();
if (packs) ctx.stats[3].value = packs.length;
} catch { /* non-critical */ }
} catch (err) {
console.error('HomePage: failed to load data:', err);
} finally {
ctx.loading = false;
}
}