@rathnasgala/theme 2.0.12 → 2.0.16
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/package.json +2 -2
- package/payload/.gala/managed-files.json +22 -22
- package/payload/lib/accent.js +1 -1
- package/payload/lib/build-manifest.js +16 -7
- package/payload/lib/performance-budget.js +1 -1
- package/payload/lib/site-config.js +1 -1
- package/payload/src/_data/feedLinks.js +1 -1
- package/payload/src/_data/site.js +1 -1
- package/payload/src/_includes/components/ui.njk +3 -3
- package/payload/src/_includes/layouts/base.njk +1 -1
- package/payload/src/_includes/layouts/post.njk +1 -1
- package/payload/src/assets/engagement-transport.js +1 -1
- package/payload/src/assets/interactions.js +36 -16
- package/payload/src/assets/reader.js +2 -2
- package/payload/src/assets/theme-mode.js +1 -1
- package/payload/src/assets/theme.css +1 -1
- package/payload/src/assets/version.js +23 -17
- package/payload/src/cname.11ty.js +1 -1
- package/payload/src/feed.11ty.js +1 -1
- package/payload/src/index.njk +1 -1
- package/payload/src/languages.11ty.js +5 -4
- package/payload/src/styles/theme.css +8 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rathnasgala/theme",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.16",
|
|
4
4
|
"description": "Managed Gala static-site theme payload",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"url": "git+https://github.com/rathnasgala/site-template.git"
|
|
9
9
|
},
|
|
10
10
|
"gala": {
|
|
11
|
-
"sourceCommit": "
|
|
11
|
+
"sourceCommit": "3a860088611e3a69f68e30bca4d14779dba0a635"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"payload"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"themePackage": {
|
|
4
4
|
"name": "@rathnasgala/theme",
|
|
5
|
-
"version": "2.0.
|
|
5
|
+
"version": "2.0.16",
|
|
6
6
|
"availableDesignThemes": [
|
|
7
7
|
"editorial",
|
|
8
8
|
"modern",
|
|
@@ -11,17 +11,17 @@
|
|
|
11
11
|
"securityAdvisories": []
|
|
12
12
|
},
|
|
13
13
|
"requiredBudgets": {
|
|
14
|
-
"managedJavaScriptBytes":
|
|
14
|
+
"managedJavaScriptBytes": 68608,
|
|
15
15
|
"managedCssBytes": 34816
|
|
16
16
|
},
|
|
17
17
|
"files": {
|
|
18
18
|
".gala/publish.yml.template": "d93da94c10378693cdd927cd6623a003ff57c15d8b8aeba3644c914f2753d824",
|
|
19
19
|
".gitignore": "b5fa24e10155097a0d372926fd2e59e6072e7cc2cb6399bbfb4beb8b33ea315b",
|
|
20
20
|
"eleventy.config.js": "7772953f8c55673d9e11fb803c75470f8a61747de9e1cec0d03bbbedb7fae246",
|
|
21
|
-
"lib/accent.js": "
|
|
22
|
-
"lib/build-manifest.js": "
|
|
21
|
+
"lib/accent.js": "24e5d2bcf00e6b3ad86cf8942e56dcb98b0d06ff8e0673ccb7cc85fa732e0837",
|
|
22
|
+
"lib/build-manifest.js": "b6fd412256515603a075d1fcf5f9fb57f197b4fd5f92c9ca28ca5fe2486daf11",
|
|
23
23
|
"lib/engagement-snapshot.js": "298c11b1ab135a3a98a4e31b6320ab60dfd7fda6a9d9053df37d86282a5a6687",
|
|
24
|
-
"lib/performance-budget.js": "
|
|
24
|
+
"lib/performance-budget.js": "70c5d1d697302466e1fa982fd07b19abb299e3fcae0bb05aa8cdebf5d7b69241",
|
|
25
25
|
"lib/prism-compiled-output.js": "0c1f93d025c70dbec10c78f17f59ae10a9a6de773b58adb2f26f42de62f7be2c",
|
|
26
26
|
"lib/prism-session.js": "16a55d9d3d6b5bd2f95bfdb15a76a795b0406ddc51fae45014e0b0f5795194ca",
|
|
27
27
|
"lib/provider-fixtures/embeds.v1.json": "5f26909fb433135321ddf7fdd14e45aeb702805c88f5676941166c5735143ee2",
|
|
@@ -30,19 +30,19 @@
|
|
|
30
30
|
"lib/render-markdown.js": "275b7da721aef1f68cda515673153471c0d597061e8fdf867c5f2b32c98b91db",
|
|
31
31
|
"lib/seo.js": "19cf1d67d5b711b8d3a533f207f267e770ee46fd4a4e6561c5f7fd4e9c1989ff",
|
|
32
32
|
"lib/share-targets.js": "458aff86fab2b62a364442cd8c612565c6e0b53eef2f20cb6237cc562b1e0731",
|
|
33
|
-
"lib/site-config.js": "
|
|
33
|
+
"lib/site-config.js": "e646e031b46a1f8d71ea3afce8cca45d31e65d7b39cbdac10ab5c62934dfbf7e",
|
|
34
34
|
"package-lock.json": "dc11470b1ad897c58d91745f23ff25711c593e62972ec9fb83f4e10fd77e1ecd",
|
|
35
35
|
"package.json": "7a3c2b559054b1613d84cbda3a1fd370bc725818f9f418f0fe90cf3f8f0a9caa",
|
|
36
36
|
"scripts/build-reader.js": "743194c8dc9cfb9b6631db1732b5f36a92e3a4f905a2965c71d56e1ef50c2e45",
|
|
37
37
|
"scripts/lint.js": "c4bb3c961686043fa8c79bed546758ef09d44441421d8209fec9d69f74171025",
|
|
38
38
|
"src/_data/buildManifest.js": "a889cb4642c2f0beaa7f31f662fd8da7ab2d4dce819faf69a3aa984e55270336",
|
|
39
39
|
"src/_data/engagementSnapshot.js": "9217dacebfaf008f7a0ad67ff4e8d1fa8fba609a9dc7e3430c78c5b32024da0e",
|
|
40
|
-
"src/_data/feedLinks.js": "
|
|
40
|
+
"src/_data/feedLinks.js": "8317ec93ef58d2d73d0b23f753109e8485026249c877c2904c2b3b5c8d336782",
|
|
41
41
|
"src/_data/languages.js": "1358748f7fa2f74406d3295a030827d6229a53e94da72cf712e719d7a356aeac",
|
|
42
|
-
"src/_data/site.js": "
|
|
43
|
-
"src/_includes/components/ui.njk": "
|
|
44
|
-
"src/_includes/layouts/base.njk": "
|
|
45
|
-
"src/_includes/layouts/post.njk": "
|
|
42
|
+
"src/_data/site.js": "41e60190d22337321d480a29be9774eaa93a98b848e92b5139129ae97c44fd97",
|
|
43
|
+
"src/_includes/components/ui.njk": "993ddb816a3a74f476040dc403abae3fd02896e99be480fd57177c342c8e8201",
|
|
44
|
+
"src/_includes/layouts/base.njk": "61526fd4f1614d1ca8e52115a6f9402428215e651648dc77d63559471bee763f",
|
|
45
|
+
"src/_includes/layouts/post.njk": "e48e00ca916b740d95e465caae1f1efc0523056ce4bdadd2d2593b80072de530",
|
|
46
46
|
"src/_includes/layouts/prism-configuration.njk": "c984441ac50465eb6ee0e050c68c054e3a0bca98960fb92fb2f5795443301594",
|
|
47
47
|
"src/404.njk": "be0d44370ac5402156aaf3d32ea581057bc61ec94c1a5f92bb809733af7a8a20",
|
|
48
48
|
"src/accent.11ty.js": "52e7d3eb731c5f4649c14c4745f65da026c25b58cf462d075b8ef363f0e487dc",
|
|
@@ -51,29 +51,29 @@
|
|
|
51
51
|
"src/assets/embed-x.svg": "0ae74edc6790ec7e933067fe5392bce756e93fa7ad71caacf1c1ca99fa4ec9b6",
|
|
52
52
|
"src/assets/embed-youtube.svg": "10d192f2b14b67664fea88c8cf246321d20190def51d59fad5762e552219b067",
|
|
53
53
|
"src/assets/engagement-comments.js": "eda0fee471c7e42ee01a25b8aa14ed6ae9690d52b3fbb34027d97534ab62998b",
|
|
54
|
-
"src/assets/engagement-transport.js": "
|
|
54
|
+
"src/assets/engagement-transport.js": "7f69815c4eb4fc0e127a5c09702da4189130d47b73b4598fce4196d7d6f5cbe8",
|
|
55
55
|
"src/assets/favicon.svg": "8615e3d96fe37ae9ed14afb1886e7c0e690eef5b10be66ca91cccfd085a74bb6",
|
|
56
|
-
"src/assets/interactions.js": "
|
|
56
|
+
"src/assets/interactions.js": "a346766369071f0351c52de5daeecc52ecffa8c516a37a9c75ca37e2f431c8fc",
|
|
57
57
|
"src/assets/preferences.js": "789127bee013ed6a1bbf8cefd7b9109291c16220e7a6e09314e3c93c54731d1d",
|
|
58
58
|
"src/assets/prism-session.js": "1fab23725b843006c3f589af7669ef3ad6ad01f3264a3b00564e64b56738d2cd",
|
|
59
|
-
"src/assets/reader.js": "
|
|
59
|
+
"src/assets/reader.js": "66aa4eacbce0d78d5c1f56d21ba6028738292d54eef568b86264007f3ab8c992",
|
|
60
60
|
"src/assets/search.js": "df425f0239277792ad39f2138bc89cb66cc4ecda5ed59042d46abc2f05372088",
|
|
61
|
-
"src/assets/theme-mode.js": "
|
|
62
|
-
"src/assets/theme.css": "
|
|
63
|
-
"src/assets/version.js": "
|
|
61
|
+
"src/assets/theme-mode.js": "d81af8317b59f6d94dc8154c1dcba9d3e16accb37348bc3fce127712141d6c63",
|
|
62
|
+
"src/assets/theme.css": "7391086efd6f61791c47bbdf3523d7452e17b1cdb1fc91339861fe2f0a2c884b",
|
|
63
|
+
"src/assets/version.js": "57d1bb2bd949a7bdc00de6293138c1044a57b41f611ca6cc2da5aa168e5f0543",
|
|
64
64
|
"src/client/reader.js": "643aec955c5beb6f902c737313dbc1a8232150fcb5d800350aa7fb1fb69f1760",
|
|
65
|
-
"src/cname.11ty.js": "
|
|
65
|
+
"src/cname.11ty.js": "890f963f42fa8d20dce35f2784e099dcb6d7c03840909db3c9fa10c6a94cbeca",
|
|
66
66
|
"src/contact.njk": "f4d74d038781e15af0acef571901c91e7753a2e21b6bef617429585698839a34",
|
|
67
|
-
"src/feed.11ty.js": "
|
|
68
|
-
"src/index.njk": "
|
|
69
|
-
"src/languages.11ty.js": "
|
|
67
|
+
"src/feed.11ty.js": "0082bb5d69bcb91618c4069c56766a48ecb756bf12df0c794ed63f3ae6936a88",
|
|
68
|
+
"src/index.njk": "98e415ad30a668e7696b4647533c7fed030f8da79760026634bc87702b015ee2",
|
|
69
|
+
"src/languages.11ty.js": "cf3df5a22676abd9c862af6ff7048c41c28dc2eaa6baddbeb44228b73ddfe7e3",
|
|
70
70
|
"src/posts.11ty.js": "abdfd572fb93fc79c92e1ba2aa9601e768e2ee32ae108038a429407c0b1cb053",
|
|
71
71
|
"src/prism-configurations.11ty.js": "ecfda10ef9c1092549e7eeb75b971c14481bea0f48919764b7df79bf026ec6aa",
|
|
72
72
|
"src/redirects.11ty.js": "3c163246a59f5364861db6a6f05ae88afc938a5379d86cf42776d8d49b0031f3",
|
|
73
73
|
"src/search-index.11ty.js": "700dc2a2be0ef20de3a828a4612fcf7a082691eade29c6e1d75f09c24b6c6024",
|
|
74
74
|
"src/search.njk": "63f2e8aeebb63c90da2e375eece639ce096cad1eff7f28628cafc46faf7ceee6",
|
|
75
75
|
"src/sitemap.11ty.js": "4f233e94fb4a913ad5659116518432ed264984a7b622eba3558c77b4ec6435dc",
|
|
76
|
-
"src/styles/theme.css": "
|
|
76
|
+
"src/styles/theme.css": "56fe8387182aee00df73f24d853f9d98fe0026f9e627e193e6d9deba9a4f631e",
|
|
77
77
|
"src/version.njk": "c0833dcf14a246c069903d35f8be92d410935b968a26346af237bc1392c88340",
|
|
78
78
|
"static/favicon.ico": "ad3cb0b02f0164f3af6280d0bfe712879d55cec0e9a7a567f658aaddca5a55a2",
|
|
79
79
|
"static/robots.txt": "16ceb5ee3e0dc13aa9adf31a3ebbe45a1d965b8c2b9f72eaf84e5911e140ed95"
|
package/payload/lib/accent.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Offering a handful of named presets was not picking a colour, and an unconstrained colour picker
|
|
5
5
|
* is worse: most colours a person likes fail contrast against one ground or the other, and a
|
|
6
6
|
* writer cannot see that until the site is deployed. So the seed is treated as a *hue* the writer
|
|
7
|
-
* chose, and the lightness is moved
|
|
7
|
+
* chose, and the lightness is moved - as little as possible - until the result is readable on the
|
|
8
8
|
* background it will actually sit on.
|
|
9
9
|
*
|
|
10
10
|
* The maths is OKLab, so a hue keeps its identity while its lightness changes. Doing this in sRGB
|
|
@@ -22,7 +22,7 @@ function absoluteHttps(value, field) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
function validatedPost(post, index, seen) {
|
|
25
|
+
function validatedPost(post, index, seen, preview) {
|
|
26
26
|
if (post == null || Array.isArray(post) || typeof post !== 'object') {
|
|
27
27
|
throw new TypeError(`build manifest posts[${index}] must be a mapping`);
|
|
28
28
|
}
|
|
@@ -30,7 +30,10 @@ function validatedPost(post, index, seen) {
|
|
|
30
30
|
|| post.source.split('/').some((part) => part === '.' || part === '..')) {
|
|
31
31
|
throw new TypeError(`build manifest posts[${index}].source is invalid`);
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
const transientPreviewPost = preview
|
|
34
|
+
&& post.publicationState === 'not-emitted'
|
|
35
|
+
&& post.id === null;
|
|
36
|
+
if (!ULID.test(post.id) && !transientPreviewPost) {
|
|
34
37
|
throw new TypeError(`build manifest posts[${index}].id is invalid`);
|
|
35
38
|
}
|
|
36
39
|
if (!SLUG.test(post.slug) || !LANGUAGE.test(post.language)) {
|
|
@@ -43,7 +46,8 @@ function validatedPost(post, index, seen) {
|
|
|
43
46
|
}
|
|
44
47
|
absoluteHttps(post.canonicalUrl, `build manifest posts[${index}].canonicalUrl`);
|
|
45
48
|
absoluteHttps(post.pageUrl, `build manifest posts[${index}].pageUrl`);
|
|
46
|
-
if (!STATES.has(post.publicationState)
|
|
49
|
+
if (!STATES.has(post.publicationState)
|
|
50
|
+
&& !(preview && post.publicationState === 'not-emitted')) {
|
|
47
51
|
throw new TypeError(`build manifest posts[${index}].publicationState is invalid`);
|
|
48
52
|
}
|
|
49
53
|
if (post.frontmatter == null || Array.isArray(post.frontmatter)
|
|
@@ -86,12 +90,14 @@ function validatedPost(post, index, seen) {
|
|
|
86
90
|
return Object.freeze({ source, output });
|
|
87
91
|
});
|
|
88
92
|
}
|
|
89
|
-
const identityVariant =
|
|
93
|
+
const identityVariant = post.id === null
|
|
94
|
+
? null
|
|
95
|
+
: `${post.id}\u0000${post.language.toLowerCase()}`;
|
|
90
96
|
for (const [type, value] of [
|
|
91
97
|
['source', post.source],
|
|
92
98
|
['relativeUrl', post.relativeUrl],
|
|
93
99
|
['pageUrl', post.pageUrl],
|
|
94
|
-
['identity-language', identityVariant]
|
|
100
|
+
...(identityVariant == null ? [] : [['identity-language', identityVariant]])
|
|
95
101
|
]) {
|
|
96
102
|
const values = seen[type];
|
|
97
103
|
if (values.has(value)) throw new TypeError(`build manifest duplicate ${type}: ${value}`);
|
|
@@ -132,7 +138,8 @@ function validatedRedirect(redirect, index, seen) {
|
|
|
132
138
|
export function validateBuildManifest(value) {
|
|
133
139
|
if (value == null || Array.isArray(value) || typeof value !== 'object'
|
|
134
140
|
|| ![1, 2].includes(value.schemaVersion) || !DATE.test(value.evaluationDate)
|
|
135
|
-
|| !Array.isArray(value.posts) || !Array.isArray(value.redirects)
|
|
141
|
+
|| !Array.isArray(value.posts) || !Array.isArray(value.redirects)
|
|
142
|
+
|| (value.preview != null && typeof value.preview !== 'boolean')) {
|
|
136
143
|
throw new TypeError('Unsupported build manifest schema');
|
|
137
144
|
}
|
|
138
145
|
const seen = {
|
|
@@ -141,7 +148,8 @@ export function validateBuildManifest(value) {
|
|
|
141
148
|
pageUrl: new Set(),
|
|
142
149
|
'identity-language': new Set()
|
|
143
150
|
};
|
|
144
|
-
const
|
|
151
|
+
const preview = value.preview === true;
|
|
152
|
+
const posts = value.posts.map((post, index) => validatedPost(post, index, seen, preview));
|
|
145
153
|
const redirects = value.redirects.map((redirect, index) =>
|
|
146
154
|
validatedRedirect(redirect, index, seen)
|
|
147
155
|
);
|
|
@@ -200,6 +208,7 @@ export function validateBuildManifest(value) {
|
|
|
200
208
|
return Object.freeze({
|
|
201
209
|
schemaVersion: value.schemaVersion,
|
|
202
210
|
evaluationDate: value.evaluationDate,
|
|
211
|
+
...(preview ? { preview: true } : {}),
|
|
203
212
|
...(assignedContentIds == null
|
|
204
213
|
? {}
|
|
205
214
|
: { assignedContentIds: Object.freeze(assignedContentIds) }),
|
|
@@ -26,7 +26,7 @@ export async function enforcePerformanceBudgets({ outputDirectory, budgets }) {
|
|
|
26
26
|
const files = await filesBelow(output);
|
|
27
27
|
/*
|
|
28
28
|
* Everything under `assets`, nested included. This used to compare `path.dirname` against the
|
|
29
|
-
* assets directory exactly, so anything in a subdirectory was invisible to the budget
|
|
29
|
+
* assets directory exactly, so anything in a subdirectory was invisible to the budget - the
|
|
30
30
|
* moment the reader runtime moved into `assets/vendor` it would have stopped being counted, and
|
|
31
31
|
* a budget that quietly ignores two thirds of the JavaScript it is meant to guard is worse than
|
|
32
32
|
* having none.
|
|
@@ -56,7 +56,7 @@ export function validateCanonicalUrlTemplate(value) {
|
|
|
56
56
|
* back to its own defaults for anything absent. Present-but-unknown is still an error, because
|
|
57
57
|
* that is a writer expecting something the CSS will not do.
|
|
58
58
|
*/
|
|
59
|
-
/** A free colour, not a menu
|
|
59
|
+
/** A free colour, not a menu - but still a colour. */
|
|
60
60
|
const ACCENT = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
61
61
|
|
|
62
62
|
function validateImplementedDesign(config) {
|
|
@@ -6,7 +6,7 @@ const site = await loadSiteConfiguration();
|
|
|
6
6
|
|
|
7
7
|
export default languages.map((language) => Object.freeze({
|
|
8
8
|
language,
|
|
9
|
-
title: `${site.site.name}
|
|
9
|
+
title: `${site.site.name} - ${language}`,
|
|
10
10
|
href: siteUrl({
|
|
11
11
|
canonicalBaseUrl: site.hosting.canonicalBaseUrl,
|
|
12
12
|
pathPrefix: site.hosting.pathPrefix ?? '/',
|
|
@@ -21,7 +21,7 @@ export default async function siteConfiguration() {
|
|
|
21
21
|
};
|
|
22
22
|
/*
|
|
23
23
|
* The writer picks one colour; the page needs two, because the same colour cannot be readable on
|
|
24
|
-
* both a light and a dark ground. Derived here rather than in CSS so the contrast is exact
|
|
24
|
+
* both a light and a dark ground. Derived here rather than in CSS so the contrast is exact - a
|
|
25
25
|
* `color-mix()` approximation can land under 4.5:1 and nobody would know until a reader could
|
|
26
26
|
* not read a link.
|
|
27
27
|
*
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
{% macro hero(title, description='', image='', imageAlt='') %}<header class="gala-hero">{% if image %}<img class="gala-hero__image" src="{{ image }}" alt="{{ imageAlt }}">{% endif %}<div class="gala-hero__text"><h1>{{ title }}</h1>{% if description %}<p>{{ description }}</p>{% endif %}</div></header>{% endmacro %}
|
|
6
6
|
{% macro pageContent(content) %}<article class="gala-page-content">{{ content | safe }}</article>{% endmacro %}
|
|
7
7
|
{% macro authorProfile(name, bio='', avatar='', links=[]) %}<section class="gala-author-profile" aria-label="Author profile">{% if avatar %}<img src="{{ avatar }}" alt="" width="80" height="80">{% endif %}<div><h2>{{ name }}</h2>{% if bio %}<p>{{ bio }}</p>{% endif %}{% if links.length %}<ul class="gala-social-links">{% for link in links %}<li><a href="{{ link.url }}" rel="me">{{ link.label }}</a></li>{% endfor %}</ul>{% endif %}</div></section>{% endmacro %}
|
|
8
|
-
{% macro cardIndex(posts) %}<ol class="gala-card-index">{% for post in posts %}{% if post.publicationState == "published" %}<li><article class="gala-card" lang="{{ post.language | escape }}"><h2><a href="{{ post.relativeUrl | url }}">{{ post.frontmatter.title }}</a></h2>{% if post.frontmatter.description %}<p>{{ post.frontmatter.description }}</p>{% endif %}</article></li>{% endif %}{% endfor %}</ol>{% endmacro %}
|
|
8
|
+
{% macro cardIndex(posts, preview=false) %}<ol class="gala-card-index">{% for post in posts %}{% if post.publicationState == "published" or (preview and post.publicationState == "not-emitted") %}<li><article class="gala-card" lang="{{ post.language | escape }}"><h2><a href="{{ post.relativeUrl | url }}">{{ post.frontmatter.title }}</a></h2>{% if preview and post.publicationState == "not-emitted" %}<p>Scheduled for {{ post.frontmatter.publishAfterDate }}</p>{% elif post.frontmatter.description %}<p>{{ post.frontmatter.description }}</p>{% endif %}</article></li>{% endif %}{% endfor %}</ol>{% endmacro %}
|
|
9
9
|
{% macro statsGraph(label, points) %}<figure class="gala-stats-graph"><svg role="img" aria-label="{{ label }}" viewBox="0 0 100 40" preserveAspectRatio="none"><polyline points="{{ points }}" vector-effect="non-scaling-stroke"></polyline></svg><figcaption>{{ label }}</figcaption></figure>{% endmacro %}
|
|
10
10
|
{% macro loading(label='Loading') %}<div class="gala-loading" role="status"><span class="gala-loading__indicator" aria-hidden="true"></span><span class="gala-visually-hidden">{{ label }}</span></div>{% endmacro %}
|
|
11
11
|
{% macro pagination(previous='', next='') %}<nav class="gala-pagination" aria-label="Pagination">{% if previous %}<a href="{{ previous }}" rel="prev">Previous</a>{% endif %}{% if next %}<a href="{{ next }}" rel="next">Next</a>{% endif %}</nav>{% endmacro %}
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
at the moment of intent, and the intent is replayed once they are back. #}
|
|
58
58
|
{# The conversation is an island: it owns its own state, because a thread five deep with a page
|
|
59
59
|
cursor, an open reply form and an edit in progress is state, and hand-written DOM kept losing
|
|
60
|
-
track of it. Nothing here is needed by a search engine
|
|
61
|
-
API after load
|
|
60
|
+
track of it. Nothing here is needed by a search engine - comments have always arrived from the
|
|
61
|
+
API after load - so rendering them on the client costs nothing the article cares about. #}
|
|
62
62
|
<div id="comments" data-gala-comments></div>
|
|
63
63
|
<output class="gala-conversation__status" data-engagement-status aria-live="polite">Loading comments…</output>
|
|
64
64
|
</div>
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
<dialog id="gala-account-dialog" class="gala-dialog gala-account-dialog" aria-labelledby="gala-account-title">
|
|
106
106
|
<form method="dialog" class="gala-dialog__close"><button type="submit" aria-label="Close account">Close</button></form>
|
|
107
107
|
<h2 id="gala-account-title" class="gala-visually-hidden">Gala account</h2>
|
|
108
|
-
<iframe data-gala-session-frame title="Gala account and sign in" loading="
|
|
108
|
+
<iframe data-gala-session-frame title="Gala account and sign in" loading="eager" allow="identity-credentials-get" src="{{ site.platform.apiBaseUrl or 'https://api.gala67.com' }}/v1/widget/session?siteId={{ site.site.id | escape }}"></iframe>
|
|
109
109
|
</dialog>
|
|
110
110
|
{% endif %}
|
|
111
111
|
</body>
|
|
@@ -3,7 +3,7 @@ layout: layouts/base.njk
|
|
|
3
3
|
---
|
|
4
4
|
{% from "components/ui.njk" import actionRail, conversation, languageSwitcher, tableOfContents %}
|
|
5
5
|
<article id="article" class="gala-article">
|
|
6
|
-
{% if post.publicationState == "published" %}<progress class="gala-reading-progress" data-reading-progress max="1" value="0" aria-label="Reading progress"></progress><header class="gala-article-header"><p class="gala-eyebrow">Published {{ post.frontmatter.publishAfterDate }}
|
|
6
|
+
{% if post.publicationState == "published" or (buildManifest.preview and post.publicationState == "not-emitted") %}<progress class="gala-reading-progress" data-reading-progress max="1" value="0" aria-label="Reading progress"></progress><header class="gala-article-header"><p class="gala-eyebrow">{% if post.publicationState == "published" %}Published{% else %}Scheduled for{% endif %} {{ post.frontmatter.publishAfterDate }} - {{ post.language }} - {{ readingMinutes }} min read</p><h1>{{ post.frontmatter.title }}</h1>{% set byline = post.frontmatter.author or site.site.author %}{% if byline and byline != "unavailable" and byline != "New Gala Site" and byline != site.site.name %}<p class="gala-byline">By {{ byline }}</p>{% endif %}</header>{% endif %}
|
|
7
7
|
{% if post.publicationState == "published" %}{{ languageSwitcher(hreflangLinks, post.language) }}{% endif %}
|
|
8
8
|
{% if prismConfigurations.length %}<nav class="gala-prism-selector" aria-label="Approved editions">
|
|
9
9
|
<span aria-current="page">Complete</span>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The reader's identity, and the only way to write on their behalf.
|
|
3
3
|
*
|
|
4
4
|
* Both belong to the account frame, which is served by the API and runs on its origin. The
|
|
5
|
-
* reader's token lives there and nowhere else
|
|
5
|
+
* reader's token lives there and nowhere else - this page never sees it, and never should: the
|
|
6
6
|
* page is the writer's own site, and a reader's credentials are not the writer's to hold. So a
|
|
7
7
|
* write is a request posted into the frame, and the frame answers with a result.
|
|
8
8
|
*
|
|
@@ -323,6 +323,29 @@ const pendingEngagementWrites = new Map();
|
|
|
323
323
|
* and the comment they were typing keeps its text and its caret.
|
|
324
324
|
*/
|
|
325
325
|
let pendingIntent = null;
|
|
326
|
+
const sessionFrame = document.querySelector('[data-gala-session-frame]');
|
|
327
|
+
const sessionOrigin = sessionFrame ? new URL(sessionFrame.src).origin : null;
|
|
328
|
+
let sessionFrameReady = false;
|
|
329
|
+
let pendingSessionTransferCode = null;
|
|
330
|
+
|
|
331
|
+
function deliverSessionTransfer() {
|
|
332
|
+
if (!sessionFrameReady || !sessionFrame || !pendingSessionTransferCode) return;
|
|
333
|
+
sessionFrame.contentWindow?.postMessage({
|
|
334
|
+
type: 'gala-session-transfer', transferCode: pendingSessionTransferCode,
|
|
335
|
+
}, sessionOrigin);
|
|
336
|
+
pendingSessionTransferCode = null;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function requestSessionState() {
|
|
340
|
+
sessionFrame?.contentWindow?.postMessage({ type: 'gala-session-request' }, sessionOrigin);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (sessionFrame) {
|
|
344
|
+
// If the frame already loaded, the immediate request reaches it. If it is still loading, the
|
|
345
|
+
// load handler repeats the request. This covers both orderings without timing assumptions.
|
|
346
|
+
sessionFrame.addEventListener('load', requestSessionState);
|
|
347
|
+
requestSessionState();
|
|
348
|
+
}
|
|
326
349
|
|
|
327
350
|
function requestSession(intent) {
|
|
328
351
|
const field = intent.field;
|
|
@@ -384,20 +407,18 @@ window.addEventListener('focus', () => {
|
|
|
384
407
|
/*
|
|
385
408
|
* The sign-in window reports success to this page rather than to the account frame.
|
|
386
409
|
*
|
|
387
|
-
* It cannot reach the frame directly: a frame embedded in a publication has its storage
|
|
388
|
-
* BroadcastChannel
|
|
389
|
-
* so the session it wrote is invisible there. The signal carries
|
|
390
|
-
*
|
|
391
|
-
*
|
|
410
|
+
* It cannot reach the frame directly: a frame embedded in a publication has its storage - and its
|
|
411
|
+
* BroadcastChannel - partitioned away from a window running at the top level on the API's origin,
|
|
412
|
+
* so the session it wrote is invisible there. The signal carries only an opaque, one-time transfer
|
|
413
|
+
* code. This page waits for the frame's readiness message before relaying that code; otherwise a
|
|
414
|
+
* fast popup can return before the frame is listening and every later click asks the reader to sign
|
|
415
|
+
* in once more.
|
|
392
416
|
*/
|
|
393
417
|
window.addEventListener('message', (event) => {
|
|
394
|
-
|
|
395
|
-
if (
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
frame.contentWindow?.postMessage({
|
|
399
|
-
type: 'gala-session-transfer', transferCode: event.data.transferCode,
|
|
400
|
-
}, apiOrigin);
|
|
418
|
+
if (!sessionFrame || event.data?.type !== 'gala-session-established') return;
|
|
419
|
+
if (event.origin !== sessionOrigin || typeof event.data.transferCode !== 'string') return;
|
|
420
|
+
pendingSessionTransferCode = event.data.transferCode;
|
|
421
|
+
deliverSessionTransfer();
|
|
401
422
|
});
|
|
402
423
|
|
|
403
424
|
document.querySelectorAll('[data-engagement-url]').forEach((region) => {
|
|
@@ -405,8 +426,6 @@ document.querySelectorAll('[data-engagement-url]').forEach((region) => {
|
|
|
405
426
|
recordView(region);
|
|
406
427
|
});
|
|
407
428
|
|
|
408
|
-
const sessionFrame = document.querySelector('[data-gala-session-frame]');
|
|
409
|
-
|
|
410
429
|
function engagementErrorMessage(code) {
|
|
411
430
|
if (code === 'AUTHENTICATION_REQUIRED' || code === 'INVALID_BEARER_TOKEN'
|
|
412
431
|
|| code === 'REAUTHENTICATION_REQUIRED') return 'Sign in again to continue.';
|
|
@@ -480,7 +499,6 @@ async function mutateEngagement(region, operation, payload) {
|
|
|
480
499
|
}
|
|
481
500
|
|
|
482
501
|
if (sessionFrame) {
|
|
483
|
-
const sessionOrigin = new URL(sessionFrame.src).origin;
|
|
484
502
|
window.addEventListener('message', (event) => {
|
|
485
503
|
if (event.origin !== sessionOrigin || event.source !== sessionFrame.contentWindow) return;
|
|
486
504
|
if (event.data?.type === 'gala-engagement-result') {
|
|
@@ -504,7 +522,7 @@ if (sessionFrame) {
|
|
|
504
522
|
});
|
|
505
523
|
return;
|
|
506
524
|
}
|
|
507
|
-
/* The frame is cross-origin, so its content height is not readable from here
|
|
525
|
+
/* The frame is cross-origin, so its content height is not readable from here - it reports
|
|
508
526
|
its own, and the box is sized to it. Without this the account panel scrolled inside a fixed
|
|
509
527
|
box, clipping the first line of its own text. */
|
|
510
528
|
if (event.data?.type === 'gala-session-height') {
|
|
@@ -515,6 +533,8 @@ if (sessionFrame) {
|
|
|
515
533
|
return;
|
|
516
534
|
}
|
|
517
535
|
if (event.data?.type !== 'gala-session') return;
|
|
536
|
+
sessionFrameReady = true;
|
|
537
|
+
if (pendingSessionTransferCode) deliverSessionTransfer();
|
|
518
538
|
sessionUser = event.data.user && typeof event.data.user.id === 'string' ? event.data.user : null;
|
|
519
539
|
const control = document.querySelector('[data-user-control]');
|
|
520
540
|
const displayName = sessionUser?.displayName;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(()=>{var b=["system","light","dark"],O="gala-color-mode";function D(){let e=document.documentElement.dataset.mode;return b.includes(e)?e:"system"}function Z(){try{let e=localStorage.getItem(O);if(b.includes(e))return e}catch{return D()}return D()}function M(e){document.documentElement.dataset.mode=e;let t=b[(b.indexOf(e)+1)%b.length];document.querySelectorAll("[data-theme-mode-toggle]").forEach(a=>{let n=a.querySelector?.("[data-theme-mode-label]");n?n.textContent=`Theme: ${e}`:a.textContent=`Theme: ${e}`,a.setAttribute("aria-label",`Color mode: ${e}. Activate for ${t}.`)})}document.addEventListener("DOMContentLoaded",()=>{M(Z()),document.querySelectorAll("[data-theme-mode-toggle]").forEach(e=>{e.addEventListener("click",()=>{let t=b[(b.indexOf(document.documentElement.dataset.mode)+1)%b.length];try{localStorage.setItem(O,t)}catch{}M(t)})})});var F="gala-language-preference";function ee(){try{return localStorage.getItem(F)}catch{return null}}function P(e,t){return[...e.options].find(a=>a.value===t)}document.addEventListener("DOMContentLoaded",()=>{let e=ee();document.querySelectorAll("[data-language-preference]").forEach(t=>{e!=null&&P(t,e)!=null&&(t.value=e),t.addEventListener("change",()=>{try{localStorage.setItem(F,t.value)}catch{}if(!t.hasAttribute("data-navigate-on-selection"))return;let a=P(t,t.value);a?.dataset.url&&window.location.assign(a.dataset.url)})})});var H="gala.prism.session.v1";document.addEventListener("click",e=>{if(e.target.closest("[data-prism-restore]")){try{sessionStorage.removeItem(H)}catch{}return}let a=e.target.closest("[data-prism-select]");if(a)try{sessionStorage.setItem(H,JSON.stringify({articleId:a.dataset.prismArticleId,language:a.dataset.prismLanguage,configurationId:a.dataset.prismConfigurationId,sourceRevisionHash:a.dataset.prismSourceHash,selectedAt:new Date().toISOString()}))}catch{}});function v(e){let t=e.closest(".gala-share"),a=t?.querySelector(".gala-share__fallback");a?.classList.add("gala-share__fallback--visible"),a?.focus(),a?.select();let n=t?.querySelector(".gala-share__status");n&&(n.textContent="Select and copy the URL shown.")}var G=document.querySelector("[data-reading-progress]"),q=document.querySelector(".gala-markdown");if(G&&q){let e=null,t=()=>{e=null;let n=q.offsetTop,s=Math.max(1,q.offsetHeight-window.innerHeight),o=Math.min(1,Math.max(0,(window.scrollY-n)/s));G.value=o},a=()=>{e==null&&(e=requestAnimationFrame(t))};addEventListener("scroll",a,{passive:!0}),addEventListener("resize",a,{passive:!0}),a()}var C=document.querySelector(".gala-toc");if(C?.tagName==="DETAILS"){let e=C.querySelector("nav"),t=[...C.querySelectorAll('a[href^="#"]')],a=t.map(d=>document.getElementById(d.hash.slice(1))).filter(Boolean),n=null,s=()=>{if(n=null,C.classList.toggle("gala-toc--floating",C.getBoundingClientRect().top<=96),!a.length)return;let d=window.innerHeight*.42,f=a[0];for(let i of a)if(i.getBoundingClientRect().top<=d)f=i;else break;let y=t.find(i=>i.hash===`#${f.id}`);for(let i of t)i===y?i.setAttribute("aria-current","location"):i.removeAttribute("aria-current");if(e&&t.length){let i=t[0].getBoundingClientRect(),r=t[t.length-1].getBoundingClientRect();C.classList.toggle("gala-toc--overflowing",r.bottom-i.top>e.clientHeight)}if(y&&e&&e.scrollHeight>e.clientHeight){let i=e.getBoundingClientRect(),r=y.getBoundingClientRect(),c=e.scrollTop+r.top-i.top-(e.clientHeight-r.height)/2;e.scrollTo({top:Math.max(0,c),behavior:"auto"})}},o=()=>{n==null&&(n=requestAnimationFrame(s))};addEventListener("scroll",o,{passive:!0}),addEventListener("resize",o,{passive:!0}),o()}var $=document.querySelector(".gala-action-rail"),j=document.querySelector("[data-action-dock]");if($&&j){let e=t=>{$.classList.toggle("gala-action-rail--integrated",t[0].isIntersecting)};new IntersectionObserver(e,{rootMargin:"0px 0px 18% 0px",threshold:0}).observe(j)}function Y(e,t){let a=t?"Unfollow article":"Follow article";e.setAttribute("aria-pressed",String(t)),e.setAttribute("aria-label",a),e.title=a;let n=e.querySelector("[data-follow-label]");n&&(n.textContent=a)}document.addEventListener("click",async e=>{if(e.target instanceof HTMLDialogElement&&e.target.open){let i=e.target.getBoundingClientRect();if(e.clientX<i.left||e.clientX>i.right||e.clientY<i.top||e.clientY>i.bottom){e.target.close();return}}let t=e.target.closest("[data-gala-embed-load]");if(t){let i=t.closest("[data-gala-embed]"),r=t.dataset.galaEmbedSrc,c=t.dataset.galaEmbedLoad;if(!i||!r||!["youtube","codepen"].includes(c))return;let u=document.createElement("iframe");u.src=r,u.title=c==="youtube"?"YouTube video":"CodePen example",u.loading="eager",u.referrerPolicy="strict-origin-when-cross-origin",u.setAttribute("sandbox","allow-forms allow-popups allow-presentation allow-same-origin allow-scripts"),u.setAttribute("allow",c==="youtube"?"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture; web-share":"fullscreen"),u.setAttribute("allowfullscreen",""),i.replaceChildren(u);return}let a=e.target.closest("[data-open-dialog]");if(a){let i=document.getElementById(a.dataset.openDialog);i instanceof HTMLDialogElement&&i.showModal();return}let n=e.target.closest("[data-reaction]");if(n){let i=n.closest("[data-engagement-url]");if(!i)return;if(!E)return R({kind:"reaction",region:i,selector:`[data-reaction="${n.dataset.reaction}"]`});let r=n.getAttribute("aria-pressed")!=="true";if(!await W(i,r?"reaction.add":"reaction.remove",{articleId:i.dataset.articleId,reaction:n.dataset.reaction}))return;n.setAttribute("aria-pressed",String(r));return}let s=e.target.closest("[data-follow-article]");if(s){let i=s.closest("[data-engagement-url]");if(!i)return;if(!E)return R({kind:"follow",region:i,selector:"[data-follow-article]"});let r=s.getAttribute("aria-pressed")!=="true";if(!await W(i,r?"follow.add":"follow.remove",{targetType:"articles",targetId:i.dataset.articleId}))return;Y(s,r);return}let o=e.target.closest("[data-copy-url]");if(o){let i=o.dataset.copyUrl;if(!window.isSecureContext||!navigator.clipboard?.writeText){v(o);return}try{await navigator.clipboard.writeText(i);let r=o.closest(".gala-share")?.querySelector(".gala-share__status");r&&(r.textContent="Link copied.")}catch{v(o)}return}let d=e.target.closest("[data-native-share]");if(d){if(navigator.share)try{await navigator.share({title:document.title,url:d.dataset.nativeShare})}catch(i){i.name!=="AbortError"&&v(d)}else v(d);return}let f=e.target.closest("[data-copy-code]");if(!f)return;let y=f.closest(".gala-code-block")?.querySelector("code");if(!(!y||!navigator.clipboard?.writeText||!window.isSecureContext))try{await navigator.clipboard.writeText(y.textContent),f.textContent="Copied"}catch{f.textContent="Select code to copy"}});var te=new Set([...document.querySelectorAll("pre code")].map(e=>e.closest("pre")).filter(Boolean));te.forEach(e=>{let t=document.createElement("div");t.className="gala-code-block",e.before(t),t.append(e);let a=document.createElement("button");a.type="button",a.dataset.copyCode="",a.textContent="Copy code",a.setAttribute("aria-label","Copy code block"),t.prepend(a)});function ae(e){return[["Reactions",Object.values(e.reactions??{}).reduce((a,n)=>a+(Number.isSafeInteger(n)?n:0),0)],["Comments",Number.isSafeInteger(e.comments?.totalCount)?e.comments.totalCount:0],["Views",Number.isSafeInteger(e.views?.count)?e.views.count:0]]}function ne(e){return e<1e3?"<1K":new Intl.NumberFormat("en",{notation:"compact",maximumFractionDigits:1}).format(e)}function re(e,t){let a=t?.data;if(!a||typeof a!="object")throw new TypeError("Engagement data is invalid");for(let[n,s]of ae(a))e.querySelectorAll(`[data-engagement-stat="${n.toLowerCase()}"]`).forEach(o=>{o.textContent=ne(s),o.closest("div")?.setAttribute("title",`${s} ${n.toLowerCase()}`)});e.querySelector("[data-engagement-snapshot]")?.remove(),e.querySelector(".gala-engagement__placeholder")?.remove()}async function x(e,t="",a=!1,n=!1){let s=e.querySelector("[data-engagement-status]"),o=!!e.querySelector("[data-gala-comments]");try{let d=new URL(e.dataset.engagementUrl);t&&d.searchParams.set("commentsCursor",t);let f=await fetch(d,{headers:{Accept:"application/json"},credentials:"omit",cache:n?"no-store":"default"});if(!f.ok)throw new Error(`Engagement returned HTTP ${f.status}`);let y=await f.json();re(e,y),s&&!o&&(s.textContent=y.errors?.length?"Some engagement data is temporarily unavailable.":"")}catch{s&&!o&&(s.textContent="Engagement is temporarily unavailable.")}}function oe(){let e=new URL(window.location.href).searchParams,t={};for(let a of["utm_source","utm_medium","utm_campaign","utm_content","utm_term"]){let n=e.get(a);n&&[...n].length<=128&&(t[a]=n)}return t}function se(e){let t=new URL(e.dataset.engagementUrl);t.pathname=t.pathname.replace(/\/engagement$/,"/views"),fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({language:document.documentElement.lang||void 0,referrer:document.referrer||void 0,campaign:oe()}),credentials:"omit",keepalive:!0}).catch(()=>{})}document.addEventListener("gala-request-sign-in",e=>{E||S||R({kind:e.detail?.kind??"comment"})});var E=null,B=null,_=null,L=new Map,S=null;function R(e){let t=e.field;S={kind:e.kind,region:e.region,selector:e.selector,draft:t?t.value:null,caret:t?t.selectionStart:null};let a=document.querySelector("[data-gala-session-frame]"),n=document.querySelector("[data-engagement-status]");if(!a)return;let s=new URL(a.getAttribute("src"),window.location.href),o=new URL("/v1/widget/session/sign-in",s.origin);o.searchParams.set("siteId",s.searchParams.get("siteId")??""),t&&t.blur(),window.open(o,"gala-sign-in","popup,width=520,height=680")||(S=null,n&&(n.textContent="Allow pop-ups for this site to sign in, then try again."))}function V(){let e=S;if(!e||!document.hasFocus())return;let t=e.region?.querySelector(e.selector);if(t){if(S=null,e.kind==="comment"){e.draft!=null&&t.value===""&&(t.value=e.draft),t.focus(),e.caret!=null&&typeof t.setSelectionRange=="function"&&t.setSelectionRange(e.caret,e.caret);return}t.click()}}window.addEventListener("focus",()=>{E&&S&&V()});window.addEventListener("message",e=>{let t=document.querySelector("[data-gala-session-frame]");if(!t||e.data?.type!=="gala-session-established")return;let a=new URL(t.src).origin;e.origin!==a||typeof e.data.transferCode!="string"||t.contentWindow?.postMessage({type:"gala-session-transfer",transferCode:e.data.transferCode},a)});document.querySelectorAll("[data-engagement-url]").forEach(e=>{x(e),se(e)});var I=document.querySelector("[data-gala-session-frame]");function Q(e){return e==="AUTHENTICATION_REQUIRED"||e==="INVALID_BEARER_TOKEN"||e==="REAUTHENTICATION_REQUIRED"?"Sign in again to continue.":e==="ENGAGEMENT_RATE_LIMITED"?"You are posting too quickly; try again shortly.":e==="CONTACT_RATE_LIMITED"?"You are sending too quickly; try again later.":e==="RESOURCE_NOT_FOUND"?"That item is no longer available.":e==="INVALID_ENGAGEMENT_WRITE"||e==="INVALID_CONTACT_SUBMISSION"||e==="INVALID_REQUEST"?"Check your entry and try again.":e==="ACCESS_DENIED"?"That action is not available for this account.":e==="IDEMPOTENCY_CONFLICT"||e==="ENGAGEMENT_STATE_CONFLICT"?"The item changed; reload and try again.":"The action could not be completed. Try again."}function K(e,t){if(!I||!E)return Promise.reject(new Error("AUTHENTICATION_REQUIRED"));let a=crypto.randomUUID();return new Promise((n,s)=>{let o=setTimeout(()=>{L.delete(a),s(new Error("REQUEST_TIMEOUT"))},1e4);L.set(a,{resolve:d=>{clearTimeout(o),n(d)},reject:d=>{clearTimeout(o),s(d)}}),I.contentWindow.postMessage({type:"gala-engagement-write",requestId:a,operation:e,payload:t},new URL(I.src).origin)})}document.addEventListener("submit",async e=>{let t=e.target.closest("[data-contact-form] form");if(!t)return;e.preventDefault();let a=t.closest("[data-contact-form]"),n=a.querySelector("[data-contact-status]");if(!E){n.textContent="Sign in with the account button before sending.";return}let s=new FormData(t);try{n.textContent="Sending\u2026",await K("contact.submit",{siteId:a.dataset.siteId,subject:s.get("subject"),message:s.get("message"),website:s.get("website")||null,phone:s.get("phone")||null}),t.reset(),n.textContent="Message sent."}catch(o){n.textContent=Q(o.message)}});async function W(e,t,a){let n=e.querySelector("[data-engagement-status]");try{return n&&(n.textContent="Saving\u2026"),await K(t,a),n&&(n.textContent="Saved."),await x(e,"",!1,!0),!0}catch(s){return n&&(n.textContent=Q(s.message)),!1}}if(I){let e=new URL(I.src).origin;window.addEventListener("message",t=>{if(t.origin!==e||t.source!==I.contentWindow)return;if(t.data?.type==="gala-engagement-result"){let o=L.get(t.data.requestId);if(!o)return;L.delete(t.data.requestId),t.data.ok===!0?o.resolve(t.data.result):o.reject(new Error(t.data.error?.code||"ENGAGEMENT_WRITE_FAILED"));return}if(t.data?.type==="gala-reader-state-result"){if(t.data.requestId!==_||t.data.ok!==!0)return;_=null;let o=new Set(Array.isArray(t.data.state?.reactions)?t.data.state.reactions:[]);document.querySelectorAll("[data-reaction]").forEach(d=>{d.setAttribute("aria-pressed",String(o.has(d.dataset.reaction)))}),document.querySelectorAll("[data-follow-article]").forEach(d=>{let f=t.data.state?.following===!0;Y(d,f)});return}if(t.data?.type==="gala-session-height"){let o=Number(t.data.height);Number.isFinite(o)&&o>0&&o<2e3&&(I.height=String(Math.ceil(o)));return}if(t.data?.type!=="gala-session")return;E=t.data.user&&typeof t.data.user.id=="string"?t.data.user:null;let a=document.querySelector("[data-user-control]"),n=E?.displayName;if(a){a.setAttribute("aria-label",n?`Account: ${n}`:"Sign in or view account"),a.title=n?`Account: ${n}`:"Account";let o=a.querySelector("[data-user-label]");o&&(o.textContent=n||"Sign in")}let s=B!==(E?.id??null);if(B=E?.id??null,E){let o=document.querySelector("[data-article-id]");o&&(_=crypto.randomUUID(),I.contentWindow.postMessage({type:"gala-reader-state-request",requestId:_,articleId:o.dataset.articleId},e))}document.querySelectorAll("[data-engagement-url]").forEach(o=>{s&&x(o)}),E&&S&&V()})}document.querySelectorAll("[data-gala-search]").forEach(e=>{let t=e.querySelector("form"),a=t?.elements.namedItem("q"),n=e.querySelector("[data-search-status]"),s=e.querySelector("[data-search-results]"),o=e.dataset.indexUrl,d,f=c=>String(c??"").normalize("NFKC").toLocaleLowerCase();function y(c,u){s.replaceChildren();for(let m of c){let p=document.createElement("li"),l=document.createElement("article"),h=document.createElement("h2"),w=document.createElement("a");if(w.href=m.url,w.lang=m.language,w.textContent=m.title,h.append(w),l.append(h),m.description){let k=document.createElement("p");k.textContent=m.description,l.append(k)}p.append(l),s.append(p)}n.textContent=`${c.length} result${c.length===1?"":"s"} for \u201C${u}\u201D.`}async function i(c){let u=c.trim();if(u===""){s.replaceChildren(),n.textContent="Enter a search term.";return}n.textContent="Searching\u2026";try{d??=fetch(o,{headers:{Accept:"application/json"}}).then(async l=>{if(!l.ok)throw new Error(`Search index returned HTTP ${l.status}`);let h=await l.json();if(h?.schemaVersion!==1||!Array.isArray(h.entries))throw new TypeError("Search index schema is unsupported");return h.entries});let m=f(u),p=(await d).filter(l=>f([l.title,l.description,l.language,...Array.isArray(l.tags)?l.tags:[],l.body].join(`
|
|
2
|
-
`)).includes(
|
|
1
|
+
(()=>{var I=["system","light","dark"],P="gala-color-mode";function O(){let e=document.documentElement.dataset.mode;return I.includes(e)?e:"system"}function re(){try{let e=localStorage.getItem(P);if(I.includes(e))return e}catch{return O()}return O()}function F(e){document.documentElement.dataset.mode=e;let n=I[(I.indexOf(e)+1)%I.length];document.querySelectorAll("[data-theme-mode-toggle]").forEach(t=>{let a=t.querySelector?.("[data-theme-mode-label]");a?a.textContent=`Theme: ${e}`:t.textContent=`Theme: ${e}`,t.setAttribute("aria-label",`Color mode: ${e}. Activate for ${n}.`)})}document.addEventListener("DOMContentLoaded",()=>{F(re()),document.querySelectorAll("[data-theme-mode-toggle]").forEach(e=>{e.addEventListener("click",()=>{let n=I[(I.indexOf(document.documentElement.dataset.mode)+1)%I.length];try{localStorage.setItem(P,n)}catch{}F(n)})})});var G="gala-language-preference";function oe(){try{return localStorage.getItem(G)}catch{return null}}function H(e,n){return[...e.options].find(t=>t.value===n)}document.addEventListener("DOMContentLoaded",()=>{let e=oe();document.querySelectorAll("[data-language-preference]").forEach(n=>{e!=null&&H(n,e)!=null&&(n.value=e),n.addEventListener("change",()=>{try{localStorage.setItem(G,n.value)}catch{}if(!n.hasAttribute("data-navigate-on-selection"))return;let t=H(n,n.value);t?.dataset.url&&window.location.assign(t.dataset.url)})})});var $="gala.prism.session.v1";document.addEventListener("click",e=>{if(e.target.closest("[data-prism-restore]")){try{sessionStorage.removeItem($)}catch{}return}let t=e.target.closest("[data-prism-select]");if(t)try{sessionStorage.setItem($,JSON.stringify({articleId:t.dataset.prismArticleId,language:t.dataset.prismLanguage,configurationId:t.dataset.prismConfigurationId,sourceRevisionHash:t.dataset.prismSourceHash,selectedAt:new Date().toISOString()}))}catch{}});function L(e){let n=e.closest(".gala-share"),t=n?.querySelector(".gala-share__fallback");t?.classList.add("gala-share__fallback--visible"),t?.focus(),t?.select();let a=n?.querySelector(".gala-share__status");a&&(a.textContent="Select and copy the URL shown.")}var j=document.querySelector("[data-reading-progress]"),x=document.querySelector(".gala-markdown");if(j&&x){let e=null,n=()=>{e=null;let a=x.offsetTop,r=Math.max(1,x.offsetHeight-window.innerHeight),c=Math.min(1,Math.max(0,(window.scrollY-a)/r));j.value=c},t=()=>{e==null&&(e=requestAnimationFrame(n))};addEventListener("scroll",t,{passive:!0}),addEventListener("resize",t,{passive:!0}),t()}var C=document.querySelector(".gala-toc");if(C?.tagName==="DETAILS"){let e=C.querySelector("nav"),n=[...C.querySelectorAll('a[href^="#"]')],t=n.map(m=>document.getElementById(m.hash.slice(1))).filter(Boolean),a=null,r=()=>{if(a=null,C.classList.toggle("gala-toc--floating",C.getBoundingClientRect().top<=96),!t.length)return;let m=window.innerHeight*.42,f=t[0];for(let s of t)if(s.getBoundingClientRect().top<=m)f=s;else break;let E=n.find(s=>s.hash===`#${f.id}`);for(let s of n)s===E?s.setAttribute("aria-current","location"):s.removeAttribute("aria-current");if(e&&n.length){let s=n[0].getBoundingClientRect(),o=n[n.length-1].getBoundingClientRect();C.classList.toggle("gala-toc--overflowing",o.bottom-s.top>e.clientHeight)}if(E&&e&&e.scrollHeight>e.clientHeight){let s=e.getBoundingClientRect(),o=E.getBoundingClientRect(),i=e.scrollTop+o.top-s.top-(e.clientHeight-o.height)/2;e.scrollTo({top:Math.max(0,i),behavior:"auto"})}},c=()=>{a==null&&(a=requestAnimationFrame(r))};addEventListener("scroll",c,{passive:!0}),addEventListener("resize",c,{passive:!0}),c()}var B=document.querySelector(".gala-action-rail"),W=document.querySelector("[data-action-dock]");if(B&&W){let e=n=>{B.classList.toggle("gala-action-rail--integrated",n[0].isIntersecting)};new IntersectionObserver(e,{rootMargin:"0px 0px 18% 0px",threshold:0}).observe(W)}function K(e,n){let t=n?"Unfollow article":"Follow article";e.setAttribute("aria-pressed",String(n)),e.setAttribute("aria-label",t),e.title=t;let a=e.querySelector("[data-follow-label]");a&&(a.textContent=t)}document.addEventListener("click",async e=>{if(e.target instanceof HTMLDialogElement&&e.target.open){let s=e.target.getBoundingClientRect();if(e.clientX<s.left||e.clientX>s.right||e.clientY<s.top||e.clientY>s.bottom){e.target.close();return}}let n=e.target.closest("[data-gala-embed-load]");if(n){let s=n.closest("[data-gala-embed]"),o=n.dataset.galaEmbedSrc,i=n.dataset.galaEmbedLoad;if(!s||!o||!["youtube","codepen"].includes(i))return;let d=document.createElement("iframe");d.src=o,d.title=i==="youtube"?"YouTube video":"CodePen example",d.loading="eager",d.referrerPolicy="strict-origin-when-cross-origin",d.setAttribute("sandbox","allow-forms allow-popups allow-presentation allow-same-origin allow-scripts"),d.setAttribute("allow",i==="youtube"?"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture; web-share":"fullscreen"),d.setAttribute("allowfullscreen",""),s.replaceChildren(d);return}let t=e.target.closest("[data-open-dialog]");if(t){let s=document.getElementById(t.dataset.openDialog);s instanceof HTMLDialogElement&&s.showModal();return}let a=e.target.closest("[data-reaction]");if(a){let s=a.closest("[data-engagement-url]");if(!s)return;if(!w)return U({kind:"reaction",region:s,selector:`[data-reaction="${a.dataset.reaction}"]`});let o=a.getAttribute("aria-pressed")!=="true";if(!await Q(s,o?"reaction.add":"reaction.remove",{articleId:s.dataset.articleId,reaction:a.dataset.reaction}))return;a.setAttribute("aria-pressed",String(o));return}let r=e.target.closest("[data-follow-article]");if(r){let s=r.closest("[data-engagement-url]");if(!s)return;if(!w)return U({kind:"follow",region:s,selector:"[data-follow-article]"});let o=r.getAttribute("aria-pressed")!=="true";if(!await Q(s,o?"follow.add":"follow.remove",{targetType:"articles",targetId:s.dataset.articleId}))return;K(r,o);return}let c=e.target.closest("[data-copy-url]");if(c){let s=c.dataset.copyUrl;if(!window.isSecureContext||!navigator.clipboard?.writeText){L(c);return}try{await navigator.clipboard.writeText(s);let o=c.closest(".gala-share")?.querySelector(".gala-share__status");o&&(o.textContent="Link copied.")}catch{L(c)}return}let m=e.target.closest("[data-native-share]");if(m){if(navigator.share)try{await navigator.share({title:document.title,url:m.dataset.nativeShare})}catch(s){s.name!=="AbortError"&&L(m)}else L(m);return}let f=e.target.closest("[data-copy-code]");if(!f)return;let E=f.closest(".gala-code-block")?.querySelector("code");if(!(!E||!navigator.clipboard?.writeText||!window.isSecureContext))try{await navigator.clipboard.writeText(E.textContent),f.textContent="Copied"}catch{f.textContent="Select code to copy"}});var se=new Set([...document.querySelectorAll("pre code")].map(e=>e.closest("pre")).filter(Boolean));se.forEach(e=>{let n=document.createElement("div");n.className="gala-code-block",e.before(n),n.append(e);let t=document.createElement("button");t.type="button",t.dataset.copyCode="",t.textContent="Copy code",t.setAttribute("aria-label","Copy code block"),n.prepend(t)});function ie(e){return[["Reactions",Object.values(e.reactions??{}).reduce((t,a)=>t+(Number.isSafeInteger(a)?a:0),0)],["Comments",Number.isSafeInteger(e.comments?.totalCount)?e.comments.totalCount:0],["Views",Number.isSafeInteger(e.views?.count)?e.views.count:0]]}function ce(e){return e<1e3?"<1K":new Intl.NumberFormat("en",{notation:"compact",maximumFractionDigits:1}).format(e)}function le(e,n){let t=n?.data;if(!t||typeof t!="object")throw new TypeError("Engagement data is invalid");for(let[a,r]of ie(t))e.querySelectorAll(`[data-engagement-stat="${a.toLowerCase()}"]`).forEach(c=>{c.textContent=ce(r),c.closest("div")?.setAttribute("title",`${r} ${a.toLowerCase()}`)});e.querySelector("[data-engagement-snapshot]")?.remove(),e.querySelector(".gala-engagement__placeholder")?.remove()}async function k(e,n="",t=!1,a=!1){let r=e.querySelector("[data-engagement-status]"),c=!!e.querySelector("[data-gala-comments]");try{let m=new URL(e.dataset.engagementUrl);n&&m.searchParams.set("commentsCursor",n);let f=await fetch(m,{headers:{Accept:"application/json"},credentials:"omit",cache:a?"no-store":"default"});if(!f.ok)throw new Error(`Engagement returned HTTP ${f.status}`);let E=await f.json();le(e,E),r&&!c&&(r.textContent=E.errors?.length?"Some engagement data is temporarily unavailable.":"")}catch{r&&!c&&(r.textContent="Engagement is temporarily unavailable.")}}function de(){let e=new URL(window.location.href).searchParams,n={};for(let t of["utm_source","utm_medium","utm_campaign","utm_content","utm_term"]){let a=e.get(t);a&&[...a].length<=128&&(n[t]=a)}return n}function ue(e){let n=new URL(e.dataset.engagementUrl);n.pathname=n.pathname.replace(/\/engagement$/,"/views"),fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({language:document.documentElement.lang||void 0,referrer:document.referrer||void 0,campaign:de()}),credentials:"omit",keepalive:!0}).catch(()=>{})}document.addEventListener("gala-request-sign-in",e=>{w||S||U({kind:e.detail?.kind??"comment"})});var w=null,Y=null,N=null,q=new Map,S=null,y=document.querySelector("[data-gala-session-frame]"),_=y?new URL(y.src).origin:null,z=!1,v=null;function X(){!z||!y||!v||(y.contentWindow?.postMessage({type:"gala-session-transfer",transferCode:v},_),v=null)}function V(){y?.contentWindow?.postMessage({type:"gala-session-request"},_)}y&&(y.addEventListener("load",V),V());function U(e){let n=e.field;S={kind:e.kind,region:e.region,selector:e.selector,draft:n?n.value:null,caret:n?n.selectionStart:null};let t=document.querySelector("[data-gala-session-frame]"),a=document.querySelector("[data-engagement-status]");if(!t)return;let r=new URL(t.getAttribute("src"),window.location.href),c=new URL("/v1/widget/session/sign-in",r.origin);c.searchParams.set("siteId",r.searchParams.get("siteId")??""),n&&n.blur(),window.open(c,"gala-sign-in","popup,width=520,height=680")||(S=null,a&&(a.textContent="Allow pop-ups for this site to sign in, then try again."))}function J(){let e=S;if(!e||!document.hasFocus())return;let n=e.region?.querySelector(e.selector);if(n){if(S=null,e.kind==="comment"){e.draft!=null&&n.value===""&&(n.value=e.draft),n.focus(),e.caret!=null&&typeof n.setSelectionRange=="function"&&n.setSelectionRange(e.caret,e.caret);return}n.click()}}window.addEventListener("focus",()=>{w&&S&&J()});window.addEventListener("message",e=>{!y||e.data?.type!=="gala-session-established"||e.origin!==_||typeof e.data.transferCode!="string"||(v=e.data.transferCode,X())});document.querySelectorAll("[data-engagement-url]").forEach(e=>{k(e),ue(e)});function Z(e){return e==="AUTHENTICATION_REQUIRED"||e==="INVALID_BEARER_TOKEN"||e==="REAUTHENTICATION_REQUIRED"?"Sign in again to continue.":e==="ENGAGEMENT_RATE_LIMITED"?"You are posting too quickly; try again shortly.":e==="CONTACT_RATE_LIMITED"?"You are sending too quickly; try again later.":e==="RESOURCE_NOT_FOUND"?"That item is no longer available.":e==="INVALID_ENGAGEMENT_WRITE"||e==="INVALID_CONTACT_SUBMISSION"||e==="INVALID_REQUEST"?"Check your entry and try again.":e==="ACCESS_DENIED"?"That action is not available for this account.":e==="IDEMPOTENCY_CONFLICT"||e==="ENGAGEMENT_STATE_CONFLICT"?"The item changed; reload and try again.":"The action could not be completed. Try again."}function ee(e,n){if(!y||!w)return Promise.reject(new Error("AUTHENTICATION_REQUIRED"));let t=crypto.randomUUID();return new Promise((a,r)=>{let c=setTimeout(()=>{q.delete(t),r(new Error("REQUEST_TIMEOUT"))},1e4);q.set(t,{resolve:m=>{clearTimeout(c),a(m)},reject:m=>{clearTimeout(c),r(m)}}),y.contentWindow.postMessage({type:"gala-engagement-write",requestId:t,operation:e,payload:n},new URL(y.src).origin)})}document.addEventListener("submit",async e=>{let n=e.target.closest("[data-contact-form] form");if(!n)return;e.preventDefault();let t=n.closest("[data-contact-form]"),a=t.querySelector("[data-contact-status]");if(!w){a.textContent="Sign in with the account button before sending.";return}let r=new FormData(n);try{a.textContent="Sending\u2026",await ee("contact.submit",{siteId:t.dataset.siteId,subject:r.get("subject"),message:r.get("message"),website:r.get("website")||null,phone:r.get("phone")||null}),n.reset(),a.textContent="Message sent."}catch(c){a.textContent=Z(c.message)}});async function Q(e,n,t){let a=e.querySelector("[data-engagement-status]");try{return a&&(a.textContent="Saving\u2026"),await ee(n,t),a&&(a.textContent="Saved."),await k(e,"",!1,!0),!0}catch(r){return a&&(a.textContent=Z(r.message)),!1}}y&&window.addEventListener("message",e=>{if(e.origin!==_||e.source!==y.contentWindow)return;if(e.data?.type==="gala-engagement-result"){let r=q.get(e.data.requestId);if(!r)return;q.delete(e.data.requestId),e.data.ok===!0?r.resolve(e.data.result):r.reject(new Error(e.data.error?.code||"ENGAGEMENT_WRITE_FAILED"));return}if(e.data?.type==="gala-reader-state-result"){if(e.data.requestId!==N||e.data.ok!==!0)return;N=null;let r=new Set(Array.isArray(e.data.state?.reactions)?e.data.state.reactions:[]);document.querySelectorAll("[data-reaction]").forEach(c=>{c.setAttribute("aria-pressed",String(r.has(c.dataset.reaction)))}),document.querySelectorAll("[data-follow-article]").forEach(c=>{let m=e.data.state?.following===!0;K(c,m)});return}if(e.data?.type==="gala-session-height"){let r=Number(e.data.height);Number.isFinite(r)&&r>0&&r<2e3&&(y.height=String(Math.ceil(r)));return}if(e.data?.type!=="gala-session")return;z=!0,v&&X(),w=e.data.user&&typeof e.data.user.id=="string"?e.data.user:null;let n=document.querySelector("[data-user-control]"),t=w?.displayName;if(n){n.setAttribute("aria-label",t?`Account: ${t}`:"Sign in or view account"),n.title=t?`Account: ${t}`:"Account";let r=n.querySelector("[data-user-label]");r&&(r.textContent=t||"Sign in")}let a=Y!==(w?.id??null);if(Y=w?.id??null,w){let r=document.querySelector("[data-article-id]");r&&(N=crypto.randomUUID(),y.contentWindow.postMessage({type:"gala-reader-state-request",requestId:N,articleId:r.dataset.articleId},_))}document.querySelectorAll("[data-engagement-url]").forEach(r=>{a&&k(r)}),w&&S&&J()});document.querySelectorAll("[data-gala-search]").forEach(e=>{let n=e.querySelector("form"),t=n?.elements.namedItem("q"),a=e.querySelector("[data-search-status]"),r=e.querySelector("[data-search-results]"),c=e.dataset.indexUrl,m,f=i=>String(i??"").normalize("NFKC").toLocaleLowerCase();function E(i,d){r.replaceChildren();for(let u of i){let p=document.createElement("li"),l=document.createElement("article"),h=document.createElement("h2"),b=document.createElement("a");if(b.href=u.url,b.lang=u.language,b.textContent=u.title,h.append(b),l.append(h),u.description){let M=document.createElement("p");M.textContent=u.description,l.append(M)}p.append(l),r.append(p)}a.textContent=`${i.length} result${i.length===1?"":"s"} for \u201C${d}\u201D.`}async function s(i){let d=i.trim();if(d===""){r.replaceChildren(),a.textContent="Enter a search term.";return}a.textContent="Searching\u2026";try{m??=fetch(c,{headers:{Accept:"application/json"}}).then(async l=>{if(!l.ok)throw new Error(`Search index returned HTTP ${l.status}`);let h=await l.json();if(h?.schemaVersion!==1||!Array.isArray(h.entries))throw new TypeError("Search index schema is unsupported");return h.entries});let u=f(d),p=(await m).filter(l=>f([l.title,l.description,l.language,...Array.isArray(l.tags)?l.tags:[],l.body].join(`
|
|
2
|
+
`)).includes(u));E(p,d)}catch{r.replaceChildren(),a.textContent="Search is temporarily unavailable."}}n?.addEventListener("submit",i=>{i.preventDefault();let d=t.value,u=new URL(window.location.href);d.trim()===""?u.searchParams.delete("q"):u.searchParams.set("q",d),window.history.replaceState(null,"",u),s(d)});let o=new URLSearchParams(window.location.search).get("q")??"";t&&(t.value=o),s(o)});var T={value:null},A=document.querySelector("[data-gala-session-frame]"),te=A?new URL(A.src).origin:null,R=new Map;function ne(e){return e==="AUTHENTICATION_REQUIRED"||e==="INVALID_BEARER_TOKEN"||e==="REAUTHENTICATION_REQUIRED"?"Sign in again to continue.":e==="ENGAGEMENT_RATE_LIMITED"?"You are posting too quickly; try again shortly.":e==="CONTACT_RATE_LIMITED"?"You are sending too quickly; try again later.":e==="RESOURCE_NOT_FOUND"?"That item is no longer available.":e==="INVALID_ENGAGEMENT_WRITE"||e==="INVALID_CONTACT_SUBMISSION"||e==="INVALID_REQUEST"?"Check your entry and try again.":e==="ACCESS_DENIED"?"That action is not available for this account.":e==="IDEMPOTENCY_CONFLICT"||e==="ENGAGEMENT_STATE_CONFLICT"?"The item changed; reload and try again.":"The action could not be completed. Try again."}function ae(e,n){if(!A||!T.value)return Promise.reject(new Error("AUTHENTICATION_REQUIRED"));let t=crypto.randomUUID();return new Promise((a,r)=>{let c=setTimeout(()=>{R.delete(t),r(new Error("REQUEST_TIMEOUT"))},1e4);R.set(t,{resolve:m=>{clearTimeout(c),a(m)},reject:m=>{clearTimeout(c),r(m)}}),A.contentWindow.postMessage({type:"gala-engagement-write",requestId:t,operation:e,payload:n},te)})}function D(e){document.dispatchEvent(new CustomEvent("gala-request-sign-in",{detail:e}))}A&&window.addEventListener("message",e=>{if(e.origin!==te||e.source!==A.contentWindow)return;if(e.data?.type==="gala-engagement-result"){let t=R.get(e.data.requestId);if(!t)return;R.delete(e.data.requestId),e.data.ok===!0?t.resolve(e.data.result):t.reject(new Error(e.data.error?.code||"ENGAGEMENT_WRITE_FAILED"));return}if(e.data?.type!=="gala-session")return;let n=e.data.user;T.value=n&&typeof n.id=="string"?n:null,window.dispatchEvent(new CustomEvent("gala-session-change"))});var me=5,g=(e,n,t)=>{let a=document.createElement(e);return n&&(a.className=n),t!==void 0&&(a.textContent=t),a};function ge(e){let n=new Map;for(let a of e){let r=a.parentCommentId??"";n.has(r)||n.set(r,[]),n.get(r).push(a)}let t=(a,r)=>(n.get(a)??[]).map(c=>({...c,depth:r,replies:t(c.commentId,r+1)}));return t("",0)}function fe(e,n){let t={items:[],cursor:null,total:0,busy:!1,status:"",phase:"loading"},a=/\/v1\/articles\/([^/]+)\/engagement/.exec(n)?.[1]??"",r=e.closest(".gala-conversation")?.querySelector("[data-engagement-status]");async function c(o="",{append:i=!1,fresh:d=!1}={}){let u=new URL(n);o&&u.searchParams.set("commentsCursor",o);let p=await fetch(u,{headers:{Accept:"application/json"},credentials:"omit",cache:d?"no-store":"default"});if(!p.ok)throw new Error(`Comments returned HTTP ${p.status}`);let l=(await p.json())?.data?.comments;if(!l||!Array.isArray(l.items))throw new TypeError("Comment page is invalid");t.items=i?[...t.items,...l.items]:l.items,t.cursor=l.nextCursor??null,Number.isSafeInteger(l.totalCount)&&(t.total=l.totalCount)}async function m(o,i){if(!T.value)return D({kind:"comment"}),!1;t.busy=!0,t.status="",s();try{return await ae(o,i),await c("",{fresh:!0}),!0}catch(d){return t.status=ne(d.message),!1}finally{t.busy=!1,s()}}function f(o,i,d){let u=g("form","gala-comment__form"),p=g("textarea");p.rows=3,p.value=i,p.placeholder=o,p.setAttribute("aria-label",o);let l=g("button","","Post");return l.type="submit",l.disabled=t.busy,u.append(p,l),u.addEventListener("submit",async h=>{h.preventDefault();let b=p.value.trim();b&&await d(b)&&(p.value="")}),u}function E(o){let i=g("li","gala-comment");i.dataset.commentId=o.commentId,i.dataset.depth=o.depth;let d=g("p","gala-comment__meta");if(d.append(g("strong","",o.author?.displayName??"[deleted]")),o.createdAt){let u=g("time","",new Date(o.createdAt).toLocaleDateString());u.dateTime=o.createdAt,d.append(u)}if(i.append(d,g("p","gala-comment__body",o.deleted?"[deleted]":o.body)),!o.deleted){let u=g("div","gala-comment-actions");if(o.depth<me){let l=g("button","","Reply");l.type="button",l.dataset.replyComment=o.commentId,l.addEventListener("click",()=>{let h=i.querySelector(":scope > .gala-comment__form");if(h){h.remove();return}u.after(f(`Reply to ${o.author?.displayName??"this comment"}`,"",b=>m("comment.create",{articleId:a,parentCommentId:o.commentId,body:b})))}),u.append(l)}if(T.value&&o.author?.userId===T.value.id){let l=g("button","","Edit");l.type="button",l.dataset.editComment=o.commentId,l.addEventListener("click",()=>u.after(f("Edit your comment",o.body??"",b=>m("comment.edit",{articleId:a,commentId:o.commentId,body:b}))));let h=g("button","","Delete");h.type="button",h.dataset.deleteComment=o.commentId,h.addEventListener("click",()=>m("comment.delete",{articleId:a,commentId:o.commentId})),u.append(l,h)}else{let l=g("button","","Report");l.type="button",l.dataset.reportComment=o.commentId,l.addEventListener("click",()=>m("comment.report",{articleId:a,commentId:o.commentId,reason:"OTHER"})),u.append(l)}i.append(u)}if(o.replies.length){let u=g("ol","gala-comment-replies");u.append(...o.replies.map(E)),i.append(u)}return i}function s(){if(r&&(r.textContent=t.phase==="loading"?"Loading comments\u2026":t.phase==="unavailable"?"Comments are temporarily unavailable.":t.status),t.phase!=="ready"){e.replaceChildren();return}let o=g("section","gala-comments-island");if(o.setAttribute("aria-label","Comments"),o.append(g("p","gala-comments__heading",t.total===1?"1 comment":`${t.total} comments`)),T.value)o.append(f("Add a comment","",i=>m("comment.create",{articleId:a,body:i})));else{let i=g("p","gala-comments__prompt"),d=g("button","","Sign in to join the conversation");d.type="button",d.addEventListener("click",()=>D({kind:"comment"})),i.append(d),o.append(i)}if(!t.items.length)o.append(g("p","gala-comments__empty","No comments yet."));else{let i=g("ol","gala-comments");i.append(...ge(t.items).map(E)),o.append(i)}if(t.cursor){let i=g("button","gala-comments__more","Show more comments");i.type="button",i.disabled=t.busy,i.addEventListener("click",async()=>{t.busy=!0,t.status="Loading more comments\u2026",s();try{await c(t.cursor,{append:!0}),t.status=""}catch{t.status="More comments couldn\u2019t be loaded. Try again."}t.busy=!1,s()}),o.append(i)}e.replaceChildren(o)}window.addEventListener("gala-session-change",s),s(),c().then(()=>{t.phase="ready"}).catch(()=>{t.phase="unavailable"}).finally(s)}document.querySelectorAll("[data-gala-comments]").forEach(e=>{let n=e.closest("[data-engagement-url]")?.dataset.engagementUrl;n&&fe(e,n)});})();
|
|
@@ -3,7 +3,7 @@ const STORAGE_KEY = 'gala-color-mode';
|
|
|
3
3
|
|
|
4
4
|
/*
|
|
5
5
|
* The reader's own choice always wins. Absent one, the publication's `design.colorMode` decides
|
|
6
|
-
* what they see first
|
|
6
|
+
* what they see first - it is already on the element, server-rendered. This used to fall back to
|
|
7
7
|
* `system` unconditionally, which meant a writer could set a colour mode and no reader ever saw
|
|
8
8
|
* it: the setting existed everywhere except in the page.
|
|
9
9
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
:root{color-scheme:light dark;--gala-color-background: light-dark(#fdfcfb, #131211);--gala-color-surface: light-dark(#f6f4f1, #1c1a18);--gala-color-raised: light-dark(#fffffe, #24211f);--gala-color-text: light-dark(#1a1817, #f2efec);--gala-color-muted: light-dark(#6b645e, #a8a099);--gala-color-border: light-dark(#968e86, #6b645e);--gala-color-hairline: light-dark(#e8e3dd, #2a2724);--gala-color-border-strong: light-dark(#c9c2ba, #46413d);--gala-color-accent: light-dark( var(--gala-accent-light, #8a4b2a), var(--gala-accent-dark, #e8a87c));--gala-font-body: ui-sans-serif, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;--gala-font-display: var(--gala-font-body);--gala-font-mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, monospace;--gala-text-xs: .78rem;--gala-text-sm: .875rem;--gala-text-base: clamp(1rem, .97rem + .15vw, 1.0625rem);--gala-text-lg: clamp(1.15rem, 1.09rem + .3vw, 1.3rem);--gala-text-xl: clamp(1.4rem, 1.28rem + .6vw, 1.75rem);--gala-text-2xl: clamp(1.75rem, 1.5rem + 1.2vw, 2.4rem);--gala-text-3xl: clamp(2.2rem, 1.7rem + 2.4vw, 3.4rem);--gala-leading-tight: 1.15;--gala-leading-body: 1.65;--gala-tracking-tight: -.018em;--gala-tracking-wide: .08em;--gala-weight-display: 640;--gala-step: 1rem;--gala-space-2xs: calc(var(--gala-step) * .25);--gala-space-xs: calc(var(--gala-step) * .5);--gala-space-sm: calc(var(--gala-step) * .75);--gala-space-md: var(--gala-step);--gala-space-lg: calc(var(--gala-step) * 1.75);--gala-space-xl: calc(var(--gala-step) * 3);--gala-space-page: clamp(1.25rem, 5vw, 4.5rem);--gala-radius-control: .625rem;--gala-radius-surface: 1rem;--gala-shadow-high: 0 1px 2px light-dark(rgb(26 24 23 / 5%), rgb(0 0 0 / 35%)), 0 12px 28px -12px light-dark(rgb(26 24 23 / 18%), rgb(0 0 0 / 60%));--gala-motion-fast: .12s;--gala-motion-base: .22s;--gala-ease: cubic-bezier(.2, 0, 0, 1);--gala-content-width: 72rem;--gala-measure: 68ch;--gala-widget-min-block-size: 8rem}:root[data-mode=light]{color-scheme:light;--gala-color-background: #fdfcfb;--gala-color-surface: #f6f4f1;--gala-color-raised: #fffffe;--gala-color-text: #1a1817;--gala-color-muted: #6b645e;--gala-color-border: #968e86;--gala-color-hairline: #e8e3dd;--gala-color-border-strong: #c9c2ba;--gala-color-accent: var(--gala-accent-light, #8a4b2a)}:root[data-mode=dark]{color-scheme:dark;--gala-color-background: #131211;--gala-color-surface: #1c1a18;--gala-color-raised: #24211f;--gala-color-text: #f2efec;--gala-color-muted: #a8a099;--gala-color-border: #6b645e;--gala-color-hairline: #2a2724;--gala-color-border-strong: #46413d;--gala-color-accent: var(--gala-accent-dark, #e8a87c)}:root[data-palette=ocean]{--gala-color-background: light-dark(#f8fbfd, #0b1a24);--gala-color-surface: light-dark(#eef4f9, #12252f);--gala-color-raised: light-dark(#ffffff, #172d39);--gala-color-text: light-dark(#12242f, #eaf3f8);--gala-color-muted: light-dark(#5a6f7d, #98aebb);--gala-color-border: light-dark(#7d8d99, #5f7d8c);--gala-color-hairline: light-dark(#dde7ee, #1d3644);--gala-color-border-strong: light-dark(#bdccd7, #33505f);--gala-color-accent: light-dark(#0f5f7a, #7fd0e8)}:root[data-palette=ocean][data-mode=light]{color-scheme:light;--gala-color-background: #f8fbfd;--gala-color-surface: #eef4f9;--gala-color-raised: #ffffff;--gala-color-text: #12242f;--gala-color-muted: #5a6f7d;--gala-color-border: #7d8d99;--gala-color-hairline: #dde7ee;--gala-color-border-strong: #bdccd7;--gala-color-accent: var(--gala-accent-light, #0f5f7a)}:root[data-palette=ocean][data-mode=dark]{color-scheme:dark;--gala-color-background: #0b1a24;--gala-color-surface: #12252f;--gala-color-raised: #172d39;--gala-color-text: #eaf3f8;--gala-color-muted: #98aebb;--gala-color-border: #5f7d8c;--gala-color-hairline: #1d3644;--gala-color-border-strong: #33505f;--gala-color-accent: var(--gala-accent-dark, #7fd0e8)}:root[data-palette=forest]{--gala-color-background: light-dark(#fbfcfa, #101410);--gala-color-surface: light-dark(#f0f4ee, #182018);--gala-color-raised: light-dark(#ffffff, #1f271f);--gala-color-text: light-dark(#18201a, #eef3ec);--gala-color-muted: light-dark(#5b665d, #9aa89b);--gala-color-border: light-dark(#849084, #5e685f);--gala-color-hairline: light-dark(#e2e9df, #232b23);--gala-color-border-strong: light-dark(#5b665d, #7d897e);--gala-color-accent: light-dark(#2f6b3f, #8fd3a1)}:root[data-palette=forest][data-mode=light]{color-scheme:light;--gala-color-background: #fbfcfa;--gala-color-surface: #f0f4ee;--gala-color-raised: #ffffff;--gala-color-text: #18201a;--gala-color-muted: #5b665d;--gala-color-border: #849084;--gala-color-hairline: #e2e9df;--gala-color-border-strong: #5b665d;--gala-color-accent: var(--gala-accent-light, #2f6b3f)}:root[data-palette=forest][data-mode=dark]{color-scheme:dark;--gala-color-background: #101410;--gala-color-surface: #182018;--gala-color-raised: #1f271f;--gala-color-text: #eef3ec;--gala-color-muted: #9aa89b;--gala-color-border: #5e685f;--gala-color-hairline: #232b23;--gala-color-border-strong: #7d897e;--gala-color-accent: var(--gala-accent-dark, #8fd3a1)}:root[data-palette=plum]{--gala-color-background: light-dark(#fdfbfd, #141016);--gala-color-surface: light-dark(#f5eff5, #1d1720);--gala-color-raised: light-dark(#ffffff, #241d28);--gala-color-text: light-dark(#1f1820, #f2ecf3);--gala-color-muted: light-dark(#665c69, #a99dac);--gala-color-border: light-dark(#948a95, #6b606d);--gala-color-hairline: light-dark(#ebe2ec, #281f2b);--gala-color-border-strong: light-dark(#665c69, #8b7f8d);--gala-color-accent: light-dark(#7a3d76, #dda6d8)}:root[data-palette=plum][data-mode=light]{color-scheme:light;--gala-color-background: #fdfbfd;--gala-color-surface: #f5eff5;--gala-color-raised: #ffffff;--gala-color-text: #1f1820;--gala-color-muted: #665c69;--gala-color-border: #948a95;--gala-color-hairline: #ebe2ec;--gala-color-border-strong: #665c69;--gala-color-accent: var(--gala-accent-light, #7a3d76)}:root[data-palette=plum][data-mode=dark]{color-scheme:dark;--gala-color-background: #141016;--gala-color-surface: #1d1720;--gala-color-raised: #241d28;--gala-color-text: #f2ecf3;--gala-color-muted: #a99dac;--gala-color-border: #6b606d;--gala-color-hairline: #281f2b;--gala-color-border-strong: #8b7f8d;--gala-color-accent: var(--gala-accent-dark, #dda6d8)}:root[data-theme=editorial]{--gala-font-body: ui-serif, "Iowan Old Style", Charter, "Source Serif 4", Georgia, serif;--gala-font-display: var(--gala-font-body);--gala-weight-display: 600;--gala-step: 1.1rem;--gala-leading-body: 1.7;--gala-radius-control: .5rem;--gala-radius-surface: .75rem;--gala-measure: 66ch;--gala-surface-border: var(--gala-color-hairline);--gala-surface-shadow: none}:root[data-theme=modern]{--gala-font-body: ui-sans-serif, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;--gala-font-display: var(--gala-font-body);--gala-weight-display: 640;--gala-step: 1rem;--gala-leading-body: 1.65;--gala-radius-control: .625rem;--gala-radius-surface: 1rem;--gala-measure: 70ch;--gala-surface-border: transparent;--gala-surface-shadow: var(--gala-shadow-high)}:root[data-theme=technical]{--gala-font-body: ui-sans-serif, -apple-system, "Segoe UI", Roboto, sans-serif;--gala-font-display: var(--gala-font-body);--gala-weight-display: 660;--gala-tracking-tight: -.022em;--gala-step: .9rem;--gala-leading-body: 1.6;--gala-radius-control: .375rem;--gala-radius-surface: .5rem;--gala-measure: 74ch;--gala-surface-border: var(--gala-color-border-strong);--gala-surface-shadow: none}:root[data-theme=technical] .gala-eyebrow,:root[data-theme=technical] .gala-engagement dt,:root[data-theme=technical] .gala-comment__meta time{font-family:var(--gala-font-mono)}@media(prefers-reduced-motion:reduce){:root{--gala-motion-fast: 0ms;--gala-motion-base: 0ms;scroll-behavior:auto}*,*:before,*:after{animation-duration:1ms!important;transition-duration:1ms!important}}:root[data-layout=portfolio]{--gala-content-width: 82rem}html{scroll-behavior:smooth}body{box-sizing:border-box;display:flex;flex-direction:column;min-block-size:100vh;min-block-size:100svh;margin:0;padding:var(--gala-space-page);color:var(--gala-color-text);background:var(--gala-color-background);font-family:var(--gala-font-body);font-size:var(--gala-text-base);line-height:var(--gala-leading-body);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.gala-skip-link{position:fixed;inset-block-start:1rem;inset-inline-start:1rem;z-index:100;padding:.75rem 1rem;color:#fff;background:var(--gala-color-accent);border-radius:999px;transform:translateY(-200%)}.gala-skip-link:focus{transform:translateY(0)}.gala-site-name{color:var(--gala-color-text);font-family:var(--gala-font-display);font-size:var(--gala-text-lg);font-weight:var(--gala-weight-display);letter-spacing:var(--gala-tracking-tight);text-decoration:none;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.gala-header-link{color:var(--gala-color-text);font-size:var(--gala-text-sm);text-decoration:none}.gala-account-control{display:inline-flex;align-items:center;gap:.5rem;min-block-size:2.75rem;padding-inline:.9rem;border-radius:999px}.gala-account-control svg{inline-size:1rem;block-size:1rem}.gala-article{max-inline-size:76rem;margin-inline:auto}.gala-page--article .gala-site-header,.gala-page--article main,.gala-page--article .gala-page-footer{max-inline-size:112rem}.gala-page--article .gala-article,.gala-page--article .gala-engagement{max-inline-size:none}.gala-article-header{padding-block:clamp(2.5rem,5vw,5rem)}.gala-article-header h1{max-inline-size:12ch;font-size:clamp(2.5rem,7vw,5.25rem);line-height:.96;letter-spacing:-.055em}.gala-byline{color:var(--gala-color-muted);font-size:var(--gala-text-sm)}.gala-markdown{max-inline-size:42rem;margin-inline:auto;font-size:clamp(1.1rem,1rem + .35vw,1.3rem)}.gala-markdown :where(h2){margin-block-start:clamp(3rem,7vw,5rem);font-size:clamp(2.2rem,5vw,3.8rem)}.gala-markdown :where(h2,h3,h4,h5,h6){scroll-margin-block-start:7rem}.gala-markdown :where(img,video){max-inline-size:100%;block-size:auto}.gala-markdown :where(pre){box-sizing:border-box;max-inline-size:100%;overflow-x:auto;scrollbar-color:var(--gala-color-border-strong) var(--gala-color-surface)}:where(h1,h2,h3,h4){font-family:var(--gala-font-display);font-weight:var(--gala-weight-display);line-height:var(--gala-leading-tight);letter-spacing:var(--gala-tracking-tight);text-wrap:balance;margin-block:0 var(--gala-space-sm)}:where(h1){font-size:var(--gala-text-3xl)}:where(h2){font-size:var(--gala-text-2xl)}:where(h3){font-size:var(--gala-text-xl)}:where(h4){font-size:var(--gala-text-lg)}:where(p,ul,ol,blockquote){margin-block:0 var(--gala-space-md)}:where(p){text-wrap:pretty}blockquote{margin-inline:0;padding-inline-start:var(--gala-space-md);border-inline-start:2px solid var(--gala-color-accent);color:var(--gala-color-muted)}:where(code,kbd,samp){font-family:var(--gala-font-mono);font-size:.9em}:where(pre){padding:var(--gala-space-md);overflow-x:auto;background:var(--gala-color-surface);border-radius:var(--gala-radius-control)}:where(hr){border:0;border-block-start:1px solid var(--gala-color-hairline);margin-block:var(--gala-space-xl)}:where(table){border-collapse:collapse;inline-size:100%}:where(th,td){padding:var(--gala-space-xs) var(--gala-space-sm);border-block-end:1px solid var(--gala-color-hairline);text-align:start}.gala-eyebrow,.gala-comment__meta time,.gala-comment__edited{font-size:var(--gala-text-xs);letter-spacing:var(--gala-tracking-wide);text-transform:uppercase;color:var(--gala-color-muted)}.gala-site-header{position:sticky;inset-block-start:0;z-index:10;display:flex;align-items:center;justify-content:space-between;gap:var(--gala-space-md);max-inline-size:var(--gala-content-width);margin-inline:auto;margin-block-end:var(--gala-space-xl);padding-block:var(--gala-space-xs);background:color-mix(in srgb,var(--gala-color-background) 88%,transparent);backdrop-filter:blur(8px);border-block-end:1px solid var(--gala-color-hairline)}@supports not (backdrop-filter: blur(8px)){.gala-site-header{background:var(--gala-color-background)}}.gala-site-header__actions{display:flex;align-items:center;gap:.5rem}.gala-header-link{display:inline-flex;align-items:center;min-block-size:2.75rem}.gala-icon-control{display:inline-grid;inline-size:2.75rem;block-size:2.75rem;box-sizing:border-box;padding:.6rem;place-items:center;color:inherit;border:.125rem solid transparent;border-radius:50%}.gala-icon-control{transition:background var(--gala-motion-fast) var(--gala-ease),color var(--gala-motion-fast) var(--gala-ease);color:var(--gala-color-muted)}.gala-icon-control:hover{background:var(--gala-color-surface);color:var(--gala-color-text)}.gala-icon-control:focus-visible{outline:.1875rem solid currentcolor;outline-offset:.1875rem}.gala-icon-control svg{inline-size:100%;block-size:100%;fill:none;stroke:currentcolor}.gala-dialog{inline-size:min(42rem,calc(100% - 2rem));max-block-size:calc(100% - 2rem);box-sizing:border-box;padding:1.5rem;color:var(--gala-color-text);background:var(--gala-color-background);border:1px solid var(--gala-surface-border);border-radius:var(--gala-radius-surface);box-shadow:var(--gala-shadow-high)}.gala-dialog::backdrop{background:#000000a6}.gala-account-dialog{inline-size:min(26rem,calc(100% - 2rem))}.gala-account-dialog iframe{display:block;inline-size:100%;block-size:7rem;border:0;overflow:hidden}.gala-dialog{display:grid;gap:var(--gala-space-sm)}.gala-dialog:not([open]){display:none}.gala-dialog__close{justify-self:end;margin:0}.gala-dialog__header{display:flex;align-items:center;justify-content:space-between;gap:var(--gala-space-md)}.gala-dialog__header h2{margin:0}main,.gala-page-footer{max-inline-size:var(--gala-content-width);margin-inline:auto}.gala-page-footer{box-sizing:border-box;inline-size:100%}main{flex:1 0 auto;inline-size:100%}a{color:var(--gala-color-accent)}button{min-block-size:2.75rem;padding-inline:var(--gala-space-md);color:inherit;background:var(--gala-color-surface);border:1px solid var(--gala-color-border);border-radius:var(--gala-radius-control);font:inherit;font-size:var(--gala-text-sm);cursor:pointer;transition:background var(--gala-motion-fast) var(--gala-ease),border-color var(--gala-motion-fast) var(--gala-ease)}button:hover{border-color:var(--gala-color-border-strong);background:var(--gala-color-raised)}button[disabled]{opacity:.55;cursor:not-allowed}button:focus-visible{outline:.1875rem solid currentcolor;outline-offset:.1875rem}.gala-button,.gala-badge,.gala-tag-chip{display:inline-flex;align-items:center;min-block-size:2.75rem;border:1px solid var(--gala-color-border);border-radius:var(--gala-radius-control);font-size:var(--gala-text-sm)}.gala-badge,.gala-tag-chip{padding-inline:.75rem}.gala-tag-chip{text-decoration:none}.gala-section-heading{margin-block:3rem 1rem}.gala-section-heading>:where(h2,p){margin-block:0 .5rem}.gala-hero{display:grid;gap:1.5rem;align-items:center}.gala-hero__image{inline-size:100%;block-size:auto;border-radius:var(--gala-radius-control)}.gala-page-content{max-inline-size:var(--gala-measure)}.gala-page-content :where(img,video){max-inline-size:100%;block-size:auto}.gala-author-profile{display:flex;gap:1rem;padding:1rem;background:var(--gala-color-surface);border-radius:var(--gala-radius-control)}.gala-author-profile img{border-radius:50%;object-fit:cover}.gala-social-links,.gala-page-footer ul,.gala-share ul{display:flex;flex-wrap:wrap;gap:1rem;padding:0;list-style:none}.gala-card-index{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,18rem),1fr));gap:1rem;padding:0;list-style:none}.gala-version-page{inline-size:min(100%,74rem);margin-inline:auto}.gala-version-page__header{max-inline-size:48rem}.gala-version-page__header>p{color:var(--gala-color-muted)}.gala-version-page__header .gala-button{margin-block-start:var(--gala-space-md)}.gala-version-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,18rem),1fr));gap:var(--gala-space-md);margin-block-start:var(--gala-space-lg)}.gala-version-card{display:grid;align-content:start;gap:var(--gala-space-sm);min-block-size:10rem;margin-block-start:var(--gala-space-lg);padding:var(--gala-space-lg);border:1px solid var(--gala-color-border);border-radius:var(--gala-radius-surface);background:var(--gala-color-surface)}.gala-version-grid .gala-version-card{margin-block-start:0}.gala-version-card>*{margin:0}.gala-version-commit{font-family:var(--gala-font-mono);font-weight:700}.gala-version-status{color:var(--gala-color-muted)}.gala-version-status--error{color:var(--gala-color-accent)}[data-layout=portfolio] .gala-card-index{grid-template-columns:repeat(auto-fit,minmax(min(100%,22rem),1fr));gap:1.5rem}[data-layout=portfolio] .gala-card{padding:clamp(1.25rem,3vw,2rem)}.gala-card{block-size:100%;box-sizing:border-box;padding:var(--gala-space-lg);background:var(--gala-color-surface);border:1px solid var(--gala-surface-border);border-radius:var(--gala-radius-surface);box-shadow:var(--gala-surface-shadow)}.gala-card{transition:transform var(--gala-motion-base) var(--gala-ease),box-shadow var(--gala-motion-base) var(--gala-ease)}.gala-card-index li:hover .gala-card{transform:translateY(-2px);box-shadow:var(--gala-shadow-high)}.gala-stats-graph{min-block-size:var(--gala-widget-min-block-size);margin-inline:0}.gala-stats-graph svg{inline-size:100%;block-size:8rem}.gala-stats-graph polyline{fill:none;stroke:var(--gala-color-accent);stroke-width:2}.gala-loading{display:grid;min-block-size:var(--gala-widget-min-block-size);place-items:center}.gala-loading__indicator{inline-size:2rem;block-size:2rem;border:.25rem solid var(--gala-color-border);border-block-start-color:var(--gala-color-accent);border-radius:50%;animation:gala-spin .8s linear infinite}.gala-pagination{display:flex;justify-content:space-between;gap:1rem}.gala-search{display:grid;grid-template-columns:1fr auto;gap:.5rem}.gala-search label{grid-column:1 / -1}.gala-search input{min-block-size:2.75rem;font:inherit}.gala-search-results{display:grid;gap:1rem;padding:0;list-style:none}.gala-search-results article{padding:1rem;background:var(--gala-color-surface);border-radius:var(--gala-radius-control)}.gala-search-results h2{margin-block-start:0}.gala-toc{box-sizing:border-box;min-inline-size:0;max-inline-size:42rem;margin-inline:auto;padding:1rem;background:var(--gala-color-surface)}.gala-toc summary{color:var(--gala-color-text);font-weight:var(--gala-weight-display);cursor:pointer}.gala-toc nav{margin-block-start:var(--gala-space-sm);overscroll-behavior:contain}.gala-toc ol{margin:0;padding-inline-start:1.25rem}.gala-toc a[aria-current=location]{color:var(--gala-color-text);font-weight:700}.gala-share__fallback{inline-size:min(100%,36rem)}.gala-engagement__placeholder{display:grid;min-block-size:var(--gala-widget-min-block-size);align-content:center;margin:0}.gala-engagement-profile>*{margin-block:0 .5rem}.gala-engagement-actions{display:grid;gap:1rem;margin-block-start:1.5rem}.gala-engagement-actions[hidden]{display:none}.gala-account-dialog__invite{margin-block:0 1rem;max-inline-size:34rem}.gala-account-dialog__invite[hidden]{display:none}.gala-engagement-actions form,[data-comment-editor]{display:grid;gap:.5rem;max-inline-size:42rem}.gala-engagement-actions label{display:grid;gap:.5rem}.gala-engagement-actions textarea,[data-comment-editor] textarea{min-block-size:7rem;padding:.75rem;font:inherit}.gala-page-footer{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--gala-space-lg);margin-block-start:var(--gala-space-xl);padding-block-start:var(--gala-space-md);border-block-start:.125rem solid var(--gala-color-border)}.gala-page-footer__identity{display:flex;gap:var(--gala-space-sm);max-inline-size:56rem}.gala-page-footer__identity img{flex:0 0 auto;border-radius:50%;object-fit:cover}.gala-page-footer__identity p{margin:0;color:var(--gala-color-muted);font-size:var(--gala-text-sm)}.gala-page-footer__details{display:grid;gap:var(--gala-space-2xs);align-content:center}.gala-page-footer__line{display:grid;grid-template-columns:repeat(4,max-content);align-items:baseline;gap:0 var(--gala-space-sm)}.gala-page-footer__line>*+*:before{content:"\b7";margin-inline-end:var(--gala-space-sm);color:var(--gala-color-border)}.gala-page-footer__bio{line-height:1.4}.gala-page-footer__author{color:var(--gala-color-text);font-weight:var(--gala-weight-display)}.gala-page-footer nav{margin-inline-start:auto}.gala-page-footer ul{justify-content:flex-end;margin:0}.gala-error-page{min-block-size:60vh;display:grid;align-content:center;justify-items:start}.gala-code-block{position:relative;box-sizing:border-box;min-inline-size:0;max-inline-size:100%;inline-size:100%;overflow:hidden}.gala-code-block>button{position:absolute;inset-block-start:.5rem;inset-inline-end:.5rem;background:var(--gala-color-background)}.gala-code-block pre{box-sizing:border-box;min-inline-size:0;max-inline-size:100%;inline-size:100%;overflow-x:auto;overscroll-behavior-inline:contain;padding:3.5rem 1rem 1rem;background:var(--gala-color-surface);border-radius:var(--gala-radius-control)}.gala-code-block code{display:block;inline-size:max-content;min-inline-size:100%}.gala-embed{display:grid;place-items:center;gap:.75rem;box-sizing:border-box;inline-size:100%;margin-inline:0;padding:1rem;overflow:hidden;background:var(--gala-color-surface);border:.125rem solid var(--gala-color-border);border-radius:var(--gala-radius-control)}.gala-embed--youtube{aspect-ratio:16 / 9}.gala-embed--codepen{min-block-size:25rem}.gala-embed--gist,.gala-embed--x{min-block-size:8rem}.gala-embed iframe{inline-size:100%;block-size:100%;border:0}.gala-embed__icon{inline-size:2rem;block-size:2rem;background:center / contain no-repeat}.gala-embed--youtube .gala-embed__icon{background-image:url(../assets/embed-youtube.svg)}.gala-embed--codepen .gala-embed__icon{background-image:url(../assets/embed-codepen.svg)}.gala-embed--gist .gala-embed__icon{background-image:url(../assets/embed-gist.svg)}.gala-embed--x .gala-embed__icon{background-image:url(../assets/embed-x.svg)}.shiki,.shiki span{color:inherit;background-color:var(--gala-color-surface)}.gala-visually-hidden{position:absolute;inline-size:1px;block-size:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.gala-contact form{display:grid;gap:1rem;max-inline-size:42rem}.gala-contact label{display:grid;gap:.35rem}.gala-contact input,.gala-contact textarea{font:inherit;padding:.65rem}.gala-contact textarea{min-block-size:12rem;resize:vertical}@keyframes gala-spin{to{transform:rotate(1turn)}}@media(min-width:48rem){.gala-hero{grid-template-columns:minmax(0,1fr) minmax(18rem,1fr)}}@media(prefers-reduced-motion:no-preference){@view-transition{navigation:auto}}.gala-comments-island{margin-block-start:var(--gala-space-xl)}.gala-comments__heading{font-size:var(--gala-text-lg);margin-block-end:var(--gala-space-md)}.gala-comments,.gala-comment-replies{list-style:none;margin:0;padding:0}.gala-comment-replies{margin-block-start:var(--gala-space-sm);margin-inline-start:var(--gala-space-xs);padding-inline-start:var(--gala-space-md);border-inline-start:1px solid var(--gala-color-hairline)}.gala-comment{padding-block:var(--gala-space-sm)}.gala-comment+.gala-comment{border-block-start:1px solid var(--gala-color-hairline)}.gala-comment-replies .gala-comment+.gala-comment{border-block-start:0}.gala-comment__meta{display:flex;align-items:baseline;gap:var(--gala-space-xs);margin-block-end:var(--gala-space-2xs);font-size:var(--gala-text-sm)}.gala-comment__meta strong{font-weight:600}.gala-comment__pending{font-size:var(--gala-text-xs);color:var(--gala-color-muted)}.gala-comment__body{margin-block:0;overflow-wrap:anywhere}.gala-comment-actions{display:flex;gap:var(--gala-space-2xs);margin-block-start:var(--gala-space-2xs);opacity:.72;transition:opacity var(--gala-motion-fast) var(--gala-ease)}.gala-comment:hover>.gala-comment-actions,.gala-comment:focus-within>.gala-comment-actions{opacity:1}.gala-comment-actions button{min-block-size:2rem;padding-inline:var(--gala-space-xs);font-size:var(--gala-text-xs);background:transparent;border-color:transparent;color:var(--gala-color-muted)}.gala-comment-actions button:hover{color:var(--gala-color-text);background:var(--gala-color-surface)}.gala-comment__form{display:grid;gap:var(--gala-space-xs);margin-block:var(--gala-space-sm)}.gala-comment__form textarea{inline-size:100%;box-sizing:border-box;padding:var(--gala-space-sm);color:inherit;background:var(--gala-color-raised);border:1px solid var(--gala-color-border);border-radius:var(--gala-radius-control);font:inherit;font-size:var(--gala-text-sm);line-height:var(--gala-leading-body);resize:vertical}.gala-comment__form textarea:focus-visible{outline:2px solid var(--gala-color-accent);outline-offset:1px}.gala-comment__controls{display:flex;gap:var(--gala-space-xs)}.gala-comments__status,.gala-comments__empty,.gala-comments__prompt{color:var(--gala-color-muted);font-size:var(--gala-text-sm)}.gala-comments__more{margin-block-start:var(--gala-space-md)}.gala-engagement{min-block-size:var(--gala-widget-min-block-size);max-inline-size:var(--gala-content-width);margin-block-start:var(--gala-space-xl);padding-block-start:var(--gala-space-lg);border-block-start:1px solid var(--gala-color-border)}.gala-engagement-profile{display:flex;align-items:baseline;gap:var(--gala-space-sm);padding:0;background:none}.gala-engagement-profile h2{font-size:var(--gala-text-lg);margin:0}.gala-engagement-profile p{margin:0;color:var(--gala-color-muted);font-size:var(--gala-text-sm)}.gala-engagement>dl{display:flex;flex-wrap:wrap;gap:var(--gala-space-lg);margin-block:var(--gala-space-md)}.gala-engagement>dl>div{display:flex;flex-direction:column-reverse;gap:.1rem}.gala-engagement>dl dt{font-size:var(--gala-text-xs);letter-spacing:var(--gala-tracking-wide);text-transform:uppercase;color:var(--gala-color-muted)}.gala-engagement>dl dd{margin:0;font-size:var(--gala-text-lg);font-variant-numeric:tabular-nums}.gala-reaction-actions{display:flex;flex-wrap:wrap;gap:var(--gala-space-2xs)}.gala-engagement-actions{display:grid;gap:var(--gala-space-sm);justify-items:start}.gala-engagement-actions>[data-follow-article]{inline-size:auto}.gala-share{margin-block-start:var(--gala-space-lg)}.gala-engagement{position:relative}.gala-reading-progress{position:fixed;z-index:20;inset:0 0 auto;inline-size:100%;block-size:.2rem;appearance:none;border:0;background:transparent;pointer-events:none}.gala-reading-progress::-webkit-progress-bar{background:transparent}.gala-reading-progress::-webkit-progress-value{background:var(--gala-color-accent)}.gala-reading-progress::-moz-progress-bar{background:var(--gala-color-accent)}.gala-reading-layout{display:block}.gala-conversation{max-width:var(--gala-measure);margin-inline:auto;padding-block:clamp(3rem,7vw,6rem)}.gala-conversation__header{margin-block-end:var(--gala-space-xl)}.gala-conversation__header h2{margin:0;font:var(--gala-weight-display) var(--gala-text-2xl)/var(--gala-leading-tight) var(--gala-font-display);letter-spacing:var(--gala-tracking-tight)}.gala-conversation__status{display:block;min-block-size:1.5em;color:var(--gala-color-muted)}.gala-conversation__status:empty{display:none}.gala-action-rail{position:static;z-index:4;inset-block-start:6rem;box-sizing:border-box;width:min(100%,42rem);margin:var(--gala-space-xl) auto 0;display:grid;grid-template-columns:1fr auto 1fr;gap:var(--gala-space-md);align-items:center;padding:var(--gala-space-md);border:1px solid var(--gala-color-hairline);border-radius:var(--gala-radius-surface);background:color-mix(in srgb,var(--gala-color-background) 88%,transparent);box-shadow:var(--gala-shadow-high);backdrop-filter:blur(16px)}.gala-action-rail button,.gala-action-rail a{inline-size:2.5rem;min-block-size:2.5rem;display:grid;place-items:center;padding:0;border:0;border-radius:50%;color:var(--gala-color-muted);background:transparent;font:600 var(--gala-text-xs)/1 var(--gala-font-body);text-decoration:none;cursor:pointer}.gala-action-rail svg{inline-size:1.2rem;block-size:1.2rem}.gala-action-rail button:hover,.gala-action-rail a:hover,.gala-action-rail button[aria-pressed=true]{color:var(--gala-color-background);background:var(--gala-color-accent)}.gala-engagement .gala-action-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--gala-space-xs);margin:0}.gala-action-stats>div{display:flex;min-block-size:2.5rem;align-items:center;justify-content:space-between;gap:.4rem;padding-inline:.25rem}.gala-action-stats>div+div{border-inline-start:1px solid var(--gala-color-hairline)}.gala-action-stats dt{display:grid;place-items:center;color:var(--gala-color-muted)}.gala-action-stats dt svg{inline-size:1.2rem;block-size:1.2rem}.gala-action-stats dd{margin:0;color:var(--gala-color-text);font:400 1.2rem/1 var(--gala-font-mono)}.gala-action-group{display:grid}.gala-reaction-actions,.gala-utility-actions,.gala-share{display:grid;grid-template-columns:repeat(6,2.5rem);gap:var(--gala-space-2xs);justify-content:center}.gala-utility-actions>.gala-share{display:contents}.gala-action-rail__separator{inline-size:1px;align-self:stretch;margin-block:.25rem;background:var(--gala-color-hairline)}.gala-action-rail .gala-share__fallback{position:fixed;inline-size:1px;block-size:1px;overflow:hidden;clip-path:inset(50%)}.gala-action-rail .gala-share__fallback--visible{z-index:10;inset:auto 1rem 1rem;inline-size:min(32rem,calc(100vw - 2rem));block-size:auto;padding:.75rem;overflow:visible;clip-path:none}.gala-action-dock{display:block;min-block-size:4.25rem}.gala-article-boundary{width:100%;margin:clamp(4rem,8vw,7rem) auto var(--gala-space-lg);padding-block-start:var(--gala-space-md);display:flex;align-items:baseline;justify-content:space-between;gap:var(--gala-space-md);border-block-start:.25rem solid var(--gala-color-text);font-size:var(--gala-text-sm)}@media(min-width:75rem){.gala-reading-layout{display:grid;grid-template-columns:minmax(10rem,15rem) minmax(0,1fr) 8.5rem;align-items:start;justify-content:space-between;gap:clamp(2rem,4vw,5rem)}.gala-reading-layout .gala-toc{grid-column:1;grid-row:1;position:sticky;inset-block-start:6rem;max-inline-size:none;margin-inline:0;max-block-size:calc(100vh - 8rem);transition:box-shadow var(--gala-motion-fast) var(--gala-ease)}.gala-reading-layout .gala-toc--floating{box-shadow:var(--gala-shadow-high)}.gala-reading-layout .gala-toc>summary{display:none}.gala-reading-layout .gala-toc>nav{display:block;max-block-size:calc(100vh - 10rem);margin-block-start:0;overflow-y:auto;scrollbar-width:thin}.gala-reading-layout .gala-toc--overflowing ol{padding-block:max(1rem,calc((100vh - 11.5rem)/2))}.gala-reading-layout .gala-markdown{grid-column:2;grid-row:1;max-inline-size:56rem;inline-size:100%;margin:0}.gala-article-boundary{grid-column:1 / -1;grid-row:2;margin-inline:0}.gala-action-dock{grid-column:2;grid-row:3}.gala-action-rail{grid-column:3;grid-row:1;position:sticky;inset-block-start:6rem;width:8.5rem;margin:0;display:grid;grid-template-columns:1fr;gap:var(--gala-space-sm);padding:var(--gala-space-sm);border-radius:var(--gala-radius-surface)}.gala-action-rail .gala-action-stats{gap:var(--gala-space-2xs)}.gala-action-rail:not(.gala-action-rail--integrated) .gala-action-stats{grid-template-columns:1fr}.gala-action-rail:not(.gala-action-rail--integrated) .gala-action-stats>div+div{border-inline-start:0;border-block-start:1px solid var(--gala-color-hairline)}.gala-action-rail .gala-action-stats>div{min-block-size:2.5rem;display:grid;grid-template-columns:repeat(2,2.5rem);justify-content:center;gap:.25rem;padding-inline:0}.gala-action-rail .gala-action-stats dd{justify-self:center}.gala-action-rail .gala-reaction-actions,.gala-action-rail .gala-utility-actions,.gala-action-rail .gala-share{grid-template-columns:repeat(2,2.5rem)}.gala-action-rail__separator{inline-size:auto;block-size:1px;margin:.25rem}.gala-action-rail--integrated{grid-column:2;grid-row:3;position:static;align-self:start;width:100%;margin:0;display:grid;grid-template-columns:minmax(0,1fr) 1px minmax(0,1fr);grid-template-rows:auto 1px auto;grid-template-areas:"reactions utility-separator utilities" "stats-separator stats-separator stats-separator" "stats stats stats";justify-content:stretch;gap:clamp(var(--gala-space-2xs),1vw,var(--gala-space-md));align-items:center;padding:var(--gala-space-md);border-radius:var(--gala-radius-surface)}.gala-action-rail--integrated .gala-action-rail__separator{grid-area:utility-separator;inline-size:1px;block-size:auto;margin:.25rem 0}.gala-action-rail--integrated .gala-action-rail__separator--stats{grid-area:stats-separator;inline-size:auto;block-size:1px;margin:0}.gala-action-rail--integrated .gala-action-group--reactions{grid-area:reactions}.gala-action-rail--integrated .gala-action-group--utilities{grid-area:utilities}.gala-action-rail--integrated .gala-reaction-actions{grid-template-columns:repeat(6,2.5rem)}.gala-action-rail--integrated .gala-utility-actions{grid-template-columns:repeat(4,2.5rem)}.gala-action-rail--integrated .gala-action-stats>div{min-block-size:2.5rem;display:flex;flex-direction:row;gap:.4rem;padding-inline:.25rem}.gala-action-rail--integrated .gala-action-stats{grid-area:stats;grid-template-columns:repeat(3,minmax(0,1fr))}}@media(max-width:74.99rem){.gala-action-rail{grid-template-columns:1fr;width:min(100%,42rem)}.gala-action-rail__separator{inline-size:auto;block-size:1px;margin:0}}@media(max-width:47.99rem){.gala-page-footer{flex-direction:column}.gala-page-footer__line{grid-template-columns:minmax(0,1fr);gap:var(--gala-space-2xs)}.gala-page-footer__line>*+*:before{content:none}.gala-page-footer nav{margin-inline-start:0}.gala-page-footer ul{justify-content:flex-start}.gala-site-name{flex:1 1 auto;min-inline-size:4rem;font-size:var(--gala-text-base)}.gala-header-link{display:none}.gala-site-header__actions{flex:0 0 auto;gap:.125rem}.gala-account-control{inline-size:2.75rem;padding:0;justify-content:center}.gala-account-control [data-user-label]{display:none}}
|
|
1
|
+
:root{color-scheme:light dark;--gala-color-background: light-dark(#fdfcfb, #131211);--gala-color-surface: light-dark(#f6f4f1, #1c1a18);--gala-color-raised: light-dark(#fffffe, #24211f);--gala-color-text: light-dark(#1a1817, #f2efec);--gala-color-muted: light-dark(#6b645e, #a8a099);--gala-color-border: light-dark(#968e86, #6b645e);--gala-color-hairline: light-dark(#e8e3dd, #2a2724);--gala-color-border-strong: light-dark(#c9c2ba, #46413d);--gala-color-accent: light-dark( var(--gala-accent-light, #8a4b2a), var(--gala-accent-dark, #e8a87c));--gala-font-body: ui-sans-serif, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;--gala-font-display: var(--gala-font-body);--gala-font-mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, monospace;--gala-text-xs: .78rem;--gala-text-sm: .875rem;--gala-text-base: clamp(1rem, .97rem + .15vw, 1.0625rem);--gala-text-lg: clamp(1.15rem, 1.09rem + .3vw, 1.3rem);--gala-text-xl: clamp(1.4rem, 1.28rem + .6vw, 1.75rem);--gala-text-2xl: clamp(1.75rem, 1.5rem + 1.2vw, 2.4rem);--gala-text-3xl: clamp(2.2rem, 1.7rem + 2.4vw, 3.4rem);--gala-leading-tight: 1.15;--gala-leading-body: 1.65;--gala-tracking-tight: -.018em;--gala-tracking-wide: .08em;--gala-weight-display: 640;--gala-step: 1rem;--gala-space-2xs: calc(var(--gala-step) * .25);--gala-space-xs: calc(var(--gala-step) * .5);--gala-space-sm: calc(var(--gala-step) * .75);--gala-space-md: var(--gala-step);--gala-space-lg: calc(var(--gala-step) * 1.75);--gala-space-xl: calc(var(--gala-step) * 3);--gala-space-page: clamp(1.25rem, 5vw, 4.5rem);--gala-radius-control: .625rem;--gala-radius-surface: 1rem;--gala-shadow-high: 0 1px 2px light-dark(rgb(26 24 23 / 5%), rgb(0 0 0 / 35%)), 0 12px 28px -12px light-dark(rgb(26 24 23 / 18%), rgb(0 0 0 / 60%));--gala-motion-fast: .12s;--gala-motion-base: .22s;--gala-ease: cubic-bezier(.2, 0, 0, 1);--gala-content-width: 72rem;--gala-measure: 68ch;--gala-widget-min-block-size: 8rem}:root[data-mode=light]{color-scheme:light;--gala-color-background: #fdfcfb;--gala-color-surface: #f6f4f1;--gala-color-raised: #fffffe;--gala-color-text: #1a1817;--gala-color-muted: #6b645e;--gala-color-border: #968e86;--gala-color-hairline: #e8e3dd;--gala-color-border-strong: #c9c2ba;--gala-color-accent: var(--gala-accent-light, #8a4b2a)}:root[data-mode=dark]{color-scheme:dark;--gala-color-background: #131211;--gala-color-surface: #1c1a18;--gala-color-raised: #24211f;--gala-color-text: #f2efec;--gala-color-muted: #a8a099;--gala-color-border: #6b645e;--gala-color-hairline: #2a2724;--gala-color-border-strong: #46413d;--gala-color-accent: var(--gala-accent-dark, #e8a87c)}:root[data-palette=ocean]{--gala-color-background: light-dark(#f8fbfd, #0b1a24);--gala-color-surface: light-dark(#eef4f9, #12252f);--gala-color-raised: light-dark(#ffffff, #172d39);--gala-color-text: light-dark(#12242f, #eaf3f8);--gala-color-muted: light-dark(#5a6f7d, #98aebb);--gala-color-border: light-dark(#7d8d99, #5f7d8c);--gala-color-hairline: light-dark(#dde7ee, #1d3644);--gala-color-border-strong: light-dark(#bdccd7, #33505f);--gala-color-accent: light-dark(#0f5f7a, #7fd0e8)}:root[data-palette=ocean][data-mode=light]{color-scheme:light;--gala-color-background: #f8fbfd;--gala-color-surface: #eef4f9;--gala-color-raised: #ffffff;--gala-color-text: #12242f;--gala-color-muted: #5a6f7d;--gala-color-border: #7d8d99;--gala-color-hairline: #dde7ee;--gala-color-border-strong: #bdccd7;--gala-color-accent: var(--gala-accent-light, #0f5f7a)}:root[data-palette=ocean][data-mode=dark]{color-scheme:dark;--gala-color-background: #0b1a24;--gala-color-surface: #12252f;--gala-color-raised: #172d39;--gala-color-text: #eaf3f8;--gala-color-muted: #98aebb;--gala-color-border: #5f7d8c;--gala-color-hairline: #1d3644;--gala-color-border-strong: #33505f;--gala-color-accent: var(--gala-accent-dark, #7fd0e8)}:root[data-palette=forest]{--gala-color-background: light-dark(#fbfcfa, #101410);--gala-color-surface: light-dark(#f0f4ee, #182018);--gala-color-raised: light-dark(#ffffff, #1f271f);--gala-color-text: light-dark(#18201a, #eef3ec);--gala-color-muted: light-dark(#5b665d, #9aa89b);--gala-color-border: light-dark(#849084, #5e685f);--gala-color-hairline: light-dark(#e2e9df, #232b23);--gala-color-border-strong: light-dark(#5b665d, #7d897e);--gala-color-accent: light-dark(#2f6b3f, #8fd3a1)}:root[data-palette=forest][data-mode=light]{color-scheme:light;--gala-color-background: #fbfcfa;--gala-color-surface: #f0f4ee;--gala-color-raised: #ffffff;--gala-color-text: #18201a;--gala-color-muted: #5b665d;--gala-color-border: #849084;--gala-color-hairline: #e2e9df;--gala-color-border-strong: #5b665d;--gala-color-accent: var(--gala-accent-light, #2f6b3f)}:root[data-palette=forest][data-mode=dark]{color-scheme:dark;--gala-color-background: #101410;--gala-color-surface: #182018;--gala-color-raised: #1f271f;--gala-color-text: #eef3ec;--gala-color-muted: #9aa89b;--gala-color-border: #5e685f;--gala-color-hairline: #232b23;--gala-color-border-strong: #7d897e;--gala-color-accent: var(--gala-accent-dark, #8fd3a1)}:root[data-palette=plum]{--gala-color-background: light-dark(#fdfbfd, #141016);--gala-color-surface: light-dark(#f5eff5, #1d1720);--gala-color-raised: light-dark(#ffffff, #241d28);--gala-color-text: light-dark(#1f1820, #f2ecf3);--gala-color-muted: light-dark(#665c69, #a99dac);--gala-color-border: light-dark(#948a95, #6b606d);--gala-color-hairline: light-dark(#ebe2ec, #281f2b);--gala-color-border-strong: light-dark(#665c69, #8b7f8d);--gala-color-accent: light-dark(#7a3d76, #dda6d8)}:root[data-palette=plum][data-mode=light]{color-scheme:light;--gala-color-background: #fdfbfd;--gala-color-surface: #f5eff5;--gala-color-raised: #ffffff;--gala-color-text: #1f1820;--gala-color-muted: #665c69;--gala-color-border: #948a95;--gala-color-hairline: #ebe2ec;--gala-color-border-strong: #665c69;--gala-color-accent: var(--gala-accent-light, #7a3d76)}:root[data-palette=plum][data-mode=dark]{color-scheme:dark;--gala-color-background: #141016;--gala-color-surface: #1d1720;--gala-color-raised: #241d28;--gala-color-text: #f2ecf3;--gala-color-muted: #a99dac;--gala-color-border: #6b606d;--gala-color-hairline: #281f2b;--gala-color-border-strong: #8b7f8d;--gala-color-accent: var(--gala-accent-dark, #dda6d8)}:root[data-theme=editorial]{--gala-font-body: ui-serif, "Iowan Old Style", Charter, "Source Serif 4", Georgia, serif;--gala-font-display: var(--gala-font-body);--gala-weight-display: 600;--gala-step: 1.1rem;--gala-leading-body: 1.7;--gala-radius-control: .5rem;--gala-radius-surface: .75rem;--gala-measure: 66ch;--gala-surface-border: var(--gala-color-hairline);--gala-surface-shadow: none}:root[data-theme=modern]{--gala-font-body: ui-sans-serif, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;--gala-font-display: var(--gala-font-body);--gala-weight-display: 640;--gala-step: 1rem;--gala-leading-body: 1.65;--gala-radius-control: .625rem;--gala-radius-surface: 1rem;--gala-measure: 70ch;--gala-surface-border: transparent;--gala-surface-shadow: var(--gala-shadow-high)}:root[data-theme=technical]{--gala-font-body: ui-sans-serif, -apple-system, "Segoe UI", Roboto, sans-serif;--gala-font-display: var(--gala-font-body);--gala-weight-display: 660;--gala-tracking-tight: -.022em;--gala-step: .9rem;--gala-leading-body: 1.6;--gala-radius-control: .375rem;--gala-radius-surface: .5rem;--gala-measure: 74ch;--gala-surface-border: var(--gala-color-border-strong);--gala-surface-shadow: none}:root[data-theme=technical] .gala-eyebrow,:root[data-theme=technical] .gala-engagement dt,:root[data-theme=technical] .gala-comment__meta time{font-family:var(--gala-font-mono)}@media(prefers-reduced-motion:reduce){:root{--gala-motion-fast: 0ms;--gala-motion-base: 0ms;scroll-behavior:auto}*,*:before,*:after{animation-duration:1ms!important;transition-duration:1ms!important}}:root[data-layout=portfolio]{--gala-content-width: 82rem}html{scroll-behavior:smooth}body{box-sizing:border-box;display:flex;flex-direction:column;min-block-size:100vh;min-block-size:100svh;margin:0;padding:var(--gala-space-page);color:var(--gala-color-text);background:var(--gala-color-background);font-family:var(--gala-font-body);font-size:var(--gala-text-base);line-height:var(--gala-leading-body);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.gala-skip-link{position:fixed;inset-block-start:1rem;inset-inline-start:1rem;z-index:100;padding:.75rem 1rem;color:#fff;background:var(--gala-color-accent);border-radius:999px;transform:translateY(-200%)}.gala-skip-link:focus{transform:translateY(0)}.gala-site-name{color:var(--gala-color-text);font-family:var(--gala-font-display);font-size:var(--gala-text-lg);font-weight:var(--gala-weight-display);letter-spacing:var(--gala-tracking-tight);text-decoration:none;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.gala-header-link{color:var(--gala-color-text);font-size:var(--gala-text-sm);text-decoration:none}.gala-account-control{display:inline-flex;align-items:center;gap:.5rem;min-block-size:2.75rem;padding-inline:.9rem;border-radius:999px}.gala-account-control svg{inline-size:1rem;block-size:1rem}.gala-article{max-inline-size:76rem;margin-inline:auto}.gala-page--article .gala-site-header,.gala-page--article main,.gala-page--article .gala-page-footer{max-inline-size:112rem}.gala-page--article .gala-article,.gala-page--article .gala-engagement{max-inline-size:none}.gala-article-header{padding-block:clamp(2.5rem,5vw,5rem)}.gala-article-header h1{max-inline-size:12ch;font-size:clamp(2.5rem,7vw,5.25rem);line-height:.96;letter-spacing:-.055em}.gala-byline{color:var(--gala-color-muted);font-size:var(--gala-text-sm)}.gala-markdown{max-inline-size:42rem;margin-inline:auto;font-size:clamp(1.1rem,1rem + .35vw,1.3rem)}.gala-markdown :where(h2){margin-block-start:clamp(3rem,7vw,5rem);font-size:clamp(2.2rem,5vw,3.8rem)}.gala-markdown :where(h2,h3,h4,h5,h6){scroll-margin-block-start:7rem}.gala-markdown :where(img,video){max-inline-size:100%;block-size:auto}.gala-markdown :where(pre){box-sizing:border-box;max-inline-size:100%;overflow-x:auto;scrollbar-color:var(--gala-color-border-strong) var(--gala-color-surface)}:where(h1,h2,h3,h4){font-family:var(--gala-font-display);font-weight:var(--gala-weight-display);line-height:var(--gala-leading-tight);letter-spacing:var(--gala-tracking-tight);text-wrap:balance;margin-block:0 var(--gala-space-sm)}:where(h1){font-size:var(--gala-text-3xl)}:where(h2){font-size:var(--gala-text-2xl)}:where(h3){font-size:var(--gala-text-xl)}:where(h4){font-size:var(--gala-text-lg)}:where(p,ul,ol,blockquote){margin-block:0 var(--gala-space-md)}:where(p){text-wrap:pretty}blockquote{margin-inline:0;padding-inline-start:var(--gala-space-md);border-inline-start:2px solid var(--gala-color-accent);color:var(--gala-color-muted)}:where(code,kbd,samp){font-family:var(--gala-font-mono);font-size:.9em}:where(pre){padding:var(--gala-space-md);overflow-x:auto;background:var(--gala-color-surface);border-radius:var(--gala-radius-control)}:where(hr){border:0;border-block-start:1px solid var(--gala-color-hairline);margin-block:var(--gala-space-xl)}:where(table){border-collapse:collapse;inline-size:100%}:where(th,td){padding:var(--gala-space-xs) var(--gala-space-sm);border-block-end:1px solid var(--gala-color-hairline);text-align:start}.gala-eyebrow,.gala-comment__meta time,.gala-comment__edited{font-size:var(--gala-text-xs);letter-spacing:var(--gala-tracking-wide);text-transform:uppercase;color:var(--gala-color-muted)}.gala-site-header{position:sticky;inset-block-start:0;z-index:10;box-sizing:border-box;inline-size:100%;display:flex;align-items:center;justify-content:space-between;gap:var(--gala-space-md);max-inline-size:var(--gala-content-width);margin-inline:auto;margin-block-end:var(--gala-space-xl);padding-block:var(--gala-space-xs);background:color-mix(in srgb,var(--gala-color-background) 88%,transparent);backdrop-filter:blur(8px);border-block-end:1px solid var(--gala-color-hairline)}@supports not (backdrop-filter: blur(8px)){.gala-site-header{background:var(--gala-color-background)}}.gala-site-header__actions{display:flex;align-items:center;gap:.5rem}.gala-header-link{display:inline-flex;align-items:center;min-block-size:2.75rem}.gala-icon-control{display:inline-grid;inline-size:2.75rem;block-size:2.75rem;box-sizing:border-box;padding:.6rem;place-items:center;color:inherit;border:.125rem solid transparent;border-radius:50%}.gala-icon-control{transition:background var(--gala-motion-fast) var(--gala-ease),color var(--gala-motion-fast) var(--gala-ease);color:var(--gala-color-muted)}.gala-icon-control:hover{background:var(--gala-color-surface);color:var(--gala-color-text)}.gala-icon-control:focus-visible{outline:.1875rem solid currentcolor;outline-offset:.1875rem}.gala-icon-control svg{inline-size:100%;block-size:100%;fill:none;stroke:currentcolor}.gala-dialog{inline-size:min(42rem,calc(100% - 2rem));max-block-size:calc(100% - 2rem);box-sizing:border-box;padding:1.5rem;color:var(--gala-color-text);background:var(--gala-color-background);border:1px solid var(--gala-surface-border);border-radius:var(--gala-radius-surface);box-shadow:var(--gala-shadow-high)}.gala-dialog::backdrop{background:#000000a6}.gala-account-dialog{inline-size:min(26rem,calc(100% - 2rem))}.gala-account-dialog iframe{display:block;inline-size:100%;block-size:7rem;border:0;overflow:hidden}.gala-dialog{display:grid;gap:var(--gala-space-sm)}.gala-dialog:not([open]){display:none}.gala-dialog__close{justify-self:end;margin:0}.gala-dialog__header{display:flex;align-items:center;justify-content:space-between;gap:var(--gala-space-md)}.gala-dialog__header h2{margin:0}main,.gala-page-footer{max-inline-size:var(--gala-content-width);margin-inline:auto}.gala-page-footer{box-sizing:border-box;inline-size:100%}main{flex:1 0 auto;inline-size:100%}a{color:var(--gala-color-accent)}button{min-block-size:2.75rem;padding-inline:var(--gala-space-md);color:inherit;background:var(--gala-color-surface);border:1px solid var(--gala-color-border);border-radius:var(--gala-radius-control);font:inherit;font-size:var(--gala-text-sm);cursor:pointer;transition:background var(--gala-motion-fast) var(--gala-ease),border-color var(--gala-motion-fast) var(--gala-ease)}button:hover{border-color:var(--gala-color-border-strong);background:var(--gala-color-raised)}button[disabled]{opacity:.55;cursor:not-allowed}button:focus-visible{outline:.1875rem solid currentcolor;outline-offset:.1875rem}.gala-button,.gala-badge,.gala-tag-chip{display:inline-flex;align-items:center;min-block-size:2.75rem;border:1px solid var(--gala-color-border);border-radius:var(--gala-radius-control);font-size:var(--gala-text-sm)}.gala-badge,.gala-tag-chip{padding-inline:.75rem}.gala-tag-chip{text-decoration:none}.gala-section-heading{margin-block:3rem 1rem}.gala-section-heading>:where(h2,p){margin-block:0 .5rem}.gala-hero{display:grid;gap:1.5rem;align-items:center}.gala-hero__image{inline-size:100%;block-size:auto;border-radius:var(--gala-radius-control)}.gala-page-content{max-inline-size:var(--gala-measure)}.gala-page-content :where(img,video){max-inline-size:100%;block-size:auto}.gala-author-profile{display:flex;gap:1rem;padding:1rem;background:var(--gala-color-surface);border-radius:var(--gala-radius-control)}.gala-author-profile img{border-radius:50%;object-fit:cover}.gala-social-links,.gala-page-footer ul,.gala-share ul{display:flex;flex-wrap:wrap;gap:1rem;padding:0;list-style:none}.gala-card-index{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,18rem),1fr));gap:1rem;padding:0;list-style:none}.gala-version-page{inline-size:min(100%,74rem);margin-inline:auto}.gala-version-page__header{max-inline-size:48rem}.gala-version-page__header>p{color:var(--gala-color-muted)}.gala-version-page__header .gala-button{margin-block-start:var(--gala-space-md)}.gala-version-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,18rem),1fr));gap:var(--gala-space-md);margin-block-start:var(--gala-space-lg)}.gala-version-card{display:grid;align-content:start;gap:var(--gala-space-sm);min-block-size:10rem;margin-block-start:var(--gala-space-lg);padding:var(--gala-space-lg);border:1px solid var(--gala-color-border);border-radius:var(--gala-radius-surface);background:var(--gala-color-surface)}.gala-version-grid .gala-version-card{margin-block-start:0}.gala-version-card>*{margin:0}.gala-version-commit{font-family:var(--gala-font-mono);font-weight:700}.gala-version-status{color:var(--gala-color-muted)}.gala-version-status--error{color:var(--gala-color-accent)}[data-layout=portfolio] .gala-card-index{grid-template-columns:repeat(auto-fit,minmax(min(100%,22rem),1fr));gap:1.5rem}[data-layout=portfolio] .gala-card{padding:clamp(1.25rem,3vw,2rem)}.gala-card{block-size:100%;box-sizing:border-box;padding:var(--gala-space-lg);background:var(--gala-color-surface);border:1px solid var(--gala-surface-border);border-radius:var(--gala-radius-surface);box-shadow:var(--gala-surface-shadow)}.gala-card{transition:transform var(--gala-motion-base) var(--gala-ease),box-shadow var(--gala-motion-base) var(--gala-ease)}.gala-card-index li:hover .gala-card{transform:translateY(-2px);box-shadow:var(--gala-shadow-high)}.gala-stats-graph{min-block-size:var(--gala-widget-min-block-size);margin-inline:0}.gala-stats-graph svg{inline-size:100%;block-size:8rem}.gala-stats-graph polyline{fill:none;stroke:var(--gala-color-accent);stroke-width:2}.gala-loading{display:grid;min-block-size:var(--gala-widget-min-block-size);place-items:center}.gala-loading__indicator{inline-size:2rem;block-size:2rem;border:.25rem solid var(--gala-color-border);border-block-start-color:var(--gala-color-accent);border-radius:50%;animation:gala-spin .8s linear infinite}.gala-pagination{display:flex;justify-content:space-between;gap:1rem}.gala-search{display:grid;grid-template-columns:1fr auto;gap:.5rem}.gala-search label{grid-column:1 / -1}.gala-search input{min-block-size:2.75rem;font:inherit}.gala-search-results{display:grid;gap:1rem;padding:0;list-style:none}.gala-search-results article{padding:1rem;background:var(--gala-color-surface);border-radius:var(--gala-radius-control)}.gala-search-results h2{margin-block-start:0}.gala-toc{box-sizing:border-box;min-inline-size:0;max-inline-size:42rem;margin-inline:auto;padding:1rem;background:var(--gala-color-surface)}.gala-toc summary{color:var(--gala-color-text);font-weight:var(--gala-weight-display);cursor:pointer}.gala-toc nav{margin-block-start:var(--gala-space-sm);overscroll-behavior:contain}.gala-toc ol{margin:0;padding-inline-start:1.25rem}.gala-toc a[aria-current=location]{color:var(--gala-color-text);font-weight:700}.gala-share__fallback{inline-size:min(100%,36rem)}.gala-engagement__placeholder{display:grid;min-block-size:var(--gala-widget-min-block-size);align-content:center;margin:0}.gala-engagement-profile>*{margin-block:0 .5rem}.gala-engagement-actions{display:grid;gap:1rem;margin-block-start:1.5rem}.gala-engagement-actions[hidden]{display:none}.gala-account-dialog__invite{margin-block:0 1rem;max-inline-size:34rem}.gala-account-dialog__invite[hidden]{display:none}.gala-engagement-actions form,[data-comment-editor]{display:grid;gap:.5rem;max-inline-size:42rem}.gala-engagement-actions label{display:grid;gap:.5rem}.gala-engagement-actions textarea,[data-comment-editor] textarea{min-block-size:7rem;padding:.75rem;font:inherit}.gala-page-footer{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--gala-space-lg);margin-block-start:var(--gala-space-xl);padding-block-start:var(--gala-space-md);border-block-start:.125rem solid var(--gala-color-border)}.gala-page-footer__identity{display:flex;gap:var(--gala-space-sm);max-inline-size:56rem}.gala-page-footer__identity img{flex:0 0 auto;border-radius:50%;object-fit:cover}.gala-page-footer__identity p{margin:0;color:var(--gala-color-muted);font-size:var(--gala-text-sm)}.gala-page-footer__details{display:grid;gap:var(--gala-space-2xs);align-content:center}.gala-page-footer__line{display:grid;grid-template-columns:repeat(4,max-content);align-items:baseline;gap:0 var(--gala-space-sm)}.gala-page-footer__line>*+*:before{content:"\b7";margin-inline-end:var(--gala-space-sm);color:var(--gala-color-border)}.gala-page-footer__bio{line-height:1.4}.gala-page-footer__author{color:var(--gala-color-text);font-weight:var(--gala-weight-display)}.gala-page-footer nav{margin-inline-start:auto}.gala-page-footer ul{justify-content:flex-end;margin:0}.gala-error-page{min-block-size:60vh;display:grid;align-content:center;justify-items:start}.gala-code-block{position:relative;box-sizing:border-box;min-inline-size:0;max-inline-size:100%;inline-size:100%;overflow:hidden}.gala-code-block>button{position:absolute;inset-block-start:.5rem;inset-inline-end:.5rem;background:var(--gala-color-background)}.gala-code-block pre{box-sizing:border-box;min-inline-size:0;max-inline-size:100%;inline-size:100%;overflow-x:auto;overscroll-behavior-inline:contain;padding:3.5rem 1rem 1rem;background:var(--gala-color-surface);border-radius:var(--gala-radius-control)}.gala-code-block code{display:block;inline-size:max-content;min-inline-size:100%}.gala-embed{display:grid;place-items:center;gap:.75rem;box-sizing:border-box;inline-size:100%;margin-inline:0;padding:1rem;overflow:hidden;background:var(--gala-color-surface);border:.125rem solid var(--gala-color-border);border-radius:var(--gala-radius-control)}.gala-embed--youtube{aspect-ratio:16 / 9}.gala-embed--codepen{min-block-size:25rem}.gala-embed--gist,.gala-embed--x{min-block-size:8rem}.gala-embed iframe{inline-size:100%;block-size:100%;border:0}.gala-embed__icon{inline-size:2rem;block-size:2rem;background:center / contain no-repeat}.gala-embed--youtube .gala-embed__icon{background-image:url(../assets/embed-youtube.svg)}.gala-embed--codepen .gala-embed__icon{background-image:url(../assets/embed-codepen.svg)}.gala-embed--gist .gala-embed__icon{background-image:url(../assets/embed-gist.svg)}.gala-embed--x .gala-embed__icon{background-image:url(../assets/embed-x.svg)}.shiki,.shiki span{color:inherit;background-color:var(--gala-color-surface)}.gala-visually-hidden{position:absolute;inline-size:1px;block-size:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.gala-contact form{display:grid;gap:1rem;max-inline-size:42rem}.gala-contact label{display:grid;gap:.35rem}.gala-contact input,.gala-contact textarea{font:inherit;padding:.65rem}.gala-contact textarea{min-block-size:12rem;resize:vertical}@keyframes gala-spin{to{transform:rotate(1turn)}}@media(min-width:48rem){.gala-hero{grid-template-columns:minmax(0,1fr) minmax(18rem,1fr)}}@media(prefers-reduced-motion:no-preference){@view-transition{navigation:auto}}.gala-comments-island{margin-block-start:var(--gala-space-xl)}.gala-comments__heading{font-size:var(--gala-text-lg);margin-block-end:var(--gala-space-md)}.gala-comments,.gala-comment-replies{list-style:none;margin:0;padding:0}.gala-comment-replies{margin-block-start:var(--gala-space-sm);margin-inline-start:var(--gala-space-xs);padding-inline-start:var(--gala-space-md);border-inline-start:1px solid var(--gala-color-hairline)}.gala-comment{padding-block:var(--gala-space-sm)}.gala-comment+.gala-comment{border-block-start:1px solid var(--gala-color-hairline)}.gala-comment-replies .gala-comment+.gala-comment{border-block-start:0}.gala-comment__meta{display:flex;align-items:baseline;gap:var(--gala-space-xs);margin-block-end:var(--gala-space-2xs);font-size:var(--gala-text-sm)}.gala-comment__meta strong{font-weight:600}.gala-comment__pending{font-size:var(--gala-text-xs);color:var(--gala-color-muted)}.gala-comment__body{margin-block:0;overflow-wrap:anywhere}.gala-comment-actions{display:flex;gap:var(--gala-space-2xs);margin-block-start:var(--gala-space-2xs);opacity:.72;transition:opacity var(--gala-motion-fast) var(--gala-ease)}.gala-comment:hover>.gala-comment-actions,.gala-comment:focus-within>.gala-comment-actions{opacity:1}.gala-comment-actions button{min-block-size:2rem;padding-inline:var(--gala-space-xs);font-size:var(--gala-text-xs);background:transparent;border-color:transparent;color:var(--gala-color-muted)}.gala-comment-actions button:hover{color:var(--gala-color-text);background:var(--gala-color-surface)}.gala-comment__form{display:grid;gap:var(--gala-space-xs);margin-block:var(--gala-space-sm)}.gala-comment__form textarea{inline-size:100%;box-sizing:border-box;padding:var(--gala-space-sm);color:inherit;background:var(--gala-color-raised);border:1px solid var(--gala-color-border);border-radius:var(--gala-radius-control);font:inherit;font-size:var(--gala-text-sm);line-height:var(--gala-leading-body);resize:vertical}.gala-comment__form textarea:focus-visible{outline:2px solid var(--gala-color-accent);outline-offset:1px}.gala-comment__controls{display:flex;gap:var(--gala-space-xs)}.gala-comments__status,.gala-comments__empty,.gala-comments__prompt{color:var(--gala-color-muted);font-size:var(--gala-text-sm)}.gala-comments__more{margin-block-start:var(--gala-space-md)}.gala-engagement{min-block-size:var(--gala-widget-min-block-size);max-inline-size:var(--gala-content-width);margin-block-start:var(--gala-space-xl);padding-block-start:var(--gala-space-lg);border-block-start:1px solid var(--gala-color-border)}.gala-engagement-profile{display:flex;align-items:baseline;gap:var(--gala-space-sm);padding:0;background:none}.gala-engagement-profile h2{font-size:var(--gala-text-lg);margin:0}.gala-engagement-profile p{margin:0;color:var(--gala-color-muted);font-size:var(--gala-text-sm)}.gala-engagement>dl{display:flex;flex-wrap:wrap;gap:var(--gala-space-lg);margin-block:var(--gala-space-md)}.gala-engagement>dl>div{display:flex;flex-direction:column-reverse;gap:.1rem}.gala-engagement>dl dt{font-size:var(--gala-text-xs);letter-spacing:var(--gala-tracking-wide);text-transform:uppercase;color:var(--gala-color-muted)}.gala-engagement>dl dd{margin:0;font-size:var(--gala-text-lg);font-variant-numeric:tabular-nums}.gala-reaction-actions{display:flex;flex-wrap:wrap;gap:var(--gala-space-2xs)}.gala-engagement-actions{display:grid;gap:var(--gala-space-sm);justify-items:start}.gala-engagement-actions>[data-follow-article]{inline-size:auto}.gala-share{margin-block-start:var(--gala-space-lg)}.gala-engagement{position:relative}.gala-reading-progress{position:fixed;z-index:20;inset:0 0 auto;inline-size:100%;block-size:.2rem;appearance:none;border:0;background:transparent;pointer-events:none}.gala-reading-progress::-webkit-progress-bar{background:transparent}.gala-reading-progress::-webkit-progress-value{background:var(--gala-color-accent)}.gala-reading-progress::-moz-progress-bar{background:var(--gala-color-accent)}.gala-reading-layout{display:block}.gala-conversation{max-width:var(--gala-measure);margin-inline:auto;padding-block:clamp(3rem,7vw,6rem)}.gala-conversation__header{margin-block-end:var(--gala-space-xl)}.gala-conversation__header h2{margin:0;font:var(--gala-weight-display) var(--gala-text-2xl)/var(--gala-leading-tight) var(--gala-font-display);letter-spacing:var(--gala-tracking-tight)}.gala-conversation__status{display:block;min-block-size:1.5em;color:var(--gala-color-muted)}.gala-conversation__status:empty{display:none}.gala-action-rail{position:static;z-index:4;inset-block-start:6rem;box-sizing:border-box;width:min(100%,42rem);margin:var(--gala-space-xl) auto 0;display:grid;grid-template-columns:1fr auto 1fr;gap:var(--gala-space-md);align-items:center;padding:var(--gala-space-md);border:1px solid var(--gala-color-hairline);border-radius:var(--gala-radius-surface);background:color-mix(in srgb,var(--gala-color-background) 88%,transparent);box-shadow:var(--gala-shadow-high);backdrop-filter:blur(16px)}.gala-action-rail button,.gala-action-rail a{inline-size:2.5rem;min-block-size:2.5rem;display:grid;place-items:center;padding:0;border:0;border-radius:50%;color:var(--gala-color-muted);background:transparent;font:600 var(--gala-text-xs)/1 var(--gala-font-body);text-decoration:none;cursor:pointer}.gala-action-rail svg{inline-size:1.2rem;block-size:1.2rem}.gala-action-rail button:hover,.gala-action-rail a:hover,.gala-action-rail button[aria-pressed=true]{color:var(--gala-color-background);background:var(--gala-color-accent)}.gala-engagement .gala-action-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--gala-space-xs);margin:0}.gala-action-stats>div{display:flex;min-block-size:2.5rem;align-items:center;justify-content:space-between;gap:.4rem;padding-inline:.25rem}.gala-action-stats>div+div{border-inline-start:1px solid var(--gala-color-hairline)}.gala-action-stats dt{display:grid;place-items:center;color:var(--gala-color-muted)}.gala-action-stats dt svg{inline-size:1.2rem;block-size:1.2rem}.gala-action-stats dd{margin:0;color:var(--gala-color-text);font:400 1.2rem/1 var(--gala-font-mono)}.gala-action-group{display:grid}.gala-reaction-actions,.gala-utility-actions,.gala-share{display:grid;grid-template-columns:repeat(6,2.5rem);gap:var(--gala-space-2xs);justify-content:center}.gala-utility-actions>.gala-share{display:contents}.gala-action-rail__separator{inline-size:1px;align-self:stretch;margin-block:.25rem;background:var(--gala-color-hairline)}.gala-action-rail .gala-share__fallback{position:fixed;inline-size:1px;block-size:1px;overflow:hidden;clip-path:inset(50%)}.gala-action-rail .gala-share__fallback--visible{z-index:10;inset:auto 1rem 1rem;inline-size:min(32rem,calc(100vw - 2rem));block-size:auto;padding:.75rem;overflow:visible;clip-path:none}.gala-action-dock{display:block;min-block-size:4.25rem}.gala-article-boundary{width:100%;margin:clamp(4rem,8vw,7rem) auto var(--gala-space-lg);padding-block-start:var(--gala-space-md);display:flex;align-items:baseline;justify-content:space-between;gap:var(--gala-space-md);border-block-start:.25rem solid var(--gala-color-text);font-size:var(--gala-text-sm)}@media(min-width:75rem){.gala-reading-layout{display:grid;grid-template-columns:minmax(10rem,15rem) minmax(0,1fr) 8.5rem;align-items:start;justify-content:space-between;gap:clamp(2rem,4vw,5rem)}.gala-reading-layout .gala-toc{grid-column:1;grid-row:1;position:sticky;inset-block-start:6rem;max-inline-size:none;margin-inline:0;max-block-size:calc(100vh - 8rem);transition:box-shadow var(--gala-motion-fast) var(--gala-ease)}.gala-reading-layout .gala-toc--floating{box-shadow:var(--gala-shadow-high)}.gala-reading-layout .gala-toc>summary{display:none}.gala-reading-layout .gala-toc>nav{display:block;max-block-size:calc(100vh - 10rem);margin-block-start:0;overflow-y:auto;scrollbar-width:thin}.gala-reading-layout .gala-toc--overflowing ol{padding-block:max(1rem,calc((100vh - 11.5rem)/2))}.gala-reading-layout .gala-markdown{grid-column:2;grid-row:1;max-inline-size:56rem;inline-size:100%;margin:0}.gala-article-boundary{grid-column:1 / -1;grid-row:2;margin-inline:0}.gala-action-dock{grid-column:2;grid-row:3}.gala-action-rail{grid-column:3;grid-row:1;position:sticky;inset-block-start:6rem;width:8.5rem;margin:0;display:grid;grid-template-columns:1fr;gap:var(--gala-space-sm);padding:var(--gala-space-sm);border-radius:var(--gala-radius-surface)}.gala-action-rail .gala-action-stats{gap:var(--gala-space-2xs)}.gala-action-rail:not(.gala-action-rail--integrated) .gala-action-stats{grid-template-columns:1fr}.gala-action-rail:not(.gala-action-rail--integrated) .gala-action-stats>div+div{border-inline-start:0;border-block-start:1px solid var(--gala-color-hairline)}.gala-action-rail .gala-action-stats>div{min-block-size:2.5rem;display:grid;grid-template-columns:repeat(2,2.5rem);justify-content:center;gap:.25rem;padding-inline:0}.gala-action-rail .gala-action-stats dd{justify-self:center}.gala-action-rail .gala-reaction-actions,.gala-action-rail .gala-utility-actions,.gala-action-rail .gala-share{grid-template-columns:repeat(2,2.5rem)}.gala-action-rail__separator{inline-size:auto;block-size:1px;margin:.25rem}.gala-action-rail--integrated{grid-column:2;grid-row:3;position:static;align-self:start;width:100%;margin:0;display:grid;grid-template-columns:minmax(0,1fr) 1px minmax(0,1fr);grid-template-rows:auto 1px auto;grid-template-areas:"reactions utility-separator utilities" "stats-separator stats-separator stats-separator" "stats stats stats";justify-content:stretch;gap:clamp(var(--gala-space-2xs),1vw,var(--gala-space-md));align-items:center;padding:var(--gala-space-md);border-radius:var(--gala-radius-surface)}.gala-action-rail--integrated .gala-action-rail__separator{grid-area:utility-separator;inline-size:1px;block-size:auto;margin:.25rem 0}.gala-action-rail--integrated .gala-action-rail__separator--stats{grid-area:stats-separator;inline-size:auto;block-size:1px;margin:0}.gala-action-rail--integrated .gala-action-group--reactions{grid-area:reactions}.gala-action-rail--integrated .gala-action-group--utilities{grid-area:utilities}.gala-action-rail--integrated .gala-reaction-actions{grid-template-columns:repeat(6,2.5rem)}.gala-action-rail--integrated .gala-utility-actions{grid-template-columns:repeat(4,2.5rem)}.gala-action-rail--integrated .gala-action-stats>div{min-block-size:2.5rem;display:flex;flex-direction:row;gap:.4rem;padding-inline:.25rem}.gala-action-rail--integrated .gala-action-stats{grid-area:stats;grid-template-columns:repeat(3,minmax(0,1fr))}}@media(max-width:74.99rem){.gala-action-rail{grid-template-columns:1fr;width:min(100%,42rem)}.gala-action-rail__separator{inline-size:auto;block-size:1px;margin:0}}@media(max-width:47.99rem){.gala-page-footer{flex-direction:column}.gala-page-footer__line{grid-template-columns:minmax(0,1fr);gap:var(--gala-space-2xs)}.gala-page-footer__line>*+*:before{content:none}.gala-page-footer nav{margin-inline-start:0}.gala-page-footer ul{justify-content:flex-start}.gala-site-name{flex:1 1 auto;min-inline-size:4rem;font-size:var(--gala-text-base)}.gala-header-link{display:none}.gala-site-header__actions{flex:0 0 auto;gap:.125rem}.gala-account-control{inline-size:2.75rem;padding:0;justify-content:center}.gala-account-control [data-user-label]{display:none}}
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
const page = document.querySelector('[data-version-page]');
|
|
3
3
|
if (!(page instanceof HTMLElement)) return;
|
|
4
4
|
const modules = Object.freeze([
|
|
5
|
-
{ id: 'app', name: 'Gala App', url: 'https://app.gala67.com/version.json'
|
|
6
|
-
{ id: 'api', name: 'Gala API', url: `${page.dataset.apiBaseUrl}/v1/version?siteId=${encodeURIComponent(page.dataset.siteId ?? '')}
|
|
7
|
-
{ id: 'cli', name: 'Gala CLI', url: 'https://registry.npmjs.org/%40rathnasgala%2Fcli/latest', repository: 'https://github.com/rathnasgala/cli' },
|
|
8
|
-
{ id: 'theme', name: 'Gala Site Template', url: 'https://registry.npmjs.org/%40rathnasgala%2Ftheme/latest', repository: 'https://github.com/rathnasgala/site-template' },
|
|
9
|
-
{ id: 'content-validation', name: 'Gala Content Validation', url: 'https://registry.npmjs.org/%40rathnasgala%2Fcontent-validation/latest', repository: 'https://github.com/rathnasgala/publish' }
|
|
5
|
+
{ id: 'app', name: 'Gala App', url: 'https://app.gala67.com/version.json' },
|
|
6
|
+
{ id: 'api', name: 'Gala API', url: `${page.dataset.apiBaseUrl}/v1/version?siteId=${encodeURIComponent(page.dataset.siteId ?? '')}` },
|
|
7
|
+
{ id: 'cli', name: 'Gala CLI', url: 'https://registry.npmjs.org/%40rathnasgala%2Fcli/latest', repository: 'https://github.com/rathnasgala/cli', repositoryName: 'rathnasgala/cli' },
|
|
8
|
+
{ id: 'theme', name: 'Gala Site Template', url: 'https://registry.npmjs.org/%40rathnasgala%2Ftheme/latest', repository: 'https://github.com/rathnasgala/site-template', repositoryName: 'rathnasgala/site-template' },
|
|
9
|
+
{ id: 'content-validation', name: 'Gala Content Validation', url: 'https://registry.npmjs.org/%40rathnasgala%2Fcontent-validation/latest', repository: 'https://github.com/rathnasgala/publish', repositoryName: 'rathnasgala/publish' }
|
|
10
10
|
]);
|
|
11
11
|
const commitPattern = /^[0-9a-f]{40}$/;
|
|
12
12
|
function exactCommit(value) {
|
|
@@ -31,23 +31,29 @@
|
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
33
|
const value = result.value;
|
|
34
|
-
const commit = exactCommit(value);
|
|
35
34
|
if (typeof value.version === 'string') {
|
|
36
35
|
const release = document.createElement('p');
|
|
37
36
|
release.textContent = `Release ${value.version}`;
|
|
38
37
|
card.append(release);
|
|
39
38
|
}
|
|
40
|
-
if (
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
if (module.repository) {
|
|
40
|
+
const repository = document.createElement('a');
|
|
41
|
+
repository.href = module.repository;
|
|
42
|
+
repository.textContent = module.repositoryName;
|
|
43
|
+
card.append(repository);
|
|
44
|
+
const commit = exactCommit(value);
|
|
45
|
+
if (commit) {
|
|
46
|
+
const link = document.createElement('a');
|
|
47
|
+
link.href = `${module.repository}/commit/${commit}`;
|
|
48
|
+
link.className = 'gala-version-commit';
|
|
49
|
+
link.textContent = commit.slice(0, 8);
|
|
50
|
+
card.append(link);
|
|
51
|
+
} else {
|
|
52
|
+
const status = document.createElement('p');
|
|
53
|
+
status.className = 'gala-version-status gala-version-status--error';
|
|
54
|
+
status.textContent = 'The live source did not report an exact commit.';
|
|
55
|
+
card.append(status);
|
|
56
|
+
}
|
|
51
57
|
}
|
|
52
58
|
if (typeof value.builtAt === 'string') {
|
|
53
59
|
const built = document.createElement('time');
|
|
@@ -12,7 +12,7 @@ const PROVIDER_HOST = /\.github\.io$/;
|
|
|
12
12
|
* The host this repository may claim as its own, or null.
|
|
13
13
|
*
|
|
14
14
|
* A site served under a path is served that way because GitHub gives it the domain held by the
|
|
15
|
-
* owner's main site
|
|
15
|
+
* owner's main site - https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/about-custom-domains-and-github-pages
|
|
16
16
|
* puts a project site at `www.octocat.com/octo-project`. GitHub reads this file from the
|
|
17
17
|
* published branch and treats it as *this* repository's own domain, which overrides that
|
|
18
18
|
* inheritance: the blog would move to the domain root and take the address away from the site
|
package/payload/src/feed.11ty.js
CHANGED
|
@@ -28,7 +28,7 @@ export default class LanguageFeeds {
|
|
|
28
28
|
});
|
|
29
29
|
return renderAtomFeed({
|
|
30
30
|
id: selfUrl,
|
|
31
|
-
title: `${site.site.name}
|
|
31
|
+
title: `${site.site.name} - ${language}`,
|
|
32
32
|
author: typeof site.site.author === 'string' && site.site.author.trim() !== ''
|
|
33
33
|
? site.site.author.trim()
|
|
34
34
|
: site.site.name,
|
package/payload/src/index.njk
CHANGED
|
@@ -22,9 +22,10 @@ export default class LanguageIndexes {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
render({ buildManifest, language, site }) {
|
|
25
|
-
const posts = buildManifest.posts.filter(
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
const posts = buildManifest.posts.filter((post) => (
|
|
26
|
+
post.publicationState === 'published'
|
|
27
|
+
|| (buildManifest.preview === true && post.publicationState === 'not-emitted')
|
|
28
|
+
) && post.language === language);
|
|
28
29
|
const items = posts.map((post) => {
|
|
29
30
|
const href = siteUrl({
|
|
30
31
|
canonicalBaseUrl: site.hosting.canonicalBaseUrl,
|
|
@@ -33,6 +34,6 @@ export default class LanguageIndexes {
|
|
|
33
34
|
});
|
|
34
35
|
return `<li><a href="${html(href)}">${html(post.frontmatter.title)}</a></li>`;
|
|
35
36
|
}).join('');
|
|
36
|
-
return `<h1>${html(site.site.name)}
|
|
37
|
+
return `<h1>${html(site.site.name)} - ${html(language)}</h1><ol>${items}</ol>`;
|
|
37
38
|
}
|
|
38
39
|
}
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
|
|
130
|
-
/* Two more, because colour is the choice a writer actually wants to make
|
|
130
|
+
/* Two more, because colour is the choice a writer actually wants to make - and the one that is
|
|
131
131
|
safe to offer, since a palette only swaps values every rule already reads and each is checked
|
|
132
132
|
for contrast in both modes. */
|
|
133
133
|
:root[data-palette='forest'] {
|
|
@@ -205,8 +205,8 @@
|
|
|
205
205
|
*
|
|
206
206
|
* Three, each designed whole.
|
|
207
207
|
*
|
|
208
|
-
* This replaced nine independent knobs
|
|
209
|
-
* palette
|
|
208
|
+
* This replaced nine independent knobs - typeface, spacing, density, radius, motion, surfaces,
|
|
209
|
+
* palette - which a writer set one at a time, blind, and only saw combined after a deploy. Nine
|
|
210
210
|
* knobs is several hundred looks, and nobody had seen most of them: `mono` + `round` + `spacious`
|
|
211
211
|
* + `raised` produced monospace headings inside page-wide pills, and it was reachable in four
|
|
212
212
|
* clicks. A look is a set of decisions that agree with each other, so it is chosen as a set.
|
|
@@ -254,7 +254,7 @@
|
|
|
254
254
|
--gala-surface-border: var(--gala-color-border-strong);
|
|
255
255
|
--gala-surface-shadow: none;
|
|
256
256
|
}
|
|
257
|
-
/* Mono is an accent here
|
|
257
|
+
/* Mono is an accent here - labels, counts, metadata - not a face to set a page in. */
|
|
258
258
|
:root[data-theme='technical'] .gala-eyebrow,
|
|
259
259
|
:root[data-theme='technical'] .gala-engagement dt,
|
|
260
260
|
:root[data-theme='technical'] .gala-comment__meta time {
|
|
@@ -350,7 +350,7 @@ body {
|
|
|
350
350
|
}
|
|
351
351
|
|
|
352
352
|
/* Headings carry the hierarchy, so they get the display face, a tighter measure and tighter
|
|
353
|
-
tracking
|
|
353
|
+
tracking - large type set at body tracking is the single clearest sign of an unconsidered page. */
|
|
354
354
|
:where(h1, h2, h3, h4) {
|
|
355
355
|
font-family: var(--gala-font-display);
|
|
356
356
|
font-weight: var(--gala-weight-display);
|
|
@@ -394,13 +394,15 @@ blockquote {
|
|
|
394
394
|
|
|
395
395
|
/*
|
|
396
396
|
* Stays with the reader. It used to scroll away, so on a long post the only way back to the site
|
|
397
|
-
*
|
|
397
|
+
* - or to sign in - was to scroll to the top. The negative inline margin lets the bar's background
|
|
398
398
|
* reach the edge of the viewport while its contents stay on the page's own measure.
|
|
399
399
|
*/
|
|
400
400
|
.gala-site-header {
|
|
401
401
|
position: sticky;
|
|
402
402
|
inset-block-start: 0;
|
|
403
403
|
z-index: 10;
|
|
404
|
+
box-sizing: border-box;
|
|
405
|
+
inline-size: 100%;
|
|
404
406
|
display: flex;
|
|
405
407
|
align-items: center;
|
|
406
408
|
justify-content: space-between;
|