@agent-cms/agentcms 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/LICENSE +143 -0
  2. package/README.md +1119 -2
  3. package/changelog.json +20 -0
  4. package/dist/admin/admin-files.json +48 -0
  5. package/dist/admin/assets/CodeEditor-DcFzGUt8.js +24 -0
  6. package/dist/admin/assets/flexweg-logo-CdtuqAFA.png +0 -0
  7. package/dist/admin/assets/index-CLZ1gQZl.js +5460 -0
  8. package/dist/admin/assets/index-DMcGogR2.css +1 -0
  9. package/dist/admin/assets/sql-wasm-C1U8OeUW.wasm +0 -0
  10. package/dist/admin/assets/sql-wasm-CYl3LXH_.js +2 -0
  11. package/dist/admin/assets/sql-wasm-V5jTDcrT.js +1 -0
  12. package/dist/admin/config.js +11 -0
  13. package/dist/admin/external.default.json +72 -0
  14. package/dist/admin/index.html +116 -0
  15. package/dist/admin/plugins/core-seo/bundle.js +55 -0
  16. package/dist/admin/plugins/core-seo/manifest.json +7 -0
  17. package/dist/admin/plugins/flexweg-archives/bundle.js +1414 -0
  18. package/dist/admin/plugins/flexweg-archives/manifest.json +7 -0
  19. package/dist/admin/plugins/flexweg-multilang/bundle.js +1187 -0
  20. package/dist/admin/plugins/flexweg-multilang/manifest.json +7 -0
  21. package/dist/admin/plugins/flexweg-rss/bundle.js +1354 -0
  22. package/dist/admin/plugins/flexweg-rss/manifest.json +7 -0
  23. package/dist/admin/plugins/flexweg-search/bundle.js +1243 -0
  24. package/dist/admin/plugins/flexweg-search/manifest.json +7 -0
  25. package/dist/admin/plugins/flexweg-sitemaps/bundle.js +1293 -0
  26. package/dist/admin/plugins/flexweg-sitemaps/manifest.json +7 -0
  27. package/dist/admin/runtime/cms-runtime.js +164 -0
  28. package/dist/admin/runtime/react-dom-client.js +13 -0
  29. package/dist/admin/runtime/react-dom.js +16 -0
  30. package/dist/admin/runtime/react-i18next.js +21 -0
  31. package/dist/admin/runtime/react-jsx-runtime.js +17 -0
  32. package/dist/admin/runtime/react.js +55 -0
  33. package/dist/admin/themes/corporate/bundle.js +14465 -0
  34. package/dist/admin/themes/corporate/manifest.json +7 -0
  35. package/dist/admin/themes/corporate/theme.css +2 -0
  36. package/dist/admin/themes/magazine/bundle.js +10229 -0
  37. package/dist/admin/themes/magazine/manifest.json +7 -0
  38. package/dist/admin/themes/magazine/theme.css +2 -0
  39. package/dist/admin/themes/marketplace-core/bundle.js +11213 -0
  40. package/dist/admin/themes/marketplace-core/manifest.json +7 -0
  41. package/dist/admin/themes/marketplace-core/theme.css +1613 -0
  42. package/dist/admin/themes/portfolio/bundle.js +9195 -0
  43. package/dist/admin/themes/portfolio/manifest.json +7 -0
  44. package/dist/admin/themes/portfolio/theme.css +2 -0
  45. package/dist/admin/themes/storefront/bundle.js +13172 -0
  46. package/dist/admin/themes/storefront/manifest.json +7 -0
  47. package/dist/admin/themes/storefront/theme.css +2 -0
  48. package/dist/agent/agentcms.mjs +1711 -0
  49. package/dist/agent/chunks/hosting-D_uj-Mt1.mjs +117 -0
  50. package/dist/agent/chunks/index-CrvEoyi0.mjs +3608 -0
  51. package/dist/agent/chunks/install-DkCCcKAO.mjs +577 -0
  52. package/dist/agent/chunks/plan-EYKegfhp.mjs +245 -0
  53. package/dist/agent/chunks/registry-lkfb_E9O.mjs +53517 -0
  54. package/dist/agent/chunks/siteAdd-BqcYt8nu.mjs +118 -0
  55. package/dist/agent/chunks/sql-wasm-GjJDiefO.mjs +10 -0
  56. package/dist/agent/chunks/sqljs-DjSCStJ0.mjs +20 -0
  57. package/dist/agent/vendor/sql-wasm.cjs +188 -0
  58. package/dist/theme-assets/corporate-menu.js +266 -0
  59. package/dist/theme-assets/corporate-posts.js +571 -0
  60. package/dist/theme-assets/corporate.css +2 -0
  61. package/dist/theme-assets/default-menu.js +266 -0
  62. package/dist/theme-assets/default-posts.js +378 -0
  63. package/dist/theme-assets/default.css +1 -0
  64. package/dist/theme-assets/magazine-menu.js +227 -0
  65. package/dist/theme-assets/magazine-posts.js +361 -0
  66. package/dist/theme-assets/magazine.css +2 -0
  67. package/dist/theme-assets/portfolio-filters.js +104 -0
  68. package/dist/theme-assets/portfolio-menu.js +240 -0
  69. package/dist/theme-assets/portfolio-posts.js +358 -0
  70. package/dist/theme-assets/portfolio.css +2 -0
  71. package/dist/theme-assets/storefront-catalog.js +504 -0
  72. package/dist/theme-assets/storefront-menu.js +239 -0
  73. package/dist/theme-assets/storefront-posts.js +404 -0
  74. package/dist/theme-assets/storefront.css +2 -0
  75. package/dist/version.json +9 -0
  76. package/package.json +101 -4
  77. package/plugin/.claude-plugin/plugin.json +16 -0
  78. package/plugin/skills/agentcms-install/SKILL.md +110 -0
  79. package/plugin/skills/agentcms-install/references/cloudflare.md +15 -0
  80. package/plugin/skills/agentcms-install/references/flexweg.md +11 -0
  81. package/plugin/skills/agentcms-install/references/github.md +67 -0
  82. package/plugin/skills/agentcms-install/references/gitlab.md +15 -0
  83. package/plugin/skills/agentcms-install/references/s3.md +81 -0
  84. package/plugin/skills/agentcms-manage/SKILL.md +92 -0
