@takuhon/api 1.1.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 +35 -0
- package/dist/index.d.ts +209 -75
- package/dist/index.js +773 -138
- 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) {
|
|
@@ -85,55 +85,477 @@ function nonEmpty(values, separator) {
|
|
|
85
85
|
return joined.length > 0 ? joined : void 0;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
// src/html/brand-icons.ts
|
|
89
|
+
var GLYPHS = {
|
|
90
|
+
github: {
|
|
91
|
+
viewBox: "0 0 24 24",
|
|
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"
|
|
93
|
+
},
|
|
94
|
+
gitlab: {
|
|
95
|
+
viewBox: "0 0 24 24",
|
|
96
|
+
path: "m23.6004 9.5927-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.8748.8748 0 0 0-.9997.0539.8748.8748 0 0 0-.29.4399l-2.2055 6.748H7.5375l-2.2057-6.748a.8573.8573 0 0 0-.29-.4412.8748.8748 0 0 0-.9997-.0537.8585.8585 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.0657 6.0657 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.0085 1.0085 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7489.0125-.01a6.0682 6.0682 0 0 0 2.0094-7.003z"
|
|
97
|
+
},
|
|
98
|
+
linkedin: {
|
|
99
|
+
viewBox: "0 0 16 16",
|
|
100
|
+
path: "M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z"
|
|
101
|
+
},
|
|
102
|
+
x: {
|
|
103
|
+
viewBox: "0 0 24 24",
|
|
104
|
+
path: "M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"
|
|
105
|
+
},
|
|
106
|
+
mastodon: {
|
|
107
|
+
viewBox: "0 0 24 24",
|
|
108
|
+
path: "M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"
|
|
109
|
+
},
|
|
110
|
+
bluesky: {
|
|
111
|
+
viewBox: "0 0 24 24",
|
|
112
|
+
path: "M5.202 2.857C7.954 4.922 10.913 9.11 12 11.358c1.087-2.247 4.046-6.436 6.798-8.501C20.783 1.366 24 .213 24 3.883c0 .732-.42 6.156-.667 7.037-.856 3.061-3.978 3.842-6.755 3.37 4.854.826 6.089 3.562 3.422 6.299-5.065 5.196-7.28-1.304-7.847-2.97-.104-.305-.152-.448-.153-.327 0-.121-.05.022-.153.327-.568 1.666-2.782 8.166-7.847 2.97-2.667-2.737-1.432-5.473 3.422-6.3-2.777.473-5.899-.308-6.755-3.369C.42 10.04 0 4.615 0 3.883c0-3.67 3.217-2.517 5.202-1.026"
|
|
113
|
+
},
|
|
114
|
+
instagram: {
|
|
115
|
+
viewBox: "0 0 24 24",
|
|
116
|
+
path: "M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077"
|
|
117
|
+
},
|
|
118
|
+
youtube: {
|
|
119
|
+
viewBox: "0 0 24 24",
|
|
120
|
+
path: "M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"
|
|
121
|
+
},
|
|
122
|
+
threads: {
|
|
123
|
+
viewBox: "0 0 24 24",
|
|
124
|
+
path: "M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.589 12c.027 3.086.718 5.496 2.057 7.164 1.43 1.783 3.631 2.698 6.54 2.717 2.623-.02 4.358-.631 5.8-2.045 1.647-1.613 1.618-3.593 1.09-4.798-.31-.71-.873-1.3-1.634-1.75-.192 1.352-.622 2.446-1.284 3.272-.886 1.102-2.14 1.704-3.73 1.79-1.202.065-2.361-.218-3.259-.801-1.063-.689-1.685-1.74-1.752-2.964-.065-1.19.408-2.285 1.33-3.082.88-.76 2.119-1.207 3.583-1.291a13.853 13.853 0 0 1 3.02.142c-.126-.742-.375-1.332-.75-1.757-.513-.586-1.308-.883-2.359-.89h-.029c-.844 0-1.992.232-2.721 1.32L7.734 7.847c.98-1.454 2.568-2.256 4.478-2.256h.044c3.194.02 5.097 1.975 5.287 5.388.108.046.216.094.321.142 1.49.7 2.58 1.761 3.154 3.07.797 1.82.871 4.79-1.548 7.158-1.85 1.81-4.094 2.628-7.277 2.65Zm1.003-11.69c-.242 0-.487.007-.739.021-1.836.103-2.98.946-2.916 2.143.067 1.256 1.452 1.839 2.784 1.767 1.224-.065 2.818-.543 3.086-3.71a10.5 10.5 0 0 0-2.215-.221z"
|
|
125
|
+
},
|
|
126
|
+
facebook: {
|
|
127
|
+
viewBox: "0 0 24 24",
|
|
128
|
+
path: "M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z"
|
|
129
|
+
},
|
|
130
|
+
rss: {
|
|
131
|
+
viewBox: "0 0 24 24",
|
|
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"
|
|
149
|
+
}
|
|
150
|
+
};
|
|
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",
|
|
163
|
+
blog: "rss"
|
|
164
|
+
};
|
|
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];
|
|
195
|
+
if (!icon) return "";
|
|
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>`;
|
|
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
|
+
}
|
|
212
|
+
|
|
88
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
|
+
}
|
|
89
270
|
function escapeJsonLd(json) {
|
|
90
271
|
return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
91
272
|
}
|
|
92
|
-
var
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
273
|
+
var DEFAULT_TOKENS = {
|
|
274
|
+
"--takuhon-color-bg": "#ffffff",
|
|
275
|
+
"--takuhon-color-surface": "#f6f7f9",
|
|
276
|
+
"--takuhon-color-text": "#1f2933",
|
|
277
|
+
"--takuhon-color-text-muted": "#52606d",
|
|
278
|
+
"--takuhon-color-border": "#d8dee7",
|
|
279
|
+
"--takuhon-color-primary": "#2563eb",
|
|
280
|
+
"--takuhon-color-primary-contrast": "#ffffff",
|
|
281
|
+
"--takuhon-color-accent": "#4f46e5",
|
|
282
|
+
"--takuhon-color-heading": "#1e1888",
|
|
283
|
+
"--takuhon-font-family": "system-ui,-apple-system,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif"
|
|
284
|
+
};
|
|
285
|
+
var DEFAULT_TOKENS_DARK = {
|
|
286
|
+
"--takuhon-color-bg": "#0f172a",
|
|
287
|
+
"--takuhon-color-surface": "#1e293b",
|
|
288
|
+
"--takuhon-color-text": "#e2e8f0",
|
|
289
|
+
"--takuhon-color-text-muted": "#94a3b8",
|
|
290
|
+
"--takuhon-color-border": "#334155",
|
|
291
|
+
"--takuhon-color-primary": "#60a5fa",
|
|
292
|
+
"--takuhon-color-primary-contrast": "#0f172a",
|
|
293
|
+
"--takuhon-color-accent": "#818cf8",
|
|
294
|
+
"--takuhon-color-heading": "#c7d2fe"
|
|
295
|
+
};
|
|
296
|
+
var INTERNAL_TOKENS = {
|
|
297
|
+
"--takuhon-space-1": "4px",
|
|
298
|
+
"--takuhon-space-2": "8px",
|
|
299
|
+
"--takuhon-space-3": "12px",
|
|
300
|
+
"--takuhon-space-4": "16px",
|
|
301
|
+
"--takuhon-space-5": "24px",
|
|
302
|
+
"--takuhon-space-6": "32px",
|
|
303
|
+
"--takuhon-radius-sm": "6px",
|
|
304
|
+
"--takuhon-radius-md": "12px",
|
|
305
|
+
"--takuhon-radius-full": "9999px",
|
|
306
|
+
"--takuhon-tap-target": "44px",
|
|
307
|
+
"--takuhon-font-size-sm": "14px",
|
|
308
|
+
"--takuhon-font-size-base": "16px",
|
|
309
|
+
"--takuhon-font-size-lg": "18px",
|
|
310
|
+
"--takuhon-font-size-xl": "22px",
|
|
311
|
+
"--takuhon-font-size-2xl": "28px",
|
|
312
|
+
"--takuhon-line-height": "1.7",
|
|
313
|
+
"--takuhon-max-content-width": "720px"
|
|
314
|
+
};
|
|
315
|
+
var COLOR_TOKEN_VARS = {
|
|
316
|
+
bg: "--takuhon-color-bg",
|
|
317
|
+
surface: "--takuhon-color-surface",
|
|
318
|
+
text: "--takuhon-color-text",
|
|
319
|
+
textMuted: "--takuhon-color-text-muted",
|
|
320
|
+
border: "--takuhon-color-border",
|
|
321
|
+
accent: "--takuhon-color-accent",
|
|
322
|
+
primary: "--takuhon-color-primary",
|
|
323
|
+
primaryContrast: "--takuhon-color-primary-contrast",
|
|
324
|
+
heading: "--takuhon-color-heading"
|
|
325
|
+
};
|
|
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-]*\))$/;
|
|
327
|
+
var SAFE_FONT = /^[A-Za-z0-9\s,'"._-]+$/;
|
|
328
|
+
function safeValue(value, pattern, maxLength) {
|
|
329
|
+
if (typeof value !== "string") return void 0;
|
|
330
|
+
const v = value.trim();
|
|
331
|
+
if (v === "" || v.length > maxLength || !pattern.test(v)) return void 0;
|
|
332
|
+
return v;
|
|
333
|
+
}
|
|
334
|
+
var safeColor = (value) => safeValue(value, SAFE_COLOR, 64);
|
|
335
|
+
var safeFont = (value) => safeValue(value, SAFE_FONT, 256);
|
|
336
|
+
function colorOverrides(colors) {
|
|
337
|
+
if (!colors) return [];
|
|
338
|
+
const out = [];
|
|
339
|
+
for (const key of Object.keys(COLOR_TOKEN_VARS)) {
|
|
340
|
+
const safe = safeColor(colors[key]);
|
|
341
|
+
if (safe !== void 0) out.push([COLOR_TOKEN_VARS[key], safe]);
|
|
342
|
+
}
|
|
343
|
+
return out;
|
|
344
|
+
}
|
|
345
|
+
function rootBlock(pairs) {
|
|
346
|
+
return `:root{${[...pairs].map(([k, v]) => `${k}:${v}`).join(";")}}`;
|
|
347
|
+
}
|
|
348
|
+
function buildTokenCss(appearance) {
|
|
349
|
+
const light = new Map([
|
|
350
|
+
...Object.entries(INTERNAL_TOKENS),
|
|
351
|
+
...Object.entries(DEFAULT_TOKENS)
|
|
352
|
+
]);
|
|
353
|
+
if (appearance) {
|
|
354
|
+
const font = safeFont(appearance.fontFamily);
|
|
355
|
+
if (font !== void 0) light.set("--takuhon-font-family", font);
|
|
356
|
+
for (const [cssVar, value] of colorOverrides(appearance.colors)) {
|
|
357
|
+
light.set(cssVar, value);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const dark = new Map(Object.entries(DEFAULT_TOKENS_DARK));
|
|
361
|
+
for (const [cssVar, value] of colorOverrides(appearance?.colorsDark)) {
|
|
362
|
+
dark.set(cssVar, value);
|
|
363
|
+
}
|
|
364
|
+
return `${rootBlock(light)}
|
|
365
|
+
@media (prefers-color-scheme:dark){${rootBlock(dark)}}`;
|
|
366
|
+
}
|
|
367
|
+
var CSS = `*{box-sizing:border-box}
|
|
368
|
+
html{font-size:100%}
|
|
369
|
+
body{margin:0;color:var(--takuhon-color-text);background:var(--takuhon-color-bg);font-family:var(--takuhon-font-family);font-size:var(--takuhon-font-size-base);line-height:var(--takuhon-line-height);-webkit-text-size-adjust:100%}
|
|
370
|
+
main{max-width:var(--takuhon-max-content-width);margin:0 auto;padding:var(--takuhon-space-6) var(--takuhon-space-4)}
|
|
371
|
+
a{color:var(--takuhon-color-primary)}
|
|
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}
|
|
375
|
+
h1{font-size:var(--takuhon-font-size-2xl);font-weight:700;line-height:1.2;margin:0 0 var(--takuhon-space-2)}
|
|
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)}
|
|
377
|
+
h3{font-size:var(--takuhon-font-size-lg);font-weight:600;margin:0}
|
|
378
|
+
header{margin-bottom:var(--takuhon-space-6);display:flow-root}
|
|
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)}
|
|
380
|
+
.tagline{font-size:var(--takuhon-font-size-lg);color:var(--takuhon-color-text-muted);margin:0 0 var(--takuhon-space-2)}
|
|
381
|
+
.location{font-size:var(--takuhon-font-size-sm);color:var(--takuhon-color-text-muted);margin:0}
|
|
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}
|
|
104
389
|
ul{padding:0;margin:0;list-style:none}
|
|
105
|
-
.entries>li{margin:0 0
|
|
106
|
-
.
|
|
107
|
-
.
|
|
108
|
-
.
|
|
109
|
-
.
|
|
110
|
-
.
|
|
111
|
-
.
|
|
112
|
-
.
|
|
113
|
-
.
|
|
114
|
-
|
|
115
|
-
.
|
|
116
|
-
|
|
390
|
+
.entries>li{margin:0 0 var(--takuhon-space-4)}
|
|
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)}
|
|
392
|
+
.entries--timeline>li:last-child{padding-bottom:0}
|
|
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)}
|
|
394
|
+
.entries--timeline>li.is-current::before{background:var(--takuhon-color-accent)}
|
|
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}
|
|
397
|
+
.entries--cards{display:grid;gap:var(--takuhon-space-3)}
|
|
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)}
|
|
399
|
+
.entries--cards>li.is-highlighted{border-color:var(--takuhon-color-accent)}
|
|
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}
|
|
409
|
+
.meta{margin:var(--takuhon-space-1) 0 0;color:var(--takuhon-color-text-muted);font-size:var(--takuhon-font-size-sm)}
|
|
410
|
+
.featured-links,.other-links{margin:0 0 var(--takuhon-space-6)}
|
|
411
|
+
.featured-links>ul,.other-links>ul{list-style:none;padding:0;margin:0;display:grid;gap:var(--takuhon-space-2)}
|
|
412
|
+
.featured-links>ul{grid-template-columns:repeat(auto-fill,minmax(220px,1fr))}
|
|
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)}
|
|
414
|
+
.featured-links a:hover,.other-links a:hover{border-color:var(--takuhon-color-accent)}
|
|
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)}
|
|
417
|
+
.brand-icon{width:1.15em;height:1.15em;flex:none;opacity:.85}
|
|
418
|
+
.skills,.tags{display:flex;flex-wrap:wrap;gap:var(--takuhon-space-2)}
|
|
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)}
|
|
422
|
+
.skills-groups{display:grid;gap:var(--takuhon-space-4)}
|
|
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}
|
|
436
|
+
.rec{margin:0 0 var(--takuhon-space-4)}
|
|
437
|
+
.rec blockquote{margin:0;padding-left:var(--takuhon-space-3);border-left:3px solid var(--takuhon-color-border)}
|
|
438
|
+
.rec figcaption{color:var(--takuhon-color-text-muted);font-size:var(--takuhon-font-size-sm);margin-top:var(--takuhon-space-2)}
|
|
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
|
+
}
|
|
117
475
|
function renderEntry(entry) {
|
|
118
|
-
const
|
|
119
|
-
const heading =
|
|
476
|
+
const headHref = entry.url ? safeUrl(entry.url) : void 0;
|
|
477
|
+
const heading = headHref ? externalLink(headHref, escapeHtml(entry.heading)) : escapeHtml(entry.heading);
|
|
120
478
|
const parts = [`<h3>${heading}</h3>`];
|
|
121
|
-
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
|
+
}
|
|
122
485
|
if (entry.dates) parts.push(`<p class="meta">${entry.dates}</p>`);
|
|
123
|
-
if (entry.body) parts.push(`<p>${escapeHtml(entry.body)}</p>`);
|
|
486
|
+
if (entry.body) parts.push(`<p class="desc">${escapeHtml(entry.body)}</p>`);
|
|
124
487
|
if (entry.tags && entry.tags.length > 0) {
|
|
125
488
|
parts.push(
|
|
126
489
|
`<ul class="tags">${entry.tags.map((t) => `<li>${escapeHtml(t)}</li>`).join("")}</ul>`
|
|
127
490
|
);
|
|
128
491
|
}
|
|
129
|
-
|
|
492
|
+
const cls = [entry.current ? "is-current" : "", entry.highlighted ? "is-highlighted" : ""].filter(Boolean).join(" ");
|
|
493
|
+
return `<li${cls ? ` class="${cls}"` : ""}>${parts.join("")}</li>`;
|
|
130
494
|
}
|
|
131
|
-
function entryList(title, entries) {
|
|
495
|
+
function entryList(title, entries, variant) {
|
|
132
496
|
if (entries.length === 0) return "";
|
|
133
|
-
|
|
497
|
+
const cls = variant ? `entries entries--${variant}` : "entries";
|
|
498
|
+
return `<section><h2>${escapeHtml(title)}</h2><ul class="${cls}">${entries.map(renderEntry).join("")}</ul></section>`;
|
|
134
499
|
}
|
|
135
|
-
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) {
|
|
136
557
|
const parts = [];
|
|
558
|
+
if (localeNavHtml) parts.push(localeNavHtml);
|
|
137
559
|
const avatarSrc = p.avatar?.url ? safeUrl(p.avatar.url) : void 0;
|
|
138
560
|
if (avatarSrc) {
|
|
139
561
|
parts.push(
|
|
@@ -143,29 +565,114 @@ function renderHeader(p) {
|
|
|
143
565
|
parts.push(`<h1>${escapeHtml(p.displayName)}</h1>`);
|
|
144
566
|
if (p.tagline) parts.push(`<p class="tagline">${escapeHtml(p.tagline)}</p>`);
|
|
145
567
|
if (p.location?.display) parts.push(`<p class="location">${escapeHtml(p.location.display)}</p>`);
|
|
146
|
-
if (p.bio) parts.push(`<p class="bio">${escapeHtml(p.bio)}</p>`);
|
|
147
568
|
return `<header>${parts.join("")}</header>`;
|
|
148
569
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
+
}
|
|
591
|
+
function renderLinkItem(link) {
|
|
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) : "";
|
|
595
|
+
const href = safeUrl(link.url);
|
|
596
|
+
return href ? `<li><a href="${escapeHtml(href)}" rel="me noopener">${main}${pill}</a></li>` : `<li>${main}${pill}</li>`;
|
|
597
|
+
}
|
|
598
|
+
function byOrder(a, b) {
|
|
599
|
+
return (a.order ?? 0) - (b.order ?? 0);
|
|
157
600
|
}
|
|
158
|
-
function
|
|
601
|
+
function renderFeaturedLinks(links, ariaLabel) {
|
|
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) {
|
|
607
|
+
const others = links.filter((l) => l.featured !== true).sort(byOrder);
|
|
608
|
+
if (others.length === 0) return "";
|
|
609
|
+
return `<section class="other-links"><h2>${escapeHtml(heading)}</h2><ul>${others.map(renderLinkItem).join("")}</ul></section>`;
|
|
610
|
+
}
|
|
611
|
+
function renderSkills(skills, categories, heading) {
|
|
159
612
|
if (skills.length === 0) return "";
|
|
160
|
-
const
|
|
161
|
-
|
|
613
|
+
const h = escapeHtml(heading);
|
|
614
|
+
const chips = (list) => `<ul class="skills">${list.map((s) => `<li>${escapeHtml(s.label)}</li>`).join("")}</ul>`;
|
|
615
|
+
if (!categories || categories.length === 0) {
|
|
616
|
+
return `<section><h2>${h}</h2>${chips(skills)}</section>`;
|
|
617
|
+
}
|
|
618
|
+
const UNCAT = "";
|
|
619
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
620
|
+
for (const s of skills) {
|
|
621
|
+
const key = s.category ?? UNCAT;
|
|
622
|
+
const arr = buckets.get(key) ?? [];
|
|
623
|
+
arr.push(s);
|
|
624
|
+
buckets.set(key, arr);
|
|
625
|
+
}
|
|
626
|
+
const group = (heading2, list) => `<div class="skill-group">${heading2 !== void 0 ? `<h3>${escapeHtml(heading2)}</h3>` : ""}${chips(list)}</div>`;
|
|
627
|
+
const seen = /* @__PURE__ */ new Set();
|
|
628
|
+
const groups = [];
|
|
629
|
+
for (const cat of categories) {
|
|
630
|
+
const list = buckets.get(cat.id);
|
|
631
|
+
if (!list || list.length === 0) continue;
|
|
632
|
+
seen.add(cat.id);
|
|
633
|
+
groups.push(group(cat.label, list));
|
|
634
|
+
}
|
|
635
|
+
for (const [key, list] of buckets) {
|
|
636
|
+
if (key === UNCAT || seen.has(key)) continue;
|
|
637
|
+
groups.push(group(key, list));
|
|
638
|
+
}
|
|
639
|
+
const uncategorized = buckets.get(UNCAT);
|
|
640
|
+
if (uncategorized && uncategorized.length > 0) groups.push(group(void 0, uncategorized));
|
|
641
|
+
return `<section><h2>${h}</h2><div class="skills-groups">${groups.join("")}</div></section>`;
|
|
642
|
+
}
|
|
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
|
+
}
|
|
162
650
|
}
|
|
163
|
-
function
|
|
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) {
|
|
164
671
|
if (languages.length === 0) return "";
|
|
165
672
|
const items = languages.map((l) => `<li>${escapeHtml(`${l.displayName ?? l.language} \u2014 ${l.proficiency}`)}</li>`).join("");
|
|
166
|
-
return `<section><h2
|
|
673
|
+
return `<section><h2>${escapeHtml(heading)}</h2><ul class="entries">${items}</ul></section>`;
|
|
167
674
|
}
|
|
168
|
-
function renderRecommendations(recs) {
|
|
675
|
+
function renderRecommendations(recs, heading) {
|
|
169
676
|
if (recs.length === 0) return "";
|
|
170
677
|
const items = recs.map((r) => {
|
|
171
678
|
const authorHref = r.author.url ? safeUrl(r.author.url) : void 0;
|
|
@@ -174,9 +681,9 @@ function renderRecommendations(recs) {
|
|
|
174
681
|
const rel = r.relationship ? ` (${escapeHtml(r.relationship)})` : "";
|
|
175
682
|
return `<figure class="rec"><blockquote>${escapeHtml(r.body)}</blockquote><figcaption>\u2014 ${caption}${rel}</figcaption></figure>`;
|
|
176
683
|
}).join("");
|
|
177
|
-
return `<section><h2
|
|
684
|
+
return `<section><h2>${escapeHtml(heading)}</h2>${items}</section>`;
|
|
178
685
|
}
|
|
179
|
-
function renderContact(contact) {
|
|
686
|
+
function renderContact(contact, heading) {
|
|
180
687
|
const items = [];
|
|
181
688
|
if (contact.email) {
|
|
182
689
|
items.push(
|
|
@@ -188,191 +695,266 @@ function renderContact(contact) {
|
|
|
188
695
|
items.push(`<li><a href="${escapeHtml(formHref)}">Contact form</a></li>`);
|
|
189
696
|
}
|
|
190
697
|
if (items.length === 0) return "";
|
|
191
|
-
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>`;
|
|
192
745
|
}
|
|
193
|
-
function renderActivity(snapshot) {
|
|
746
|
+
function renderActivity(snapshot, heading) {
|
|
194
747
|
if (!snapshot) return "";
|
|
195
748
|
const svg = renderActivitySvg(snapshot);
|
|
196
749
|
if (svg === "") return "";
|
|
197
|
-
return `<section class="activity"><h2
|
|
750
|
+
return `<section class="activity"><h2>${escapeHtml(heading)}</h2>${svg}</section>`;
|
|
198
751
|
}
|
|
199
752
|
function renderJsonLdScript(data) {
|
|
200
753
|
const payload = JSON.stringify(generateJsonLd(data));
|
|
201
754
|
return `<script type="application/ld+json">${escapeJsonLd(payload)}</script>`;
|
|
202
755
|
}
|
|
203
|
-
function renderLocaleNav(localeNav) {
|
|
756
|
+
function renderLocaleNav(localeNav, ariaLabel) {
|
|
204
757
|
const items = localeNav.map(
|
|
205
758
|
(l) => l.current ? `<span aria-current="true">${escapeHtml(l.locale)}</span>` : `<a href="${escapeHtml(l.href)}">${escapeHtml(l.locale)}</a>`
|
|
206
759
|
).join("");
|
|
207
|
-
return `<nav class="locales" aria-label="
|
|
760
|
+
return `<nav class="locales" aria-label="${escapeHtml(ariaLabel)}">${items}</nav>`;
|
|
208
761
|
}
|
|
762
|
+
var DATE_SEPARATOR_JA = " \u301C ";
|
|
763
|
+
var DATE_SEPARATOR_DEFAULT = " \u2013 ";
|
|
209
764
|
function renderProfileHtml(input) {
|
|
210
765
|
const d = input.localized;
|
|
211
766
|
const p = d.profile;
|
|
767
|
+
const locale = d.resolvedLocale;
|
|
212
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 });
|
|
213
778
|
const head = [
|
|
214
779
|
'<meta charset="utf-8">',
|
|
215
780
|
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
216
781
|
`<title>${escapeHtml(p.displayName)}</title>`,
|
|
217
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)}">` : "",
|
|
218
789
|
input.canonicalUrl ? `<link rel="canonical" href="${escapeHtml(input.canonicalUrl)}">` : "",
|
|
219
790
|
...input.alternates.map(
|
|
220
791
|
(a) => `<link rel="alternate" hreflang="${escapeHtml(a.hreflang)}" href="${escapeHtml(a.href)}">`
|
|
221
792
|
),
|
|
222
793
|
input.jsonLd ? renderJsonLdScript(d) : "",
|
|
223
794
|
input.contact ? '<link rel="stylesheet" href="/contact-widget.css">' : "",
|
|
224
|
-
`<style>${
|
|
795
|
+
`<style>${buildTokenCss(d.settings.appearance)}
|
|
796
|
+
${CSS}</style>`,
|
|
797
|
+
// First-party <head> slot (raw, unescaped): OG image tags, manifest link, etc.
|
|
798
|
+
input.slots?.head ?? ""
|
|
225
799
|
].filter(Boolean).join("\n ");
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
entryList(
|
|
231
|
-
"Experience",
|
|
800
|
+
const sections = {
|
|
801
|
+
about: renderBio(p.bio, L.about),
|
|
802
|
+
careers: entryList(
|
|
803
|
+
L.careers,
|
|
232
804
|
d.careers.map((c) => ({
|
|
233
805
|
heading: c.role,
|
|
234
806
|
sub: c.organization,
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
isCurrent: c.isCurrent,
|
|
238
|
-
locale: d.resolvedLocale
|
|
239
|
-
}),
|
|
807
|
+
subUrl: c.url,
|
|
808
|
+
dates: dates(c.startDate, { end: c.endDate, isCurrent: c.isCurrent }),
|
|
240
809
|
body: c.description,
|
|
241
|
-
|
|
242
|
-
}))
|
|
810
|
+
current: c.isCurrent
|
|
811
|
+
})),
|
|
812
|
+
"timeline"
|
|
243
813
|
),
|
|
244
|
-
entryList(
|
|
245
|
-
|
|
814
|
+
projects: entryList(
|
|
815
|
+
L.projects,
|
|
246
816
|
d.projects.map((x) => ({
|
|
247
817
|
heading: x.title,
|
|
248
|
-
dates: dateRange(x.startDate, { end: x.endDate, locale: d.resolvedLocale }),
|
|
249
|
-
body: x.description,
|
|
250
818
|
url: x.url,
|
|
251
|
-
|
|
252
|
-
|
|
819
|
+
role: x.role,
|
|
820
|
+
dates: dates(x.startDate, { end: x.endDate }),
|
|
821
|
+
body: x.description,
|
|
822
|
+
tags: x.tags,
|
|
823
|
+
highlighted: x.highlighted
|
|
824
|
+
})),
|
|
825
|
+
"cards"
|
|
253
826
|
),
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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,
|
|
258
832
|
d.education.map((e) => {
|
|
259
833
|
const degree = nonEmpty([e.degree, e.fieldOfStudy], ", ");
|
|
260
834
|
return {
|
|
261
835
|
heading: degree ?? e.institution,
|
|
262
836
|
sub: degree ? e.institution : void 0,
|
|
263
|
-
dates:
|
|
264
|
-
end: e.endDate,
|
|
265
|
-
isCurrent: e.isCurrent,
|
|
266
|
-
locale: d.resolvedLocale
|
|
267
|
-
}),
|
|
837
|
+
dates: dates(e.startDate, { end: e.endDate, isCurrent: e.isCurrent }),
|
|
268
838
|
body: e.description,
|
|
269
839
|
url: e.url
|
|
270
840
|
};
|
|
271
841
|
})
|
|
272
842
|
),
|
|
273
|
-
entryList(
|
|
274
|
-
|
|
843
|
+
certifications: entryList(
|
|
844
|
+
L.certifications,
|
|
275
845
|
d.certifications.map((c) => ({
|
|
276
846
|
heading: c.title,
|
|
277
847
|
sub: c.issuingOrganization,
|
|
278
|
-
dates:
|
|
848
|
+
dates: dates(c.issueDate, { end: c.expirationDate }),
|
|
279
849
|
url: c.url
|
|
280
850
|
}))
|
|
281
851
|
),
|
|
282
|
-
entryList(
|
|
283
|
-
|
|
852
|
+
publications: entryList(
|
|
853
|
+
L.publications,
|
|
284
854
|
d.publications.map((x) => ({
|
|
285
855
|
heading: x.title,
|
|
286
856
|
sub: nonEmpty([x.publisher, x.coAuthors?.join(", ")], " \xB7 "),
|
|
287
|
-
dates:
|
|
857
|
+
dates: dates(x.date, {}),
|
|
288
858
|
body: x.description,
|
|
289
859
|
url: x.url ?? (x.doi ? `https://doi.org/${x.doi}` : void 0)
|
|
290
860
|
}))
|
|
291
861
|
),
|
|
292
|
-
entryList(
|
|
293
|
-
|
|
862
|
+
honors: entryList(
|
|
863
|
+
L.honors,
|
|
294
864
|
d.honors.map((x) => ({
|
|
295
865
|
heading: x.title,
|
|
296
866
|
sub: x.issuer,
|
|
297
|
-
dates:
|
|
867
|
+
dates: dates(x.date, {}),
|
|
298
868
|
body: x.description,
|
|
299
869
|
url: x.url
|
|
300
870
|
}))
|
|
301
871
|
),
|
|
302
|
-
entryList(
|
|
303
|
-
|
|
872
|
+
memberships: entryList(
|
|
873
|
+
L.memberships,
|
|
304
874
|
d.memberships.map((x) => ({
|
|
305
875
|
heading: x.role ?? x.organization,
|
|
306
876
|
sub: x.role ? x.organization : void 0,
|
|
307
|
-
dates:
|
|
308
|
-
end: x.endDate,
|
|
309
|
-
isCurrent: x.isCurrent,
|
|
310
|
-
locale: d.resolvedLocale
|
|
311
|
-
}),
|
|
312
|
-
body: x.description,
|
|
313
|
-
url: x.url
|
|
314
|
-
}))
|
|
315
|
-
),
|
|
316
|
-
entryList(
|
|
317
|
-
"Volunteering",
|
|
318
|
-
d.volunteering.map((x) => ({
|
|
319
|
-
heading: x.role,
|
|
320
|
-
sub: nonEmpty([x.organization, x.cause], " \xB7 "),
|
|
321
|
-
dates: dateRange(x.startDate, {
|
|
322
|
-
end: x.endDate,
|
|
323
|
-
isCurrent: x.isCurrent,
|
|
324
|
-
locale: d.resolvedLocale
|
|
325
|
-
}),
|
|
877
|
+
dates: dates(x.startDate, { end: x.endDate, isCurrent: x.isCurrent }),
|
|
326
878
|
body: x.description,
|
|
327
879
|
url: x.url
|
|
328
880
|
}))
|
|
329
881
|
),
|
|
330
|
-
entryList(
|
|
331
|
-
|
|
882
|
+
courses: entryList(
|
|
883
|
+
L.courses,
|
|
332
884
|
d.courses.map((x) => ({
|
|
333
885
|
heading: x.title,
|
|
334
886
|
sub: x.provider,
|
|
335
|
-
dates:
|
|
887
|
+
dates: dates(x.completionDate, {}),
|
|
336
888
|
body: x.description,
|
|
337
889
|
url: x.certificateUrl
|
|
338
890
|
}))
|
|
339
891
|
),
|
|
340
|
-
entryList(
|
|
341
|
-
|
|
892
|
+
patents: entryList(
|
|
893
|
+
L.patents,
|
|
342
894
|
d.patents.map((x) => ({
|
|
343
895
|
heading: x.title,
|
|
344
896
|
sub: nonEmpty([x.patentNumber, x.office, x.status, x.coInventors?.join(", ")], " \xB7 "),
|
|
345
|
-
dates:
|
|
897
|
+
dates: dates(x.filingDate ?? x.grantDate, {}),
|
|
346
898
|
body: x.description,
|
|
347
899
|
url: x.url
|
|
348
900
|
}))
|
|
349
901
|
),
|
|
350
|
-
entryList(
|
|
351
|
-
|
|
902
|
+
testScores: entryList(
|
|
903
|
+
L.testScores,
|
|
352
904
|
d.testScores.map((x) => ({
|
|
353
905
|
heading: `${x.title}: ${x.score}`,
|
|
354
|
-
dates:
|
|
906
|
+
dates: dates(x.date, {}),
|
|
355
907
|
body: x.description,
|
|
356
908
|
url: x.url
|
|
357
909
|
}))
|
|
358
910
|
),
|
|
359
|
-
renderLanguages(d.languages),
|
|
360
|
-
renderRecommendations(d.recommendations),
|
|
361
|
-
|
|
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 ?? ""
|
|
362
935
|
].filter(Boolean).join("\n");
|
|
363
|
-
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>` : "";
|
|
364
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 ?? "";
|
|
365
947
|
return `<!DOCTYPE html>
|
|
366
948
|
<html lang="${escapeHtml(d.resolvedLocale)}">
|
|
367
949
|
<head>
|
|
368
950
|
${head}
|
|
369
951
|
</head>
|
|
370
952
|
<body>
|
|
371
|
-
<main>
|
|
953
|
+
${skipLink}<main id="main">
|
|
372
954
|
${body}
|
|
373
955
|
</main>
|
|
374
956
|
${footer ? `${footer}
|
|
375
|
-
` : ""}${contactScript}</body>
|
|
957
|
+
` : ""}${contactScript}${bodyEnd}</body>
|
|
376
958
|
</html>
|
|
377
959
|
`;
|
|
378
960
|
}
|
|
@@ -497,7 +1079,33 @@ function pathLocaleFromUrl(url) {
|
|
|
497
1079
|
// src/public-app.ts
|
|
498
1080
|
var FALLBACK_VERSION = "bundled-fixture";
|
|
499
1081
|
var TURNSTILE_ORIGIN = "https://challenges.cloudflare.com";
|
|
500
|
-
|
|
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 ?? [];
|
|
501
1109
|
return [
|
|
502
1110
|
"default-src 'self'",
|
|
503
1111
|
// `https:` lets the server-rendered profile page load remote avatar images
|
|
@@ -505,10 +1113,11 @@ function buildPublicCsp(contact) {
|
|
|
505
1113
|
// already blocks non-http(s) schemes); `data:` covers inline placeholders.
|
|
506
1114
|
"img-src 'self' https: data:",
|
|
507
1115
|
"style-src 'self' 'unsafe-inline'",
|
|
508
|
-
|
|
1116
|
+
`script-src ${scriptSrc.join(" ")}`,
|
|
509
1117
|
"font-src 'self'",
|
|
510
|
-
|
|
511
|
-
...
|
|
1118
|
+
`connect-src ${connectSrc.join(" ")}`,
|
|
1119
|
+
...frameSrc.length > 0 ? [`frame-src ${frameSrc.join(" ")}`] : [],
|
|
1120
|
+
...workerSrc.length > 0 ? [`worker-src ${workerSrc.join(" ")}`] : [],
|
|
512
1121
|
"frame-ancestors 'none'",
|
|
513
1122
|
"base-uri 'self'",
|
|
514
1123
|
"form-action 'self'",
|
|
@@ -529,6 +1138,19 @@ async function loadProfile(deps) {
|
|
|
529
1138
|
}
|
|
530
1139
|
function createPublicApp(deps) {
|
|
531
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
|
+
}
|
|
532
1154
|
app.use("*", async (c, next) => {
|
|
533
1155
|
await next();
|
|
534
1156
|
const h = c.res.headers;
|
|
@@ -537,9 +1159,11 @@ function createPublicApp(deps) {
|
|
|
537
1159
|
h.set("x-frame-options", "DENY");
|
|
538
1160
|
h.set("referrer-policy", "strict-origin-when-cross-origin");
|
|
539
1161
|
h.set("permissions-policy", "camera=(), microphone=(), geolocation=(), interest-cohort=()");
|
|
1162
|
+
const contact = c.get("contactEnabled") === true;
|
|
1163
|
+
const page = c.get("profilePage") === true;
|
|
540
1164
|
h.set(
|
|
541
1165
|
"content-security-policy",
|
|
542
|
-
|
|
1166
|
+
page ? contact ? pageCspWithContact : pageCsp : contact ? PUBLIC_CSP_WITH_CONTACT : PUBLIC_CSP
|
|
543
1167
|
);
|
|
544
1168
|
h.set("access-control-allow-origin", "*");
|
|
545
1169
|
h.set("access-control-expose-headers", "ETag");
|
|
@@ -584,6 +1208,14 @@ function createPublicApp(deps) {
|
|
|
584
1208
|
} : void 0;
|
|
585
1209
|
if (contact) c.set("contactEnabled", true);
|
|
586
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
|
+
} : {},
|
|
587
1219
|
localized,
|
|
588
1220
|
canonicalUrl: `${origin}${localePath(current)}`,
|
|
589
1221
|
alternates: [
|
|
@@ -593,11 +1225,13 @@ function createPublicApp(deps) {
|
|
|
593
1225
|
localeNav: locales.map((l) => ({ locale: l, href: localePath(l), current: l === current })),
|
|
594
1226
|
jsonLd: profile.settings.enableJsonLd !== false,
|
|
595
1227
|
activitySnapshot: snapshot ?? void 0,
|
|
596
|
-
contact
|
|
1228
|
+
contact,
|
|
1229
|
+
year: (/* @__PURE__ */ new Date()).getFullYear()
|
|
597
1230
|
});
|
|
598
1231
|
c.header("etag", `"${version}"`);
|
|
599
1232
|
c.header("cache-control", "public, max-age=300");
|
|
600
1233
|
c.header("vary", "Accept-Language, Cookie");
|
|
1234
|
+
c.set("profilePage", true);
|
|
601
1235
|
return c.html(html);
|
|
602
1236
|
});
|
|
603
1237
|
app.get("/health", (c) => {
|
|
@@ -1552,7 +2186,8 @@ function generateSite(profile, options = {}) {
|
|
|
1552
2186
|
alternates,
|
|
1553
2187
|
localeNav,
|
|
1554
2188
|
jsonLd,
|
|
1555
|
-
activitySnapshot
|
|
2189
|
+
activitySnapshot,
|
|
2190
|
+
year: (/* @__PURE__ */ new Date()).getFullYear()
|
|
1556
2191
|
});
|
|
1557
2192
|
pages.push({
|
|
1558
2193
|
route: isDefault ? "/" : `/${locale}/`,
|