astro-tractstack 2.0.47 → 2.1.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 (119) hide show
  1. package/README.md +35 -39
  2. package/package.json +25 -20
  3. package/templates/src/components/Footer.astro +4 -4
  4. package/templates/src/components/Header.astro +6 -6
  5. package/templates/src/components/Menu.tsx +15 -15
  6. package/templates/src/components/codehooks/BunnyVideoSetup.tsx +6 -6
  7. package/templates/src/components/codehooks/EpinetDurationSelector.tsx +3 -3
  8. package/templates/src/components/codehooks/FeaturedArticle.astro +1 -1
  9. package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +3 -3
  10. package/templates/src/components/codehooks/ListContentSetup.tsx +2 -2
  11. package/templates/src/components/codehooks/ProductCardSetup.tsx +1 -1
  12. package/templates/src/components/codehooks/ProductGridSetup.tsx +2 -2
  13. package/templates/src/components/codehooks/SandboxRegisterForm.tsx +3 -3
  14. package/templates/src/components/compositor/Compositor.tsx +1 -1
  15. package/templates/src/components/compositor/elements/Belief.tsx +1 -1
  16. package/templates/src/components/compositor/elements/BunnyVideo.tsx +2 -2
  17. package/templates/src/components/compositor/elements/SignUp.tsx +1 -1
  18. package/templates/src/components/compositor/elements/ToggleBelief.tsx +1 -1
  19. package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +1 -1
  20. package/templates/src/components/compositor/nodes/GridLayout.tsx +1 -1
  21. package/templates/src/components/compositor/nodes/tagElements/TabIndicator.tsx +1 -1
  22. package/templates/src/components/compositor/preview/ListContentPreview.tsx +8 -8
  23. package/templates/src/components/edit/Header.tsx +1 -1
  24. package/templates/src/components/edit/SettingsPanel.tsx +3 -3
  25. package/templates/src/components/edit/ToolBar.tsx +3 -3
  26. package/templates/src/components/edit/ToolMode.tsx +5 -5
  27. package/templates/src/components/edit/pane/AddPanePanel.tsx +5 -5
  28. package/templates/src/components/edit/pane/AddPanePanel_break.tsx +6 -6
  29. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +2 -2
  30. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +2 -2
  31. package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +4 -4
  32. package/templates/src/components/edit/pane/AiRestylePaneModal.tsx +2 -2
  33. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -1
  34. package/templates/src/components/edit/pane/PanePanel_impression.tsx +4 -4
  35. package/templates/src/components/edit/pane/RestylePaneModal.tsx +3 -3
  36. package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +2 -2
  37. package/templates/src/components/edit/pane/steps/CopyInputStep.tsx +5 -5
  38. package/templates/src/components/edit/panels/StyleBreakPanel.tsx +2 -2
  39. package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +6 -6
  40. package/templates/src/components/edit/panels/StyleElementPanel.tsx +5 -5
  41. package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +6 -6
  42. package/templates/src/components/edit/panels/StyleElementPanel_remove.tsx +1 -1
  43. package/templates/src/components/edit/panels/StyleElementPanel_update.tsx +3 -3
  44. package/templates/src/components/edit/panels/StyleImagePanel.tsx +8 -8
  45. package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +6 -6
  46. package/templates/src/components/edit/panels/StyleImagePanel_remove.tsx +1 -1
  47. package/templates/src/components/edit/panels/StyleImagePanel_update.tsx +3 -3
  48. package/templates/src/components/edit/panels/StyleLiElementPanel.tsx +4 -4
  49. package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +6 -6
  50. package/templates/src/components/edit/panels/StyleLiElementPanel_remove.tsx +1 -1
  51. package/templates/src/components/edit/panels/StyleLiElementPanel_update.tsx +3 -3
  52. package/templates/src/components/edit/panels/StyleLinkPanel.tsx +5 -5
  53. package/templates/src/components/edit/panels/StyleLinkPanel_add.tsx +6 -6
  54. package/templates/src/components/edit/panels/StyleLinkPanel_remove.tsx +1 -1
  55. package/templates/src/components/edit/panels/StyleLinkPanel_update.tsx +1 -1
  56. package/templates/src/components/edit/panels/StyleParentPanel.tsx +9 -9
  57. package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +6 -6
  58. package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +2 -2
  59. package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +1 -1
  60. package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +1 -1
  61. package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +7 -7
  62. package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +6 -6
  63. package/templates/src/components/edit/panels/StyleWidgetPanel_remove.tsx +1 -1
  64. package/templates/src/components/edit/panels/StyleWidgetPanel_update.tsx +3 -3
  65. package/templates/src/components/edit/panels/StyleWordCarouselPanel.tsx +1 -1
  66. package/templates/src/components/edit/state/SaveModal.tsx +1 -1
  67. package/templates/src/components/edit/state/SaveToLibraryModal.tsx +2 -2
  68. package/templates/src/components/edit/state/StylesMemory.tsx +1 -1
  69. package/templates/src/components/edit/storyfragment/StoryFragmentConfigPanel.tsx +1 -1
  70. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +5 -5
  71. package/templates/src/components/edit/widgets/BunnyWidget.tsx +5 -5
  72. package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +1 -1
  73. package/templates/src/components/edit/widgets/SignupWidget.tsx +3 -3
  74. package/templates/src/components/fields/ActionBuilderTimeSelector.tsx +7 -7
  75. package/templates/src/components/fields/ArtpackImage.tsx +15 -15
  76. package/templates/src/components/fields/BackgroundImage.tsx +13 -13
  77. package/templates/src/components/fields/BackgroundImageWrapper.tsx +3 -3
  78. package/templates/src/components/fields/ColorPickerCombo.tsx +8 -8
  79. package/templates/src/components/fields/ImageUpload.tsx +8 -8
  80. package/templates/src/components/fields/PaneBreakCollectionSelector.tsx +3 -3
  81. package/templates/src/components/fields/PaneBreakShapeSelector.tsx +4 -4
  82. package/templates/src/components/fields/SelectedTailwindClass.tsx +2 -2
  83. package/templates/src/components/fields/ViewportComboBox.tsx +4 -4
  84. package/templates/src/components/form/ActionBuilderField.tsx +1 -1
  85. package/templates/src/components/form/ActionBuilderSlugSelector.tsx +1 -1
  86. package/templates/src/components/form/UnsavedChangesBar.tsx +1 -1
  87. package/templates/src/components/form/advanced/APIConfigSection.tsx +2 -2
  88. package/templates/src/components/form/advanced/AuthConfigSection.tsx +2 -2
  89. package/templates/src/components/form/brand/BrandColorsSection.tsx +1 -1
  90. package/templates/src/components/profile/ProfileCreate.tsx +4 -4
  91. package/templates/src/components/profile/ProfileEdit.tsx +4 -4
  92. package/templates/src/components/profile/ProfileSwitch.tsx +2 -2
  93. package/templates/src/components/profile/ProfileUnlock.tsx +3 -3
  94. package/templates/src/components/search/SearchModal.tsx +10 -10
  95. package/templates/src/components/search/SearchResults.tsx +10 -10
  96. package/templates/src/components/search/SearchWrapper.tsx +1 -1
  97. package/templates/src/components/storykeep/Dashboard.tsx +1 -1
  98. package/templates/src/components/storykeep/Dashboard_Advanced.tsx +2 -2
  99. package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +1 -1
  100. package/templates/src/components/storykeep/controls/content/ContentSummary.tsx +2 -2
  101. package/templates/src/components/storykeep/controls/content/KnownResourceTable.tsx +1 -1
  102. package/templates/src/components/storykeep/controls/content/ManageContent.tsx +2 -2
  103. package/templates/src/components/storykeep/controls/content/MenuForm.tsx +1 -1
  104. package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +1 -1
  105. package/templates/src/components/widgets/Impression.tsx +2 -2
  106. package/templates/src/components/widgets/ImpressionWrapper.tsx +2 -2
  107. package/templates/src/hooks/useSearch.ts +1 -1
  108. package/templates/src/layouts/Layout.astro +2 -2
  109. package/templates/src/pages/[...slug]/edit.astro +3 -3
  110. package/templates/src/pages/[...slug].astro +1 -1
  111. package/templates/src/pages/context/[...contextSlug]/edit.astro +3 -3
  112. package/templates/src/pages/maint.astro +9 -9
  113. package/templates/src/pages/sandbox.astro +50 -61
  114. package/templates/src/pages/storykeep/login.astro +6 -6
  115. package/templates/src/types/formTypes.ts +6 -4
  116. package/templates/src/types/nodeProps.ts +1 -1
  117. package/templates/src/types/tractstack.ts +1 -2
  118. package/templates/src/utils/compositor/nodesHelper.ts +1 -1
  119. package/templates/src/utils/compositor/typeGuards.ts +3 -3
