@treeseed/core 0.10.21 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +69 -125
  2. package/dist/content.js +1 -0
  3. package/dist/dev-watch.js +2 -1
  4. package/dist/dev.d.ts +1 -1
  5. package/dist/dev.js +51 -35
  6. package/dist/pages/404.astro +1 -1
  7. package/dist/pages/[slug].astro +4 -4
  8. package/dist/pages/agents/[slug].astro +3 -3
  9. package/dist/pages/agents/index.astro +3 -3
  10. package/dist/pages/books/[slug].astro +3 -3
  11. package/dist/pages/books/index.astro +3 -3
  12. package/dist/pages/contact.astro +2 -2
  13. package/dist/pages/decisions/[slug].astro +3 -3
  14. package/dist/pages/decisions/index.astro +3 -3
  15. package/dist/pages/docs-runtime/[...slug].astro +3 -3
  16. package/dist/pages/docs-runtime/index.astro +3 -3
  17. package/dist/pages/index.astro +11 -11
  18. package/dist/pages/notes/[slug].astro +3 -3
  19. package/dist/pages/notes/index.astro +3 -3
  20. package/dist/pages/objectives/[slug].astro +3 -3
  21. package/dist/pages/objectives/index.astro +3 -3
  22. package/dist/pages/people/[slug].astro +3 -3
  23. package/dist/pages/people/index.astro +3 -3
  24. package/dist/pages/proposals/[slug].astro +3 -3
  25. package/dist/pages/proposals/index.astro +3 -3
  26. package/dist/pages/questions/[slug].astro +3 -3
  27. package/dist/pages/questions/index.astro +3 -3
  28. package/dist/pages/ui/index.astro +23 -23
  29. package/dist/platform-resources.js +5 -1
  30. package/dist/scripts/build-dist.js +2 -0
  31. package/dist/scripts/release-verify.js +24 -2
  32. package/dist/scripts/workspace-bootstrap.js +3 -0
  33. package/dist/site.js +49 -11
  34. package/dist/styles/global.css +5 -5
  35. package/dist/templates.d.ts +2 -0
  36. package/dist/templates.js +8 -6
  37. package/package.json +3 -45
  38. package/templates/github/deploy-web.workflow.yml +36 -2
  39. package/dist/components/DevWatchReload.astro +0 -45
  40. package/dist/components/SiteTitle.astro +0 -51
  41. package/dist/components/content/ContentStatusLegend.astro +0 -18
  42. package/dist/components/content/StatusBadge.astro +0 -11
  43. package/dist/components/docs/BookFontControls.astro +0 -180
  44. package/dist/components/docs/DesktopSidebarToggle.astro +0 -88
  45. package/dist/components/docs/DownloadBook.astro +0 -34
  46. package/dist/components/docs/Footer.astro +0 -112
  47. package/dist/components/docs/Header.astro +0 -157
  48. package/dist/components/docs/PageFrame.astro +0 -260
  49. package/dist/components/docs/PageSidebar.astro +0 -63
  50. package/dist/components/docs/PageTitle.astro +0 -39
  51. package/dist/components/docs/Sidebar.astro +0 -41
  52. package/dist/components/docs/ThemeSelect.astro +0 -5
  53. package/dist/components/forms/ContactForm.astro +0 -233
  54. package/dist/components/forms/FooterSubscribeForm.astro +0 -188
  55. package/dist/components/site/BookList.astro +0 -27
  56. package/dist/components/site/CTASection.astro +0 -24
  57. package/dist/components/site/ChronicleList.astro +0 -33
  58. package/dist/components/site/Hero.astro +0 -18
  59. package/dist/components/site/NotesList.astro +0 -29
  60. package/dist/components/site/PathCard.astro +0 -16
  61. package/dist/components/site/ProfileList.astro +0 -30
  62. package/dist/components/site/PublishedContentBody.astro +0 -5
  63. package/dist/components/site/RouteNotFound.astro +0 -25
  64. package/dist/components/site/SectionIntro.astro +0 -9
  65. package/dist/components/site/StageBanner.astro +0 -8
  66. package/dist/components/site/TrustCallout.astro +0 -9
  67. package/dist/components/starlight.js +0 -6
  68. package/dist/components/ui/data/ActionList.astro +0 -51
  69. package/dist/components/ui/data/Badge.astro +0 -19
  70. package/dist/components/ui/data/DataTable.astro +0 -51
  71. package/dist/components/ui/data/KeyValueList.astro +0 -28
  72. package/dist/components/ui/data/MetricCard.astro +0 -25
  73. package/dist/components/ui/data/MetricGrid.astro +0 -27
  74. package/dist/components/ui/data/StatusPill.astro +0 -20
  75. package/dist/components/ui/forms/Button.astro +0 -59
  76. package/dist/components/ui/forms/Field.astro +0 -39
  77. package/dist/components/ui/forms/FormActions.astro +0 -12
  78. package/dist/components/ui/forms/PasswordMeter.astro +0 -80
  79. package/dist/components/ui/forms/RadioGroup.astro +0 -55
  80. package/dist/components/ui/forms/Select.astro +0 -47
  81. package/dist/components/ui/forms/TextInput.astro +0 -58
  82. package/dist/components/ui/forms/Textarea.astro +0 -45
  83. package/dist/components/ui/layout/PageHeader.astro +0 -45
  84. package/dist/components/ui/shell/AppShell.astro +0 -130
  85. package/dist/components/ui/shell/BottomNav.astro +0 -42
  86. package/dist/components/ui/shell/ProjectHeader.astro +0 -66
  87. package/dist/components/ui/shell/PublicFooter.astro +0 -39
  88. package/dist/components/ui/shell/PublicShell.astro +0 -184
  89. package/dist/components/ui/shell/RailNav.astro +0 -42
  90. package/dist/components/ui/shell/ShellIconLink.astro +0 -30
  91. package/dist/components/ui/shell/TopBar.astro +0 -52
  92. package/dist/components/ui/surface/Card.astro +0 -46
  93. package/dist/components/ui/surface/EmptyState.astro +0 -45
  94. package/dist/components/ui/surface/Panel.astro +0 -54
  95. package/dist/components/ui/theme/ThemeMenu.astro +0 -58
  96. package/dist/components/ui/theme/ThemePreviewSwatch.astro +0 -18
  97. package/dist/components/ui/theme/ThemeScript.astro +0 -112
  98. package/dist/components/ui/theme/ThemeSelector.astro +0 -202
  99. package/dist/components/ui/types.js +0 -0
  100. package/dist/layouts/AuthoredEntryLayout.astro +0 -195
  101. package/dist/layouts/BookLayout.astro +0 -35
  102. package/dist/layouts/BridgeLayout.astro +0 -11
  103. package/dist/layouts/ContentLayout.astro +0 -24
  104. package/dist/layouts/MainLayout.astro +0 -76
  105. package/dist/layouts/NoteLayout.astro +0 -26
  106. package/dist/layouts/ProfileLayout.astro +0 -85
  107. package/dist/styles/app-shell.css +0 -626
  108. package/dist/styles/forms.css +0 -274
  109. package/dist/styles/theme.css +0 -198
  110. package/dist/styles/tokens.css +0 -65
  111. package/dist/styles/ui.css +0 -551
