@jant/core 0.3.50 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/{app-C7CtIQM-.js → app-DQgkp6yV.js} +155 -130
  2. package/dist/app-DYJLFZaM.js +6 -0
  3. package/dist/client/.vite/manifest.json +3 -3
  4. package/dist/client/_assets/client-BbJ0FhON.css +2 -0
  5. package/dist/client/_assets/client-DqsPJKiP.js +272 -0
  6. package/dist/client/_assets/{client-auth-Ce5WEAVS.js → client-auth-N6fiJcOg.js} +82 -82
  7. package/dist/{export-ZBlfKSKm.js → export-DwH3ga3Y.js} +2 -2
  8. package/dist/{github-sync-C593r22F.js → github-sync-D2FO19Re.js} +2 -2
  9. package/dist/{github-sync-bL1hnx3Q.js → github-sync-eHOTYZGO.js} +1 -1
  10. package/dist/index.js +3 -3
  11. package/dist/node.js +4 -4
  12. package/package.json +1 -1
  13. package/src/client/__tests__/compose-shortcuts.test.ts +1 -4
  14. package/src/client/components/__tests__/jant-compose-dialog.test.ts +1 -1
  15. package/src/client/components/__tests__/jant-media-lightbox.test.ts +89 -0
  16. package/src/client/components/compose-types.ts +6 -1
  17. package/src/client/components/jant-compose-dialog.ts +2 -0
  18. package/src/client/components/jant-compose-editor.ts +2 -1
  19. package/src/client/components/jant-media-lightbox.ts +33 -10
  20. package/src/client/compose-bridge.ts +88 -25
  21. package/src/client/compose-launch.ts +0 -13
  22. package/src/client/palette-search-trigger.ts +35 -0
  23. package/src/client/thread-context.ts +1 -140
  24. package/src/client/upload-session.ts +77 -31
  25. package/src/client-auth.ts +1 -0
  26. package/src/i18n/locales/public/en.po +0 -4
  27. package/src/i18n/locales/public/zh-Hans.po +0 -4
  28. package/src/i18n/locales/public/zh-Hant.po +0 -4
  29. package/src/lib/__tests__/hosted-domain.test.ts +1 -1
  30. package/src/lib/hosted-domain-check.ts +21 -80
  31. package/src/lib/hosted-domain.ts +1 -1
  32. package/src/routes/api/internal/__tests__/sites.test.ts +168 -0
  33. package/src/routes/api/internal/sites.ts +63 -10
  34. package/src/routes/hosted/__tests__/domain-check.test.ts +30 -19
  35. package/src/routes/hosted/domain-check.ts +9 -14
  36. package/src/services/export-theme/assets/client-site.js +1 -1
  37. package/src/services/site-admin.ts +62 -1
  38. package/src/styles/tokens.css +0 -1
  39. package/src/styles/ui.css +0 -71
  40. package/src/ui/feed/ThreadPreview.tsx +34 -65
  41. package/src/ui/feed/__tests__/thread-preview.test.ts +64 -58
  42. package/src/ui/feed/thread-preview-state.ts +0 -48
  43. package/dist/app-CIx9SSOi.js +0 -6
  44. package/dist/client/_assets/client-BoUn7xBo.css +0 -2
  45. package/dist/client/_assets/client-dSfWfMe9.js +0 -272
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import { Hono } from "hono";
3
3
  import { errorHandler } from "../../../middleware/error-handler.js";
4
- import { verifyHostedDomainCheckToken } from "../../../lib/hosted-domain-check.js";
4
+ import { computeHostedVerificationToken } from "../../../lib/hosted-domain-check.js";
5
5
  import { hostedDomainCheckRoutes } from "../domain-check.js";
6
6
  import type { Bindings } from "../../../types.js";
7
7
  import type { AppVariables } from "../../../types/app-context.js";
