@beforesemicolon/builder 1.7.0 → 1.8.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 (161) hide show
  1. package/README.md +33 -30
  2. package/dist/cjs/build-modules.js +1 -1
  3. package/dist/cjs/docs/run.js +1 -1
  4. package/dist/cjs/docs/templates/fading-citrus/assets/before-semicolon-logo.png +0 -0
  5. package/dist/cjs/docs/templates/fading-citrus/assets/client-server.svg +4 -0
  6. package/dist/cjs/docs/templates/fading-citrus/assets/facebook.svg +8 -0
  7. package/dist/cjs/docs/templates/fading-citrus/assets/fast.svg +4 -0
  8. package/dist/cjs/docs/templates/fading-citrus/assets/favicon/android-chrome-192x192.png +0 -0
  9. package/dist/cjs/docs/templates/fading-citrus/assets/favicon/android-chrome-512x512.png +0 -0
  10. package/dist/cjs/docs/templates/fading-citrus/assets/favicon/apple-touch-icon.png +0 -0
  11. package/dist/cjs/docs/templates/fading-citrus/assets/favicon/favicon-16x16.png +0 -0
  12. package/dist/cjs/docs/templates/fading-citrus/assets/favicon/favicon-32x32.png +0 -0
  13. package/dist/cjs/docs/templates/fading-citrus/assets/favicon/favicon.ico +0 -0
  14. package/dist/cjs/docs/templates/fading-citrus/assets/favicon/site.webmanifest +19 -0
  15. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-Black.otf +0 -0
  16. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-BlackItalic.otf +0 -0
  17. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-Bold.otf +0 -0
  18. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-BoldItalic.otf +0 -0
  19. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-ExtraBold.otf +0 -0
  20. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-ExtraBoldItalic.otf +0 -0
  21. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-ExtraLight.otf +0 -0
  22. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-ExtraLightItalic.otf +0 -0
  23. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-Italic.otf +0 -0
  24. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-Light.otf +0 -0
  25. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-LightItalic.otf +0 -0
  26. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-Medium.otf +0 -0
  27. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-MediumItalic.otf +0 -0
  28. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-Regular.otf +0 -0
  29. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-SemiBold.otf +0 -0
  30. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-SemiBoldItalic.otf +0 -0
  31. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-Thin.otf +0 -0
  32. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-ThinItalic.otf +0 -0
  33. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/exo-2/SIL Open Font License.txt +43 -0
  34. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/open-sans/Apache License.txt +201 -0
  35. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-Bold.ttf +0 -0
  36. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-BoldItalic.ttf +0 -0
  37. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-ExtraBold.ttf +0 -0
  38. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-ExtraBoldItalic.ttf +0 -0
  39. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-Italic.ttf +0 -0
  40. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-Light.ttf +0 -0
  41. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-LightItalic.ttf +0 -0
  42. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-Regular.ttf +0 -0
  43. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-Semibold.ttf +0 -0
  44. package/dist/cjs/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-SemiboldItalic.ttf +0 -0
  45. package/dist/cjs/docs/templates/fading-citrus/assets/independent.svg +23 -0
  46. package/dist/cjs/docs/templates/fading-citrus/assets/instagram.svg +28 -0
  47. package/dist/cjs/docs/templates/fading-citrus/assets/markup-banner.jpg +0 -0
  48. package/dist/cjs/docs/templates/fading-citrus/assets/markup-essentials-training.jpg +0 -0
  49. package/dist/cjs/docs/templates/fading-citrus/assets/markup-favicon.jpg +0 -0
  50. package/dist/cjs/docs/templates/fading-citrus/assets/markup-favicon.png +0 -0
  51. package/dist/cjs/docs/templates/fading-citrus/assets/markup-logo-name.svg +10 -0
  52. package/dist/cjs/docs/templates/fading-citrus/assets/markup-logo-name@2x.png +0 -0
  53. package/dist/cjs/docs/templates/fading-citrus/assets/markup-logo.svg +13 -0
  54. package/dist/cjs/docs/templates/fading-citrus/assets/markup-logo@2x.png +0 -0
  55. package/dist/cjs/docs/templates/fading-citrus/assets/medium2.svg +18 -0
  56. package/dist/cjs/docs/templates/fading-citrus/assets/reactive.svg +3 -0
  57. package/dist/cjs/docs/templates/fading-citrus/assets/reddit.svg +16 -0
  58. package/dist/cjs/docs/templates/fading-citrus/assets/simple.svg +8 -0
  59. package/dist/cjs/docs/templates/fading-citrus/assets/small.svg +8 -0
  60. package/dist/cjs/docs/templates/fading-citrus/assets/twitter.svg +11 -0
  61. package/dist/cjs/docs/templates/fading-citrus/assets/youtube.svg +10 -0
  62. package/dist/cjs/docs/templates/fading-citrus/layouts/_code-snippet.js +75 -0
  63. package/dist/cjs/docs/templates/fading-citrus/layouts/_copy-code.js +50 -0
  64. package/dist/cjs/docs/templates/fading-citrus/layouts/_footer.js +72 -0
  65. package/dist/cjs/docs/templates/fading-citrus/layouts/_head-meta.js +231 -0
  66. package/dist/cjs/docs/templates/fading-citrus/layouts/_header.js +41 -0
  67. package/dist/cjs/docs/templates/fading-citrus/layouts/_logo.js +31 -0
  68. package/dist/cjs/docs/templates/fading-citrus/layouts/document.js +92 -0
  69. package/dist/cjs/docs/templates/fading-citrus/layouts/landing.js +636 -0
  70. package/dist/cjs/docs/templates/fading-citrus/stylesheets/common.css +814 -0
  71. package/dist/cjs/docs/templates/fading-citrus/stylesheets/documentation.css +603 -0
  72. package/dist/cjs/docs/templates/fading-citrus/stylesheets/fonts.css +1 -0
  73. package/dist/cjs/docs/templates/fading-citrus/stylesheets/github-dark.hightlighter.css +107 -0
  74. package/dist/cjs/docs/templates/fading-citrus/stylesheets/github-light.hightlighter.css +107 -0
  75. package/dist/cjs/docs/templates/fading-citrus/stylesheets/hybrid.hightlighter.css +102 -0
  76. package/dist/cjs/docs/templates/fading-citrus/stylesheets/landing.css +1553 -0
  77. package/dist/cjs/docs/templates/fading-citrus/stylesheets/normalize.css +351 -0
  78. package/dist/cjs/docs/templates/fading-citrus/template.config.js +14 -0
  79. package/dist/cjs/docs/types.js +1 -1
  80. package/dist/esm/build-modules.js +1 -1
  81. package/dist/esm/docs/run.js +1 -1
  82. package/dist/esm/docs/templates/fading-citrus/assets/before-semicolon-logo.png +0 -0
  83. package/dist/esm/docs/templates/fading-citrus/assets/client-server.svg +4 -0
  84. package/dist/esm/docs/templates/fading-citrus/assets/facebook.svg +8 -0
  85. package/dist/esm/docs/templates/fading-citrus/assets/fast.svg +4 -0
  86. package/dist/esm/docs/templates/fading-citrus/assets/favicon/android-chrome-192x192.png +0 -0
  87. package/dist/esm/docs/templates/fading-citrus/assets/favicon/android-chrome-512x512.png +0 -0
  88. package/dist/esm/docs/templates/fading-citrus/assets/favicon/apple-touch-icon.png +0 -0
  89. package/dist/esm/docs/templates/fading-citrus/assets/favicon/favicon-16x16.png +0 -0
  90. package/dist/esm/docs/templates/fading-citrus/assets/favicon/favicon-32x32.png +0 -0
  91. package/dist/esm/docs/templates/fading-citrus/assets/favicon/favicon.ico +0 -0
  92. package/dist/esm/docs/templates/fading-citrus/assets/favicon/site.webmanifest +19 -0
  93. package/dist/esm/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-Black.otf +0 -0
  94. package/dist/esm/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-BlackItalic.otf +0 -0
  95. package/dist/esm/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-Bold.otf +0 -0
  96. package/dist/esm/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-BoldItalic.otf +0 -0
  97. package/dist/esm/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-ExtraBold.otf +0 -0
  98. package/dist/esm/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-ExtraBoldItalic.otf +0 -0
  99. package/dist/esm/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-ExtraLight.otf +0 -0
  100. package/dist/esm/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-ExtraLightItalic.otf +0 -0
  101. package/dist/esm/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-Italic.otf +0 -0
  102. package/dist/esm/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-Light.otf +0 -0
  103. package/dist/esm/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-LightItalic.otf +0 -0
  104. package/dist/esm/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-Medium.otf +0 -0
  105. package/dist/esm/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-MediumItalic.otf +0 -0
  106. package/dist/esm/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-Regular.otf +0 -0
  107. package/dist/esm/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-SemiBold.otf +0 -0
  108. package/dist/esm/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-SemiBoldItalic.otf +0 -0
  109. package/dist/esm/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-Thin.otf +0 -0
  110. package/dist/esm/docs/templates/fading-citrus/assets/fonts/exo-2/Exo2.0-ThinItalic.otf +0 -0
  111. package/dist/esm/docs/templates/fading-citrus/assets/fonts/exo-2/SIL Open Font License.txt +43 -0
  112. package/dist/esm/docs/templates/fading-citrus/assets/fonts/open-sans/Apache License.txt +201 -0
  113. package/dist/esm/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-Bold.ttf +0 -0
  114. package/dist/esm/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-BoldItalic.ttf +0 -0
  115. package/dist/esm/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-ExtraBold.ttf +0 -0
  116. package/dist/esm/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-ExtraBoldItalic.ttf +0 -0
  117. package/dist/esm/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-Italic.ttf +0 -0
  118. package/dist/esm/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-Light.ttf +0 -0
  119. package/dist/esm/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-LightItalic.ttf +0 -0
  120. package/dist/esm/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-Regular.ttf +0 -0
  121. package/dist/esm/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-Semibold.ttf +0 -0
  122. package/dist/esm/docs/templates/fading-citrus/assets/fonts/open-sans/OpenSans-SemiboldItalic.ttf +0 -0
  123. package/dist/esm/docs/templates/fading-citrus/assets/independent.svg +23 -0
  124. package/dist/esm/docs/templates/fading-citrus/assets/instagram.svg +28 -0
  125. package/dist/esm/docs/templates/fading-citrus/assets/markup-banner.jpg +0 -0
  126. package/dist/esm/docs/templates/fading-citrus/assets/markup-essentials-training.jpg +0 -0
  127. package/dist/esm/docs/templates/fading-citrus/assets/markup-favicon.jpg +0 -0
  128. package/dist/esm/docs/templates/fading-citrus/assets/markup-favicon.png +0 -0
  129. package/dist/esm/docs/templates/fading-citrus/assets/markup-logo-name.svg +10 -0
  130. package/dist/esm/docs/templates/fading-citrus/assets/markup-logo-name@2x.png +0 -0
  131. package/dist/esm/docs/templates/fading-citrus/assets/markup-logo.svg +13 -0
  132. package/dist/esm/docs/templates/fading-citrus/assets/markup-logo@2x.png +0 -0
  133. package/dist/esm/docs/templates/fading-citrus/assets/medium2.svg +18 -0
  134. package/dist/esm/docs/templates/fading-citrus/assets/reactive.svg +3 -0
  135. package/dist/esm/docs/templates/fading-citrus/assets/reddit.svg +16 -0
  136. package/dist/esm/docs/templates/fading-citrus/assets/simple.svg +8 -0
  137. package/dist/esm/docs/templates/fading-citrus/assets/small.svg +8 -0
  138. package/dist/esm/docs/templates/fading-citrus/assets/twitter.svg +11 -0
  139. package/dist/esm/docs/templates/fading-citrus/assets/youtube.svg +10 -0
  140. package/dist/esm/docs/templates/fading-citrus/layouts/_code-snippet.js +75 -0
  141. package/dist/esm/docs/templates/fading-citrus/layouts/_copy-code.js +50 -0
  142. package/dist/esm/docs/templates/fading-citrus/layouts/_footer.js +72 -0
  143. package/dist/esm/docs/templates/fading-citrus/layouts/_head-meta.js +231 -0
  144. package/dist/esm/docs/templates/fading-citrus/layouts/_header.js +41 -0
  145. package/dist/esm/docs/templates/fading-citrus/layouts/_logo.js +31 -0
  146. package/dist/esm/docs/templates/fading-citrus/layouts/document.js +92 -0
  147. package/dist/esm/docs/templates/fading-citrus/layouts/landing.js +636 -0
  148. package/dist/esm/docs/templates/fading-citrus/stylesheets/common.css +814 -0
  149. package/dist/esm/docs/templates/fading-citrus/stylesheets/documentation.css +603 -0
  150. package/dist/esm/docs/templates/fading-citrus/stylesheets/fonts.css +1 -0
  151. package/dist/esm/docs/templates/fading-citrus/stylesheets/github-dark.hightlighter.css +107 -0
  152. package/dist/esm/docs/templates/fading-citrus/stylesheets/github-light.hightlighter.css +107 -0
  153. package/dist/esm/docs/templates/fading-citrus/stylesheets/hybrid.hightlighter.css +102 -0
  154. package/dist/esm/docs/templates/fading-citrus/stylesheets/landing.css +1553 -0
  155. package/dist/esm/docs/templates/fading-citrus/stylesheets/normalize.css +351 -0
  156. package/dist/esm/docs/templates/fading-citrus/template.config.js +14 -0
  157. package/dist/types/build-browser.d.ts +1 -1
  158. package/dist/types/build-modules.d.ts +1 -1
  159. package/dist/types/docs/run.d.ts +2 -1
  160. package/dist/types/docs/types.d.ts +28 -6
  161. package/package.json +1 -1
