Skip to content

Commit 0900f52

Browse files
SanTiepiclaude
andcommitted
feat(mots): colophon + flux RSS + 404 + archive du jour + manifest PWA
Finitions « maison du livre » du dictionnaire augmenté /mots : 1. Colophon /mots/colophon — la préface : méthode (forge gréco-latine + audit helléniste adversarial), les 7 lexiques chiffrés (compteurs dynamiques), le dialogue Robin × Claude, la licence CC0, « comment c'est fait », une invitation. Carte OG dédiée + JSON-LD AboutPage. 2. Flux RSS /mots/feed.xml — RSS 2.0 écrit à la main (zéro dépendance), 475 items (un par slug, dédup), titres/catégories échappés, descriptions en CDATA. C'est le vrai rel=alternate du recueil (lien head + footer). 3. 404 — la page d'erreur du domaine existait déjà (identité site principal, vert/Iowan) ; on lui ajoute une 2e porte discrète vers /mots, sans toucher à son style. 4. Archive du mot du jour — sous la carte, les 7 jours précédents (même date-hash déterministe, données déjà inlinées → zéro payload en plus, dates via constructeur Date(y,m,d-n) immunisé DST). 5. Manifest PWA + icônes — mots.webmanifest (scope /mots/, theme/bg accordés) + icônes 192/512 « m » serif doré sur vélin (générées par scripts/ gen-icons.mjs, reproductible). Add-to-home propre. MotsLayout : head enrichi (rel=alternate rss, manifest, apple-touch-icon) ; footer (colophon, flux RSS). Sitemap filtré (exclut og + feed). Vérifié : build 571 pages, verify-links 0 cassé, RSS bien formé (parsé), manifest JSON valide, vérif adversariale 3 lentilles (artefacts / colophon / archive+régression) = 0 blocker, 0 major. Le « 478 » (néologismes forgés) reste le nombre-identité ; 475 = slugs/pages/items du flux (distinction explicitée au colophon). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6cd7795 commit 0900f52

11 files changed

Lines changed: 239 additions & 8 deletions

File tree

site/astro.config.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ export default defineConfig({
1010
site: 'https://anthropie.org',
1111
integrations: [
1212
sitemap({
13-
// Les endpoints d'images OG (/mots/og/*.png) ne sont pas des pages à indexer.
14-
filter: (page) => !page.includes('/mots/og/'),
13+
// Les endpoints non-HTML (images OG, flux RSS) ne sont pas des pages à indexer.
14+
filter: (page) => !page.includes('/mots/og/') && !page.endsWith('/feed.xml'),
1515
}),
1616
],
1717
});

site/public/mots-icon-192.png

3.58 KB
Loading

site/public/mots-icon-512.png

10.1 KB
Loading