package/README.md CHANGED
@@ -1,23 +1,43 @@
1
- # TractStack v2
1
+ # TractStack v2.1
2
2
 
3
- **Redeeming the web from boring experiences**
3
+ **The Digital Experience Platform (DXP) for the Missing Middle.**
4
4
 
5
- Free web press by [At Risk Media](https://atriskmedia.com)
5
+ > "Static websites treat every visitor like a stranger. TractStack treats them like a relationship."
6
6
 
7
- ## Epistemic Hypermedia Server
7
+ ## The Problem: The Legacy Web has Amnesia
8
8
 
9
- TractStack is a new species of web platform that makes it possible for millions of websites to adapt to each visitor instead of showing everyone the same thing. It's an **adaptive website builder** that creates fast, beautiful, SEO-ready, and accessible websites that respond intelligently to user behavior.
9
+ Static technology forces you to serve the exact same generic monologue to everyone—whether they are a "tire kicker" or a "high-ticket buyer."
10
10
 
11
- Built on [Astro](https://astro.build/) with [HTMX](https://htmx.org/) and a [Golang](https://go.dev/) backend, TractStack uses SQLite by default with optional [Turso](https://app.turso.tech/) cloud database support.
11
+ Legacy tools (Wix, WordPress) suffer from **Intent Blindness**. They were built for page views, not signal clarity. They force your best leads to dig through a haystack to find the "hidden door" they need, causing them to bounce.
12
12
 
13
- ## Key Features
13
+ ## The Solution: A Living Funnel
14
14
 
15
- - **Adaptive Content**: Websites that dynamically respond to visitor behavior
16
- - **Multi-tenant Architecture**: Host multiple sites from a single installation
17
- - **Built-in CMS**: StoryKeep content management system
18
- - **Real-time Updates**: Server-sent events and HTMX for dynamic interactions
19
- - **Production Ready**: SSL certificates, nginx integration, systemd services
20
- - **Zero Config Database**: Works out of the box with SQLite3
15
+ TractStack is an **Epistemic Hypermedia Server** that acts as a triage nurse, not a brochure. It turns a fragile "page view" into a powerful, privacy-first user journey.
16
+
17
+ ### Core Philosophy
18
+
19
+ 1. **The Recall (No Cookies Required):** We moved "memory" from fragile cookies to a proprietary server-side logic engine. This ensures the conversation persists across privacy blocks and browser refreshes.
20
+ 2. **Smart Triage:** Our logic engine identifies 'High-Intent' users instantly and accelerates them to conversion, while automatically nurturing the rest.
21
+ 3. **Hidden Doors:** Instead of a static page, you define content paths that only open when a user signals interest.
22
+
23
+ ---
24
+
25
+ ## The Tech Stack: "Convert OS"
26
+
27
+ TractStack is a hybrid engine designed for performance and logic.
28
+
29
+ - **Frontend:** [Astro](https://astro.build/) (The "Free" Web Press). Renders SEO-ready, indexable HTML.
30
+ - **Backend:** [Golang](https://go.dev/) (The Logic Processor). A compiled, server-side engine that manages state, session continuity, and adaptive content.
31
+ - **Glue:** [HTMX](https://htmx.org/). Enables real-time, dynamic interactions without heavy client-side frameworks.
32
+ - **Database:** SQLite by default (Zero Config) with optional [Turso](https://app.turso.tech/) support.
33
+
34
+ ## For Agencies & Empire Builders
35
+
36
+ TractStack was engineered for the "Missing Middle"—high-volume creators and agencies who have outgrown static sites but don't need enterprise bloat.
37
+
38
+ - **Wholesale DXP:** An infrastructure-first model. Agencies pay a flat wholesale fee and own the recurring margin on "Growth Projects."
39
+ - **Visual Journey Mapping:** Don't guess what works. See it. TractStack visualizes the narrative journey of your audience, showing exactly how they navigate your Hidden Doors.
40
+ - **No Code Required:** Build engaging, interactive websites where the "general noise" fades and specific content triggers based on engagement.
21
41
 
22
42
  ## Quick Install
23
43
 
@@ -174,22 +194,6 @@ TractStack v2 includes powerful multi-tenant capabilities:
174
194
  - **Capacity Management**: Configurable tenant limits
175
195
  - **Email Activation**: Automated tenant activation emails
176
196
 
177
- ## SSL Certificate Management
178
-
179
- ### Cloudflare DNS (Automated)
180
-
181
- Create `/root/.secrets/certbot/cloudflare.ini`:
182
-
183
- ```ini
184
- dns_cloudflare_api_token = YOUR_API_TOKEN_HERE
185
- ```
186
-
187
- Certificates are obtained automatically during installation.
188
-
189
- ### Manual DNS Verification
190
-
191
- Without Cloudflare, the installer guides you through manual DNS TXT record verification.
192
-
193
197
  ## Service Management
194
198
 
195
199
  ### Main Installation
@@ -291,17 +295,9 @@ sudo ~/t8k/src/tractstack-go/pkg/scripts/t8k-uninstall.sh
291
295
  ## License
292
296
 
293
297
  The frontend Astro integration is available via the MIT license.
294
-
295
- The backend epistemic hypermedia server is available via the **Functional Source License (FSL)** - Commercial use encouraged!
296
-
297
- The only restriction is no re-selling TractStack as-a-service. Perfect for:
298
-
299
- - Agency client projects
300
- - Corporate websites
301
- - Personal projects
302
- - Open source contributions
298
+ The backend epistemic hypermedia server is available via the **O'Sassy Open Source License**
303
299
 
304
300
  ---
305
301
 
306
- _TractStack v2 - Making the web adaptive, one site at a time_
302
+ TractStack v2.1
307
303
  _Made with ❤️ by [At Risk Media](https://atriskmedia.com)_
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "astro-tractstack",
3
- "version": "2.0.47",
4
- "description": "Astro integration for TractStack - redeeming the web from boring experiences",
3
+ "version": "2.1.1",
4
+ "description": "Astro integration for TractStack - the digital experience platform (DXP) for the missing middle",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "exports": {
@@ -44,42 +44,46 @@
44
44
  "registry": "https://registry.npmjs.org/"
45
45
  },
46
46
  "peerDependencies": {
47
- "@astrojs/react": "^4.0.0",
48
- "astro": "^5.0.0"
47
+ "@astrojs/react": "^4.4.2",
48
+ "astro": "^5.0.0",
49
+ "react": "^19.0.0",
50
+ "react-dom": "^19.0.0"
49
51
  },
50
52
  "dependencies": {
51
53
  "kleur": "^4.1.5",
52
54
  "prompts": "^2.4.2"
53
55
  },
54
56
  "devDependencies": {
55
- "@ark-ui/react": "^5.26.0",
56
- "@astrojs/react": "^3.6.3",
57
+ "@ark-ui/react": "^5.30.0",
58
+ "@astrojs/react": "^4.4.2",
57
59
  "@heroicons/react": "^2.1.1",
58
- "@internationalized/date": "^3.10.0",
60
+ "@internationalized/date": "^3.10.1",
59
61
  "@mhsdesign/jit-browser-tailwindcss": "^0.4.2",
60
- "@nanostores/persistent": "^1.1.0",
62
+ "@nanostores/persistent": "^1.2.0",
61
63
  "@nanostores/react": "^1.0.0",
62
64
  "@types/d3": "^7.4.3",
63
- "@types/d3-sankey": "^0.12.3",
65
+ "@types/d3-sankey": "^0.12.5",
64
66
  "@types/node": "^22.18.0",
65
67
  "@types/prompts": "^2.4.9",
66
- "@types/react": "18.3.11",
67
- "@types/react-dom": "18.3.0",
68
+ "@types/react": "19.2.7",
69
+ "@types/react-dom": "19.2.3",
68
70
  "@types/tinycolor2": "^1.4.6",
69
- "astro": "^5.13.3",
71
+ "astro": "^5.16.6",
70
72
  "d3": "^7.9.0",
71
73
  "d3-sankey": "^0.12.3",
72
74
  "html-to-image": "^1.11.13",
73
- "nanostores": "^1.0.1",
75
+ "nanostores": "^1.1.0",
74
76
  "player.js": "^0.1.0",
75
- "prettier": "3.3.3",
77
+ "prettier": "3.7.4",
76
78
  "prettier-plugin-astro": "0.14.1",
77
- "prettier-plugin-tailwindcss": "0.6.8",
78
- "recharts": "^3.1.2",
79
+ "prettier-plugin-tailwindcss": "0.7.2",
80
+ "react": "^19.2.3",
81
+ "react-dom": "^19.2.3",
82
+ "recharts": "^3.6.0",
79
83
  "tinycolor2": "^1.6.0",
80
- "typescript": "^5.9.2",
81
- "ulid": "^3.0.1",
82
- "vite": "^7.1.3",
84
+ "typescript": "^5.9.3",
85
+ "ulid": "^3.0.2",
86
+ "vite": "^7.3.0",
83
87
  "vite-plugin-dts": "^4.5.4"
84
88
  },
85
89
  "engines": {
@@ -88,7 +92,8 @@
88
92
  "packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0",
89
93
  "pnpm": {
90
94
  "overrides": {
91
- "esbuild@<=0.24.2": ">=0.25.0"
95
+ "esbuild@<=0.24.2": ">=0.25.0",
96
+ "@internationalized/date": "3.10.1"
92
97
  }
93
98
  }
94
99
  }
@@ -106,7 +106,7 @@ const createdDate = created ? new Date(created) : new Date();
106
106
  {allLinks.map((item: any) => (
107
107
  <a
108
108
  href={item.to}
109
- class="bg-brand-7 hover:bg-myblack focus:bg-brand-7 z-10 whitespace-nowrap rounded px-3.5 py-1.5 text-lg text-white shadow-sm transition-colors hover:text-white focus:text-white"
109
+ class="z-10 whitespace-nowrap rounded bg-brand-7 px-3.5 py-1.5 text-lg text-white shadow-sm transition-colors hover:bg-myblack hover:text-white focus:bg-brand-7 focus:text-white"
110
110
  title={item.description}
111
111
  >
112
112
  <span class="font-bold">{item.name}</span>
@@ -126,7 +126,7 @@ const createdDate = created ? new Date(created) : new Date();
126
126
  .map((social: string[]) => (
127
127
  <a
128
128
  href={social[1]}
129
- class="hover:text-myblue group my-1 inline-block p-2 hover:rotate-6"
129
+ class="group my-1 inline-block p-2 hover:rotate-6 hover:text-myblue"
130
130
  target="_blank"
131
131
  title={social[2] ?? social[1]}
132
132
  >
@@ -146,7 +146,7 @@ const createdDate = created ? new Date(created) : new Date();
146
146
  }
147
147
 
148
148
  <div
149
- class="text-myblue xs:flex-row my-2 flex flex-col items-center justify-center"
149
+ class="my-2 flex flex-col items-center justify-center text-myblue xs:flex-row"
150
150
  >
151
151
  <div class="px-4 text-center text-2xl md:px-12">
152
152
  Copyright &#169; {createdDate.getFullYear()}{
@@ -159,7 +159,7 @@ const createdDate = created ? new Date(created) : new Date();
159
159
  </div>
160
160
  </div>
161
161
  <div
162
- class="text-myblue my-2 flex flex-row items-center justify-center md:flex-col"
162
+ class="my-2 flex flex-row items-center justify-center text-myblue md:flex-col"
163
163
  >
164
164
  <div class="px-4 text-center text-lg md:px-12">
165
165
  pressed with
@@ -107,15 +107,15 @@ const authStatus = {
107
107
 
108
108
  <!-- BOTTOM ROW: Title + Action Icons -->
109
109
  <div
110
- class="bg-mywhite flex flex-row flex-nowrap justify-between px-4 pb-3 pt-4 shadow-inner md:px-8"
110
+ class="flex flex-row flex-nowrap justify-between bg-mywhite px-4 pb-3 pt-4 shadow-inner md:px-8"
111
111
  >
112
- <h1 class="text-mydarkgrey truncate text-xl">{title}</h1>
112
+ <h1 class="truncate text-xl text-mydarkgrey">{title}</h1>
113
113
  <div class="flex flex-row flex-nowrap items-center gap-x-2">
114
114
  {
115
115
  !isHome ? (
116
116
  <a
117
117
  href="/"
118
- class="text-myblue/80 hover:text-myblue hover:rotate-6"
118
+ class="text-myblue/80 hover:rotate-6 hover:text-myblue"
119
119
  title="Go to home page"
120
120
  >
121
121
  <svg
@@ -139,11 +139,11 @@ const authStatus = {
139
139
  authStatus.isAdmin || authStatus.isAuthenticated ? (
140
140
  <a
141
141
  href="/storykeep"
142
- class="hover:text-myblue hover:rotate-6"
142
+ class="hover:rotate-6 hover:text-myblue"
143
143
  title="Your Story Keep Dashboard"
144
144
  >
145
145
  <svg
146
- class="text-myblue/80 h-6 w-6"
146
+ class="h-6 w-6 text-myblue/80"
147
147
  fill="none"
148
148
  viewBox="0 0 24 24"
149
149
  stroke-width="1.5"
@@ -165,7 +165,7 @@ const authStatus = {
165
165
  <a
166
166
  data-astro-reload
167
167
  href={!isContext ? `/${slug}/edit` : `/context/${slug}/edit`}
168
- class="text-myblue/80 hover:text-myblue hover:rotate-6"
168
+ class="text-myblue/80 hover:rotate-6 hover:text-myblue"
169
169
  title={!isContext ? 'Edit this story' : 'Edit this page'}
170
170
  >
171
171
  <svg
@@ -145,7 +145,7 @@ const MenuComponent = (props: MenuProps) => {
145
145
  return (
146
146
  <button
147
147
  type="button"
148
- className="text-mydarkgrey focus:ring-myblue block text-2xl font-bold leading-6 hover:text-black hover:underline hover:decoration-dashed hover:decoration-4 hover:underline-offset-4 focus:text-black focus:outline-none focus:ring-2"
148
+ className="block text-2xl font-bold leading-6 text-mydarkgrey hover:text-black hover:underline hover:decoration-dashed hover:decoration-4 hover:underline-offset-4 focus:text-black focus:outline-none focus:ring-2 focus:ring-myblue"
149
149
  title={item.description}
150
150
  aria-label={`${item.name} - ${item.description}`}
151
151
  hx-post="/api/v1/state"
@@ -161,7 +161,7 @@ const MenuComponent = (props: MenuProps) => {
161
161
  return (
162
162
  <a
163
163
  href={item.href}
164
- className="text-mydarkgrey focus:ring-myblue block text-2xl font-bold leading-6 hover:text-black hover:underline hover:decoration-dashed hover:decoration-4 hover:underline-offset-4 focus:text-black focus:outline-none focus:ring-2"
164
+ className="block text-2xl font-bold leading-6 text-mydarkgrey hover:text-black hover:underline hover:decoration-dashed hover:decoration-4 hover:underline-offset-4 focus:text-black focus:outline-none focus:ring-2 focus:ring-myblue"
165
165
  title={item.description}
166
166
  aria-label={`${item.name} - ${item.description}`}
167
167
  >
@@ -172,7 +172,7 @@ const MenuComponent = (props: MenuProps) => {
172
172
 
173
173
  return (
174
174
  <span
175
- className="text-mydarkgrey block text-2xl font-bold leading-6 opacity-50"
175
+ className="block text-2xl font-bold leading-6 text-mydarkgrey opacity-50"
176
176
  title={item.description}
177
177
  aria-label={`${item.name} - ${item.description}`}
178
178
  >
@@ -186,7 +186,7 @@ const MenuComponent = (props: MenuProps) => {
186
186
  <style dangerouslySetInnerHTML={{ __html: menuStyles }} />
187
187
 
188
188
  {/* Desktop Navigation */}
189
- <nav className="font-action ml-6 hidden flex-wrap items-center justify-end space-x-3 md:flex md:space-x-6">
189
+ <nav className="ml-6 hidden flex-wrap items-center justify-end space-x-3 font-action md:flex md:space-x-6">
190
190
  {featuredLinks.map((item: ProcessedMenuLinkDatum) => (
191
191
  <div key={item.name} className="relative py-1.5">
192
192
  <InteractiveMenuItem item={item} />
@@ -198,7 +198,7 @@ const MenuComponent = (props: MenuProps) => {
198
198
  <div className="font-action md:hidden">
199
199
  <Menu.Root>
200
200
  <Menu.Trigger
201
- className="text-myblue focus:ring-myblue inline-flex rounded-md px-3 py-2 text-xl font-bold hover:text-black focus:outline-none focus:ring-2"
201
+ className="inline-flex rounded-md px-3 py-2 text-xl font-bold text-myblue hover:text-black focus:outline-none focus:ring-2 focus:ring-myblue"
202
202
  aria-label="Open navigation menu"
203
203
  >
204
204
  <span>MENU</span>
@@ -209,20 +209,20 @@ const MenuComponent = (props: MenuProps) => {
209
209
  <Menu.Positioner>
210
210
  <Menu.Content className="menu-content mt-5 flex">
211
211
  <div className="w-screen">
212
- <div className="text-md ring-mydarkgrey/5 flex-auto overflow-hidden rounded-3xl bg-white p-4 leading-6 shadow-lg ring-1">
212
+ <div className="text-md flex-auto overflow-hidden rounded-3xl bg-white p-4 leading-6 shadow-lg ring-1 ring-mydarkgrey/5">
213
213
  {/* Featured Links Section */}
214
214
  <div className="px-8">
215
215
  {featuredLinks.map((item: ProcessedMenuLinkDatum) => (
216
216
  <Menu.Item
217
217
  key={item.name}
218
218
  value={item.name}
219
- className="menu-item hover:bg-mygreen/20 group relative flex gap-x-6 rounded-lg p-4"
219
+ className="menu-item group relative flex gap-x-6 rounded-lg p-4 hover:bg-mygreen/20"
220
220
  >
221
221
  <div>
222
222
  {item.renderAs === 'button' ? (
223
223
  <button
224
224
  type="button"
225
- className="font-action text-myblack text-xl hover:text-black focus:text-black focus:outline-none"
225
+ className="font-action text-xl text-myblack hover:text-black focus:text-black focus:outline-none"
226
226
  aria-label={`${item.name} - ${item.description}`}
227
227
  hx-post="/api/v1/state"
228
228
  hx-swap="none"
@@ -234,7 +234,7 @@ const MenuComponent = (props: MenuProps) => {
234
234
  ) : item.renderAs === 'a' ? (
235
235
  <a
236
236
  href={item.href}
237
- className="font-action text-myblack text-xl hover:text-black focus:text-black focus:outline-none"
237
+ className="font-action text-xl text-myblack hover:text-black focus:text-black focus:outline-none"
238
238
  aria-label={`${item.name} - ${item.description}`}
239
239
  >
240
240
  {item.name}
@@ -242,13 +242,13 @@ const MenuComponent = (props: MenuProps) => {
242
242
  </a>
243
243
  ) : (
244
244
  <span
245
- className="font-action text-myblack text-xl opacity-50"
245
+ className="font-action text-xl text-myblack opacity-50"
246
246
  aria-label={`${item.name} - ${item.description}`}
247
247
  >
248
248
  {item.name}
249
249
  </span>
250
250
  )}
251
- <p className="text-mydarkgrey mt-1">
251
+ <p className="mt-1 text-mydarkgrey">
252
252
  {item.description}
253
253
  </p>
254
254
  </div>
@@ -261,7 +261,7 @@ const MenuComponent = (props: MenuProps) => {
261
261
  <div className="bg-slate-50 p-8">
262
262
  <div className="flex justify-between">
263
263
  <h3
264
- className="text-myblue mt-4 text-sm leading-6"
264
+ className="mt-4 text-sm leading-6 text-myblue"
265
265
  id="additional-links-heading"
266
266
  >
267
267
  Additional Links
@@ -282,7 +282,7 @@ const MenuComponent = (props: MenuProps) => {
282
282
  {item.renderAs === 'button' ? (
283
283
  <button
284
284
  type="button"
285
- className="text-mydarkgrey block truncate rounded p-2 text-sm font-bold leading-6 hover:text-black focus:text-black focus:underline focus:outline-none"
285
+ className="block truncate rounded p-2 text-sm font-bold leading-6 text-mydarkgrey hover:text-black focus:text-black focus:underline focus:outline-none"
286
286
  title={item.description}
287
287
  aria-label={`${item.name} - ${item.description}`}
288
288
  hx-post="/api/v1/state"
@@ -295,7 +295,7 @@ const MenuComponent = (props: MenuProps) => {
295
295
  ) : item.renderAs === 'a' ? (
296
296
  <a
297
297
  href={item.href}
298
- className="text-mydarkgrey block truncate rounded p-2 text-sm font-bold leading-6 hover:text-black focus:text-black focus:underline focus:outline-none"
298
+ className="block truncate rounded p-2 text-sm font-bold leading-6 text-mydarkgrey hover:text-black focus:text-black focus:underline focus:outline-none"
299
299
  title={item.description}
300
300
  aria-label={`${item.name} - ${item.description}`}
301
301
  >
@@ -304,7 +304,7 @@ const MenuComponent = (props: MenuProps) => {
304
304
  </a>
305
305
  ) : (
306
306
  <span
307
- className="text-mydarkgrey block truncate rounded p-2 text-sm font-bold leading-6 opacity-50"
307
+ className="block truncate rounded p-2 text-sm font-bold leading-6 text-mydarkgrey opacity-50"
308
308
  title={item.description}
309
309
  aria-label={`${item.name} - ${item.description}`}
310
310
  >
@@ -353,7 +353,7 @@ const BunnyVideoSetup = ({ nodeId, params }: BunnyVideoSetupProps) => {
353
353
  <input
354
354
  type="text"
355
355
  id="videoUrl"
356
- className={`block w-full rounded-md border-gray-300 px-2.5 py-1.5 pr-10 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm ${videoUrl && !isValidBunnyUrl(videoUrl) ? 'border-red-300 focus:border-red-500 focus:ring-red-500' : ''}`}
356
+ className={`sm:text-sm block w-full rounded-md border-gray-300 px-2.5 py-1.5 pr-10 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 ${videoUrl && !isValidBunnyUrl(videoUrl) ? 'border-red-300 focus:border-red-500 focus:ring-red-500' : ''}`}
357
357
  value={videoUrl}
358
358
  onChange={(e) => setVideoUrl(e.target.value)}
359
359
  onKeyDown={(e) => {
@@ -396,7 +396,7 @@ const BunnyVideoSetup = ({ nodeId, params }: BunnyVideoSetupProps) => {
396
396
  <input
397
397
  type="text"
398
398
  id="videoTitle"
399
- className="mt-1 block w-full rounded-md border-gray-300 px-2.5 py-1.5 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
399
+ className="sm:text-sm mt-1 block w-full rounded-md border-gray-300 px-2.5 py-1.5 shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
400
400
  value={videoTitle}
401
401
  onChange={(e) => setVideoTitle(e.target.value)}
402
402
  onKeyDown={(e) => {
@@ -524,7 +524,7 @@ const BunnyVideoSetup = ({ nodeId, params }: BunnyVideoSetupProps) => {
524
524
  }
525
525
  }}
526
526
  onBlur={() => saveChanges()}
527
- className={`mt-1 block w-full rounded-md border-gray-300 px-2.5 py-1.5 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm ${
527
+ className={`sm:text-sm mt-1 block w-full rounded-md border-gray-300 px-2.5 py-1.5 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 ${
528
528
  formErrors[`title-${index}`] ? 'border-red-300' : ''
529
529
  }`}
530
530
  />
@@ -551,7 +551,7 @@ const BunnyVideoSetup = ({ nodeId, params }: BunnyVideoSetupProps) => {
551
551
  }
552
552
  }}
553
553
  onBlur={() => saveChanges()}
554
- className="mt-1 block w-full rounded-md border-gray-300 px-2.5 py-1.5 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
554
+ className="sm:text-sm mt-1 block w-full rounded-md border-gray-300 px-2.5 py-1.5 shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
555
555
  />
556
556
  </div>
557
557
  </div>
@@ -577,7 +577,7 @@ const BunnyVideoSetup = ({ nodeId, params }: BunnyVideoSetupProps) => {
577
577
  e.currentTarget.blur();
578
578
  }
579
579
  }}
580
- className={`mt-1 block w-full rounded-md border-gray-300 px-2.5 py-1.5 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm ${
580
+ className={`sm:text-sm mt-1 block w-full rounded-md border-gray-300 px-2.5 py-1.5 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 ${
581
581
  formErrors[`startTime-${index}`]
582
582
  ? 'border-red-300'
583
583
  : ''
@@ -611,7 +611,7 @@ const BunnyVideoSetup = ({ nodeId, params }: BunnyVideoSetupProps) => {
611
611
  e.currentTarget.blur();
612
612
  }
613
613
  }}
614
- className={`mt-1 block w-full rounded-md border-gray-300 px-2.5 py-1.5 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm ${
614
+ className={`sm:text-sm mt-1 block w-full rounded-md border-gray-300 px-2.5 py-1.5 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 ${
615
615
  formErrors[`endTime-${index}`] ? 'border-red-300' : ''
616
616
  }`}
617
617
  placeholder="60"
@@ -607,7 +607,7 @@ const EpinetDurationSelector = ({
607
607
  : 'Select date range'}
608
608
  </button>
609
609
  {isDatePickerOpen && (
610
- <div className="absolute z-10 mt-1 w-full rounded-md bg-white p-2 shadow-lg sm:w-auto">
610
+ <div className="sm:w-auto absolute z-10 mt-1 w-full rounded-md bg-white p-2 shadow-lg">
611
611
  <div className="mb-2 flex flex-wrap justify-between gap-2">
612
612
  <button
613
613
  className="rounded-md p-1 text-sm hover:bg-gray-100"
@@ -642,7 +642,7 @@ const EpinetDurationSelector = ({
642
642
  End date: {formatDateDisplay(endDate)}
643
643
  </p>
644
644
  </div>
645
- <div className="flex flex-col gap-4 sm:flex-row">
645
+ <div className="sm:flex-row flex flex-col gap-4">
646
646
  <div className="flex-1">
647
647
  <label
648
648
  htmlFor="start-date"
@@ -884,7 +884,7 @@ const EpinetDurationSelector = ({
884
884
  className={`fixed bottom-0 left-0 right-0 z-50 transform pr-12 transition-all duration-300 ease-in-out ${isAnimating ? 'translate-y-0 opacity-100' : 'translate-y-full opacity-0'}`}
885
885
  >
886
886
  <div className="absolute inset-0 bg-black/10 backdrop-blur-sm" />
887
- <div className="relative mx-auto max-w-7xl px-4 py-4 sm:px-6 lg:px-8">
887
+ <div className="sm:px-6 lg:px-8 relative mx-auto max-w-7xl px-4 py-4">
888
888
  <div
889
889
  className={`flex items-center justify-between rounded-lg border px-6 py-4 shadow-lg ${styling.bgColor} ${styling.borderColor}`}
890
890
  >
@@ -44,7 +44,7 @@ const bgColor = parsedOptions.bgColor || '';
44
44
  <div class="w-full">
45
45
  <a href={`/${featuredStory.slug}`} class="block">
46
46
  <div class="max-w-lg pr-12">
47
- <p class="font-action mb-4 text-lg font-bold uppercase text-gray-500">
47
+ <p class="mb-4 font-action text-lg font-bold uppercase text-gray-500">
48
48
  Featured Article
49
49
  </p>
50
50
  <div class="space-y-6">
@@ -143,7 +143,7 @@ const FeaturedArticleSetup = ({
143
143
  return (
144
144
  <div className="flex flex-col gap-8 pt-4">
145
145
  <div className="w-full">
146
- <p className="font-action text-md mb-4 font-bold uppercase text-gray-500">
146
+ <p className="text-md mb-4 font-action font-bold uppercase text-gray-500">
147
147
  Featured Article
148
148
  </p>
149
149
  <div className="space-y-6">
@@ -173,7 +173,7 @@ const FeaturedArticleSetup = ({
173
173
  return (
174
174
  <div className="flex flex-row items-center gap-12 pt-4">
175
175
  <div className="w-3/5">
176
- <p className="font-action text-md mb-4 font-bold uppercase text-gray-500">
176
+ <p className="text-md mb-4 font-action font-bold uppercase text-gray-500">
177
177
  Featured Article
178
178
  </p>
179
179
  <div className="space-y-6">
@@ -264,7 +264,7 @@ const FeaturedArticleSetup = ({
264
264
  />
265
265
  </Combobox.Trigger>
266
266
  </div>
267
- <Combobox.Content className="absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
267
+ <Combobox.Content className="sm:text-sm absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
268
268
  {collection.items.map((item) => (
269
269
  <Combobox.Item
270
270
  key={item.slug}
@@ -288,7 +288,7 @@ const ListContentSetup = ({ params, nodeId }: ListContentSetupProps) => {
288
288
  <input
289
289
  type="text"
290
290
  id="list-title"
291
- className="mt-1 block w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-600 focus:ring-cyan-600 sm:text-sm"
291
+ className="sm:text-sm mt-1 block w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-600 focus:ring-cyan-600"
292
292
  value={title}
293
293
  onChange={(e) => setTitle(e.target.value)}
294
294
  placeholder="e.g., Recent Articles"
@@ -305,7 +305,7 @@ const ListContentSetup = ({ params, nodeId }: ListContentSetupProps) => {
305
305
  <select
306
306
  id="page-size"
307
307
  name="page-size"
308
- className="mt-1 block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-cyan-600 focus:outline-none focus:ring-cyan-600 sm:text-sm"
308
+ className="sm:text-sm mt-1 block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-cyan-600 focus:outline-none focus:ring-cyan-600"
309
309
  value={pageSize}
310
310
  onChange={(e) => handlePageSizeChange(parseInt(e.target.value))}
311
311
  >
@@ -121,7 +121,7 @@ export const ProductCardSetup = (props: ProductCardSetupProps) => {
121
121
  </Combobox.Label>
122
122
  <Combobox.Control>
123
123
  <Combobox.Input
124
- className="w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
124
+ className="sm:text-sm w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
125
125
  placeholder="Search products..."
126
126
  />
127
127
  </Combobox.Control>
@@ -198,7 +198,7 @@ export const ProductGridSetup = (props: ProductGridSetupProps) => {
198
198
  value={productType}
199
199
  onChange={(e) => setProductType(e.target.value)}
200
200
  placeholder="e.g., 'electronics'"
201
- className="w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
201
+ className="sm:text-sm w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
202
202
  />
203
203
  </div>
204
204
  )}
@@ -238,7 +238,7 @@ export const ProductGridSetup = (props: ProductGridSetupProps) => {
238
238
  </Combobox.Label>
239
239
  <Combobox.Control>
240
240
  <Combobox.Input
241
- className="w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
241
+ className="sm:text-sm w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
242
242
  placeholder="Search products..."
243
243
  />
244
244
  </Combobox.Control>
@@ -123,7 +123,7 @@ export default function SandboxRegisterForm({
123
123
  value={firstname}
124
124
  onChange={(e) => setFirstname(e.target.value)}
125
125
  disabled={isLoading}
126
- className="mt-1 block w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
126
+ className="sm:text-sm mt-1 block w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
127
127
  required
128
128
  />
129
129
  </div>
@@ -140,7 +140,7 @@ export default function SandboxRegisterForm({
140
140
  value={email}
141
141
  onChange={(e) => setEmail(e.target.value)}
142
142
  disabled={isLoading}
143
- className="mt-1 block w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
143
+ className="sm:text-sm mt-1 block w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
144
144
  required
145
145
  />
146
146
  </div>
@@ -157,7 +157,7 @@ export default function SandboxRegisterForm({
157
157
  value={codeword}
158
158
  onChange={(e) => setCodeword(e.target.value)}
159
159
  disabled={isLoading}
160
- className="mt-1 block w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
160
+ className="sm:text-sm mt-1 block w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500"
161
161
  required
162
162
  />
163
163
  </div>
@@ -485,7 +485,7 @@ export const Compositor = (props: CompositorProps) => {
485
485
  {/* Selection drag box */}
486
486
  {selectionRect && (
487
487
  <div
488
- className="bg-mygreen/20 fixed z-50 border border-blue-600"
488
+ className="fixed z-50 border border-blue-600 bg-mygreen/20"
489
489
  style={{
490
490
  left: `${selectionRect.left}px`,
491
491
  top: `${selectionRect.top}px`,
@@ -104,7 +104,7 @@ export const Belief = ({
104
104
  </span>
105
105
  <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
106
106
  <ChevronUpDownIcon
107
- className="text-mylightgrey h-5 w-5"
107
+ className="h-5 w-5 text-mylightgrey"
108
108
  aria-hidden="true"
109
109
  />
110
110
  </span>
@@ -20,7 +20,7 @@ const BunnyVideo = ({ videoId, title, className = '' }: BunnyVideoProps) => {
20
20
  className={`flex aspect-video w-full items-center justify-center bg-gray-100 ${className}`}
21
21
  >
22
22
  <div className="p-4 text-center">
23
- <div className="text-mydarkgrey mb-2">Video ID not set</div>
23
+ <div className="mb-2 text-mydarkgrey">Video ID not set</div>
24
24
  <div className="text-mygrey text-sm">
25
25
  Configure this widget with a valid Bunny Video ID
26
26
  </div>
@@ -44,7 +44,7 @@ const BunnyVideo = ({ videoId, title, className = '' }: BunnyVideoProps) => {
44
44
  <div
45
45
  className={`flex aspect-video w-full items-center justify-center bg-gray-100 ${className}`}
46
46
  >
47
- <div className="text-mydarkgrey text-center">Invalid Video ID</div>
47
+ <div className="text-center text-mydarkgrey">Invalid Video ID</div>
48
48
  </div>
49
49
  );
50
50
  }