-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.js
More file actions
152 lines (132 loc) · 6.33 KB
/
Copy pathapp.js
File metadata and controls
152 lines (132 loc) · 6.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// ── SHIDNER APP.JS ────────────────────────────────────────────────────────────
// Hash-based router. Routes: #/ (home), #/about, #/project/:slug
const app = document.getElementById('app');
// Accent colors cycled per card
const ACCENTS = ['#ff3c00', '#0057ff', '#c8ff00', '#ff0090', '#0057ff', '#ff3c00'];
// ── ROUTER ────────────────────────────────────────────────────────────────────
function router() {
const hash = window.location.hash || '#/';
const [, route, ...rest] = hash.split('/');
if (hash === '#/' || hash === '#') {
renderHome();
} else if (hash === '#/about') {
renderAbout();
} else if (hash.startsWith('#/project/')) {
const slug = rest.join('/');
renderProject(slug);
} else {
renderNotFound();
}
}
window.addEventListener('hashchange', router);
window.addEventListener('DOMContentLoaded', router);
// ── FETCH PROJECTS ─────────────────────────────────────────────────────────────
async function fetchProjects() {
const res = await fetch('projects.json?v=' + Date.now());
if (!res.ok) throw new Error('Could not load projects.json');
return res.json();
}
// ── HOME ───────────────────────────────────────────────────────────────────────
async function renderHome() {
app.innerHTML = `
<section class="hero animate-in">
<p class="hero-eyebrow">A lab for code, tools & demos</p>
<h1 class="hero-title">
SHID<span class="accent-r">N</span>E<span class="accent-b">R</span>
</h1>
<p class="hero-sub">Experimental projects built in the margins of the wilderness.</p>
<div class="hero-badge">Open Lab ✦ Live Demos</div>
</section>
<p class="section-label">All projects</p>
<div class="project-grid" id="grid">
<div class="loading">Loading projects…</div>
</div>
`;
const grid = document.getElementById('grid');
try {
const projects = await fetchProjects();
if (!projects.length) {
grid.innerHTML = `
<div class="empty-state">
<p>No projects yet — check back soon.</p>
</div>`;
return;
}
grid.innerHTML = projects.map((p, i) => `
<a class="card" href="#/project/${p.slug}"
style="--card-accent: ${ACCENTS[i % ACCENTS.length]}">
<div class="card-header">
<span class="card-num">${String(i + 1).padStart(2, '0')}</span>
<span class="card-arrow">↗</span>
</div>
<h2 class="card-title">${p.title}</h2>
<p class="card-desc">${p.description}</p>
<div class="card-footer">
${p.tags.map(t => `<span class="card-tag">${t}</span>`).join('')}
</div>
</a>
`).join('');
} catch (err) {
grid.innerHTML = `
<div class="empty-state">
<p>Could not load projects. Check that projects.json exists.</p>
</div>`;
console.error(err);
}
}
// ── PROJECT PAGE ───────────────────────────────────────────────────────────────
async function renderProject(slug) {
app.innerHTML = `<div class="loading">Loading…</div>`;
let projects;
try {
projects = await fetchProjects();
} catch {
renderNotFound();
return;
}
const project = projects.find(p => p.slug === slug);
if (!project) { renderNotFound(); return; }
const tagsHtml = project.tags
.map(t => `<span class="card-tag">${t}</span>`)
.join('');
const embedHtml = project.embed_url
? `<iframe class="project-embed" src="${project.embed_url}" title="${project.title}" frameborder="0" allowfullscreen></iframe>`
: `<div class="project-embed" style="display:flex;align-items:center;justify-content:center;color:#6b6456;font-size:12px;letter-spacing:0.1em;text-transform:uppercase;">No demo available</div>`;
app.innerHTML = `
<div class="project-page animate-in">
<a class="project-back" href="#/">← All projects</a>
<header class="project-header">
<h1 class="project-page-title">${project.title}</h1>
<div class="project-meta">
${project.date ? `<div class="meta-item"><span class="meta-label">Date</span><span class="meta-value">${project.date}</span></div>` : ''}
${project.status ? `<div class="meta-item"><span class="meta-label">Status</span><span class="meta-value">${project.status}</span></div>` : ''}
</div>
<div class="project-tags">${tagsHtml}</div>
</header>
${embedHtml}
${project.long_description ? `<p class="project-description">${project.long_description}</p>` : ''}
</div>
`;
}
// ── ABOUT ──────────────────────────────────────────────────────────────────────
function renderAbout() {
app.innerHTML = `
<div class="about-page animate-in">
<h1 class="about-title">ABOUT<br>THIS LAB</h1>
<div class="about-body">
<p>Shidner is a personal sketchbook for code — <span class="about-accent">a place to build, break, and share experiments</span> without the overhead of a polished product.</p>
<p>Projects here are Claude-powered tools, interactive demos, and utilities built in McCarthy, Alaska, where the winters are long and the side projects are plentiful.</p>
<p>Nothing here is meant to be finished. Everything here is meant to be useful.</p>
</div>
</div>
`;
}
// ── 404 ────────────────────────────────────────────────────────────────────────
function renderNotFound() {
app.innerHTML = `
<div class="not-found animate-in">
<h2>404</h2>
<p>Nothing here. <a href="#/">Back to projects →</a></p>
</div>
`;
}