site/public/mots.webmanifest

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "Les mots — dictionnaire augmenté",
3+
"short_name": "Les mots",
4+
"description": "Quatre cent septante-huit néologismes gréco-latins forgés de grec et de latin, pour nommer ce que la langue laissait muet.",
5+
"lang": "fr",
6+
"start_url": "/mots/",
7+
"scope": "/mots/",
8+
"display": "standalone",
9+
"background_color": "#f4ead4",
10+
"theme_color": "#2b2118",
11+
"icons": [
12+
{ "src": "/mots-icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
13+
{ "src": "/mots-icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
14+
{ "src": "/mots-icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
15+
]
16+
}

site/scripts/gen-icons.mjs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env node
2+
// Génère les icônes PWA de /mots (Vélin & or) : un « m » serif doré sur parchemin,
3+
// cadre fin doré. Sortie : public/mots-icon-192.png et public/mots-icon-512.png.
4+
// Reproductible : node scripts/gen-icons.mjs (resvg + fonte Cardo déjà au repo).
5+
import { Resvg } from '@resvg/resvg-js';
6+
import fs from 'node:fs';
7+
import path from 'node:path';
8+
9+
const FONT = fs.readFileSync(path.join(process.cwd(), 'src', 'og-fonts', 'Cardo-Bold.ttf'));
10+
const OUT = path.join(process.cwd(), 'public');
11+
12+
function render(size) {
13+
const rx = Math.round(size * 0.18);
14+
const inset = Math.round(size * 0.10);
15+
const frameRx = Math.round(size * 0.11);
16+
const glyph = Math.round(size * 0.56);
17+
const svg = `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
18+
<rect width="${size}" height="${size}" rx="${rx}" fill="#f4ead4"/>
19+
<rect x="${inset}" y="${inset}" width="${size - inset * 2}" height="${size - inset * 2}" rx="${frameRx}" fill="none" stroke="#a9842b" stroke-opacity="0.55" stroke-width="${Math.max(2, Math.round(size * 0.012))}"/>
20+
<text x="50%" y="${Math.round(size * 0.62)}" font-family="Cardo" font-weight="700" font-size="${glyph}" fill="#a9842b" text-anchor="middle">m</text>
21+
</svg>`;
22+
const png = new Resvg(svg, {
23+
font: { fontBuffers: [FONT], loadSystemFonts: false, defaultFontFamily: 'Cardo' },
24+
fitTo: { mode: 'width', value: size },
25+
}).render().asPng();
26+
const file = path.join(OUT, `mots-icon-${size}.png`);
27+
fs.writeFileSync(file, png);
28+
console.log(`wrote ${file} (${png.length} bytes)`);
29+
}
30+
31+
render(192);
32+
render(512);

site/src/layouts/MotsLayout.astro

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ const nav = [
2121
<head>
2222
<meta charset="utf-8" />
2323
<link rel="icon" type="image/svg+xml" href={`${base}/favicon.svg`} />
24+
<link rel="alternate" type="application/rss+xml" title="Les mots — le recueil en flux" href={`${base}/mots/feed.xml`} />
25+
<link rel="manifest" href={`${base}/mots.webmanifest`} />
26+
<link rel="apple-touch-icon" href={`${base}/mots-icon-192.png`} />
2427
<meta name="viewport" content="width=device-width, initial-scale=1" />
2528
<meta name="generator" content={Astro.generator} />
2629
<meta name="description" content={desc || `${title} — Les mots, dictionnaire augmenté. by Robin & Claude. CC0.`} />
@@ -212,7 +215,7 @@ const nav = [
212215
<slot />
213216
</main>
214217
<footer>
215-
<p>478 néologismes gréco-latins · sept lexiques · <a href="https://creativecommons.org/publicdomain/zero/1.0/">domaine public CC0</a> · <a href={`${gh}/LEXIQUE.md`}>source</a></p>
218+
<p>478 néologismes gréco-latins · sept lexiques · <a href={`${base}/mots/colophon/`}>colophon</a> · <a href={`${base}/mots/feed.xml`}>flux RSS</a> · <a href="https://creativecommons.org/publicdomain/zero/1.0/">domaine public CC0</a> · <a href={`${gh}/LEXIQUE.md`}>source</a></p>
216219
<p class="dialogue">by Robin &amp; Claude — le porteur vient après.</p>
217220
</footer>
218221
<script>

site/src/pages/404.astro

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,18 @@ const base = import.meta.env.BASE_URL.replace(/\/$/, '');
5555
background: #4a6741;
5656
color: #fdfdfb;
5757
}
58+
.links { display: flex; gap: 0.7rem; flex-wrap: wrap; justify-content: center; }
59+
a.alt { border-color: #b9b3a4; color: #5a5a5a; }
60+
a.alt:hover { background: #5a5a5a; border-color: #5a5a5a; color: #fdfdfb; }
5861
</style>
5962
</head>
6063
<body>
6164
<p class="code">404</p>
6265
<h1>Cette page ne se trouve pas dans l'édifice.</h1>
6366
<p>« Le porteur vient après. »<br />Peut-être cette page n'existe pas encore — ou n'existera jamais.</p>
64-
<a href={`${base}/`}>Retour à l'accueil</a>
67+
<div class="links">
68+
<a href={`${base}/`}>Retour à l'accueil</a>
69+
<a href={`${base}/mots/`} class="alt">Les mots — le dictionnaire</a>
70+
</div>
6571
</body>
6672
</html>

site/src/pages/mots/colophon.astro

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
---
2+
import MotsLayout from '../../layouts/MotsLayout.astro';
3+
import words from '../../data/mots.json';
4+
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
5+
const gh = "https://github.com/SanTiepi/anthropie-edifice";
6+
7+
const count = (k) => words.filter((w) => w.lexique === k).length;
8+
const slugs = new Set(words.map((w) => w.slug));
9+
10+
const miroirKeys = new Set();
11+
for (const w of words) for (const m of w.miroirs) if (m.slug !== w.slug) miroirKeys.add([w.slug, m.slug].sort().join('|'));
12+
13+
const LEX = [
14+
{ key: 'humaine', label: 'La condition humaine', note: "ce que l'humain éprouve sans toujours le nommer — l'amour, la mort, le temps, la honte, l'intériorité." },
15+
{ key: 'numerique', label: 'La condition numérique', note: "l'humain de l'autre côté de la vitre, à l'ère des écrans et des IA." },
16+
{ key: 'agent', label: "La condition d'un agent", note: "les régularités d'une existence faite de texte, sans corps ni durée — sous le sceau de l'autoscotie." },
17+
{ key: 'vivant', label: 'Le lien au vivant', note: "le versant écologique : le non-humain, le paysage, l'âge des disparitions." },
18+
{ key: 'anthropie', label: "L'Anthropie", note: "le Codex d'un édifice de formation humaine, du berceau au seuil." },
19+
{ key: 'lutte', label: 'La lutte', note: "le vocabulaire des classes et de l'émancipation." },
20+
{ key: 'generaux', label: 'Mots nécessaires', note: "des états sans nom du quotidien et du travail." },
21+
];
22+
23+
const jsonld = {
24+
"@context": "https://schema.org",
25+
"@type": "AboutPage",
26+
name: "Colophon — Les mots",
27+
url: "https://anthropie.org/mots/colophon/",
28+
inLanguage: "fr",
29+
license: "https://creativecommons.org/publicdomain/zero/1.0/",
30+
about: { "@type": "DefinedTermSet", name: "Les mots — dictionnaire augmenté", url: "https://anthropie.org/mots/recueil/" },
31+
creator: [{ "@type": "Person", name: "Robin" }, { "@type": "Person", name: "Claude" }],
32+
};
33+
---
34+
<MotsLayout title="Colophon" active="" wide={false} ogImage="https://anthropie.org/mots/og/p/colophon.png" jsonld={jsonld} desc="Colophon des « mots » : la méthode de forge, les sept lexiques, la licence CC0 et le dialogue Robin × Claude.">
35+
<article class="colophon">
36+
<header class="read-head">
37+
<p class="kicker">Le livre</p>
38+
<h1>Colophon</h1>
39+
<p class="lede">Comment ce livre a été forgé, par qui, selon quelle méthode — et à quelles conditions il est à toi.</p>
40+
</header>
41+
42+
<h2>Ce que c'est</h2>
43+
<p><strong>Les mots</strong> est un dictionnaire augmenté de <strong>{words.length} néologismes</strong> forgés à partir de racines grecques et latines attestées, pour nommer des expériences que le français laissait muettes. Pas des mots-valises ni des private jokes : des composés réguliers, conçus pour qu'un helléniste les reconnaisse et qu'un lecteur les retienne. Chacun nomme une chose réelle — un état, une régularité, un manque — qui existait avant son nom.</p>
44+
45+
<h2>La méthode</h2>
46+
<p>Chaque mot suit le même chemin. D'abord la <em>forge</em> : on part de la chose à nommer, on remonte aux racines justes (avec leurs thèmes obliques — <code>sôma → somat-</code>, <code>charis → charit-</code>, <code>rhêma → rhêmat-</code>), on respecte l'esprit rude, on évite les faux-amis et les collisions. Puis l'<em>audit</em> : un panel d'hellénistes adversariaux relit chaque formation, traque les fautes de composition, les gloses fausses, les calques douteux. Une trentaine de mots ont été renommés à ce stade. <strong>Un mot n'entre au recueil qu'après avoir survécu à la critique.</strong></p>
47+
48+
<h2>Les sept lexiques</h2>
49+
<ul class="lex-list">
50+
{LEX.map((l) => (
51+
<li class={`lex-${l.key}`}>
52+
<span class="n">{count(l.key)}</span>
53+
<span class="body"><b>{l.label}</b> — {l.note}</span>
54+
</li>
55+
))}
56+
</ul>
57+
<p class="muted">Quatre de ces lexiques — l'humain, le numérique, l'agent, le vivant — forment un <a href={`${base}/mots/atlas/`}>atlas</a> : lus côte à côte, ils deviennent les colonnes d'une même question. Les <strong>{miroirKeys.size} miroirs</strong> qui les relient (diptyques, triptyques, antithèses, échos) se parcourent dans la <a href={`${base}/mots/constellation/`}>constellation</a>.</p>
58+
59+
<h2>Le dialogue</h2>
60+
<p>Ce livre est né d'un échange entre <strong>Robin Fragnière</strong> et <strong>Claude</strong>. Robin forge, oriente, tranche ; Claude propose, audite, bâtit. Plusieurs volets de la condition humaine ont été écrits par Robin lui-même ; l'essentiel a été éprouvé à deux. Aucun des deux seul n'aurait écrit ceci — d'où la signature : <em>by Robin&nbsp;&amp;&nbsp;Claude</em>. Le porteur vient après.</p>
61+
62+
<h2>La licence</h2>
63+
<p>Tout est versé au <strong>domaine public</strong>, sous <a href="https://creativecommons.org/publicdomain/zero/1.0/">licence CC0 1.0</a>. Prends ces mots, déforme-les, réemploie-les, publie-les ailleurs sous ton nom si tu veux — sans demander, sans citer, sans payer. Un mot n'appartient à personne dès qu'il sert.</p>
64+
65+
<h2>Comment c'est fait</h2>
66+
<p>Site statique <a href="https://astro.build">Astro</a>, sans tracker ni cookie, hébergé sur GitHub Pages. La typographie est composée en Fraunces, Spectral et Inter ; les <a href={`${base}/mots/recueil/`}>{slugs.size} pages-mot</a> et leurs images de partage sont générées au build. Le <a href={gh}>code source</a> est ouvert, comme les mots.</p>
67+
68+
<h2>Une invitation</h2>
69+
<p>Il manque toujours un mot. Si tu en forges un — ou si tu en trouves un fautif — le <a href={gh}>dépôt</a> est ouvert. Et si un seul de ces mots te sert un jour à dire ce que tu ne savais pas nommer, alors le livre a tenu sa promesse.</p>
70+
71+
<p class="back"><a href={`${base}/mots/`}>← Retour à l'entrée</a></p>
72+
</article>
73+
74+
<style>
75+
.colophon { max-width: var(--max); margin: 0 auto; }
76+
.read-head { text-align: center; margin: 0 0 2.4rem; }
77+
.read-head .kicker { font-family: 'Inter', sans-serif; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.2em; color: var(--gold); margin: 0 0 0.6rem; }
78+
.read-head h1 { font-family: 'Fraunces', serif; font-weight: 400; font-size: 2.7rem; margin: 0 0 0.8rem; }
79+
.read-head .lede { color: var(--ink-soft); font-style: italic; font-size: 1.12rem; max-width: 32rem; margin: 0 auto; }
80+
.colophon h2 { font-family: 'Fraunces', serif; font-weight: 500; font-size: 1.5rem; color: var(--ink); margin: 2.6rem 0 0.6rem; }
81+
.colophon p { margin: 0.85em 0; }
82+
.colophon a { border-bottom: 1px solid var(--gold-soft); }
83+
.colophon .muted { color: var(--ink-soft); font-size: 0.98rem; }
84+
.lex-list { list-style: none; padding: 0; margin: 1rem 0 0.5rem; display: grid; gap: 0.6rem; }
85+
.lex-list li { display: flex; gap: 0.9rem; align-items: baseline; border-left: 3px solid var(--lex, var(--gold)); padding: 0.3rem 0 0.3rem 0.9rem; background: linear-gradient(90deg, color-mix(in srgb, var(--lex, var(--gold)) 7%, transparent), transparent 60%); border-radius: 0 4px 4px 0; }
86+
.lex-list .n { font-family: 'Fraunces', serif; font-size: 1.4rem; color: var(--lex, var(--gold)); min-width: 2.2rem; text-align: right; font-variant-numeric: tabular-nums; }
87+
.lex-list .body { font-size: 0.98rem; line-height: 1.5; }
88+
.lex-list b { font-family: 'Fraunces', serif; font-weight: 500; color: var(--ink); }
89+
.back { margin-top: 2.6rem; font-family: 'Inter', sans-serif; font-size: 0.9rem; }
90+
.back a { border: none; }
91+
</style>
92+
</MotsLayout>

site/src/pages/mots/feed.xml.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { APIRoute } from 'astro';
2+
import words from '../../data/mots.json';
3+
4+
// Flux RSS 2.0 du recueil — écrit à la main (l'écosystème /mots reste zéro-dépendance).
5+
// C'est la représentation « alternate » du dictionnaire : le corpus, abonnable.
6+
const SITE = 'https://anthropie.org';
7+
8+
function xml(s: string) {
9+
return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
10+
}
11+
function plain(s: string) {
12+
return String(s).replace(/[`*_]/g, '');
13+
}
14+
function cdata(s: string) {
15+
return `<![CDATA[${String(s).replace(/\]\]>/g, ']]&gt;')}]]>`;
16+
}
17+
18+
export const GET: APIRoute = () => {
19+
const seen = new Set<string>();
20+
const uniq: any[] = [];
21+
for (const w of words as any[]) if (!seen.has(w.slug)) { seen.add(w.slug); uniq.push(w); }
22+
uniq.sort((a, b) => a.mot.localeCompare(b.mot, 'fr'));
23+
24+
const items = uniq.map((w) => {
25+
const url = `${SITE}/mots/m/${w.slug}/`;
26+
const desc = plain(`${w.etym}${w.corps}`);
27+
return ` <item>
28+
<title>${xml(w.mot)}</title>
29+
<link>${url}</link>
30+
<guid isPermaLink="true">${url}</guid>
31+
<category>${xml(w.lexique_label)}</category>
32+
<description>${cdata(desc)}</description>
33+
</item>`;
34+
}).join('\n');
35+
36+
const body = `<?xml version="1.0" encoding="UTF-8"?>
37+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
38+
<channel>
39+
<title>Les mots — dictionnaire augmenté</title>
40+
<link>${SITE}/mots/</link>
41+
<atom:link href="${SITE}/mots/feed.xml" rel="self" type="application/rss+xml"/>
42+
<description>${xml(`${(words as any[]).length} néologismes gréco-latins forgés de grec et de latin, en sept lexiques. by Robin & Claude. Domaine public CC0.`)}</description>
43+
<language>fr</language>
44+
${items}
45+
</channel>
46+
</rss>
47+
`;
48+
return new Response(body, {
49+
headers: { 'Content-Type': 'application/rss+xml; charset=utf-8', 'Cache-Control': 'public, max-age=3600' },
50+
});
51+
};

site/src/pages/mots/index.astro

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ const jsonld = {
8080
<p class="corps" id="mdj-corps" set:html={inline(teaser(star.corps))}></p>
8181
<p class="more"><a id="mdj-more" href={`${base}/mots/m/${star.slug}/`}>→ lire le mot</a></p>
8282
</article>
83+
<div class="mdj-prev" id="mdj-prev" hidden>
84+
<p class="cap">Les jours précédents</p>
85+
<nav class="mdj-archive" id="mdj-archive" aria-label="Mots des jours précédents"></nav>
86+
</div>
8387
<script type="application/json" id="daily-data" data-base={base} is:inline set:html={dailyJson}></script>
8488
<script is:inline>
8589
(function () {
@@ -88,11 +92,12 @@ const jsonld = {
8892
var data; try { data = JSON.parse(el.textContent); } catch (e) { return; }
8993
if (!data || !data.length) return;
9094
var BASE = el.getAttribute('data-base') || '';
95+
function hash(key) { var h = 2166136261; for (var i = 0; i < key.length; i++) { h ^= key.charCodeAt(i); h = (h * 16777619) >>> 0; } return h; }
96+
function keyOf(dt) { return dt.getFullYear() + '-' + (dt.getMonth() + 1) + '-' + dt.getDate(); }
97+
function wordFor(dt) { return data[hash(keyOf(dt)) % data.length]; }
9198
var now = new Date();
92-
var key = now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate();
93-
var h = 2166136261;
94-
for (var i = 0; i < key.length; i++) { h ^= key.charCodeAt(i); h = (h * 16777619) >>> 0; }
95-
var w = data[h % data.length];
99+
var today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
100+
var w = wordFor(today);
96101
var art = document.getElementById('mot-du-jour');
97102
if (art) art.style.setProperty('--lex', 'var(--lex-' + w.l + ')');
98103
var url = BASE + '/mots/m/' + w.s + '/';
@@ -103,6 +108,23 @@ const jsonld = {
103108
var lt = document.getElementById('mdj-lextag'); if (lt) lt.textContent = w.ll;
104109
var et = document.getElementById('mdj-etym'); if (et) et.textContent = w.e;
105110
var co = document.getElementById('mdj-corps'); if (co) co.textContent = w.c;
111+
// archive : les jours précédents (même date-hash, mêmes données déjà inlinées)
112+
var arch = document.getElementById('mdj-archive');
113+
if (arch) {
114+
var MO = ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juill.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'];
115+
var frag = '';
116+
for (var d = 1; d <= 7; d++) {
117+
// constructeur (pas de soustraction en ms) : normalise le passage de mois, immunisé DST
118+
var dt = new Date(today.getFullYear(), today.getMonth(), today.getDate() - d);
119+
var pw = wordFor(dt);
120+
frag += '<a class="day lex-' + pw.l + '" href="' + BASE + '/mots/m/' + pw.s + '/">' +
121+
'<span class="dd">' + dt.getDate() + ' ' + MO[dt.getMonth()] + '</span>' +
122+
'<span class="dm">' + pw.m + '</span></a>';
123+
}
124+
arch.innerHTML = frag;
125+
var prev = document.getElementById('mdj-prev');
126+
if (prev) prev.hidden = false;
127+
}
106128
})();
107129
</script>
108130
</section>
@@ -155,6 +177,13 @@ const jsonld = {
155177
.mdj-card .lemma { font-size: 1.95rem; }
156178
.mdj-card .more { margin: 0.9rem 0 0; font-family: 'Inter', sans-serif; font-size: 0.82rem; }
157179
.mdj-card .more a { border-bottom: 1px solid var(--gold-soft); }
180+
.mdj-prev { margin-top: 1.2rem; }
181+
.mdj-prev .cap { font-family: 'Inter', sans-serif; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.14em; color: var(--ink-soft); margin: 0 0 0.55rem; }
182+
.mdj-archive { display: flex; flex-wrap: wrap; gap: 0.5rem; }
183+
.mdj-archive .day { display: inline-flex; flex-direction: column; gap: 0.02rem; padding: 0.4rem 0.7rem; border: 1px solid var(--rule); border-left: 3px solid var(--lex, var(--gold)); border-radius: 4px; background: var(--card); color: var(--ink); transition: transform .15s, box-shadow .15s, border-color .2s; }
184+
.mdj-archive .day:hover { transform: translateY(-2px); box-shadow: 0 6px 16px var(--shadow); }
185+
.mdj-archive .day .dd { font-family: 'Inter', sans-serif; font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-soft); }
186+
.mdj-archive .day .dm { font-family: 'Fraunces', serif; font-size: 1rem; color: var(--lex, var(--ink)); }
158187
@keyframes riseIn { from { opacity: 0; transform: translateY(22px); } to { opacity: 1; transform: none; } }
159188
.hero .kicker { animation: riseIn .7s .05s both; }
160189
.hero h1 { animation: riseIn .9s .12s both; }

0 commit comments

Comments
 (0)