@@ -0,0 +1,378 @@
1
+ /**
2
+ * Default theme — runtime sidebar loader.
3
+ *
4
+ * Contract:
5
+ * - Templates declare empty (or fallback-rendered) containers via
6
+ * `data-cms-*` attributes.
7
+ * - This script fetches `/data/posts.json` AND `/data/authors.json`
8
+ * once on DOMContentLoaded and populates each container with the
9
+ * right slice of data — so editing a post sibling list, an author
10
+ * bio, or an author avatar takes effect on every existing post
11
+ * HTML without re-publishing.
12
+ * - Failure is silent: if a fetch fails (404 because no publish has
13
+ * happened yet, or transient network), the container either stays
14
+ * hidden (related) or keeps its publish-time fallback content
15
+ * (author bio).
16
+ *
17
+ * Currently supports:
18
+ * [data-cms-related] — list of posts in the same category as the
19
+ * current one, with a "latest articles" fallback when the category
20
+ * has no other matches.
21
+ * Reads:
22
+ * data-cms-current-id current post id (excluded from list)
23
+ * data-cms-term-id primary term id to filter by
24
+ * data-cms-limit max items (default 3)
25
+ * data-cms-label heading when category match exists
26
+ * data-cms-fallback-label heading when falling back to latest
27
+ *
28
+ * [data-cms-author-bio] — overrides the publish-time-rendered author
29
+ * bio block with the current /authors.json data, so editing the
30
+ * bio / name / avatar in /users updates every post sidebar
31
+ * immediately on the next page load.
32
+ * Reads:
33
+ * data-cms-author-id user id to look up in authors.json
34
+ * data-cms-eyebrow section eyebrow ("About the author")
35
+ *
36
+ * Adding a new widget: query for its `[data-cms-*]` selector, render
37
+ * into the host, and add a paint pass below.
38
+ */
39
+ (function () {
40
+ "use strict";
41
+
42
+ // ─── Base path ────────────────────────────────────────────────────
43
+ // Sites served from a sub-folder (e.g. GitHub Pages project sites)
44
+ // carry <meta name="x-cms-base" content="/repo/"> in <head>; no meta
45
+ // means the site lives at the domain root. URLs built at runtime from
46
+ // JSON data go through withBase() so they land under that folder.
47
+ var BASE = (function () {
48
+ var meta = document.querySelector('meta[name="x-cms-base"]');
49
+ var b = (meta && meta.getAttribute("content")) || "/";
50
+ if (b.charAt(0) !== "/") b = "/" + b;
51
+ if (b.charAt(b.length - 1) !== "/") b += "/";
52
+ return b;
53
+ })();
54
+
55
+ function withBase(url) {
56
+ if (BASE === "/" || !url) return url;
57
+ // Absolute URLs (http:, mailto:, data:, …), protocol-relative and
58
+ // fragment-only links pass through.
59
+ if (/^([a-z][a-z0-9+.-]*:|\/\/|#)/i.test(url)) return url;
60
+ if (url.indexOf(BASE) === 0 || url === BASE.slice(0, -1)) return url;
61
+ return BASE + (url.charAt(0) === "/" ? url.slice(1) : url);
62
+ }
63
+
64
+ // "/repo/fr/x.html" → "/fr/x.html" so path parsing ignores the base.
65
+ function stripBase(pathname) {
66
+ if (BASE === "/") return pathname;
67
+ if (pathname.indexOf(BASE) === 0) return "/" + pathname.slice(BASE.length);
68
+ if (pathname === BASE.slice(0, -1)) return "/";
69
+ return pathname;
70
+ }
71
+
72
+ // ─── Social icons ─────────────────────────────────────────────────
73
+ // Brand SVG paths + display labels for the curated SocialNetwork
74
+ // list. Mirror in core/socialIcons.tsx (and each other theme's
75
+ // posts-loader.js) when adding a network. Comment-pinned so the
76
+ // duplication is explicit.
77
+ var SOCIAL_ICON_PATHS = {
78
+ twitter: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z",
79
+ linkedin: "M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z",
80
+ instagram: "M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z",
81
+ mastodon: "M23.193 7.88c0-5.207-3.411-6.733-3.411-6.733C18.062.357 15.108.025 12.041 0h-.076c-3.068.025-6.02.357-7.74 1.147 0 0-3.412 1.526-3.412 6.733 0 1.193-.023 2.619.015 4.13.124 5.092.934 10.11 5.641 11.355 2.17.574 4.034.695 5.535.612 2.722-.151 4.25-.972 4.25-.972l-.09-1.975s-1.945.613-4.129.539c-2.165-.074-4.449-.233-4.799-2.891a5.499 5.499 0 0 1-.048-.745s2.125.52 4.817.643c1.646.075 3.19-.097 4.758-.283 3.007-.359 5.625-2.212 5.954-3.905.52-2.667.477-6.508.477-6.508zm-4.024 6.709h-2.497V8.469c0-1.29-.543-1.944-1.628-1.944-1.2 0-1.802.776-1.802 2.312v3.349h-2.484v-3.35c0-1.536-.601-2.312-1.802-2.312-1.085 0-1.628.655-1.628 1.945v6.119H4.831V8.285c0-1.29.328-2.313.987-3.07.68-.756 1.57-1.144 2.674-1.144 1.278 0 2.246.491 2.886 1.474L12 6.585l.622-1.04c.64-.983 1.608-1.474 2.886-1.474 1.104 0 1.994.388 2.674 1.144.658.757.986 1.78.986 3.07v6.304z",
82
+ bluesky: "M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.137-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.952 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 0 1-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8Z",
83
+ github: "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12",
84
+ website: "M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95a15.65 15.65 0 0 0-1.38-3.56A8.03 8.03 0 0 1 18.92 8zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56A7.987 7.987 0 0 1 5.08 16zm2.95-8H5.08a7.987 7.987 0 0 1 4.33-3.56A15.65 15.65 0 0 0 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95a8.03 8.03 0 0 1-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z",
85
+ };
86
+ var SOCIAL_ICON_LABELS = {
87
+ twitter: "Twitter / X",
88
+ linkedin: "LinkedIn",
89
+ instagram: "Instagram",
90
+ mastodon: "Mastodon",
91
+ bluesky: "Bluesky",
92
+ github: "GitHub",
93
+ website: "Website",
94
+ };
95
+ function socialIconSvg(network) {
96
+ var path = SOCIAL_ICON_PATHS[network];
97
+ if (!path) return "";
98
+ return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true"><path d="' + path + '"/></svg>';
99
+ }
100
+ function socialIconLabel(network) {
101
+ return SOCIAL_ICON_LABELS[network] || network;
102
+ }
103
+
104
+ function fetchJson(path) {
105
+ return fetch(withBase(path), { credentials: "omit" })
106
+ .then(function (res) {
107
+ if (!res.ok) throw new Error("HTTP " + res.status);
108
+ return res.json();
109
+ })
110
+ .catch(function (err) {
111
+ console.warn("[cms] " + path + " load failed:", err);
112
+ return null;
113
+ });
114
+ }
115
+
116
+ // ─── Related posts ────────────────────────────────────────────────
117
+
118
+ function renderRelatedItem(entry) {
119
+ var li = document.createElement("li");
120
+ li.className = "related-posts__item";
121
+ var a = document.createElement("a");
122
+ a.href = withBase("/" + entry.url);
123
+ if (entry.category && entry.category.name) {
124
+ var eyebrow = document.createElement("p");
125
+ eyebrow.className = "related-posts__eyebrow";
126
+ eyebrow.textContent = entry.category.name;
127
+ a.appendChild(eyebrow);
128
+ }
129
+ var title = document.createElement("p");
130
+ title.className = "related-posts__title-line";
131
+ title.textContent = entry.title || "";
132
+ a.appendChild(title);
133
+ if (entry.dateLabel) {
134
+ var date = document.createElement("p");
135
+ date.className = "related-posts__date";
136
+ date.textContent = entry.dateLabel;
137
+ a.appendChild(date);
138
+ }
139
+ li.appendChild(a);
140
+ return li;
141
+ }
142
+
143
+ // Populates a `[data-cms-related]` container. Strategy:
144
+ // 1. If `data-cms-term-id` is set, try same-category siblings first
145
+ // and label the section with `data-cms-label` ("Continue reading").
146
+ // 2. If that yields zero matches (fresh category, only-post case,
147
+ // or no term-id at all), fall back to the most-recent posts and
148
+ // label with `data-cms-fallback-label` ("Latest articles").
149
+ // 3. If even the fallback is empty (truly empty corpus), hide.
150
+ // Pages are skipped in both passes — sidebar widgets target article
151
+ // flow content, not timeless pages (About, Contact).
152
+ function renderRelated(host, posts) {
153
+ var termId = host.getAttribute("data-cms-term-id") || "";
154
+ var currentId = host.getAttribute("data-cms-current-id") || "";
155
+ var limit = parseInt(host.getAttribute("data-cms-limit") || "3", 10);
156
+ var label = host.getAttribute("data-cms-label") || "Continue reading";
157
+ var fallbackLabel =
158
+ host.getAttribute("data-cms-fallback-label") || "Latest articles";
159
+
160
+ var matching = [];
161
+ var heading = label;
162
+
163
+ if (termId) {
164
+ for (var i = 0; i < posts.length && matching.length < limit; i++) {
165
+ var p = posts[i];
166
+ if (p.id === currentId) continue;
167
+ if (p.type !== "post") continue;
168
+ if (p.primaryTermId !== termId) continue;
169
+ matching.push(p);
170
+ }
171
+ }
172
+
173
+ if (matching.length === 0) {
174
+ heading = fallbackLabel;
175
+ for (var j = 0; j < posts.length && matching.length < limit; j++) {
176
+ var pp = posts[j];
177
+ if (pp.id === currentId) continue;
178
+ if (pp.type !== "post") continue;
179
+ matching.push(pp);
180
+ }
181
+ }
182
+
183
+ if (matching.length === 0) {
184
+ host.setAttribute("hidden", "");
185
+ return;
186
+ }
187
+
188
+ host.removeAttribute("hidden");
189
+ host.innerHTML = "";
190
+ var headingEl = document.createElement("h4");
191
+ headingEl.className = "related-posts__title";
192
+ headingEl.textContent = heading;
193
+ host.appendChild(headingEl);
194
+ var list = document.createElement("ul");
195
+ list.className = "related-posts__list";
196
+ matching.forEach(function (entry) {
197
+ list.appendChild(renderRelatedItem(entry));
198
+ });
199
+ host.appendChild(list);
200
+ }
201
+
202
+ // ─── Author bio ───────────────────────────────────────────────────
203
+
204
+ function authorInitials(name) {
205
+ if (!name) return "?";
206
+ var parts = name.trim().split(/\s+/);
207
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
208
+ var first = parts[0][0] || "";
209
+ var last = parts[parts.length - 1][0] || "";
210
+ return (first + last).toUpperCase();
211
+ }
212
+
213
+ // Builds the avatar element. Renders an <img> when a URL is given,
214
+ // otherwise a typographic initials placeholder — same shape and
215
+ // class chain that AuthorAvatar.tsx produces at publish time, so the
216
+ // existing CSS applies seamlessly.
217
+ function authorAvatar(name, url) {
218
+ if (url) {
219
+ var img = document.createElement("img");
220
+ img.className = "author-avatar author-avatar--lg author-avatar--image";
221
+ img.src = withBase(url);
222
+ img.alt = name || "";
223
+ img.loading = "lazy";
224
+ return img;
225
+ }
226
+ var span = document.createElement("span");
227
+ span.className = "author-avatar author-avatar--lg";
228
+ span.setAttribute("aria-hidden", "true");
229
+ span.textContent = authorInitials(name);
230
+ return span;
231
+ }
232
+
233
+ // Replaces a `[data-cms-author-bio]` host with the current author
234
+ // data. The host is rendered with `hidden` at publish time and gets
235
+ // its content (and visibility) restored here — so missing data
236
+ // (user deleted, /authors.json unreachable) leaves the section
237
+ // invisible rather than showing an empty card.
238
+ function renderAuthorBio(host, authors) {
239
+ var authorId = host.getAttribute("data-cms-author-id") || "";
240
+ if (!authorId) return;
241
+ var entry = authors[authorId];
242
+ if (!entry) return;
243
+
244
+ var eyebrowText = host.getAttribute("data-cms-eyebrow") || "About the author";
245
+
246
+ host.innerHTML = "";
247
+
248
+ var eyebrow = document.createElement("p");
249
+ eyebrow.className = "author-bio__eyebrow";
250
+ eyebrow.textContent = eyebrowText;
251
+ host.appendChild(eyebrow);
252
+
253
+ var head = document.createElement("div");
254
+ head.className = "author-bio__head";
255
+ head.appendChild(authorAvatar(entry.displayName, entry.avatar));
256
+ var nameWrap = document.createElement("div");
257
+ // Author name is rendered as an <a> linking to the author's
258
+ // archive page (/author/<slug>.html) when a URL is available.
259
+ // Falls back to a non-clickable <p> if the entry doesn't carry
260
+ // one — e.g. the JSON pre-dates this feature.
261
+ var nameEl;
262
+ if (entry.url) {
263
+ nameEl = document.createElement("a");
264
+ nameEl.href = withBase("/" + entry.url);
265
+ } else {
266
+ nameEl = document.createElement("p");
267
+ }
268
+ nameEl.className = "author-bio__name";
269
+ nameEl.textContent = entry.displayName || "";
270
+ nameWrap.appendChild(nameEl);
271
+ // Title (e.g. "Journaliste") replaces the legacy email line.
272
+ // Hidden when not set — keeps the design clean.
273
+ if (entry.title) {
274
+ var role = document.createElement("p");
275
+ role.className = "author-bio__role";
276
+ role.textContent = entry.title;
277
+ nameWrap.appendChild(role);
278
+ }
279
+ head.appendChild(nameWrap);
280
+ host.appendChild(head);
281
+
282
+ if (entry.bio) {
283
+ var bio = document.createElement("p");
284
+ bio.className = "author-bio__bio";
285
+ bio.textContent = entry.bio;
286
+ host.appendChild(bio);
287
+ }
288
+
289
+ // Visible socials — already filtered server-side, the loader just
290
+ // renders the icon list. Hidden entirely when none.
291
+ if (entry.socials && entry.socials.length) {
292
+ var socials = document.createElement("div");
293
+ socials.className = "author-bio__socials";
294
+ for (var i = 0; i < entry.socials.length; i++) {
295
+ var s = entry.socials[i];
296
+ var a = document.createElement("a");
297
+ a.className = "author-bio__social-link";
298
+ a.href = s.url;
299
+ a.target = "_blank";
300
+ a.rel = "noopener noreferrer";
301
+ a.setAttribute("aria-label", socialIconLabel(s.network));
302
+ a.title = socialIconLabel(s.network);
303
+ a.innerHTML = socialIconSvg(s.network);
304
+ socials.appendChild(a);
305
+ }
306
+ host.appendChild(socials);
307
+ }
308
+
309
+ host.removeAttribute("hidden");
310
+ }
311
+
312
+ // ─── Paint passes ─────────────────────────────────────────────────
313
+
314
+ function paintRelated(postsBlob) {
315
+ if (!postsBlob || !Array.isArray(postsBlob.posts)) return;
316
+ document.querySelectorAll("[data-cms-related]").forEach(function (host) {
317
+ renderRelated(host, postsBlob.posts);
318
+ });
319
+ }
320
+
321
+ function paintAuthorBios(authorsBlob) {
322
+ if (!authorsBlob || !authorsBlob.authors) return;
323
+ document.querySelectorAll("[data-cms-author-bio]").forEach(function (host) {
324
+ renderAuthorBio(host, authorsBlob.authors);
325
+ });
326
+ }
327
+
328
+ function ready(fn) {
329
+ if (document.readyState !== "loading") fn();
330
+ else document.addEventListener("DOMContentLoaded", fn);
331
+ }
332
+
333
+ // Multi-language helper — when the page's <html lang> attribute
334
+ // differs from a "primary" reference and a per-language data file
335
+ // exists, fetch it instead of the root one. Lets the multilang
336
+ // plugin ship `/<lang>/data/posts.json` so the sidebar widgets
337
+ // reflect translations on non-primary pages without us hardcoding
338
+ // anything per-language.
339
+ //
340
+ // The "primary" inference: the loader doesn't know the site's
341
+ // primary language directly. We deduce it from the URL — if the
342
+ // page lives under `/<2-letter>/...`, we treat the path's leading
343
+ // segment as the current locale. Otherwise we treat the page as
344
+ // primary and fetch the root files (existing behaviour).
345
+ function detectLocalePrefix() {
346
+ var path = stripBase(window.location.pathname).replace(/^\/+/, "");
347
+ var first = path.split("/")[0];
348
+ if (!first) return null;
349
+ // Accept 2-letter codes or BCP-47-like (e.g. "fr-ca"). Anything
350
+ // longer than 5 chars isn't a locale prefix.
351
+ if (/^[a-z]{2}(-[a-z]{2})?$/.test(first.toLowerCase())) return first.toLowerCase();
352
+ return null;
353
+ }
354
+
355
+ function fetchWithLocaleFallback(filename) {
356
+ var locale = detectLocalePrefix();
357
+ if (!locale) return fetchJson("/data/" + filename);
358
+ // Try the localised file first; if it 404s, fall back to the root.
359
+ return fetchJson("/" + locale + "/data/" + filename).then(function (data) {
360
+ if (data) return data;
361
+ return fetchJson("/data/" + filename);
362
+ });
363
+ }
364
+
365
+ ready(function () {
366
+ // Parallel fetches — both files live at the same site root and
367
+ // are independent. Each paint pass tolerates the other's failure.
368
+ Promise.all([
369
+ fetchWithLocaleFallback("posts.json"),
370
+ fetchWithLocaleFallback("authors.json"),
371
+ ]).then(
372
+ function (results) {
373
+ paintRelated(results[0]);
374
+ paintAuthorBios(results[1]);
375
+ },
376
+ );
377
+ });
378
+ })();
@@ -0,0 +1 @@
1
+ @import"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Newsreader:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400;1,700&display=swap";:root{--color-bg: #fbf9f9;--color-surface: #ffffff;--color-surface-low: #f5f3f3;--color-surface-mid: #efeded;--color-surface-high: #e9e8e7;--color-surface-highest: #e3e2e2;--color-on-surface: #1b1c1c;--color-on-surface-variant: #444748;--color-on-surface-muted: #868382;--color-outline: #747878;--color-outline-variant: #c4c7c7;--color-primary: #000000;--color-on-primary: #ffffff;--color-secondary: #476558;--color-secondary-container: #c9ead9;--color-on-secondary-container: #4d6b5d;--font-serif: "Newsreader", Georgia, "Times New Roman", serif;--font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--weight-display: 600;--weight-heading: 500;--weight-body: 400;--weight-meta: 600;--weight-emphasis: 700;--weight-lede: 300;--container-max: 1120px;--gutter: 24px;--section-gap: 80px;--radius-sm: 0.125rem;--radius: 0.25rem;--radius-lg: 0.5rem;--radius-xl: 0.75rem}*,*::before,*::after{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;font-family:var(--font-sans);font-size:16px;line-height:1.6;color:var(--color-on-surface);background:var(--color-bg);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}::selection{background:var(--color-secondary-container)}img,picture,video,svg{display:block;max-width:100%}button{font-family:inherit;cursor:pointer;background:none;border:none;padding:0;color:inherit}a{color:inherit}h1,h2,h3,h4,h5,h6{font-family:var(--font-serif);margin:0;color:var(--color-on-surface)}p{margin:0}ul,ol{margin:0}.container{width:100%;max-width:var(--container-max);margin:0 auto;padding:0 20px}@media(min-width: 768px){.container{padding:0 40px}}.label-caps{font-family:var(--font-sans);font-size:12px;line-height:1;font-weight:var(--weight-meta);letter-spacing:.05em;text-transform:uppercase;color:var(--color-on-surface-muted)}.site-header{position:sticky;top:0;z-index:50;background:rgba(251,249,249,.85);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border-bottom:1px solid var(--color-surface-highest)}.site-header__inner{position:relative;display:flex;align-items:center;justify-content:space-between;height:64px}.site-brand{font-family:var(--font-serif);font-style:italic;font-size:22px;font-weight:var(--weight-emphasis);letter-spacing:-0.02em;text-decoration:none;color:var(--color-on-surface);position:absolute;left:50%;top:50%;transform:translate(-50%, -50%);white-space:nowrap;display:inline-flex;align-items:center}.site-brand__logo{display:block;height:36px;width:auto;max-width:min(240px,60vw);object-fit:contain}.burger-toggle{display:inline-flex;align-items:center;justify-content:center;padding:8px;margin-left:-8px;border-radius:var(--radius);transition:opacity .2s ease}.burger-toggle:hover{opacity:.7}.burger-toggle svg{width:24px;height:24px}.burger-menu{position:fixed;inset:0 auto 0 0;width:min(360px,88vw);background:var(--color-bg);z-index:60;padding:64px 32px 32px;transform:translateX(-100%);transition:transform .3s ease;overflow-y:auto;box-shadow:4px 0 24px rgba(0,0,0,.08)}.burger-menu.is-open{transform:translateX(0)}.burger-menu ul{list-style:none;padding:0;display:flex;flex-direction:column;gap:24px;margin-top:24px}.burger-menu li{margin:0}.burger-menu a{display:block;font-family:var(--font-serif);font-size:22px;font-weight:var(--weight-body);color:var(--color-on-surface-muted);text-decoration:none;padding-left:16px;border-left:4px solid rgba(0,0,0,0);transition:color .2s ease,border-color .2s ease}.burger-menu a:hover{color:var(--color-on-surface)}.burger-menu a[aria-current=page]{color:var(--color-on-surface);font-weight:var(--weight-meta);border-left-color:var(--color-on-surface)}.burger-close{position:absolute;top:16px;right:16px;display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:var(--radius);color:var(--color-on-surface);transition:opacity .2s ease}.burger-close:hover{opacity:.7}.burger-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:55;opacity:0;pointer-events:none;transition:opacity .3s ease}.burger-backdrop.is-open{opacity:1;pointer-events:auto}body.burger-open{overflow:hidden}.site-footer{margin-top:var(--section-gap);border-top:1px solid var(--color-surface-highest);background:var(--color-surface-low);padding:56px 0}.site-footer__inner{display:flex;flex-direction:column;align-items:center;text-align:center;gap:24px}@media(min-width: 768px){.site-footer__inner{flex-direction:row;justify-content:space-between;text-align:left}}.site-footer__brand{font-family:var(--font-serif);font-style:italic;font-size:18px;font-weight:var(--weight-emphasis);letter-spacing:-0.02em;margin:0}.site-footer__nav ul{list-style:none;padding:0;display:flex;flex-wrap:wrap;gap:24px;justify-content:center}.site-footer__nav a{font-family:var(--font-sans);font-size:11px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;color:var(--color-on-surface-muted);text-decoration:none;transition:color .2s ease}.site-footer__nav a:hover{color:var(--color-on-surface)}.site-footer__copy{font-family:var(--font-sans);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--color-on-surface-muted);margin:0}.site-main{display:block;min-height:60vh;padding-bottom:var(--section-gap)}.card-post{display:flex;flex-direction:column;background:var(--color-surface-low);border:1px solid var(--color-outline-variant);overflow:hidden;height:100%}.card-post__link{display:flex;flex-direction:column;flex:1;text-decoration:none;color:inherit}.card-post__media{aspect-ratio:16/9;background:var(--color-surface-high);overflow:hidden}.card-post__media img{width:100%;height:100%;object-fit:cover;transition:transform .5s ease}.card-post:hover .card-post__media img{transform:scale(1.03)}.card-post__body{display:flex;flex-direction:column;flex:1;padding:20px}.card-post__eyebrow{font-family:var(--font-sans);font-size:11px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;color:var(--color-secondary);margin:0 0 12px 0}.card-post__title{font-family:var(--font-serif);font-size:22px;line-height:1.3;font-weight:var(--weight-heading);margin:0 0 12px 0;transition:color .2s ease}.card-post:hover .card-post__title{color:var(--color-secondary)}.card-post__excerpt{font-size:15px;line-height:1.6;color:var(--color-on-surface-variant);margin:0 0 16px 0;display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.card-post__meta{font-family:var(--font-sans);font-size:11px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;color:var(--color-on-surface-muted);margin:auto 0 0 0}.hero-featured{margin-top:48px}@media(min-width: 768px){.hero-featured{margin-top:80px}}.hero-featured__link{text-decoration:none;color:inherit;display:block}.hero-featured__media{aspect-ratio:21/9;background:var(--color-surface-high);overflow:hidden;border-radius:var(--radius)}.hero-featured__media img{width:100%;height:100%;object-fit:cover;transition:transform .7s ease}.hero-featured__link:hover .hero-featured__media img{transform:scale(1.03)}.hero-featured__body{margin-top:32px;max-width:720px}.hero-featured__pill{display:inline-block;background:var(--color-secondary-container);color:var(--color-on-secondary-container);padding:6px 12px;font-family:var(--font-sans);font-size:11px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;border-radius:var(--radius);margin-bottom:16px}.hero-featured__title{font-family:var(--font-serif);font-size:clamp(32px,5vw,48px);line-height:1.1;letter-spacing:-0.02em;font-weight:var(--weight-display);margin:0 0 16px 0}.hero-featured__excerpt{font-size:18px;line-height:1.7;color:var(--color-on-surface-variant);margin:0 0 24px 0;max-width:600px}.hero-featured__date{font-family:var(--font-sans);font-size:11px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;color:var(--color-on-surface-muted)}.page-home__title-row{display:flex;align-items:center;justify-content:space-between;margin-top:var(--section-gap);margin-bottom:32px;padding-bottom:16px;border-bottom:1px solid var(--color-surface-highest)}.page-home__title{font-family:var(--font-serif);font-size:22px;font-weight:var(--weight-heading);letter-spacing:.04em;text-transform:uppercase;margin:0}.post-grid{list-style:none;padding:0;display:grid;grid-template-columns:1fr;gap:48px 32px}@media(min-width: 768px){.post-grid{grid-template-columns:repeat(2, 1fr)}}@media(min-width: 1024px){.post-grid{grid-template-columns:repeat(3, 1fr)}}.post-grid li{list-style:none}.page-single{padding-top:48px;display:grid;grid-template-columns:minmax(0, 1fr);gap:64px}@media(min-width: 1024px){.page-single{padding-top:80px;grid-template-columns:minmax(0, 8fr) minmax(0, 4fr);column-gap:64px}}.page-static{padding-top:48px;padding-bottom:96px}@media(min-width: 1024px){.page-static{padding-top:80px}}.page-single__main{min-width:0}.page-single__meta-strip{display:flex;align-items:center;flex-wrap:wrap;gap:12px 16px;margin-bottom:40px}.page-single__meta-strip .label-caps{color:var(--color-secondary)}.page-single__meta-dot{width:4px;height:4px;border-radius:50%;background:var(--color-outline-variant);flex-shrink:0}.page-single__title{font-family:var(--font-serif);font-size:clamp(32px,5vw,48px);line-height:1.1;letter-spacing:-0.02em;font-weight:var(--weight-display);margin:0 0 32px 0}.page-single__lede{font-family:var(--font-serif);font-style:italic;font-size:clamp(20px,2.5vw,24px);line-height:1.5;font-weight:var(--weight-lede);color:var(--color-on-surface-variant);margin:0 0 40px 0}.page-single__author-strip{display:flex;align-items:center;gap:16px;padding-bottom:32px;margin-bottom:48px;border-bottom:1px solid var(--color-surface-highest)}.page-single__author-name{font-family:var(--font-sans);font-size:14px;font-weight:var(--weight-emphasis);margin:0;color:var(--color-on-surface)}.page-single__author-meta{font-family:var(--font-sans);font-size:11px;letter-spacing:.05em;text-transform:uppercase;color:var(--color-on-surface-muted);margin:4px 0 0 0}.page-single__hero{margin:0 0 48px 0;border-radius:var(--radius-lg);overflow:hidden;background:var(--color-surface-high)}.page-single__hero img{aspect-ratio:16/9;width:100%;height:auto;object-fit:cover}.page-single__hero figcaption{font-family:var(--font-sans);font-size:12px;color:var(--color-on-surface-muted);padding:8px 16px}.page-single__tags{list-style:none;margin:64px 0 0 0;padding:32px 0 0 0;border-top:1px solid var(--color-surface-highest);display:flex;flex-wrap:wrap;gap:8px}.page-single__tags li{background:var(--color-surface-low);color:var(--color-on-surface-muted);padding:8px 16px;border-radius:999px;font-family:var(--font-sans);font-size:11px;font-weight:var(--weight-meta);letter-spacing:.05em;text-transform:uppercase}.page-single__sidebar{display:flex;flex-direction:column;gap:48px}.page-body{font-family:var(--font-serif);font-size:18px;line-height:1.8;color:var(--color-on-surface)}.page-body>p:first-of-type::first-letter{float:left;font-size:5rem;line-height:4rem;padding-right:12px;padding-top:4px;font-family:var(--font-serif);font-weight:var(--weight-emphasis)}.page-body p{margin:0 0 28px 0}.page-body strong,.page-body b{font-weight:var(--weight-emphasis)}.page-body h2{font-size:32px;margin:56px 0 16px 0;font-weight:var(--weight-display);letter-spacing:-0.01em}.page-body h3{font-size:24px;margin:40px 0 12px 0;font-weight:var(--weight-heading)}.page-body blockquote{border-left:2px solid var(--color-primary);padding-left:24px;margin:48px 0;font-family:var(--font-serif);font-style:italic;font-size:clamp(22px,2.5vw,28px);font-weight:var(--weight-lede);line-height:1.5;color:var(--color-on-surface-variant)}.page-body blockquote p{margin:0 0 12px 0}.page-body img{width:100%;border-radius:var(--radius)}.page-body a{color:var(--color-secondary);text-decoration:underline;text-underline-offset:.2em}.page-body code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,monospace;background:var(--color-surface-mid);padding:.1em .4em;border-radius:3px;font-size:.85em}.page-body pre{background:var(--color-surface-mid);border-radius:var(--radius);padding:16px 20px;overflow-x:auto;margin:32px 0;font-size:14px;line-height:1.6}.page-body pre code{background:rgba(0,0,0,0);padding:0;font-size:inherit}.page-body ul,.page-body ol{padding-left:28px;margin:0 0 28px 0}.page-body li{margin:8px 0}.page-body hr{border:none;border-top:1px solid var(--color-surface-highest);margin:48px 0}.author-bio{background:var(--color-surface-low);padding:32px;border-radius:var(--radius-lg);border:1px solid var(--color-surface-highest)}.author-bio__eyebrow{font-family:var(--font-sans);font-size:11px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;color:var(--color-on-surface);margin:0 0 24px 0}.author-bio__head{display:flex;align-items:flex-start;gap:16px}.author-bio__name{font-family:var(--font-serif);font-size:20px;font-weight:var(--weight-emphasis);margin:0;color:var(--color-on-surface);text-decoration:none;display:inline-block}a.author-bio__name:hover{color:var(--color-secondary);text-decoration:underline;text-underline-offset:.2em}.author-bio__role{font-family:var(--font-sans);font-size:13px;font-style:italic;color:var(--color-on-surface-muted);margin:4px 0 0 0}.author-bio__bio{font-family:var(--font-sans);font-size:14px;line-height:1.6;color:var(--color-on-surface-variant);margin:24px 0 0 0}.author-bio__socials{display:flex;gap:12px;margin:16px 0 0 0}.author-bio__social-link{display:inline-flex;align-items:center;justify-content:center;color:var(--color-on-surface-variant);line-height:0;transition:color .15s ease}.author-bio__social-link:hover{color:var(--color-primary)}.author-bio__social-link svg{width:18px;height:18px}.author-avatar{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%;background:var(--color-on-surface);color:var(--color-bg);font-family:var(--font-sans);font-size:13px;font-weight:var(--weight-emphasis);letter-spacing:.02em;text-transform:uppercase}.author-avatar--lg{width:56px;height:56px;font-size:16px}.author-avatar--xl{width:100%;max-width:280px;aspect-ratio:3/4;height:auto;border-radius:var(--radius-lg);font-size:clamp(48px,8vw,72px);font-weight:var(--weight-display)}.author-avatar--image{background:var(--color-surface-high);object-fit:cover;display:block;color:rgba(0,0,0,0)}.related-posts__title{font-family:var(--font-sans);font-size:11px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;color:var(--color-on-surface);margin:0 0 24px 0;padding-bottom:16px;border-bottom:1px solid var(--color-surface-highest)}.related-posts__list{list-style:none;padding:0;display:flex;flex-direction:column;gap:28px}.related-posts__item a{display:block;color:inherit;text-decoration:none}.related-posts__eyebrow{font-family:var(--font-sans);font-size:10px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;color:var(--color-secondary);margin:0 0 6px 0}.related-posts__title-line{font-family:var(--font-serif);font-size:17px;font-weight:var(--weight-emphasis);line-height:1.3;margin:0 0 6px 0;transition:color .2s ease}.related-posts__item a:hover .related-posts__title-line{color:var(--color-secondary)}.related-posts__date{font-family:var(--font-sans);font-size:10px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;color:var(--color-on-surface-muted);margin:0}.page-archive{padding-top:64px}.page-archive__header{display:flex;flex-direction:column;gap:32px;padding-bottom:48px;border-bottom:1px solid var(--color-surface-highest);margin-bottom:64px}@media(min-width: 768px){.page-archive__header{flex-direction:row;align-items:flex-end;justify-content:space-between}}.page-archive__head{max-width:720px}.page-archive__eyebrow{font-family:var(--font-sans);font-size:12px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;color:var(--color-secondary);margin:0 0 16px 0}.page-archive__title{font-family:var(--font-serif);font-size:clamp(36px,5vw,48px);line-height:1.1;letter-spacing:-0.02em;font-weight:var(--weight-display);margin:0 0 16px 0}.page-archive__desc{font-size:18px;line-height:1.7;color:var(--color-on-surface-variant);margin:0;max-width:56ch}.page-archive__rss{display:inline-flex;align-items:center;gap:8px;padding:10px 24px;border:1px solid var(--color-primary);background:rgba(0,0,0,0);color:var(--color-on-surface);font-family:var(--font-sans);font-size:11px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;border-radius:var(--radius);text-decoration:none;transition:background .2s ease,color .2s ease;align-self:flex-start}.page-archive__rss:hover{background:var(--color-primary);color:var(--color-on-primary)}.page-archive__rss svg{width:16px;height:16px}.page-author{padding-top:64px}.page-author__hero{display:flex;flex-direction:column;gap:48px;padding-bottom:64px;border-bottom:1px solid var(--color-surface-highest);margin-bottom:64px}@media(min-width: 768px){.page-author__hero{flex-direction:row;align-items:center;gap:96px}}.page-author__portrait{flex-shrink:0;width:100%;max-width:280px}.page-author__body{display:flex;flex-direction:column;justify-content:center}.page-author__eyebrow{font-family:var(--font-sans);font-size:12px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;color:var(--color-secondary);margin:0 0 16px 0}.page-author__title{font-family:var(--font-serif);font-size:clamp(36px,5vw,48px);line-height:1.1;letter-spacing:-0.02em;font-weight:var(--weight-display);margin:0 0 12px 0}.page-author__role{font-family:var(--font-sans);font-size:13px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;color:var(--color-secondary);margin:0 0 16px 0}.page-author__bio{font-size:18px;line-height:1.7;color:var(--color-on-surface-variant);margin:0}.page-author__socials{display:flex;gap:16px;margin:24px 0 0 0}.page-author__social-link{display:inline-flex;align-items:center;justify-content:center;color:var(--color-on-surface-variant);line-height:0;transition:color .15s ease}.page-author__social-link:hover{color:var(--color-primary)}.page-author__social-link svg{width:20px;height:20px}.empty-state{text-align:center;padding:80px 0;color:var(--color-on-surface-muted);font-style:italic}.page-static{padding-top:64px}.page-static .page-title,.page-title{font-family:var(--font-serif);font-size:clamp(36px,5vw,48px);line-height:1.1;letter-spacing:-0.02em;font-weight:var(--weight-display);margin:0 0 32px 0}.breadcrumb{display:flex;flex-wrap:wrap;align-items:center;gap:8px;font-family:var(--font-sans);font-size:11px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;color:var(--color-on-surface-muted);margin:0 0 32px 0;padding:0;list-style:none}.breadcrumb li{display:inline-flex;align-items:center;gap:8px}.breadcrumb a{color:var(--color-on-surface-muted);text-decoration:none;transition:color .2s ease}.breadcrumb a:hover{color:var(--color-on-surface)}.breadcrumb__sep{color:var(--color-outline-variant)}.breadcrumb__current{color:var(--color-on-surface);display:inline-block;max-width:min(420px,60vw);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.pagination{display:flex;align-items:center;justify-content:space-between;margin-top:var(--section-gap);padding-top:48px;border-top:1px solid var(--color-surface-highest)}.pagination__pages{display:none;gap:12px}@media(min-width: 768px){.pagination__pages{display:flex}}.pagination a,.pagination span{font-family:var(--font-sans);font-size:11px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;color:var(--color-on-surface-muted);text-decoration:none;transition:color .2s ease}.pagination a:hover{color:var(--color-on-surface)}.pagination__pages a,.pagination__pages span{width:36px;height:36px;display:inline-flex;align-items:center;justify-content:center;border-radius:50%}.pagination__pages a.is-active{background:var(--color-primary);color:var(--color-on-primary)}.page-not-found{display:flex;flex-direction:column;align-items:center;text-align:center;padding:120px 20px}.page-not-found h1{font-family:var(--font-serif);font-size:clamp(72px,12vw,120px);font-weight:var(--weight-display);margin:0 0 24px 0;letter-spacing:-0.04em;line-height:1}.page-not-found p{font-size:18px;color:var(--color-on-surface-variant);max-width:420px;margin:0 0 32px 0}.page-not-found a{font-family:var(--font-sans);font-size:11px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;color:var(--color-on-surface);border-bottom:2px solid var(--color-primary);text-decoration:none;padding-bottom:4px;transition:color .2s ease,border-color .2s ease}.page-not-found a:hover{color:var(--color-secondary);border-color:var(--color-secondary)}.cms-hero a,.cms-card a,.cms-cta a,.cms-categories a,.cms-list-block a,.cms-list-header a,.card-post a{text-decoration:none}.cms-hero{position:relative;display:flex;overflow:hidden;width:100%;margin:32px 0;background:var(--color-bg)}.cms-hero .cms-hero-image-wrap{background:var(--color-surface-mid);overflow:hidden}.cms-hero .cms-hero-image-wrap a{display:block;width:100%;height:100%}.cms-hero .cms-hero-image{width:100%;height:100%;object-fit:cover;display:block}.cms-hero .cms-hero-content{padding:32px;flex:1;display:flex;flex-direction:column;justify-content:center;min-width:0}.cms-hero .cms-hero-category{display:inline-block;align-self:flex-start;font-family:var(--font-sans);font-size:11px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;background:var(--color-secondary-container);color:var(--color-on-surface);padding:4px 10px;margin:0 0 16px 0;text-decoration:none;transition:opacity .2s ease}.cms-hero .cms-hero-category:hover{opacity:.85}.cms-hero .cms-hero-title{font-family:var(--font-serif);font-size:clamp(26px,4.5vw,40px);font-weight:var(--weight-display);line-height:1.15;letter-spacing:-0.02em;margin:0 0 20px 0}.cms-hero .cms-hero-title a{color:inherit;text-decoration:none;transition:color .2s ease}.cms-hero .cms-hero-title a:hover{color:var(--color-secondary)}.cms-hero .cms-hero-excerpt{font-family:var(--font-sans);font-size:16px;line-height:1.6;color:var(--color-on-surface-variant);margin:0 0 20px 0;max-width:60ch}.cms-hero-image-overlay{min-height:320px;align-items:flex-end}.cms-hero-image-overlay .cms-hero-image-wrap{position:absolute;inset:0;z-index:1}.cms-hero-image-overlay::after{content:"";position:absolute;inset:0;background:linear-gradient(to top, rgba(0, 0, 0, 0.85) 0%, rgba(0, 0, 0, 0.2) 60%, rgba(0, 0, 0, 0) 100%);z-index:2;pointer-events:none}.cms-hero-image-overlay .cms-hero-content{position:relative;z-index:3;width:100%;color:#fff;padding:32px}.cms-hero-image-overlay .cms-hero-title a{color:#fff}.cms-hero-image-overlay .cms-hero-title a:hover{color:var(--color-secondary-container)}.cms-hero-image-overlay .cms-hero-excerpt{color:hsla(0,0%,100%,.85)}@media(min-width: 768px){.cms-hero-image-overlay{min-height:480px}.cms-hero-image-overlay .cms-hero-content{padding:48px}}.cms-hero-split-left,.cms-hero-split-right{background:var(--color-surface-low);border:1px solid var(--color-outline-variant);flex-direction:column}.cms-hero-split-left .cms-hero-image-wrap,.cms-hero-split-right .cms-hero-image-wrap{width:100%;height:240px;flex-shrink:0}@media(min-width: 768px){.cms-hero-split-left,.cms-hero-split-right{flex-direction:row;min-height:360px}.cms-hero-split-left .cms-hero-image-wrap,.cms-hero-split-right .cms-hero-image-wrap{flex:0 0 50%;height:auto}}@media(min-width: 768px){.cms-hero-split-right{flex-direction:row-reverse}}.cms-hero-minimal{flex-direction:column;text-align:center;align-items:center;padding:48px 16px;border-bottom:2px solid var(--color-on-surface)}.cms-hero-minimal .cms-hero-content{max-width:720px;align-items:center;padding:0}.cms-hero-minimal .cms-hero-category{align-self:center}.cms-hero-minimal .cms-hero-title{font-size:clamp(32px,6vw,56px)}@media(min-width: 768px){.cms-hero-minimal{padding:80px 24px}}.cms-list-block{margin:48px 0}.cms-list-header{display:flex;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid var(--color-outline-variant);padding-bottom:12px;margin-bottom:24px}.cms-list-title{font-family:var(--font-serif);font-size:20px;font-weight:var(--weight-heading);text-transform:uppercase;letter-spacing:.05em;margin:0}.cms-list-see-more{font-family:var(--font-sans);font-size:11px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;color:var(--color-on-surface-variant);text-decoration:none}.cms-list-see-more:hover{color:var(--color-primary)}.cms-list-cards{display:grid;grid-template-columns:1fr;gap:32px}@media(min-width: 768px){.cms-list-cards.cms-list-cols-2{grid-template-columns:repeat(2, 1fr)}.cms-list-cards.cms-list-cols-3{grid-template-columns:repeat(3, 1fr)}.cms-list-cards.cms-list-cols-4{grid-template-columns:repeat(4, 1fr)}}.cms-list-list{display:flex;flex-direction:column;gap:24px}.cms-list-compact{display:flex;flex-direction:column;gap:8px}.cms-list-numbered{display:flex;flex-direction:column;gap:16px}.cms-list-slider{display:flex;gap:16px;overflow-x:auto;scroll-snap-type:x mandatory;padding-bottom:8px;-webkit-overflow-scrolling:touch;scrollbar-width:thin}.cms-list-slider>.cms-card{scroll-snap-align:start;flex:0 0 240px}@media(min-width: 768px){.cms-list-slider>.cms-card{flex:0 0 280px}}.cms-card-slider{position:relative;display:block;overflow:hidden;aspect-ratio:4/5;background:var(--color-on-surface);color:#fff;text-decoration:none}.cms-card-slider-image-wrap{position:absolute;inset:0;z-index:1}.cms-card-slider-image{width:100%;height:100%;object-fit:cover;display:block;transition:transform .5s ease}.cms-card-slider:hover .cms-card-slider-image{transform:scale(1.05)}.cms-card-slider::after{content:"";position:absolute;inset:0;background:linear-gradient(to top, rgba(0, 0, 0, 0.85) 0%, rgba(0, 0, 0, 0.3) 55%, rgba(0, 0, 0, 0) 100%);z-index:2;pointer-events:none}.cms-card-slider-no-image::after{display:none}.cms-card-slider-overlay{position:absolute;inset:auto 0 0 0;z-index:3;padding:16px;display:flex;flex-direction:column;gap:6px}.cms-card-slider-overlay .cms-card-category{align-self:flex-start;margin:0}.cms-card-slider-overlay .cms-card-title{color:#fff;font-size:16px;line-height:1.25;margin:0}.cms-card-slider-overlay .cms-card-meta{color:hsla(0,0%,100%,.85)}.cms-card{display:block;text-decoration:none;color:inherit}.cms-card-image-wrap{aspect-ratio:16/9;background:var(--color-surface-mid);overflow:hidden}.cms-card-image{width:100%;height:100%;object-fit:cover;display:block;transition:transform .4s ease}.cms-card:hover .cms-card-image{transform:scale(1.03)}.cms-card-category{display:inline-block;font-family:var(--font-sans);font-size:11px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;color:var(--color-on-surface-muted);margin-bottom:6px}.cms-card-title{font-family:var(--font-serif);font-size:20px;font-weight:var(--weight-display);line-height:1.3;margin:0 0 8px 0;color:var(--color-on-surface);transition:color .2s ease}.cms-card:hover .cms-card-title{color:var(--color-secondary)}.cms-card-excerpt{font-family:var(--font-sans);font-size:15px;line-height:1.6;color:var(--color-on-surface-variant);margin:0 0 8px 0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.cms-card-meta{display:inline-block;font-family:var(--font-sans);font-size:11px;letter-spacing:.05em;color:var(--color-on-surface-muted)}.cms-card-default{display:flex;flex-direction:column;background:var(--color-surface-low);border:1px solid var(--color-outline-variant);overflow:hidden;height:100%}.cms-card-body{display:flex;flex-direction:column;flex:1;padding:20px}.cms-card-body .cms-card-meta{margin-top:auto;padding-top:8px}.cms-card-compact{display:flex;align-items:baseline;justify-content:space-between;gap:16px;padding:8px 0;border-bottom:1px solid var(--color-outline-variant)}.cms-card-compact .cms-card-title{font-size:16px;font-weight:var(--weight-display);margin:0;flex:1;font-family:var(--font-sans)}.cms-card-numbered{display:grid;grid-template-columns:auto 1fr;column-gap:16px;row-gap:2px;align-items:start}.cms-card-number{font-family:var(--font-serif);font-style:italic;font-size:32px;font-weight:700;color:var(--color-outline-variant);line-height:1}.cms-card-numbered .cms-card-title{font-size:16px;font-family:var(--font-sans);font-weight:var(--weight-display);line-height:1.3;margin:0}.cms-card-numbered .cms-card-meta{grid-column:2;display:block}.cms-card-list{display:block;padding-bottom:24px;border-bottom:1px solid var(--color-outline-variant)}.cms-card-list:last-child{border-bottom:none}.cms-card-list-with-image{display:flex;gap:16px;align-items:flex-start}.cms-card-list-with-image .cms-card-list-image-wrap{flex:0 0 120px;aspect-ratio:1;background:var(--color-surface-mid);overflow:hidden}.cms-card-list-with-image .cms-card-list-image{width:100%;height:100%;object-fit:cover;display:block;transition:transform .4s ease}.cms-card-list-with-image:hover .cms-card-list-image{transform:scale(1.03)}.cms-card-list-with-image .cms-card-list-body{flex:1;min-width:0;display:flex;flex-direction:column}@media(max-width: 480px){.cms-card-list-with-image{gap:12px}.cms-card-list-with-image .cms-card-list-image-wrap{flex:0 0 84px}}.cms-card-list .cms-card-title,.cms-card-list .cms-card-excerpt{margin-bottom:2px}.cms-cta{display:flex;flex-direction:column;gap:16px;padding:32px;margin:32px 0}.cms-cta-light{background:var(--color-surface-mid);color:var(--color-on-surface)}.cms-cta-dark{background:var(--color-primary);color:var(--color-on-primary)}.cms-cta-title{font-family:var(--font-serif);font-size:24px;font-weight:var(--weight-heading);line-height:1.3;margin:0}.cms-cta-description{font-family:var(--font-sans);font-size:15px;line-height:1.6;margin:0;opacity:.85}.cms-cta-button{display:inline-block;align-self:flex-start;font-family:var(--font-sans);font-size:11px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;border-bottom:2px solid currentColor;padding-bottom:4px;text-decoration:none;color:inherit;margin-top:8px;transition:opacity .2s ease}.cms-cta-button:hover{opacity:.7}@media(min-width: 768px){.cms-cta{padding:40px}}.cms-categories{margin:32px 0}.cms-categories-title{font-family:var(--font-sans);font-size:11px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;border-bottom:1px solid var(--color-outline-variant);padding-bottom:12px;margin:0 0 16px 0}.cms-categories-items{display:flex;flex-wrap:wrap;gap:8px}.cms-categories-pill{display:inline-block;font-family:var(--font-sans);font-size:11px;font-weight:var(--weight-meta);letter-spacing:.1em;text-transform:uppercase;padding:8px 16px;border:1px solid var(--color-outline-variant);border-radius:9999px;text-decoration:none;color:var(--color-on-surface);transition:background .2s ease,color .2s ease}.cms-categories-pill:hover{background:var(--color-primary);color:var(--color-on-primary)}.cms-categories-list .cms-categories-items{flex-direction:column;gap:4px}.cms-categories-list .cms-categories-pill{border:none;border-radius:0;padding:4px 0;text-transform:none;letter-spacing:0;font-size:14px}.cms-spacer{display:block}.cms-spacer-sm{height:24px}.cms-spacer-md{height:48px}.cms-spacer-lg{height:96px}.cms-spacer-divider{position:relative}.cms-spacer-divider::before{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);width:64px;height:1px;background:var(--color-outline-variant)}.archives{max-width:var(--container-max);margin:0 auto;padding:var(--section-gap, 4rem) var(--gutter, 1rem)}.archives__header{margin-bottom:2.5rem}.archives__back{margin:0 0 .75rem;font-size:.875rem}.archives__back a{color:var(--color-on-surface-variant);text-decoration:none}.archives__back a:hover{color:var(--color-on-surface);text-decoration:underline}.archives__title{font-family:var(--font-serif);font-weight:var(--weight-display);font-size:clamp(2rem,4vw,3rem);line-height:1.1;margin:0 0 .5rem;color:var(--color-on-surface)}.archives__subtitle{margin:0;color:var(--color-on-surface-variant);font-size:1rem}.archives__empty{color:var(--color-on-surface-muted);font-style:italic}.archives__years{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2rem}.archives__year{border-top:1px solid var(--color-outline-variant);padding-top:1.5rem}.archives__year:first-child{border-top:0;padding-top:0}.archives__year-heading{font-family:var(--font-serif);font-weight:var(--weight-heading);font-size:1.75rem;margin:0 0 .75rem;display:flex;align-items:baseline;gap:.5rem}.archives__year-link,.archives__group-link,.archives__drilldown-link,.archives__item-link{color:var(--color-on-surface);text-decoration:none;border-bottom:1px solid rgba(0,0,0,0);transition:border-color .15s}.archives__year-link:hover,.archives__group-link:hover,.archives__drilldown-link:hover,.archives__item-link:hover{border-bottom-color:currentColor}.archives__count{color:var(--color-on-surface-muted);font-size:.875rem;font-weight:400}.archives__drilldown{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:.5rem 1rem}.archives__drilldown-item{display:inline-flex;align-items:baseline;gap:.35rem;font-size:.95rem}.archives__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.5rem}.archives__item{display:flex;align-items:baseline;justify-content:space-between;gap:1rem;padding:.5rem 0;border-bottom:1px solid var(--color-outline-variant)}.archives__item:last-child{border-bottom:0}.archives__item-link{flex:1;font-size:1rem}.archives__item-date{color:var(--color-on-surface-muted);font-size:.85rem;white-space:nowrap}.archives__groups{display:flex;flex-direction:column;gap:2.5rem}.archives__group-heading{font-family:var(--font-serif);font-weight:var(--weight-heading);font-size:1.25rem;margin:0 0 .75rem;display:flex;align-items:baseline;gap:.5rem}.archives-link{margin:3rem 0 0;text-align:right;font-size:.95rem}.archives-link a{color:var(--color-secondary);text-decoration:none;border-bottom:1px solid currentColor;padding-bottom:.1rem}.archives-link a:hover{color:var(--color-on-surface)}