@rathnasgala/theme 2.0.1 → 2.0.3

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.
@@ -22,8 +22,9 @@ function threads(items) {
22
22
  }
23
23
 
24
24
  function commentsController(root, endpoint) {
25
- const state = { items: [], cursor: null, total: 0, busy: false, status: '', loading: true };
25
+ const state = { items: [], cursor: null, total: 0, busy: false, status: '', phase: 'loading' };
26
26
  const articleId = /\/v1\/articles\/([^/]+)\/engagement/.exec(endpoint)?.[1] ?? '';
27
+ const statusNode = root.closest('.gala-conversation')?.querySelector('[data-engagement-status]');
27
28
 
28
29
  async function load(nextCursor = '', { append = false, fresh = false } = {}) {
29
30
  const url = new URL(endpoint);
@@ -110,23 +111,31 @@ function commentsController(root, endpoint) {
110
111
  }
111
112
 
112
113
  function render() {
114
+ if (statusNode) {
115
+ statusNode.textContent = state.phase === 'loading'
116
+ ? 'Loading comments…'
117
+ : state.phase === 'unavailable' ? 'Comments are temporarily unavailable.' : state.status;
118
+ }
119
+ if (state.phase !== 'ready') {
120
+ root.replaceChildren();
121
+ return;
122
+ }
113
123
  const section = element('section', 'gala-comments-island'); section.setAttribute('aria-label', 'Comments');
114
- section.append(element('h2', 'gala-comments__heading', state.total === 1 ? '1 comment' : `${state.total} comments`));
124
+ section.append(element('p', 'gala-comments__heading', state.total === 1 ? '1 comment' : `${state.total} comments`));
115
125
  if (sessionUser.value) section.append(form('Add a comment', '', (body) => write('comment.create', { articleId, body })));
116
126
  else {
117
127
  const prompt = element('p', 'gala-comments__prompt');
118
128
  const signIn = element('button', '', 'Sign in to join the conversation'); signIn.type = 'button';
119
129
  signIn.addEventListener('click', () => requestSignIn({ kind: 'comment' })); prompt.append(signIn); section.append(prompt);
120
130
  }
121
- if (state.status) { const status = element('p', 'gala-comments__status', state.status); status.role = 'status'; section.append(status); }
122
- if (state.loading) section.append(element('p', 'gala-comments__status', 'Loading comments…'));
123
- else if (!state.items.length) section.append(element('p', 'gala-comments__empty', 'No comments yet.'));
131
+ if (!state.items.length) section.append(element('p', 'gala-comments__empty', 'No comments yet.'));
124
132
  else { const list = element('ol', 'gala-comments'); list.append(...threads(state.items).map(commentNode)); section.append(list); }
125
133
  if (state.cursor) {
126
134
  const more = element('button', 'gala-comments__more', 'Show more comments'); more.type = 'button'; more.disabled = state.busy;
127
135
  more.addEventListener('click', async () => {
128
- state.busy = true; render();
129
- try { await load(state.cursor, { append: true }); } catch { state.status = 'Could not load more comments.'; }
136
+ state.busy = true; state.status = 'Loading more comments…'; render();
137
+ try { await load(state.cursor, { append: true }); state.status = ''; }
138
+ catch { state.status = 'More comments couldn’t be loaded. Try again.'; }
130
139
  state.busy = false; render();
131
140
  }); section.append(more);
132
141
  }
@@ -135,8 +144,9 @@ function commentsController(root, endpoint) {
135
144
 
136
145
  window.addEventListener('gala-session-change', render);
137
146
  render();
138
- load().catch(() => { state.status = 'Comments are temporarily unavailable.'; })
139
- .finally(() => { state.loading = false; render(); });
147
+ load().then(() => { state.phase = 'ready'; })
148
+ .catch(() => { state.phase = 'unavailable'; })
149
+ .finally(render);
140
150
  }
141
151
 
142
152
  document.querySelectorAll('[data-gala-comments]').forEach((root) => {
@@ -8,7 +8,99 @@ function selectableFallback(control) {
8
8
  if (status) status.textContent = 'Select and copy the URL shown.';
9
9
  }
10
10
 
11
+ const readingProgress = document.querySelector('[data-reading-progress]');
12
+ const readingContent = document.querySelector('.gala-markdown');
13
+ if (readingProgress && readingContent) {
14
+ let progressFrame = null;
15
+ const updateReadingProgress = () => {
16
+ progressFrame = null;
17
+ const start = readingContent.offsetTop;
18
+ const distance = Math.max(1, readingContent.offsetHeight - window.innerHeight);
19
+ const progress = Math.min(1, Math.max(0, (window.scrollY - start) / distance));
20
+ readingProgress.value = progress;
21
+ };
22
+ const scheduleReadingProgress = () => {
23
+ if (progressFrame == null) progressFrame = requestAnimationFrame(updateReadingProgress);
24
+ };
25
+ addEventListener('scroll', scheduleReadingProgress, { passive: true });
26
+ addEventListener('resize', scheduleReadingProgress, { passive: true });
27
+ scheduleReadingProgress();
28
+ }
29
+
30
+ const articleToc = document.querySelector('.gala-toc');
31
+ if (articleToc?.tagName === 'DETAILS') {
32
+ const tocNavigation = articleToc.querySelector('nav');
33
+ const tocLinks = [...articleToc.querySelectorAll('a[href^="#"]')];
34
+ const headings = tocLinks.map((link) => document.getElementById(link.hash.slice(1))).filter(Boolean);
35
+ let tocFrame = null;
36
+ const synchronizeToc = () => {
37
+ tocFrame = null;
38
+ articleToc.classList.toggle('gala-toc--floating', articleToc.getBoundingClientRect().top <= 96);
39
+ if (!headings.length) return;
40
+ const readingLine = window.innerHeight * 0.42;
41
+ let active = headings[0];
42
+ for (const heading of headings) {
43
+ if (heading.getBoundingClientRect().top <= readingLine) active = heading;
44
+ else break;
45
+ }
46
+ const activeLink = tocLinks.find((link) => link.hash === `#${active.id}`);
47
+ for (const link of tocLinks) {
48
+ if (link === activeLink) link.setAttribute('aria-current', 'location');
49
+ else link.removeAttribute('aria-current');
50
+ }
51
+ if (tocNavigation && tocLinks.length) {
52
+ const firstRect = tocLinks[0].getBoundingClientRect();
53
+ const lastRect = tocLinks[tocLinks.length - 1].getBoundingClientRect();
54
+ articleToc.classList.toggle('gala-toc--overflowing',
55
+ lastRect.bottom - firstRect.top > tocNavigation.clientHeight);
56
+ }
57
+ if (activeLink && tocNavigation && tocNavigation.scrollHeight > tocNavigation.clientHeight) {
58
+ const navigationRect = tocNavigation.getBoundingClientRect();
59
+ const activeRect = activeLink.getBoundingClientRect();
60
+ const target = tocNavigation.scrollTop + activeRect.top - navigationRect.top
61
+ - (tocNavigation.clientHeight - activeRect.height) / 2;
62
+ tocNavigation.scrollTo({ top: Math.max(0, target), behavior: 'auto' });
63
+ }
64
+ };
65
+ const scheduleToc = () => {
66
+ if (tocFrame == null) tocFrame = requestAnimationFrame(synchronizeToc);
67
+ };
68
+ addEventListener('scroll', scheduleToc, { passive: true });
69
+ addEventListener('resize', scheduleToc, { passive: true });
70
+ scheduleToc();
71
+ }
72
+
73
+ const actionRail = document.querySelector('.gala-action-rail');
74
+ const actionDock = document.querySelector('[data-action-dock]');
75
+ if (actionRail && actionDock) {
76
+ const synchronizeActionRail = (entries) => {
77
+ actionRail.classList.toggle('gala-action-rail--integrated', entries[0].isIntersecting);
78
+ };
79
+ new IntersectionObserver(synchronizeActionRail, {
80
+ rootMargin: '0px 0px 18% 0px', threshold: 0
81
+ }).observe(actionDock);
82
+ }
83
+
84
+ function presentFollowState(control, following) {
85
+ const label = following ? 'Unfollow article' : 'Follow article';
86
+ control.setAttribute('aria-pressed', String(following));
87
+ control.setAttribute('aria-label', label);
88
+ control.title = label;
89
+ const visibleLabel = control.querySelector('[data-follow-label]');
90
+ if (visibleLabel) visibleLabel.textContent = label;
91
+ }
92
+
11
93
  document.addEventListener('click', async (event) => {
94
+ if (event.target instanceof HTMLDialogElement && event.target.open) {
95
+ const bounds = event.target.getBoundingClientRect();
96
+ const outside = event.clientX < bounds.left || event.clientX > bounds.right
97
+ || event.clientY < bounds.top || event.clientY > bounds.bottom;
98
+ if (outside) {
99
+ event.target.close();
100
+ return;
101
+ }
102
+ }
103
+
12
104
  const embed = event.target.closest('[data-gala-embed-load]');
13
105
  if (embed) {
14
106
  const container = embed.closest('[data-gala-embed]');
@@ -66,8 +158,7 @@ document.addEventListener('click', async (event) => {
66
158
  targetType: 'articles', targetId: region.dataset.articleId
67
159
  });
68
160
  if (!saved) return;
69
- follow.setAttribute('aria-pressed', String(active));
70
- follow.textContent = active ? 'Unfollow article' : 'Follow article';
161
+ presentFollowState(follow, active);
71
162
  return;
72
163
  }
73
164
 
@@ -130,13 +221,6 @@ codeBlocks.forEach((pre) => {
130
221
  wrapper.prepend(control);
131
222
  });
132
223
 
133
- function textElement(name, text, className) {
134
- const element = document.createElement(name);
135
- element.textContent = text;
136
- if (className) element.className = className;
137
- return element;
138
- }
139
-
140
224
  function engagementCounts(data) {
141
225
  const reactionTotal = Object.values(data.reactions ?? {})
142
226
  .reduce((total, count) => total + (Number.isSafeInteger(count) ? count : 0), 0);
@@ -147,33 +231,22 @@ function engagementCounts(data) {
147
231
  ];
148
232
  }
149
233
 
150
- function renderEngagement(region, payload, appendComments = false) {
234
+ function publicCount(value) {
235
+ if (value < 1000) return '<1K';
236
+ return new Intl.NumberFormat('en', {
237
+ notation: 'compact', maximumFractionDigits: 1
238
+ }).format(value);
239
+ }
240
+
241
+ function renderEngagement(region, payload) {
151
242
  const data = payload?.data;
152
243
  if (!data || typeof data !== 'object') throw new TypeError('Engagement data is invalid');
153
- const live = region.querySelector('[data-engagement-live]');
154
- if (!live) return;
155
- if (!appendComments) live.replaceChildren();
156
-
157
- if (!appendComments && data.profile) {
158
- const profile = document.createElement('section');
159
- profile.className = 'gala-engagement-profile';
160
- profile.setAttribute('aria-label', 'Author profile');
161
- profile.append(textElement('h2', data.profile.displayName));
162
- if (data.profile.username) profile.append(textElement('p', `@${data.profile.username}`));
163
- if (Number.isSafeInteger(data.profile.followerCount)) {
164
- profile.append(textElement('p', `${data.profile.followerCount} followers`));
165
- }
166
- live.append(profile);
167
- }
168
-
169
- if (!appendComments) {
170
- const summary = document.createElement('dl');
171
- for (const [label, value] of engagementCounts(data)) {
172
- const item = document.createElement('div');
173
- item.append(textElement('dt', label), textElement('dd', String(value)));
174
- summary.append(item);
175
- }
176
- live.append(summary);
244
+ for (const [label, value] of engagementCounts(data)) {
245
+ region.querySelectorAll(`[data-engagement-stat="${label.toLowerCase()}"]`)
246
+ .forEach((count) => {
247
+ count.textContent = publicCount(value);
248
+ count.closest('div')?.setAttribute('title', `${value} ${label.toLowerCase()}`);
249
+ });
177
250
  }
178
251
 
179
252
  region.querySelector('[data-engagement-snapshot]')?.remove();
@@ -182,6 +255,7 @@ function renderEngagement(region, payload, appendComments = false) {
182
255
 
183
256
  async function refreshEngagement(region, commentsCursor = '', appendComments = false, fresh = false) {
184
257
  const status = region.querySelector('[data-engagement-status]');
258
+ const commentsOwnStatus = Boolean(region.querySelector('[data-gala-comments]'));
185
259
  try {
186
260
  const requestUrl = new URL(region.dataset.engagementUrl);
187
261
  if (commentsCursor) requestUrl.searchParams.set('commentsCursor', commentsCursor);
@@ -192,11 +266,11 @@ async function refreshEngagement(region, commentsCursor = '', appendComments = f
192
266
  });
193
267
  if (!response.ok) throw new Error(`Engagement returned HTTP ${response.status}`);
194
268
  const payload = await response.json();
195
- renderEngagement(region, payload, appendComments);
196
- if (status) status.textContent = payload.errors?.length
269
+ renderEngagement(region, payload);
270
+ if (status && !commentsOwnStatus) status.textContent = payload.errors?.length
197
271
  ? 'Some engagement data is temporarily unavailable.' : '';
198
272
  } catch {
199
- if (status) status.textContent = 'Live engagement data is temporarily unavailable.';
273
+ if (status && !commentsOwnStatus) status.textContent = 'Engagement is temporarily unavailable.';
200
274
  }
201
275
  }
202
276
 
@@ -424,9 +498,7 @@ if (sessionFrame) {
424
498
  });
425
499
  document.querySelectorAll('[data-follow-article]').forEach((control) => {
426
500
  const following = event.data.state?.following === true;
427
- control.setAttribute('aria-pressed', String(following));
428
- control.setAttribute('aria-label', following ? 'Unfollow article' : 'Follow article');
429
- control.title = following ? 'Unfollow article' : 'Follow article';
501
+ presentFollowState(control, following);
430
502
  });
431
503
  return;
432
504
  }
@@ -436,7 +508,7 @@ if (sessionFrame) {
436
508
  if (event.data?.type === 'gala-session-height') {
437
509
  const height = Number(event.data.height);
438
510
  if (Number.isFinite(height) && height > 0 && height < 2000) {
439
- sessionFrame.style.height = `${Math.ceil(height)}px`;
511
+ sessionFrame.height = String(Math.ceil(height));
440
512
  }
441
513
  return;
442
514
  }
@@ -1,2 +1,2 @@
1
- (()=>{var w=["system","light","dark"],O="gala-color-mode";function D(){let e=document.documentElement.dataset.mode;return w.includes(e)?e:"system"}function Y(){try{let e=localStorage.getItem(O);if(w.includes(e))return e}catch{return D()}return D()}function k(e){document.documentElement.dataset.mode=e;let t=w[(w.indexOf(e)+1)%w.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",()=>{k(Y()),document.querySelectorAll("[data-theme-mode-toggle]").forEach(e=>{e.addEventListener("click",()=>{let t=w[(w.indexOf(document.documentElement.dataset.mode)+1)%w.length];try{localStorage.setItem(O,t)}catch{}k(t)})})});var P="gala-language-preference";function B(){try{return localStorage.getItem(P)}catch{return null}}function M(e,t){return[...e.options].find(a=>a.value===t)}document.addEventListener("DOMContentLoaded",()=>{let e=B();document.querySelectorAll("[data-language-preference]").forEach(t=>{e!=null&&M(t,e)!=null&&(t.value=e),t.addEventListener("change",()=>{try{localStorage.setItem(P,t.value)}catch{}if(!t.hasAttribute("data-navigate-on-selection"))return;let a=M(t,t.value);a?.dataset.url&&window.location.assign(a.dataset.url)})})});function _(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.")}document.addEventListener("click",async e=>{let t=e.target.closest("[data-gala-embed-load]");if(t){let r=t.closest("[data-gala-embed]"),i=t.dataset.galaEmbedSrc,u=t.dataset.galaEmbedLoad;if(!r||!i||!["youtube","codepen"].includes(u))return;let c=document.createElement("iframe");c.src=i,c.title=u==="youtube"?"YouTube video":"CodePen example",c.loading="eager",c.referrerPolicy="strict-origin-when-cross-origin",c.setAttribute("sandbox","allow-forms allow-popups allow-presentation allow-same-origin allow-scripts"),c.setAttribute("allow",u==="youtube"?"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture; web-share":"fullscreen"),c.setAttribute("allowfullscreen",""),r.replaceChildren(c);return}let a=e.target.closest("[data-open-dialog]");if(a){let r=document.getElementById(a.dataset.openDialog);r instanceof HTMLDialogElement&&r.showModal();return}let n=e.target.closest("[data-reaction]");if(n){let r=n.closest("[data-engagement-url]");if(!r)return;if(!y)return q({kind:"reaction",region:r,selector:`[data-reaction="${n.dataset.reaction}"]`});let i=n.getAttribute("aria-pressed")!=="true";if(!await F(r,i?"reaction.add":"reaction.remove",{articleId:r.dataset.articleId,reaction:n.dataset.reaction}))return;n.setAttribute("aria-pressed",String(i));return}let s=e.target.closest("[data-follow-article]");if(s){let r=s.closest("[data-engagement-url]");if(!r)return;if(!y)return q({kind:"follow",region:r,selector:"[data-follow-article]"});let i=s.getAttribute("aria-pressed")!=="true";if(!await F(r,i?"follow.add":"follow.remove",{targetType:"articles",targetId:r.dataset.articleId}))return;s.setAttribute("aria-pressed",String(i)),s.textContent=i?"Unfollow article":"Follow article";return}let o=e.target.closest("[data-copy-url]");if(o){let r=o.dataset.copyUrl;if(!window.isSecureContext||!navigator.clipboard?.writeText){_(o);return}try{await navigator.clipboard.writeText(r);let i=o.closest(".gala-share")?.querySelector(".gala-share__status");i&&(i.textContent="Link copied.")}catch{_(o)}return}let l=e.target.closest("[data-native-share]");if(l){if(navigator.share)try{await navigator.share({title:document.title,url:l.dataset.nativeShare})}catch(r){r.name!=="AbortError"&&_(l)}else _(l);return}let p=e.target.closest("[data-copy-code]");if(!p)return;let E=p.closest(".gala-code-block")?.querySelector("code");if(!(!E||!navigator.clipboard?.writeText||!window.isSecureContext))try{await navigator.clipboard.writeText(E.textContent),p.textContent="Copied"}catch{p.textContent="Select code to copy"}});var K=new Set([...document.querySelectorAll("pre code")].map(e=>e.closest("pre")).filter(Boolean));K.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 C(e,t,a){let n=document.createElement(e);return n.textContent=t,a&&(n.className=a),n}function z(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 J(e,t,a=!1){let n=t?.data;if(!n||typeof n!="object")throw new TypeError("Engagement data is invalid");let s=e.querySelector("[data-engagement-live]");if(s){if(a||s.replaceChildren(),!a&&n.profile){let o=document.createElement("section");o.className="gala-engagement-profile",o.setAttribute("aria-label","Author profile"),o.append(C("h2",n.profile.displayName)),n.profile.username&&o.append(C("p",`@${n.profile.username}`)),Number.isSafeInteger(n.profile.followerCount)&&o.append(C("p",`${n.profile.followerCount} followers`)),s.append(o)}if(!a){let o=document.createElement("dl");for(let[l,p]of z(n)){let E=document.createElement("div");E.append(C("dt",l),C("dd",String(p))),o.append(E)}s.append(o)}e.querySelector("[data-engagement-snapshot]")?.remove(),e.querySelector(".gala-engagement__placeholder")?.remove()}}async function U(e,t="",a=!1,n=!1){let s=e.querySelector("[data-engagement-status]");try{let o=new URL(e.dataset.engagementUrl);t&&o.searchParams.set("commentsCursor",t);let l=await fetch(o,{headers:{Accept:"application/json"},credentials:"omit",cache:n?"no-store":"default"});if(!l.ok)throw new Error(`Engagement returned HTTP ${l.status}`);let p=await l.json();J(e,p,a),s&&(s.textContent=p.errors?.length?"Some engagement data is temporarily unavailable.":"")}catch{s&&(s.textContent="Live engagement data is temporarily unavailable.")}}function X(){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 Z(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:X()}),credentials:"omit",keepalive:!0}).catch(()=>{})}document.addEventListener("gala-request-sign-in",e=>{y||b||q({kind:e.detail?.kind??"comment"})});var y=null,G=null,N=null,v=new Map,b=null;function q(e){let t=e.field;b={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")||(b=null,n&&(n.textContent="Allow pop-ups for this site to sign in, then try again."))}function $(){let e=b;if(!e||!document.hasFocus())return;let t=e.region?.querySelector(e.selector);if(t){if(b=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",()=>{y&&b&&$()});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&&t.contentWindow?.postMessage({type:"gala-session-recheck"},a)});document.querySelectorAll("[data-engagement-url]").forEach(e=>{U(e),Z(e)});var I=document.querySelector("[data-gala-session-frame]");function j(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 W(e,t){if(!I||!y)return Promise.reject(new Error("AUTHENTICATION_REQUIRED"));let a=crypto.randomUUID();return new Promise((n,s)=>{let o=setTimeout(()=>{v.delete(a),s(new Error("REQUEST_TIMEOUT"))},1e4);v.set(a,{resolve:l=>{clearTimeout(o),n(l)},reject:l=>{clearTimeout(o),s(l)}}),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(!y){n.textContent="Sign in with the account button before sending.";return}let s=new FormData(t);try{n.textContent="Sending\u2026",await W("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=j(o.message)}});async function F(e,t,a){let n=e.querySelector("[data-engagement-status]");try{return n&&(n.textContent="Saving\u2026"),await W(t,a),n&&(n.textContent="Saved."),await U(e,"",!1,!0),!0}catch(s){return n&&(n.textContent=j(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=v.get(t.data.requestId);if(!o)return;v.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!==N||t.data.ok!==!0)return;N=null;let o=new Set(Array.isArray(t.data.state?.reactions)?t.data.state.reactions:[]);document.querySelectorAll("[data-reaction]").forEach(l=>{l.setAttribute("aria-pressed",String(o.has(l.dataset.reaction)))}),document.querySelectorAll("[data-follow-article]").forEach(l=>{let p=t.data.state?.following===!0;l.setAttribute("aria-pressed",String(p)),l.setAttribute("aria-label",p?"Unfollow article":"Follow article"),l.title=p?"Unfollow article":"Follow article"});return}if(t.data?.type==="gala-session-height"){let o=Number(t.data.height);Number.isFinite(o)&&o>0&&o<2e3&&(I.style.height=`${Math.ceil(o)}px`);return}if(t.data?.type!=="gala-session")return;y=t.data.user&&typeof t.data.user.id=="string"?t.data.user:null;let a=document.querySelector("[data-user-control]"),n=y?.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=G!==(y?.id??null);if(G=y?.id??null,y){let o=document.querySelector("[data-article-id]");o&&(N=crypto.randomUUID(),I.contentWindow.postMessage({type:"gala-reader-state-request",requestId:N,articleId:o.dataset.articleId},e))}document.querySelectorAll("[data-engagement-url]").forEach(o=>{s&&U(o)}),y&&b&&$()})}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,l,p=u=>String(u??"").normalize("NFKC").toLocaleLowerCase();function E(u,c){s.replaceChildren();for(let m of u){let d=document.createElement("li"),g=document.createElement("article"),h=document.createElement("h2"),A=document.createElement("a");if(A.href=m.url,A.lang=m.language,A.textContent=m.title,h.append(A),g.append(h),m.description){let x=document.createElement("p");x.textContent=m.description,g.append(x)}d.append(g),s.append(d)}n.textContent=`${u.length} result${u.length===1?"":"s"} for \u201C${c}\u201D.`}async function r(u){let c=u.trim();if(c===""){s.replaceChildren(),n.textContent="Enter a search term.";return}n.textContent="Searching\u2026";try{l??=fetch(o,{headers:{Accept:"application/json"}}).then(async g=>{if(!g.ok)throw new Error(`Search index returned HTTP ${g.status}`);let h=await g.json();if(h?.schemaVersion!==1||!Array.isArray(h.entries))throw new TypeError("Search index schema is unsupported");return h.entries});let m=p(c),d=(await l).filter(g=>p([g.title,g.description,g.language,...Array.isArray(g.tags)?g.tags:[],g.body].join(`
2
- `)).includes(m));E(d,c)}catch{s.replaceChildren(),n.textContent="Search is temporarily unavailable."}}t?.addEventListener("submit",u=>{u.preventDefault();let c=a.value,m=new URL(window.location.href);c.trim()===""?m.searchParams.delete("q"):m.searchParams.set("q",c),window.history.replaceState(null,"",m),r(c)});let i=new URLSearchParams(window.location.search).get("q")??"";a&&(a.value=i),r(i)});var T={value:null},S=document.querySelector("[data-gala-session-frame]"),H=S?new URL(S.src).origin:null,L=new Map;function V(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 Q(e,t){if(!S||!T.value)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:l=>{clearTimeout(o),n(l)},reject:l=>{clearTimeout(o),s(l)}}),S.contentWindow.postMessage({type:"gala-engagement-write",requestId:a,operation:e,payload:t},H)})}function R(e){document.dispatchEvent(new CustomEvent("gala-request-sign-in",{detail:e}))}S&&window.addEventListener("message",e=>{if(e.origin!==H||e.source!==S.contentWindow)return;if(e.data?.type==="gala-engagement-result"){let a=L.get(e.data.requestId);if(!a)return;L.delete(e.data.requestId),e.data.ok===!0?a.resolve(e.data.result):a.reject(new Error(e.data.error?.code||"ENGAGEMENT_WRITE_FAILED"));return}if(e.data?.type!=="gala-session")return;let t=e.data.user;T.value=t&&typeof t.id=="string"?t:null,window.dispatchEvent(new CustomEvent("gala-session-change"))});var ee=5,f=(e,t,a)=>{let n=document.createElement(e);return t&&(n.className=t),a!==void 0&&(n.textContent=a),n};function te(e){let t=new Map;for(let n of e){let s=n.parentCommentId??"";t.has(s)||t.set(s,[]),t.get(s).push(n)}let a=(n,s)=>(t.get(n)??[]).map(o=>({...o,depth:s,replies:a(o.commentId,s+1)}));return a("",0)}function ae(e,t){let a={items:[],cursor:null,total:0,busy:!1,status:"",loading:!0},n=/\/v1\/articles\/([^/]+)\/engagement/.exec(t)?.[1]??"";async function s(r="",{append:i=!1,fresh:u=!1}={}){let c=new URL(t);r&&c.searchParams.set("commentsCursor",r);let m=await fetch(c,{headers:{Accept:"application/json"},credentials:"omit",cache:u?"no-store":"default"});if(!m.ok)throw new Error(`Comments returned HTTP ${m.status}`);let d=(await m.json())?.data?.comments;if(!d||!Array.isArray(d.items))throw new TypeError("Comment page is invalid");a.items=i?[...a.items,...d.items]:d.items,a.cursor=d.nextCursor??null,Number.isSafeInteger(d.totalCount)&&(a.total=d.totalCount)}async function o(r,i){if(!T.value)return R({kind:"comment"}),!1;a.busy=!0,a.status="",E();try{return await Q(r,i),await s("",{fresh:!0}),!0}catch(u){return a.status=V(u.message),!1}finally{a.busy=!1,E()}}function l(r,i,u){let c=f("form","gala-comment__form"),m=f("textarea");m.rows=3,m.value=i,m.placeholder=r,m.setAttribute("aria-label",r);let d=f("button","","Post");return d.type="submit",d.disabled=a.busy,c.append(m,d),c.addEventListener("submit",async g=>{g.preventDefault();let h=m.value.trim();h&&await u(h)&&(m.value="")}),c}function p(r){let i=f("li","gala-comment");i.dataset.commentId=r.commentId,i.dataset.depth=r.depth;let u=f("p","gala-comment__meta");if(u.append(f("strong","",r.author?.displayName??"[deleted]")),r.createdAt){let c=f("time","",new Date(r.createdAt).toLocaleDateString());c.dateTime=r.createdAt,u.append(c)}if(i.append(u,f("p","gala-comment__body",r.deleted?"[deleted]":r.body)),!r.deleted){let c=f("div","gala-comment-actions");if(r.depth<ee){let d=f("button","","Reply");d.type="button",d.dataset.replyComment=r.commentId,d.addEventListener("click",()=>{let g=i.querySelector(":scope > .gala-comment__form");if(g){g.remove();return}c.after(l(`Reply to ${r.author?.displayName??"this comment"}`,"",h=>o("comment.create",{articleId:n,parentCommentId:r.commentId,body:h})))}),c.append(d)}if(T.value&&r.author?.userId===T.value.id){let d=f("button","","Edit");d.type="button",d.dataset.editComment=r.commentId,d.addEventListener("click",()=>c.after(l("Edit your comment",r.body??"",h=>o("comment.edit",{articleId:n,commentId:r.commentId,body:h}))));let g=f("button","","Delete");g.type="button",g.dataset.deleteComment=r.commentId,g.addEventListener("click",()=>o("comment.delete",{articleId:n,commentId:r.commentId})),c.append(d,g)}else{let d=f("button","","Report");d.type="button",d.dataset.reportComment=r.commentId,d.addEventListener("click",()=>o("comment.report",{articleId:n,commentId:r.commentId,reason:"OTHER"})),c.append(d)}i.append(c)}if(r.replies.length){let c=f("ol","gala-comment-replies");c.append(...r.replies.map(p)),i.append(c)}return i}function E(){let r=f("section","gala-comments-island");if(r.setAttribute("aria-label","Comments"),r.append(f("h2","gala-comments__heading",a.total===1?"1 comment":`${a.total} comments`)),T.value)r.append(l("Add a comment","",i=>o("comment.create",{articleId:n,body:i})));else{let i=f("p","gala-comments__prompt"),u=f("button","","Sign in to join the conversation");u.type="button",u.addEventListener("click",()=>R({kind:"comment"})),i.append(u),r.append(i)}if(a.status){let i=f("p","gala-comments__status",a.status);i.role="status",r.append(i)}if(a.loading)r.append(f("p","gala-comments__status","Loading comments\u2026"));else if(!a.items.length)r.append(f("p","gala-comments__empty","No comments yet."));else{let i=f("ol","gala-comments");i.append(...te(a.items).map(p)),r.append(i)}if(a.cursor){let i=f("button","gala-comments__more","Show more comments");i.type="button",i.disabled=a.busy,i.addEventListener("click",async()=>{a.busy=!0,E();try{await s(a.cursor,{append:!0})}catch{a.status="Could not load more comments."}a.busy=!1,E()}),r.append(i)}e.replaceChildren(r)}window.addEventListener("gala-session-change",E),E(),s().catch(()=>{a.status="Comments are temporarily unavailable."}).finally(()=>{a.loading=!1,E()})}document.querySelectorAll("[data-gala-comments]").forEach(e=>{let t=e.closest("[data-engagement-url]")?.dataset.engagementUrl;t&&ae(e,t)});})();
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 J(){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(n=>{let a=n.querySelector?.("[data-theme-mode-label]");a?a.textContent=`Theme: ${e}`:n.textContent=`Theme: ${e}`,n.setAttribute("aria-label",`Color mode: ${e}. Activate for ${t}.`)})}document.addEventListener("DOMContentLoaded",()=>{M(J()),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 Z(){try{return localStorage.getItem(F)}catch{return null}}function P(e,t){return[...e.options].find(n=>n.value===t)}document.addEventListener("DOMContentLoaded",()=>{let e=Z();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 n=P(t,t.value);n?.dataset.url&&window.location.assign(n.dataset.url)})})});function v(e){let t=e.closest(".gala-share"),n=t?.querySelector(".gala-share__fallback");n?.classList.add("gala-share__fallback--visible"),n?.focus(),n?.select();let a=t?.querySelector(".gala-share__status");a&&(a.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 a=q.offsetTop,s=Math.max(1,q.offsetHeight-window.innerHeight),o=Math.min(1,Math.max(0,(window.scrollY-a)/s));G.value=o},n=()=>{e==null&&(e=requestAnimationFrame(t))};addEventListener("scroll",n,{passive:!0}),addEventListener("resize",n,{passive:!0}),n()}var C=document.querySelector(".gala-toc");if(C?.tagName==="DETAILS"){let e=C.querySelector("nav"),t=[...C.querySelectorAll('a[href^="#"]')],n=t.map(d=>document.getElementById(d.hash.slice(1))).filter(Boolean),a=null,s=()=>{if(a=null,C.classList.toggle("gala-toc--floating",C.getBoundingClientRect().top<=96),!n.length)return;let d=window.innerHeight*.42,f=n[0];for(let i of n)if(i.getBoundingClientRect().top<=d)f=i;else break;let E=t.find(i=>i.hash===`#${f.id}`);for(let i of t)i===E?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(E&&e&&e.scrollHeight>e.clientHeight){let i=e.getBoundingClientRect(),r=E.getBoundingClientRect(),c=e.scrollTop+r.top-i.top-(e.clientHeight-r.height)/2;e.scrollTo({top:Math.max(0,c),behavior:"auto"})}},o=()=>{a==null&&(a=requestAnimationFrame(s))};addEventListener("scroll",o,{passive:!0}),addEventListener("resize",o,{passive:!0}),o()}var H=document.querySelector(".gala-action-rail"),$=document.querySelector("[data-action-dock]");if(H&&$){let e=t=>{H.classList.toggle("gala-action-rail--integrated",t[0].isIntersecting)};new IntersectionObserver(e,{rootMargin:"0px 0px 18% 0px",threshold:0}).observe($)}function W(e,t){let n=t?"Unfollow article":"Follow article";e.setAttribute("aria-pressed",String(t)),e.setAttribute("aria-label",n),e.title=n;let a=e.querySelector("[data-follow-label]");a&&(a.textContent=n)}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 n=e.target.closest("[data-open-dialog]");if(n){let i=document.getElementById(n.dataset.openDialog);i instanceof HTMLDialogElement&&i.showModal();return}let a=e.target.closest("[data-reaction]");if(a){let i=a.closest("[data-engagement-url]");if(!i)return;if(!y)return R({kind:"reaction",region:i,selector:`[data-reaction="${a.dataset.reaction}"]`});let r=a.getAttribute("aria-pressed")!=="true";if(!await B(i,r?"reaction.add":"reaction.remove",{articleId:i.dataset.articleId,reaction:a.dataset.reaction}))return;a.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(!y)return R({kind:"follow",region:i,selector:"[data-follow-article]"});let r=s.getAttribute("aria-pressed")!=="true";if(!await B(i,r?"follow.add":"follow.remove",{targetType:"articles",targetId:i.dataset.articleId}))return;W(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 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 ee=new Set([...document.querySelectorAll("pre code")].map(e=>e.closest("pre")).filter(Boolean));ee.forEach(e=>{let t=document.createElement("div");t.className="gala-code-block",e.before(t),t.append(e);let n=document.createElement("button");n.type="button",n.dataset.copyCode="",n.textContent="Copy code",n.setAttribute("aria-label","Copy code block"),t.prepend(n)});function te(e){return[["Reactions",Object.values(e.reactions??{}).reduce((n,a)=>n+(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 ne(e){return e<1e3?"<1K":new Intl.NumberFormat("en",{notation:"compact",maximumFractionDigits:1}).format(e)}function ae(e,t){let n=t?.data;if(!n||typeof n!="object")throw new TypeError("Engagement data is invalid");for(let[a,s]of te(n))e.querySelectorAll(`[data-engagement-stat="${a.toLowerCase()}"]`).forEach(o=>{o.textContent=ne(s),o.closest("div")?.setAttribute("title",`${s} ${a.toLowerCase()}`)});e.querySelector("[data-engagement-snapshot]")?.remove(),e.querySelector(".gala-engagement__placeholder")?.remove()}async function x(e,t="",n=!1,a=!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:a?"no-store":"default"});if(!f.ok)throw new Error(`Engagement returned HTTP ${f.status}`);let E=await f.json();ae(e,E),s&&!o&&(s.textContent=E.errors?.length?"Some engagement data is temporarily unavailable.":"")}catch{s&&!o&&(s.textContent="Engagement is temporarily unavailable.")}}function re(){let e=new URL(window.location.href).searchParams,t={};for(let n of["utm_source","utm_medium","utm_campaign","utm_content","utm_term"]){let a=e.get(n);a&&[...a].length<=128&&(t[n]=a)}return t}function oe(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:re()}),credentials:"omit",keepalive:!0}).catch(()=>{})}document.addEventListener("gala-request-sign-in",e=>{y||T||R({kind:e.detail?.kind??"comment"})});var y=null,j=null,_=null,L=new Map,T=null;function R(e){let t=e.field;T={kind:e.kind,region:e.region,selector:e.selector,draft:t?t.value:null,caret:t?t.selectionStart:null};let n=document.querySelector("[data-gala-session-frame]"),a=document.querySelector("[data-engagement-status]");if(!n)return;let s=new URL(n.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")||(T=null,a&&(a.textContent="Allow pop-ups for this site to sign in, then try again."))}function Y(){let e=T;if(!e||!document.hasFocus())return;let t=e.region?.querySelector(e.selector);if(t){if(T=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",()=>{y&&T&&Y()});window.addEventListener("message",e=>{let t=document.querySelector("[data-gala-session-frame]");if(!t||e.data?.type!=="gala-session-established")return;let n=new URL(t.src).origin;e.origin===n&&t.contentWindow?.postMessage({type:"gala-session-recheck"},n)});document.querySelectorAll("[data-engagement-url]").forEach(e=>{x(e),oe(e)});var I=document.querySelector("[data-gala-session-frame]");function V(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 Q(e,t){if(!I||!y)return Promise.reject(new Error("AUTHENTICATION_REQUIRED"));let n=crypto.randomUUID();return new Promise((a,s)=>{let o=setTimeout(()=>{L.delete(n),s(new Error("REQUEST_TIMEOUT"))},1e4);L.set(n,{resolve:d=>{clearTimeout(o),a(d)},reject:d=>{clearTimeout(o),s(d)}}),I.contentWindow.postMessage({type:"gala-engagement-write",requestId:n,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 n=t.closest("[data-contact-form]"),a=n.querySelector("[data-contact-status]");if(!y){a.textContent="Sign in with the account button before sending.";return}let s=new FormData(t);try{a.textContent="Sending\u2026",await Q("contact.submit",{siteId:n.dataset.siteId,subject:s.get("subject"),message:s.get("message"),website:s.get("website")||null,phone:s.get("phone")||null}),t.reset(),a.textContent="Message sent."}catch(o){a.textContent=V(o.message)}});async function B(e,t,n){let a=e.querySelector("[data-engagement-status]");try{return a&&(a.textContent="Saving\u2026"),await Q(t,n),a&&(a.textContent="Saved."),await x(e,"",!1,!0),!0}catch(s){return a&&(a.textContent=V(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;W(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;y=t.data.user&&typeof t.data.user.id=="string"?t.data.user:null;let n=document.querySelector("[data-user-control]"),a=y?.displayName;if(n){n.setAttribute("aria-label",a?`Account: ${a}`:"Sign in or view account"),n.title=a?`Account: ${a}`:"Account";let o=n.querySelector("[data-user-label]");o&&(o.textContent=a||"Sign in")}let s=j!==(y?.id??null);if(j=y?.id??null,y){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)}),y&&T&&Y()})}document.querySelectorAll("[data-gala-search]").forEach(e=>{let t=e.querySelector("form"),n=t?.elements.namedItem("q"),a=e.querySelector("[data-search-status]"),s=e.querySelector("[data-search-results]"),o=e.dataset.indexUrl,d,f=c=>String(c??"").normalize("NFKC").toLocaleLowerCase();function E(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)}a.textContent=`${c.length} result${c.length===1?"":"s"} for \u201C${u}\u201D.`}async function i(c){let u=c.trim();if(u===""){s.replaceChildren(),a.textContent="Enter a search term.";return}a.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(m));E(p,u)}catch{s.replaceChildren(),a.textContent="Search is temporarily unavailable."}}t?.addEventListener("submit",c=>{c.preventDefault();let u=n.value,m=new URL(window.location.href);u.trim()===""?m.searchParams.delete("q"):m.searchParams.set("q",u),window.history.replaceState(null,"",m),i(u)});let r=new URLSearchParams(window.location.search).get("q")??"";n&&(n.value=r),i(r)});var S={value:null},A=document.querySelector("[data-gala-session-frame]"),z=A?new URL(A.src).origin:null,N=new Map;function K(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 X(e,t){if(!A||!S.value)return Promise.reject(new Error("AUTHENTICATION_REQUIRED"));let n=crypto.randomUUID();return new Promise((a,s)=>{let o=setTimeout(()=>{N.delete(n),s(new Error("REQUEST_TIMEOUT"))},1e4);N.set(n,{resolve:d=>{clearTimeout(o),a(d)},reject:d=>{clearTimeout(o),s(d)}}),A.contentWindow.postMessage({type:"gala-engagement-write",requestId:n,operation:e,payload:t},z)})}function U(e){document.dispatchEvent(new CustomEvent("gala-request-sign-in",{detail:e}))}A&&window.addEventListener("message",e=>{if(e.origin!==z||e.source!==A.contentWindow)return;if(e.data?.type==="gala-engagement-result"){let n=N.get(e.data.requestId);if(!n)return;N.delete(e.data.requestId),e.data.ok===!0?n.resolve(e.data.result):n.reject(new Error(e.data.error?.code||"ENGAGEMENT_WRITE_FAILED"));return}if(e.data?.type!=="gala-session")return;let t=e.data.user;S.value=t&&typeof t.id=="string"?t:null,window.dispatchEvent(new CustomEvent("gala-session-change"))});var se=5,g=(e,t,n)=>{let a=document.createElement(e);return t&&(a.className=t),n!==void 0&&(a.textContent=n),a};function ie(e){let t=new Map;for(let a of e){let s=a.parentCommentId??"";t.has(s)||t.set(s,[]),t.get(s).push(a)}let n=(a,s)=>(t.get(a)??[]).map(o=>({...o,depth:s,replies:n(o.commentId,s+1)}));return n("",0)}function ce(e,t){let n={items:[],cursor:null,total:0,busy:!1,status:"",phase:"loading"},a=/\/v1\/articles\/([^/]+)\/engagement/.exec(t)?.[1]??"",s=e.closest(".gala-conversation")?.querySelector("[data-engagement-status]");async function o(r="",{append:c=!1,fresh:u=!1}={}){let m=new URL(t);r&&m.searchParams.set("commentsCursor",r);let p=await fetch(m,{headers:{Accept:"application/json"},credentials:"omit",cache:u?"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");n.items=c?[...n.items,...l.items]:l.items,n.cursor=l.nextCursor??null,Number.isSafeInteger(l.totalCount)&&(n.total=l.totalCount)}async function d(r,c){if(!S.value)return U({kind:"comment"}),!1;n.busy=!0,n.status="",i();try{return await X(r,c),await o("",{fresh:!0}),!0}catch(u){return n.status=K(u.message),!1}finally{n.busy=!1,i()}}function f(r,c,u){let m=g("form","gala-comment__form"),p=g("textarea");p.rows=3,p.value=c,p.placeholder=r,p.setAttribute("aria-label",r);let l=g("button","","Post");return l.type="submit",l.disabled=n.busy,m.append(p,l),m.addEventListener("submit",async h=>{h.preventDefault();let w=p.value.trim();w&&await u(w)&&(p.value="")}),m}function E(r){let c=g("li","gala-comment");c.dataset.commentId=r.commentId,c.dataset.depth=r.depth;let u=g("p","gala-comment__meta");if(u.append(g("strong","",r.author?.displayName??"[deleted]")),r.createdAt){let m=g("time","",new Date(r.createdAt).toLocaleDateString());m.dateTime=r.createdAt,u.append(m)}if(c.append(u,g("p","gala-comment__body",r.deleted?"[deleted]":r.body)),!r.deleted){let m=g("div","gala-comment-actions");if(r.depth<se){let l=g("button","","Reply");l.type="button",l.dataset.replyComment=r.commentId,l.addEventListener("click",()=>{let h=c.querySelector(":scope > .gala-comment__form");if(h){h.remove();return}m.after(f(`Reply to ${r.author?.displayName??"this comment"}`,"",w=>d("comment.create",{articleId:a,parentCommentId:r.commentId,body:w})))}),m.append(l)}if(S.value&&r.author?.userId===S.value.id){let l=g("button","","Edit");l.type="button",l.dataset.editComment=r.commentId,l.addEventListener("click",()=>m.after(f("Edit your comment",r.body??"",w=>d("comment.edit",{articleId:a,commentId:r.commentId,body:w}))));let h=g("button","","Delete");h.type="button",h.dataset.deleteComment=r.commentId,h.addEventListener("click",()=>d("comment.delete",{articleId:a,commentId:r.commentId})),m.append(l,h)}else{let l=g("button","","Report");l.type="button",l.dataset.reportComment=r.commentId,l.addEventListener("click",()=>d("comment.report",{articleId:a,commentId:r.commentId,reason:"OTHER"})),m.append(l)}c.append(m)}if(r.replies.length){let m=g("ol","gala-comment-replies");m.append(...r.replies.map(E)),c.append(m)}return c}function i(){if(s&&(s.textContent=n.phase==="loading"?"Loading comments\u2026":n.phase==="unavailable"?"Comments are temporarily unavailable.":n.status),n.phase!=="ready"){e.replaceChildren();return}let r=g("section","gala-comments-island");if(r.setAttribute("aria-label","Comments"),r.append(g("p","gala-comments__heading",n.total===1?"1 comment":`${n.total} comments`)),S.value)r.append(f("Add a comment","",c=>d("comment.create",{articleId:a,body:c})));else{let c=g("p","gala-comments__prompt"),u=g("button","","Sign in to join the conversation");u.type="button",u.addEventListener("click",()=>U({kind:"comment"})),c.append(u),r.append(c)}if(!n.items.length)r.append(g("p","gala-comments__empty","No comments yet."));else{let c=g("ol","gala-comments");c.append(...ie(n.items).map(E)),r.append(c)}if(n.cursor){let c=g("button","gala-comments__more","Show more comments");c.type="button",c.disabled=n.busy,c.addEventListener("click",async()=>{n.busy=!0,n.status="Loading more comments\u2026",i();try{await o(n.cursor,{append:!0}),n.status=""}catch{n.status="More comments couldn\u2019t be loaded. Try again."}n.busy=!1,i()}),r.append(c)}e.replaceChildren(r)}window.addEventListener("gala-session-change",i),i(),o().then(()=>{n.phase="ready"}).catch(()=>{n.phase="unavailable"}).finally(i)}document.querySelectorAll("[data-gala-comments]").forEach(e=>{let t=e.closest("[data-engagement-url]")?.dataset.engagementUrl;t&&ce(e,t)});})();
@@ -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}*,*:before,*:after{animation-duration:1ms!important;transition-duration:1ms!important}}:root[data-layout=portfolio]{--gala-content-width: 82rem}body{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}.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-article-header{padding-block:clamp(3rem,9vw,8rem)}.gala-article-header h1{max-inline-size:12ch;font-size:clamp(3.25rem,9vw,7.5rem);line-height:.92;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(img,video){max-inline-size:100%;block-size:auto}@media(max-width:47.99rem){.gala-header-link{display:none}.gala-site-header__actions{gap:.25rem}}: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;gap:.5rem}.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__close{justify-self:end;margin:0}.gala-dialog>h2{margin-block:0}main,.gala-page-footer{max-inline-size:var(--gala-content-width);margin-inline:auto}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}[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{padding:1rem;background:var(--gala-color-surface)}.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{margin-block-start:4rem;padding-block-start:1rem;border-block-start:.125rem solid var(--gala-color-border)}.gala-error-page{min-block-size:60vh;display:grid;align-content:center;justify-items:start}.gala-code-block{position:relative}.gala-code-block>button{position:absolute;inset-block-start:.5rem;inset-inline-end:.5rem;background:var(--gala-color-background)}.gala-code-block pre{overflow:auto;padding:3.5rem 1rem 1rem;background:var(--gala-color-surface);border-radius:var(--gala-radius-control)}.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:var(--shiki-light);background-color:var(--shiki-light-bg)}:root[data-mode=dark] .shiki,:root[data-mode=dark] .shiki span{color:var(--shiki-dark);background-color:var(--shiki-dark-bg)}@media(prefers-color-scheme:dark){:root[data-mode=system] .shiki,:root[data-mode=system] .shiki span{color:var(--shiki-dark);background-color:var(--shiki-dark-bg)}}.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 dt{font-size:var(--gala-text-xs);letter-spacing:var(--gala-tracking-wide);text-transform:uppercase;color:var(--gala-color-muted)}.gala-engagement 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;margin-block-start:clamp(4rem,9vw,8rem)}.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-action-rail{position:sticky;z-index:4;inset-block-start:6rem;float:left;width:3.25rem;margin-inline-start:max(-5rem,calc((var(--gala-measure) - 100%) / 2 - 4rem));display:grid;gap:.5rem;padding:.5rem;border:1px solid var(--gala-color-hairline);border-radius:999px;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.25rem;min-block-size:2.25rem;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 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-action-rail .gala-reaction-actions,.gala-action-rail .gala-share{display:contents}.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}@media(max-width:54rem){.gala-action-rail{inset-block-start:auto;inset-block-end:1rem;float:none;box-sizing:border-box;width:calc(100% - 2rem);max-width:calc(100% - 2rem);margin:0 auto;grid-auto-flow:column;overflow-x:auto}.gala-conversation{padding-block-start:var(--gala-space-xl)}}
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{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}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}[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(3,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 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 .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}}
@@ -31,6 +31,7 @@ export default class ValidatedPostPages {
31
31
  postTableOfContents: ({ post }) => post?.publicationState === 'published'
32
32
  ? renderedDocument(post).tableOfContents
33
33
  : [],
34
+ readingMinutes: ({ post }) => readingMinutes(post?.body),
34
35
  seo: ({ post, site }) => post?.publicationState === 'published'
35
36
  ? postSeo({ post, site, renderedHtml: renderedDocument(post).html })
36
37
  : null
@@ -46,6 +47,11 @@ export default class ValidatedPostPages {
46
47
  }
47
48
  }
48
49
 
50
+ function readingMinutes(markdown) {
51
+ const words = String(markdown ?? '').trim().match(/[\p{L}\p{N}]+(?:['’_-][\p{L}\p{N}]+)*/gu)?.length ?? 0;
52
+ return Math.max(1, Math.ceil(words / 225));
53
+ }
54
+
49
55
  const renderedDocuments = new WeakMap();
50
56
 
51
57
  function renderedDocument(post) {