Skip to content

Commit 09b8e90

Browse files
CodeManEthanclaude
andcommitted
Restyle project detail pages to match the archipelago world
Light pastel theme, per-project island accent + gem glyph, LOC/island-size line, shared islands data module, skip missing screenshots at build time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X2b2tJP5uUTXJexhi7nKNA
1 parent 25f7df3 commit 09b8e90

4 files changed

Lines changed: 148 additions & 72 deletions

File tree

src/components/ArchipelagoHome.astro

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,7 @@
22
import { getCollection } from 'astro:content';
33
import ArchipelagoScene from './designs/archipelago/ArchipelagoScene';
44
5-
// Real lines of code, measured from each repo. Keys = content entry ids.
6-
const LOC: Record<string, number> = {
7-
'finance-tracker': 67732,
8-
kdoitall: 26417,
9-
'project-hub': 19773,
10-
'pdf-merger': 13609,
11-
'war-card-game': 3921,
12-
'one-record-many-bells': 2330,
13-
'montage-it': 1691,
14-
};
5+
import { LOC, GEM_COLORS, fmtLoc, sizeLabel } from '../data/islands';
156
167
const projects = (await getCollection('projects')).sort(
178
(a, b) => a.data.order - b.data.order
@@ -26,27 +17,7 @@ const islands = projects.map((p) => ({
2617
featured: p.data.featured,
2718
}));
2819
29-
// Index-matched to GEM_COLORS in the scene so each card echoes its island.
30-
const gemColors = [
31-
'#ef7f93',
32-
'#f5a25d',
33-
'#f0c75e',
34-
'#6cc4d9',
35-
'#9b8fe8',
36-
'#63c9a8',
37-
'#e98fc3',
38-
];
39-
40-
function fmtLoc(n: number): string {
41-
return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : `${n}`;
42-
}
43-
44-
function sizeLabel(n: number): string {
45-
if (n >= 25000) return 'large island';
46-
if (n >= 10000) return 'mid-size island';
47-
if (n >= 3000) return 'small island';
48-
return 'tiny island';
49-
}
20+
const gemColors = GEM_COLORS;
5021
---
5122

5223
<!doctype html>

src/data/islands.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Shared island metadata: real lines of code measured from each repo, and the
2+
// gem palette that colors each project's island in the archipelago scene.
3+
// GEM_COLORS is index-matched to projects sorted by `order` frontmatter.
4+
5+
export const LOC: Record<string, number> = {
6+
'finance-tracker': 67732,
7+
kdoitall: 26417,
8+
'project-hub': 19773,
9+
'pdf-merger': 13609,
10+
'war-card-game': 3921,
11+
'one-record-many-bells': 2330,
12+
'montage-it': 1691,
13+
};
14+
15+
export const GEM_COLORS = [
16+
'#ef7f93',
17+
'#f5a25d',
18+
'#f0c75e',
19+
'#6cc4d9',
20+
'#9b8fe8',
21+
'#63c9a8',
22+
'#e98fc3',
23+
];
24+
25+
export function fmtLoc(n: number): string {
26+
return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : `${n}`;
27+
}
28+
29+
export function sizeLabel(n: number): string {
30+
if (n >= 25000) return 'large island';
31+
if (n >= 10000) return 'mid-size island';
32+
if (n >= 3000) return 'small island';
33+
return 'tiny island';
34+
}

src/layouts/Base.astro

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@
22
interface Props {
33
title: string;
44
description?: string;
5+
/** Per-page accent color (a project's island gem color). */
6+
accent?: string;
57
}
68
7-
const { title, description = 'Portfolio of Ethan — software engineer and agentic engineer.' } = Astro.props;
9+
const {
10+
title,
11+
description = 'Portfolio of Ethan — software engineer and agentic engineer.',
12+
accent = '#63c9a8',
13+
} = Astro.props;
814
---
915

1016
<!doctype html>
@@ -16,7 +22,7 @@ const { title, description = 'Portfolio of Ethan — software engineer and agent
1622
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
1723
<title>{title}</title>
1824
</head>
19-
<body>
25+
<body style={`--accent: ${accent};`}>
2026
<header class="site-header">
2127
<nav>
2228
<a href="/" class="brand">CodeManEthan</a>
@@ -41,14 +47,13 @@ const { title, description = 'Portfolio of Ethan — software engineer and agent
4147

4248
<style is:global>
4349
:root {
44-
--bg: #0b0e14;
45-
--bg-raised: #131722;
46-
--border: #232936;
47-
--text: #e6e9ef;
48-
--text-dim: #9aa3b2;
49-
--accent: #5eead4;
50-
--accent-2: #818cf8;
51-
--radius: 12px;
50+
--cream: #fdf8ef;
51+
--card: #ffffff;
52+
--ink: #43405a;
53+
--ink-soft: #7a7690;
54+
--border: #ece3d2;
55+
--mint: #63c9a8;
56+
--radius: 16px;
5257
--max-width: 1080px;
5358
}
5459

@@ -62,16 +67,22 @@ const { title, description = 'Portfolio of Ethan — software engineer and agent
6267
scroll-behavior: smooth;
6368
}
6469

70+
@media (prefers-reduced-motion: reduce) {
71+
html {
72+
scroll-behavior: auto;
73+
}
74+
}
75+
6576
body {
66-
background: var(--bg);
67-
color: var(--text);
68-
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
77+
background: var(--cream);
78+
color: var(--ink);
79+
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
6980
line-height: 1.6;
7081
-webkit-font-smoothing: antialiased;
7182
}
7283

7384
a {
74-
color: var(--accent);
85+
color: color-mix(in srgb, var(--accent) 65%, var(--ink));
7586
text-decoration: none;
7687
}
7788

@@ -89,7 +100,7 @@ const { title, description = 'Portfolio of Ethan — software engineer and agent
89100
position: sticky;
90101
top: 0;
91102
z-index: 10;
92-
background: color-mix(in srgb, var(--bg) 85%, transparent);
103+
background: color-mix(in srgb, var(--cream) 88%, transparent);
93104
backdrop-filter: blur(10px);
94105
border-bottom: 1px solid var(--border);
95106
}
@@ -104,9 +115,13 @@ const { title, description = 'Portfolio of Ethan — software engineer and agent
104115
}
105116

106117
.brand {
107-
font-weight: 700;
108-
color: var(--text);
109-
letter-spacing: 0.02em;
118+
font-weight: 800;
119+
color: var(--ink);
120+
letter-spacing: 0.01em;
121+
}
122+
123+
.brand:hover {
124+
text-decoration: none;
110125
}
111126

112127
.nav-links {
@@ -115,12 +130,13 @@ const { title, description = 'Portfolio of Ethan — software engineer and agent
115130
}
116131

117132
.nav-links a {
118-
color: var(--text-dim);
119-
font-size: 0.95rem;
133+
color: var(--ink-soft);
134+
font-weight: 600;
135+
font-size: 0.92rem;
120136
}
121137

122138
.nav-links a:hover {
123-
color: var(--text);
139+
color: var(--ink);
124140
text-decoration: none;
125141
}
126142

@@ -129,21 +145,21 @@ const { title, description = 'Portfolio of Ethan — software engineer and agent
129145
margin-top: 5rem;
130146
padding: 2rem 1.25rem;
131147
text-align: center;
132-
color: var(--text-dim);
148+
color: var(--ink-soft);
133149
font-size: 0.9rem;
134150
}
135151

136152
code {
137153
font-family: 'JetBrains Mono', ui-monospace, 'Cascadia Code', Menlo, monospace;
138154
font-size: 0.9em;
139-
background: var(--bg-raised);
155+
background: #f6efe1;
140156
border: 1px solid var(--border);
141157
border-radius: 6px;
142158
padding: 0.1em 0.35em;
143159
}
144160

145161
pre {
146-
background: var(--bg-raised);
162+
background: #f6efe1;
147163
border: 1px solid var(--border);
148164
border-radius: var(--radius);
149165
padding: 1rem;

src/pages/projects/[slug].astro

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,40 @@
11
---
2+
import fs from 'node:fs';
23
import { getCollection, render } from 'astro:content';
34
import Base from '../../layouts/Base.astro';
5+
import { LOC, GEM_COLORS, fmtLoc, sizeLabel } from '../../data/islands';
46
57
export async function getStaticPaths() {
6-
const projects = await getCollection('projects');
7-
return projects.map((project) => ({
8+
const projects = (await getCollection('projects')).sort(
9+
(a, b) => a.data.order - b.data.order
10+
);
11+
return projects.map((project, index) => ({
812
params: { slug: project.id },
9-
props: { project },
13+
props: { project, accent: GEM_COLORS[index % GEM_COLORS.length] },
1014
}));
1115
}
1216
13-
const { project } = Astro.props;
14-
const { title, summary, tech, status, repo, demo, screenshot } = project.data;
17+
const { project, accent } = Astro.props;
18+
const { title, summary, tech, status, repo, demo } = project.data;
19+
// Only render the screenshot if the file actually exists in public/
20+
const screenshot =
21+
project.data.screenshot && fs.existsSync(`public${project.data.screenshot}`)
22+
? project.data.screenshot
23+
: undefined;
24+
const loc = LOC[project.id];
1525
const { Content } = await render(project);
1626
---
1727

18-
<Base title={`${title} · Ethan`} description={summary}>
28+
<Base title={`${title} · Ethan`} description={summary} accent={accent}>
1929
<article class="project">
20-
<a href="/#projects" class="back">← All projects</a>
21-
<h1>{title}</h1>
30+
<a href="/#projects" class="back">← Back to the archipelago</a>
31+
<div class="title-row">
32+
<span class="gem" aria-hidden="true"></span>
33+
<h1>{title}</h1>
34+
</div>
35+
{loc && (
36+
<p class="island-line">{fmtLoc(loc)} lines of code · {sizeLabel(loc)}</p>
37+
)}
2238
<p class="summary">{summary}</p>
2339
<div class="meta">
2440
<ul class="tech-list">
@@ -47,17 +63,49 @@ const { Content } = await render(project);
4763
}
4864

4965
.back {
50-
color: var(--text-dim);
66+
color: var(--ink-soft);
5167
font-size: 0.9rem;
68+
font-weight: 600;
69+
}
70+
71+
.back:hover {
72+
color: var(--ink);
73+
text-decoration: none;
74+
}
75+
76+
.title-row {
77+
display: flex;
78+
align-items: center;
79+
gap: 0.8rem;
80+
margin: 1rem 0 0.15rem;
81+
}
82+
83+
.gem {
84+
width: 1.05rem;
85+
height: 1.05rem;
86+
background: var(--accent);
87+
border-radius: 4px;
88+
transform: rotate(45deg);
89+
flex-shrink: 0;
90+
box-shadow: 0 2px 8px color-mix(in srgb, var(--accent) 45%, transparent);
5291
}
5392

5493
h1 {
5594
font-size: clamp(1.8rem, 5vw, 2.6rem);
56-
margin: 0.8rem 0 0.5rem;
95+
line-height: 1.2;
96+
}
97+
98+
.island-line {
99+
color: color-mix(in srgb, var(--accent) 70%, var(--ink));
100+
font-weight: 700;
101+
font-size: 0.9rem;
102+
text-transform: uppercase;
103+
letter-spacing: 0.06em;
104+
margin-bottom: 0.9rem;
57105
}
58106

59107
.summary {
60-
color: var(--text-dim);
108+
color: var(--ink-soft);
61109
font-size: 1.1rem;
62110
margin-bottom: 1.2rem;
63111
}
@@ -78,33 +126,37 @@ const { Content } = await render(project);
78126

79127
.tech-list li {
80128
font-size: 0.8rem;
81-
padding: 0.15rem 0.6rem;
82-
border: 1px solid var(--border);
129+
font-weight: 600;
130+
padding: 0.18rem 0.65rem;
131+
border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
83132
border-radius: 999px;
84-
color: var(--accent);
85-
background: color-mix(in srgb, var(--accent) 6%, transparent);
133+
color: color-mix(in srgb, var(--accent) 60%, var(--ink));
134+
background: color-mix(in srgb, var(--accent) 10%, var(--card));
86135
}
87136

88137
.links {
89138
display: flex;
90139
gap: 1rem;
91140
flex-wrap: wrap;
92141
align-items: center;
142+
font-weight: 600;
93143
}
94144

95145
.badge {
96-
color: var(--text-dim);
146+
color: var(--ink-soft);
97147
font-size: 0.85rem;
98148
border: 1px dashed var(--border);
99149
border-radius: 999px;
100150
padding: 0.15rem 0.7rem;
151+
background: var(--card);
101152
}
102153

103154
.shot {
104155
width: 100%;
105156
border-radius: var(--radius);
106157
border: 1px solid var(--border);
107158
margin-bottom: 1.6rem;
159+
box-shadow: 0 10px 30px rgba(67, 64, 90, 0.08);
108160
}
109161

110162
.body :global(h2) {
@@ -119,7 +171,6 @@ const { Content } = await render(project);
119171

120172
.body :global(p) {
121173
margin-bottom: 1rem;
122-
color: var(--text);
123174
}
124175

125176
.body :global(ul),
@@ -130,4 +181,8 @@ const { Content } = await render(project);
130181
.body :global(li) {
131182
margin-bottom: 0.35rem;
132183
}
184+
185+
.body :global(strong) {
186+
color: color-mix(in srgb, var(--accent) 45%, var(--ink));
187+
}
133188
</style>

0 commit comments

Comments
 (0)