@takuhon/api 1.3.0 → 1.4.1
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.
- package/NOTICE +1 -1
- package/dist/index.d.ts +209 -75
- package/dist/index.js +549 -124
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
import { Hono } from "hono";
|
|
55
55
|
|
|
56
56
|
// src/html/build-html.ts
|
|
57
|
-
import { generateJsonLd, renderActivitySvg } from "@takuhon/core";
|
|
57
|
+
import { generateJsonLd, renderActivitySvg, SECTION_KEYS } from "@takuhon/core";
|
|
58
58
|
|
|
59
59
|
// src/html/html-helpers.ts
|
|
60
60
|
import { formatDate, getPresentLabel } from "@takuhon/core";
|
|
@@ -74,10 +74,10 @@ function presentLabel(locale) {
|
|
|
74
74
|
return escapeHtml(getPresentLabel(locale));
|
|
75
75
|
}
|
|
76
76
|
function dateRange(start, opts) {
|
|
77
|
-
const { end, isCurrent, locale } = opts;
|
|
77
|
+
const { end, isCurrent, locale, separator = " \u2013 " } = opts;
|
|
78
78
|
const left = start ? timeTag(start, locale) : "";
|
|
79
79
|
const right = isCurrent === true || end === null ? presentLabel(locale) : end ? timeTag(end, locale) : "";
|
|
80
|
-
if (left && right) return `${left}
|
|
80
|
+
if (left && right) return `${left}${separator}${right}`;
|
|
81
81
|
return left || right;
|
|
82
82
|
}
|
|
83
83
|
function nonEmpty(values, separator) {
|
|
@@ -86,7 +86,7 @@ function nonEmpty(values, separator) {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
// src/html/brand-icons.ts
|
|
89
|
-
var
|
|
89
|
+
var GLYPHS = {
|
|
90
90
|
github: {
|
|
91
91
|
viewBox: "0 0 24 24",
|
|
92
92
|
path: "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"
|
|
@@ -130,18 +130,143 @@ var BRAND_ICONS = {
|
|
|
130
130
|
rss: {
|
|
131
131
|
viewBox: "0 0 24 24",
|
|
132
132
|
path: "M19.199 24C19.199 13.467 10.533 4.8 0 4.8V0c13.165 0 24 10.835 24 24h-4.801zM3.291 17.415c1.814 0 3.293 1.479 3.293 3.295 0 1.813-1.485 3.29-3.301 3.29C1.47 24 0 22.526 0 20.71s1.475-3.294 3.291-3.295zM15.909 24h-4.665c0-6.169-5.075-11.245-11.244-11.245V8.09c8.727 0 15.909 7.184 15.909 15.91z"
|
|
133
|
+
},
|
|
134
|
+
npm: {
|
|
135
|
+
viewBox: "0 0 24 24",
|
|
136
|
+
path: "M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z"
|
|
137
|
+
},
|
|
138
|
+
linktree: {
|
|
139
|
+
viewBox: "0 0 24 24",
|
|
140
|
+
path: "m13.73635 5.85251 4.00467-4.11665 2.3248 2.3808-4.20064 4.00466h5.9085v3.30473h-5.9365l4.22865 4.10766-2.3248 2.3338L12.0005 12.099l-5.74052 5.76852-2.3248-2.3248 4.22864-4.10766h-5.9375V8.12132h5.9085L3.93417 4.11666l2.3248-2.3808 4.00468 4.11665V0h3.4727zm-3.4727 10.30614h3.4727V24h-3.4727z"
|
|
141
|
+
},
|
|
142
|
+
wakatime: {
|
|
143
|
+
viewBox: "0 0 24 24",
|
|
144
|
+
path: "M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm0 2.824a9.176 9.176 0 1 1 0 18.352 9.176 9.176 0 0 1 0-18.352zm5.097 5.058c-.327 0-.61.19-.764.45-1.025 1.463-2.21 3.162-3.288 4.706l-.387-.636a.897.897 0 0 0-.759-.439.901.901 0 0 0-.788.492l-.357.581-1.992-2.943a.897.897 0 0 0-.761-.446c-.514 0-.903.452-.903.96a1 1 0 0 0 .207.61l2.719 3.96c.152.272.44.47.776.47a.91.91 0 0 0 .787-.483c.046-.071.23-.368.314-.504l.324.52c-.035-.047.076.113.087.13.024.031.054.059.078.085.019.019.04.036.058.052.036.033.08.056.115.08.025.016.052.028.076.04.029.015.06.024.088.035.058.025.122.027.18.04.031.004.064.003.092.005.29 0 .546-.149.707-.36 1.4-2 2.842-4.055 4.099-5.849A.995.995 0 0 0 18 8.842c0-.508-.389-.96-.903-.96"
|
|
145
|
+
},
|
|
146
|
+
wordpress: {
|
|
147
|
+
viewBox: "0 0 24 24",
|
|
148
|
+
path: "M21.469 6.825c.84 1.537 1.318 3.3 1.318 5.175 0 3.979-2.156 7.456-5.363 9.325l3.295-9.527c.615-1.54.82-2.771.82-3.864 0-.405-.026-.78-.07-1.11m-7.981.105c.647-.03 1.232-.105 1.232-.105.582-.075.514-.93-.067-.899 0 0-1.755.135-2.88.135-1.064 0-2.85-.15-2.85-.15-.585-.03-.661.855-.075.885 0 0 .54.061 1.125.09l1.68 4.605-2.37 7.08L5.354 6.9c.649-.03 1.234-.1 1.234-.1.585-.075.516-.93-.065-.896 0 0-1.746.138-2.874.138-.2 0-.438-.008-.69-.015C4.911 3.15 8.235 1.215 12 1.215c2.809 0 5.365 1.072 7.286 2.833-.046-.003-.091-.009-.141-.009-1.06 0-1.812.923-1.812 1.914 0 .89.513 1.643 1.06 2.531.411.72.89 1.643.89 2.977 0 .915-.354 1.994-.821 3.479l-1.075 3.585-3.9-11.61.001.014zM12 22.784c-1.059 0-2.081-.153-3.048-.437l3.237-9.406 3.315 9.087c.024.053.05.101.078.149-1.12.393-2.325.609-3.582.609M1.211 12c0-1.564.336-3.05.935-4.39L7.29 21.709C3.694 19.96 1.212 16.271 1.211 12M12 0C5.385 0 0 5.385 0 12s5.385 12 12 12 12-5.385 12-12S18.615 0 12 0"
|
|
133
149
|
}
|
|
134
150
|
};
|
|
135
|
-
var
|
|
151
|
+
var TYPE_GLYPHS = {
|
|
152
|
+
github: "github",
|
|
153
|
+
gitlab: "gitlab",
|
|
154
|
+
linkedin: "linkedin",
|
|
155
|
+
x: "x",
|
|
156
|
+
mastodon: "mastodon",
|
|
157
|
+
bluesky: "bluesky",
|
|
158
|
+
instagram: "instagram",
|
|
159
|
+
youtube: "youtube",
|
|
160
|
+
threads: "threads",
|
|
161
|
+
facebook: "facebook",
|
|
162
|
+
rss: "rss",
|
|
136
163
|
blog: "rss"
|
|
137
164
|
};
|
|
138
|
-
|
|
139
|
-
|
|
165
|
+
var HOST_GLYPHS = {
|
|
166
|
+
"github.com": "github",
|
|
167
|
+
"gitlab.com": "gitlab",
|
|
168
|
+
"linkedin.com": "linkedin",
|
|
169
|
+
"x.com": "x",
|
|
170
|
+
"twitter.com": "x",
|
|
171
|
+
"instagram.com": "instagram",
|
|
172
|
+
"youtube.com": "youtube",
|
|
173
|
+
"threads.net": "threads",
|
|
174
|
+
"facebook.com": "facebook",
|
|
175
|
+
"bsky.app": "bluesky",
|
|
176
|
+
"npmjs.com": "npm",
|
|
177
|
+
"linktr.ee": "linktree",
|
|
178
|
+
"wakatime.com": "wakatime",
|
|
179
|
+
"wordpress.org": "wordpress"
|
|
180
|
+
};
|
|
181
|
+
function hostGlyph(url) {
|
|
182
|
+
let host;
|
|
183
|
+
try {
|
|
184
|
+
host = new URL(url, "https://takuhon.invalid").hostname.toLowerCase();
|
|
185
|
+
} catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
for (const [domain, glyph] of Object.entries(HOST_GLYPHS)) {
|
|
189
|
+
if (host === domain || host.endsWith(`.${domain}`)) return glyph;
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
function renderGlyph(name) {
|
|
194
|
+
const icon = GLYPHS[name];
|
|
140
195
|
if (!icon) return "";
|
|
141
196
|
return `<svg class="brand-icon" viewBox="${icon.viewBox}" width="18" height="18" fill="currentColor" aria-hidden="true" focusable="false"><path d="${escapeHtml(icon.path)}"/></svg>`;
|
|
142
197
|
}
|
|
198
|
+
function brandIconForLink(link) {
|
|
199
|
+
const glyph = link.type === "custom" ? hostGlyph(link.url) : TYPE_GLYPHS[link.type];
|
|
200
|
+
return glyph ? renderGlyph(glyph) : "";
|
|
201
|
+
}
|
|
202
|
+
function brandIconForPlatform(platform, url) {
|
|
203
|
+
const key = platform.trim().toLowerCase();
|
|
204
|
+
const named = key === "blog" ? "rss" : Object.hasOwn(GLYPHS, key) ? key : null;
|
|
205
|
+
const glyph = named ?? hostGlyph(url);
|
|
206
|
+
return glyph ? renderGlyph(glyph) : "";
|
|
207
|
+
}
|
|
208
|
+
function brandIconForHost(url) {
|
|
209
|
+
const glyph = hostGlyph(url);
|
|
210
|
+
return glyph ? renderGlyph(glyph) : "";
|
|
211
|
+
}
|
|
143
212
|
|
|
144
213
|
// src/html/build-html.ts
|
|
214
|
+
var LABELS_EN = {
|
|
215
|
+
about: "About",
|
|
216
|
+
careers: "Experience",
|
|
217
|
+
projects: "Projects",
|
|
218
|
+
volunteering: "Volunteering",
|
|
219
|
+
skills: "Skills",
|
|
220
|
+
activity: "Developer activity",
|
|
221
|
+
education: "Education",
|
|
222
|
+
certifications: "Certifications",
|
|
223
|
+
publications: "Publications",
|
|
224
|
+
honors: "Honors & awards",
|
|
225
|
+
memberships: "Memberships",
|
|
226
|
+
courses: "Courses",
|
|
227
|
+
patents: "Patents",
|
|
228
|
+
testScores: "Test scores",
|
|
229
|
+
languages: "Languages",
|
|
230
|
+
recommendations: "Recommendations",
|
|
231
|
+
highlights: "Selected posts",
|
|
232
|
+
contact: "Contact",
|
|
233
|
+
skipLink: "Skip to main content",
|
|
234
|
+
localeNav: "Language",
|
|
235
|
+
featuredLinks: "Featured links",
|
|
236
|
+
otherLinks: "Links",
|
|
237
|
+
poweredBy: "Powered by"
|
|
238
|
+
};
|
|
239
|
+
var LABELS_JA = {
|
|
240
|
+
about: "\u81EA\u5DF1\u7D39\u4ECB",
|
|
241
|
+
careers: "\u7D4C\u6B74",
|
|
242
|
+
projects: "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8",
|
|
243
|
+
volunteering: "\u30DC\u30E9\u30F3\u30C6\u30A3\u30A2",
|
|
244
|
+
skills: "\u30B9\u30AD\u30EB",
|
|
245
|
+
activity: "\u958B\u767A\u30A2\u30AF\u30C6\u30A3\u30D3\u30C6\u30A3",
|
|
246
|
+
education: "\u5B66\u6B74",
|
|
247
|
+
certifications: "\u8CC7\u683C\u30FB\u8A8D\u5B9A",
|
|
248
|
+
publications: "\u51FA\u7248\u7269",
|
|
249
|
+
honors: "\u53D7\u8CDE\u30FB\u6804\u8A89",
|
|
250
|
+
memberships: "\u6240\u5C5E",
|
|
251
|
+
courses: "\u8B1B\u5EA7",
|
|
252
|
+
patents: "\u7279\u8A31",
|
|
253
|
+
testScores: "\u30C6\u30B9\u30C8\u30B9\u30B3\u30A2",
|
|
254
|
+
languages: "\u8A00\u8A9E",
|
|
255
|
+
recommendations: "\u63A8\u85A6",
|
|
256
|
+
highlights: "\u30D4\u30C3\u30AF\u30A2\u30C3\u30D7\u6295\u7A3F",
|
|
257
|
+
contact: "\u9023\u7D61\u5148",
|
|
258
|
+
skipLink: "\u30E1\u30A4\u30F3\u30B3\u30F3\u30C6\u30F3\u30C4\u3078\u30B9\u30AD\u30C3\u30D7",
|
|
259
|
+
localeNav: "\u8A00\u8A9E",
|
|
260
|
+
featuredLinks: "\u4E3B\u8981\u30EA\u30F3\u30AF",
|
|
261
|
+
otherLinks: "\u305D\u306E\u4ED6\u306E\u30EA\u30F3\u30AF",
|
|
262
|
+
poweredBy: "Powered by"
|
|
263
|
+
};
|
|
264
|
+
function isJapaneseLocale(resolvedLocale) {
|
|
265
|
+
return resolvedLocale.toLowerCase().split(/[-_]/)[0] === "ja";
|
|
266
|
+
}
|
|
267
|
+
function pickLabelPack(resolvedLocale) {
|
|
268
|
+
return isJapaneseLocale(resolvedLocale) ? LABELS_JA : LABELS_EN;
|
|
269
|
+
}
|
|
145
270
|
function escapeJsonLd(json) {
|
|
146
271
|
return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
147
272
|
}
|
|
@@ -154,6 +279,7 @@ var DEFAULT_TOKENS = {
|
|
|
154
279
|
"--takuhon-color-primary": "#2563eb",
|
|
155
280
|
"--takuhon-color-primary-contrast": "#ffffff",
|
|
156
281
|
"--takuhon-color-accent": "#4f46e5",
|
|
282
|
+
"--takuhon-color-heading": "#1e1888",
|
|
157
283
|
"--takuhon-font-family": "system-ui,-apple-system,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif"
|
|
158
284
|
};
|
|
159
285
|
var DEFAULT_TOKENS_DARK = {
|
|
@@ -164,7 +290,8 @@ var DEFAULT_TOKENS_DARK = {
|
|
|
164
290
|
"--takuhon-color-border": "#334155",
|
|
165
291
|
"--takuhon-color-primary": "#60a5fa",
|
|
166
292
|
"--takuhon-color-primary-contrast": "#0f172a",
|
|
167
|
-
"--takuhon-color-accent": "#818cf8"
|
|
293
|
+
"--takuhon-color-accent": "#818cf8",
|
|
294
|
+
"--takuhon-color-heading": "#c7d2fe"
|
|
168
295
|
};
|
|
169
296
|
var INTERNAL_TOKENS = {
|
|
170
297
|
"--takuhon-space-1": "4px",
|
|
@@ -193,7 +320,8 @@ var COLOR_TOKEN_VARS = {
|
|
|
193
320
|
border: "--takuhon-color-border",
|
|
194
321
|
accent: "--takuhon-color-accent",
|
|
195
322
|
primary: "--takuhon-color-primary",
|
|
196
|
-
primaryContrast: "--takuhon-color-primary-contrast"
|
|
323
|
+
primaryContrast: "--takuhon-color-primary-contrast",
|
|
324
|
+
heading: "--takuhon-color-heading"
|
|
197
325
|
};
|
|
198
326
|
var SAFE_COLOR = /^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\([A-Za-z0-9.,%/\s-]*\))$/;
|
|
199
327
|
var SAFE_FONT = /^[A-Za-z0-9\s,'"._-]+$/;
|
|
@@ -242,6 +370,8 @@ body{margin:0;color:var(--takuhon-color-text);background:var(--takuhon-color-bg)
|
|
|
242
370
|
main{max-width:var(--takuhon-max-content-width);margin:0 auto;padding:var(--takuhon-space-6) var(--takuhon-space-4)}
|
|
243
371
|
a{color:var(--takuhon-color-primary)}
|
|
244
372
|
a:focus-visible{outline:2px solid var(--takuhon-color-accent);outline-offset:2px;border-radius:var(--takuhon-radius-sm)}
|
|
373
|
+
.skip-link{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
|
|
374
|
+
.skip-link:focus{position:fixed;left:var(--takuhon-space-2);top:var(--takuhon-space-2);width:auto;height:auto;padding:var(--takuhon-space-2) var(--takuhon-space-4);background:var(--takuhon-color-primary);color:var(--takuhon-color-primary-contrast);border-radius:var(--takuhon-radius-sm);z-index:100}
|
|
245
375
|
h1{font-size:var(--takuhon-font-size-2xl);font-weight:700;line-height:1.2;margin:0 0 var(--takuhon-space-2)}
|
|
246
376
|
h2{font-size:var(--takuhon-font-size-xl);margin:0 0 var(--takuhon-space-3);padding-bottom:var(--takuhon-space-2);border-bottom:1px solid var(--takuhon-color-border)}
|
|
247
377
|
h3{font-size:var(--takuhon-font-size-lg);font-weight:600;margin:0}
|
|
@@ -249,44 +379,110 @@ header{margin-bottom:var(--takuhon-space-6);display:flow-root}
|
|
|
249
379
|
header .avatar{width:96px;height:96px;border-radius:var(--takuhon-radius-full);object-fit:cover;float:left;margin:0 var(--takuhon-space-3) var(--takuhon-space-3) 0;shape-outside:circle();border:1px solid var(--takuhon-color-border)}
|
|
250
380
|
.tagline{font-size:var(--takuhon-font-size-lg);color:var(--takuhon-color-text-muted);margin:0 0 var(--takuhon-space-2)}
|
|
251
381
|
.location{font-size:var(--takuhon-font-size-sm);color:var(--takuhon-color-text-muted);margin:0}
|
|
252
|
-
.bio{margin:var(--takuhon-space-3) 0 0}
|
|
253
382
|
section{margin:0 0 var(--takuhon-space-6)}
|
|
383
|
+
.bio-body h3{font-size:var(--takuhon-font-size-lg);font-weight:600;color:var(--takuhon-color-heading);margin:var(--takuhon-space-5) 0 var(--takuhon-space-2)}
|
|
384
|
+
.bio-body h4{font-size:var(--takuhon-font-size-base);font-weight:600;color:var(--takuhon-color-heading);margin:var(--takuhon-space-4) 0 var(--takuhon-space-2)}
|
|
385
|
+
.bio-body p{margin:0 0 var(--takuhon-space-3)}
|
|
386
|
+
.bio-body ul{list-style:disc;margin:0 0 var(--takuhon-space-3);padding-left:var(--takuhon-space-5)}
|
|
387
|
+
.bio-body li{margin:0 0 var(--takuhon-space-1)}
|
|
388
|
+
.bio-body hr{border:0;border-top:1px solid var(--takuhon-color-border);margin:var(--takuhon-space-5) 0}
|
|
254
389
|
ul{padding:0;margin:0;list-style:none}
|
|
255
390
|
.entries>li{margin:0 0 var(--takuhon-space-4)}
|
|
256
391
|
.entries--timeline>li{position:relative;display:flex;flex-direction:column;margin:0 0 0 var(--takuhon-space-2);padding:0 0 var(--takuhon-space-5) var(--takuhon-space-4);border-left:2px solid var(--takuhon-color-border)}
|
|
257
392
|
.entries--timeline>li:last-child{padding-bottom:0}
|
|
258
|
-
.entries--timeline>li::before{content:"";position:absolute;left:-7px;top:6px;width:12px;height:12px;border-radius:var(--takuhon-radius-full);background:var(--takuhon-color-
|
|
393
|
+
.entries--timeline>li::before{content:"";position:absolute;left:-7px;top:6px;width:12px;height:12px;border-radius:var(--takuhon-radius-full);background:var(--takuhon-color-text-muted)}
|
|
259
394
|
.entries--timeline>li.is-current::before{background:var(--takuhon-color-accent)}
|
|
260
395
|
.entries--timeline .meta{order:-1;margin:0 0 var(--takuhon-space-1)}
|
|
396
|
+
.entries--timeline>li>h3{font-size:var(--takuhon-font-size-base);font-weight:600;margin:0}
|
|
261
397
|
.entries--cards{display:grid;gap:var(--takuhon-space-3)}
|
|
262
398
|
.entries--cards>li{margin:0;padding:var(--takuhon-space-4);border:1px solid var(--takuhon-color-border);border-radius:var(--takuhon-radius-md);background:var(--takuhon-color-surface)}
|
|
263
399
|
.entries--cards>li.is-highlighted{border-color:var(--takuhon-color-accent)}
|
|
264
|
-
.
|
|
400
|
+
.entries h3 a{color:inherit;text-decoration:none}
|
|
401
|
+
.entries h3 a:hover{color:var(--takuhon-color-primary);text-decoration:underline}
|
|
402
|
+
.sub{margin:var(--takuhon-space-1) 0 0;color:var(--takuhon-color-text-muted)}
|
|
403
|
+
.sub a{color:inherit;text-decoration:none}
|
|
404
|
+
.sub a:hover{color:var(--takuhon-color-primary);text-decoration:underline}
|
|
405
|
+
.role-badge{margin:var(--takuhon-space-1) 0 0;font-size:var(--takuhon-font-size-sm);font-weight:600;letter-spacing:.02em;color:var(--takuhon-color-accent)}
|
|
406
|
+
.desc{margin:var(--takuhon-space-2) 0 0}
|
|
407
|
+
.entries--timeline .desc{white-space:pre-wrap}
|
|
408
|
+
.external-icon{width:.72em;height:.72em;margin-left:.35em;vertical-align:-.05em;opacity:.55}
|
|
265
409
|
.meta{margin:var(--takuhon-space-1) 0 0;color:var(--takuhon-color-text-muted);font-size:var(--takuhon-font-size-sm)}
|
|
266
410
|
.featured-links,.other-links{margin:0 0 var(--takuhon-space-6)}
|
|
267
411
|
.featured-links>ul,.other-links>ul{list-style:none;padding:0;margin:0;display:grid;gap:var(--takuhon-space-2)}
|
|
268
412
|
.featured-links>ul{grid-template-columns:repeat(auto-fill,minmax(220px,1fr))}
|
|
269
|
-
.featured-links a,.other-links a{display:flex;align-items:center;gap:var(--takuhon-space-2);min-height:var(--takuhon-tap-target);padding:var(--takuhon-space-2) var(--takuhon-space-3);background:var(--takuhon-color-surface);color:var(--takuhon-color-text);text-decoration:none;border:1px solid var(--takuhon-color-border);border-radius:var(--takuhon-radius-md)}
|
|
413
|
+
.featured-links a,.other-links a{display:flex;align-items:center;justify-content:space-between;gap:var(--takuhon-space-2);min-height:var(--takuhon-tap-target);padding:var(--takuhon-space-2) var(--takuhon-space-3);background:var(--takuhon-color-surface);color:var(--takuhon-color-text);text-decoration:none;border:1px solid var(--takuhon-color-border);border-radius:var(--takuhon-radius-md)}
|
|
270
414
|
.featured-links a:hover,.other-links a:hover{border-color:var(--takuhon-color-accent)}
|
|
271
415
|
.link-main{display:inline-flex;align-items:center;gap:var(--takuhon-space-2);min-width:0}
|
|
416
|
+
.link-type{font-size:var(--takuhon-font-size-sm);color:var(--takuhon-color-text-muted)}
|
|
272
417
|
.brand-icon{width:1.15em;height:1.15em;flex:none;opacity:.85}
|
|
273
418
|
.skills,.tags{display:flex;flex-wrap:wrap;gap:var(--takuhon-space-2)}
|
|
274
419
|
.skills>li,.tags>li{background:var(--takuhon-color-surface);border:1px solid var(--takuhon-color-border);border-radius:var(--takuhon-radius-full);padding:var(--takuhon-space-1) var(--takuhon-space-3);font-size:var(--takuhon-font-size-sm)}
|
|
420
|
+
.entries--cards .tags{margin-top:var(--takuhon-space-2)}
|
|
421
|
+
.tags>li{background:var(--takuhon-color-bg);color:var(--takuhon-color-text-muted);padding:2px var(--takuhon-space-2)}
|
|
275
422
|
.skills-groups{display:grid;gap:var(--takuhon-space-4)}
|
|
276
423
|
.skill-group h3{font-size:var(--takuhon-font-size-base);margin:0 0 var(--takuhon-space-2);color:var(--takuhon-color-text-muted);text-transform:uppercase;letter-spacing:.04em}
|
|
424
|
+
.vol-list{display:grid;gap:var(--takuhon-space-2)}
|
|
425
|
+
.vol{padding:var(--takuhon-space-3) 0;border-bottom:1px solid var(--takuhon-color-border)}
|
|
426
|
+
.vol:last-child{border-bottom:0}
|
|
427
|
+
.vol-org{margin:0;font-size:var(--takuhon-font-size-lg)}
|
|
428
|
+
.vol-org a{color:inherit;text-decoration:none}
|
|
429
|
+
.vol-org a:hover{color:var(--takuhon-color-primary);text-decoration:underline}
|
|
430
|
+
.vol-role{margin:var(--takuhon-space-1) 0 0;color:var(--takuhon-color-text-muted);font-size:var(--takuhon-font-size-sm)}
|
|
431
|
+
.vol-desc{margin:var(--takuhon-space-1) 0 0;font-size:var(--takuhon-font-size-sm)}
|
|
432
|
+
.vol-secondary{display:inline-flex;align-items:center;gap:var(--takuhon-space-1);margin-top:var(--takuhon-space-2);padding:2px var(--takuhon-space-2);font-size:var(--takuhon-font-size-sm);background:var(--takuhon-color-surface);border:1px solid var(--takuhon-color-border);border-radius:var(--takuhon-radius-full);color:var(--takuhon-color-text-muted);text-decoration:none}
|
|
433
|
+
.vol-secondary:hover{color:var(--takuhon-color-text);border-color:var(--takuhon-color-accent)}
|
|
434
|
+
.vol-secondary .brand-icon{width:1em;height:1em;opacity:.85}
|
|
277
435
|
.rec{margin:0 0 var(--takuhon-space-4)}
|
|
278
436
|
.rec blockquote{margin:0;padding-left:var(--takuhon-space-3);border-left:3px solid var(--takuhon-color-border)}
|
|
279
437
|
.rec figcaption{color:var(--takuhon-color-text-muted);font-size:var(--takuhon-font-size-sm);margin-top:var(--takuhon-space-2)}
|
|
280
|
-
nav.locales{display:flex;gap:var(--takuhon-space-3);margin-bottom:var(--takuhon-space-4);font-size:var(--takuhon-font-size-sm)}
|
|
281
|
-
.
|
|
282
|
-
|
|
438
|
+
nav.locales{display:flex;justify-content:flex-end;gap:var(--takuhon-space-3);margin-bottom:var(--takuhon-space-4);font-size:var(--takuhon-font-size-sm)}
|
|
439
|
+
nav.locales a,nav.locales [aria-current]{padding:var(--takuhon-space-1) var(--takuhon-space-2);border-radius:var(--takuhon-radius-sm);text-transform:uppercase}
|
|
440
|
+
nav.locales a{color:var(--takuhon-color-primary);text-decoration:none}
|
|
441
|
+
nav.locales a:hover{text-decoration:underline}
|
|
442
|
+
nav.locales [aria-current]{color:var(--takuhon-color-text);font-weight:700;background:var(--takuhon-color-surface)}
|
|
443
|
+
.activity svg{width:100%;height:auto}
|
|
444
|
+
footer.powered{max-width:var(--takuhon-max-content-width);margin:var(--takuhon-space-6) auto 0;padding:var(--takuhon-space-4) var(--takuhon-space-4) var(--takuhon-space-6);border-top:1px solid var(--takuhon-color-border);text-align:center;color:var(--takuhon-color-text-muted);font-size:var(--takuhon-font-size-sm)}
|
|
445
|
+
footer.powered p{margin:0 0 var(--takuhon-space-2)}
|
|
446
|
+
footer.powered p:last-child{margin-bottom:0}
|
|
447
|
+
footer.powered .powered-by a{color:var(--takuhon-color-primary);text-decoration:none}
|
|
448
|
+
footer.powered .powered-by a:hover{text-decoration:underline}
|
|
449
|
+
.highlights .highlights-intro{color:var(--takuhon-color-text-muted);margin:0 0 var(--takuhon-space-3)}
|
|
450
|
+
.highlights-track{display:flex;gap:var(--takuhon-space-3);list-style:none;margin:0;padding:0 0 var(--takuhon-space-2);overflow-x:auto;scroll-snap-type:x mandatory;scroll-padding-left:var(--takuhon-space-1);-webkit-overflow-scrolling:touch;overscroll-behavior-x:contain}
|
|
451
|
+
.highlight-card{position:relative;flex:0 0 85%;scroll-snap-align:start;display:flex;flex-direction:column;background:var(--takuhon-color-surface);border:1px solid var(--takuhon-color-border);border-radius:var(--takuhon-radius-md);overflow:hidden}
|
|
452
|
+
@media (min-width:640px){.highlight-card{flex-basis:calc((100% - var(--takuhon-space-3)) / 2)}}
|
|
453
|
+
@media (min-width:960px){.highlight-card{flex-basis:calc((100% - 2 * var(--takuhon-space-3)) / 3)}}
|
|
454
|
+
.highlight-card:hover{border-color:var(--takuhon-color-accent)}
|
|
455
|
+
.highlight-card:focus-within{outline:2px solid var(--takuhon-color-accent);outline-offset:2px}
|
|
456
|
+
.highlight-thumb{aspect-ratio:1 / 1;background:var(--takuhon-color-bg)}
|
|
457
|
+
.highlight-thumb img{display:block;width:100%;height:100%;object-fit:cover}
|
|
458
|
+
.highlight-body{padding:var(--takuhon-space-3);display:flex;flex-direction:column;gap:var(--takuhon-space-2)}
|
|
459
|
+
.highlight-body>*{margin:0}
|
|
460
|
+
.highlight-badge{display:inline-flex;align-items:center;gap:var(--takuhon-space-1);font-size:var(--takuhon-font-size-sm);color:var(--takuhon-color-text-muted)}
|
|
461
|
+
.highlight-badge .brand-icon{width:1em;height:1em;opacity:.85}
|
|
462
|
+
.highlight-title{font-size:var(--takuhon-font-size-base)}
|
|
463
|
+
.highlight-title a{color:var(--takuhon-color-text);text-decoration:none}
|
|
464
|
+
.highlight-title a:hover{color:var(--takuhon-color-primary)}
|
|
465
|
+
.highlight-title a::after{content:"";position:absolute;inset:0}
|
|
466
|
+
.highlight-title a:focus-visible{outline:none}
|
|
467
|
+
.highlight-date{font-size:var(--takuhon-font-size-sm);color:var(--takuhon-color-text-muted)}
|
|
468
|
+
.highlight-desc{font-size:var(--takuhon-font-size-sm)}
|
|
469
|
+
.highlight-cta{font-size:var(--takuhon-font-size-sm);font-weight:600;color:var(--takuhon-color-primary)}`;
|
|
470
|
+
var EXTERNAL_ICON = '<svg class="external-icon" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>';
|
|
471
|
+
function externalLink(url, innerEscaped) {
|
|
472
|
+
return `<a href="${escapeHtml(url)}" rel="noopener">${innerEscaped}${EXTERNAL_ICON}</a>`;
|
|
473
|
+
}
|
|
283
474
|
function renderEntry(entry) {
|
|
284
|
-
const
|
|
285
|
-
const heading =
|
|
475
|
+
const headHref = entry.url ? safeUrl(entry.url) : void 0;
|
|
476
|
+
const heading = headHref ? externalLink(headHref, escapeHtml(entry.heading)) : escapeHtml(entry.heading);
|
|
286
477
|
const parts = [`<h3>${heading}</h3>`];
|
|
287
|
-
if (entry.
|
|
478
|
+
if (entry.role) parts.push(`<p class="role-badge">${escapeHtml(entry.role)}</p>`);
|
|
479
|
+
if (entry.sub) {
|
|
480
|
+
const subHref = entry.subUrl ? safeUrl(entry.subUrl) : void 0;
|
|
481
|
+
const sub = subHref ? externalLink(subHref, escapeHtml(entry.sub)) : escapeHtml(entry.sub);
|
|
482
|
+
parts.push(`<p class="sub">${sub}</p>`);
|
|
483
|
+
}
|
|
288
484
|
if (entry.dates) parts.push(`<p class="meta">${entry.dates}</p>`);
|
|
289
|
-
if (entry.body) parts.push(`<p>${escapeHtml(entry.body)}</p>`);
|
|
485
|
+
if (entry.body) parts.push(`<p class="desc">${escapeHtml(entry.body)}</p>`);
|
|
290
486
|
if (entry.tags && entry.tags.length > 0) {
|
|
291
487
|
parts.push(
|
|
292
488
|
`<ul class="tags">${entry.tags.map((t) => `<li>${escapeHtml(t)}</li>`).join("")}</ul>`
|
|
@@ -300,8 +496,65 @@ function entryList(title, entries, variant) {
|
|
|
300
496
|
const cls = variant ? `entries entries--${variant}` : "entries";
|
|
301
497
|
return `<section><h2>${escapeHtml(title)}</h2><ul class="${cls}">${entries.map(renderEntry).join("")}</ul></section>`;
|
|
302
498
|
}
|
|
303
|
-
function
|
|
499
|
+
function renderInline(text) {
|
|
500
|
+
return escapeHtml(text).replace(/\*\*([^*]+?)\*\*/g, "<strong>$1</strong>");
|
|
501
|
+
}
|
|
502
|
+
function renderMarkdown(input) {
|
|
503
|
+
const lines = input.split("\n");
|
|
504
|
+
const out = [];
|
|
505
|
+
let i = 0;
|
|
506
|
+
while (i < lines.length) {
|
|
507
|
+
const trimmed = (lines[i] ?? "").trim();
|
|
508
|
+
if (trimmed === "") {
|
|
509
|
+
i++;
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
if (trimmed === "---") {
|
|
513
|
+
out.push("<hr>");
|
|
514
|
+
i++;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (trimmed.startsWith("### ")) {
|
|
518
|
+
out.push(`<h4>${renderInline(trimmed.slice(4))}</h4>`);
|
|
519
|
+
i++;
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
if (trimmed.startsWith("## ")) {
|
|
523
|
+
out.push(`<h3>${renderInline(trimmed.slice(3))}</h3>`);
|
|
524
|
+
i++;
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
if (trimmed.startsWith("- ")) {
|
|
528
|
+
const items = [];
|
|
529
|
+
while (i < lines.length) {
|
|
530
|
+
const cur = (lines[i] ?? "").trim();
|
|
531
|
+
if (!cur.startsWith("- ")) break;
|
|
532
|
+
items.push(`<li>${renderInline(cur.slice(2))}</li>`);
|
|
533
|
+
i++;
|
|
534
|
+
}
|
|
535
|
+
out.push(`<ul>${items.join("")}</ul>`);
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
const para = [];
|
|
539
|
+
while (i < lines.length) {
|
|
540
|
+
const cur = (lines[i] ?? "").trim();
|
|
541
|
+
if (cur === "" || cur === "---" || cur.startsWith("## ") || cur.startsWith("### ") || cur.startsWith("- ")) {
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
para.push(cur);
|
|
545
|
+
i++;
|
|
546
|
+
}
|
|
547
|
+
if (para.length > 0) out.push(`<p>${renderInline(para.join(" "))}</p>`);
|
|
548
|
+
}
|
|
549
|
+
return out.join("\n");
|
|
550
|
+
}
|
|
551
|
+
function renderBio(bio, heading) {
|
|
552
|
+
if (!bio) return "";
|
|
553
|
+
return `<section class="bio"><h2>${escapeHtml(heading)}</h2><div class="bio-body">${renderMarkdown(bio)}</div></section>`;
|
|
554
|
+
}
|
|
555
|
+
function renderHeader(p, localeNavHtml) {
|
|
304
556
|
const parts = [];
|
|
557
|
+
if (localeNavHtml) parts.push(localeNavHtml);
|
|
305
558
|
const avatarSrc = p.avatar?.url ? safeUrl(p.avatar.url) : void 0;
|
|
306
559
|
if (avatarSrc) {
|
|
307
560
|
parts.push(
|
|
@@ -311,33 +564,55 @@ function renderHeader(p) {
|
|
|
311
564
|
parts.push(`<h1>${escapeHtml(p.displayName)}</h1>`);
|
|
312
565
|
if (p.tagline) parts.push(`<p class="tagline">${escapeHtml(p.tagline)}</p>`);
|
|
313
566
|
if (p.location?.display) parts.push(`<p class="location">${escapeHtml(p.location.display)}</p>`);
|
|
314
|
-
if (p.bio) parts.push(`<p class="bio">${escapeHtml(p.bio)}</p>`);
|
|
315
567
|
return `<header>${parts.join("")}</header>`;
|
|
316
568
|
}
|
|
569
|
+
var LINK_TYPE_DISPLAY = {
|
|
570
|
+
website: "Website",
|
|
571
|
+
blog: "Blog",
|
|
572
|
+
github: "GitHub",
|
|
573
|
+
gitlab: "GitLab",
|
|
574
|
+
linkedin: "LinkedIn",
|
|
575
|
+
x: "X (Twitter)",
|
|
576
|
+
mastodon: "Mastodon",
|
|
577
|
+
bluesky: "Bluesky",
|
|
578
|
+
instagram: "Instagram",
|
|
579
|
+
youtube: "YouTube",
|
|
580
|
+
threads: "Threads",
|
|
581
|
+
facebook: "Facebook",
|
|
582
|
+
email: "Email",
|
|
583
|
+
rss: "RSS"
|
|
584
|
+
};
|
|
585
|
+
function linkTypePill(link, label) {
|
|
586
|
+
const display = LINK_TYPE_DISPLAY[link.type];
|
|
587
|
+
if (!display || display === label) return "";
|
|
588
|
+
return `<span class="link-type">${escapeHtml(display)}</span>`;
|
|
589
|
+
}
|
|
317
590
|
function renderLinkItem(link) {
|
|
318
|
-
const label =
|
|
319
|
-
const main = `<span class="link-main">${
|
|
591
|
+
const label = link.label ?? link.url;
|
|
592
|
+
const main = `<span class="link-main">${brandIconForLink(link)}<span>${escapeHtml(label)}</span></span>`;
|
|
593
|
+
const pill = link.label == null ? linkTypePill(link, label) : "";
|
|
320
594
|
const href = safeUrl(link.url);
|
|
321
|
-
return href ? `<li><a href="${escapeHtml(href)}" rel="me noopener">${main}</a></li>` : `<li>${main}</li>`;
|
|
595
|
+
return href ? `<li><a href="${escapeHtml(href)}" rel="me noopener">${main}${pill}</a></li>` : `<li>${main}${pill}</li>`;
|
|
322
596
|
}
|
|
323
597
|
function byOrder(a, b) {
|
|
324
598
|
return (a.order ?? 0) - (b.order ?? 0);
|
|
325
599
|
}
|
|
326
|
-
function
|
|
327
|
-
if (links.length === 0) return "";
|
|
600
|
+
function renderFeaturedLinks(links, ariaLabel) {
|
|
328
601
|
const featured = links.filter((l) => l.featured === true).sort(byOrder);
|
|
602
|
+
if (featured.length === 0) return "";
|
|
603
|
+
return `<nav class="featured-links" aria-label="${escapeHtml(ariaLabel)}"><ul>${featured.map(renderLinkItem).join("")}</ul></nav>`;
|
|
604
|
+
}
|
|
605
|
+
function renderOtherLinks(links, heading) {
|
|
329
606
|
const others = links.filter((l) => l.featured !== true).sort(byOrder);
|
|
330
|
-
|
|
331
|
-
return
|
|
332
|
-
group("featured-links", "Featured links", featured),
|
|
333
|
-
group("other-links", "Links", others)
|
|
334
|
-
].filter(Boolean).join("\n");
|
|
607
|
+
if (others.length === 0) return "";
|
|
608
|
+
return `<section class="other-links"><h2>${escapeHtml(heading)}</h2><ul>${others.map(renderLinkItem).join("")}</ul></section>`;
|
|
335
609
|
}
|
|
336
|
-
function renderSkills(skills, categories) {
|
|
610
|
+
function renderSkills(skills, categories, heading) {
|
|
337
611
|
if (skills.length === 0) return "";
|
|
612
|
+
const h = escapeHtml(heading);
|
|
338
613
|
const chips = (list) => `<ul class="skills">${list.map((s) => `<li>${escapeHtml(s.label)}</li>`).join("")}</ul>`;
|
|
339
614
|
if (!categories || categories.length === 0) {
|
|
340
|
-
return `<section><h2
|
|
615
|
+
return `<section><h2>${h}</h2>${chips(skills)}</section>`;
|
|
341
616
|
}
|
|
342
617
|
const UNCAT = "";
|
|
343
618
|
const buckets = /* @__PURE__ */ new Map();
|
|
@@ -347,7 +622,7 @@ function renderSkills(skills, categories) {
|
|
|
347
622
|
arr.push(s);
|
|
348
623
|
buckets.set(key, arr);
|
|
349
624
|
}
|
|
350
|
-
const group = (
|
|
625
|
+
const group = (heading2, list) => `<div class="skill-group">${heading2 !== void 0 ? `<h3>${escapeHtml(heading2)}</h3>` : ""}${chips(list)}</div>`;
|
|
351
626
|
const seen = /* @__PURE__ */ new Set();
|
|
352
627
|
const groups = [];
|
|
353
628
|
for (const cat of categories) {
|
|
@@ -362,14 +637,41 @@ function renderSkills(skills, categories) {
|
|
|
362
637
|
}
|
|
363
638
|
const uncategorized = buckets.get(UNCAT);
|
|
364
639
|
if (uncategorized && uncategorized.length > 0) groups.push(group(void 0, uncategorized));
|
|
365
|
-
return `<section><h2
|
|
640
|
+
return `<section><h2>${h}</h2><div class="skills-groups">${groups.join("")}</div></section>`;
|
|
366
641
|
}
|
|
367
|
-
function
|
|
642
|
+
function hostLabel(url) {
|
|
643
|
+
try {
|
|
644
|
+
const host = new URL(url).hostname.replace(/^www\./, "");
|
|
645
|
+
return host || url;
|
|
646
|
+
} catch {
|
|
647
|
+
return url;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
function secondaryLinkPill(link) {
|
|
651
|
+
const label = escapeHtml(link.label ?? hostLabel(link.url));
|
|
652
|
+
const href = safeUrl(link.url);
|
|
653
|
+
const glyph = href ? brandIconForHost(link.url) : "";
|
|
654
|
+
const inner = `${glyph}<span>${label}</span>`;
|
|
655
|
+
return href ? `<a class="vol-secondary" href="${escapeHtml(href)}" rel="noopener">${inner}</a>` : `<span class="vol-secondary">${inner}</span>`;
|
|
656
|
+
}
|
|
657
|
+
function renderVolunteering(items, heading) {
|
|
658
|
+
if (items.length === 0) return "";
|
|
659
|
+
const li = (v) => {
|
|
660
|
+
const orgHref = v.url ? safeUrl(v.url) : void 0;
|
|
661
|
+
const org = orgHref ? externalLink(orgHref, escapeHtml(v.organization)) : escapeHtml(v.organization);
|
|
662
|
+
const role = v.role ? `<p class="vol-role">${escapeHtml(v.role)}</p>` : "";
|
|
663
|
+
const desc = v.description ? `<p class="vol-desc">${escapeHtml(v.description)}</p>` : "";
|
|
664
|
+
const secondary = v.secondaryLink ? secondaryLinkPill(v.secondaryLink) : "";
|
|
665
|
+
return `<li class="vol"><div class="vol-head"><p class="vol-org">${org}</p>${role}</div>${desc}${secondary}</li>`;
|
|
666
|
+
};
|
|
667
|
+
return `<section><h2>${escapeHtml(heading)}</h2><ul class="vol-list">${items.map(li).join("")}</ul></section>`;
|
|
668
|
+
}
|
|
669
|
+
function renderLanguages(languages, heading) {
|
|
368
670
|
if (languages.length === 0) return "";
|
|
369
671
|
const items = languages.map((l) => `<li>${escapeHtml(`${l.displayName ?? l.language} \u2014 ${l.proficiency}`)}</li>`).join("");
|
|
370
|
-
return `<section><h2
|
|
672
|
+
return `<section><h2>${escapeHtml(heading)}</h2><ul class="entries">${items}</ul></section>`;
|
|
371
673
|
}
|
|
372
|
-
function renderRecommendations(recs) {
|
|
674
|
+
function renderRecommendations(recs, heading) {
|
|
373
675
|
if (recs.length === 0) return "";
|
|
374
676
|
const items = recs.map((r) => {
|
|
375
677
|
const authorHref = r.author.url ? safeUrl(r.author.url) : void 0;
|
|
@@ -378,9 +680,9 @@ function renderRecommendations(recs) {
|
|
|
378
680
|
const rel = r.relationship ? ` (${escapeHtml(r.relationship)})` : "";
|
|
379
681
|
return `<figure class="rec"><blockquote>${escapeHtml(r.body)}</blockquote><figcaption>\u2014 ${caption}${rel}</figcaption></figure>`;
|
|
380
682
|
}).join("");
|
|
381
|
-
return `<section><h2
|
|
683
|
+
return `<section><h2>${escapeHtml(heading)}</h2>${items}</section>`;
|
|
382
684
|
}
|
|
383
|
-
function renderContact(contact) {
|
|
685
|
+
function renderContact(contact, heading) {
|
|
384
686
|
const items = [];
|
|
385
687
|
if (contact.email) {
|
|
386
688
|
items.push(
|
|
@@ -392,33 +694,97 @@ function renderContact(contact) {
|
|
|
392
694
|
items.push(`<li><a href="${escapeHtml(formHref)}">Contact form</a></li>`);
|
|
393
695
|
}
|
|
394
696
|
if (items.length === 0) return "";
|
|
395
|
-
return `<section><h2
|
|
697
|
+
return `<section><h2>${escapeHtml(heading)}</h2><ul class="entries">${items.join("")}</ul></section>`;
|
|
698
|
+
}
|
|
699
|
+
var PLATFORM_DISPLAY = {
|
|
700
|
+
instagram: "Instagram",
|
|
701
|
+
x: "X",
|
|
702
|
+
linkedin: "LinkedIn",
|
|
703
|
+
github: "GitHub",
|
|
704
|
+
gitlab: "GitLab",
|
|
705
|
+
youtube: "YouTube",
|
|
706
|
+
threads: "Threads",
|
|
707
|
+
facebook: "Facebook",
|
|
708
|
+
mastodon: "Mastodon",
|
|
709
|
+
bluesky: "Bluesky",
|
|
710
|
+
zenn: "Zenn",
|
|
711
|
+
qiita: "Qiita",
|
|
712
|
+
note: "note",
|
|
713
|
+
blog: "Blog",
|
|
714
|
+
event: "Event",
|
|
715
|
+
artwork: "Artwork",
|
|
716
|
+
project: "Project"
|
|
717
|
+
};
|
|
718
|
+
function platformDisplay(platform) {
|
|
719
|
+
const label = PLATFORM_DISPLAY[platform.trim().toLowerCase()];
|
|
720
|
+
return typeof label === "string" ? label : platform;
|
|
721
|
+
}
|
|
722
|
+
function highlightBadge(h) {
|
|
723
|
+
return `<span class="highlight-badge">${brandIconForPlatform(h.platform, h.url)}<span>${escapeHtml(platformDisplay(h.platform))}</span></span>`;
|
|
724
|
+
}
|
|
725
|
+
function renderHighlightCard(h, locale) {
|
|
726
|
+
const imgSrc = h.image ? safeUrl(h.image) : void 0;
|
|
727
|
+
const img = imgSrc ? `<div class="highlight-thumb"><img src="${escapeHtml(imgSrc)}" alt="${escapeHtml(h.alt)}" loading="lazy" decoding="async"></div>` : "";
|
|
728
|
+
const href = h.url ? safeUrl(h.url) : void 0;
|
|
729
|
+
const name = platformDisplay(h.platform);
|
|
730
|
+
const cta = isJapaneseLocale(locale) ? `${name}\u3067\u898B\u308B` : `View on ${name}`;
|
|
731
|
+
const title = escapeHtml(h.title);
|
|
732
|
+
const titleEl = href ? `<h3 class="highlight-title"><a href="${escapeHtml(href)}" rel="noopener" aria-label="${escapeHtml(`${h.title} \u2014 ${cta}`)}">${title}</a></h3>` : `<h3 class="highlight-title">${title}</h3>`;
|
|
733
|
+
const date = h.postedAt ? `<p class="highlight-date">${timeTag(h.postedAt, locale)}</p>` : "";
|
|
734
|
+
const desc = h.description ? `<p class="highlight-desc">${escapeHtml(h.description)}</p>` : "";
|
|
735
|
+
const tags = h.tags && h.tags.length > 0 ? `<ul class="tags">${h.tags.map((t) => `<li>${escapeHtml(t)}</li>`).join("")}</ul>` : "";
|
|
736
|
+
const ctaEl = href ? `<span class="highlight-cta" aria-hidden="true">${escapeHtml(cta)} \u2192</span>` : "";
|
|
737
|
+
return `<li class="highlight-card">${img}<div class="highlight-body">${highlightBadge(h)}${titleEl}${date}${desc}${tags}${ctaEl}</div></li>`;
|
|
738
|
+
}
|
|
739
|
+
function renderHighlights(items, heading, intro, locale) {
|
|
740
|
+
if (items.length === 0) return "";
|
|
741
|
+
const introEl = intro ? `<p class="highlights-intro">${escapeHtml(intro)}</p>` : "";
|
|
742
|
+
const cards = items.map((h) => renderHighlightCard(h, locale)).join("");
|
|
743
|
+
return `<section class="highlights"><h2>${escapeHtml(heading)}</h2>${introEl}<ul class="highlights-track" role="list">${cards}</ul></section>`;
|
|
396
744
|
}
|
|
397
|
-
function renderActivity(snapshot) {
|
|
745
|
+
function renderActivity(snapshot, heading) {
|
|
398
746
|
if (!snapshot) return "";
|
|
399
747
|
const svg = renderActivitySvg(snapshot);
|
|
400
748
|
if (svg === "") return "";
|
|
401
|
-
return `<section class="activity"><h2
|
|
749
|
+
return `<section class="activity"><h2>${escapeHtml(heading)}</h2>${svg}</section>`;
|
|
402
750
|
}
|
|
403
751
|
function renderJsonLdScript(data) {
|
|
404
752
|
const payload = JSON.stringify(generateJsonLd(data));
|
|
405
753
|
return `<script type="application/ld+json">${escapeJsonLd(payload)}</script>`;
|
|
406
754
|
}
|
|
407
|
-
function renderLocaleNav(localeNav) {
|
|
755
|
+
function renderLocaleNav(localeNav, ariaLabel) {
|
|
408
756
|
const items = localeNav.map(
|
|
409
757
|
(l) => l.current ? `<span aria-current="true">${escapeHtml(l.locale)}</span>` : `<a href="${escapeHtml(l.href)}">${escapeHtml(l.locale)}</a>`
|
|
410
758
|
).join("");
|
|
411
|
-
return `<nav class="locales" aria-label="
|
|
759
|
+
return `<nav class="locales" aria-label="${escapeHtml(ariaLabel)}">${items}</nav>`;
|
|
412
760
|
}
|
|
761
|
+
var DATE_SEPARATOR_JA = " \u301C ";
|
|
762
|
+
var DATE_SEPARATOR_DEFAULT = " \u2013 ";
|
|
413
763
|
function renderProfileHtml(input) {
|
|
414
764
|
const d = input.localized;
|
|
415
765
|
const p = d.profile;
|
|
766
|
+
const locale = d.resolvedLocale;
|
|
416
767
|
const description = p.tagline ?? p.bio ?? "";
|
|
768
|
+
const L = {
|
|
769
|
+
...pickLabelPack(locale),
|
|
770
|
+
...d.settings.sectionLabels ?? {},
|
|
771
|
+
...input.labels
|
|
772
|
+
};
|
|
773
|
+
const omit = new Set(input.omitSections ?? []);
|
|
774
|
+
const keep = (key, html) => omit.has(key) ? "" : html;
|
|
775
|
+
const dateSeparator = isJapaneseLocale(locale) ? DATE_SEPARATOR_JA : DATE_SEPARATOR_DEFAULT;
|
|
776
|
+
const dates = (start, opts) => dateRange(start, { ...opts, locale, separator: dateSeparator });
|
|
417
777
|
const head = [
|
|
418
778
|
'<meta charset="utf-8">',
|
|
419
779
|
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
420
780
|
`<title>${escapeHtml(p.displayName)}</title>`,
|
|
421
781
|
description ? `<meta name="description" content="${escapeHtml(description.slice(0, 300))}">` : "",
|
|
782
|
+
// Data-derived Open Graph tags (the renderer owns these; asset tags like
|
|
783
|
+
// og:image and PWA/theme-color are the host's, via the `head` slot).
|
|
784
|
+
'<meta property="og:type" content="profile">',
|
|
785
|
+
`<meta property="og:title" content="${escapeHtml(p.displayName)}">`,
|
|
786
|
+
description ? `<meta property="og:description" content="${escapeHtml(description.slice(0, 300))}">` : "",
|
|
787
|
+
input.canonicalUrl ? `<meta property="og:url" content="${escapeHtml(input.canonicalUrl)}">` : "",
|
|
422
788
|
input.canonicalUrl ? `<link rel="canonical" href="${escapeHtml(input.canonicalUrl)}">` : "",
|
|
423
789
|
...input.alternates.map(
|
|
424
790
|
(a) => `<link rel="alternate" hreflang="${escapeHtml(a.hreflang)}" href="${escapeHtml(a.href)}">`
|
|
@@ -426,162 +792,168 @@ function renderProfileHtml(input) {
|
|
|
426
792
|
input.jsonLd ? renderJsonLdScript(d) : "",
|
|
427
793
|
input.contact ? '<link rel="stylesheet" href="/contact-widget.css">' : "",
|
|
428
794
|
`<style>${buildTokenCss(d.settings.appearance)}
|
|
429
|
-
${CSS}</style
|
|
795
|
+
${CSS}</style>`,
|
|
796
|
+
// First-party <head> slot (raw, unescaped): OG image tags, manifest link, etc.
|
|
797
|
+
input.slots?.head ?? ""
|
|
430
798
|
].filter(Boolean).join("\n ");
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
entryList(
|
|
436
|
-
"Experience",
|
|
799
|
+
const sections = {
|
|
800
|
+
about: renderBio(p.bio, L.about),
|
|
801
|
+
careers: entryList(
|
|
802
|
+
L.careers,
|
|
437
803
|
d.careers.map((c) => ({
|
|
438
804
|
heading: c.role,
|
|
439
805
|
sub: c.organization,
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
isCurrent: c.isCurrent,
|
|
443
|
-
locale: d.resolvedLocale
|
|
444
|
-
}),
|
|
806
|
+
subUrl: c.url,
|
|
807
|
+
dates: dates(c.startDate, { end: c.endDate, isCurrent: c.isCurrent }),
|
|
445
808
|
body: c.description,
|
|
446
|
-
url: c.url,
|
|
447
809
|
current: c.isCurrent
|
|
448
810
|
})),
|
|
449
811
|
"timeline"
|
|
450
812
|
),
|
|
451
|
-
entryList(
|
|
452
|
-
|
|
813
|
+
projects: entryList(
|
|
814
|
+
L.projects,
|
|
453
815
|
d.projects.map((x) => ({
|
|
454
816
|
heading: x.title,
|
|
455
|
-
dates: dateRange(x.startDate, { end: x.endDate, locale: d.resolvedLocale }),
|
|
456
|
-
body: x.description,
|
|
457
817
|
url: x.url,
|
|
818
|
+
role: x.role,
|
|
819
|
+
dates: dates(x.startDate, { end: x.endDate }),
|
|
820
|
+
body: x.description,
|
|
458
821
|
tags: x.tags,
|
|
459
822
|
highlighted: x.highlighted
|
|
460
823
|
})),
|
|
461
824
|
"cards"
|
|
462
825
|
),
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
826
|
+
volunteering: renderVolunteering(d.volunteering, L.volunteering),
|
|
827
|
+
skills: renderSkills(d.skills, d.settings.skillCategories, L.skills),
|
|
828
|
+
activity: renderActivity(input.activitySnapshot, L.activity),
|
|
829
|
+
education: entryList(
|
|
830
|
+
L.education,
|
|
467
831
|
d.education.map((e) => {
|
|
468
832
|
const degree = nonEmpty([e.degree, e.fieldOfStudy], ", ");
|
|
469
833
|
return {
|
|
470
834
|
heading: degree ?? e.institution,
|
|
471
835
|
sub: degree ? e.institution : void 0,
|
|
472
|
-
dates:
|
|
473
|
-
end: e.endDate,
|
|
474
|
-
isCurrent: e.isCurrent,
|
|
475
|
-
locale: d.resolvedLocale
|
|
476
|
-
}),
|
|
836
|
+
dates: dates(e.startDate, { end: e.endDate, isCurrent: e.isCurrent }),
|
|
477
837
|
body: e.description,
|
|
478
838
|
url: e.url
|
|
479
839
|
};
|
|
480
840
|
})
|
|
481
841
|
),
|
|
482
|
-
entryList(
|
|
483
|
-
|
|
842
|
+
certifications: entryList(
|
|
843
|
+
L.certifications,
|
|
484
844
|
d.certifications.map((c) => ({
|
|
485
845
|
heading: c.title,
|
|
486
846
|
sub: c.issuingOrganization,
|
|
487
|
-
dates:
|
|
847
|
+
dates: dates(c.issueDate, { end: c.expirationDate }),
|
|
488
848
|
url: c.url
|
|
489
849
|
}))
|
|
490
850
|
),
|
|
491
|
-
entryList(
|
|
492
|
-
|
|
851
|
+
publications: entryList(
|
|
852
|
+
L.publications,
|
|
493
853
|
d.publications.map((x) => ({
|
|
494
854
|
heading: x.title,
|
|
495
855
|
sub: nonEmpty([x.publisher, x.coAuthors?.join(", ")], " \xB7 "),
|
|
496
|
-
dates:
|
|
856
|
+
dates: dates(x.date, {}),
|
|
497
857
|
body: x.description,
|
|
498
858
|
url: x.url ?? (x.doi ? `https://doi.org/${x.doi}` : void 0)
|
|
499
859
|
}))
|
|
500
860
|
),
|
|
501
|
-
entryList(
|
|
502
|
-
|
|
861
|
+
honors: entryList(
|
|
862
|
+
L.honors,
|
|
503
863
|
d.honors.map((x) => ({
|
|
504
864
|
heading: x.title,
|
|
505
865
|
sub: x.issuer,
|
|
506
|
-
dates:
|
|
866
|
+
dates: dates(x.date, {}),
|
|
507
867
|
body: x.description,
|
|
508
868
|
url: x.url
|
|
509
869
|
}))
|
|
510
870
|
),
|
|
511
|
-
entryList(
|
|
512
|
-
|
|
871
|
+
memberships: entryList(
|
|
872
|
+
L.memberships,
|
|
513
873
|
d.memberships.map((x) => ({
|
|
514
874
|
heading: x.role ?? x.organization,
|
|
515
875
|
sub: x.role ? x.organization : void 0,
|
|
516
|
-
dates:
|
|
517
|
-
end: x.endDate,
|
|
518
|
-
isCurrent: x.isCurrent,
|
|
519
|
-
locale: d.resolvedLocale
|
|
520
|
-
}),
|
|
521
|
-
body: x.description,
|
|
522
|
-
url: x.url
|
|
523
|
-
}))
|
|
524
|
-
),
|
|
525
|
-
entryList(
|
|
526
|
-
"Volunteering",
|
|
527
|
-
d.volunteering.map((x) => ({
|
|
528
|
-
heading: x.role,
|
|
529
|
-
sub: nonEmpty([x.organization, x.cause], " \xB7 "),
|
|
530
|
-
dates: dateRange(x.startDate, {
|
|
531
|
-
end: x.endDate,
|
|
532
|
-
isCurrent: x.isCurrent,
|
|
533
|
-
locale: d.resolvedLocale
|
|
534
|
-
}),
|
|
876
|
+
dates: dates(x.startDate, { end: x.endDate, isCurrent: x.isCurrent }),
|
|
535
877
|
body: x.description,
|
|
536
878
|
url: x.url
|
|
537
879
|
}))
|
|
538
880
|
),
|
|
539
|
-
entryList(
|
|
540
|
-
|
|
881
|
+
courses: entryList(
|
|
882
|
+
L.courses,
|
|
541
883
|
d.courses.map((x) => ({
|
|
542
884
|
heading: x.title,
|
|
543
885
|
sub: x.provider,
|
|
544
|
-
dates:
|
|
886
|
+
dates: dates(x.completionDate, {}),
|
|
545
887
|
body: x.description,
|
|
546
888
|
url: x.certificateUrl
|
|
547
889
|
}))
|
|
548
890
|
),
|
|
549
|
-
entryList(
|
|
550
|
-
|
|
891
|
+
patents: entryList(
|
|
892
|
+
L.patents,
|
|
551
893
|
d.patents.map((x) => ({
|
|
552
894
|
heading: x.title,
|
|
553
895
|
sub: nonEmpty([x.patentNumber, x.office, x.status, x.coInventors?.join(", ")], " \xB7 "),
|
|
554
|
-
dates:
|
|
896
|
+
dates: dates(x.filingDate ?? x.grantDate, {}),
|
|
555
897
|
body: x.description,
|
|
556
898
|
url: x.url
|
|
557
899
|
}))
|
|
558
900
|
),
|
|
559
|
-
entryList(
|
|
560
|
-
|
|
901
|
+
testScores: entryList(
|
|
902
|
+
L.testScores,
|
|
561
903
|
d.testScores.map((x) => ({
|
|
562
904
|
heading: `${x.title}: ${x.score}`,
|
|
563
|
-
dates:
|
|
905
|
+
dates: dates(x.date, {}),
|
|
564
906
|
body: x.description,
|
|
565
907
|
url: x.url
|
|
566
908
|
}))
|
|
567
909
|
),
|
|
568
|
-
renderLanguages(d.languages),
|
|
569
|
-
renderRecommendations(d.recommendations),
|
|
570
|
-
|
|
910
|
+
languages: renderLanguages(d.languages, L.languages),
|
|
911
|
+
recommendations: renderRecommendations(d.recommendations, L.recommendations),
|
|
912
|
+
highlights: renderHighlights(d.highlights, L.highlights, d.settings.highlightsIntro, locale),
|
|
913
|
+
contact: renderContact(d.contact, L.contact)
|
|
914
|
+
};
|
|
915
|
+
const localeNavHtml = input.localeNav.length > 1 ? renderLocaleNav(input.localeNav, L.localeNav) : "";
|
|
916
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
917
|
+
const orderedKeys = [];
|
|
918
|
+
for (const key of d.settings.sectionOrder ?? []) {
|
|
919
|
+
if (SECTION_KEYS.includes(key) && !seenKeys.has(key)) {
|
|
920
|
+
seenKeys.add(key);
|
|
921
|
+
orderedKeys.push(key);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
for (const key of SECTION_KEYS) {
|
|
925
|
+
if (!seenKeys.has(key)) orderedKeys.push(key);
|
|
926
|
+
}
|
|
927
|
+
const body = [
|
|
928
|
+
renderHeader(p, localeNavHtml),
|
|
929
|
+
// Featured links at the top; the remaining links are a fixed bottom section.
|
|
930
|
+
renderFeaturedLinks(d.links, L.featuredLinks),
|
|
931
|
+
...orderedKeys.map((key) => keep(key, sections[key])),
|
|
932
|
+
renderOtherLinks(d.links, L.otherLinks),
|
|
933
|
+
input.slots?.mainEnd ?? ""
|
|
571
934
|
].filter(Boolean).join("\n");
|
|
572
|
-
const
|
|
935
|
+
const spdx = d.meta?.contentLicense?.spdxId;
|
|
936
|
+
const year = typeof input.year === "number" && Number.isFinite(input.year) ? input.year : void 0;
|
|
937
|
+
const licenseText = year !== void 0 ? `\xA9 ${year} ${escapeHtml(p.displayName)} \u2014 ${escapeHtml(spdx ?? "")}` : `\xA9 ${escapeHtml(p.displayName)} \u2014 ${escapeHtml(spdx ?? "")}`;
|
|
938
|
+
const licenseLine = spdx ? `<p class="license">${licenseText}</p>` : "";
|
|
939
|
+
const poweredByLine = d.settings.showPoweredBy !== false ? `<p class="powered-by">${escapeHtml(L.poweredBy)} <a href="https://takuhon.org/" rel="noopener">Takuhon</a></p>` : "";
|
|
940
|
+
const footerInner = `${licenseLine}${poweredByLine}`;
|
|
941
|
+
const footer = footerInner ? `<footer class="powered">${footerInner}</footer>` : "";
|
|
573
942
|
const contactScript = input.contact ? `<script src="/contact-widget.js" data-site-key="${escapeHtml(input.contact.siteKey)}"` + (input.contact.endpoint ? ` data-endpoint="${escapeHtml(input.contact.endpoint)}"` : "") + " defer></script>\n" : "";
|
|
943
|
+
const skipLink = `<a class="skip-link" href="#main">${escapeHtml(L.skipLink)}</a>
|
|
944
|
+
`;
|
|
945
|
+
const bodyEnd = input.slots?.bodyEnd ?? "";
|
|
574
946
|
return `<!DOCTYPE html>
|
|
575
947
|
<html lang="${escapeHtml(d.resolvedLocale)}">
|
|
576
948
|
<head>
|
|
577
949
|
${head}
|
|
578
950
|
</head>
|
|
579
951
|
<body>
|
|
580
|
-
<main>
|
|
952
|
+
${skipLink}<main id="main">
|
|
581
953
|
${body}
|
|
582
954
|
</main>
|
|
583
955
|
${footer ? `${footer}
|
|
584
|
-
` : ""}${contactScript}</body>
|
|
956
|
+
` : ""}${contactScript}${bodyEnd}</body>
|
|
585
957
|
</html>
|
|
586
958
|
`;
|
|
587
959
|
}
|
|
@@ -706,7 +1078,33 @@ function pathLocaleFromUrl(url) {
|
|
|
706
1078
|
// src/public-app.ts
|
|
707
1079
|
var FALLBACK_VERSION = "bundled-fixture";
|
|
708
1080
|
var TURNSTILE_ORIGIN = "https://challenges.cloudflare.com";
|
|
709
|
-
|
|
1081
|
+
var CSP_SOURCE = /^[!-~]+$/;
|
|
1082
|
+
var CSP_HASH = /^'sha(256|384|512)-[A-Za-z0-9+/]+={0,2}'$/;
|
|
1083
|
+
var CSP_BLANKET = /* @__PURE__ */ new Set(["'unsafe-inline'", "'unsafe-eval'"]);
|
|
1084
|
+
function sanitizeCsp(ext) {
|
|
1085
|
+
const dropped = [];
|
|
1086
|
+
const keep = (tokens, ok) => (tokens ?? []).filter((t) => {
|
|
1087
|
+
if (ok(t)) return true;
|
|
1088
|
+
dropped.push(t);
|
|
1089
|
+
return false;
|
|
1090
|
+
});
|
|
1091
|
+
const source = (t) => CSP_SOURCE.test(t) && !t.includes(";") && !t.includes(",") && !CSP_BLANKET.has(t.toLowerCase());
|
|
1092
|
+
return {
|
|
1093
|
+
clean: {
|
|
1094
|
+
scriptSrc: keep(ext.scriptSrc, source),
|
|
1095
|
+
connectSrc: keep(ext.connectSrc, source),
|
|
1096
|
+
workerSrc: keep(ext.workerSrc, source),
|
|
1097
|
+
scriptHashes: keep(ext.scriptHashes, (t) => CSP_HASH.test(t))
|
|
1098
|
+
},
|
|
1099
|
+
dropped
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
function buildPublicCsp(contact, ext) {
|
|
1103
|
+
const turnstile = contact ? [TURNSTILE_ORIGIN] : [];
|
|
1104
|
+
const scriptSrc = ["'self'", ...turnstile, ...ext?.scriptSrc ?? [], ...ext?.scriptHashes ?? []];
|
|
1105
|
+
const connectSrc = ["'self'", ...turnstile, ...ext?.connectSrc ?? []];
|
|
1106
|
+
const frameSrc = [...turnstile];
|
|
1107
|
+
const workerSrc = ext?.workerSrc ?? [];
|
|
710
1108
|
return [
|
|
711
1109
|
"default-src 'self'",
|
|
712
1110
|
// `https:` lets the server-rendered profile page load remote avatar images
|
|
@@ -714,10 +1112,11 @@ function buildPublicCsp(contact) {
|
|
|
714
1112
|
// already blocks non-http(s) schemes); `data:` covers inline placeholders.
|
|
715
1113
|
"img-src 'self' https: data:",
|
|
716
1114
|
"style-src 'self' 'unsafe-inline'",
|
|
717
|
-
|
|
1115
|
+
`script-src ${scriptSrc.join(" ")}`,
|
|
718
1116
|
"font-src 'self'",
|
|
719
|
-
|
|
720
|
-
...
|
|
1117
|
+
`connect-src ${connectSrc.join(" ")}`,
|
|
1118
|
+
...frameSrc.length > 0 ? [`frame-src ${frameSrc.join(" ")}`] : [],
|
|
1119
|
+
...workerSrc.length > 0 ? [`worker-src ${workerSrc.join(" ")}`] : [],
|
|
721
1120
|
"frame-ancestors 'none'",
|
|
722
1121
|
"base-uri 'self'",
|
|
723
1122
|
"form-action 'self'",
|
|
@@ -738,6 +1137,19 @@ async function loadProfile(deps) {
|
|
|
738
1137
|
}
|
|
739
1138
|
function createPublicApp(deps) {
|
|
740
1139
|
const app = new Hono({ getPath: localePrefixGetPath });
|
|
1140
|
+
const cspExt = deps.render?.csp;
|
|
1141
|
+
let pageCsp = PUBLIC_CSP;
|
|
1142
|
+
let pageCspWithContact = PUBLIC_CSP_WITH_CONTACT;
|
|
1143
|
+
if (cspExt) {
|
|
1144
|
+
const { clean, dropped } = sanitizeCsp(cspExt);
|
|
1145
|
+
if (dropped.length > 0) {
|
|
1146
|
+
console.warn(
|
|
1147
|
+
`[takuhon] Ignored ${dropped.length} invalid render.csp token(s): ${dropped.join(", ")}`
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
pageCsp = buildPublicCsp(false, clean);
|
|
1151
|
+
pageCspWithContact = buildPublicCsp(true, clean);
|
|
1152
|
+
}
|
|
741
1153
|
app.use("*", async (c, next) => {
|
|
742
1154
|
await next();
|
|
743
1155
|
const h = c.res.headers;
|
|
@@ -746,9 +1158,11 @@ function createPublicApp(deps) {
|
|
|
746
1158
|
h.set("x-frame-options", "DENY");
|
|
747
1159
|
h.set("referrer-policy", "strict-origin-when-cross-origin");
|
|
748
1160
|
h.set("permissions-policy", "camera=(), microphone=(), geolocation=(), interest-cohort=()");
|
|
1161
|
+
const contact = c.get("contactEnabled") === true;
|
|
1162
|
+
const page = c.get("profilePage") === true;
|
|
749
1163
|
h.set(
|
|
750
1164
|
"content-security-policy",
|
|
751
|
-
|
|
1165
|
+
page ? contact ? pageCspWithContact : pageCsp : contact ? PUBLIC_CSP_WITH_CONTACT : PUBLIC_CSP
|
|
752
1166
|
);
|
|
753
1167
|
h.set("access-control-allow-origin", "*");
|
|
754
1168
|
h.set("access-control-expose-headers", "ETag");
|
|
@@ -793,6 +1207,14 @@ function createPublicApp(deps) {
|
|
|
793
1207
|
} : void 0;
|
|
794
1208
|
if (contact) c.set("contactEnabled", true);
|
|
795
1209
|
const html = renderProfileHtml({
|
|
1210
|
+
// Host composition seams (slots / labels / omitSections). `csp` is a
|
|
1211
|
+
// response-header concern handled above, not a renderer input, so it is
|
|
1212
|
+
// intentionally excluded here.
|
|
1213
|
+
...deps.render ? {
|
|
1214
|
+
slots: deps.render.slots,
|
|
1215
|
+
labels: deps.render.labels,
|
|
1216
|
+
omitSections: deps.render.omitSections
|
|
1217
|
+
} : {},
|
|
796
1218
|
localized,
|
|
797
1219
|
canonicalUrl: `${origin}${localePath(current)}`,
|
|
798
1220
|
alternates: [
|
|
@@ -802,11 +1224,13 @@ function createPublicApp(deps) {
|
|
|
802
1224
|
localeNav: locales.map((l) => ({ locale: l, href: localePath(l), current: l === current })),
|
|
803
1225
|
jsonLd: profile.settings.enableJsonLd !== false,
|
|
804
1226
|
activitySnapshot: snapshot ?? void 0,
|
|
805
|
-
contact
|
|
1227
|
+
contact,
|
|
1228
|
+
year: (/* @__PURE__ */ new Date()).getFullYear()
|
|
806
1229
|
});
|
|
807
1230
|
c.header("etag", `"${version}"`);
|
|
808
1231
|
c.header("cache-control", "public, max-age=300");
|
|
809
1232
|
c.header("vary", "Accept-Language, Cookie");
|
|
1233
|
+
c.set("profilePage", true);
|
|
810
1234
|
return c.html(html);
|
|
811
1235
|
});
|
|
812
1236
|
app.get("/health", (c) => {
|
|
@@ -1761,7 +2185,8 @@ function generateSite(profile, options = {}) {
|
|
|
1761
2185
|
alternates,
|
|
1762
2186
|
localeNav,
|
|
1763
2187
|
jsonLd,
|
|
1764
|
-
activitySnapshot
|
|
2188
|
+
activitySnapshot,
|
|
2189
|
+
year: (/* @__PURE__ */ new Date()).getFullYear()
|
|
1765
2190
|
});
|
|
1766
2191
|
pages.push({
|
|
1767
2192
|
route: isDefault ? "/" : `/${locale}/`,
|