init elas atelier #1

Merged
nvrl merged 82 commits from ela into main 2026-05-18 13:55:42 +02:00
3 changed files with 43 additions and 73 deletions
Showing only changes of commit d74f682155 - Show all commits
+5 -23
View File
@@ -42,19 +42,6 @@ function formatMonth(date: string) {
return new Date(date).toLocaleDateString('en-US', { month: 'short' }).toUpperCase(); return new Date(date).toLocaleDateString('en-US', { month: 'short' }).toUpperCase();
} }
function toRoman(n: number): string {
const map: [number, string][] = [
[1000, 'M'], [900, 'CM'], [500, 'D'], [400, 'CD'],
[100, 'C'], [90, 'XC'], [50, 'L'], [40, 'XL'],
[10, 'X'], [9, 'IX'], [5, 'V'], [4, 'IV'], [1, 'I'],
];
let out = '';
for (const [val, sym] of map) {
while (n >= val) { out += sym; n -= val; }
}
return out;
}
// Deterministic salon-hang layout. Each tile gets a col-span (out of 12) and an aspect ratio. // Deterministic salon-hang layout. Each tile gets a col-span (out of 12) and an aspect ratio.
// The cycle is chosen so the room reads asymmetric but balanced. // The cycle is chosen so the room reads asymmetric but balanced.
const LAYOUT_CYCLE: Array<{ col: number; aspect: string; tilt: number }> = [ const LAYOUT_CYCLE: Array<{ col: number; aspect: string; tilt: number }> = [
@@ -123,7 +110,6 @@ export default function PostList({ posts: initialPosts, isAdmin = false }: Props
const displayTitle = post.title || formatSlug(post.slug); const displayTitle = post.title || formatSlug(post.slug);
const isDeleting = deleting === post.slug; const isDeleting = deleting === post.slug;
const layout = LAYOUT_CYCLE[idx % LAYOUT_CYCLE.length]; const layout = LAYOUT_CYCLE[idx % LAYOUT_CYCLE.length];
const exhibitNumber = toRoman(idx + 1);
const hasCover = !!post.cover_image?.url; const hasCover = !!post.cover_image?.url;
return ( return (
@@ -141,8 +127,6 @@ export default function PostList({ posts: initialPosts, isAdmin = false }: Props
style={{ transform: `rotate(${layout.tilt}deg)` }} style={{ transform: `rotate(${layout.tilt}deg)` }}
aria-label={`View ${displayTitle}`} aria-label={`View ${displayTitle}`}
> >
<span className="plate-tag"> {exhibitNumber}</span>
<div <div
className="plate-image" className="plate-image"
style={{ aspectRatio: layout.aspect }} style={{ aspectRatio: layout.aspect }}
@@ -178,18 +162,16 @@ export default function PostList({ posts: initialPosts, isAdmin = false }: Props
</div> </div>
<div className="plate-caption"> <div className="plate-caption">
<div className="min-w-0"> <div className="plate-caption-title">{displayTitle}</div>
<div className="plate-caption-title truncate">{displayTitle}</div>
{post.summary && ( {post.summary && (
<div className="mt-1 text-xs text-[var(--subtext0)] font-sans italic line-clamp-1"> <div className="plate-caption-summary">{post.summary}</div>
{post.summary}
</div>
)} )}
</div>
<div className="plate-caption-meta"> <div className="plate-caption-meta">
<span>{formatMonth(post.date)}</span> <span>{formatMonth(post.date)}</span>
<span className="opacity-50 mx-1">·</span> <span className="plate-caption-sep" aria-hidden="true">·</span>
<span>{formatYear(post.date)}</span> <span>{formatYear(post.date)}</span>
<span className="plate-caption-sep" aria-hidden="true">·</span>
<span>{post.reading_time} min</span>
</div> </div>
</div> </div>
</a> </a>
+1 -23
View File
@@ -28,19 +28,6 @@ function formatDate(d: string) {
return new Date(d).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); return new Date(d).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
} }
function toRoman(n: number): string {
const map: [number, string][] = [
[1000, 'M'], [900, 'CM'], [500, 'D'], [400, 'CD'],
[100, 'C'], [90, 'XC'], [50, 'L'], [40, 'XL'],
[10, 'X'], [9, 'IX'], [5, 'V'], [4, 'IV'], [1, 'I'],
];
let out = '';
for (const [val, sym] of map) {
while (n >= val) { out += sym; n -= val; }
}
return out;
}
function formatSlug(s: string) { function formatSlug(s: string) {
if (!s) return ''; if (!s) return '';
return s.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); return s.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
@@ -49,7 +36,7 @@ function formatSlug(s: string) {
let post: PostDetail | null = null; let post: PostDetail | null = null;
let html = ''; let html = '';
let error = ''; let error = '';
let neighbors: { prev?: PostInfo; next?: PostInfo; index: number; total: number } = { index: -1, total: 0 }; let neighbors: { prev?: PostInfo; next?: PostInfo } = {};
try { try {
const [postRes, listRes] = await Promise.all([ const [postRes, listRes] = await Promise.all([
@@ -67,8 +54,6 @@ try {
const i = list.findIndex(p => p.slug === slug); const i = list.findIndex(p => p.slug === slug);
if (i >= 0) { if (i >= 0) {
neighbors = { neighbors = {
index: i,
total: list.length,
prev: i > 0 ? list[i - 1] : undefined, prev: i > 0 ? list[i - 1] : undefined,
next: i < list.length - 1 ? list[i + 1] : undefined, next: i < list.length - 1 ? list[i + 1] : undefined,
}; };
@@ -82,7 +67,6 @@ try {
const isAdmin = Astro.cookies.get('admin_session')?.value === '1'; const isAdmin = Astro.cookies.get('admin_session')?.value === '1';
const displayTitle = post ? (post.title || formatSlug(post.slug)) : 'Work'; const displayTitle = post ? (post.title || formatSlug(post.slug)) : 'Work';
const exhibitNumber = neighbors.index >= 0 ? toRoman(neighbors.index + 1) : '';
--- ---
<Layout <Layout
@@ -123,12 +107,6 @@ const exhibitNumber = neighbors.index >= 0 ? toRoman(neighbors.index + 1) : '';
{/* Plaque header */} {/* Plaque header */}
<header class="max-w-3xl mx-auto text-center mb-12 md:mb-16"> <header class="max-w-3xl mx-auto text-center mb-12 md:mb-16">
{exhibitNumber && (
<div class="font-display italic text-[var(--mauve)] tracking-[0.3em] text-sm mb-5">
№ {exhibitNumber} <span class="text-[var(--subtext0)] not-italic">/ {neighbors.total}</span>
</div>
)}
<h1 class="font-display italic font-semibold text-[var(--text)] text-4xl md:text-6xl lg:text-7xl leading-[0.95] tracking-tight mb-6"> <h1 class="font-display italic font-semibold text-[var(--text)] text-4xl md:text-6xl lg:text-7xl leading-[0.95] tracking-tight mb-6">
{displayTitle} {displayTitle}
</h1> </h1>
+35 -25
View File
@@ -553,46 +553,56 @@ code, pre, kbd, samp {
filter: saturate(1.05) contrast(1.04); filter: saturate(1.05) contrast(1.04);
} }
.plate .plate-caption { .plate .plate-caption {
padding: 12px 4px 14px 4px; padding: 14px 6px 16px 6px;
margin-top: 2px;
border-top: 1px solid color-mix(in srgb, var(--surface2) 50%, transparent);
display: flex; display: flex;
align-items: flex-end; flex-direction: column;
justify-content: space-between; gap: 0.4rem;
gap: 1rem;
} }
.plate .plate-caption-title { .plate .plate-caption-title {
font-family: var(--font-display); font-family: var(--font-display);
font-style: italic; font-style: italic;
font-weight: 500; font-weight: 500;
font-size: 1.05rem; font-size: 1.18rem;
line-height: 1.2; line-height: 1.18;
color: var(--text); color: var(--text);
letter-spacing: -0.005em; letter-spacing: -0.005em;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
transition: color 0.25s ease;
}
.plate:hover .plate-caption-title {
color: var(--mauve);
}
.plate .plate-caption-summary {
font-family: var(--font-sans);
font-style: italic;
font-size: 0.82rem;
line-height: 1.4;
color: var(--subtext0);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
} }
.plate .plate-caption-meta { .plate .plate-caption-meta {
font-family: var(--font-sans); font-family: var(--font-sans);
font-size: 0.72rem; font-size: 0.68rem;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.18em; letter-spacing: 0.22em;
color: var(--subtext0); color: var(--subtext0);
white-space: nowrap; white-space: nowrap;
align-self: flex-start; display: flex;
padding-top: 0.35rem; align-items: center;
gap: 0.5rem;
padding-top: 0.25rem;
} }
.plate .plate-caption-sep {
/* The little exhibit number stuck to the corner of a plate */ color: var(--mauve);
.plate-tag { opacity: 0.55;
position: absolute;
top: -10px;
left: 14px;
background: var(--mauve);
color: var(--rosewater);
font-family: var(--font-display);
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.18em;
padding: 4px 8px;
text-transform: uppercase;
box-shadow: 0 2px 6px -2px rgba(20, 16, 12, 0.45);
} }
.plate-tag-mini { .plate-tag-mini {
position: absolute; position: absolute;