@@ -42,12 +42,12 @@ function createHostedDomainCheckTestApp(options?: {
42
42
  return app;
43
43
  }
44
44
 
45
- describe("hostedDomainCheckRoutes", () => {
45
+ describe("hostedDomainCheckRoutes (jant-verification)", () => {
46
46
  it("returns 404 when the domain check secret is not configured", async () => {
47
47
  const app = createHostedDomainCheckTestApp();
48
48
 
49
49
  const response = await app.request(
50
- "/.well-known/jant-domain-check?nonce=test-nonce",
50
+ "/.well-known/jant-verification?nonce=test-nonce",
51
51
  );
52
52
 
53
53
  expect(response.status).toBe(404);
@@ -59,36 +59,47 @@ describe("hostedDomainCheckRoutes", () => {
59
59
  secret: "cloud-domain-check-secret-cloud-domain-check-secret",
60
60
  });
61
61
 
62
- const response = await app.request("/.well-known/jant-domain-check");
62
+ const response = await app.request("/.well-known/jant-verification");
63
63
 
64
64
  expect(response.status).toBe(400);
65
- await expect(response.json()).resolves.toEqual({
66
- error: "Missing nonce.",
67
- });
65
+ await expect(response.text()).resolves.toBe("Missing nonce.");
68
66
  });
69
67
 
70
- it("returns a signed token for the current hosted domain", async () => {
68
+ it("returns the plaintext HMAC token for the current hosted domain", async () => {
71
69
  const secret = "cloud-domain-check-secret-cloud-domain-check-secret";
70
+ const host = "blog.example.com";
71
+ const nonce = "test-nonce";
72
72
  const app = createHostedDomainCheckTestApp({
73
73
  domainId: "sdom_custom",
74
- host: "blog.example.com",
74
+ host,
75
75
  secret,
76
76
  });
77
77
 
78
78
  const response = await app.request(
79
- "/.well-known/jant-domain-check?nonce=test-nonce",
79
+ `/.well-known/jant-verification?nonce=${nonce}`,
80
80
  );
81
- const body = (await response.json()) as { token: string };
82
- const claims = await verifyHostedDomainCheckToken(secret, body.token);
81
+ const body = (await response.text()).trim();
82
+ const expected = await computeHostedVerificationToken(secret, host, nonce);
83
83
 
84
84
  expect(response.status).toBe(200);
85
85
  expect(response.headers.get("cache-control")).toBe("no-store");
86
- expect(claims).toMatchObject({
87
- aud: "jant-cloud",
88
- domainId: "sdom_custom",
89
- host: "blog.example.com",
90
- iss: "jant-core",
91
- nonce: "test-nonce",
92
- });
86
+ expect(response.headers.get("content-type")).toMatch(/^text\/plain/);
87
+ expect(body).toBe(`jant-verification=${expected}`);
88
+ });
89
+
90
+ it("normalizes host casing when computing the token", async () => {
91
+ const secret = "cloud-domain-check-secret-cloud-domain-check-secret";
92
+ const nonce = "abc";
93
+ const lower = await computeHostedVerificationToken(
94
+ secret,
95
+ "blog.example.com",
96
+ nonce,
97
+ );
98
+ const mixed = await computeHostedVerificationToken(
99
+ secret,
100
+ "Blog.Example.COM",
101
+ nonce,
102
+ );
103
+ expect(lower).toBe(mixed);
93
104
  });
94
105
  });
@@ -1,6 +1,6 @@
1
1
  import { Hono } from "hono";
2
2
  import { getHostedControlPlaneDomainCheckSecret } from "../../lib/env.js";
3
- import { signHostedDomainCheckToken } from "../../lib/hosted-domain-check.js";
3
+ import { computeHostedVerificationToken } from "../../lib/hosted-domain-check.js";
4
4
  import { NotFoundError } from "../../lib/errors.js";
5
5
  import type { Bindings } from "../../types.js";
6
6
  import type { AppVariables } from "../../types/app-context.js";
@@ -9,31 +9,26 @@ type Env = { Bindings: Bindings; Variables: AppVariables };
9
9
 
10
10
  export const hostedDomainCheckRoutes = new Hono<Env>();
11
11
 
12
- hostedDomainCheckRoutes.get("/.well-known/jant-domain-check", async (c) => {
12
+ hostedDomainCheckRoutes.get("/.well-known/jant-verification", async (c) => {
13
13
  const secret = getHostedControlPlaneDomainCheckSecret(c.env);
14
14
  if (!secret) {
15
- throw new NotFoundError("Hosted domain check endpoint");
15
+ throw new NotFoundError("Hosted domain verification endpoint");
16
16
  }
17
17
 
18
18
  if (!c.var.currentSiteDomain) {
19
- throw new NotFoundError("Hosted domain check endpoint");
19
+ throw new NotFoundError("Hosted domain verification endpoint");
20
20
  }
21
21
 
22
22
  const nonce = c.req.query("nonce")?.trim();
23
23
  if (!nonce) {
24
- return c.json({ error: "Missing nonce." }, 400);
24
+ return c.text("Missing nonce.", 400);
25
25
  }
26
26
 
27
- const token = await signHostedDomainCheckToken(secret, {
28
- aud: "jant-cloud",
29
- domainId: c.var.currentSiteDomain.id,
30
- host: c.var.currentSiteDomain.host.trim().toLowerCase(),
31
- iat: Math.floor(Date.now() / 1000),
32
- iss: "jant-core",
33
- nonce,
34
- });
27
+ const host = c.var.currentSiteDomain.host.trim().toLowerCase();
28
+ const token = await computeHostedVerificationToken(secret, host, nonce);
35
29
 
36
- return c.json({ token }, 200, {
30
+ return c.text(`jant-verification=${token}\n`, 200, {
37
31
  "Cache-Control": "no-store",
32
+ "Content-Type": "text/plain; charset=utf-8",
38
33
  });
39
34
  });
@@ -1,5 +1,5 @@
1
1
  var e=null,t=0,n=!1,r=null,i=null,a=0;function o(e){return!isFinite(e)||e<0?`0:00`:`${Math.floor(e/60)}:${String(Math.floor(e%60)).padStart(2,`0`)}`}function s(e){return e.querySelector(`audio.media-audio-el`)}function c(e){return e.querySelector(`[data-audio-range]`)}function l(e){return e.querySelector(`[data-audio-waveform]`)}function u(e,t){let n=`${(t*100).toFixed(1)}%`;e.style.background=`linear-gradient(to right, var(--site-text-primary) ${n}, transparent ${n})`}var d=new WeakMap,f=new WeakSet;async function p(e,t){let n=await(await fetch(e)).arrayBuffer(),r=new AudioContext;try{let e=(await r.decodeAudioData(n)).getChannelData(0),i=Math.max(1,Math.floor(e.length/t)),a=Array(t);for(let n=0;n<t;n++){let t=0,r=n*i,o=Math.min(r+i,e.length);for(let n=r;n<o;n++){let r=Math.abs(e[n]);r>t&&(t=r)}a[n]=t}let o=0;for(let e of a)e>o&&(o=e);if(o>0)for(let e=0;e<t;e++)a[e]/=o;return a}finally{await r.close()}}function m(){let e=document.querySelectorAll(`[data-audio-peaks]`),t=[];for(let n of e){let e=n.dataset.audioPeaks;if(!e)continue;let r=n.closest(`.media-audio-card`);if(!(!r||d.has(r)))try{let n=JSON.parse(e);if(!Array.isArray(n))continue;d.set(r,n),r.classList.add(`has-waveform`),t.push(r)}catch{}}t.length>0&&requestAnimationFrame(()=>{for(let e of t)g(e,0)})}async function h(e){if(d.has(e)||f.has(e))return;f.add(e);let t=e.querySelector(`audio.media-audio-el source`),n=l(e);if(!t?.src||!n)return;let r=n.getBoundingClientRect().width,i=Math.max(20,Math.floor(r/3));try{let n=await p(t.src,i);d.set(e,n),e.classList.add(`has-waveform`);let r=s(e),a=r?.duration??0;g(e,r&&isFinite(a)&&a>0?r.currentTime/a:0)}catch{}}function g(e,t){let n=d.get(e),r=l(e);if(!n||!r)return;let i=window.devicePixelRatio||1,a=r.getBoundingClientRect(),o=Math.round(a.width*i),s=Math.round(a.height*i);if(o===0||s===0)return;(r.width!==o||r.height!==s)&&(r.width=o,r.height=s);let c=r.getContext(`2d`);if(!c)return;c.clearRect(0,0,o,s);let u=n.length,f=o/u,p=Math.max(1,Math.round(f*.6)),m=Math.round(2*i),h=s*.85,g=getComputedStyle(r).getPropertyValue(`--site-text-primary`).trim()||`#000`;for(let e=0;e<u;e++){let r=Math.round(e*f+(f-p)/2),a=Math.max(m,Math.round(n[e]*h)),o=Math.round((s-a)/2);c.globalAlpha=(e+.5)/u<=t?.9:.2,c.fillStyle=g;let l=Math.min(p/2,i);c.beginPath(),c.roundRect(r,o,p,a,l),c.fill()}c.globalAlpha=1}function ee(e,t){let{currentTime:r,duration:i}=t,a=isFinite(i)&&i>0,s=a?r/i:0;if(!n){let t=c(e);t&&a&&(t.value=String(Math.round(s*1e3)),u(t,s)),d.has(e)&&g(e,s);let n=e.querySelector(`[data-audio-time]`);n&&(n.textContent=a?`${o(r)} / ${o(i)}`:o(r))}}function _(){if(!e)return;let n=s(e);!n||n.paused||(ee(e,n),t=requestAnimationFrame(_))}function te(){if(e){let t=s(e);t&&!t.paused&&t.pause(),e.classList.remove(`is-playing`),e=null}cancelAnimationFrame(t)}async function ne(n){let r=s(n);if(r)if(e&&e!==n&&te(),r.paused){e=n,n.classList.add(`is-playing`);try{await r.play()}catch{n.classList.remove(`is-playing`),e=null;return}t=requestAnimationFrame(_),h(n)}else r.pause(),n.classList.remove(`is-playing`),cancelAnimationFrame(t),e=null}function re(e){let t=e.closest(`.media-audio-card`);if(!t)return;let n=s(t);if(!n)return;let r=Number(e.value)/1e3,i=n.duration;isFinite(i)&&i>0&&(n.currentTime=r*i)}function ie(e,t){let n=e.getBoundingClientRect();a=Math.max(0,Math.min(1,(t.clientX-n.left)/n.width));let r=e.closest(`.media-audio-card`);if(!r)return;g(r,a);let i=s(r),c=r.querySelector(`[data-audio-time]`);if(i&&c){let e=i.duration;isFinite(e)&&e>0&&(c.textContent=`${o(a*e)} / ${o(e)}`)}}async function ae(n){let r=n.closest(`.media-audio-card`);if(!r)return;let i=s(r);if(i)if(i.paused){e&&e!==r&&te(),e=r,r.classList.add(`is-playing`);try{await i.play()}catch{r.classList.remove(`is-playing`),e=null;return}let n=i.duration;isFinite(n)&&n>0&&(i.currentTime=a*n),t=requestAnimationFrame(_),h(r)}else{let e=i.duration;isFinite(e)&&e>0&&(i.currentTime=a*e)}}document.addEventListener(`pointerdown`,e=>{let t=e.target;t.matches(`[data-audio-range]`)?(n=!0,r=t):t.matches(`[data-audio-waveform]`)&&(n=!0,i=t,ie(t,e))},!0),document.addEventListener(`pointermove`,e=>{n&&i&&ie(i,e)},!0),document.addEventListener(`pointerup`,()=>{n&&(r?(re(r),r=null):i&&=(ae(i),null)),n=!1},!0),document.addEventListener(`pointercancel`,()=>{r=null,i=null,n=!1},!0),document.addEventListener(`click`,e=>{let t=e.target.closest(`[data-audio-play]`);if(!t)return;e.preventDefault();let n=t.closest(`.media-audio-card`);n&&ne(n)}),document.addEventListener(`input`,e=>{let t=e.target;if(!t.matches(`[data-audio-range]`))return;let n=t.closest(`.media-audio-card`);if(!n)return;let r=s(n);if(!r)return;let i=Number(t.value)/1e3;u(t,i);let a=r.duration,c=n.querySelector(`[data-audio-time]`);c&&isFinite(a)&&a>0&&(c.textContent=`${o(i*a)} / ${o(a)}`)},!0),document.addEventListener(`ended`,n=>{let r=n.target;if(!r.closest)return;let i=r.closest(`.media-audio-card`);if(!i)return;i.classList.remove(`is-playing`),cancelAnimationFrame(t),e=null;let a=c(i);a&&(a.value=`0`,u(a,0)),d.has(i)&&g(i,0);let o=i.querySelector(`[data-audio-time]`);o&&(o.textContent=`0:00`)},!0),document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,m):m();var v=`jant:media-lightbox-toggle`,oe=`75% 0px`,se=.6,y=.25,ce=160,b=new Set,le=new WeakMap,ue=new WeakSet,x=null,S=null,C=null,de=!1,w=null,T=null,E=null;function fe(e){return e.length===0?null:[...e].sort((e,t)=>t.visibleArea===e.visibleArea?e.centerDistance===t.centerDistance?t.intersectionRatio-e.intersectionRatio:e.centerDistance-t.centerDistance:t.visibleArea-e.visibleArea)[0]??null}function pe(){return{x:(globalThis.innerWidth||document.documentElement.clientWidth||0)/2,y:(globalThis.innerHeight||document.documentElement.clientHeight||0)/2}}function me(e){return le.get(e)}function he(e){return e.closest(`.media-video-wrap`)?.querySelector(`[data-feed-video-mute-toggle]`)??null}function D(e){let t=he(e);if(!t)return;let n=C!==e||e.muted;t.dataset.muted=n?`true`:`false`,t.setAttribute(`aria-label`,n?`Play with sound`:`Mute video`)}function ge(){for(let e of b)D(e)}function _e(e){if(ue.has(e))return;let t=e.dataset.videoSrc;t&&(e.getAttribute(`src`)!==t&&(e.src=t),e.load(),ue.add(e))}function O(e){e?.pause()}function ve(e){_e(e),e.muted=C!==e,e.playsInline=!0,e.loop=!0,D(e),e.play().catch(()=>{})}function ye(){for(let e of b)e.isConnected||(T?.unobserve(e),E?.unobserve(e),b.delete(e),e===x&&(x=null),e===S&&(S=null),e===C&&(C=null))}function be(){if(w=null,ye(),document.hidden||de){O(x);return}let e=[];for(let t of b){let n=me(t);n&&(n.intersectionRatio<se||e.push({video:t,...n}))}let t=null;if(S?.isConnected){let e=me(S);e&&e.intersectionRatio>y?t=S:(!e||e.intersectionRatio<=y)&&(S=null)}if(t||=fe(e)?.video??null,!t){let e=x?me(x):void 0;if(x&&e&&e.intersectionRatio>y)return;O(x),x=null;return}t!==x&&(O(x),x=t),ve(t);for(let e of b)e!==t&&(O(e),D(e))}function k(){w!==null&&globalThis.clearTimeout(w),w=globalThis.setTimeout(be,ce)}function xe(e){let t=pe();for(let n of e){let e=n.target,r=n.boundingClientRect,i=r.left+r.width/2,a=r.top+r.height/2,o=n.intersectionRect.width*n.intersectionRect.height,s=Math.hypot(i-t.x,a-t.y);le.set(e,{intersectionRatio:n.intersectionRatio,visibleArea:o,centerDistance:s})}k()}function Se(e){for(let t of e)t.isIntersecting&&_e(t.target)}function Ce(){T&&E||globalThis.IntersectionObserver!==void 0&&(T=new globalThis.IntersectionObserver(xe,{threshold:[0,y,se,1]}),E=new globalThis.IntersectionObserver(Se,{rootMargin:oe,threshold:0}))}function we(e){b.has(e)||(Ce(),!(!T||!E)&&(b.add(e),T.observe(e),E.observe(e),he(e)?.addEventListener(`click`,Te),D(e)))}function Te(e){e.preventDefault(),e.stopPropagation();let t=e.currentTarget.closest(`.media-video-wrap`)?.querySelector(`[data-feed-short-video]`);t&&(S=t,C!==t||t.muted?(C&&C!==t&&(C.muted=!0),C=t,x!==t&&(O(x),x=t),ve(t)):(C=null,t.muted=!0,D(t)),ge(),k())}function Ee(e=document){let t=e.querySelectorAll(`[data-feed-short-video]`);for(let e of t)we(e);k()}document.addEventListener(v,e=>{de=e.detail?.open===!0,de&&O(x),k()}),document.addEventListener(`visibilitychange`,()=>{document.hidden&&O(x),k()}),document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,()=>Ee(),{once:!0}):queueMicrotask(()=>Ee());var De=4;function Oe(e){let t=e.querySelector(`[data-post-media]`);if(!t)return;let{scrollLeft:n,scrollWidth:r,clientWidth:i}=t;e.classList.toggle(`can-scroll-start`,n>De),e.classList.toggle(`can-scroll-end`,n+i<r-De)}function A(e){let t=e.querySelector(`[data-post-media]`);t&&(Oe(e),t.addEventListener(`scroll`,()=>Oe(e),{passive:!0}))}function ke(){document.querySelectorAll(`.media-gallery-scroll-wrap`).forEach(A)}var Ae=new globalThis.MutationObserver(e=>{for(let t of e)for(let e of t.addedNodes)e instanceof HTMLElement&&(e.matches(`.media-gallery-scroll-wrap`)&&A(e),e.querySelectorAll(`.media-gallery-scroll-wrap`).forEach(A))});document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,()=>{ke(),Ae.observe(document.body,{childList:!0,subtree:!0})}):(ke(),Ae.observe(document.body,{childList:!0,subtree:!0}));function je(e){let t=e.querySelector(`.site-header-more-btn`),n=e.querySelector(`.site-header-more-popover`);if(!t||!n||t.dataset.moreInitialized===`true`)return;t.dataset.moreInitialized=`true`;function r(){n.setAttribute(`aria-hidden`,`false`),t.setAttribute(`aria-expanded`,`true`),document.dispatchEvent(new CustomEvent(`basecoat:popover`,{detail:{source:t.parentElement}}))}function i(e=!1){n.setAttribute(`aria-hidden`,`true`),t.setAttribute(`aria-expanded`,`false`),e&&t.focus()}t.addEventListener(`click`,e=>{e.preventDefault(),e.stopPropagation(),t.getAttribute(`aria-expanded`)===`true`?i():r()}),document.addEventListener(`click`,e=>{e.target instanceof Node&&(t.parentElement?.contains(e.target)||i())}),document.addEventListener(`keydown`,e=>{e.key===`Escape`&&n.getAttribute(`aria-hidden`)===`false`&&i(!0)}),document.addEventListener(`basecoat:popover`,e=>{e.detail?.source!==t.parentElement&&i()})}var Me=`jant:nav-fresh-visits`;function Ne(){let e=document.createElement(`span`);return e.className=`site-header-link-fresh`,e.setAttribute(`aria-hidden`,`true`),e.textContent=`*`,e}function Pe(e){try{let t=JSON.parse(localStorage.getItem(Me)||`{}`),n=location.pathname,r=e.querySelectorAll(`[data-fresh-at]`);for(let e of r){let r=new URL(e.href).pathname,i=parseInt(e.dataset.freshAt,10);if(r===n)t[r]=Math.floor(Date.now()/1e3);else{let n=t[r];if(!n||n<i){let t=e.querySelector(`svg`);e.insertBefore(Ne(),t)}}}localStorage.setItem(Me,JSON.stringify(t))}catch{}}function Fe(e=document){let t=e.querySelector(`.site-header-hamburger`),n=e.querySelector(`#site-nav-drawer`),r=e.querySelector(`.site-nav-drawer-backdrop`),i=n?.querySelector(`.site-nav-drawer-close`);if(Pe(e),je(e),!t||!n||!r||t.dataset.drawerInitialized===`true`)return;t.dataset.drawerInitialized=`true`;function a(){n.setAttribute(`aria-hidden`,`false`),n.removeAttribute(`inert`),r.setAttribute(`aria-hidden`,`false`),t.setAttribute(`aria-expanded`,`true`),document.documentElement.classList.add(`drawer-open`);let e=n.querySelector(`.site-nav-drawer-close`)??n.querySelector(`a[href], button`);e&&e.focus()}function o(e=!0){n.setAttribute(`aria-hidden`,`true`),r.setAttribute(`aria-hidden`,`true`),t.setAttribute(`aria-expanded`,`false`),document.documentElement.classList.remove(`drawer-open`),n.addEventListener(`transitionend`,()=>{n.getAttribute(`aria-hidden`)===`true`&&n.setAttribute(`inert`,``)},{once:!0}),e&&t.focus()}t.addEventListener(`click`,()=>{t.getAttribute(`aria-expanded`)===`true`?o():a()}),i?.addEventListener(`click`,()=>o()),r.addEventListener(`click`,()=>o()),n.addEventListener(`click`,e=>{e.target instanceof Element&&e.target.closest(`a[href]`)&&o(!1)}),n.addEventListener(`keydown`,e=>{e.key===`Escape`&&(e.preventDefault(),o())})}Fe();var j=globalThis,M=j.ShadowRoot&&(j.ShadyCSS===void 0||j.ShadyCSS.nativeShadow)&&`adoptedStyleSheets`in Document.prototype&&`replace`in CSSStyleSheet.prototype,Ie=Symbol(),Le=new WeakMap,Re=class{constructor(e,t,n){if(this._$cssResult$=!0,n!==Ie)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o,t=this.t;if(M&&e===void 0){let n=t!==void 0&&t.length===1;n&&(e=Le.get(t)),e===void 0&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),n&&Le.set(t,e))}return e}toString(){return this.cssText}},ze=e=>new Re(typeof e==`string`?e:e+``,void 0,Ie),Be=(e,t)=>{if(M)e.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(let n of t){let t=document.createElement(`style`),r=j.litNonce;r!==void 0&&t.setAttribute(`nonce`,r),t.textContent=n.cssText,e.appendChild(t)}},Ve=M?e=>e:e=>e instanceof CSSStyleSheet?(e=>{let t=``;for(let n of e.cssRules)t+=n.cssText;return ze(t)})(e):e,{is:He,defineProperty:Ue,getOwnPropertyDescriptor:We,getOwnPropertyNames:Ge,getOwnPropertySymbols:Ke,getPrototypeOf:qe}=Object,N=globalThis,Je=N.trustedTypes,Ye=Je?Je.emptyScript:``,Xe=N.reactiveElementPolyfillSupport,P=(e,t)=>e,F={toAttribute(e,t){switch(t){case Boolean:e=e?Ye:null;break;case Object:case Array:e=e==null?e:JSON.stringify(e)}return e},fromAttribute(e,t){let n=e;switch(t){case Boolean:n=e!==null;break;case Number:n=e===null?null:Number(e);break;case Object:case Array:try{n=JSON.parse(e)}catch{n=null}}return n}},Ze=(e,t)=>!He(e,t),Qe={attribute:!0,type:String,converter:F,reflect:!1,useDefault:!1,hasChanged:Ze};Symbol.metadata??=Symbol(`metadata`),N.litPropertyMetadata??=new WeakMap;var I=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=Qe){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){let n=Symbol(),r=this.getPropertyDescriptor(e,n,t);r!==void 0&&Ue(this.prototype,e,r)}}static getPropertyDescriptor(e,t,n){let{get:r,set:i}=We(this.prototype,e)??{get(){return this[t]},set(e){this[t]=e}};return{get:r,set(t){let a=r?.call(this);i?.call(this,t),this.requestUpdate(e,a,n)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??Qe}static _$Ei(){if(this.hasOwnProperty(P(`elementProperties`)))return;let e=qe(this);e.finalize(),e.l!==void 0&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(P(`finalized`)))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(P(`properties`))){let e=this.properties,t=[...Ge(e),...Ke(e)];for(let n of t)this.createProperty(n,e[n])}let e=this[Symbol.metadata];if(e!==null){let t=litPropertyMetadata.get(e);if(t!==void 0)for(let[e,n]of t)this.elementProperties.set(e,n)}this._$Eh=new Map;for(let[e,t]of this.elementProperties){let n=this._$Eu(e,t);n!==void 0&&this._$Eh.set(n,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){let t=[];if(Array.isArray(e)){let n=new Set(e.flat(1/0).reverse());for(let e of n)t.unshift(Ve(e))}else e!==void 0&&t.push(Ve(e));return t}static _$Eu(e,t){let n=t.attribute;return!1===n?void 0:typeof n==`string`?n:typeof e==`string`?e.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),this.renderRoot!==void 0&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){let e=new Map,t=this.constructor.elementProperties;for(let n of t.keys())this.hasOwnProperty(n)&&(e.set(n,this[n]),delete this[n]);e.size>0&&(this._$Ep=e)}createRenderRoot(){let e=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return Be(e,this.constructor.elementStyles),e}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,t,n){this._$AK(e,n)}_$ET(e,t){let n=this.constructor.elementProperties.get(e),r=this.constructor._$Eu(e,n);if(r!==void 0&&!0===n.reflect){let i=(n.converter?.toAttribute===void 0?F:n.converter).toAttribute(t,n.type);this._$Em=e,i==null?this.removeAttribute(r):this.setAttribute(r,i),this._$Em=null}}_$AK(e,t){let n=this.constructor,r=n._$Eh.get(e);if(r!==void 0&&this._$Em!==r){let e=n.getPropertyOptions(r),i=typeof e.converter==`function`?{fromAttribute:e.converter}:e.converter?.fromAttribute===void 0?F:e.converter;this._$Em=r;let a=i.fromAttribute(t,e.type);this[r]=a??this._$Ej?.get(r)??a,this._$Em=null}}requestUpdate(e,t,n,r=!1,i){if(e!==void 0){let a=this.constructor;if(!1===r&&(i=this[e]),n??=a.getPropertyOptions(e),!((n.hasChanged??Ze)(i,t)||n.useDefault&&n.reflect&&i===this._$Ej?.get(e)&&!this.hasAttribute(a._$Eu(e,n))))return;this.C(e,t,n)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(e,t,{useDefault:n,reflect:r,wrapped:i},a){n&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,a??t??this[e]),!0!==i||a!==void 0)||(this._$AL.has(e)||(this.hasUpdated||n||(t=void 0),this._$AL.set(e,t)),!0===r&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}let e=this.scheduleUpdate();return e!=null&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(let[e,t]of this._$Ep)this[e]=t;this._$Ep=void 0}let e=this.constructor.elementProperties;if(e.size>0)for(let[t,n]of e){let{wrapped:e}=n,r=this[t];!0!==e||this._$AL.has(t)||r===void 0||this.C(t,void 0,n,r)}}let e=!1,t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach(e=>e.hostUpdate?.()),this.update(t)):this._$EM()}catch(t){throw e=!1,this._$EM(),t}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(e){}firstUpdated(e){}};I.elementStyles=[],I.shadowRootOptions={mode:`open`},I[P(`elementProperties`)]=new Map,I[P(`finalized`)]=new Map,Xe?.({ReactiveElement:I}),(N.reactiveElementVersions??=[]).push(`2.1.2`);var L=globalThis,$e=e=>e,R=L.trustedTypes,et=R?R.createPolicy(`lit-html`,{createHTML:e=>e}):void 0,tt=`$lit$`,z=`lit$${Math.random().toFixed(9).slice(2)}$`,nt=`?`+z,rt=`<${nt}>`,B=document,V=()=>B.createComment(``),H=e=>e===null||typeof e!=`object`&&typeof e!=`function`,U=Array.isArray,it=e=>U(e)||typeof e?.[Symbol.iterator]==`function`,at=`[
2
- \f\r]`,W=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,ot=/-->/g,st=/>/g,G=RegExp(`>|${at}(?:([^\\s"'>=/]+)(${at}*=${at}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,`g`),ct=/'/g,lt=/"/g,ut=/^(?:script|style|textarea|title)$/i,dt=e=>(t,...n)=>({_$litType$:e,strings:t,values:n}),K=dt(1),ft=dt(2),q=Symbol.for(`lit-noChange`),J=Symbol.for(`lit-nothing`),pt=new WeakMap,Y=B.createTreeWalker(B,129);function mt(e,t){if(!U(e)||!e.hasOwnProperty(`raw`))throw Error(`invalid template strings array`);return et===void 0?t:et.createHTML(t)}var ht=(e,t)=>{let n=e.length-1,r=[],i,a=t===2?`<svg>`:t===3?`<math>`:``,o=W;for(let t=0;t<n;t++){let n=e[t],s,c,l=-1,u=0;for(;u<n.length&&(o.lastIndex=u,c=o.exec(n),c!==null);)u=o.lastIndex,o===W?c[1]===`!--`?o=ot:c[1]===void 0?c[2]===void 0?c[3]!==void 0&&(o=G):(ut.test(c[2])&&(i=RegExp(`</`+c[2],`g`)),o=G):o=st:o===G?c[0]===`>`?(o=i??W,l=-1):c[1]===void 0?l=-2:(l=o.lastIndex-c[2].length,s=c[1],o=c[3]===void 0?G:c[3]===`"`?lt:ct):o===lt||o===ct?o=G:o===ot||o===st?o=W:(o=G,i=void 0);let d=o===G&&e[t+1].startsWith(`/>`)?` `:``;a+=o===W?n+rt:l>=0?(r.push(s),n.slice(0,l)+tt+n.slice(l)+z+d):n+z+(l===-2?t:d)}return[mt(e,a+(e[n]||`<?>`)+(t===2?`</svg>`:t===3?`</math>`:``)),r]},gt=class e{constructor({strings:t,_$litType$:n},r){let i;this.parts=[];let a=0,o=0,s=t.length-1,c=this.parts,[l,u]=ht(t,n);if(this.el=e.createElement(l,r),Y.currentNode=this.el.content,n===2||n===3){let e=this.el.content.firstChild;e.replaceWith(...e.childNodes)}for(;(i=Y.nextNode())!==null&&c.length<s;){if(i.nodeType===1){if(i.hasAttributes())for(let e of i.getAttributeNames())if(e.endsWith(tt)){let t=u[o++],n=i.getAttribute(e).split(z),r=/([.?@])?(.*)/.exec(t);c.push({type:1,index:a,name:r[2],strings:n,ctor:r[1]===`.`?yt:r[1]===`?`?bt:r[1]===`@`?xt:Z}),i.removeAttribute(e)}else e.startsWith(z)&&(c.push({type:6,index:a}),i.removeAttribute(e));if(ut.test(i.tagName)){let e=i.textContent.split(z),t=e.length-1;if(t>0){i.textContent=R?R.emptyScript:``;for(let n=0;n<t;n++)i.append(e[n],V()),Y.nextNode(),c.push({type:2,index:++a});i.append(e[t],V())}}}else if(i.nodeType===8)if(i.data===nt)c.push({type:2,index:a});else{let e=-1;for(;(e=i.data.indexOf(z,e+1))!==-1;)c.push({type:7,index:a}),e+=z.length-1}a++}}static createElement(e,t){let n=B.createElement(`template`);return n.innerHTML=e,n}};function X(e,t,n=e,r){if(t===q)return t;let i=r===void 0?n._$Cl:n._$Co?.[r],a=H(t)?void 0:t._$litDirective$;return i?.constructor!==a&&(i?._$AO?.(!1),a===void 0?i=void 0:(i=new a(e),i._$AT(e,n,r)),r===void 0?n._$Cl=i:(n._$Co??=[])[r]=i),i!==void 0&&(t=X(e,i._$AS(e,t.values),i,r)),t}var _t=class{constructor(e,t){this._$AV=[],this._$AN=void 0,this._$AD=e,this._$AM=t}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(e){let{el:{content:t},parts:n}=this._$AD,r=(e?.creationScope??B).importNode(t,!0);Y.currentNode=r;let i=Y.nextNode(),a=0,o=0,s=n[0];for(;s!==void 0;){if(a===s.index){let t;s.type===2?t=new vt(i,i.nextSibling,this,e):s.type===1?t=new s.ctor(i,s.name,s.strings,this,e):s.type===6&&(t=new St(i,this,e)),this._$AV.push(t),s=n[++o]}a!==s?.index&&(i=Y.nextNode(),a++)}return Y.currentNode=B,r}p(e){let t=0;for(let n of this._$AV)n!==void 0&&(n.strings===void 0?n._$AI(e[t]):(n._$AI(e,n,t),t+=n.strings.length-2)),t++}},vt=class e{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(e,t,n,r){this.type=2,this._$AH=J,this._$AN=void 0,this._$AA=e,this._$AB=t,this._$AM=n,this.options=r,this._$Cv=r?.isConnected??!0}get parentNode(){let e=this._$AA.parentNode,t=this._$AM;return t!==void 0&&e?.nodeType===11&&(e=t.parentNode),e}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(e,t=this){e=X(this,e,t),H(e)?e===J||e==null||e===``?(this._$AH!==J&&this._$AR(),this._$AH=J):e!==this._$AH&&e!==q&&this._(e):e._$litType$===void 0?e.nodeType===void 0?it(e)?this.k(e):this._(e):this.T(e):this.$(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==J&&H(this._$AH)?this._$AA.nextSibling.data=e:this.T(B.createTextNode(e)),this._$AH=e}$(e){let{values:t,_$litType$:n}=e,r=typeof n==`number`?this._$AC(e):(n.el===void 0&&(n.el=gt.createElement(mt(n.h,n.h[0]),this.options)),n);if(this._$AH?._$AD===r)this._$AH.p(t);else{let e=new _t(r,this),n=e.u(this.options);e.p(t),this.T(n),this._$AH=e}}_$AC(e){let t=pt.get(e.strings);return t===void 0&&pt.set(e.strings,t=new gt(e)),t}k(t){U(this._$AH)||(this._$AH=[],this._$AR());let n=this._$AH,r,i=0;for(let a of t)i===n.length?n.push(r=new e(this.O(V()),this.O(V()),this,this.options)):r=n[i],r._$AI(a),i++;i<n.length&&(this._$AR(r&&r._$AB.nextSibling,i),n.length=i)}_$AR(e=this._$AA.nextSibling,t){for(this._$AP?.(!1,!0,t);e!==this._$AB;){let t=$e(e).nextSibling;$e(e).remove(),e=t}}setConnected(e){this._$AM===void 0&&(this._$Cv=e,this._$AP?.(e))}},Z=class{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(e,t,n,r,i){this.type=1,this._$AH=J,this._$AN=void 0,this.element=e,this.name=t,this._$AM=r,this.options=i,n.length>2||n[0]!==``||n[1]!==``?(this._$AH=Array(n.length-1).fill(new String),this.strings=n):this._$AH=J}_$AI(e,t=this,n,r){let i=this.strings,a=!1;if(i===void 0)e=X(this,e,t,0),a=!H(e)||e!==this._$AH&&e!==q,a&&(this._$AH=e);else{let r=e,o,s;for(e=i[0],o=0;o<i.length-1;o++)s=X(this,r[n+o],t,o),s===q&&(s=this._$AH[o]),a||=!H(s)||s!==this._$AH[o],s===J?e=J:e!==J&&(e+=(s??``)+i[o+1]),this._$AH[o]=s}a&&!r&&this.j(e)}j(e){e===J?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,e??``)}},yt=class extends Z{constructor(){super(...arguments),this.type=3}j(e){this.element[this.name]=e===J?void 0:e}},bt=class extends Z{constructor(){super(...arguments),this.type=4}j(e){this.element.toggleAttribute(this.name,!!e&&e!==J)}},xt=class extends Z{constructor(e,t,n,r,i){super(e,t,n,r,i),this.type=5}_$AI(e,t=this){if((e=X(this,e,t,0)??J)===q)return;let n=this._$AH,r=e===J&&n!==J||e.capture!==n.capture||e.once!==n.once||e.passive!==n.passive,i=e!==J&&(n===J||r);r&&this.element.removeEventListener(this.name,this,n),i&&this.element.addEventListener(this.name,this,e),this._$AH=e}handleEvent(e){typeof this._$AH==`function`?this._$AH.call(this.options?.host??this.element,e):this._$AH.handleEvent(e)}},St=class{constructor(e,t,n){this.element=e,this.type=6,this._$AN=void 0,this._$AM=t,this.options=n}get _$AU(){return this._$AM._$AU}_$AI(e){X(this,e)}},Ct=L.litHtmlPolyfillSupport;Ct?.(gt,vt),(L.litHtmlVersions??=[]).push(`3.3.2`);var wt=(e,t,n)=>{let r=n?.renderBefore??t,i=r._$litPart$;if(i===void 0){let e=n?.renderBefore??null;r._$litPart$=i=new vt(t.insertBefore(V(),e),e,void 0,n??{})}return i._$AI(e),i},Tt=globalThis,Q=class extends I{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let e=super.createRenderRoot();return this.renderOptions.renderBefore??=e.firstChild,e}update(e){let t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(e),this._$Do=wt(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return q}};Q._$litElement$=!0,Q.finalized=!0,Tt.litElementHydrateSupport?.({LitElement:Q});var Et=Tt.litElementPolyfillSupport;Et?.({LitElement:Q}),(Tt.litElementVersions??=[]).push(`4.2.2`);function Dt(e){if(!e.mimeType?.startsWith(`video/`)||!Number.isFinite(e.durationSeconds)||!e.durationSeconds||e.durationSeconds<=0||e.durationSeconds>15)return!1;let t=e.size;return!(typeof t==`number`&&t>12582912)}var Ot=640,kt=8,At=72,jt=16,Mt=704,Nt=.9,Pt=.85;function $(e){if(!(!Number.isFinite(e)||!e||e<=0))return e}function Ft(){return{width:globalThis.innerWidth||document.documentElement.clientWidth||0,height:globalThis.innerHeight||document.documentElement.clientHeight||0}}function It(e,t){let n=e<=Ot?kt:At;return{width:Math.max(0,e-n*2),height:Math.max(0,t-jt*2)}}function Lt(e,t,n){let r=$(e?.width),i=$(e?.height);if(!r||!i)return null;let a=It(t,n);if(a.width<=0||a.height<=0)return null;let o=Math.min(a.width/r,a.height/i);return{width:Math.max(1,Math.round(r*o)),height:Math.max(1,Math.round(i*o))}}function Rt(e,t,n){if(!e||e.mimeType?.startsWith(`video/`)||!$(e.width)||!$(e.height)||t<=0||n<=0)return!1;let r=$(e.width),i=$(e.height);if(!r||!i)return!1;let a=t<=Ot,o=It(t,n),s=o.width,c=o.height;if(s<=0||c<=0)return!1;let l=r/i,u=Math.min(s,c*l);return l<Nt&&u<(a?s:Math.min(s,Mt))*Pt}var zt=class extends Q{static properties={_images:{state:!0},_currentIndex:{state:!0},_open:{state:!0},_viewportWidth:{state:!0},_viewportHeight:{state:!0},_videoCurrentTime:{state:!0},_videoDuration:{state:!0},_videoMuted:{state:!0}};createRenderRoot(){return this.innerHTML=``,this}constructor(){super();let e=Ft();this._images=[],this._currentIndex=0,this._open=!1,this._viewportWidth=e.width,this._viewportHeight=e.height,this._videoCurrentTime=0,this._videoDuration=0,this._videoMuted=!1}connectedCallback(){super.connectedCallback(),document.addEventListener(`click`,this.#e),window.addEventListener(`resize`,this.#o),this.#s()}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener(`click`,this.#e),window.removeEventListener(`resize`,this.#o)}open(e,t){this.#s(),this.#c(),this._images=e,this._currentIndex=Math.max(0,Math.min(t,e.length-1)),this.#l(this._images[this._currentIndex]),this._open=!0,document.dispatchEvent(new CustomEvent(v,{detail:{open:!0}})),this.updateComplete.then(()=>{this.querySelector(`.media-lightbox`)?.showModal(),this.querySelector(`.media-lightbox-content`)?.focus()})}close(){this.#c(),this.querySelector(`.media-lightbox`)?.close(),this._open=!1,document.dispatchEvent(new CustomEvent(v,{detail:{open:!1}}))}#e=e=>{let t=e.target,n=t.closest(`[data-post-media] a[data-lightbox-index]`);if(n){let t=n.closest(`[data-lightbox-group]`);if(!t)return;e.preventDefault();let r=parseInt(n.dataset.lightboxIndex??`0`,10);try{let e=JSON.parse(t.dataset.lightboxGroup??`[]`);e.length>0&&this.open(e,r)}catch{}return}let r=t.closest(`[data-post-body] img`);if(r){e.preventDefault();let t=r.closest(`[data-post-body]`);if(!t)return;let n=Array.from(t.querySelectorAll(`img`)),i=n.map(e=>({url:e.src,alt:e.alt||``,width:$(e.naturalWidth||Number(e.getAttribute(`width`))),height:$(e.naturalHeight||Number(e.getAttribute(`height`)))})),a=n.indexOf(r);i.length>0&&this.open(i,Math.max(0,a))}};#t(){this._images.length<=1||(this.#c(),this._currentIndex=(this._currentIndex-1+this._images.length)%this._images.length)}#n(){this._images.length<=1||(this.#c(),this._currentIndex=(this._currentIndex+1)%this._images.length)}#r=e=>{let t=e,n=e.target;if(t.key===`Escape`)e.preventDefault(),this.close();else if(n?.classList.contains(`media-lightbox-short-progress`)&&(t.key===`ArrowLeft`||t.key===`ArrowRight`))return;else t.key===`ArrowLeft`?(e.preventDefault(),this.#t()):t.key===`ArrowRight`&&(e.preventDefault(),this.#n())};#i=e=>{let t=e.target;(t===e.currentTarget||t.classList.contains(`media-lightbox-content`)||t.classList.contains(`media-lightbox-stage`))&&this.close()};#a=()=>{this.#c(),this._open&&document.dispatchEvent(new CustomEvent(v,{detail:{open:!1}})),this._open=!1};#o=()=>{this.#s()};#s(){let e=Ft();e.width===this._viewportWidth&&e.height===this._viewportHeight||(this._viewportWidth=e.width,this._viewportHeight=e.height)}#c(){this.querySelector(`.media-lightbox-video`)?.pause()}#l(e){this._videoCurrentTime=0,this._videoDuration=e?.durationSeconds&&e.durationSeconds>0?e.durationSeconds:0,this._videoMuted=!1}#u(){let e=this._images[this._currentIndex];if(!Dt(e)){this.#l(e);return}let t=this.querySelector(`.media-lightbox-video`);t&&(t.currentTime=0,t.muted=this._videoMuted,t.play().catch(()=>{}))}#d=e=>{let t=e.currentTarget;Number.isFinite(t.duration)&&t.duration>0&&(this._videoDuration=t.duration),this._videoCurrentTime=t.currentTime,t.muted=this._videoMuted};#f=e=>{let t=e.currentTarget;this._videoCurrentTime=t.currentTime,Number.isFinite(t.duration)&&t.duration>0&&(this._videoDuration=t.duration)};#p=e=>{let t=e.currentTarget,n=this.querySelector(`.media-lightbox-video`),r=Number.parseFloat(t.value);!n||!Number.isFinite(r)||r<0||(n.currentTime=r,this._videoCurrentTime=r)};#m=()=>{this._videoMuted=!this._videoMuted;let e=this.querySelector(`.media-lightbox-video`);e&&(e.muted=this._videoMuted)};updated(e){if(super.updated(e),!this._open||!e.has(`_currentIndex`)&&!e.has(`_open`))return;let t=this.querySelector(`.media-lightbox-stage`);t&&(t.scrollTop=0,t.scrollLeft=0,this.#u())}render(){if(!this._open)return J;let e=this._images[this._currentIndex],t=this._images.length>1,n=e?.mimeType?.startsWith(`video/`),r=Dt(e),i=Rt(e,this._viewportWidth,this._viewportHeight),a=r?Lt(e,this._viewportWidth,this._viewportHeight):null,o=r&&!!a&&a.height>a.width,s=a?`width:${a.width}px;height:${a.height}px;`:J,c=this._videoDuration>0?this._videoDuration:e?.durationSeconds??1,l=Math.min(this._videoCurrentTime,c),u=c>0?l/c*100:0;return K`
2
+ \f\r]`,W=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,ot=/-->/g,st=/>/g,G=RegExp(`>|${at}(?:([^\\s"'>=/]+)(${at}*=${at}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,`g`),ct=/'/g,lt=/"/g,ut=/^(?:script|style|textarea|title)$/i,dt=e=>(t,...n)=>({_$litType$:e,strings:t,values:n}),K=dt(1),ft=dt(2),q=Symbol.for(`lit-noChange`),J=Symbol.for(`lit-nothing`),pt=new WeakMap,Y=B.createTreeWalker(B,129);function mt(e,t){if(!U(e)||!e.hasOwnProperty(`raw`))throw Error(`invalid template strings array`);return et===void 0?t:et.createHTML(t)}var ht=(e,t)=>{let n=e.length-1,r=[],i,a=t===2?`<svg>`:t===3?`<math>`:``,o=W;for(let t=0;t<n;t++){let n=e[t],s,c,l=-1,u=0;for(;u<n.length&&(o.lastIndex=u,c=o.exec(n),c!==null);)u=o.lastIndex,o===W?c[1]===`!--`?o=ot:c[1]===void 0?c[2]===void 0?c[3]!==void 0&&(o=G):(ut.test(c[2])&&(i=RegExp(`</`+c[2],`g`)),o=G):o=st:o===G?c[0]===`>`?(o=i??W,l=-1):c[1]===void 0?l=-2:(l=o.lastIndex-c[2].length,s=c[1],o=c[3]===void 0?G:c[3]===`"`?lt:ct):o===lt||o===ct?o=G:o===ot||o===st?o=W:(o=G,i=void 0);let d=o===G&&e[t+1].startsWith(`/>`)?` `:``;a+=o===W?n+rt:l>=0?(r.push(s),n.slice(0,l)+tt+n.slice(l)+z+d):n+z+(l===-2?t:d)}return[mt(e,a+(e[n]||`<?>`)+(t===2?`</svg>`:t===3?`</math>`:``)),r]},gt=class e{constructor({strings:t,_$litType$:n},r){let i;this.parts=[];let a=0,o=0,s=t.length-1,c=this.parts,[l,u]=ht(t,n);if(this.el=e.createElement(l,r),Y.currentNode=this.el.content,n===2||n===3){let e=this.el.content.firstChild;e.replaceWith(...e.childNodes)}for(;(i=Y.nextNode())!==null&&c.length<s;){if(i.nodeType===1){if(i.hasAttributes())for(let e of i.getAttributeNames())if(e.endsWith(tt)){let t=u[o++],n=i.getAttribute(e).split(z),r=/([.?@])?(.*)/.exec(t);c.push({type:1,index:a,name:r[2],strings:n,ctor:r[1]===`.`?yt:r[1]===`?`?bt:r[1]===`@`?xt:Z}),i.removeAttribute(e)}else e.startsWith(z)&&(c.push({type:6,index:a}),i.removeAttribute(e));if(ut.test(i.tagName)){let e=i.textContent.split(z),t=e.length-1;if(t>0){i.textContent=R?R.emptyScript:``;for(let n=0;n<t;n++)i.append(e[n],V()),Y.nextNode(),c.push({type:2,index:++a});i.append(e[t],V())}}}else if(i.nodeType===8)if(i.data===nt)c.push({type:2,index:a});else{let e=-1;for(;(e=i.data.indexOf(z,e+1))!==-1;)c.push({type:7,index:a}),e+=z.length-1}a++}}static createElement(e,t){let n=B.createElement(`template`);return n.innerHTML=e,n}};function X(e,t,n=e,r){if(t===q)return t;let i=r===void 0?n._$Cl:n._$Co?.[r],a=H(t)?void 0:t._$litDirective$;return i?.constructor!==a&&(i?._$AO?.(!1),a===void 0?i=void 0:(i=new a(e),i._$AT(e,n,r)),r===void 0?n._$Cl=i:(n._$Co??=[])[r]=i),i!==void 0&&(t=X(e,i._$AS(e,t.values),i,r)),t}var _t=class{constructor(e,t){this._$AV=[],this._$AN=void 0,this._$AD=e,this._$AM=t}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(e){let{el:{content:t},parts:n}=this._$AD,r=(e?.creationScope??B).importNode(t,!0);Y.currentNode=r;let i=Y.nextNode(),a=0,o=0,s=n[0];for(;s!==void 0;){if(a===s.index){let t;s.type===2?t=new vt(i,i.nextSibling,this,e):s.type===1?t=new s.ctor(i,s.name,s.strings,this,e):s.type===6&&(t=new St(i,this,e)),this._$AV.push(t),s=n[++o]}a!==s?.index&&(i=Y.nextNode(),a++)}return Y.currentNode=B,r}p(e){let t=0;for(let n of this._$AV)n!==void 0&&(n.strings===void 0?n._$AI(e[t]):(n._$AI(e,n,t),t+=n.strings.length-2)),t++}},vt=class e{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(e,t,n,r){this.type=2,this._$AH=J,this._$AN=void 0,this._$AA=e,this._$AB=t,this._$AM=n,this.options=r,this._$Cv=r?.isConnected??!0}get parentNode(){let e=this._$AA.parentNode,t=this._$AM;return t!==void 0&&e?.nodeType===11&&(e=t.parentNode),e}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(e,t=this){e=X(this,e,t),H(e)?e===J||e==null||e===``?(this._$AH!==J&&this._$AR(),this._$AH=J):e!==this._$AH&&e!==q&&this._(e):e._$litType$===void 0?e.nodeType===void 0?it(e)?this.k(e):this._(e):this.T(e):this.$(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==J&&H(this._$AH)?this._$AA.nextSibling.data=e:this.T(B.createTextNode(e)),this._$AH=e}$(e){let{values:t,_$litType$:n}=e,r=typeof n==`number`?this._$AC(e):(n.el===void 0&&(n.el=gt.createElement(mt(n.h,n.h[0]),this.options)),n);if(this._$AH?._$AD===r)this._$AH.p(t);else{let e=new _t(r,this),n=e.u(this.options);e.p(t),this.T(n),this._$AH=e}}_$AC(e){let t=pt.get(e.strings);return t===void 0&&pt.set(e.strings,t=new gt(e)),t}k(t){U(this._$AH)||(this._$AH=[],this._$AR());let n=this._$AH,r,i=0;for(let a of t)i===n.length?n.push(r=new e(this.O(V()),this.O(V()),this,this.options)):r=n[i],r._$AI(a),i++;i<n.length&&(this._$AR(r&&r._$AB.nextSibling,i),n.length=i)}_$AR(e=this._$AA.nextSibling,t){for(this._$AP?.(!1,!0,t);e!==this._$AB;){let t=$e(e).nextSibling;$e(e).remove(),e=t}}setConnected(e){this._$AM===void 0&&(this._$Cv=e,this._$AP?.(e))}},Z=class{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(e,t,n,r,i){this.type=1,this._$AH=J,this._$AN=void 0,this.element=e,this.name=t,this._$AM=r,this.options=i,n.length>2||n[0]!==``||n[1]!==``?(this._$AH=Array(n.length-1).fill(new String),this.strings=n):this._$AH=J}_$AI(e,t=this,n,r){let i=this.strings,a=!1;if(i===void 0)e=X(this,e,t,0),a=!H(e)||e!==this._$AH&&e!==q,a&&(this._$AH=e);else{let r=e,o,s;for(e=i[0],o=0;o<i.length-1;o++)s=X(this,r[n+o],t,o),s===q&&(s=this._$AH[o]),a||=!H(s)||s!==this._$AH[o],s===J?e=J:e!==J&&(e+=(s??``)+i[o+1]),this._$AH[o]=s}a&&!r&&this.j(e)}j(e){e===J?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,e??``)}},yt=class extends Z{constructor(){super(...arguments),this.type=3}j(e){this.element[this.name]=e===J?void 0:e}},bt=class extends Z{constructor(){super(...arguments),this.type=4}j(e){this.element.toggleAttribute(this.name,!!e&&e!==J)}},xt=class extends Z{constructor(e,t,n,r,i){super(e,t,n,r,i),this.type=5}_$AI(e,t=this){if((e=X(this,e,t,0)??J)===q)return;let n=this._$AH,r=e===J&&n!==J||e.capture!==n.capture||e.once!==n.once||e.passive!==n.passive,i=e!==J&&(n===J||r);r&&this.element.removeEventListener(this.name,this,n),i&&this.element.addEventListener(this.name,this,e),this._$AH=e}handleEvent(e){typeof this._$AH==`function`?this._$AH.call(this.options?.host??this.element,e):this._$AH.handleEvent(e)}},St=class{constructor(e,t,n){this.element=e,this.type=6,this._$AN=void 0,this._$AM=t,this.options=n}get _$AU(){return this._$AM._$AU}_$AI(e){X(this,e)}},Ct=L.litHtmlPolyfillSupport;Ct?.(gt,vt),(L.litHtmlVersions??=[]).push(`3.3.2`);var wt=(e,t,n)=>{let r=n?.renderBefore??t,i=r._$litPart$;if(i===void 0){let e=n?.renderBefore??null;r._$litPart$=i=new vt(t.insertBefore(V(),e),e,void 0,n??{})}return i._$AI(e),i},Tt=globalThis,Q=class extends I{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let e=super.createRenderRoot();return this.renderOptions.renderBefore??=e.firstChild,e}update(e){let t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(e),this._$Do=wt(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return q}};Q._$litElement$=!0,Q.finalized=!0,Tt.litElementHydrateSupport?.({LitElement:Q});var Et=Tt.litElementPolyfillSupport;Et?.({LitElement:Q}),(Tt.litElementVersions??=[]).push(`4.2.2`);function Dt(e){if(!e.mimeType?.startsWith(`video/`)||!Number.isFinite(e.durationSeconds)||!e.durationSeconds||e.durationSeconds<=0||e.durationSeconds>15)return!1;let t=e.size;return!(typeof t==`number`&&t>12582912)}var Ot=640,kt=8,At=72,jt=16,Mt=704,Nt=.9,Pt=.85;function $(e){if(!(!Number.isFinite(e)||!e||e<=0))return e}function Ft(){return{width:globalThis.innerWidth||document.documentElement.clientWidth||0,height:globalThis.innerHeight||document.documentElement.clientHeight||0}}function It(e,t){let n=e<=Ot?kt:At;return{width:Math.max(0,e-n*2),height:Math.max(0,t-jt*2)}}function Lt(e,t,n){let r=$(e?.width),i=$(e?.height);if(!r||!i)return null;let a=It(t,n);if(a.width<=0||a.height<=0)return null;let o=Math.min(a.width/r,a.height/i);return{width:Math.max(1,Math.round(r*o)),height:Math.max(1,Math.round(i*o))}}function Rt(e,t,n){if(!e||e.mimeType?.startsWith(`video/`)||!$(e.width)||!$(e.height)||t<=0||n<=0)return!1;let r=$(e.width),i=$(e.height);if(!r||!i)return!1;let a=t<=Ot,o=It(t,n),s=o.width,c=o.height;if(s<=0||c<=0)return!1;let l=r/i,u=Math.min(s,c*l);return l<Nt&&u<(a?s:Math.min(s,Mt))*Pt}var zt=class extends Q{static properties={_images:{state:!0},_currentIndex:{state:!0},_open:{state:!0},_viewportWidth:{state:!0},_viewportHeight:{state:!0},_videoCurrentTime:{state:!0},_videoDuration:{state:!0},_videoMuted:{state:!0}};createRenderRoot(){return this.innerHTML=``,this}constructor(){super();let e=Ft();this._images=[],this._currentIndex=0,this._open=!1,this._viewportWidth=e.width,this._viewportHeight=e.height,this._videoCurrentTime=0,this._videoDuration=0,this._videoMuted=!1}connectedCallback(){super.connectedCallback(),document.addEventListener(`click`,this.#e),window.addEventListener(`resize`,this.#o),this.#s()}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener(`click`,this.#e),window.removeEventListener(`resize`,this.#o)}open(e,t){this.#s(),this.#c(),this._images=e,this._currentIndex=Math.max(0,Math.min(t,e.length-1)),this.#l(this._images[this._currentIndex]),this._open=!0,document.dispatchEvent(new CustomEvent(v,{detail:{open:!0}})),this.updateComplete.then(()=>{this.querySelector(`.media-lightbox`)?.showModal(),this.querySelector(`.media-lightbox-content`)?.focus()})}close(){this.#c(),this.querySelector(`.media-lightbox`)?.close(),this._open=!1,document.dispatchEvent(new CustomEvent(v,{detail:{open:!1}}))}#e=e=>{let t=e.target,n=t.closest(`[data-post-media] a[data-lightbox-index]`);if(n){let t=n.closest(`[data-lightbox-group]`);if(!t)return;e.preventDefault();let r=parseInt(n.dataset.lightboxIndex??`0`,10);try{let e=JSON.parse(t.dataset.lightboxGroup??`[]`);e.length>0&&this.open(e,r)}catch{}return}let r=t.closest(`[data-post-body] img`);if(r){e.preventDefault();let t=r.closest(`[data-post-body]`);if(!t)return;let n=Array.from(t.querySelectorAll(`img`)),i=n.map(e=>({url:e.src,alt:e.alt||``,width:$(e.naturalWidth||Number(e.getAttribute(`width`))),height:$(e.naturalHeight||Number(e.getAttribute(`height`)))})),a=n.indexOf(r);i.length>0&&this.open(i,Math.max(0,a))}};#t(){this._images.length<=1||(this.#c(),this._currentIndex=(this._currentIndex-1+this._images.length)%this._images.length)}#n(){this._images.length<=1||(this.#c(),this._currentIndex=(this._currentIndex+1)%this._images.length)}#r=e=>{let t=e,n=e.target;if(t.key===`Escape`){e.preventDefault(),this.close();return}if(!(t.key!==`ArrowLeft`&&t.key!==`ArrowRight`)&&!n?.classList.contains(`media-lightbox-short-progress`)){if(this._images[this._currentIndex]?.mimeType?.startsWith(`video/`)){let n=this.querySelector(`.media-lightbox-video`);if(n){e.preventDefault();let r=t.key===`ArrowLeft`?-5:5,i=Number.isFinite(n.duration)&&n.duration>0?n.duration:null,a=i==null?Math.max(0,n.currentTime+r):Math.max(0,Math.min(n.currentTime+r,i));n.currentTime=a,this._videoCurrentTime=a}return}e.preventDefault(),t.key===`ArrowLeft`?this.#t():this.#n()}};#i=e=>{let t=e.target;(t===e.currentTarget||t.classList.contains(`media-lightbox-content`)||t.classList.contains(`media-lightbox-stage`))&&this.close()};#a=()=>{this.#c(),this._open&&document.dispatchEvent(new CustomEvent(v,{detail:{open:!1}})),this._open=!1};#o=()=>{this.#s()};#s(){let e=Ft();e.width===this._viewportWidth&&e.height===this._viewportHeight||(this._viewportWidth=e.width,this._viewportHeight=e.height)}#c(){this.querySelector(`.media-lightbox-video`)?.pause()}#l(e){this._videoCurrentTime=0,this._videoDuration=e?.durationSeconds&&e.durationSeconds>0?e.durationSeconds:0,this._videoMuted=!1}#u(){let e=this._images[this._currentIndex];if(!Dt(e)){this.#l(e);return}let t=this.querySelector(`.media-lightbox-video`);t&&(t.currentTime=0,t.muted=this._videoMuted,t.play().catch(()=>{}))}#d=e=>{let t=e.currentTarget;Number.isFinite(t.duration)&&t.duration>0&&(this._videoDuration=t.duration),this._videoCurrentTime=t.currentTime,t.muted=this._videoMuted};#f=e=>{let t=e.currentTarget;this._videoCurrentTime=t.currentTime,Number.isFinite(t.duration)&&t.duration>0&&(this._videoDuration=t.duration)};#p=e=>{let t=e.currentTarget,n=this.querySelector(`.media-lightbox-video`),r=Number.parseFloat(t.value);!n||!Number.isFinite(r)||r<0||(n.currentTime=r,this._videoCurrentTime=r)};#m=()=>{this._videoMuted=!this._videoMuted;let e=this.querySelector(`.media-lightbox-video`);e&&(e.muted=this._videoMuted)};updated(e){if(super.updated(e),!this._open||!e.has(`_currentIndex`)&&!e.has(`_open`))return;let t=this.querySelector(`.media-lightbox-stage`);t&&(t.scrollTop=0,t.scrollLeft=0,this.#u())}render(){if(!this._open)return J;let e=this._images[this._currentIndex],t=this._images.length>1,n=e?.mimeType?.startsWith(`video/`),r=Dt(e),i=Rt(e,this._viewportWidth,this._viewportHeight),a=r?Lt(e,this._viewportWidth,this._viewportHeight):null,o=r&&!!a&&a.height>a.width,s=a?`width:${a.width}px;height:${a.height}px;`:J,c=this._videoDuration>0?this._videoDuration:e?.durationSeconds??1,l=Math.min(this._videoCurrentTime,c),u=c>0?l/c*100:0;return K`
3
3
  <dialog
4
4
  class=${`media-lightbox${r?` media-lightbox-short`:``}`}
5
5
  @keydown=${this.#r}
@@ -87,12 +87,26 @@ export interface ManagedSiteMediaUsageResult {
87
87
  siteId: string;
88
88
  }
89
89
 
90
+ export interface ManagedSiteKeyAvailabilityResult {
91
+ available: boolean;
92
+ key: string;
93
+ }
94
+
90
95
  export interface SiteAdminService {
91
96
  addManagedSiteDomain(
92
97
  siteId: string,
93
98
  input: ManageManagedSiteDomainInput,
94
99
  ): Promise<SiteDomain[]>;
95
100
  createManagedSite(input: CreateManagedSiteInput): Promise<ManagedSiteResult>;
101
+ /**
102
+ * Lookup whether the given site key is free in `site`. Returns the
103
+ * normalized key so the caller can confirm what was checked. Used by the
104
+ * control plane before reserving a cloud_site row, so the user sees a
105
+ * conflict on the form instead of after provisioning.
106
+ */
107
+ isManagedSiteKeyAvailable(
108
+ key: string,
109
+ ): Promise<ManagedSiteKeyAvailabilityResult>;
96
110
  exportManagedSite(
97
111
  siteId: string,
98
112
  deps: ExportManagedSiteDeps,
@@ -115,6 +129,11 @@ export interface SiteAdminService {
115
129
  siteId: string,
116
130
  domainId: string,
117
131
  ): Promise<SiteDomain[]>;
132
+ setManagedSiteDomainRedirect(
133
+ siteId: string,
134
+ domainId: string,
135
+ redirectToPrimary: boolean,
136
+ ): Promise<SiteDomain[]>;
118
137
  }
119
138
 
120
139
  export interface SiteAdminServiceConfig {
@@ -604,6 +623,16 @@ export function createSiteAdminService(
604
623
 
605
624
  return createWithDatabase(db, input);
606
625
  },
626
+ async isManagedSiteKeyAvailable(key) {
627
+ assertManagedSiteOperationsEnabled();
628
+ const normalizedKey = key.trim();
629
+ const existing = await db
630
+ .select({ id: sites.id })
631
+ .from(sites)
632
+ .where(eq(sites.key, normalizedKey))
633
+ .limit(1);
634
+ return { available: !existing[0], key: normalizedKey };
635
+ },
607
636
  async getManagedSiteMediaUsage(siteId) {
608
637
  assertManagedSiteOperationsEnabled();
609
638
  return getManagedSiteMediaUsage(siteId);
@@ -819,11 +848,16 @@ export function createSiteAdminService(
819
848
 
820
849
  const timestamp = now();
821
850
  if (input.makePrimary) {
851
+ // Newly-added primaries (e.g. custom domains) are unverified at this
852
+ // point. Leave any demoted alias serving directly so the site stays
853
+ // reachable while the new primary's DNS propagates. The caller is
854
+ // expected to flip redirectToPrimary back on once the new primary is
855
+ // confirmed to work.
822
856
  await targetDb
823
857
  .update(siteDomains)
824
858
  .set({
825
859
  kind: "alias",
826
- redirectToPrimary: true,
860
+ redirectToPrimary: false,
827
861
  updatedAt: timestamp,
828
862
  })
829
863
  .where(eq(siteDomains.siteId, normalizedSiteId));
@@ -841,6 +875,33 @@ export function createSiteAdminService(
841
875
  });
842
876
  });
843
877
  },
878
+ async setManagedSiteDomainRedirect(siteId, domainId, redirectToPrimary) {
879
+ assertManagedSiteOperationsEnabled();
880
+ return mutateSiteDomains(siteId, async (targetDb, normalizedSiteId) => {
881
+ await requireSite(targetDb, normalizedSiteId);
882
+
883
+ const normalizedDomainId = domainId.trim();
884
+ const current = await targetDb
885
+ .select({ id: siteDomains.id })
886
+ .from(siteDomains)
887
+ .where(
888
+ sql`${siteDomains.id} = ${normalizedDomainId} AND ${siteDomains.siteId} = ${normalizedSiteId}`,
889
+ )
890
+ .limit(1);
891
+ if (!current[0]) {
892
+ throw new NotFoundError("Site domain");
893
+ }
894
+
895
+ const timestamp = now();
896
+ await targetDb
897
+ .update(siteDomains)
898
+ .set({
899
+ redirectToPrimary,
900
+ updatedAt: timestamp,
901
+ })
902
+ .where(eq(siteDomains.id, normalizedDomainId));
903
+ });
904
+ },
844
905
  async setManagedSitePrimaryDomain(siteId, domainId) {
845
906
  assertManagedSiteOperationsEnabled();
846
907
  return mutateSiteDomains(siteId, async (targetDb, normalizedSiteId) => {
@@ -281,7 +281,6 @@
281
281
  transparent
282
282
  );
283
283
  --site-thread-item-spacing: 16px;
284
- --site-thread-context-max-height: clamp(196px, 40vh, 400px);
285
284
  --site-thread-dot-ring: color-mix(
286
285
  in srgb,
287
286
  var(--site-accent) 16%,
package/src/styles/ui.css CHANGED
@@ -2615,13 +2615,6 @@
2615
2615
  var(--site-thread-dot-ring);
2616
2616
  }
2617
2617
 
2618
- .thread-context-shell {
2619
- position: relative;
2620
- display: grid;
2621
- gap: 0.7rem;
2622
- padding-bottom: 0.35rem;
2623
- }
2624
-
2625
2618
  .thread-item-gap {
2626
2619
  padding-top: 0.15rem;
2627
2620
  padding-bottom: 0.35rem;
@@ -2694,70 +2687,6 @@
2694
2687
  @apply text-sm;
2695
2688
  }
2696
2689
 
2697
- /* Faded thread ancestor context */
2698
- .thread-context-collapsed {
2699
- position: relative;
2700
- max-height: var(--site-thread-context-max-height);
2701
- overflow: hidden;
2702
- transition: max-height 0.3s ease;
2703
- }
2704
-
2705
- .thread-context-collapsed.expanded {
2706
- max-height: none;
2707
- overflow: visible;
2708
- }
2709
-
2710
- .thread-context-fade {
2711
- display: none;
2712
- position: absolute;
2713
- bottom: 0;
2714
- left: 0;
2715
- right: 0;
2716
- height: 52px;
2717
- background: linear-gradient(
2718
- transparent,
2719
- color-mix(in srgb, var(--site-page-bg) 76%, var(--site-feed-card-bg))
2720
- );
2721
- pointer-events: none;
2722
- }
2723
-
2724
- .thread-context-faded .thread-context-fade {
2725
- display: block;
2726
- }
2727
-
2728
- .thread-context-collapsed.expanded .thread-context-fade {
2729
- display: none;
2730
- }
2731
-
2732
- .thread-context-toggle {
2733
- display: inline-flex;
2734
- align-items: center;
2735
- padding: 0.38rem 0.78rem;
2736
- font-size: var(--type-thread-context-meta);
2737
- margin-bottom: 4px;
2738
- cursor: pointer;
2739
- background: var(--site-thread-gap-bg);
2740
- border: 1px solid var(--site-thread-context-border);
2741
- border-radius: 999px;
2742
- line-height: 1.1;
2743
- transition:
2744
- border-color 0.18s ease,
2745
- color 0.18s ease,
2746
- background-color 0.18s ease;
2747
- }
2748
-
2749
- .thread-context-toggle.hidden {
2750
- display: none;
2751
- }
2752
-
2753
- .thread-context-toggle:hover {
2754
- border-color: color-mix(
2755
- in srgb,
2756
- var(--site-accent) 22%,
2757
- var(--site-divider)
2758
- );
2759
- }
2760
-
2761
2690
  /* Thread detail: current post anchor + dot-only highlight */
2762
2691
  .thread-detail-item {
2763
2692
  scroll-margin-top: 80px;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Thread Preview
3
3
  *
4
- * Shows latest reply as the hero post with faded ancestor context above.
4
+ * Shows latest reply as the hero post with ancestor context above.
5
5
  * Thread line connects all posts via `.thread-group` / `.thread-item`.
6
6
  */
7
7
 
@@ -36,18 +36,6 @@ export const ThreadPreview: FC<ThreadPreviewProps> = ({
36
36
  totalReplyCount,
37
37
  }) => {
38
38
  const { i18n } = useLingui();
39
- const showMoreLabel = i18n._(
40
- msg({
41
- message: "Show more",
42
- comment: "@context: Button to expand faded thread context",
43
- }),
44
- );
45
- const showLessLabel = i18n._(
46
- msg({
47
- message: "Show less",
48
- comment: "@context: Button to collapse expanded thread context",
49
- }),
50
- );
51
39
  const { hiddenCount } = getThreadPreviewState({
52
40
  secondReply,
53
41
  penultimateReply,
@@ -72,68 +60,49 @@ export const ThreadPreview: FC<ThreadPreviewProps> = ({
72
60
  penultimateReply.id !== secondReply?.id
73
61
  ? penultimateReply
74
62
  : undefined;
63
+ const gapHref = renderedSecondReply?.permalink ?? latestReply.permalink;
75
64
 
76
65
  return (
77
66
  <div class="thread-group thread-group-preview">
78
- {/* Faded ancestor context */}
79
- <div
80
- class="thread-context-shell thread-context-collapsed"
81
- data-thread-context
82
- >
83
- {/* Root post */}
67
+ {/* Root post */}
68
+ <div class="thread-item thread-item-context">
69
+ <TimelineItemFromPost
70
+ post={rootPost}
71
+ mode="feed"
72
+ display={ROOT_CONTEXT_DISPLAY}
73
+ />
74
+ </div>
75
+
76
+ {/* Second post in the thread */}
77
+ {renderedSecondReply && (
84
78
  <div class="thread-item thread-item-context">
85
79
  <TimelineItemFromPost
86
- post={rootPost}
80
+ post={renderedSecondReply}
87
81
  mode="feed"
88
- display={ROOT_CONTEXT_DISPLAY}
82
+ display={CONTEXT_DISPLAY}
89
83
  />
90
84
  </div>
85
+ )}
91
86
 
92
- {/* Second post in the thread */}
93
- {renderedSecondReply && (
94
- <div class="thread-item thread-item-context">
95
- <TimelineItemFromPost
96
- post={renderedSecondReply}
97
- mode="feed"
98
- display={CONTEXT_DISPLAY}
99
- />
100
- </div>
101
- )}
102
-
103
- {/* Hidden posts gap */}
104
- {hiddenCount > 0 && (
105
- <div class="thread-item thread-item-gap">
106
- <a href={latestReply.permalink} class="thread-gap-link">
107
- {hiddenPostsLabel}
108
- </a>
109
- </div>
110
- )}
111
-
112
- {/* Penultimate post in the thread */}
113
- {renderedPenultimateReply && (
114
- <div class="thread-item thread-item-context">
115
- <TimelineItemFromPost
116
- post={renderedPenultimateReply}
117
- mode="feed"
118
- display={CONTEXT_DISPLAY}
119
- />
120
- </div>
121
- )}
122
-
123
- <div class="thread-context-fade" />
124
- </div>
87
+ {/* Hidden posts gap */}
88
+ {hiddenCount > 0 && (
89
+ <div class="thread-item thread-item-gap">
90
+ <a href={gapHref} class="thread-gap-link">
91
+ {hiddenPostsLabel}
92
+ </a>
93
+ </div>
94
+ )}
125
95
 
126
- {/* Toggle button always present for expanding context in timeline */}
127
- <button
128
- type="button"
129
- class="thread-context-toggle text-muted-foreground hover:text-foreground hidden"
130
- data-thread-context-toggle
131
- data-label-more={showMoreLabel}
132
- data-label-less={showLessLabel}
133
- aria-expanded="false"
134
- >
135
- {showMoreLabel}
136
- </button>
96
+ {/* Penultimate post in the thread */}
97
+ {renderedPenultimateReply && (
98
+ <div class="thread-item thread-item-context">
99
+ <TimelineItemFromPost
100
+ post={renderedPenultimateReply}
101
+ mode="feed"
102
+ display={CONTEXT_DISPLAY}
103
+ />
104
+ </div>
105
+ )}
137
106
 
138
107
  {/* Latest reply (full card, hero) */}
139
108
  <div class="thread-item thread-item-hero">