@@ -1,233 +0,0 @@
1
- ---
2
- import {
3
- TREESEED_PUBLIC_TURNSTILE_SITE_KEY,
4
- } from 'astro:env/client';
5
- import { CONTACT_TYPE_LABELS } from '../../utils/forms/constants';
6
- import { CONTACT_TYPES } from '../../types/forms';
7
- import { SITE_FORMS } from '../../utils/site-config';
8
-
9
- const { status, code } = Astro.props as {
10
- status?: string | null;
11
- code?: string | null;
12
- };
13
-
14
- const messages: Record<string, string> = {
15
- success: 'Your message was sent successfully.',
16
- invalid_request: 'The request could not be accepted. Please refresh and try again.',
17
- invalid_form: 'Please complete the required fields and try again.',
18
- captcha_failed: 'Please complete the verification challenge and try again.',
19
- token_invalid: 'The form session expired. Refresh the page and try again.',
20
- token_expired: 'The form session expired. Refresh the page and try again.',
21
- token_replayed: 'This form token has already been used. Please refresh the page.',
22
- rate_limited: 'Too many attempts were detected. Please wait a few minutes and try again.',
23
- delivery_failed: 'We could not deliver the message right now. Please try again shortly.',
24
- };
25
-
26
- const statusMessage = code ? messages[code] ?? null : null;
27
- const shouldBypassTurnstile = import.meta.env.DEV;
28
- const formsApiBaseUrl = SITE_FORMS?.apiBaseUrl ?? '/api/form/submit';
29
- ---
30
-
31
- <section class="max-w-3xl">
32
- {statusMessage && (
33
- <div class:list={[
34
- 'rounded-lg border px-4 py-3 text-sm font-medium',
35
- status === 'success'
36
- ? 'border-[color:var(--ts-color-info)] bg-[color:var(--ts-color-info-soft)] text-[color:var(--ts-color-text)]'
37
- : 'border-[color:var(--ts-color-warning)] bg-[color:var(--ts-color-warning-soft)] text-[color:var(--ts-color-text)]',
38
- ]}>
39
- {statusMessage}
40
- </div>
41
- )}
42
-
43
- <form
44
- method="post"
45
- action={formsApiBaseUrl}
46
- class="js-secure-form mt-6 grid gap-5"
47
- data-form-type="contact"
48
- data-turnstile-action="contact_submit"
49
- >
50
- <p class="text-sm text-[color:var(--ts-color-text-subtle)]">
51
- Fields marked <span class="font-semibold text-[color:var(--ts-color-warning)]">*</span> are required.
52
- </p>
53
- <input type="hidden" name="formType" value="contact" />
54
- <input type="hidden" name="formToken" value="" />
55
- <input type="hidden" name="formSession" value="" />
56
- <input type="hidden" name="redirectTo" value="/contact/" />
57
- <div class="sr-only" aria-hidden="true">
58
- <label>
59
- Website
60
- <input type="text" name="website" tabindex="-1" autocomplete="off" />
61
- </label>
62
- </div>
63
-
64
- <div class="grid gap-5 md:grid-cols-2">
65
- <label class="grid gap-2">
66
- <span class="text-sm font-semibold text-[color:var(--ts-color-text)]">Name <span aria-hidden="true" class="text-[color:var(--ts-color-warning)]">*</span></span>
67
- <input
68
- class="min-h-12 rounded-md border border-[color:var(--ts-color-border-strong)] bg-white/70 px-4 py-3"
69
- type="text"
70
- name="name"
71
- autocomplete="name"
72
- required
73
- aria-required="true"
74
- />
75
- </label>
76
- <label class="grid gap-2">
77
- <span class="text-sm font-semibold text-[color:var(--ts-color-text)]">Email <span aria-hidden="true" class="text-[color:var(--ts-color-warning)]">*</span></span>
78
- <input
79
- class="min-h-12 rounded-md border border-[color:var(--ts-color-border-strong)] bg-white/70 px-4 py-3"
80
- type="email"
81
- name="email"
82
- autocomplete="email"
83
- required
84
- aria-required="true"
85
- />
86
- </label>
87
- </div>
88
-
89
- <div class="grid gap-5 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]">
90
- <label class="grid gap-2">
91
- <span class="text-sm font-semibold text-[color:var(--ts-color-text)]">Organization</span>
92
- <input
93
- class="min-h-12 rounded-md border border-[color:var(--ts-color-border-strong)] bg-white/70 px-4 py-3"
94
- type="text"
95
- name="organization"
96
- autocomplete="organization"
97
- />
98
- </label>
99
- <label class="grid gap-2">
100
- <span class="text-sm font-semibold text-[color:var(--ts-color-text)]">Topic <span aria-hidden="true" class="text-[color:var(--ts-color-warning)]">*</span></span>
101
- <select
102
- class="ts-control ts-control--select min-h-12 rounded-md border border-[color:var(--ts-color-border-strong)] px-4 py-3"
103
- name="contactType"
104
- required
105
- aria-required="true"
106
- >
107
- {CONTACT_TYPES.map((contactType) => (
108
- <option value={contactType}>{CONTACT_TYPE_LABELS[contactType]}</option>
109
- ))}
110
- </select>
111
- </label>
112
- </div>
113
-
114
- <label class="grid gap-2">
115
- <span class="text-sm font-semibold text-[color:var(--ts-color-text)]">Subject <span aria-hidden="true" class="text-[color:var(--ts-color-warning)]">*</span></span>
116
- <input
117
- class="min-h-12 rounded-md border border-[color:var(--ts-color-border-strong)] bg-white/70 px-4 py-3"
118
- type="text"
119
- name="subject"
120
- required
121
- aria-required="true"
122
- />
123
- </label>
124
-
125
- <label class="grid gap-2">
126
- <span class="text-sm font-semibold text-[color:var(--ts-color-text)]">Message <span aria-hidden="true" class="text-[color:var(--ts-color-warning)]">*</span></span>
127
- <textarea
128
- class="min-h-48 rounded-md border border-[color:var(--ts-color-border-strong)] bg-white/70 px-4 py-3"
129
- name="message"
130
- required
131
- aria-required="true"
132
- ></textarea>
133
- </label>
134
-
135
- {TREESEED_PUBLIC_TURNSTILE_SITE_KEY && !shouldBypassTurnstile ? (
136
- <div class="js-turnstile rounded-md border border-dashed border-[color:var(--ts-color-border-strong)] p-3"></div>
137
- ) : shouldBypassTurnstile ? (
138
- <p class="rounded-md border border-[color:var(--ts-color-info)] bg-[color:var(--ts-color-info-soft)] px-4 py-3 text-sm text-[color:var(--ts-color-text)]">
139
- Local development mode is bypassing Turnstile so you can test the form workflow without Cloudflare captcha.
140
- </p>
141
- ) : (
142
- <p class="rounded-md border border-[color:var(--ts-color-warning)] bg-[color:var(--ts-color-warning-soft)] px-4 py-3 text-sm text-[color:var(--ts-color-text)]">
143
- Turnstile is not configured yet. Add `TREESEED_PUBLIC_TURNSTILE_SITE_KEY` before deploying forms.
144
- </p>
145
- )}
146
-
147
- <div class="flex flex-wrap items-center justify-between gap-4">
148
- <p class="text-sm leading-7 text-[color:var(--ts-color-text-subtle)]">
149
- By sending this message, you agree that we may reply by email about this inquiry.
150
- </p>
151
- <button
152
- type="submit"
153
- class="js-submit inline-flex min-h-12 items-center justify-center border border-[color:var(--ts-color-accent)] bg-[color:var(--ts-color-accent)] px-6 py-3 text-base font-semibold text-[color:var(--ts-color-text)] transition hover:border-[color:var(--ts-color-info)] hover:bg-[color:var(--ts-color-info-soft)] disabled:cursor-not-allowed disabled:opacity-60"
154
- disabled
155
- >
156
- Send message
157
- </button>
158
- </div>
159
- </form>
160
- </section>
161
-
162
- <script
163
- is:inline
164
- define:vars={{ siteKey: TREESEED_PUBLIC_TURNSTILE_SITE_KEY, shouldBypassTurnstile, formsApiBaseUrl }}
165
- >
166
- const bootSecureForms = () => {
167
- const forms = Array.from(document.querySelectorAll('.js-secure-form'));
168
-
169
- for (const form of forms) {
170
- if (!(form instanceof HTMLFormElement) || form.dataset.ready === 'true') continue;
171
- form.dataset.ready = 'true';
172
-
173
- const formType = form.dataset.formType;
174
- const action = form.dataset.turnstileAction ?? '';
175
- const tokenField = form.querySelector('input[name="formToken"]');
176
- const sessionField = form.querySelector('input[name="formSession"]');
177
- const submitButton = form.querySelector('.js-submit');
178
- const widgetContainer = form.querySelector('.js-turnstile');
179
-
180
- const enableSubmit = () => {
181
- if (submitButton instanceof HTMLButtonElement) {
182
- submitButton.disabled = !(tokenField instanceof HTMLInputElement && tokenField.value);
183
- }
184
- };
185
-
186
- const assignToken = async () => {
187
- const response = await fetch(`${formsApiBaseUrl}?formType=${encodeURIComponent(formType ?? '')}`, {
188
- headers: { accept: 'application/json' },
189
- credentials: 'same-origin',
190
- });
191
-
192
- const data = await response.json();
193
- if (tokenField instanceof HTMLInputElement && data?.formToken) {
194
- tokenField.value = data.formToken;
195
- }
196
- if (sessionField instanceof HTMLInputElement && data?.sessionId) {
197
- sessionField.value = data.sessionId;
198
- }
199
- enableSubmit();
200
- };
201
-
202
- const renderWidget = () => {
203
- if (shouldBypassTurnstile) return;
204
- if (!siteKey || !(widgetContainer instanceof HTMLElement)) return;
205
- if (!('turnstile' in window) || typeof window.turnstile?.render !== 'function') return;
206
- if (widgetContainer.dataset.rendered === 'true') return;
207
-
208
- window.turnstile.render(widgetContainer, {
209
- sitekey: siteKey,
210
- action,
211
- theme: 'light',
212
- });
213
-
214
- widgetContainer.dataset.rendered = 'true';
215
- };
216
-
217
- assignToken().catch((error) => {
218
- console.error('Unable to initialize form token', error);
219
- });
220
-
221
- if (siteKey) {
222
- renderWidget();
223
- window.addEventListener('load', renderWidget, { once: true });
224
- }
225
- }
226
- };
227
-
228
- if (document.readyState === 'loading') {
229
- document.addEventListener('DOMContentLoaded', bootSecureForms, { once: true });
230
- } else {
231
- bootSecureForms();
232
- }
233
- </script>
@@ -1,188 +0,0 @@
1
- ---
2
- import {
3
- TREESEED_PUBLIC_TURNSTILE_SITE_KEY,
4
- } from 'astro:env/client';
5
- import { FORM_CODE_PARAM, FORM_SUCCESS_PARAM, SUBSCRIBE_ANCHOR_ID } from '../../utils/forms/constants';
6
- import { SITE_FORMS } from '../../utils/site-config';
7
-
8
- const { currentPath = '/' } = Astro.props as { currentPath?: string };
9
- const redirectTarget = `${currentPath}#${SUBSCRIBE_ANCHOR_ID}`;
10
- const shouldBypassTurnstile = import.meta.env.DEV;
11
- const formsApiBaseUrl = SITE_FORMS?.apiBaseUrl ?? '/api/form/submit';
12
- ---
13
-
14
- <section id={SUBSCRIBE_ANCHOR_ID} class="mt-6 border-t border-[color:var(--ts-color-border)] pt-4">
15
- <div class="flex flex-col items-center gap-3 text-center">
16
- <div class="flex flex-col gap-1">
17
- <p class="text-sm font-semibold uppercase tracking-[0.16em] text-[color:var(--ts-color-info-text)]">Subscribe for updates</p>
18
- <p class="text-sm text-[color:var(--ts-color-text-muted)]">
19
- Get occasional notes when new writing or project updates are published.
20
- </p>
21
- </div>
22
- <p class="js-subscribe-status hidden rounded-md border px-4 py-2 text-sm font-medium"></p>
23
- <form
24
- method="post"
25
- action={formsApiBaseUrl}
26
- class="js-secure-form flex w-full max-w-4xl flex-col items-center gap-3"
27
- data-form-type="subscribe"
28
- data-turnstile-action="subscribe_submit"
29
- >
30
- <input type="hidden" name="formType" value="subscribe" />
31
- <input type="hidden" name="formToken" value="" />
32
- <input type="hidden" name="formSession" value="" />
33
- <input type="hidden" name="redirectTo" value={redirectTarget} />
34
- <div class="sr-only" aria-hidden="true">
35
- <label>
36
- Website
37
- <input type="text" name="website" tabindex="-1" autocomplete="off" />
38
- </label>
39
- </div>
40
- <input type="hidden" name="name" value="" />
41
- <div class="flex w-full flex-col items-center justify-center gap-3 md:flex-row md:flex-wrap">
42
- <input
43
- class="min-h-11 w-full rounded-md border border-[color:var(--ts-color-border-strong)] bg-white/80 px-4 py-2 md:w-auto md:min-w-[16rem] md:max-w-[18rem]"
44
- type="email"
45
- name="email"
46
- placeholder="Email address"
47
- autocomplete="email"
48
- required
49
- />
50
- {TREESEED_PUBLIC_TURNSTILE_SITE_KEY && !shouldBypassTurnstile ? (
51
- <div class="js-turnstile min-h-0"></div>
52
- ) : shouldBypassTurnstile ? (
53
- <p class="text-xs text-[color:var(--ts-color-text-subtle)]">Captcha bypassed locally.</p>
54
- ) : (
55
- <p class="text-xs text-[color:var(--ts-color-text-subtle)]">Turnstile not configured.</p>
56
- )}
57
- <button
58
- type="submit"
59
- class="js-submit inline-flex min-h-11 items-center justify-center whitespace-nowrap border border-[color:var(--ts-color-accent)] bg-[color:var(--ts-color-accent)] px-4 py-2 text-sm font-semibold text-[color:var(--ts-color-text)] transition hover:border-[color:var(--ts-color-info)] hover:bg-[color:var(--ts-color-info-soft)] disabled:cursor-not-allowed disabled:opacity-60"
60
- disabled
61
- >
62
- Subscribe
63
- </button>
64
- </div>
65
- </form>
66
- </div>
67
- </section>
68
-
69
- {TREESEED_PUBLIC_TURNSTILE_SITE_KEY && !shouldBypassTurnstile && (
70
- <script is:inline src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit" async defer></script>
71
- )}
72
-
73
- <script
74
- is:inline
75
- define:vars={{
76
- siteKey: TREESEED_PUBLIC_TURNSTILE_SITE_KEY,
77
- shouldBypassTurnstile,
78
- formsApiBaseUrl,
79
- successParam: FORM_SUCCESS_PARAM,
80
- codeParam: FORM_CODE_PARAM,
81
- subscribeAnchorId: SUBSCRIBE_ANCHOR_ID,
82
- }}
83
- >
84
- const subscribeMessages = {
85
- success: 'You are subscribed. Thanks for following along.',
86
- invalid_form: 'Please provide your email address.',
87
- captcha_failed: 'Please complete the verification challenge and try again.',
88
- token_invalid: 'Please refresh the page and try again.',
89
- token_expired: 'Please refresh the page and try again.',
90
- rate_limited: 'Too many attempts were detected. Please wait a few minutes.',
91
- delivery_failed: 'We could not finish the signup right now. Please try again shortly.',
92
- };
93
-
94
- const syncSubscribeStatus = () => {
95
- const statusNode = document.querySelector('.js-subscribe-status');
96
- if (!(statusNode instanceof HTMLElement)) return;
97
-
98
- const params = new URLSearchParams(window.location.search);
99
- const status = params.get(successParam);
100
- const code = params.get(codeParam);
101
-
102
- if (!status || !code || window.location.hash !== `#${subscribeAnchorId}`) {
103
- return;
104
- }
105
-
106
- statusNode.textContent = subscribeMessages[code] ?? (status === 'success' ? 'Subscription complete.' : 'We could not complete the signup.');
107
- statusNode.classList.remove('hidden');
108
- if (status === 'success') {
109
- statusNode.className = 'js-subscribe-status rounded-md border border-[color:var(--ts-color-info)] bg-[color:var(--ts-color-info-soft)] px-4 py-2 text-sm font-medium text-[color:var(--ts-color-text)]';
110
- } else {
111
- statusNode.className = 'js-subscribe-status rounded-md border border-[color:var(--ts-color-warning)] bg-[color:var(--ts-color-warning-soft)] px-4 py-2 text-sm font-medium text-[color:var(--ts-color-text)]';
112
- }
113
- };
114
-
115
- if (document.readyState === 'loading') {
116
- document.addEventListener('DOMContentLoaded', syncSubscribeStatus, { once: true });
117
- } else {
118
- syncSubscribeStatus();
119
- }
120
-
121
- const bootSecureForms = () => {
122
- const forms = Array.from(document.querySelectorAll('.js-secure-form'));
123
-
124
- for (const form of forms) {
125
- if (!(form instanceof HTMLFormElement) || form.dataset.ready === 'true') continue;
126
- form.dataset.ready = 'true';
127
-
128
- const formType = form.dataset.formType;
129
- const action = form.dataset.turnstileAction ?? '';
130
- const tokenField = form.querySelector('input[name="formToken"]');
131
- const sessionField = form.querySelector('input[name="formSession"]');
132
- const submitButton = form.querySelector('.js-submit');
133
- const widgetContainer = form.querySelector('.js-turnstile');
134
-
135
- const enableSubmit = () => {
136
- if (submitButton instanceof HTMLButtonElement) {
137
- submitButton.disabled = !(tokenField instanceof HTMLInputElement && tokenField.value);
138
- }
139
- };
140
-
141
- const assignToken = async () => {
142
- const response = await fetch(`${formsApiBaseUrl}?formType=${encodeURIComponent(formType ?? '')}`, {
143
- headers: { accept: 'application/json' },
144
- credentials: 'same-origin',
145
- });
146
-
147
- const data = await response.json();
148
- if (tokenField instanceof HTMLInputElement && data?.formToken) {
149
- tokenField.value = data.formToken;
150
- }
151
- if (sessionField instanceof HTMLInputElement && data?.sessionId) {
152
- sessionField.value = data.sessionId;
153
- }
154
- enableSubmit();
155
- };
156
-
157
- const renderWidget = () => {
158
- if (shouldBypassTurnstile) return;
159
- if (!siteKey || !(widgetContainer instanceof HTMLElement)) return;
160
- if (!('turnstile' in window) || typeof window.turnstile?.render !== 'function') return;
161
- if (widgetContainer.dataset.rendered === 'true') return;
162
-
163
- window.turnstile.render(widgetContainer, {
164
- sitekey: siteKey,
165
- action,
166
- theme: 'light',
167
- });
168
-
169
- widgetContainer.dataset.rendered = 'true';
170
- };
171
-
172
- assignToken().catch((error) => {
173
- console.error('Unable to initialize form token', error);
174
- });
175
-
176
- if (siteKey) {
177
- renderWidget();
178
- window.addEventListener('load', renderWidget, { once: true });
179
- }
180
- }
181
- };
182
-
183
- if (document.readyState === 'loading') {
184
- document.addEventListener('DOMContentLoaded', bootSecureForms, { once: true });
185
- } else {
186
- bootSecureForms();
187
- }
188
- </script>
@@ -1,27 +0,0 @@
1
- ---
2
- type BookItem = {
3
- href: string;
4
- title: string;
5
- summary: string;
6
- meta: string;
7
- landingPath: string;
8
- downloadHref: string;
9
- };
10
-
11
- const { items } = Astro.props as { items: BookItem[] };
12
- ---
13
-
14
- <div class="grid gap-6 md:grid-cols-2">
15
- {items.map((item) => (
16
- <div class="border border-[color:var(--ts-color-border)] bg-[color:var(--ts-color-surface)] p-6">
17
- <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">{item.meta}</p>
18
- <h3 class="mt-4 text-2xl font-bold text-[color:var(--ts-color-text)]">{item.title}</h3>
19
- <p class="mt-3 text-base leading-8 text-[color:var(--ts-color-text-muted)]">{item.summary}</p>
20
- <div class="mt-5 flex flex-wrap gap-3">
21
- <a href={item.href} class="border border-[color:var(--ts-color-border-strong)] px-4 py-2 text-sm font-semibold text-[color:var(--ts-color-text)] transition hover:border-[color:var(--ts-color-info)] hover:bg-[color:var(--ts-color-info-soft)]">Book page</a>
22
- <a href={item.landingPath} class="border border-[color:var(--ts-color-border-strong)] px-4 py-2 text-sm font-semibold text-[color:var(--ts-color-text)] transition hover:border-[color:var(--ts-color-info)] hover:bg-[color:var(--ts-color-info-soft)]">Open knowledge</a>
23
- <a href={item.downloadHref} class="border border-[color:var(--ts-color-accent)] bg-[color:var(--ts-color-accent)] px-4 py-2 text-sm font-semibold text-[color:var(--ts-color-text)] transition hover:border-[color:var(--ts-color-info)] hover:bg-[color:var(--ts-color-info-soft)]">Download</a>
24
- </div>
25
- </div>
26
- ))}
27
- </div>
@@ -1,24 +0,0 @@
1
- ---
2
- const { title, body, primaryHref, primaryLabel, secondaryHref, secondaryLabel } = Astro.props;
3
- ---
4
-
5
- <section class="site-footer-cta px-0 py-0 text-[color:var(--ts-color-text)]">
6
- <div class="site-footer-cta__inner">
7
- <div class="mx-auto flex max-w-4xl flex-col items-center gap-6">
8
- <div class="max-w-3xl">
9
- <h2 class="font-serif text-3xl font-bold md:text-4xl">{title}</h2>
10
- <p class="mt-4 text-lg leading-9 text-[color:var(--ts-color-text-muted)]">{body}</p>
11
- </div>
12
- <div class="flex flex-wrap justify-center gap-3">
13
- <a href={primaryHref} class="border border-[color:var(--ts-color-accent)] bg-[color:var(--ts-color-accent)] px-5 py-3 text-base font-semibold text-[color:var(--ts-color-text)] transition hover:border-[color:var(--ts-color-info)] hover:bg-[color:var(--ts-color-info-soft)]">{
14
- primaryLabel
15
- }</a>
16
- {secondaryHref && secondaryLabel && (
17
- <a href={secondaryHref} class="border border-[color:var(--ts-color-border-strong)] bg-[color:var(--ts-color-surface-muted)] px-5 py-3 text-base font-semibold text-[color:var(--ts-color-text)] transition hover:border-[color:var(--ts-color-info)] hover:bg-[color:var(--ts-color-info-soft)]">
18
- {secondaryLabel}
19
- </a>
20
- )}
21
- </div>
22
- </div>
23
- </div>
24
- </section>
@@ -1,33 +0,0 @@
1
- ---
2
- import type { ContentStatus } from '../../utils/content-status';
3
- import StatusBadge from '../content/StatusBadge.astro';
4
-
5
- type ChronicleItem = {
6
- href: string;
7
- title: string;
8
- summary: string;
9
- status: ContentStatus;
10
- date: Date;
11
- meta?: string;
12
- tags?: string[];
13
- };
14
-
15
- const { items } = Astro.props as { items: ChronicleItem[] };
16
- ---
17
-
18
- <div class="grid gap-1">
19
- {items.map((item) => (
20
- <a href={item.href} class="block border-t border-[color:var(--ts-color-border)] py-7 transition hover:border-[color:var(--ts-color-border-strong)]">
21
- <div class="flex flex-wrap items-center gap-4">
22
- <StatusBadge status={item.status} />
23
- <p class="text-sm font-medium text-[color:var(--ts-color-text-subtle)]">{item.date.toISOString().slice(0, 10)}</p>
24
- {item.meta && <p class="text-sm text-[color:var(--ts-color-text-subtle)]">{item.meta}</p>}
25
- </div>
26
- <h3 class="mt-4 max-w-3xl text-2xl font-bold text-[color:var(--ts-color-text)] md:text-3xl">{item.title}</h3>
27
- <p class="mt-3 max-w-3xl text-lg leading-9 text-[color:var(--ts-color-text-muted)]">{item.summary}</p>
28
- {item.tags && item.tags.length > 0 && (
29
- <p class="mt-4 text-sm uppercase tracking-[0.14em] text-[color:var(--ts-color-accent-strong)]">{item.tags.join(' / ')}</p>
30
- )}
31
- </a>
32
- ))}
33
- </div>
@@ -1,18 +0,0 @@
1
- <section class="relative overflow-hidden border-y border-[color:var(--ts-color-border-strong)] py-12 md:py-18">
2
- <div class="absolute inset-y-0 left-[68%] hidden w-px bg-[linear-gradient(180deg,transparent,var(--ts-color-border-strong),transparent)] lg:block"></div>
3
- <div class="grid gap-10 lg:grid-cols-[1.45fr_0.7fr] lg:items-start">
4
- <div class="space-y-7 pr-0 lg:pr-10">
5
- <slot name="eyebrow" />
6
- <div class="space-y-4">
7
- <slot name="title" />
8
- <slot name="body" />
9
- </div>
10
- <div class="flex flex-wrap gap-4 pt-2">
11
- <slot name="actions" />
12
- </div>
13
- </div>
14
- <div class="border-l border-[color:var(--ts-color-border)] pl-0 lg:pl-8">
15
- <slot name="aside" />
16
- </div>
17
- </div>
18
- </section>
@@ -1,29 +0,0 @@
1
- ---
2
- import ChronicleList from './ChronicleList.astro';
3
-
4
- type NoteListEntry = {
5
- id: string;
6
- data: {
7
- title: string;
8
- summary: string;
9
- status: 'live' | 'in progress' | 'exploratory' | 'planned' | 'speculative';
10
- date: Date;
11
- author?: string;
12
- tags: string[];
13
- };
14
- };
15
-
16
- const { notes } = Astro.props as { notes: NoteListEntry[] };
17
- ---
18
-
19
- <ChronicleList
20
- items={notes.map((note) => ({
21
- href: `/notes/${note.id}/`,
22
- title: note.data.title,
23
- summary: note.data.summary,
24
- status: note.data.status,
25
- date: note.data.date,
26
- meta: note.data.author,
27
- tags: note.data.tags,
28
- }))}
29
- />
@@ -1,16 +0,0 @@
1
- ---
2
- const { href, title, description, meta } = Astro.props;
3
- ---
4
-
5
- <a href={href} class="group block border-t border-[color:var(--ts-color-border)] py-6 transition hover:border-[color:var(--ts-color-border-strong)]">
6
- <div class="grid gap-3 md:grid-cols-[0.42fr_1fr_auto] md:items-start md:gap-6">
7
- <div>
8
- {meta && <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-accent-strong)]">{meta}</p>}
9
- </div>
10
- <div>
11
- <h3 class="text-2xl font-bold text-[color:var(--ts-color-text)]">{title}</h3>
12
- <p class="mt-3 max-w-2xl text-base leading-8 text-[color:var(--ts-color-text-muted)]">{description}</p>
13
- </div>
14
- <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)] group-hover:text-[color:var(--ts-color-accent-strong)]">Open path</p>
15
- </div>
16
- </a>
@@ -1,30 +0,0 @@
1
- ---
2
- import StatusBadge from '../content/StatusBadge.astro';
3
-
4
- type ProfileItem = {
5
- href: string;
6
- name: string;
7
- summary: string;
8
- meta: string;
9
- status?: 'live' | 'in progress' | 'exploratory' | 'planned' | 'speculative';
10
- tags?: string[];
11
- };
12
-
13
- const { items } = Astro.props as { items: ProfileItem[] };
14
- ---
15
-
16
- <div class="grid gap-6 md:grid-cols-2">
17
- {items.map((item) => (
18
- <a href={item.href} class="border border-[color:var(--ts-color-border)] bg-[color:var(--ts-color-surface)] p-6 transition hover:border-[color:var(--ts-color-border-strong)]">
19
- <div class="flex flex-wrap items-center gap-3">
20
- {item.status && <StatusBadge status={item.status} />}
21
- <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-info-text)]">{item.meta}</p>
22
- </div>
23
- <h3 class="mt-4 text-2xl font-bold text-[color:var(--ts-color-text)]">{item.name}</h3>
24
- <p class="mt-3 text-base leading-8 text-[color:var(--ts-color-text-muted)]">{item.summary}</p>
25
- {item.tags && item.tags.length > 0 && (
26
- <p class="mt-4 text-sm uppercase tracking-[0.14em] text-[color:var(--ts-color-accent-strong)]">{item.tags.join(' / ')}</p>
27
- )}
28
- </a>
29
- ))}
30
- </div>
@@ -1,5 +0,0 @@
1
- ---
2
- const { html } = Astro.props as { html: string };
3
- ---
4
-
5
- <Fragment set:html={html} />
@@ -1,25 +0,0 @@
1
- ---
2
- import MainLayout from '../../layouts/MainLayout.astro';
3
-
4
- const {
5
- title = 'Page not found',
6
- description = 'The requested Treeseed content could not be found.',
7
- currentPath = '/404/',
8
- } = Astro.props;
9
- ---
10
-
11
- <MainLayout title={title} description={description} currentPath={currentPath}>
12
- <section class="mx-auto max-w-3xl space-y-6 py-20">
13
- <p class="text-sm font-semibold uppercase tracking-[0.16em] text-[color:var(--ts-color-accent-strong)]">404</p>
14
- <h1 class="font-serif text-5xl text-[color:var(--ts-color-text)]">{title}</h1>
15
- <p class="text-lg leading-9 text-[color:var(--ts-color-text-muted)]">{description}</p>
16
- <div class="flex flex-wrap gap-4">
17
- <a href="/" class="border border-[color:var(--ts-color-accent)] bg-[color:var(--ts-color-accent)] px-5 py-3 text-base font-semibold text-[color:var(--ts-color-text)] transition hover:border-[color:var(--ts-color-info)] hover:bg-[color:var(--ts-color-info-soft)]">
18
- Go home
19
- </a>
20
- <a href="/books/" class="border border-[color:var(--ts-color-border-strong)] px-5 py-3 text-base font-semibold text-[color:var(--ts-color-text)] transition hover:border-[color:var(--ts-color-info)] hover:bg-[color:var(--ts-color-info-soft)]">
21
- Open books
22
- </a>
23
- </div>
24
- </section>
25
- </MainLayout>
@@ -1,9 +0,0 @@
1
- ---
2
- const { eyebrow, title, description } = Astro.props;
3
- ---
4
-
5
- <header class="max-w-4xl space-y-4">
6
- {eyebrow && <p class="text-sm font-semibold uppercase tracking-[0.16em] text-[color:var(--ts-color-accent-strong)]">{eyebrow}</p>}
7
- <h2 class="max-w-4xl font-serif text-4xl font-bold tracking-tight text-[color:var(--ts-color-text)] md:text-5xl">{title}</h2>
8
- <p class="max-w-3xl text-lg leading-9 text-[color:var(--ts-color-text-muted)] md:text-xl">{description}</p>
9
- </header>
@@ -1,8 +0,0 @@
1
- ---
2
- import { PROJECT_STAGE } from '../../utils/content-status';
3
- ---
4
-
5
- <div class="inline-flex max-w-full flex-wrap items-center gap-x-3 gap-y-1 border-l-4 border-[color:var(--ts-color-accent)] bg-[color:var(--ts-color-accent-soft)] px-4 py-3 text-[color:var(--ts-color-text)]">
6
- <span class="font-bold uppercase tracking-[0.16em] text-xs">{PROJECT_STAGE.label}</span>
7
- <span class="text-[color:var(--ts-color-text-muted)]">{PROJECT_STAGE.description}</span>
8
- </div>