add everything
This commit is contained in:
320
js/pages/Home.js
Normal file
320
js/pages/Home.js
Normal 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">←</button>
|
||||
<button class="home-nav home-nav-right" @click="next" aria-label="Next">→</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 20–30 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user