@@ -0,0 +1,636 @@
1
+ import meta from './_head-meta.js'
2
+ import header from './_header.js'
3
+ import footer from './_footer.js'
4
+ import copyCode from './_copy-code.js'
5
+ import { renderCodeBlock } from './_code-snippet.js'
6
+
7
+ const heroCode = `import { html, state, effect } from '@beforesemicolon/markup';
8
+
9
+ const [count, updateCount] = state(0);
10
+
11
+ const doubleCount = () => count() * 2;
12
+
13
+ effect(() => {
14
+ console.log(count())
15
+ })
16
+
17
+ const countUp = () => updateCount(prev => prev + 1);
18
+ const countDown = () => updateCount(prev => prev - 1);
19
+
20
+ const App = html\`
21
+ <h1>Conunter</h1>
22
+ <p><strong>Current count</strong>: \${count}</p>
23
+ <p><strong>Double count</strong>: \${doubleCount}</p>
24
+ <button type="button" onclick="\${countDown}">-</button>
25
+ <button type="button" onclick="\${countUp}">+</button>
26
+ \`;
27
+
28
+ App.render(document.getElementById('app'));`
29
+
30
+ const todoCode = `import { html, state, effect, repeat } from '@beforesemicolon/markup'
31
+
32
+ const [todos, setTodos] = state(
33
+ JSON.parse(localStorage.getItem('todos') ?? '[]')
34
+ )
35
+
36
+ effect(() => {
37
+ localStorage.setItem('todos', JSON.stringify(todos()))
38
+ })
39
+
40
+ const addTodo = () => {
41
+ const text = window.prompt('What needs doing?')?.trim()
42
+
43
+ if (text) setTodos((prev) => [...prev, { text, done: false }])
44
+ }
45
+
46
+ const toggle = (i) =>
47
+ setTodos(todos().map((t, idx) => (idx === i ? { ...t, done: !t.done } : t)))
48
+
49
+ html\`
50
+ <button type="button" onclick="\${addTodo}">Add</button>
51
+ <ul>
52
+ \${repeat(
53
+ todos,
54
+ (todo, i) => html\`
55
+ <li
56
+ class="\${todo.done ? 'done' : ''}"
57
+ onclick="\${() => toggle(i)}"
58
+ >
59
+ \${todo.text}
60
+ </li>
61
+ \`
62
+ )}
63
+ </ul>
64
+ \`.render(document.querySelector('#app'))`
65
+
66
+ const buttonComponentCode = `import { WebComponent, html } from '@beforesemicolon/web-component'
67
+ import stylesheet from './button.css' with { type: 'css' }
68
+
69
+ class Button extends WebComponent {
70
+ static observedAttributes = ['disabled', 'type']
71
+
72
+ type = 'button'
73
+ disabled = false
74
+
75
+ stylesheet = stylesheet
76
+
77
+ handleClick = (evt) => {
78
+ evt.stopPropagation()
79
+ this.dispatch('click')
80
+ }
81
+
82
+ render = () => {
83
+ return html\`
84
+ <button \${this.props} class="btn" onclick="\${this.handleClick}">
85
+ <slot></slot>
86
+ </button>
87
+ \`
88
+ }
89
+ }
90
+
91
+ customElements.define('bfs-button', Button)`
92
+
93
+ const suspenseCode = `import { html, suspense } from '@beforesemicolon/markup'
94
+
95
+ const loadUser = async () => {
96
+ const res = await fetch('/api/me')
97
+ return res.json()
98
+ }
99
+
100
+ const renderUser = async () => {
101
+ const user = await loadUser()
102
+ return html\`
103
+ <article>
104
+ <h2>\${user.name}</h2>
105
+ <p>\${user.bio}</p>
106
+ </article>
107
+ \`
108
+ }
109
+
110
+ html\`
111
+ <h1>Profile</h1>
112
+
113
+ \${suspense(
114
+ renderUser,
115
+ html\`<p>Loading profile…</p>\`, // fallback
116
+ (err) => html\`<p>Failed: \${err.message}</p>\` // catch
117
+ )}
118
+ \`.render(document.querySelector('#app'))`
119
+
120
+ const routerCode = `<!-- in <head>:
121
+ <script src="https://unpkg.com/@beforesemicolon/router/dist/client.js"></script>
122
+ -->
123
+
124
+ <nav>
125
+ <page-link path="/">Home</page-link>
126
+ <page-link path="/about">About</page-link>
127
+ <page-link path="/users">Users</page-link>
128
+ </nav>
129
+
130
+ <page-route path="/">
131
+ <h1>Welcome home</h1>
132
+ </page-route>
133
+
134
+ <page-route path="/about" src="./pages/about.js"></page-route>
135
+
136
+ <page-route path="/users" exact="false">
137
+ <page-route src="./pages/users.js"></page-route>
138
+ <page-route path="/:userId" src="./pages/user.js"></page-route>
139
+ </page-route>
140
+
141
+ <page-route path="/404"> 404 - Page not found! </page-route>
142
+
143
+ <page-redirect path="/404" title="404 - Page not found!"></page-redirect>`
144
+
145
+ const lifecycleCode = `import { html, state } from '@beforesemicolon/markup';
146
+
147
+ const [seconds, setSeconds] = state(0);
148
+
149
+ html\`
150
+ <p>Elapsed: \${seconds}s</p>
151
+ \`
152
+ .onMount(() => {
153
+ // runs once when attached to the DOM
154
+ const id = setInterval(() => setSeconds(seconds() + 1), 1000);
155
+ return () => clearInterval(id);
156
+ })
157
+ .onUpdate(() => {
158
+ // runs every time a tracked value changes
159
+ console.log('tick', seconds());
160
+ })
161
+ .render(document.querySelector('#app'));`
162
+
163
+ export default (props) => {
164
+ return `
165
+ <!doctype html>
166
+ <html lang="en">
167
+ <head>
168
+ ${meta(props)}
169
+ <link rel="stylesheet" href="/stylesheets/landing.css?v=20260621exact">
170
+ </head>
171
+ <body>
172
+ ${header(props)}
173
+
174
+ <!-- Hero Section -->
175
+ <section class="hero-section relative overflow-hidden">
176
+ <div class="absolute inset-0 bg-grid pointer-events-none"></div>
177
+ <div class="absolute inset-0 hero-glow pointer-events-none animate-pulse-glow"></div>
178
+ <div class="wrapper hero-container animate-float-up">
179
+ <div class="hero-content">
180
+ <a href="https://github.com/beforesemicolon/markup" target="_blank" rel="noopener" class="version-badge">
181
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="sparkles-icon"><path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275Z"/><path d="m5 3 1 2.5L8.5 6 6 7 5 9.5 4 7 1.5 6 4 5Z"/><path d="m19 17 1 2.5 2.5.5-2.5 1-1 2.5-1-2.5-2.5-1 2.5-1Z"/></svg>
182
+ <span>v${props.projectMeta?.version || ''}</span>
183
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
184
+ </a>
185
+ <h1 class="hero-title">
186
+ <span class="text-gradient">Reactive DOM.</span><br/>
187
+ <span class="text-gradient-primary">Zero build.</span>
188
+ </h1>
189
+ <p class="hero-subtitle">
190
+ A tiny, web-standards-first templating system that brings reactivity, state, and components to vanilla JavaScript. <strong>No bundlers. No JSX. No magic.</strong>
191
+ </p>
192
+ <div class="hero-actions">
193
+ <a href="/documentation/get-started" class="btn-primary">
194
+ <span>Get Started</span>
195
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
196
+ </a>
197
+ <a href="#install" class="btn-secondary font-mono">
198
+ npm i @beforesemicolon/markup
199
+ </a>
200
+ </div>
201
+ <div class="hero-stats">
202
+ <div class="stat-item">
203
+ <span class="stat-value text-gradient-primary">7.6KB</span>
204
+ <span class="stat-label">CDN gzip</span>
205
+ </div>
206
+ <div class="stat-divider"></div>
207
+ <div class="stat-item">
208
+ <span class="stat-value text-gradient-primary">0</span>
209
+ <span class="stat-label">third-party deps</span>
210
+ </div>
211
+ <div class="stat-divider"></div>
212
+ <div class="stat-item">
213
+ <span class="stat-value text-gradient-primary">100%</span>
214
+ <span class="stat-label">web standards</span>
215
+ </div>
216
+ </div>
217
+ </div>
218
+ <div class="hero-code-panel">
219
+ <div class="glow-container">
220
+ <div class="absolute inset-0 bg-gradient-to-br from-primary/20 to-accent/20 blur-3xl rounded-full"></div>
221
+ </div>
222
+ ${renderCodeBlock('counter.js', heroCode)}
223
+ </div>
224
+ </div>
225
+ </section>
226
+
227
+ <!-- Products/Ecosystem Section -->
228
+ <section class="products-section wrapper">
229
+ <div class="section-header">
230
+ <div>
231
+ <span class="section-tag font-mono">// the ecosystem</span>
232
+ <h2>Built on top of Markup.</h2>
233
+ </div>
234
+ <p>Production-ready libraries powered by the same reactive engine — opt-in, modular, and free of third-party runtime dependencies.</p>
235
+ </div>
236
+ <div class="products-grid">
237
+ <div class="product-card card-orange">
238
+ <div class="product-glow"></div>
239
+ <a href="https://markup.beforesemicolon.com/documentation/capabilities/web-component" target="_blank" rel="noreferrer" class="product-head">
240
+ <div class="product-title-row">
241
+ <div class="icon-container">
242
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5.5 8.5 9 12l-3.5 3.5L2 12l3.5-3.5Z"/><path d="m12 2 3.5 3.5L12 9 8.5 5.5 12 2Z"/><path d="M18.5 8.5 22 12l-3.5 3.5L15 12l3.5-3.5Z"/><path d="m12 15 3.5 3.5-3.5 3.5-3.5-3.5 3.5-3.5Z"/></svg>
243
+ </div>
244
+ <div>
245
+ <h3>Web Components</h3>
246
+ <span class="package-name font-mono">@beforesemicolon/web-component</span>
247
+ </div>
248
+ </div>
249
+ <span class="product-arrow">
250
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 17 17 7"/><path d="M7 7h10v10"/></svg>
251
+ </span>
252
+ </a>
253
+ <p>A reactive layer over the native Web Components API. Props, state, lifecycles, scoped styles — built on Markup.</p>
254
+ <a href="https://markup.beforesemicolon.com/documentation/capabilities/web-component" target="_blank" rel="noreferrer" class="product-link">
255
+ <span>Read the docs</span>
256
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 17 17 7"/><path d="M7 7h10v10"/></svg>
257
+ </a>
258
+ </div>
259
+ <div class="product-card card-cyan">
260
+ <div class="product-glow"></div>
261
+ <a href="https://markup.beforesemicolon.com/documentation/capabilities/router" target="_blank" rel="noreferrer" class="product-head">
262
+ <div class="product-title-row">
263
+ <div class="icon-container">
264
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="19" r="3"/><circle cx="18" cy="5" r="3"/><path d="M9 19h8.5a3.5 3.5 0 0 0 3.5-3.5V8.5a3.5 3.5 0 0 0-3.5-3.5H9"/></svg>
265
+ </div>
266
+ <div>
267
+ <h3>Router</h3>
268
+ <span class="package-name font-mono">@beforesemicolon/router</span>
269
+ </div>
270
+ </div>
271
+ <span class="product-arrow">
272
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 17 17 7"/><path d="M7 7h10v10"/></svg>
273
+ </span>
274
+ </a>
275
+ <p>Declarative routing as web component tags. Nested routes, query matching, lazy-loaded pages — zero JavaScript required.</p>
276
+ <a href="https://markup.beforesemicolon.com/documentation/capabilities/router" target="_blank" rel="noreferrer" class="product-link">
277
+ <span>Read the docs</span>
278
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 17 17 7"/><path d="M7 7h10v10"/></svg>
279
+ </a>
280
+ </div>
281
+ </div>
282
+ </section>
283
+
284
+ <!-- Features Section -->
285
+ <section id="features" class="features-section wrapper">
286
+ <div class="section-header">
287
+ <span class="section-tag font-mono">// why markup</span>
288
+ <h2>The platform is the framework.</h2>
289
+ <p>Web Standards, Web APIs, and modern JavaScript are all you need. Markup just adds the reactivity.</p>
290
+ </div>
291
+ <div class="features-grid">
292
+ <div class="feature-card">
293
+ <div class="feature-border"></div>
294
+ <div class="icon-box">
295
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
296
+ </div>
297
+ <h3>Reactive</h3>
298
+ <p>Template literals and functions create reactive DOM with state, lifecycles, and side-effects.</p>
299
+ </div>
300
+ <div class="feature-card">
301
+ <div class="feature-border"></div>
302
+ <div class="icon-box">
303
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.24 12.24a6 6 0 0 0-8.49-8.49L5 10.5V19h8.5z"/><line x1="16" y1="8" x2="2" y2="22"/><line x1="17.5" y1="15" x2="9" y2="15"/></svg>
304
+ </div>
305
+ <h3>Tiny — under 8KB gzip</h3>
306
+ <p>The CDN browser build transfers at about 7.6KB gzip. Ship enterprise apps without a megabyte of framework.</p>
307
+ </div>
308
+ <div class="feature-card">
309
+ <div class="feature-border"></div>
310
+ <div class="icon-box">
311
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8.3 10a3.5 3.5 0 0 1 0-7h7.4a3.5 3.5 0 0 1 0 7H8.3Z"/><path d="M12 14a3 3 0 0 1 3-3h3.5a3 3 0 0 1 3 3v3.5a3 3 0 0 1-3 3H15a3 3 0 0 1-3-3V14Z"/><path d="M2 14.5a3.5 3.5 0 0 1 7 0v2.8a3.5 3.5 0 0 1-7 0v-2.8Z"/></svg>
312
+ </div>
313
+ <h3>Web Standards</h3>
314
+ <span class="hide-element"></span>
315
+ <p>Three simple APIs that extend the platform you already know. No proprietary abstractions.</p>
316
+ </div>
317
+ <div class="feature-card">
318
+ <div class="feature-border"></div>
319
+ <div class="icon-box">
320
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22v-5"/><path d="M9 8V2"/><path d="M15 8V2"/><path d="M18 8H6v5a4 4 0 0 0 4 4h4a4 4 0 0 0 4-4Z"/></svg>
321
+ </div>
322
+ <h3>Plug & Play</h3>
323
+ <p>Drop in a script tag and go. No build step, no JSX, no configuration files.</p>
324
+ </div>
325
+ <div class="feature-card">
326
+ <div class="feature-border"></div>
327
+ <div class="icon-box">
328
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5.5 8.5 9 12l-3.5 3.5L2 12l3.5-3.5Z"/><path d="m12 2 3.5 3.5L12 9 8.5 5.5 12 2Z"/><path d="M18.5 8.5 22 12l-3.5 3.5L15 12l3.5-3.5Z"/><path d="m12 15 3.5 3.5-3.5 3.5-3.5-3.5 3.5-3.5Z"/></svg>
329
+ </div>
330
+ <h3>Web Components</h3>
331
+ <p>Supercharge native Web Components with reactivity. Skip manual DOM manipulation.</p>
332
+ </div>
333
+ <div class="feature-card">
334
+ <div class="feature-border"></div>
335
+ <div class="icon-box">
336
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/></svg>
337
+ </div>
338
+ <h3>Surgical Updates</h3>
339
+ <p>Data-driven rendering means the DOM updates only where and when it actually needs to.</p>
340
+ </div>
341
+ </div>
342
+ </section>
343
+
344
+ <!-- Showcase Section -->
345
+ <section id="code" class="showcase-section relative overflow-hidden">
346
+ <div class="absolute inset-0 showcase-glow pointer-events-none"></div>
347
+ <div class="wrapper relative">
348
+ <div class="showcase-header">
349
+ <div class="showcase-header-text">
350
+ <span class="section-tag font-mono">// see it in action</span>
351
+ <h2>Looks like HTML. Feels like magic.</h2>
352
+ <p>Reactive state, component composition, and lifecycle — all from the JavaScript primitives you already know.</p>
353
+ </div>
354
+ <div class="slider-controls">
355
+ <button id="slider-prev" aria-label="Previous example" class="ctrl-btn btn-prev">
356
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 19-7-7 7-7"/><path d="M19 12H5"/></svg>
357
+ </button>
358
+ <button id="slider-next" aria-label="Next example" class="ctrl-btn btn-next">
359
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
360
+ </button>
361
+ </div>
362
+ </div>
363
+
364
+ <div class="slider-viewport">
365
+ <div class="slider-track">
366
+ <div class="slider-slide">
367
+ <div class="slide-meta">
368
+ <span class="slide-num font-mono">EXAMPLE 01</span>
369
+ <div class="slide-line"></div>
370
+ <span class="slide-label font-mono color-orange">Todos + localStorage</span>
371
+ </div>
372
+ ${renderCodeBlock('todos.js', todoCode)}
373
+ </div>
374
+ <div class="slider-slide">
375
+ <div class="slide-meta">
376
+ <span class="slide-num font-mono">EXAMPLE 02</span>
377
+ <div class="slide-line"></div>
378
+ <span class="slide-label font-mono color-cyan">Button component using WebComponent</span>
379
+ </div>
380
+ ${renderCodeBlock('button-component.js', buttonComponentCode)}
381
+ </div>
382
+ <div class="slider-slide">
383
+ <div class="slide-meta">
384
+ <span class="slide-num font-mono">EXAMPLE 03</span>
385
+ <div class="slide-line"></div>
386
+ <span class="slide-label font-mono color-orange">Suspense (async)</span>
387
+ </div>
388
+ ${renderCodeBlock('profile.js', suspenseCode)}
389
+ </div>
390
+ <div class="slider-slide">
391
+ <div class="slide-meta">
392
+ <span class="slide-num font-mono">EXAMPLE 04</span>
393
+ <div class="slide-line"></div>
394
+ <span class="slide-label font-mono color-cyan">Page routing</span>
395
+ </div>
396
+ ${renderCodeBlock('app.html', routerCode, 'html')}
397
+ </div>
398
+ <div class="slider-slide">
399
+ <div class="slide-meta">
400
+ <span class="slide-num font-mono">EXAMPLE 05</span>
401
+ <div class="slide-line"></div>
402
+ <span class="slide-label font-mono color-orange">Template lifecycles</span>
403
+ </div>
404
+ ${renderCodeBlock('timer.js', lifecycleCode)}
405
+ </div>
406
+ </div>
407
+ </div>
408
+
409
+ <div class="slider-dots-container">
410
+ <button class="slider-dot active" aria-label="Go to slide 1"></button>
411
+ <button class="slider-dot" aria-label="Go to slide 2"></button>
412
+ <button class="slider-dot" aria-label="Go to slide 3"></button>
413
+ <button class="slider-dot" aria-label="Go to slide 4"></button>
414
+ <button class="slider-dot" aria-label="Go to slide 5"></button>
415
+ </div>
416
+ </div>
417
+ </section>
418
+
419
+ <!-- Install Section -->
420
+ <section id="install" class="install-section wrapper">
421
+ <div class="install-container">
422
+ <div class="section-header center">
423
+ <span class="section-tag font-mono">// quick start</span>
424
+ <h2>Install in seconds.</h2>
425
+ <p>Pick your weapon. Markup works everywhere JavaScript runs.</p>
426
+ </div>
427
+
428
+ <div class="tabs-container">
429
+ <div class="tabs-list" role="tablist" aria-label="Installation package manager">
430
+ <button class="tab-trigger" role="tab" aria-selected="false" aria-controls="tab-cdn" id="tab-trigger-cdn" data-tab="cdn">CDN</button>
431
+ <button class="tab-trigger active" role="tab" aria-selected="true" aria-controls="tab-npm" id="tab-trigger-npm" data-tab="npm">npm</button>
432
+ <button class="tab-trigger" role="tab" aria-selected="false" aria-controls="tab-yarn" id="tab-trigger-yarn" data-tab="yarn">yarn</button>
433
+ <button class="tab-trigger" role="tab" aria-selected="false" aria-controls="tab-pnpm" id="tab-trigger-pnpm" data-tab="pnpm">pnpm</button>
434
+ </div>
435
+ <div class="tabs-content-wrapper">
436
+ <div class="tab-content" id="tab-cdn" role="tabpanel" tabindex="0" aria-labelledby="tab-trigger-cdn">
437
+ <div class="install-code-row" data-code="&lt;script src=&quot;https://unpkg.com/@beforesemicolon/markup/dist/client.js&quot;&gt;&lt;/script&gt;">
438
+ <div class="install-command">
439
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>
440
+ <code class="font-mono">&lt;script src="https://unpkg.com/@beforesemicolon/markup/dist/client.js"&gt;&lt;/script&gt;</code>
441
+ </div>
442
+ <button class="install-copy-btn" aria-label="Copy CDN install command">
443
+ <svg aria-hidden="true" class="copy-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
444
+ <svg aria-hidden="true" class="check-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
445
+ <span>Copy</span>
446
+ </button>
447
+ </div>
448
+ </div>
449
+ <div class="tab-content active" id="tab-npm" role="tabpanel" tabindex="0" aria-labelledby="tab-trigger-npm">
450
+ <div class="install-code-row" data-code="npm install @beforesemicolon/markup">
451
+ <div class="install-command">
452
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>
453
+ <code class="font-mono">npm install @beforesemicolon/markup</code>
454
+ </div>
455
+ <button class="install-copy-btn" aria-label="Copy npm install command">
456
+ <svg aria-hidden="true" class="copy-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
457
+ <svg aria-hidden="true" class="check-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
458
+ <span>Copy</span>
459
+ </button>
460
+ </div>
461
+ </div>
462
+ <div class="tab-content" id="tab-yarn" role="tabpanel" tabindex="0" aria-labelledby="tab-trigger-yarn">
463
+ <div class="install-code-row" data-code="yarn add @beforesemicolon/markup">
464
+ <div class="install-command">
465
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>
466
+ <code class="font-mono">yarn add @beforesemicolon/markup</code>
467
+ </div>
468
+ <button class="install-copy-btn" aria-label="Copy yarn install command">
469
+ <svg aria-hidden="true" class="copy-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
470
+ <svg aria-hidden="true" class="check-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
471
+ <span>Copy</span>
472
+ </button>
473
+ </div>
474
+ </div>
475
+ <div class="tab-content" id="tab-pnpm" role="tabpanel" tabindex="0" aria-labelledby="tab-trigger-pnpm">
476
+ <div class="install-code-row" data-code="pnpm add @beforesemicolon/markup">
477
+ <div class="install-command">
478
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>
479
+ <code class="font-mono">pnpm add @beforesemicolon/markup</code>
480
+ </div>
481
+ <button class="install-copy-btn" aria-label="Copy pnpm install command">
482
+ <svg aria-hidden="true" class="copy-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
483
+ <svg aria-hidden="true" class="check-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
484
+ <span>Copy</span>
485
+ </button>
486
+ </div>
487
+ </div>
488
+ </div>
489
+ </div>
490
+ </div>
491
+ </section>
492
+
493
+ <!-- CTA Section -->
494
+ <section class="cta-section wrapper">
495
+ <div class="cta-card">
496
+ <div class="cta-glow"></div>
497
+ <h2>
498
+ <span class="text-gradient">Build the Web,</span>
499
+ <span class="text-gradient-primary">your way.</span>
500
+ </h2>
501
+ <p>Join developers shipping faster with a framework that respects the platform — and your time.</p>
502
+ <div class="cta-buttons">
503
+ <a href="/documentation/get-started" class="btn-primary">
504
+ <span>Get Started</span>
505
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
506
+ </a>
507
+ <a href="/documentation/index.html?v=20260621exact" class="btn-outline">
508
+ <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
509
+ <span>Read the Docs</span>
510
+ </a>
511
+ </div>
512
+ </div>
513
+ </section>
514
+
515
+ ${footer()}
516
+
517
+ ${copyCode()}
518
+
519
+ <!-- Slider and Tabs Scripts -->
520
+ <script type="application/javascript">
521
+ // Custom vanilla slider implementation
522
+ (function() {
523
+ const track = document.querySelector('.slider-track');
524
+ const originalSlides = Array.from(document.querySelectorAll('.slider-slide'));
525
+ const dots = document.querySelectorAll('.slider-dot');
526
+ const btnPrev = document.getElementById('slider-prev');
527
+ const btnNext = document.getElementById('slider-next');
528
+ const slideCount = originalSlides.length;
529
+ let currentIndex = 1;
530
+ let logicalIndex = 0;
531
+
532
+ if (track && slideCount > 1) {
533
+ const firstClone = originalSlides[0].cloneNode(true);
534
+ const lastClone = originalSlides[slideCount - 1].cloneNode(true);
535
+ firstClone.setAttribute('aria-hidden', 'true');
536
+ lastClone.setAttribute('aria-hidden', 'true');
537
+ track.insertBefore(lastClone, originalSlides[0]);
538
+ track.appendChild(firstClone);
539
+ }
540
+
541
+ const slides = Array.from(document.querySelectorAll('.slider-slide'));
542
+
543
+ function setTrack(index, animate = true) {
544
+ const slide = slides[index];
545
+ const offset = slide ? slide.offsetLeft : 0;
546
+ track.style.transition = animate
547
+ ? 'transform 0.5s cubic-bezier(0.22, 1, 0.36, 1)'
548
+ : 'none';
549
+ track.style.transform = 'translateX(-' + offset + 'px)';
550
+ }
551
+
552
+ function updateDots(index) {
553
+ logicalIndex = ((index % slideCount) + slideCount) % slideCount;
554
+ dots.forEach((dot, idx) => {
555
+ if (idx === logicalIndex) {
556
+ dot.classList.add('active');
557
+ } else {
558
+ dot.classList.remove('active');
559
+ }
560
+ });
561
+ }
562
+
563
+ function updateSlider(index, animate = true) {
564
+ currentIndex = index;
565
+ setTrack(currentIndex, animate);
566
+ updateDots(currentIndex - 1);
567
+ }
568
+
569
+ if (btnPrev && btnNext && track) {
570
+ btnPrev.addEventListener('click', () => updateSlider(currentIndex - 1));
571
+ btnNext.addEventListener('click', () => updateSlider(currentIndex + 1));
572
+ track.addEventListener('transitionend', () => {
573
+ if (currentIndex === 0) {
574
+ currentIndex = slideCount;
575
+ setTrack(currentIndex, false);
576
+ } else if (currentIndex === slideCount + 1) {
577
+ currentIndex = 1;
578
+ setTrack(currentIndex, false);
579
+ }
580
+ });
581
+
582
+ dots.forEach((dot, idx) => {
583
+ dot.addEventListener('click', () => updateSlider(idx + 1));
584
+ });
585
+
586
+ updateSlider(currentIndex, false);
587
+ window.addEventListener('resize', () => updateSlider(currentIndex, false));
588
+ }
589
+
590
+ // Tabs Switcher
591
+ const tabTriggers = document.querySelectorAll('.tab-trigger');
592
+ const tabContents = document.querySelectorAll('.tab-content');
593
+ const installCopyButtons = document.querySelectorAll('.install-copy-btn');
594
+
595
+ tabTriggers.forEach(trigger => {
596
+ trigger.addEventListener('click', () => {
597
+ const targetTab = trigger.getAttribute('data-tab');
598
+
599
+ tabTriggers.forEach(t => {
600
+ t.classList.remove('active');
601
+ t.setAttribute('aria-selected', 'false');
602
+ });
603
+ tabContents.forEach(c => c.classList.remove('active'));
604
+
605
+ trigger.classList.add('active');
606
+ trigger.setAttribute('aria-selected', 'true');
607
+ const activeContent = document.getElementById('tab-' + targetTab);
608
+ if (activeContent) activeContent.classList.add('active');
609
+ });
610
+ });
611
+
612
+ installCopyButtons.forEach(button => {
613
+ const originalLabel = button.getAttribute('aria-label') || '';
614
+ button.addEventListener('click', async () => {
615
+ const row = button.closest('.install-code-row');
616
+ const value = row ? row.getAttribute('data-code') : '';
617
+ try {
618
+ await navigator.clipboard.writeText(value);
619
+ button.classList.add('copied');
620
+ button.querySelector('span').textContent = 'Copied';
621
+ button.setAttribute('aria-label', 'Command copied');
622
+ setTimeout(() => {
623
+ button.classList.remove('copied');
624
+ button.querySelector('span').textContent = 'Copy';
625
+ button.setAttribute('aria-label', originalLabel);
626
+ }, 1500);
627
+ } catch {}
628
+ });
629
+ });
630
+ })();
631
+ </script>
632
+
633
+ </body>
634
+ </html>
635
+ `
636
+ }