@takuhon/api 1.3.0 → 1.4.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.
- package/NOTICE +1 -1
- package/dist/index.d.ts +209 -75
- package/dist/index.js +550 -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,111 @@ 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-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--takuhon-space-1) var(--takuhon-space-3)}
|
|
428
|
+
.vol-org{font-size:var(--takuhon-font-size-lg)}
|
|
429
|
+
.vol-org a{color:inherit;text-decoration:none}
|
|
430
|
+
.vol-org a:hover{color:var(--takuhon-color-primary);text-decoration:underline}
|
|
431
|
+
.vol-role{color:var(--takuhon-color-text-muted);font-size:var(--takuhon-font-size-sm)}
|
|
432
|
+
.vol-desc{margin:var(--takuhon-space-1) 0 0;font-size:var(--takuhon-font-size-sm)}
|
|
433
|
+
.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}
|
|
434
|
+
.vol-secondary:hover{color:var(--takuhon-color-text);border-color:var(--takuhon-color-accent)}
|
|
435
|
+
.vol-secondary .brand-icon{width:1em;height:1em;opacity:.85}
|
|
277
436
|
.rec{margin:0 0 var(--takuhon-space-4)}
|
|
278
437
|
.rec blockquote{margin:0;padding-left:var(--takuhon-space-3);border-left:3px solid var(--takuhon-color-border)}
|
|
279
438
|
.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
|
-
|
|
439
|
+
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)}
|
|
440
|
+
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}
|
|
441
|
+
nav.locales a{color:var(--takuhon-color-primary);text-decoration:none}
|
|
442
|
+
nav.locales a:hover{text-decoration:underline}
|
|
443
|
+
nav.locales [aria-current]{color:var(--takuhon-color-text);font-weight:700;background:var(--takuhon-color-surface)}
|
|
444
|
+
.activity svg{width:100%;height:auto}
|
|
445
|
+
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)}
|
|
446
|
+
footer.powered p{margin:0 0 var(--takuhon-space-2)}
|
|
447
|
+
footer.powered p:last-child{margin-bottom:0}
|
|
448
|
+
footer.powered .powered-by a{color:var(--takuhon-color-primary);text-decoration:none}
|
|
449
|
+
footer.powered .powered-by a:hover{text-decoration:underline}
|
|
450
|
+
.highlights .highlights-intro{color:var(--takuhon-color-text-muted);margin:0 0 var(--takuhon-space-3)}
|
|
451
|
+
.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}
|
|
452
|
+
.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}
|
|
453
|
+
@media (min-width:640px){.highlight-card{flex-basis:calc((100% - var(--takuhon-space-3)) / 2)}}
|
|
454
|
+
@media (min-width:960px){.highlight-card{flex-basis:calc((100% - 2 * var(--takuhon-space-3)) / 3)}}
|
|
455
|
+
.highlight-card:hover{border-color:var(--takuhon-color-accent)}
|
|
456
|
+
.highlight-card:focus-within{outline:2px solid var(--takuhon-color-accent);outline-offset:2px}
|
|
457
|
+
.highlight-thumb{aspect-ratio:1 / 1;background:var(--takuhon-color-bg)}
|
|
458
|
+
.highlight-thumb img{display:block;width:100%;height:100%;object-fit:cover}
|
|
459
|
+
.highlight-body{padding:var(--takuhon-space-3);display:flex;flex-direction:column;gap:var(--takuhon-space-2)}
|
|
460
|
+
.highlight-body>*{margin:0}
|
|
461
|
+
.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)}
|
|
462
|
+
.highlight-badge .brand-icon{width:1em;height:1em;opacity:.85}
|
|
463
|
+
.highlight-title{font-size:var(--takuhon-font-size-base)}
|
|
464
|
+
.highlight-title a{color:var(--takuhon-color-text);text-decoration:none}
|
|
465
|
+
.highlight-title a:hover{color:var(--takuhon-color-primary)}
|
|
466
|
+
.highlight-title a::after{content:"";position:absolute;inset:0}
|
|
467
|
+
.highlight-title a:focus-visible{outline:none}
|
|
468
|
+
.highlight-date{font-size:var(--takuhon-font-size-sm);color:var(--takuhon-color-text-muted)}
|
|
469
|
+
.highlight-desc{font-size:var(--takuhon-font-size-sm)}
|
|
470
|
+
.highlight-cta{font-size:var(--takuhon-font-size-sm);font-weight:600;color:var(--takuhon-color-primary)}`;
|
|
471
|
+
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>';
|
|
472
|
+
function externalLink(url, innerEscaped) {
|
|
473
|
+
return `<a href="${escapeHtml(url)}" rel="noopener">${innerEscaped}${EXTERNAL_ICON}</a>`;
|
|
474
|
+
}
|
|
283
475
|
function renderEntry(entry) {
|
|
284
|
-
const
|
|
285
|
-
const heading =
|
|
476
|
+
const headHref = entry.url ? safeUrl(entry.url) : void 0;
|
|
477
|
+
const heading = headHref ? externalLink(headHref, escapeHtml(entry.heading)) : escapeHtml(entry.heading);
|
|
286
478
|
const parts = [`<h3>${heading}</h3>`];
|
|
287
|
-
if (entry.
|
|
479
|
+
if (entry.role) parts.push(`<p class="role-badge">${escapeHtml(entry.role)}</p>`);
|
|
480
|
+
if (entry.sub) {
|
|
481
|
+
const subHref = entry.subUrl ? safeUrl(entry.subUrl) : void 0;
|
|
482
|
+
const sub = subHref ? externalLink(subHref, escapeHtml(entry.sub)) : escapeHtml(entry.sub);
|
|
483
|
+
parts.push(`<p class="sub">${sub}</p>`);
|
|
484
|
+
}
|
|
288
485
|
if (entry.dates) parts.push(`<p class="meta">${entry.dates}</p>`);
|
|
289
|
-
if (entry.body) parts.push(`<p>${escapeHtml(entry.body)}</p>`);
|
|
486
|
+
if (entry.body) parts.push(`<p class="desc">${escapeHtml(entry.body)}</p>`);
|
|
290
487
|
if (entry.tags && entry.tags.length > 0) {
|
|
291
488
|
parts.push(
|
|
292
489
|
`<ul class="tags">${entry.tags.map((t) => `<li>${escapeHtml(t)}</li>`).join("")}</ul>`
|
|
@@ -300,8 +497,65 @@ function entryList(title, entries, variant) {
|
|
|
300
497
|
const cls = variant ? `entries entries--${variant}` : "entries";
|
|
301
498
|
return `<section><h2>${escapeHtml(title)}</h2><ul class="${cls}">${entries.map(renderEntry).join("")}</ul></section>`;
|
|
302
499
|
}
|
|
303
|
-
function
|
|
500
|
+
function renderInline(text) {
|
|
501
|
+
return escapeHtml(text).replace(/\*\*([^*]+?)\*\*/g, "<strong>$1</strong>");
|
|
502
|
+
}
|
|
503
|
+
function renderMarkdown(input) {
|
|
504
|
+
const lines = input.split("\n");
|
|
505
|
+
const out = [];
|
|
506
|
+
let i = 0;
|
|
507
|
+
while (i < lines.length) {
|
|
508
|
+
const trimmed = (lines[i] ?? "").trim();
|
|
509
|
+
if (trimmed === "") {
|
|
510
|
+
i++;
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
if (trimmed === "---") {
|
|
514
|
+
out.push("<hr>");
|
|
515
|
+
i++;
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
if (trimmed.startsWith("### ")) {
|
|
519
|
+
out.push(`<h4>${renderInline(trimmed.slice(4))}</h4>`);
|
|
520
|
+
i++;
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (trimmed.startsWith("## ")) {
|
|
524
|
+
out.push(`<h3>${renderInline(trimmed.slice(3))}</h3>`);
|
|
525
|
+
i++;
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
if (trimmed.startsWith("- ")) {
|
|
529
|
+
const items = [];
|
|
530
|
+
while (i < lines.length) {
|
|
531
|
+
const cur = (lines[i] ?? "").trim();
|
|
532
|
+
if (!cur.startsWith("- ")) break;
|
|
533
|
+
items.push(`<li>${renderInline(cur.slice(2))}</li>`);
|
|
534
|
+
i++;
|
|
535
|
+
}
|
|
536
|
+
out.push(`<ul>${items.join("")}</ul>`);
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
const para = [];
|
|
540
|
+
while (i < lines.length) {
|
|
541
|
+
const cur = (lines[i] ?? "").trim();
|
|
542
|
+
if (cur === "" || cur === "---" || cur.startsWith("## ") || cur.startsWith("### ") || cur.startsWith("- ")) {
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
para.push(cur);
|
|
546
|
+
i++;
|
|
547
|
+
}
|
|
548
|
+
if (para.length > 0) out.push(`<p>${renderInline(para.join(" "))}</p>`);
|
|
549
|
+
}
|
|
550
|
+
return out.join("\n");
|
|
551
|
+
}
|
|
552
|
+
function renderBio(bio, heading) {
|
|
553
|
+
if (!bio) return "";
|
|
554
|
+
return `<section class="bio"><h2>${escapeHtml(heading)}</h2><div class="bio-body">${renderMarkdown(bio)}</div></section>`;
|
|
555
|
+
}
|
|
556
|
+
function renderHeader(p, localeNavHtml) {
|
|
304
557
|
const parts = [];
|
|
558
|
+
if (localeNavHtml) parts.push(localeNavHtml);
|
|
305
559
|
const avatarSrc = p.avatar?.url ? safeUrl(p.avatar.url) : void 0;
|
|
306
560
|
if (avatarSrc) {
|
|
307
561
|
parts.push(
|
|
@@ -311,33 +565,55 @@ function renderHeader(p) {
|
|
|
311
565
|
parts.push(`<h1>${escapeHtml(p.displayName)}</h1>`);
|
|
312
566
|
if (p.tagline) parts.push(`<p class="tagline">${escapeHtml(p.tagline)}</p>`);
|
|
313
567
|
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
568
|
return `<header>${parts.join("")}</header>`;
|
|
316
569
|
}
|
|
570
|
+
var LINK_TYPE_DISPLAY = {
|
|
571
|
+
website: "Website",
|
|
572
|
+
blog: "Blog",
|
|
573
|
+
github: "GitHub",
|
|
574
|
+
gitlab: "GitLab",
|
|
575
|
+
linkedin: "LinkedIn",
|
|
576
|
+
x: "X (Twitter)",
|
|
577
|
+
mastodon: "Mastodon",
|
|
578
|
+
bluesky: "Bluesky",
|
|
579
|
+
instagram: "Instagram",
|
|
580
|
+
youtube: "YouTube",
|
|
581
|
+
threads: "Threads",
|
|
582
|
+
facebook: "Facebook",
|
|
583
|
+
email: "Email",
|
|
584
|
+
rss: "RSS"
|
|
585
|
+
};
|
|
586
|
+
function linkTypePill(link, label) {
|
|
587
|
+
const display = LINK_TYPE_DISPLAY[link.type];
|
|
588
|
+
if (!display || display === label) return "";
|
|
589
|
+
return `<span class="link-type">${escapeHtml(display)}</span>`;
|
|
590
|
+
}
|
|
317
591
|
function renderLinkItem(link) {
|
|
318
|
-
const label =
|
|
319
|
-
const main = `<span class="link-main">${
|
|
592
|
+
const label = link.label ?? link.url;
|
|
593
|
+
const main = `<span class="link-main">${brandIconForLink(link)}<span>${escapeHtml(label)}</span></span>`;
|
|
594
|
+
const pill = link.label == null ? linkTypePill(link, label) : "";
|
|
320
595
|
const href = safeUrl(link.url);
|
|
321
|
-
return href ? `<li><a href="${escapeHtml(href)}" rel="me noopener">${main}</a></li>` : `<li>${main}</li>`;
|
|
596
|
+
return href ? `<li><a href="${escapeHtml(href)}" rel="me noopener">${main}${pill}</a></li>` : `<li>${main}${pill}</li>`;
|
|
322
597
|
}
|
|
323
598
|
function byOrder(a, b) {
|
|
324
599
|
return (a.order ?? 0) - (b.order ?? 0);
|
|
325
600
|
}
|
|
326
|
-
function
|
|
327
|
-
if (links.length === 0) return "";
|
|
601
|
+
function renderFeaturedLinks(links, ariaLabel) {
|
|
328
602
|
const featured = links.filter((l) => l.featured === true).sort(byOrder);
|
|
603
|
+
if (featured.length === 0) return "";
|
|
604
|
+
return `<nav class="featured-links" aria-label="${escapeHtml(ariaLabel)}"><ul>${featured.map(renderLinkItem).join("")}</ul></nav>`;
|
|
605
|
+
}
|
|
606
|
+
function renderOtherLinks(links, heading) {
|
|
329
607
|
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");
|
|
608
|
+
if (others.length === 0) return "";
|
|
609
|
+
return `<section class="other-links"><h2>${escapeHtml(heading)}</h2><ul>${others.map(renderLinkItem).join("")}</ul></section>`;
|
|
335
610
|
}
|
|
336
|
-
function renderSkills(skills, categories) {
|
|
611
|
+
function renderSkills(skills, categories, heading) {
|
|
337
612
|
if (skills.length === 0) return "";
|
|
613
|
+
const h = escapeHtml(heading);
|
|
338
614
|
const chips = (list) => `<ul class="skills">${list.map((s) => `<li>${escapeHtml(s.label)}</li>`).join("")}</ul>`;
|
|
339
615
|
if (!categories || categories.length === 0) {
|
|
340
|
-
return `<section><h2
|
|
616
|
+
return `<section><h2>${h}</h2>${chips(skills)}</section>`;
|
|
341
617
|
}
|
|
342
618
|
const UNCAT = "";
|
|
343
619
|
const buckets = /* @__PURE__ */ new Map();
|
|
@@ -347,7 +623,7 @@ function renderSkills(skills, categories) {
|
|
|
347
623
|
arr.push(s);
|
|
348
624
|
buckets.set(key, arr);
|
|
349
625
|
}
|
|
350
|
-
const group = (
|
|
626
|
+
const group = (heading2, list) => `<div class="skill-group">${heading2 !== void 0 ? `<h3>${escapeHtml(heading2)}</h3>` : ""}${chips(list)}</div>`;
|
|
351
627
|
const seen = /* @__PURE__ */ new Set();
|
|
352
628
|
const groups = [];
|
|
353
629
|
for (const cat of categories) {
|
|
@@ -362,14 +638,41 @@ function renderSkills(skills, categories) {
|
|
|
362
638
|
}
|
|
363
639
|
const uncategorized = buckets.get(UNCAT);
|
|
364
640
|
if (uncategorized && uncategorized.length > 0) groups.push(group(void 0, uncategorized));
|
|
365
|
-
return `<section><h2
|
|
641
|
+
return `<section><h2>${h}</h2><div class="skills-groups">${groups.join("")}</div></section>`;
|
|
366
642
|
}
|
|
367
|
-
function
|
|
643
|
+
function hostLabel(url) {
|
|
644
|
+
try {
|
|
645
|
+
const host = new URL(url).hostname.replace(/^www\./, "");
|
|
646
|
+
return host || url;
|
|
647
|
+
} catch {
|
|
648
|
+
return url;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function secondaryLinkPill(link) {
|
|
652
|
+
const label = escapeHtml(link.label ?? hostLabel(link.url));
|
|
653
|
+
const href = safeUrl(link.url);
|
|
654
|
+
const glyph = href ? brandIconForHost(link.url) : "";
|
|
655
|
+
const inner = `${glyph}<span>${label}</span>`;
|
|
656
|
+
return href ? `<a class="vol-secondary" href="${escapeHtml(href)}" rel="noopener">${inner}</a>` : `<span class="vol-secondary">${inner}</span>`;
|
|
657
|
+
}
|
|
658
|
+
function renderVolunteering(items, heading) {
|
|
659
|
+
if (items.length === 0) return "";
|
|
660
|
+
const li = (v) => {
|
|
661
|
+
const orgHref = v.url ? safeUrl(v.url) : void 0;
|
|
662
|
+
const org = orgHref ? externalLink(orgHref, escapeHtml(v.organization)) : escapeHtml(v.organization);
|
|
663
|
+
const role = v.role ? `<span class="vol-role">${escapeHtml(v.role)}</span>` : "";
|
|
664
|
+
const desc = v.description ? `<p class="vol-desc">${escapeHtml(v.description)}</p>` : "";
|
|
665
|
+
const secondary = v.secondaryLink ? secondaryLinkPill(v.secondaryLink) : "";
|
|
666
|
+
return `<li class="vol"><div class="vol-head"><span class="vol-org">${org}</span>${role}</div>${desc}${secondary}</li>`;
|
|
667
|
+
};
|
|
668
|
+
return `<section><h2>${escapeHtml(heading)}</h2><ul class="vol-list">${items.map(li).join("")}</ul></section>`;
|
|
669
|
+
}
|
|
670
|
+
function renderLanguages(languages, heading) {
|
|
368
671
|
if (languages.length === 0) return "";
|
|
369
672
|
const items = languages.map((l) => `<li>${escapeHtml(`${l.displayName ?? l.language} \u2014 ${l.proficiency}`)}</li>`).join("");
|
|
370
|
-
return `<section><h2
|
|
673
|
+
return `<section><h2>${escapeHtml(heading)}</h2><ul class="entries">${items}</ul></section>`;
|
|
371
674
|
}
|
|
372
|
-
function renderRecommendations(recs) {
|
|
675
|
+
function renderRecommendations(recs, heading) {
|
|
373
676
|
if (recs.length === 0) return "";
|
|
374
677
|
const items = recs.map((r) => {
|
|
375
678
|
const authorHref = r.author.url ? safeUrl(r.author.url) : void 0;
|
|
@@ -378,9 +681,9 @@ function renderRecommendations(recs) {
|
|
|
378
681
|
const rel = r.relationship ? ` (${escapeHtml(r.relationship)})` : "";
|
|
379
682
|
return `<figure class="rec"><blockquote>${escapeHtml(r.body)}</blockquote><figcaption>\u2014 ${caption}${rel}</figcaption></figure>`;
|
|
380
683
|
}).join("");
|
|
381
|
-
return `<section><h2
|
|
684
|
+
return `<section><h2>${escapeHtml(heading)}</h2>${items}</section>`;
|
|
382
685
|
}
|
|
383
|
-
function renderContact(contact) {
|
|
686
|
+
function renderContact(contact, heading) {
|
|
384
687
|
const items = [];
|
|
385
688
|
if (contact.email) {
|
|
386
689
|
items.push(
|
|
@@ -392,33 +695,97 @@ function renderContact(contact) {
|
|
|
392
695
|
items.push(`<li><a href="${escapeHtml(formHref)}">Contact form</a></li>`);
|
|
393
696
|
}
|
|
394
697
|
if (items.length === 0) return "";
|
|
395
|
-
return `<section><h2
|
|
698
|
+
return `<section><h2>${escapeHtml(heading)}</h2><ul class="entries">${items.join("")}</ul></section>`;
|
|
699
|
+
}
|
|
700
|
+
var PLATFORM_DISPLAY = {
|
|
701
|
+
instagram: "Instagram",
|
|
702
|
+
x: "X",
|
|
703
|
+
linkedin: "LinkedIn",
|
|
704
|
+
github: "GitHub",
|
|
705
|
+
gitlab: "GitLab",
|
|
706
|
+
youtube: "YouTube",
|
|
707
|
+
threads: "Threads",
|
|
708
|
+
facebook: "Facebook",
|
|
709
|
+
mastodon: "Mastodon",
|
|
710
|
+
bluesky: "Bluesky",
|
|
711
|
+
zenn: "Zenn",
|
|
712
|
+
qiita: "Qiita",
|
|
713
|
+
note: "note",
|
|
714
|
+
blog: "Blog",
|
|
715
|
+
event: "Event",
|
|
716
|
+
artwork: "Artwork",
|
|
717
|
+
project: "Project"
|
|
718
|
+
};
|
|
719
|
+
function platformDisplay(platform) {
|
|
720
|
+
const label = PLATFORM_DISPLAY[platform.trim().toLowerCase()];
|
|
721
|
+
return typeof label === "string" ? label : platform;
|
|
722
|
+
}
|
|
723
|
+
function highlightBadge(h) {
|
|
724
|
+
return `<span class="highlight-badge">${brandIconForPlatform(h.platform, h.url)}<span>${escapeHtml(platformDisplay(h.platform))}</span></span>`;
|
|
725
|
+
}
|
|
726
|
+
function renderHighlightCard(h, locale) {
|
|
727
|
+
const imgSrc = h.image ? safeUrl(h.image) : void 0;
|
|
728
|
+
const img = imgSrc ? `<div class="highlight-thumb"><img src="${escapeHtml(imgSrc)}" alt="${escapeHtml(h.alt)}" loading="lazy" decoding="async"></div>` : "";
|
|
729
|
+
const href = h.url ? safeUrl(h.url) : void 0;
|
|
730
|
+
const name = platformDisplay(h.platform);
|
|
731
|
+
const cta = isJapaneseLocale(locale) ? `${name}\u3067\u898B\u308B` : `View on ${name}`;
|
|
732
|
+
const title = escapeHtml(h.title);
|
|
733
|
+
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>`;
|
|
734
|
+
const date = h.postedAt ? `<p class="highlight-date">${timeTag(h.postedAt, locale)}</p>` : "";
|
|
735
|
+
const desc = h.description ? `<p class="highlight-desc">${escapeHtml(h.description)}</p>` : "";
|
|
736
|
+
const tags = h.tags && h.tags.length > 0 ? `<ul class="tags">${h.tags.map((t) => `<li>${escapeHtml(t)}</li>`).join("")}</ul>` : "";
|
|
737
|
+
const ctaEl = href ? `<span class="highlight-cta" aria-hidden="true">${escapeHtml(cta)} \u2192</span>` : "";
|
|
738
|
+
return `<li class="highlight-card">${img}<div class="highlight-body">${highlightBadge(h)}${titleEl}${date}${desc}${tags}${ctaEl}</div></li>`;
|
|
739
|
+
}
|
|
740
|
+
function renderHighlights(items, heading, intro, locale) {
|
|
741
|
+
if (items.length === 0) return "";
|
|
742
|
+
const introEl = intro ? `<p class="highlights-intro">${escapeHtml(intro)}</p>` : "";
|
|
743
|
+
const cards = items.map((h) => renderHighlightCard(h, locale)).join("");
|
|
744
|
+
return `<section class="highlights"><h2>${escapeHtml(heading)}</h2>${introEl}<ul class="highlights-track" role="list">${cards}</ul></section>`;
|
|
396
745
|
}
|
|
397
|
-
function renderActivity(snapshot) {
|
|
746
|
+
function renderActivity(snapshot, heading) {
|
|
398
747
|
if (!snapshot) return "";
|
|
399
748
|
const svg = renderActivitySvg(snapshot);
|
|
400
749
|
if (svg === "") return "";
|
|
401
|
-
return `<section class="activity"><h2
|
|
750
|
+
return `<section class="activity"><h2>${escapeHtml(heading)}</h2>${svg}</section>`;
|
|
402
751
|
}
|
|
403
752
|
function renderJsonLdScript(data) {
|
|
404
753
|
const payload = JSON.stringify(generateJsonLd(data));
|
|
405
754
|
return `<script type="application/ld+json">${escapeJsonLd(payload)}</script>`;
|
|
406
755
|
}
|
|
407
|
-
function renderLocaleNav(localeNav) {
|
|
756
|
+
function renderLocaleNav(localeNav, ariaLabel) {
|
|
408
757
|
const items = localeNav.map(
|
|
409
758
|
(l) => l.current ? `<span aria-current="true">${escapeHtml(l.locale)}</span>` : `<a href="${escapeHtml(l.href)}">${escapeHtml(l.locale)}</a>`
|
|
410
759
|
).join("");
|
|
411
|
-
return `<nav class="locales" aria-label="
|
|
760
|
+
return `<nav class="locales" aria-label="${escapeHtml(ariaLabel)}">${items}</nav>`;
|
|
412
761
|
}
|
|
762
|
+
var DATE_SEPARATOR_JA = " \u301C ";
|
|
763
|
+
var DATE_SEPARATOR_DEFAULT = " \u2013 ";
|
|
413
764
|
function renderProfileHtml(input) {
|
|
414
765
|
const d = input.localized;
|
|
415
766
|
const p = d.profile;
|
|
767
|
+
const locale = d.resolvedLocale;
|
|
416
768
|
const description = p.tagline ?? p.bio ?? "";
|
|
769
|
+
const L = {
|
|
770
|
+
...pickLabelPack(locale),
|
|
771
|
+
...d.settings.sectionLabels ?? {},
|
|
772
|
+
...input.labels
|
|
773
|
+
};
|
|
774
|
+
const omit = new Set(input.omitSections ?? []);
|
|
775
|
+
const keep = (key, html) => omit.has(key) ? "" : html;
|
|
776
|
+
const dateSeparator = isJapaneseLocale(locale) ? DATE_SEPARATOR_JA : DATE_SEPARATOR_DEFAULT;
|
|
777
|
+
const dates = (start, opts) => dateRange(start, { ...opts, locale, separator: dateSeparator });
|
|
417
778
|
const head = [
|
|
418
779
|
'<meta charset="utf-8">',
|
|
419
780
|
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
420
781
|
`<title>${escapeHtml(p.displayName)}</title>`,
|
|
421
782
|
description ? `<meta name="description" content="${escapeHtml(description.slice(0, 300))}">` : "",
|
|
783
|
+
// Data-derived Open Graph tags (the renderer owns these; asset tags like
|
|
784
|
+
// og:image and PWA/theme-color are the host's, via the `head` slot).
|
|
785
|
+
'<meta property="og:type" content="profile">',
|
|
786
|
+
`<meta property="og:title" content="${escapeHtml(p.displayName)}">`,
|
|
787
|
+
description ? `<meta property="og:description" content="${escapeHtml(description.slice(0, 300))}">` : "",
|
|
788
|
+
input.canonicalUrl ? `<meta property="og:url" content="${escapeHtml(input.canonicalUrl)}">` : "",
|
|
422
789
|
input.canonicalUrl ? `<link rel="canonical" href="${escapeHtml(input.canonicalUrl)}">` : "",
|
|
423
790
|
...input.alternates.map(
|
|
424
791
|
(a) => `<link rel="alternate" hreflang="${escapeHtml(a.hreflang)}" href="${escapeHtml(a.href)}">`
|
|
@@ -426,162 +793,168 @@ function renderProfileHtml(input) {
|
|
|
426
793
|
input.jsonLd ? renderJsonLdScript(d) : "",
|
|
427
794
|
input.contact ? '<link rel="stylesheet" href="/contact-widget.css">' : "",
|
|
428
795
|
`<style>${buildTokenCss(d.settings.appearance)}
|
|
429
|
-
${CSS}</style
|
|
796
|
+
${CSS}</style>`,
|
|
797
|
+
// First-party <head> slot (raw, unescaped): OG image tags, manifest link, etc.
|
|
798
|
+
input.slots?.head ?? ""
|
|
430
799
|
].filter(Boolean).join("\n ");
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
entryList(
|
|
436
|
-
"Experience",
|
|
800
|
+
const sections = {
|
|
801
|
+
about: renderBio(p.bio, L.about),
|
|
802
|
+
careers: entryList(
|
|
803
|
+
L.careers,
|
|
437
804
|
d.careers.map((c) => ({
|
|
438
805
|
heading: c.role,
|
|
439
806
|
sub: c.organization,
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
isCurrent: c.isCurrent,
|
|
443
|
-
locale: d.resolvedLocale
|
|
444
|
-
}),
|
|
807
|
+
subUrl: c.url,
|
|
808
|
+
dates: dates(c.startDate, { end: c.endDate, isCurrent: c.isCurrent }),
|
|
445
809
|
body: c.description,
|
|
446
|
-
url: c.url,
|
|
447
810
|
current: c.isCurrent
|
|
448
811
|
})),
|
|
449
812
|
"timeline"
|
|
450
813
|
),
|
|
451
|
-
entryList(
|
|
452
|
-
|
|
814
|
+
projects: entryList(
|
|
815
|
+
L.projects,
|
|
453
816
|
d.projects.map((x) => ({
|
|
454
817
|
heading: x.title,
|
|
455
|
-
dates: dateRange(x.startDate, { end: x.endDate, locale: d.resolvedLocale }),
|
|
456
|
-
body: x.description,
|
|
457
818
|
url: x.url,
|
|
819
|
+
role: x.role,
|
|
820
|
+
dates: dates(x.startDate, { end: x.endDate }),
|
|
821
|
+
body: x.description,
|
|
458
822
|
tags: x.tags,
|
|
459
823
|
highlighted: x.highlighted
|
|
460
824
|
})),
|
|
461
825
|
"cards"
|
|
462
826
|
),
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
827
|
+
volunteering: renderVolunteering(d.volunteering, L.volunteering),
|
|
828
|
+
skills: renderSkills(d.skills, d.settings.skillCategories, L.skills),
|
|
829
|
+
activity: renderActivity(input.activitySnapshot, L.activity),
|
|
830
|
+
education: entryList(
|
|
831
|
+
L.education,
|
|
467
832
|
d.education.map((e) => {
|
|
468
833
|
const degree = nonEmpty([e.degree, e.fieldOfStudy], ", ");
|
|
469
834
|
return {
|
|
470
835
|
heading: degree ?? e.institution,
|
|
471
836
|
sub: degree ? e.institution : void 0,
|
|
472
|
-
dates:
|
|
473
|
-
end: e.endDate,
|
|
474
|
-
isCurrent: e.isCurrent,
|
|
475
|
-
locale: d.resolvedLocale
|
|
476
|
-
}),
|
|
837
|
+
dates: dates(e.startDate, { end: e.endDate, isCurrent: e.isCurrent }),
|
|
477
838
|
body: e.description,
|
|
478
839
|
url: e.url
|
|
479
840
|
};
|
|
480
841
|
})
|
|
481
842
|
),
|
|
482
|
-
entryList(
|
|
483
|
-
|
|
843
|
+
certifications: entryList(
|
|
844
|
+
L.certifications,
|
|
484
845
|
d.certifications.map((c) => ({
|
|
485
846
|
heading: c.title,
|
|
486
847
|
sub: c.issuingOrganization,
|
|
487
|
-
dates:
|
|
848
|
+
dates: dates(c.issueDate, { end: c.expirationDate }),
|
|
488
849
|
url: c.url
|
|
489
850
|
}))
|
|
490
851
|
),
|
|
491
|
-
entryList(
|
|
492
|
-
|
|
852
|
+
publications: entryList(
|
|
853
|
+
L.publications,
|
|
493
854
|
d.publications.map((x) => ({
|
|
494
855
|
heading: x.title,
|
|
495
856
|
sub: nonEmpty([x.publisher, x.coAuthors?.join(", ")], " \xB7 "),
|
|
496
|
-
dates:
|
|
857
|
+
dates: dates(x.date, {}),
|
|
497
858
|
body: x.description,
|
|
498
859
|
url: x.url ?? (x.doi ? `https://doi.org/${x.doi}` : void 0)
|
|
499
860
|
}))
|
|
500
861
|
),
|
|
501
|
-
entryList(
|
|
502
|
-
|
|
862
|
+
honors: entryList(
|
|
863
|
+
L.honors,
|
|
503
864
|
d.honors.map((x) => ({
|
|
504
865
|
heading: x.title,
|
|
505
866
|
sub: x.issuer,
|
|
506
|
-
dates:
|
|
867
|
+
dates: dates(x.date, {}),
|
|
507
868
|
body: x.description,
|
|
508
869
|
url: x.url
|
|
509
870
|
}))
|
|
510
871
|
),
|
|
511
|
-
entryList(
|
|
512
|
-
|
|
872
|
+
memberships: entryList(
|
|
873
|
+
L.memberships,
|
|
513
874
|
d.memberships.map((x) => ({
|
|
514
875
|
heading: x.role ?? x.organization,
|
|
515
876
|
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
|
-
}),
|
|
877
|
+
dates: dates(x.startDate, { end: x.endDate, isCurrent: x.isCurrent }),
|
|
535
878
|
body: x.description,
|
|
536
879
|
url: x.url
|
|
537
880
|
}))
|
|
538
881
|
),
|
|
539
|
-
entryList(
|
|
540
|
-
|
|
882
|
+
courses: entryList(
|
|
883
|
+
L.courses,
|
|
541
884
|
d.courses.map((x) => ({
|
|
542
885
|
heading: x.title,
|
|
543
886
|
sub: x.provider,
|
|
544
|
-
dates:
|
|
887
|
+
dates: dates(x.completionDate, {}),
|
|
545
888
|
body: x.description,
|
|
546
889
|
url: x.certificateUrl
|
|
547
890
|
}))
|
|
548
891
|
),
|
|
549
|
-
entryList(
|
|
550
|
-
|
|
892
|
+
patents: entryList(
|
|
893
|
+
L.patents,
|
|
551
894
|
d.patents.map((x) => ({
|
|
552
895
|
heading: x.title,
|
|
553
896
|
sub: nonEmpty([x.patentNumber, x.office, x.status, x.coInventors?.join(", ")], " \xB7 "),
|
|
554
|
-
dates:
|
|
897
|
+
dates: dates(x.filingDate ?? x.grantDate, {}),
|
|
555
898
|
body: x.description,
|
|
556
899
|
url: x.url
|
|
557
900
|
}))
|
|
558
901
|
),
|
|
559
|
-
entryList(
|
|
560
|
-
|
|
902
|
+
testScores: entryList(
|
|
903
|
+
L.testScores,
|
|
561
904
|
d.testScores.map((x) => ({
|
|
562
905
|
heading: `${x.title}: ${x.score}`,
|
|
563
|
-
dates:
|
|
906
|
+
dates: dates(x.date, {}),
|
|
564
907
|
body: x.description,
|
|
565
908
|
url: x.url
|
|
566
909
|
}))
|
|
567
910
|
),
|
|
568
|
-
renderLanguages(d.languages),
|
|
569
|
-
renderRecommendations(d.recommendations),
|
|
570
|
-
|
|
911
|
+
languages: renderLanguages(d.languages, L.languages),
|
|
912
|
+
recommendations: renderRecommendations(d.recommendations, L.recommendations),
|
|
913
|
+
highlights: renderHighlights(d.highlights, L.highlights, d.settings.highlightsIntro, locale),
|
|
914
|
+
contact: renderContact(d.contact, L.contact)
|
|
915
|
+
};
|
|
916
|
+
const localeNavHtml = input.localeNav.length > 1 ? renderLocaleNav(input.localeNav, L.localeNav) : "";
|
|
917
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
918
|
+
const orderedKeys = [];
|
|
919
|
+
for (const key of d.settings.sectionOrder ?? []) {
|
|
920
|
+
if (SECTION_KEYS.includes(key) && !seenKeys.has(key)) {
|
|
921
|
+
seenKeys.add(key);
|
|
922
|
+
orderedKeys.push(key);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
for (const key of SECTION_KEYS) {
|
|
926
|
+
if (!seenKeys.has(key)) orderedKeys.push(key);
|
|
927
|
+
}
|
|
928
|
+
const body = [
|
|
929
|
+
renderHeader(p, localeNavHtml),
|
|
930
|
+
// Featured links at the top; the remaining links are a fixed bottom section.
|
|
931
|
+
renderFeaturedLinks(d.links, L.featuredLinks),
|
|
932
|
+
...orderedKeys.map((key) => keep(key, sections[key])),
|
|
933
|
+
renderOtherLinks(d.links, L.otherLinks),
|
|
934
|
+
input.slots?.mainEnd ?? ""
|
|
571
935
|
].filter(Boolean).join("\n");
|
|
572
|
-
const
|
|
936
|
+
const spdx = d.meta?.contentLicense?.spdxId;
|
|
937
|
+
const year = typeof input.year === "number" && Number.isFinite(input.year) ? input.year : void 0;
|
|
938
|
+
const licenseText = year !== void 0 ? `\xA9 ${year} ${escapeHtml(p.displayName)} \u2014 ${escapeHtml(spdx ?? "")}` : `\xA9 ${escapeHtml(p.displayName)} \u2014 ${escapeHtml(spdx ?? "")}`;
|
|
939
|
+
const licenseLine = spdx ? `<p class="license">${licenseText}</p>` : "";
|
|
940
|
+
const poweredByLine = d.settings.showPoweredBy !== false ? `<p class="powered-by">${escapeHtml(L.poweredBy)} <a href="https://takuhon.org/" rel="noopener">Takuhon</a></p>` : "";
|
|
941
|
+
const footerInner = `${licenseLine}${poweredByLine}`;
|
|
942
|
+
const footer = footerInner ? `<footer class="powered">${footerInner}</footer>` : "";
|
|
573
943
|
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" : "";
|
|
944
|
+
const skipLink = `<a class="skip-link" href="#main">${escapeHtml(L.skipLink)}</a>
|
|
945
|
+
`;
|
|
946
|
+
const bodyEnd = input.slots?.bodyEnd ?? "";
|
|
574
947
|
return `<!DOCTYPE html>
|
|
575
948
|
<html lang="${escapeHtml(d.resolvedLocale)}">
|
|
576
949
|
<head>
|
|
577
950
|
${head}
|
|
578
951
|
</head>
|
|
579
952
|
<body>
|
|
580
|
-
<main>
|
|
953
|
+
${skipLink}<main id="main">
|
|
581
954
|
${body}
|
|
582
955
|
</main>
|
|
583
956
|
${footer ? `${footer}
|
|
584
|
-
` : ""}${contactScript}</body>
|
|
957
|
+
` : ""}${contactScript}${bodyEnd}</body>
|
|
585
958
|
</html>
|
|
586
959
|
`;
|
|
587
960
|
}
|
|
@@ -706,7 +1079,33 @@ function pathLocaleFromUrl(url) {
|
|
|
706
1079
|
// src/public-app.ts
|
|
707
1080
|
var FALLBACK_VERSION = "bundled-fixture";
|
|
708
1081
|
var TURNSTILE_ORIGIN = "https://challenges.cloudflare.com";
|
|
709
|
-
|
|
1082
|
+
var CSP_SOURCE = /^[!-~]+$/;
|
|
1083
|
+
var CSP_HASH = /^'sha(256|384|512)-[A-Za-z0-9+/]+={0,2}'$/;
|
|
1084
|
+
var CSP_BLANKET = /* @__PURE__ */ new Set(["'unsafe-inline'", "'unsafe-eval'"]);
|
|
1085
|
+
function sanitizeCsp(ext) {
|
|
1086
|
+
const dropped = [];
|
|
1087
|
+
const keep = (tokens, ok) => (tokens ?? []).filter((t) => {
|
|
1088
|
+
if (ok(t)) return true;
|
|
1089
|
+
dropped.push(t);
|
|
1090
|
+
return false;
|
|
1091
|
+
});
|
|
1092
|
+
const source = (t) => CSP_SOURCE.test(t) && !t.includes(";") && !t.includes(",") && !CSP_BLANKET.has(t.toLowerCase());
|
|
1093
|
+
return {
|
|
1094
|
+
clean: {
|
|
1095
|
+
scriptSrc: keep(ext.scriptSrc, source),
|
|
1096
|
+
connectSrc: keep(ext.connectSrc, source),
|
|
1097
|
+
workerSrc: keep(ext.workerSrc, source),
|
|
1098
|
+
scriptHashes: keep(ext.scriptHashes, (t) => CSP_HASH.test(t))
|
|
1099
|
+
},
|
|
1100
|
+
dropped
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
function buildPublicCsp(contact, ext) {
|
|
1104
|
+
const turnstile = contact ? [TURNSTILE_ORIGIN] : [];
|
|
1105
|
+
const scriptSrc = ["'self'", ...turnstile, ...ext?.scriptSrc ?? [], ...ext?.scriptHashes ?? []];
|
|
1106
|
+
const connectSrc = ["'self'", ...turnstile, ...ext?.connectSrc ?? []];
|
|
1107
|
+
const frameSrc = [...turnstile];
|
|
1108
|
+
const workerSrc = ext?.workerSrc ?? [];
|
|
710
1109
|
return [
|
|
711
1110
|
"default-src 'self'",
|
|
712
1111
|
// `https:` lets the server-rendered profile page load remote avatar images
|
|
@@ -714,10 +1113,11 @@ function buildPublicCsp(contact) {
|
|
|
714
1113
|
// already blocks non-http(s) schemes); `data:` covers inline placeholders.
|
|
715
1114
|
"img-src 'self' https: data:",
|
|
716
1115
|
"style-src 'self' 'unsafe-inline'",
|
|
717
|
-
|
|
1116
|
+
`script-src ${scriptSrc.join(" ")}`,
|
|
718
1117
|
"font-src 'self'",
|
|
719
|
-
|
|
720
|
-
...
|
|
1118
|
+
`connect-src ${connectSrc.join(" ")}`,
|
|
1119
|
+
...frameSrc.length > 0 ? [`frame-src ${frameSrc.join(" ")}`] : [],
|
|
1120
|
+
...workerSrc.length > 0 ? [`worker-src ${workerSrc.join(" ")}`] : [],
|
|
721
1121
|
"frame-ancestors 'none'",
|
|
722
1122
|
"base-uri 'self'",
|
|
723
1123
|
"form-action 'self'",
|
|
@@ -738,6 +1138,19 @@ async function loadProfile(deps) {
|
|
|
738
1138
|
}
|
|
739
1139
|
function createPublicApp(deps) {
|
|
740
1140
|
const app = new Hono({ getPath: localePrefixGetPath });
|
|
1141
|
+
const cspExt = deps.render?.csp;
|
|
1142
|
+
let pageCsp = PUBLIC_CSP;
|
|
1143
|
+
let pageCspWithContact = PUBLIC_CSP_WITH_CONTACT;
|
|
1144
|
+
if (cspExt) {
|
|
1145
|
+
const { clean, dropped } = sanitizeCsp(cspExt);
|
|
1146
|
+
if (dropped.length > 0) {
|
|
1147
|
+
console.warn(
|
|
1148
|
+
`[takuhon] Ignored ${dropped.length} invalid render.csp token(s): ${dropped.join(", ")}`
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
pageCsp = buildPublicCsp(false, clean);
|
|
1152
|
+
pageCspWithContact = buildPublicCsp(true, clean);
|
|
1153
|
+
}
|
|
741
1154
|
app.use("*", async (c, next) => {
|
|
742
1155
|
await next();
|
|
743
1156
|
const h = c.res.headers;
|
|
@@ -746,9 +1159,11 @@ function createPublicApp(deps) {
|
|
|
746
1159
|
h.set("x-frame-options", "DENY");
|
|
747
1160
|
h.set("referrer-policy", "strict-origin-when-cross-origin");
|
|
748
1161
|
h.set("permissions-policy", "camera=(), microphone=(), geolocation=(), interest-cohort=()");
|
|
1162
|
+
const contact = c.get("contactEnabled") === true;
|
|
1163
|
+
const page = c.get("profilePage") === true;
|
|
749
1164
|
h.set(
|
|
750
1165
|
"content-security-policy",
|
|
751
|
-
|
|
1166
|
+
page ? contact ? pageCspWithContact : pageCsp : contact ? PUBLIC_CSP_WITH_CONTACT : PUBLIC_CSP
|
|
752
1167
|
);
|
|
753
1168
|
h.set("access-control-allow-origin", "*");
|
|
754
1169
|
h.set("access-control-expose-headers", "ETag");
|
|
@@ -793,6 +1208,14 @@ function createPublicApp(deps) {
|
|
|
793
1208
|
} : void 0;
|
|
794
1209
|
if (contact) c.set("contactEnabled", true);
|
|
795
1210
|
const html = renderProfileHtml({
|
|
1211
|
+
// Host composition seams (slots / labels / omitSections). `csp` is a
|
|
1212
|
+
// response-header concern handled above, not a renderer input, so it is
|
|
1213
|
+
// intentionally excluded here.
|
|
1214
|
+
...deps.render ? {
|
|
1215
|
+
slots: deps.render.slots,
|
|
1216
|
+
labels: deps.render.labels,
|
|
1217
|
+
omitSections: deps.render.omitSections
|
|
1218
|
+
} : {},
|
|
796
1219
|
localized,
|
|
797
1220
|
canonicalUrl: `${origin}${localePath(current)}`,
|
|
798
1221
|
alternates: [
|
|
@@ -802,11 +1225,13 @@ function createPublicApp(deps) {
|
|
|
802
1225
|
localeNav: locales.map((l) => ({ locale: l, href: localePath(l), current: l === current })),
|
|
803
1226
|
jsonLd: profile.settings.enableJsonLd !== false,
|
|
804
1227
|
activitySnapshot: snapshot ?? void 0,
|
|
805
|
-
contact
|
|
1228
|
+
contact,
|
|
1229
|
+
year: (/* @__PURE__ */ new Date()).getFullYear()
|
|
806
1230
|
});
|
|
807
1231
|
c.header("etag", `"${version}"`);
|
|
808
1232
|
c.header("cache-control", "public, max-age=300");
|
|
809
1233
|
c.header("vary", "Accept-Language, Cookie");
|
|
1234
|
+
c.set("profilePage", true);
|
|
810
1235
|
return c.html(html);
|
|
811
1236
|
});
|
|
812
1237
|
app.get("/health", (c) => {
|
|
@@ -1761,7 +2186,8 @@ function generateSite(profile, options = {}) {
|
|
|
1761
2186
|
alternates,
|
|
1762
2187
|
localeNav,
|
|
1763
2188
|
jsonLd,
|
|
1764
|
-
activitySnapshot
|
|
2189
|
+
activitySnapshot,
|
|
2190
|
+
year: (/* @__PURE__ */ new Date()).getFullYear()
|
|
1765
2191
|
});
|
|
1766
2192
|
pages.push({
|
|
1767
2193
|
route: isDefault ? "/" : `/${locale}/`,
|