@invisibleloop/pulse 0.1.28 → 0.1.29

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 (117) hide show
  1. package/.claude/settings.local.json +113 -0
  2. package/.github/workflows/publish.yml +11 -21
  3. package/docs/public/.pulse-ui-version +1 -1
  4. package/docs/public/docs.css +19 -1
  5. package/docs/public/pulse-ui.css +1 -0
  6. package/docs/server.js +5 -2
  7. package/docs/src/lib/highlight.js +57 -13
  8. package/docs/src/lib/layout.js +5 -2
  9. package/docs/src/pages/faq.js +5 -2
  10. package/docs/src/pages/home.js +9 -5
  11. package/docs/src/pages/meta.js +21 -0
  12. package/docs/src/pages/routing.js +12 -1
  13. package/package.json +1 -1
  14. package/src/agent/guide-routing.md +20 -0
  15. package/src/agent/guide-spec.md +9 -1
  16. package/src/cli/scaffold.js +63 -2
  17. package/src/server/index.js +21 -6
  18. package/src/server/server.test.js +47 -0
  19. package/docs/public/dist/accessibility.boot-5DVTARJU.js +0 -115
  20. package/docs/public/dist/actions.boot-P66HKQEM.js +0 -164
  21. package/docs/public/dist/auth.boot-IMAJAUPH.js +0 -140
  22. package/docs/public/dist/caching.boot-DVR6KDE7.js +0 -53
  23. package/docs/public/dist/components--accordion.boot-3HVKMNWC.js +0 -11
  24. package/docs/public/dist/components--alert.boot-GCEXOZAC.js +0 -6
  25. package/docs/public/dist/components--app-badge.boot-DVT3GCHJ.js +0 -6
  26. package/docs/public/dist/components--avatar.boot-PSW24EVA.js +0 -5
  27. package/docs/public/dist/components--badge.boot-TYDY2RMK.js +0 -7
  28. package/docs/public/dist/components--banner.boot-EI5PZSZK.js +0 -7
  29. package/docs/public/dist/components--breadcrumbs.boot-SMA2E2GO.js +0 -34
  30. package/docs/public/dist/components--button.boot-J54BQM2E.js +0 -23
  31. package/docs/public/dist/components--card.boot-PZGNDIB6.js +0 -138
  32. package/docs/public/dist/components--carousel.boot-TP6LPFZZ.js +0 -12
  33. package/docs/public/dist/components--charts.boot-2EOYQWKL.js +0 -108
  34. package/docs/public/dist/components--checkbox.boot-DS5BSL6T.js +0 -54
  35. package/docs/public/dist/components--cluster.boot-HHVIBBJG.js +0 -9
  36. package/docs/public/dist/components--code-window.boot-2GR2DV33.js +0 -20
  37. package/docs/public/dist/components--container.boot-7LOOGK2K.js +0 -5
  38. package/docs/public/dist/components--cta.boot-FSNZ5YRT.js +0 -11
  39. package/docs/public/dist/components--divider.boot-3NI2C3QG.js +0 -6
  40. package/docs/public/dist/components--empty.boot-YX2UR3PV.js +0 -7
  41. package/docs/public/dist/components--feature.boot-MUD7NSUO.js +0 -13
  42. package/docs/public/dist/components--fieldset.boot-J7BYHMKF.js +0 -19
  43. package/docs/public/dist/components--fileupload.boot-NIKVTTPD.js +0 -52
  44. package/docs/public/dist/components--footer.boot-EYUK5FRG.js +0 -14
  45. package/docs/public/dist/components--grid.boot-URDQVDDR.js +0 -59
  46. package/docs/public/dist/components--heading.boot-BPQKU43E.js +0 -44
  47. package/docs/public/dist/components--hero.boot-4RAPRGAB.js +0 -17
  48. package/docs/public/dist/components--icons.boot-ZITNU5JP.js +0 -68
  49. package/docs/public/dist/components--image.boot-XEEGHQZF.js +0 -19
  50. package/docs/public/dist/components--input.boot-SGASZG5K.js +0 -7
  51. package/docs/public/dist/components--list.boot-W3XC5MHD.js +0 -55
  52. package/docs/public/dist/components--media.boot-5VFIETZO.js +0 -13
  53. package/docs/public/dist/components--modal.boot-RZUYXBN2.js +0 -47
  54. package/docs/public/dist/components--nav.boot-ODBOHU7O.js +0 -33
  55. package/docs/public/dist/components--pricing.boot-4AQ4ZVBY.js +0 -21
  56. package/docs/public/dist/components--progress.boot-GHAGYZOK.js +0 -30
  57. package/docs/public/dist/components--prose.boot-QANJL6JI.js +0 -67
  58. package/docs/public/dist/components--pullquote.boot-Q2WMNAZU.js +0 -22
  59. package/docs/public/dist/components--radio.boot-TJRDQ2OL.js +0 -75
  60. package/docs/public/dist/components--rating.boot-QBAN6DEL.js +0 -38
  61. package/docs/public/dist/components--search.boot-PXH5O5AG.js +0 -17
  62. package/docs/public/dist/components--section.boot-AQGIYHWW.js +0 -12
  63. package/docs/public/dist/components--segmented.boot-BEVTKEJO.js +0 -33
  64. package/docs/public/dist/components--select.boot-47X5RHOC.js +0 -10
  65. package/docs/public/dist/components--slider.boot-PSRRX7XL.js +0 -47
  66. package/docs/public/dist/components--spinner.boot-MZ5MO2OH.js +0 -22
  67. package/docs/public/dist/components--stack.boot-DI4NJXBF.js +0 -9
  68. package/docs/public/dist/components--stat.boot-QMFUWBQT.js +0 -9
  69. package/docs/public/dist/components--stepper.boot-34PP2NEV.js +0 -22
  70. package/docs/public/dist/components--table.boot-FCQGSFIQ.js +0 -11
  71. package/docs/public/dist/components--testimonial.boot-DWQPDKYG.js +0 -11
  72. package/docs/public/dist/components--textarea.boot-QVXLBOJ5.js +0 -4
  73. package/docs/public/dist/components--timeline.boot-26LN52P2.js +0 -95
  74. package/docs/public/dist/components--toggle.boot-IQQEI76S.js +0 -29
  75. package/docs/public/dist/components--tooltip.boot-LGHCO6NN.js +0 -9
  76. package/docs/public/dist/components.boot-SE6PQ4P7.js +0 -103
  77. package/docs/public/dist/config.boot-DTRRWUE6.js +0 -126
  78. package/docs/public/dist/constraints.boot-DUHDZBMC.js +0 -71
  79. package/docs/public/dist/deploy.boot-SLAD3NI2.js +0 -163
  80. package/docs/public/dist/docs-8e3d4b5c.css +0 -1
  81. package/docs/public/dist/extending.boot-UA3CN243.js +0 -159
  82. package/docs/public/dist/faq.boot-6EQAWLQR.js +0 -43
  83. package/docs/public/dist/getting-started.boot-TDKIFL5U.js +0 -86
  84. package/docs/public/dist/guard.boot-AUHAWTG4.js +0 -80
  85. package/docs/public/dist/home.boot-BVQXRH32.js +0 -383
  86. package/docs/public/dist/how-it-works.boot-LTWAKWKW.js +0 -104
  87. package/docs/public/dist/hydration.boot-JRM6IPJL.js +0 -78
  88. package/docs/public/dist/images.boot-M6ZVKTZS.js +0 -80
  89. package/docs/public/dist/manifest.json +0 -94
  90. package/docs/public/dist/meta.boot-7NXGPHR4.js +0 -79
  91. package/docs/public/dist/mutations.boot-F6F43UDX.js +0 -79
  92. package/docs/public/dist/navigation.boot-AOXWS3ZF.js +0 -57
  93. package/docs/public/dist/performance.boot-C3UPCOBK.js +0 -98
  94. package/docs/public/dist/persist.boot-WT32PQOQ.js +0 -61
  95. package/docs/public/dist/project-structure.boot-FB3LRVJ4.js +0 -63
  96. package/docs/public/dist/prompt-examples.boot-YKR4VDK4.js +0 -31
  97. package/docs/public/dist/pulse-ui-81a85c03.css +0 -1
  98. package/docs/public/dist/raw-responses.boot-M4KA5YXL.js +0 -104
  99. package/docs/public/dist/routing.boot-FNX5FDGH.js +0 -70
  100. package/docs/public/dist/runtime-B73WLANC.js +0 -1
  101. package/docs/public/dist/runtime-KO4BHUQ3.js +0 -49
  102. package/docs/public/dist/runtime-L2HNXIHW.js +0 -59
  103. package/docs/public/dist/runtime-QFURDKA2.js +0 -5
  104. package/docs/public/dist/runtime-UVPXO4IR.js +0 -375
  105. package/docs/public/dist/runtime-VMJA3Z4N.js +0 -10
  106. package/docs/public/dist/runtime-ZJ4FXT5O.js +0 -11
  107. package/docs/public/dist/server-api.boot-K7X3LCFB.js +0 -219
  108. package/docs/public/dist/server-data.boot-Y7HQYC4R.js +0 -157
  109. package/docs/public/dist/slash-commands.boot-V2UV7OW2.js +0 -26
  110. package/docs/public/dist/spec.boot-2WU7ZHCV.js +0 -159
  111. package/docs/public/dist/state.boot-B24GUE3R.js +0 -73
  112. package/docs/public/dist/store.boot-TLIB4XHH.js +0 -150
  113. package/docs/public/dist/streaming.boot-W2DZSMW4.js +0 -80
  114. package/docs/public/dist/stripe.boot-QN3C2GEL.js +0 -164
  115. package/docs/public/dist/supabase.boot-BG4XXLZE.js +0 -303
  116. package/docs/public/dist/testing.boot-6U4WKMTE.js +0 -130
  117. package/docs/public/dist/validation.boot-PQHYGW5B.js +0 -100
@@ -1,63 +0,0 @@
1
- import{a as o}from"./runtime-QFURDKA2.js";import{a as n,b as c,c as d,d as p,e,g as s,h as l,i as r}from"./runtime-L2HNXIHW.js";import{a as i,b as u}from"./runtime-B73WLANC.js";var{prev:m,next:h}=n("/project-structure"),a={route:"/project-structure",meta:{title:"Project Structure \u2014 Pulse Docs",description:"Recommended directory layout for a Pulse application.",styles:["/docs.css"]},state:{},view:()=>c({currentHref:"/project-structure",prev:m,next:h,content:`
2
- ${d("Project Structure")}
3
- ${p("Pulse enforces a single convention: one spec per page, one directory per concern. The structure is not a recommendation \u2014 it is how the framework discovers and registers your pages. There is nothing to configure because there is nothing to decide.")}
4
-
5
- ${e("layout","Layout")}
6
- ${s(o(`my-app/
7
- \u251C\u2500\u2500 package.json
8
- \u251C\u2500\u2500 public/ # static assets served directly
9
- \u2502 \u251C\u2500\u2500 app.css
10
- \u2502 \u2514\u2500\u2500 dist/ # generated bundles (pulse build)
11
- \u2502 \u251C\u2500\u2500 manifest.json
12
- \u2502 \u251C\u2500\u2500 runtime-[hash].js
13
- \u2502 \u2514\u2500\u2500 [name].boot-[hash].js
14
- \u2514\u2500\u2500 src/
15
- \u251C\u2500\u2500 pages/ # one file per page \u2014 auto-discovered
16
- \u2502 \u251C\u2500\u2500 home.js
17
- \u2502 \u251C\u2500\u2500 about.js
18
- \u2502 \u2514\u2500\u2500 contact.js
19
- \u251C\u2500\u2500 lib/ # shared helpers
20
- \u2502 \u2514\u2500\u2500 db.js
21
- \u2514\u2500\u2500 components/ # shared view fragments (optional)
22
- \u2514\u2500\u2500 card.js`,"bash"))}
23
-
24
- ${e("pages","src/pages/")}
25
- <p>Each file in <code>src/pages/</code> exports a single spec as the default export. <code>pulse dev</code> auto-discovers every file in this directory and registers it as a route. There is no route registry to maintain \u2014 the file is the registration.</p>
26
-
27
- ${r("note","Routes are derived from filenames by default (<code>about.js</code> \u2192 <code>/about</code>). For dynamic segments, <code>route</code> is set explicitly in the spec.")}
28
-
29
- ${e("public","public/")}
30
- <p>Static files served directly \u2014 CSS, fonts, images. Referenced via <code>meta.styles</code> in the spec. The <code>dist/</code> subdirectory is generated by <code>pulse build</code>. Its contents are content-hashed and must not be edited manually.</p>
31
-
32
- ${l(["Path","Purpose"],[["<code>public/app.css</code>","Your global stylesheet \u2014 reference it in spec <code>meta.styles</code>"],["<code>public/dist/</code>","Generated bundles from <code>npm run build</code> \u2014 do not edit manually"],["<code>public/dist/manifest.json</code>","Maps spec hydrate paths to hashed bundle filenames"]])}
33
-
34
- ${e("lib","src/lib/")}
35
- <p>Shared helpers used across multiple pages \u2014 database clients, API wrappers, utility functions. Imported directly into specs. Plain JavaScript modules with no framework coupling \u2014 they work identically in tests, scripts, or other contexts.</p>
36
-
37
- ${e("components","src/components/ (optional)")}
38
- <p>Reusable view fragments. Since views are just functions that return HTML strings, a component is simply a function:</p>
39
-
40
- ${s(o(`// src/components/card.js
41
- export function card({ title, body }) {
42
- return \`
43
- <div class="card">
44
- <h2>\${title}</h2>
45
- <p>\${body}</p>
46
- </div>
47
- \`
48
- }`,"js"),"src/components/card.js")}
49
-
50
- ${s(o(`// src/pages/home.js
51
- import { card } from '../components/card.js'
52
-
53
- export default {
54
- route: '/',
55
- state: {},
56
- view: () => card({ title: 'Hello', body: 'Welcome to Pulse' }),
57
- }`,"js"),"src/pages/home.js")}
58
-
59
- ${e("one-file-per-page","One file per page")}
60
- <p>Pulse enforces <strong>one spec per file</strong>. Every page is self-contained \u2014 state, view, mutations, actions, and validation in one place. This is not a style preference. It is how the framework eliminates the question of where things live.</p>
61
-
62
- ${r("tip","For large pages, view fragments and helpers can be imported from other modules. The spec file remains the coordination point \u2014 the structure is always clear regardless of how the implementation is organised.")}
63
- `})};var t=document.getElementById("pulse-root");t&&!t.dataset.pulseMounted&&(t.dataset.pulseMounted="1",i(a,t,window.__PULSE_SERVER__||{},{ssr:!0}),u(t,i));var P=a;export{P as default};
@@ -1,31 +0,0 @@
1
- import{a as d,b as n,c as i,d as c}from"./runtime-L2HNXIHW.js";import{a as s,b as p}from"./runtime-B73WLANC.js";var{prev:u,next:l}=d("/prompt-examples");function g({tag:o,prompt:a,produces:h}){return`
2
- <div class="prompt-card">
3
- <div class="prompt-tag">${o}</div>
4
- <blockquote class="prompt-text">${a}</blockquote>
5
- <p class="prompt-produces">${h}</p>
6
- </div>`}function e(o,a){return`
7
- <div class="prompt-group">
8
- <h3 class="prompt-group-title">${o}</h3>
9
- <div class="prompt-grid">
10
- ${a.map(g).join("")}
11
- </div>
12
- </div>`}var r={route:"/prompt-examples",meta:{title:"Prompt Examples \u2014 Pulse Docs",description:"Real prompts for building Pulse pages with your AI agent \u2014 from creating pages to authentication, performance audits, and integrations.",styles:["/docs.css"]},state:{},view:()=>n({currentHref:"/prompt-examples",prev:u,next:l,content:`
13
- ${i("Prompt Examples")}
14
- ${c("These prompts produce correct Pulse specs because the framework constrains the output. The agent works within a defined structure \u2014 there is no ambiguity about where state lives, how data is fetched, or how validation is wired.")}
15
-
16
- ${e("Creating pages",[{tag:"New page",prompt:"Create a page at /about with a heading, a short paragraph about the company, and a link back to the home page.",produces:"A static page spec with <code>route</code>, <code>meta</code>, and a <code>view</code> returning the HTML."},{tag:"Dynamic route",prompt:"Create a blog post page at /posts/:slug. It should fetch the post from the database by slug and display the title, published date, and body content. If no post is found, return a 404.",produces:"A spec with a parameterised <code>route</code>, a <code>server.data</code> fetcher using <code>ctx.params.slug</code>, and a 404 raw response if the post is missing."},{tag:"Listing page",prompt:"Create a paginated list of articles at /articles. Read the ?page query parameter to fetch the right slice. Show 10 articles per page with previous and next links.",produces:"A spec with <code>server.data</code> reading <code>ctx.query.page</code>, pagination state, and prev/next links rendered in the view."}])}
17
-
18
- ${e("State & interactions",[{tag:"Live filter",prompt:"Add a search input to the /products page that filters the visible product list as the user types. Keep the full product list in server data and filter it in the view.",produces:'A mutation bound to <code>data-event="input:setQuery"</code> that updates a <code>query</code> state field, with the view filtering <code>server.data.products</code> against it.'},{tag:"Toggle",prompt:"Add a show/hide toggle to the FAQ page. Each question should expand its answer when clicked and collapse when clicked again.",produces:"An <code>openId</code> state field and a <code>toggle</code> mutation. The view renders answers conditionally based on whether their id matches <code>openId</code>."},{tag:"Tabs",prompt:"The /dashboard page has three tabs: Overview, Activity, and Settings. Clicking a tab should show its panel and hide the others without a page reload.",produces:"An <code>activeTab</code> state field, a <code>setTab</code> mutation, and tab panels rendered conditionally in the view."}])}
19
-
20
- ${e("Forms & validation",[{tag:"Contact form",prompt:"Add a contact form at /contact with name, email, and message fields. All three are required. Email must be a valid format. Show inline errors on failure. Show a success message on submission.",produces:"A spec with <code>validation</code> rules, an <code>actions.submit</code> with <code>validate: true</code>, and <code>onSuccess</code>/<code>onError</code> state transitions."},{tag:"Multi-step form",prompt:"Create a multi-step signup form at /signup with three steps: account details, personal info, and confirmation. Show a step indicator at the top. Only advance when the current step is valid.",produces:"A <code>step</code> state field, step-specific validation, a <code>next</code> action that validates before advancing, and a view that renders the active step panel."},{tag:"Constraints",prompt:"The quantity input on the /cart page should never go below 1 or above 99. Enforce this on the server \u2014 not just in the UI.",produces:"A <code>constraints</code> block with <code>quantity: { min: 1, max: 99 }</code> that the framework enforces after every mutation, regardless of what the client sends."}])}
21
-
22
- ${e("Server data & actions",[{tag:"Dashboard data",prompt:"The /dashboard page should show the logged-in user's name and their last 5 orders. Fetch both in parallel.",produces:"A <code>server.data</code> fetcher that runs two queries concurrently with <code>Promise.all</code> and passes the results to the view as <code>server.data.user</code> and <code>server.data.orders</code>."},{tag:"Async action",prompt:"Add a Delete button to each row in the /users table. It should send a DELETE request to the API, remove the row on success, and show an error message on failure.",produces:"An action with <code>onStart</code> setting a loading flag, <code>run</code> making the API call, <code>onSuccess</code> removing the item from state, and <code>onError</code> setting an error message."},{tag:"File upload",prompt:"Add an avatar upload form to /settings. The user picks a file, it uploads to storage, and the page shows the new avatar on success.",produces:"An action whose <code>run</code> reads the file from <code>FormData</code>, converts it to an <code>ArrayBuffer</code>, and uploads it to storage. <code>onSuccess</code> updates the avatar URL in state."}])}
23
-
24
- ${e("Authentication",[{tag:"Login page",prompt:"Create a login page at /login with email and password fields. On success, set an httpOnly session cookie and redirect to /dashboard. Show an error message if credentials are wrong.",produces:"A raw response action that verifies credentials, sets <code>Set-Cookie</code> headers with <code>httpOnly; SameSite=Strict</code>, and redirects. Failed logins return the form with an error state."},{tag:"Protected route",prompt:"Protect the /dashboard page so only logged-in users can access it. Redirect anyone without a valid session to /login.",produces:"A <code>guard</code> function that reads the session cookie, verifies it, and returns a redirect response if invalid."},{tag:"Logout",prompt:"Add a logout button to the nav. It should clear the session and redirect to /login.",produces:"A page spec at <code>/logout</code> using the raw response spec (<code>contentType</code> + <code>render</code>) that sets the session cookie to <code>maxAge: 0</code> and issues a 302 redirect."}])}
25
-
26
- ${e("Streaming SSR",[{tag:"Deferred content",prompt:"The /feed page is slow because it waits for the activity feed before painting anything. Make it stream \u2014 show the page chrome instantly and load the feed in the background.",produces:"A <code>stream</code> block with <code>shell: ['header']</code> and <code>deferred: ['feed']</code>. The view becomes a keyed object of named segment functions."},{tag:"Multiple segments",prompt:"The /home page has a hero, a trending section, and a recommendations section. The hero should appear instantly. The other two can load as data resolves.",produces:"Three view segments \u2014 <code>hero</code> in the shell, <code>trending</code> and <code>recommendations</code> deferred. Each fetches its own data independently."}])}
27
-
28
- ${e("Performance & tooling",[{tag:"Lighthouse audit",prompt:"Run a Lighthouse audit across all pages and show me the results.",produces:"The agent runs <code>/pulse-report</code>, which audits every registered route and opens the results dashboard."},{tag:"Score thresholds",prompt:"Make the audit fail if any page scores below 90 for performance or below 95 for accessibility.",produces:"A <code>lighthouse</code> block in <code>pulse.config.js</code> with <code>performance: 90</code> and <code>accessibility: 95</code>."},{tag:"Load test",prompt:"Load test the /api/data endpoint with 50 concurrent connections for 30 seconds and tell me the p99 latency.",produces:"The agent runs <code>/pulse-load</code> against that route with the specified parameters and reports the results."},{tag:"Environment config",prompt:"Set up a staging environment so I can run audits against https://staging.myapp.com instead of localhost.",produces:"An <code>environments.staging</code> block in <code>pulse.config.js</code> with the remote URL. The agent uses it when running <code>/pulse-report</code> or <code>/pulse-load</code>."}])}
29
-
30
- ${e("Integrations",[{tag:"Supabase query",prompt:"The /profile page should load the current user's row from the Supabase users table. It should only return data the logged-in user owns.",produces:"A <code>server.data</code> fetcher using the Supabase public client initialised with the user's access token, so Row Level Security applies automatically."},{tag:"Supabase auth",prompt:"Use Supabase for authentication. The login form should call Supabase's signInWithPassword and store the tokens in httpOnly cookies.",produces:"A login action that calls <code>supabase.auth.signInWithPassword</code>, reads the session tokens from the response, and sets them as <code>httpOnly; SameSite=Strict</code> cookies."},{tag:"Stripe checkout",prompt:"Add a checkout button to the /pricing page for the Pro plan. Clicking it should create a Stripe Checkout Session and redirect the user to the Stripe-hosted page.",produces:"An action whose <code>run</code> calls the Stripe API to create a session and returns the checkout URL. <code>onSuccess</code> triggers a redirect via state."}])}
31
- `})};var t=document.getElementById("pulse-root");t&&!t.dataset.pulseMounted&&(t.dataset.pulseMounted="1",s(r,t,window.__PULSE_SERVER__||{},{ssr:!0}),p(t,s));var A=r;export{A as default};
@@ -1 +0,0 @@
1
- :root{--ui-bg:var(--bg,#0d0d10);--ui-surface:var(--surface,#111116);--ui-surface-2:var(--surface-2,#18181f);--ui-border:var(--border,#38383f);--ui-text:var(--text,#e2e2ea);--ui-muted:var(--muted,#9090a0);--ui-accent:var(--accent,#9b8dff);--ui-accent-hover:var(--accent-hover,#b5aaff);--ui-accent-dim:var(--accent-dim,rgba(155,141,255,.12));--ui-accent-text:var(--accent-text,#ffffff);--ui-green:var(--green,#3dd68c);--ui-green-dim:rgba(61,214,140,.12);--ui-red:var(--red,#ff6b6b);--ui-red-dim:rgba(255,107,107,.12);--ui-yellow:#f5a623;--ui-yellow-dim:rgba(245,166,35,.12);--ui-blue:#60a5fa;--ui-blue-dim:rgba( 96,165,250,.12);--ui-radius:var(--radius,8px);--ui-radius-sm:4px;--ui-font:var(--font,system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif);--ui-mono:var(--mono,'Fira Code','Cascadia Code',monospace);--ui-touch-min:44px}*,*::before,*::after{box-sizing:border-box}h1,h2,h3,h4,h5,h6,p,ul,ol,li{margin:0}ul,ol{list-style:none}html,body{margin:0;background-color:var(--ui-bg);color:var(--ui-text);font-family:var(--ui-font);min-height:100dvh}.ui-btn:focus-visible,.ui-input:focus-visible,.ui-select:focus-visible,.ui-textarea:focus-visible{outline:2px solid var(--ui-accent);outline-offset:2px}.ui-btn{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:.45rem;padding:0 1.2rem;height:var(--ui-touch-min);min-width:var(--ui-touch-min);border:1px solid transparent;border-radius:var(--ui-radius);font-family:var(--ui-font);font-size:.9rem;font-weight:600;line-height:1;text-decoration:none;white-space:nowrap;cursor:pointer;transition:background .15s,color .15s,border-color .15s,opacity .15s;-webkit-font-smoothing:antialiased}.ui-btn--primary{background:var(--ui-accent);color:var(--ui-bg)}.ui-btn--primary:hover:not(.ui-btn--disabled):not([disabled]){background:var(--ui-accent-hover)}.ui-btn--secondary{background:var(--ui-surface-2);color:var(--ui-text);border-color:var(--ui-muted)}.ui-btn--secondary:hover:not(.ui-btn--disabled):not([disabled]){background:var(--ui-surface);border-color:var(--ui-text)}.ui-btn--ghost{background:transparent;color:var(--ui-muted);border:none}.ui-btn--ghost:hover:not(.ui-btn--disabled):not([disabled]){background:var(--ui-surface-2);color:var(--ui-text)}.ui-btn--danger{background:var(--ui-red-dim);color:var(--ui-red);border-color:var(--ui-red)}.ui-btn--danger:hover:not(.ui-btn--disabled):not([disabled]){background:var(--ui-red);color:var(--ui-bg)}.ui-btn--sm{height:36px;padding:0 .85rem;font-size:.82rem;min-width:36px}.ui-btn--lg{height:52px;padding:0 1.75rem;font-size:1rem}.ui-btn--full{width:100%}.ui-btn--disabled,.ui-btn[disabled]{opacity:.45;cursor:not-allowed;pointer-events:none}.ui-btn-icon{display:inline-flex;align-items:center;flex-shrink:0}.ui-badge{display:inline-flex;align-items:center;align-self:flex-start;padding:.3em .55em;border-radius:var(--ui-radius-sm);font-family:var(--ui-font);font-size:.72rem;font-weight:600;line-height:1;white-space:nowrap;letter-spacing:.02em}.ui-badge--default{background:var(--ui-surface-2);color:var(--ui-muted)}.ui-badge--success{background:var(--ui-green-dim);color:var(--ui-green)}.ui-badge--warning{background:var(--ui-yellow-dim);color:var(--ui-yellow)}.ui-badge--error{background:var(--ui-red-dim);color:var(--ui-red)}.ui-badge--info{background:var(--ui-blue-dim);color:var(--ui-blue)}.ui-card{background:var(--ui-surface);border:1px solid var(--ui-border);border-radius:var(--ui-radius);overflow:hidden}.ui-card-header{padding:1rem 1.25rem;border-bottom:1px solid var(--ui-border)}.ui-card-title{margin:0;font-size:.95rem;font-weight:600;color:var(--ui-text);line-height:1.4}.ui-card-body{padding:1.25rem}.ui-card--flush .ui-card-body{padding:0}.ui-card-footer{padding:1rem 1.25rem;border-top:1px solid var(--ui-border);background:var(--ui-surface-2)}.ui-fieldset{border:1px solid var(--ui-border);border-radius:var(--ui-radius);padding:0;margin:0}.ui-fieldset-legend{margin-left:1rem;padding:0 0.375rem;font-size:0.75rem;font-weight:600;letter-spacing:0.06em;text-transform:uppercase;color:var(--ui-muted);line-height:1}.ui-fieldset-body{display:flex;flex-direction:column;gap:1rem;padding:1.25rem}.ui-fieldset--gap-xs .ui-fieldset-body{gap:0.375rem}.ui-fieldset--gap-sm .ui-fieldset-body{gap:0.625rem}.ui-fieldset--gap-lg .ui-fieldset-body{gap:1.5rem}.ui-field{display:flex;flex-direction:column;gap:.4rem}.ui-label{font-family:var(--ui-font);font-size:.85rem;font-weight:600;color:var(--ui-text);line-height:1.4}.ui-required{color:var(--ui-red);margin-left:.15rem}.ui-hint{font-size:.8rem;color:var(--ui-muted);margin:0;line-height:1.5}.ui-error{font-size:.8rem;color:var(--ui-red);margin:0;line-height:1.5}.ui-input{width:100%;height:var(--ui-touch-min);padding:0 .85rem;background:var(--ui-surface);border:1px solid var(--ui-border);border-radius:var(--ui-radius);font-family:var(--ui-font);font-size:.9rem;color:var(--ui-text);transition:border-color .15s;appearance:none;-webkit-appearance:none}.ui-input::placeholder{color:var(--ui-muted)}.ui-input:hover:not(:disabled){border-color:var(--ui-muted)}.ui-input:disabled{opacity:.5;cursor:not-allowed}.ui-field--error .ui-input{border-color:var(--ui-red)}.ui-search-wrap{position:relative;display:flex;align-items:center}.ui-search-icon{position:absolute;left:.75rem;display:flex;align-items:center;color:var(--ui-muted);pointer-events:none}.ui-search-input{width:100%;height:var(--ui-touch-min);padding:0 2.5rem 0 2.5rem;background:var(--ui-surface);border:1px solid var(--ui-border);border-radius:var(--ui-radius);font-family:var(--ui-font);font-size:.9rem;color:var(--ui-text);transition:border-color .15s;appearance:none;-webkit-appearance:none}.ui-search-input::placeholder{color:var(--ui-muted)}.ui-search-input:hover:not(:disabled){border-color:var(--ui-muted)}.ui-search-input:disabled{opacity:.5;cursor:not-allowed}.ui-search-input::-webkit-search-cancel-button{display:none}.ui-search-clear{position:absolute;right:.5rem;display:flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;padding:0;background:transparent;border:none;border-radius:var(--ui-radius-sm);color:var(--ui-muted);cursor:pointer;transition:color .15s,background .15s}.ui-search-clear:hover{color:var(--ui-text);background:var(--ui-surface-2)}.ui-select-wrap{position:relative;display:flex}.ui-select{width:100%;height:var(--ui-touch-min);padding:0 2.5rem 0 .85rem;background:var(--ui-surface);border:1px solid var(--ui-border);border-radius:var(--ui-radius);font-family:var(--ui-font);font-size:.9rem;color:var(--ui-text);cursor:pointer;appearance:none;-webkit-appearance:none;transition:border-color .15s}.ui-select:hover:not(:disabled){border-color:var(--ui-muted)}.ui-select:disabled{opacity:.5;cursor:not-allowed}.ui-field--error .ui-select{border-color:var(--ui-red)}.ui-select-chevron{position:absolute;right:.85rem;top:50%;transform:translateY(-50%);pointer-events:none;color:var(--ui-muted);display:flex;align-items:center}.ui-textarea{width:100%;padding:.65rem .85rem;background:var(--ui-surface);border:1px solid var(--ui-border);border-radius:var(--ui-radius);font-family:var(--ui-font);font-size:.9rem;color:var(--ui-text);resize:vertical;transition:border-color .15s;min-height:calc(var(--ui-touch-min) * 2);appearance:none}.ui-textarea::placeholder{color:var(--ui-muted)}.ui-textarea:hover:not(:disabled){border-color:var(--ui-muted)}.ui-textarea:disabled{opacity:.5;cursor:not-allowed}.ui-field--error .ui-textarea{border-color:var(--ui-red)}.ui-alert{display:flex;align-items:flex-start;gap:.75rem;padding:.85rem 1rem;border-radius:var(--ui-radius);border:1px solid transparent;font-size:.88rem;line-height:1.6}.ui-alert--info{background:var(--ui-blue-dim);border-color:var(--ui-blue);color:var(--ui-blue)}.ui-alert--success{background:var(--ui-green-dim);border-color:var(--ui-green);color:var(--ui-green)}.ui-alert--warning{background:var(--ui-yellow-dim);border-color:var(--ui-yellow);color:var(--ui-yellow)}.ui-alert--error{background:var(--ui-red-dim);border-color:var(--ui-red);color:var(--ui-red)}.ui-alert-icon{display:flex;align-items:center;flex-shrink:0;margin-top:.1rem}.ui-alert-body{color:var(--ui-text)}.ui-alert-title{font-weight:600}.ui-stat{display:flex;flex-direction:column;gap:.3rem}.ui-stat-label{font-size:.8rem;color:var(--ui-muted);font-weight:500;margin:0}.ui-stat-value{font-size:2rem;font-weight:700;color:var(--ui-text);line-height:1;margin:0;letter-spacing:-.02em}.ui-stat-change{display:inline-flex;align-items:center;gap:.3rem;font-size:.8rem;font-weight:600;margin:0}.ui-stat-change--up{color:var(--ui-green)}.ui-stat-change--down{color:var(--ui-red)}.ui-stat-change--neutral{color:var(--ui-muted)}.ui-stat--center{align-items:center;text-align:center}.ui-avatar{display:inline-flex;align-items:center;justify-content:center;border-radius:50%;overflow:hidden;background:var(--ui-accent-dim);color:var(--ui-accent);font-family:var(--ui-font);font-weight:700;flex-shrink:0;object-fit:cover}.ui-avatar--sm{width:28px;height:28px;font-size:.65rem}.ui-avatar--md{width:40px;height:40px;font-size:.85rem}.ui-avatar--lg{width:56px;height:56px;font-size:1.1rem}.ui-avatar--xl{width:80px;height:80px;font-size:1.5rem}.ui-empty{display:flex;flex-direction:column;align-items:center;text-align:center;gap:.75rem;padding:3rem 1.5rem}.ui-empty-title{font-size:1.05rem;font-weight:600;color:var(--ui-text);margin:0}.ui-empty-desc{font-size:.88rem;color:var(--ui-muted);max-width:36ch;margin:0;line-height:1.6}.ui-table-wrap{overflow-x:auto;border:1px solid var(--ui-border);border-radius:var(--ui-radius)}.ui-table-wrap:focus-visible{outline:2px solid var(--ui-accent);outline-offset:2px}.ui-table{width:100%;border-collapse:collapse;font-size:.88rem}.ui-table-caption{caption-side:top;text-align:left;padding:.75rem 1rem;font-size:.8rem;font-weight:600;color:var(--ui-muted);text-transform:uppercase;letter-spacing:.06em;border-bottom:1px solid var(--ui-border)}.ui-table thead th{padding:.65rem 1rem;text-align:left;font-size:.78rem;font-weight:600;color:var(--ui-muted);text-transform:uppercase;letter-spacing:.06em;border-bottom:1px solid var(--ui-border);background:var(--ui-surface-2);white-space:nowrap}.ui-table tbody td{padding:.75rem 1rem;border-bottom:1px solid var(--ui-border);color:var(--ui-text);vertical-align:top;line-height:1.5}.ui-table tbody tr:last-child td{border-bottom:none}.ui-table tbody tr:nth-child(even){background:var(--ui-surface-2)}.ui-container{width:100%;margin-left:auto;margin-right:auto;padding-left:1.5rem;padding-right:1.5rem}.ui-container--md{max-width:768px}.ui-section{padding-top:5rem;padding-bottom:5rem}.ui-section--alt{background:var(--ui-surface,var(--surface,#111116))}.ui-section--dark{background:var(--ui-surface-2,var(--surface-2,#18181f))}.ui-grid{display:grid;gap:2rem}.ui-grid--cols-1{grid-template-columns:1fr}.ui-grid--cols-2{grid-template-columns:repeat(2,1fr)}.ui-grid--cols-3{grid-template-columns:repeat(3,1fr)}.ui-grid--cols-4{grid-template-columns:repeat(4,1fr)}.ui-grid--gap-sm{gap:1rem}.ui-grid--gap-lg{gap:3rem}.ui-stack{display:flex;flex-direction:column;gap:1rem}.ui-stack--gap-sm{gap:.5rem}.ui-stack--gap-lg{gap:2rem}.ui-cluster{display:flex;flex-wrap:wrap;align-items:center;gap:1rem}.ui-cluster--gap-sm{gap:.5rem}.ui-cluster--gap-lg{gap:2rem}.ui-cluster--justify-center{justify-content:center}hr.ui-divider{border:none;border-top:1px solid var(--ui-border,var(--border,#222228));margin:0}.ui-divider--label{display:flex;align-items:center;gap:.75rem}.ui-divider-line{flex:1;height:1px;background:var(--ui-border,var(--border,#222228));display:block}.ui-divider-text{font-size:.8rem;color:var(--ui-muted,var(--muted,#9090a0));white-space:nowrap;flex-shrink:0}.ui-banner{padding:.625rem 1.5rem;text-align:center;font-size:.875rem;font-weight:500;line-height:1.5}.ui-banner--info{background:var(--ui-accent-dim,rgba(155,141,255,.12));color:var(--ui-accent,var(--accent,#9b8dff))}.ui-banner--promo{background:var(--ui-accent,var(--accent,#9b8dff));color:#fff}.ui-banner--warning{background:rgba(255,193,7,.12);color:#ffc107}.ui-media{display:grid;grid-template-columns:1fr 1fr;gap:3rem;align-items:center}.ui-media-image{min-width:0}.ui-media-content{min-width:0}.ui-media-image img{width:100%;height:auto;border-radius:var(--ui-radius,8px);display:block}.ui-hero{padding:5rem 1.5rem;text-align:center}.ui-hero--sm{padding-top:2.5rem;padding-bottom:0}.ui-hero-inner{max-width:720px;margin:0 auto}.ui-hero--left .ui-hero-inner{margin:0}.ui-hero-eyebrow{font-size:.78rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--ui-accent,var(--accent,#9b8dff));margin:0 0 .75rem}.ui-hero-title{font-size:clamp(2.2rem,6vw,3.5rem);font-weight:800;line-height:1.1;letter-spacing:-.02em;color:var(--ui-text,var(--text,#e2e2ea));margin:0 0 1.25rem}.ui-hero-subtitle{font-size:1.125rem;color:var(--ui-muted,var(--muted,#9090a0));line-height:1.65;margin:0 0 2rem}.ui-hero-actions{display:flex;gap:.75rem;flex-wrap:wrap;justify-content:center}.ui-hero--left .ui-hero-actions{justify-content:flex-start}.ui-testimonial{background:var(--ui-surface,var(--surface,#111116));border:1px solid var(--ui-border,var(--border,#222228));border-radius:var(--ui-radius,8px);padding:1.5rem;margin:0}.ui-testimonial-rating{color:#f5a623;font-size:1rem;letter-spacing:.1em;margin:0 0 .75rem}.ui-testimonial-quote{margin:0 0 1.25rem}.ui-testimonial-quote p{font-size:.95rem;line-height:1.65;color:var(--ui-text,var(--text,#e2e2ea));font-style:italic;margin:0}.ui-testimonial-author{display:flex;align-items:center;gap:.75rem}.ui-testimonial-avatar{width:40px;height:40px;border-radius:50%;object-fit:cover;flex-shrink:0}.ui-testimonial-avatar--initials{background:var(--ui-accent-dim,rgba(155,141,255,.12));color:var(--ui-accent,var(--accent,#9b8dff));font-size:.8rem;font-weight:700;display:flex;align-items:center;justify-content:center}.ui-testimonial-meta{display:flex;flex-direction:column;gap:.1rem}.ui-testimonial-name{font-size:.875rem;font-weight:600;color:var(--ui-text,var(--text,#e2e2ea));margin:0}.ui-testimonial-role{font-size:.78rem;color:var(--ui-muted,var(--muted,#9090a0));margin:0}.ui-feature{display:flex;flex-direction:column;gap:.75rem}.ui-feature-icon{display:block;flex-shrink:0;line-height:1}.ui-feature-title{font-size:1rem;font-weight:600;color:var(--ui-text,var(--text,#e2e2ea));margin:0}.ui-feature-desc{font-size:.9rem;color:var(--ui-muted,var(--muted,#9090a0));line-height:1.6;margin:0}.ui-feature--center{align-items:center;text-align:center}.ui-pricing{box-sizing:border-box;background:var(--ui-surface,var(--surface,#111116));border:1px solid var(--ui-border,var(--border,#222228));border-radius:var(--ui-radius,8px);padding:2rem;display:flex;flex-direction:column;gap:1.25rem;position:relative;width:100%}.ui-pricing--highlighted{border-color:var(--ui-accent,var(--accent,#9b8dff));box-shadow:0 0 0 1px var(--ui-accent,var(--accent,#9b8dff))}.ui-pricing-badge{position:absolute;top:-12px;left:50%;transform:translateX(-50%);background:var(--ui-accent,var(--accent,#9b8dff));color:var(--ui-accent-text,#0d0a20);font-size:.72rem;font-weight:700;letter-spacing:.05em;text-transform:uppercase;padding:.25rem .75rem;border-radius:99px;margin:0;white-space:nowrap}.ui-pricing-header{display:flex;flex-direction:column;gap:.25rem}.ui-pricing-name{font-size:1.1rem;font-weight:700;color:var(--ui-text,var(--text,#e2e2ea));margin:0}.ui-pricing-desc{font-size:.875rem;color:var(--ui-muted,var(--muted,#9090a0));margin:0}.ui-pricing-price{display:flex;align-items:baseline;gap:.3rem}.ui-pricing-amount{font-size:2.25rem;font-weight:800;color:var(--ui-text,var(--text,#e2e2ea));line-height:1;letter-spacing:-.02em}.ui-pricing-period{font-size:.875rem;color:var(--ui-muted,var(--muted,#9090a0))}.ui-pricing-features{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:.6rem}.ui-pricing-feature{font-size:.875rem;color:var(--ui-text,var(--text,#e2e2ea));display:flex;align-items:flex-start;gap:.5rem;width:100%}.ui-pricing-check{color:var(--ui-green,var(--green,#3dd68c));font-weight:700;flex-shrink:0}.ui-pricing-grid{display:grid;gap:1.5rem;align-items:start;grid-template-columns:1fr;width:100%}@media (min-width:640px){.ui-pricing-grid--cols-3{grid-template-columns:repeat(3,1fr)}}.ui-pricing-action{margin-top:auto}.ui-accordion{display:flex;flex-direction:column;gap:.5rem;width:100%}.ui-accordion-item{background:var(--ui-surface,var(--surface,#111116));border:1px solid var(--ui-border,var(--border,#222228));border-radius:var(--ui-radius,8px);overflow:hidden}.ui-accordion-summary{display:flex;justify-content:space-between;align-items:center;padding:1rem 1.25rem;cursor:pointer;font-size:.95rem;font-weight:500;color:var(--ui-text,var(--text,#e2e2ea));list-style:none;user-select:none;gap:1rem}.ui-accordion-summary::-webkit-details-marker{display:none}.ui-accordion-summary:hover{color:var(--ui-accent,var(--accent,#9b8dff))}.ui-accordion-icon{width:16px;height:16px;position:relative;flex-shrink:0}.ui-accordion-icon::before,.ui-accordion-icon::after{content:'';position:absolute;background:currentColor;border-radius:2px;transition:transform .2s}.ui-accordion-icon::before{width:10px;height:1.5px;top:7px;left:3px}.ui-accordion-icon::after{width:1.5px;height:10px;top:3px;left:7px}.ui-accordion-item[open] .ui-accordion-icon::after{transform:rotate(90deg)}.ui-accordion-body{padding:0 1.25rem 1rem}.ui-accordion-body p{font-size:.9rem;color:var(--ui-muted,var(--muted,#9090a0));line-height:1.7;margin:0}.ui-tooltip{position:relative;display:inline-block}.ui-tooltip::after{content:attr(data-tip);position:absolute;bottom:calc(100%+8px);left:50%;transform:translateX(-50%) translateY(4px);background:var(--ui-surface-2,var(--surface-2,#1a1a26));color:var(--ui-text,var(--text,#e2e2ea));font-size:.78rem;font-weight:500;line-height:1.4;white-space:nowrap;padding:.35rem .7rem;border-radius:6px;border:1px solid var(--ui-border,var(--border,#2a2a38));pointer-events:none;opacity:0;transition:opacity .15s,transform .15s;z-index:var(--ui-z-tooltip,200)}.ui-tooltip::before{content:'';position:absolute;bottom:calc(100%+2px);left:50%;transform:translateX(-50%);border:5px solid transparent;border-top-color:var(--ui-border,var(--border,#2a2a38));pointer-events:none;opacity:0;transition:opacity .15s;z-index:var(--ui-z-tooltip,200)}.ui-tooltip:hover::after,.ui-tooltip:focus-within::after{opacity:1;transform:translateX(-50%) translateY(0)}.ui-tooltip:hover::before,.ui-tooltip:focus-within::before{opacity:1}.ui-tooltip--bottom::after{bottom:auto;top:calc(100%+8px);transform:translateX(-50%) translateY(-4px)}.ui-tooltip--bottom::before{bottom:auto;top:calc(100%+2px);border-top-color:transparent;border-bottom-color:var(--ui-border,var(--border,#2a2a38))}.ui-tooltip--bottom:hover::after,.ui-tooltip--bottom:focus-within::after{transform:translateX(-50%) translateY(0)}.ui-tooltip--left::after{bottom:auto;top:50%;left:auto;right:calc(100%+8px);transform:translateY(-50%) translateX(4px)}.ui-tooltip--left::before{bottom:auto;top:50%;left:auto;right:calc(100%+2px);transform:translateY(-50%);border-top-color:transparent;border-left-color:var(--ui-border,var(--border,#2a2a38))}.ui-tooltip--left:hover::after,.ui-tooltip--left:focus-within::after{transform:translateY(-50%) translateX(0)}.ui-tooltip--right::after{bottom:auto;top:50%;left:calc(100%+8px);transform:translateY(-50%) translateX(-4px)}.ui-tooltip--right::before{bottom:auto;top:50%;left:calc(100%+2px);transform:translateY(-50%);border-top-color:transparent;border-right-color:var(--ui-border,var(--border,#2a2a38))}.ui-tooltip--right:hover::after,.ui-tooltip--right:focus-within::after{transform:translateY(-50%) translateX(0)}.ui-modal{position:fixed;inset:0;margin:auto;width:min(560px,calc(100vw - 2rem));max-height:min(80vh,calc(100dvh - 2rem));padding:0;background:var(--ui-surface,var(--surface,#13131e));border:1px solid var(--ui-border,var(--border,#2a2a38));border-radius:var(--ui-radius,12px);overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,.5)}.ui-modal--sm{width:min(400px,calc(100vw - 2rem))}.ui-modal--lg{width:min(760px,calc(100vw - 2rem))}.ui-modal::backdrop{background:rgba(0,0,0,.6);backdrop-filter:blur(2px)}.ui-modal-inner{display:flex;flex-direction:column;max-height:inherit;margin:0;border:none;padding:0;background:transparent}.ui-modal-header{display:flex;align-items:center;gap:1rem;padding:1.25rem 1.5rem;border-bottom:1px solid var(--ui-border,var(--border,#2a2a38));flex-shrink:0}.ui-modal-title{flex:1;font-size:1.1rem;font-weight:700;color:var(--ui-text,var(--text,#e2e2ea));margin:0}.ui-modal-close{display:flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;background:transparent;border:none;border-radius:6px;color:var(--ui-muted,var(--muted,#9090a0));cursor:pointer;transition:color .15s,background .15s;flex-shrink:0}.ui-modal-close:hover{color:var(--ui-text,var(--text,#e2e2ea));background:var(--ui-surface-2,var(--surface-2,#1a1a26))}.ui-modal-body{flex:1;overflow-y:auto;padding:1.5rem;color:var(--ui-text,var(--text,#e2e2ea))}.ui-modal-footer{display:flex;align-items:center;justify-content:flex-end;gap:.75rem;padding:1rem 1.5rem;border-top:1px solid var(--ui-border,var(--border,#2a2a38));flex-shrink:0}.ui-carousel{position:relative;width:100%;border-radius:var(--ui-radius,12px);overflow:hidden}.ui-carousel-track{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;scroll-behavior:smooth;-webkit-overflow-scrolling:touch;scrollbar-width:none}.ui-carousel-track::-webkit-scrollbar{display:none}.ui-carousel-slide{flex:0 0 100%;scroll-snap-align:start}.ui-carousel-btn{position:absolute;top:50%;transform:translateY(-50%);z-index:2;display:flex;align-items:center;justify-content:center;width:40px;height:40px;padding:0;background:var(--ui-surface,var(--surface,#13131e));border:1px solid var(--ui-border,var(--border,#2a2a38));border-radius:50%;color:var(--ui-text,var(--text,#e2e2ea));cursor:pointer;transition:background .15s,opacity .15s}.ui-carousel-btn:hover{background:var(--ui-surface-2,var(--surface-2,#1a1a26))}.ui-carousel-btn[hidden]{display:none}.ui-carousel-prev{left:.75rem}.ui-carousel-next{right:.75rem}.ui-carousel-dots{display:flex;justify-content:center;gap:.5rem;padding:.75rem 0 .5rem}.ui-carousel-dot{width:8px;height:8px;padding:0;background:var(--ui-border,var(--border,#2a2a38));border:none;border-radius:50%;cursor:pointer;transition:background .15s,transform .15s}.ui-carousel-dot.active,.ui-carousel-dot:hover{background:var(--ui-accent,var(--accent,#9b8dff))}.ui-carousel-dot.active{transform:scale(1.35)}.ui-nav{position:relative;background:var(--ui-bg,var(--bg,#0d0d10));border-bottom:1px solid var(--ui-border,var(--border,#222228))}.ui-nav-inner{max-width:1100px;margin:0 auto;padding:.875rem 1.5rem;display:flex;align-items:center;gap:2rem}.ui-nav-logo{font-size:1rem;font-weight:700;color:var(--ui-text,var(--text,#e2e2ea));text-decoration:none;display:flex;align-items:center;gap:.5rem;flex-shrink:0}.ui-nav-logo:hover{color:var(--ui-accent,var(--accent,#9b8dff))}.ui-nav-links{display:flex;gap:1.5rem;align-items:center;flex:1}.ui-nav-link{font-size:.875rem;color:var(--ui-muted,var(--muted,#9090a0));text-decoration:none;transition:color .15s;min-height:44px;display:flex;align-items:center}.ui-nav-link:hover{color:var(--ui-text,var(--text,#e2e2ea))}.ui-nav-action{margin-left:auto}.ui-nav--burger-left .ui-nav-burger{order:-1;margin-left:0;margin-right:auto}.ui-nav-burger{display:none;margin-left:auto;padding:.35rem;background:none;border:none;color:var(--ui-text,var(--text,#e2e2ea));cursor:pointer;align-items:center;justify-content:center;border-radius:var(--ui-radius-sm,6px);transition:background .15s}.ui-nav-burger:hover{background:var(--ui-surface,var(--surface,#111116))}.ui-nav-burger-close{display:none}.ui-nav--open .ui-nav-burger-open{display:none}.ui-nav--open .ui-nav-burger-close{display:block}.ui-nav-mobile{display:none;position:absolute;top:100%;left:0;right:0;z-index:99;background:var(--ui-bg,var(--bg,#0d0d10));border-bottom:1px solid var(--ui-border,var(--border,#222228));padding:1rem 1.5rem;flex-direction:column;gap:.25rem}.ui-nav--open .ui-nav-mobile{display:flex}.ui-nav-mobile nav{display:flex;flex-direction:column}.ui-nav-mobile .ui-nav-link{padding:.6rem 0;border-bottom:1px solid var(--ui-border,var(--border,#222228))}.ui-nav-mobile .ui-nav-link:last-child{border-bottom:none}.ui-app-badge{display:inline-flex;align-items:center;gap:.625rem;background:var(--ui-text,var(--text,#e2e2ea));color:var(--ui-bg,var(--bg,#0d0d10));border-radius:10px;padding:.625rem 1.25rem;text-decoration:none;transition:opacity .15s;min-width:148px}.ui-app-badge:hover{opacity:.85}.ui-app-badge-text{display:flex;flex-direction:column;gap:.05rem}.ui-app-badge-line1{font-size:.65rem;font-weight:400;line-height:1}.ui-app-badge-line2{font-size:.95rem;font-weight:700;line-height:1}@media (max-width:640px){.ui-btn--lg{height:48px;padding:0 1.25rem;font-size:.95rem}.ui-stat-value{font-size:1.6rem}.ui-btn--full{font-size:.9rem}.ui-section{padding-top:3rem;padding-bottom:3rem}.ui-grid--cols-2,.ui-grid--cols-3,.ui-grid--cols-4{grid-template-columns:1fr}.ui-media{grid-template-columns:1fr}.ui-hero{padding:3rem 1.25rem}.ui-hero-title{font-size:2rem}.ui-hero-subtitle{font-size:1rem}.ui-nav-links{display:none}.ui-nav-burger{display:flex}.ui-nav-inner{gap:.75rem}}.ui-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.u-mt-2{margin-top:.5rem}.u-mt-3{margin-top:.75rem}.u-mb-0{margin-bottom:0}.u-mb-1{margin-bottom:.25rem}.u-mb-2{margin-bottom:.5rem}.u-mb-3{margin-bottom:.75rem}.u-mb-4{margin-bottom:1rem}.u-ml-auto{margin-left:auto}.u-p-2{padding:.5rem}.u-p-4{padding:1rem}.u-p-5{padding:1.25rem}.u-text-sm{font-size:.875rem;line-height:1.5}.u-text-base{font-size:1rem;line-height:1.6}.u-text-lg{font-size:1.125rem;line-height:1.5}.u-text-xl{font-size:1.25rem;line-height:1.4}.u-text-2xl{font-size:1.5rem;line-height:1.3}.u-text-3xl{font-size:1.875rem;line-height:1.2}.u-text-4xl{font-size:2.25rem;line-height:1.15}.u-font-semibold{font-weight:600}.u-font-bold{font-weight:700}.u-text-center{text-align:center}.u-text-balance{text-wrap:balance}.u-text-muted{color:var(--ui-muted)}.u-text-accent{color:var(--ui-accent)}.u-text-green{color:var(--ui-green)}.u-text-red{color:var(--ui-red)}.u-text-yellow{color:var(--ui-yellow)}.u-text-blue{color:var(--ui-blue)}.u-leading-tight{line-height:1.25}.u-max-w-prose{max-width:65ch}.u-flex{display:flex}.u-flex-col{display:flex;flex-direction:column}.u-items-center{align-items:center}.u-gap-1{gap:.25rem}.u-gap-2{gap:.5rem}.u-gap-3{gap:.75rem}.u-gap-4{gap:1rem}.u-gap-8{gap:2rem}.u-rounded-md{border-radius:var(--ui-radius)}.u-rounded-full{border-radius:9999px}.u-overflow-hidden{overflow:hidden}.ui-section-header{width:100%;max-width:1100px;margin:0 auto 2.5rem;padding-left:1.5rem;padding-right:1.5rem}.ui-section-header--center{text-align:center}.ui-section-eyebrow{display:block;font-size:.75rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--ui-accent);margin:0 0 2rem}.ui-section-title{font-size:2rem;font-weight:700;line-height:1.15;color:var(--ui-text);margin:0 0 1.25rem}.ui-section-subtitle{font-size:1rem;color:var(--ui-muted);line-height:1.65;margin:0;max-width:58ch}.ui-section-header--center .ui-section-subtitle{margin-left:auto;margin-right:auto}@media (max-width:640px){.ui-section-title{font-size:1.5rem}.ui-section-header{margin-bottom:2rem}}.ui-cta{text-align:center}.ui-cta-eyebrow{display:block;font-size:.75rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--ui-accent);margin:0 0 .75rem}.ui-cta-title{font-size:2.5rem;font-weight:700;line-height:1.1;color:var(--ui-text);margin:0 0 1rem}.ui-cta-subtitle{font-size:1.05rem;color:var(--ui-muted);line-height:1.65;max-width:56ch;margin:0 auto 2rem}.ui-cta--left .ui-cta-subtitle{margin-left:0}.ui-cta-actions{display:flex;gap:1rem;flex-wrap:wrap;justify-content:center}.ui-cta--left .ui-cta-actions{justify-content:flex-start}@media (max-width:640px){.ui-cta-title{font-size:1.75rem}.ui-cta-actions{flex-direction:column;align-items:stretch}}.ui-code-window{background:var(--ui-surface-2);border:1px solid var(--ui-border);border-radius:var(--ui-radius);overflow:hidden}.ui-code-window-chrome{display:flex;align-items:center;gap:.4rem;padding:.6rem 1rem;background:var(--ui-surface);border-bottom:1px solid var(--ui-border)}.ui-code-window-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}.ui-code-window-dot:nth-child(1){background:#ff5f57}.ui-code-window-dot:nth-child(2){background:#febc2e}.ui-code-window-dot:nth-child(3){background:#28c840}.ui-code-window-filename{font-size:.75rem;font-family:var(--ui-mono);color:var(--ui-muted);margin-left:.35rem}.ui-code-window-lang{font-size:.7rem;font-family:var(--ui-mono);color:var(--ui-muted);margin-left:auto}.ui-code-window-pre{margin:0;padding:1.25rem 1.5rem;overflow-x:auto;font-family:var(--ui-mono);font-size:.85rem;line-height:1.7;color:var(--ui-text);tab-size:2}.ui-code-window-code{font-family:inherit}.ui-footer{padding:3rem 1.5rem;border-top:1px solid var(--ui-border)}.ui-footer-inner{max-width:1200px;margin:0 auto;display:flex;align-items:center;gap:2rem;flex-wrap:wrap}.ui-footer-logo{font-size:1rem;font-weight:600;color:var(--ui-text);text-decoration:none;flex-shrink:0;transition:color .15s}.ui-footer-logo:hover{color:var(--ui-accent)}.ui-footer-logo:focus-visible{outline:2px solid var(--ui-accent);outline-offset:2px;border-radius:2px}.ui-footer-links{display:flex;gap:1.5rem;flex-wrap:wrap;flex:1}.ui-footer-link{font-size:.875rem;color:var(--ui-muted);text-decoration:none;transition:color .15s}.ui-footer-link:hover{color:var(--ui-text)}.ui-footer-link:focus-visible{outline:2px solid var(--ui-accent);outline-offset:2px;border-radius:2px}.ui-footer-legal{font-size:.8rem;color:var(--ui-muted);margin:0 0 0 auto}@media (max-width:640px){.ui-footer-inner{flex-direction:column;align-items:flex-start;gap:1.25rem}.ui-footer-legal{margin-left:0}.ui-footer-links{gap:1rem}}.ui-icon-wrap{display:inline-flex;align-items:center;justify-content:center;padding:.55em;flex-shrink:0;line-height:1}.ui-icon-wrap--circle{border-radius:50%}.ui-icon-wrap--square{border-radius:var(--ui-radius)}.ui-icon-wrap--accent{background:var(--ui-accent-dim);color:var(--ui-accent)}.ui-icon-wrap--success{background:var(--ui-green-dim);color:var(--ui-green)}.ui-icon-wrap--warning{background:var(--ui-yellow-dim);color:var(--ui-yellow)}.ui-icon-wrap--error{background:var(--ui-red-dim);color:var(--ui-red)}.ui-icon-wrap--muted{background:var(--ui-surface-2);color:var(--ui-muted)}.ui-timeline{list-style:none;padding:0;margin:0}.ui-timeline-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;position:relative;z-index:1}.ui-timeline-dot--icon{width:2rem;height:2rem;border-radius:50%;font-size:1rem}.ui-timeline-dot--accent{background:var(--ui-accent)}.ui-timeline-dot--success{background:var(--ui-green)}.ui-timeline-dot--warning{background:var(--ui-yellow)}.ui-timeline-dot--error{background:var(--ui-red)}.ui-timeline-dot--muted{background:var(--ui-surface-2);outline:2px solid var(--ui-border);outline-offset:0}.ui-timeline-dot--icon.ui-timeline-dot--accent{background:var(--ui-accent-dim);color:var(--ui-accent)}.ui-timeline-dot--icon.ui-timeline-dot--success{background:var(--ui-green-dim);color:var(--ui-green)}.ui-timeline-dot--icon.ui-timeline-dot--warning{background:var(--ui-yellow-dim);color:var(--ui-yellow)}.ui-timeline-dot--icon.ui-timeline-dot--error{background:var(--ui-red-dim);color:var(--ui-red)}.ui-timeline-dot--icon.ui-timeline-dot--muted{background:var(--ui-surface-2);color:var(--ui-muted)}.ui-timeline-label{display:block;font-size:.72rem;font-weight:600;color:var(--ui-muted);letter-spacing:.05em;text-transform:uppercase;margin-bottom:.35rem}.ui-timeline--vertical{display:flex;flex-direction:column}.ui-timeline--vertical .ui-timeline-item{display:flex;gap:1rem}.ui-timeline--vertical .ui-timeline-side{display:flex;flex-direction:column;align-items:center;flex-shrink:0}.ui-timeline--vertical .ui-timeline-connector{width:2px;background:var(--ui-border);flex:1;min-height:.75rem}.ui-timeline--vertical .ui-timeline-item:first-child .ui-timeline-connector--before,.ui-timeline--vertical .ui-timeline-item:last-child .ui-timeline-connector--after{visibility:hidden}.ui-timeline--vertical .ui-timeline-main{flex:1;padding-bottom:1.75rem}.ui-timeline--vertical .ui-timeline-item:last-child .ui-timeline-main{padding-bottom:0}.ui-timeline--horizontal{display:flex;align-items:flex-start;overflow-x:auto}.ui-timeline--horizontal .ui-timeline-item{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center}.ui-timeline--horizontal .ui-timeline-side{display:flex;align-items:center;width:100%;margin-bottom:.75rem}.ui-timeline--horizontal .ui-timeline-connector{flex:1;height:2px;background:var(--ui-border)}.ui-timeline--horizontal .ui-timeline-item:first-child .ui-timeline-connector--before,.ui-timeline--horizontal .ui-timeline-item:last-child .ui-timeline-connector--after{visibility:hidden}.ui-timeline--horizontal .ui-timeline-main{text-align:center;padding:0 .5rem;width:100%}.ui-switch-input{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.ui-switch-label{display:inline-flex;align-items:center;gap:.65rem;cursor:pointer;user-select:none}.ui-switch-label--disabled{opacity:.5;cursor:not-allowed}.ui-switch-track{position:relative;display:inline-flex;align-items:center;width:3rem;height:1.75rem;border-radius:999px;background:var(--ui-border);transition:background .2s;flex-shrink:0}.ui-switch-thumb{position:absolute;left:.2rem;width:1.35rem;height:1.35rem;border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.3);transition:transform .2s cubic-bezier(.34,1.56,.64,1),background .2s}.ui-switch-input:checked+.ui-switch-track{background:var(--ui-accent)}.ui-switch-input:checked+.ui-switch-track .ui-switch-thumb{transform:translateX(1.25rem)}.ui-switch-input:focus-visible+.ui-switch-track{outline:2px solid var(--ui-accent);outline-offset:2px}.ui-switch-text{font-family:var(--ui-font);font-size:.9rem;color:var(--ui-text);line-height:1.4}.ui-radio-group{border:1px solid var(--ui-border);border-radius:var(--ui-radius);padding:0;margin:0}.ui-radio-group-body{display:flex;flex-direction:column;gap:var(--radio-gap,.75rem);padding:1.25rem}.ui-checkbox{display:inline-flex;align-items:flex-start;gap:.6rem;cursor:pointer;user-select:none}.ui-checkbox--disabled{opacity:.5;cursor:not-allowed}.ui-checkbox-input{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.ui-checkbox-box{position:relative;display:inline-flex;align-items:center;justify-content:center;width:1.125rem;height:1.125rem;border-radius:.25rem;border:2px solid var(--ui-border);background:var(--ui-surface);flex-shrink:0;margin-top:.125rem;transition:border-color .15s,background .15s}.ui-checkbox-box::after{content:'';position:absolute;width:.3rem;height:.55rem;border-right:2px solid var(--ui-bg);border-bottom:2px solid var(--ui-bg);transform:rotate(45deg) scale(0);transform-origin:center;margin-top:-.1rem;opacity:0;transition:opacity .1s,transform .15s cubic-bezier(.34,1.56,.64,1)}.ui-checkbox-input:checked+.ui-checkbox-box{border-color:var(--ui-accent);background:var(--ui-accent)}.ui-checkbox-input:checked+.ui-checkbox-box::after{opacity:1;transform:rotate(45deg) scale(1)}.ui-checkbox-input:focus-visible+.ui-checkbox-box{outline:2px solid var(--ui-accent);outline-offset:2px}.ui-checkbox--error .ui-checkbox-box{border-color:var(--ui-red)}.ui-checkbox-label{font-family:var(--ui-font);font-size:.9rem;color:var(--ui-text);line-height:1.4}.ui-radio{display:inline-flex;align-items:center;gap:.6rem;cursor:pointer;user-select:none}.ui-radio--disabled{opacity:.5;cursor:not-allowed}.ui-radio-input{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.ui-radio-dot{position:relative;display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;border-radius:50%;border:2px solid var(--ui-border);background:var(--ui-surface);flex-shrink:0;transition:border-color .15s,background .15s}.ui-radio-dot::after{content:'';width:.5rem;height:.5rem;border-radius:50%;background:var(--ui-bg);opacity:0;transform:scale(0);transition:opacity .15s,transform .2s cubic-bezier(.34,1.56,.64,1)}.ui-radio-input:checked+.ui-radio-dot{border-color:var(--ui-accent);background:var(--ui-accent)}.ui-radio-input:checked+.ui-radio-dot::after{opacity:1;transform:scale(1)}.ui-radio-input:focus-visible+.ui-radio-dot{outline:2px solid var(--ui-accent);outline-offset:2px}.ui-radio-label{font-family:var(--ui-font);font-size:.9rem;color:var(--ui-text);line-height:1.4}.ui-radio-group--error .ui-radio-dot{border-color:var(--ui-red)}.ui-rating{display:inline-flex;align-items:center;gap:.1em;font-size:var(--rating-size,1.5rem);color:var(--ui-muted);line-height:1}.ui-rating-star--filled,.ui-rating-star--half{color:var(--ui-yellow)}.ui-rating-stars{display:inline-flex;flex-direction:row-reverse;gap:.1em;font-size:var(--rating-size,1.5rem);line-height:1}.ui-rating-input{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.ui-rating-stars .ui-rating-star{color:var(--ui-muted);cursor:pointer;transition:color .1s;user-select:none}.ui-rating-stars .ui-rating-star:has(input:checked),.ui-rating-stars .ui-rating-star:has(input:checked)~.ui-rating-star{color:var(--ui-yellow)}.ui-rating-stars:hover .ui-rating-star{color:var(--ui-muted)}.ui-rating-stars .ui-rating-star:hover,.ui-rating-stars .ui-rating-star:hover~.ui-rating-star{color:var(--ui-yellow)}.ui-rating-star:focus-within{outline:2px solid var(--ui-accent);outline-offset:2px;border-radius:2px}fieldset[disabled] .ui-rating-star{cursor:not-allowed;opacity:.5}fieldset[disabled] .ui-rating-stars:hover .ui-rating-star{color:revert}fieldset[disabled] .ui-rating-stars .ui-rating-star:hover,fieldset[disabled] .ui-rating-stars .ui-rating-star:hover~.ui-rating-star{color:revert}@keyframes ui-spin{to{transform:rotate(360deg)}}.ui-spinner{display:inline-block;width:var(--spinner-size,1.5rem);height:var(--spinner-size,1.5rem);border:2px solid color-mix(in srgb,var(--spinner-color,var(--ui-accent)) 20%,transparent);border-top-color:var(--spinner-color,var(--ui-accent));border-radius:50%;animation:ui-spin .7s linear infinite;flex-shrink:0}.ui-progress{width:100%}.ui-progress-header{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:.4rem;gap:1rem}.ui-progress-label{font-size:.8rem;font-weight:600;color:var(--ui-text)}.ui-progress-value{font-size:.8rem;color:var(--ui-muted);font-variant-numeric:tabular-nums}.ui-progress-track{width:100%;height:var(--progress-height,.5rem);background:var(--ui-border);border-radius:999px;overflow:hidden}.ui-progress-fill{height:100%;border-radius:999px;transition:width .3s ease}.ui-progress--accent .ui-progress-fill{background:var(--ui-accent)}.ui-progress--success .ui-progress-fill{background:var(--ui-green)}.ui-progress--warning .ui-progress-fill{background:var(--ui-yellow)}.ui-progress--error .ui-progress-fill{background:var(--ui-red)}@keyframes ui-progress-indeterminate{0%{transform:translateX(-100%) scaleX(.4)}50%{transform:translateX( 60%) scaleX(.6)}100%{transform:translateX( 200%) scaleX(.4)}}.ui-progress--indeterminate .ui-progress-fill{width:50%;animation:ui-progress-indeterminate 1.4s ease infinite;transform-origin:left center}.ui-slider{width:100%;height:var(--ui-touch-min);-webkit-appearance:none;appearance:none;background:transparent;cursor:pointer;padding:0;margin:0}.ui-slider::-webkit-slider-runnable-track{height:4px;border-radius:999px;background:linear-gradient( to right,var(--ui-accent) 0% var(--slider-fill,50%),var(--ui-border) var(--slider-fill,50%) 100% )}.ui-slider::-moz-range-track{height:4px;border-radius:999px;background:var(--ui-border)}.ui-slider::-moz-range-progress{height:4px;border-radius:999px;background:var(--ui-accent)}.ui-slider::-webkit-slider-thumb{-webkit-appearance:none;width:20px;height:20px;border-radius:50%;background:var(--ui-accent);margin-top:-8px;box-shadow:0 1px 4px rgba(0,0,0,.3);transition:transform .15s}.ui-slider::-moz-range-thumb{width:20px;height:20px;border-radius:50%;background:var(--ui-accent);border:none;box-shadow:0 1px 4px rgba(0,0,0,.3)}.ui-slider::-webkit-slider-thumb:hover{transform:scale(1.15)}.ui-slider:focus-visible{outline:none}.ui-slider:focus-visible::-webkit-slider-thumb{outline:2px solid var(--ui-accent);outline-offset:3px}.ui-slider:disabled{opacity:.5;cursor:not-allowed}.ui-label--row{display:flex;justify-content:space-between;align-items:baseline}.ui-slider-output{font-size:.8125rem;color:var(--ui-muted);font-variant-numeric:tabular-nums;min-width:3ch;text-align:right}.ui-upload{position:relative;display:flex;align-items:center;justify-content:center;padding:2rem 1.5rem;background:var(--ui-surface);border:1.5px dashed var(--ui-border);border-radius:var(--ui-radius);cursor:pointer;transition:border-color .15s,background .15s;outline:none;user-select:none}.ui-upload:hover:not(.ui-upload--disabled),.ui-upload:focus-visible{border-color:var(--ui-accent);background:color-mix(in srgb,var(--ui-accent) 5%,var(--ui-surface))}.ui-upload--error{border-color:var(--ui-red)}.ui-upload--selected .ui-upload-text{color:var(--ui-text);font-weight:500}.ui-upload--disabled{opacity:.5;cursor:not-allowed}.ui-upload-body{display:flex;flex-direction:column;align-items:center;gap:.6rem;pointer-events:none}.ui-upload-icon{color:var(--ui-muted);display:block}.ui-upload:hover:not(.ui-upload--disabled) .ui-upload-icon,.ui-upload--active .ui-upload-icon{color:var(--ui-accent)}.ui-upload-text{font-size:.875rem;color:var(--ui-muted);text-align:center;line-height:1.5}.ui-upload-browse{color:var(--ui-accent);text-decoration:underline;text-underline-offset:2px}.ui-upload-input{display:none}.ui-segmented{display:inline-flex;background:var(--ui-surface-2);border-radius:var(--ui-radius);padding:3px;gap:2px}.ui-segmented-input{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.ui-segmented-label{display:inline-flex;align-items:center;padding:.4rem .9rem;border-radius:calc(var(--ui-radius) - 2px);font-size:.85rem;font-weight:500;color:var(--ui-muted);cursor:pointer;user-select:none;white-space:nowrap;transition:background .15s,color .15s,box-shadow .15s}.ui-segmented--sm .ui-segmented-label{font-size:.75rem;padding:.25rem .65rem}.ui-segmented--lg .ui-segmented-label{font-size:.95rem;padding:.55rem 1.1rem}.ui-segmented-input:checked+.ui-segmented-label{background:var(--ui-surface);color:var(--ui-text);box-shadow:0 1px 3px rgba(0,0,0,.25)}.ui-segmented-input:focus-visible+.ui-segmented-label{outline:2px solid var(--ui-accent);outline-offset:1px}.ui-breadcrumbs-list{display:flex;flex-wrap:wrap;align-items:center;gap:.25rem;list-style:none;padding:0;margin:0;font-size:.85rem}.ui-breadcrumbs-item{display:flex;align-items:center;gap:.25rem}.ui-breadcrumbs-link{color:var(--ui-muted);text-decoration:none;transition:color .15s}.ui-breadcrumbs-link:hover{color:var(--ui-text)}.ui-breadcrumbs-current{color:var(--ui-text)}.ui-breadcrumbs-sep{color:var(--ui-border);user-select:none}.ui-stepper{display:flex;align-items:flex-start;width:100%}.ui-stepper-item{flex:1;display:flex;flex-direction:column;align-items:center;position:relative}.ui-stepper-item:not(:last-child)::after{content:'';position:absolute;top:13px;left:50%;width:100%;height:2px;background:var(--ui-border)}.ui-stepper-item--complete:not(:last-child)::after{background:var(--ui-accent)}.ui-stepper-dot{position:relative;z-index:1;width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:.75rem;font-weight:700;background:var(--ui-surface-2);border:2px solid var(--ui-border);color:var(--ui-muted);transition:background .2s,border-color .2s,color .2s}.ui-stepper-item--complete .ui-stepper-dot{background:var(--ui-accent);border-color:var(--ui-accent);color:var(--ui-bg)}.ui-stepper-item--active .ui-stepper-dot{border-color:var(--ui-accent);color:var(--ui-accent)}.ui-stepper-label{margin-top:.5rem;font-size:.72rem;color:var(--ui-muted);text-align:center;line-height:1.3;padding:0 .25rem}.ui-stepper-item--active .ui-stepper-label{color:var(--ui-text);font-weight:600}.ui-stepper-item--complete .ui-stepper-label{color:var(--ui-muted)}.ui-image{display:block}.ui-image-crop{overflow:hidden;border-radius:var(--ui-radius)}.ui-image-img--cover{display:block;width:100%;height:auto;object-fit:cover}.ui-image--rounded .ui-image-crop{border-radius:var(--ui-radius-lg,1rem)}.ui-image--rounded .ui-image-wrap{border-radius:var(--ui-radius-lg,1rem)}.ui-image--pill .ui-image-crop{border-radius:999px}.ui-image-caption{margin:.5rem 0 0;font-size:.8rem;color:var(--ui-muted);line-height:1.5}.ui-pullquote{border-left:3px solid var(--ui-accent);padding:.75rem 0 .75rem 1.5rem;margin:0}.ui-pullquote blockquote{margin:0;padding:0}.ui-pullquote-text{font-size:1.15rem;line-height:1.6;color:var(--ui-text);font-style:italic;margin:0}.ui-pullquote--lg .ui-pullquote-text{font-size:1.5rem}.ui-pullquote-cite{display:block;margin-top:.75rem;font-size:.85rem;color:var(--ui-muted);font-style:normal}.ui-list{display:flex;flex-direction:column;padding-left:1.5em;color:var(--ui-text)}.ui-list--unordered{list-style-type:disc}.ui-list--ordered{list-style-type:decimal}.ui-list-item{line-height:1.6}.ui-prose{color:var(--ui-text);line-height:1.75;font-size:1rem}.ui-prose--sm{font-size:.875rem}.ui-prose--lg{font-size:1.125rem}.ui-prose h1,.ui-prose h2,.ui-prose h3,.ui-prose h4,.ui-prose h5,.ui-prose h6{color:var(--ui-text);font-weight:700;line-height:1.3;margin-top:2em;margin-bottom:.6em}.ui-prose h1{font-size:2rem}.ui-prose h2{font-size:1.5rem}.ui-prose h3{font-size:1.25rem;font-weight:600}.ui-prose h4{font-size:1rem;font-weight:600}.ui-prose h5{font-size:.875rem;font-weight:600}.ui-prose h6{font-size:.875rem;font-weight:600;color:var(--ui-muted)}.ui-prose:is(h1,h2,h3,h4,h5,h6):first-child{margin-top:0}.ui-prose p{margin-bottom:1.25em}.ui-prose p:last-child{margin-bottom:0}.ui-prose a{color:var(--ui-accent);text-decoration:underline;text-underline-offset:2px}.ui-prose a:hover{color:var(--ui-accent-hover)}.ui-prose strong{font-weight:700}.ui-prose em{font-style:italic}.ui-prose ul,.ui-prose ol{padding-left:1.5em;margin-bottom:1.25em}.ui-prose ul{list-style-type:disc}.ui-prose ol{list-style-type:decimal}.ui-prose ul ul{list-style-type:circle}.ui-prose li{margin-bottom:.375em}.ui-prose li>ul,.ui-prose li>ol{margin-top:.375em;margin-bottom:.375em}.ui-prose blockquote{border-left:3px solid var(--ui-border);padding:.5em 0 .5em 1.25em;margin:1.5em 0;color:var(--ui-muted);font-style:italic}.ui-prose blockquote p{margin-bottom:0}.ui-prose code{background:var(--ui-surface-2);color:var(--ui-accent);padding:.1em .35em;border-radius:var(--ui-radius-sm);font-size:.875em;font-family:var(--ui-mono)}.ui-prose pre{background:var(--ui-surface-2);border:1px solid var(--ui-border);border-radius:var(--ui-radius);padding:1em 1.25em;overflow-x:auto;margin-bottom:1.25em;font-family:var(--ui-mono);font-size:.875em;line-height:1.7}.ui-prose pre code{background:none;color:inherit;padding:0;font-size:inherit}.ui-prose hr{border:none;border-top:1px solid var(--ui-border);margin:2em 0}.ui-prose img{max-width:100%;border-radius:var(--ui-radius);height:auto}.ui-prose figure{margin:1.5em 0}.ui-prose figcaption{font-size:.875em;color:var(--ui-muted);margin-top:.5em;text-align:center}.ui-prose table{width:100%;border-collapse:collapse;margin-bottom:1.25em;font-size:.9375em}.ui-prose th{text-align:left;padding:.5em .75em;border-bottom:2px solid var(--ui-border);font-weight:600;color:var(--ui-text)}.ui-prose td{padding:.5em .75em;border-bottom:1px solid var(--ui-border);color:var(--ui-text)}[data-theme="light"],.ui-theme-light{--ui-bg:#ffffff;--ui-surface:#f8f8fc;--ui-surface-2:#f0f0f8;--ui-border:#d8d8e8;--ui-text:#111118;--ui-muted:#60607a;--ui-accent:#6b5ce7;--ui-accent-hover:#5847d6;--ui-accent-dim:rgba(107,92,231,.12);--ui-green:#166534;--ui-green-dim:rgba(22,163,74,.12);--ui-red:#991b1b;--ui-red-dim:rgba(220,38,38,.12);--ui-yellow:#92400e;--ui-yellow-dim:rgba(217,119,6,.12);--ui-blue:#1e40af;--ui-blue-dim:rgba( 30,64,175,.12);--ui-nav-sticky-bg:rgba(255,255,255,.85)}
@@ -1,104 +0,0 @@
1
- import{a as s}from"./runtime-QFURDKA2.js";import{a as p,b as c,c as i,d,e,g as t,i as r}from"./runtime-L2HNXIHW.js";import{a,b as l}from"./runtime-B73WLANC.js";var{prev:u,next:m}=p("/raw-responses"),n={route:"/raw-responses",meta:{title:"Raw Responses \u2014 Pulse Docs",description:"Return non-HTML responses from Pulse specs \u2014 RSS, XML, JSON, and other content types.",styles:["/docs.css"]},state:{},view:()=>c({currentHref:"/raw-responses",prev:u,next:m,content:`
2
- ${i("Raw Responses")}
3
- ${d("Setting <code>contentType</code> switches a spec from the HTML pipeline to a raw response mode. The view returns the response body directly \u2014 no doctype, no hydration script. Security headers and compression still apply.")}
4
-
5
- ${e("basics","The basics")}
6
- <p>Set <code>contentType</code> to any valid MIME type. When present, the normal HTML wrapper (doctype, head, body, hydration script) is skipped. The <code>render</code> function receives <code>(ctx, serverState)</code> and returns a string:</p>
7
- ${t(s(`export default {
8
- route: '/feed.xml',
9
- contentType: 'application/rss+xml',
10
- state: {},
11
- server: {
12
- data: async () => ({
13
- posts: await db.posts.latest(20),
14
- }),
15
- },
16
- view: (ctx, server) => \`<?xml version="1.0" encoding="UTF-8"?>
17
- <rss version="2.0">
18
- <channel>
19
- <title>My Blog</title>
20
- <link>https://example.com</link>
21
- <description>Recent posts</description>
22
- \${server.posts.map(post => \`
23
- <item>
24
- <title>\${esc(post.title)}</title>
25
- <link>https://example.com/blog/\${post.slug}</link>
26
- <pubDate>\${new Date(post.date).toUTCString()}</pubDate>
27
- <description>\${esc(post.excerpt)}</description>
28
- </item>
29
- \`).join('')}
30
- </channel>
31
- </rss>\`,
32
- }`,"js"))}
33
-
34
- ${e("json","JSON API endpoints")}
35
- ${t(s(`export default {
36
- route: '/api/products',
37
- contentType: 'application/json',
38
- state: {},
39
- server: {
40
- data: async (ctx) => ({
41
- products: await db.products.list({
42
- page: parseInt(ctx.query.page ?? '1', 10),
43
- category: ctx.query.category,
44
- }),
45
- }),
46
- },
47
- view: (ctx, server) => JSON.stringify({
48
- products: server.products,
49
- page: parseInt(ctx.query.page ?? '1', 10),
50
- }),
51
- }`,"js"))}
52
-
53
- ${e("sitemap","XML sitemap")}
54
- ${t(s(`export default {
55
- route: '/sitemap.xml',
56
- contentType: 'application/xml',
57
- serverTtl: 3600,
58
- state: {},
59
- server: {
60
- data: async () => ({
61
- pages: await db.pages.allPublished(),
62
- }),
63
- },
64
- view: (ctx, server) => \`<?xml version="1.0" encoding="UTF-8"?>
65
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
66
- \${server.pages.map(p => \`
67
- <url>
68
- <loc>https://example.com\${p.path}</loc>
69
- <lastmod>\${p.updatedAt.toISOString().slice(0, 10)}</lastmod>
70
- </url>
71
- \`).join('')}
72
- </urlset>\`,
73
- }`,"js"))}
74
-
75
- ${e("view-signature","View function signature")}
76
- <p>For raw responses, the <code>view</code> function signature is <code>(ctx, serverState)</code>, not <code>(state, serverState)</code>. The <code>ctx</code> argument is the request context object with <code>params</code>, <code>query</code>, <code>headers</code>, and <code>cookies</code>.</p>
77
- ${r("note","There is no client state (<code>state</code>) for raw response specs \u2014 they are purely server-side. The <code>state: {}</code> field is still required for spec validation, but is not used.")}
78
-
79
- ${e("escaping","Escaping in XML and HTML")}
80
- <p>Pulse does not auto-escape raw response bodies. When returning XML or HTML, escaping all user-supplied and dynamic content is required \u2014 unescaped output is an injection vulnerability:</p>
81
- ${t(s(`// Simple escape helper for XML/HTML contexts
82
- function esc(str) {
83
- return String(str)
84
- .replace(/&/g, '&amp;')
85
- .replace(/</g, '&lt;')
86
- .replace(/>/g, '&gt;')
87
- .replace(/"/g, '&quot;')
88
- .replace(/'/g, '&#39;')
89
- }`,"js"))}
90
- ${r("warning","Never interpolate raw user data or database content into XML/HTML without escaping. This applies to raw response specs just as much as to HTML view functions.")}
91
-
92
- ${e("caching-raw","Caching raw responses")}
93
- <p>Raw response specs support the same <code>serverTtl</code> and <code>cache</code> options as HTML specs. For feeds and sitemaps that update infrequently, a generous TTL dramatically reduces server load:</p>
94
- ${t(s(`export default {
95
- route: '/feed.xml',
96
- contentType: 'application/rss+xml',
97
- serverTtl: 300, // cache data for 5 minutes
98
- cache: {
99
- public: true,
100
- maxAge: 300,
101
- },
102
- // ...
103
- }`,"js"))}
104
- `})};var o=document.getElementById("pulse-root");o&&!o.dataset.pulseMounted&&(o.dataset.pulseMounted="1",a(n,o,window.__PULSE_SERVER__||{},{ssr:!0}),l(o,a));var M=n;export{M as default};
@@ -1,70 +0,0 @@
1
- import{a as o}from"./runtime-QFURDKA2.js";import{a as c,b as i,c as n,d,e,g as t,h as p,i as u}from"./runtime-L2HNXIHW.js";import{a as s,b as m}from"./runtime-B73WLANC.js";var{prev:l,next:g}=c("/routing"),a={route:"/routing",meta:{title:"Routing \u2014 Pulse Docs",description:"How Pulse routes requests to page specs \u2014 static and dynamic routes, params, and conventions.",styles:["/docs.css"]},state:{},view:()=>i({currentHref:"/routing",prev:l,next:g,content:`
2
- ${n("Routing")}
3
- ${d("Every route in Pulse is explicitly declared in the spec. There is no file-based routing, no magic directory conventions, and no implicit mapping. Every page's URL is visible in its spec file and nowhere else.")}
4
-
5
- ${e("route-field","The route field")}
6
- <p>Every spec has a <code>route</code> field. This is the URL pattern the spec handles:</p>
7
- ${t(o(`export default {
8
- route: '/about',
9
- state: {},
10
- view: () => \`<h1>About</h1>\`,
11
- }`,"js"))}
12
- <p>Pulse matches the exact path. Trailing slashes are normalised \u2014 <code>/about</code> and <code>/about/</code> are treated the same.</p>
13
-
14
- ${e("dynamic","Dynamic segments")}
15
- <p>Use a colon prefix for dynamic path segments. Named segments are captured and available in <code>ctx.params</code> in <a href="/server-data">server data</a>:</p>
16
- ${t(o(`export default {
17
- route: '/products/:id',
18
- state: { quantity: 1 },
19
- server: {
20
- data: async (ctx) => {
21
- // ctx.params.id is the captured segment
22
- const product = await db.products.find(ctx.params.id)
23
- return { product }
24
- },
25
- },
26
- view: (state, server) => \`<h1>\${server.product.name}</h1>\`,
27
- }`,"js"))}
28
-
29
- ${e("multi-segment","Multiple dynamic segments")}
30
- <p>Any number of dynamic segments can appear in a route:</p>
31
- ${t(o(`route: '/blog/:year/:month/:slug'
32
- // Matches: /blog/2025/03/my-first-post
33
- // ctx.params = { year: '2025', month: '03', slug: 'my-first-post' }`,"js"))}
34
-
35
- ${e("registering","Registering routes")}
36
- <p>Specs are registered explicitly by passing them to <code>createServer</code> as an array. Routes are matched in order \u2014 more specific routes must come before more general ones:</p>
37
- ${t(o(`import { createServer } from '@invisibleloop/pulse'
38
- import home from './src/pages/home.js'
39
- import products from './src/pages/products.js'
40
- import product from './src/pages/product.js' // more specific \u2014 comes first
41
- import blog from './src/pages/blog.js'
42
-
43
- createServer([home, product, products, blog], { port: 3000 })`,"js"))}
44
-
45
- ${e("query","Query strings")}
46
- <p>Query string parameters are not part of the route pattern but are accessible via <code>ctx.query</code> in server data:</p>
47
- ${t(o(`// URL: /products?category=shoes&sort=price
48
- server: {
49
- data: async (ctx) => {
50
- const { category, sort } = ctx.query
51
- return { products: await db.products.list({ category, sort }) }
52
- },
53
- }`,"js"))}
54
-
55
- ${e("not-found","404 handling")}
56
- <p>If no spec matches the incoming request path, Pulse returns a 404 response. The response body is a minimal HTML page. To customise the 404 page, use the <code>onError</code> option in <code>createServer</code>:</p>
57
- ${t(o(`createServer(specs, {
58
- onError: (err, req, res) => {
59
- if (err.status === 404) {
60
- res.writeHead(404, { 'Content-Type': 'text/html' })
61
- res.end('<h1>Not found</h1>')
62
- }
63
- }
64
- })`,"js"))}
65
-
66
- ${e("conventions","File naming conventions")}
67
- <p>While Pulse does not auto-discover files, the recommended convention maps file names to routes:</p>
68
- ${p(["File","Route"],[["<code>src/pages/home.js</code>","<code>/</code>"],["<code>src/pages/about.js</code>","<code>/about</code>"],["<code>src/pages/products.js</code>","<code>/products</code>"],["<code>src/pages/product.js</code>","<code>/products/:id</code>"],["<code>src/pages/blog-post.js</code>","<code>/blog/:slug</code>"]])}
69
- ${u("tip","The filename does not need to match the route exactly \u2014 it is just a helpful convention. A file named <code>product.js</code> can handle <code>/products/:id</code> without any issue.")}
70
- `})};var r=document.getElementById("pulse-root");r&&!r.dataset.pulseMounted&&(r.dataset.pulseMounted="1",s(a,r,window.__PULSE_SERVER__||{},{ssr:!0}),m(r,s));var w=a;export{w as default};
@@ -1 +0,0 @@
1
- var S={},k=!1,j={},O=!1,C=new Set;function D(t){k||(S={...t||{}},k=!0)}function U(){return S}function q(t){return C.add(t),()=>C.delete(t)}function M(t){S={...S,...t};for(let r of C)r(S)}function R(t){O||(j=t||{},O=!0)}function x(t,r){let n=j[t];if(!n){console.warn(`[Pulse] No store mutation found for "${t}"`);return}M(n(S,r))}var A=t=>import("./runtime-KO4BHUQ3.js").then(r=>r.showToast(t));function st(t,r,n={},o={}){typeof window<"u"&&(D(window.__PULSE_STORE__||{}),o.store?.mutations&&R(o.store.mutations));let e=new Set(t.store||[]),a={};for(let[u,c]of Object.entries(n))e.has(u)||(a[u]=c);function i(){if(!e.size)return a;let u=U(),c={};for(let b of e)u[b]!==void 0&&(c[b]=u[b]);return{...c,...a}}let s=V(t.state),d=t.persist?.length?`pulse:${t.route||location.pathname}`:null,g=!1;if(d)try{let u=JSON.parse(localStorage.getItem(d)||"{}");t.persist.forEach(c=>{u[c]!==void 0&&u[c]!==t.state[c]&&(s[c]=u[c],g=!0)})}catch{}function v(){if(d)try{let u={};t.persist.forEach(c=>{u[c]=s[c]}),localStorage.setItem(d,JSON.stringify(u))}catch{}}let m=null;function h(){let u;try{u=F(t,s,i())}catch(c){console.error("[Pulse] View error:",c);let b=i();u=t.onViewError?t.onViewError(c,s,b):Q(c)}u!==m&&(m=u,G(r,u))}function E(u,c){if(t.mutations?.[u]){let b=t.mutations[u](s,c);b?._toast&&A(b._toast);let{_toast:N,...f}=b??{};s=L({...s,...f},t.constraints),v(),h();return}if(t.actions?.[u]){y(u,t.actions[u],s,c);return}console.warn(`[Pulse] No mutation or action found for "${u}"`)}async function y(u,c,b,N){if(c.onStart){let f=c.onStart(b,N);f?._toast&&A(f._toast);let{_toast:l,...T}=f??{};s=L({...s,...T},t.constraints),h()}if(c.validate){let f=X(s,t.validation);if(f.length>0){console.warn(`[Pulse] Validation failed for action "${u}":`,f);let l=c.onError?.(s,{validation:f})??{};l._toast&&A(l._toast);let{_toast:T,...$}=l;s=L({...s,...$},t.constraints),h();return}}try{let f=await c.run(s,i(),N),l=c.onSuccess(s,f)??{};l._storeUpdate&&M(l._storeUpdate),l._toast&&A(l._toast);let{_storeUpdate:T,_toast:$,...J}=l;s=L({...s,...J},t.constraints)}catch(f){console.error(`[Pulse] Action "${u}" failed:`,f);let l=c.onError(s,f)??{};l._toast&&A(l._toast);let{_toast:T,...$}=l;s=L({...s,...$},t.constraints)}v(),h()}let w=new AbortController;B(r,E,w.signal);let p=e.size>0?q(()=>h()):null;return o.ssr&&!g||h(),{getState:()=>V(s),dispatch:E,refresh:h,destroy:()=>{p?.(),w.abort(),r.innerHTML=""}}}function F(t,r,n){return typeof t.view=="function"?t.view(r,n):Object.values(t.view).map(o=>o(r,n)).join("")}function B(t,r,n){let o=n?{signal:n}:{};t.addEventListener("click",e=>{let a=e.target?.closest?.("[data-store-event]");if(a){let[m,h]=_(a.dataset.storeEvent);m==="click"&&(e.preventDefault(),x(h,e));return}let i=e.target?.closest?.("[data-dialog-open]");if(i){let m=document.getElementById(i.dataset.dialogOpen);m?.showModal&&(e.preventDefault(),m.showModal());return}let s=e.target?.closest?.("[data-dialog-close]");if(s){let m=s.closest("dialog");m&&(e.preventDefault(),m.close());return}if(e.target?.tagName==="DIALOG"){e.target.close();return}let d=e.target?.closest?.("[data-event]");if(!d)return;let[g,v]=_(d.dataset.event);g==="click"&&(e.preventDefault(),r(v,e))},o),t.addEventListener("change",e=>{let a=e.target?.closest?.("[data-store-event]");if(a){let[g,v]=_(a.dataset.storeEvent);g==="change"&&x(v,e);return}let i=e.target?.closest?.("[data-event]");if(!i)return;let[s,d]=_(i.dataset.event);s==="change"&&I(i,d,e,r)},o),t.addEventListener("input",e=>{let a=e.target?.closest?.("[data-store-event]");if(a){let[g,v]=_(a.dataset.storeEvent);g==="input"&&x(v,e);return}let i=e.target?.closest?.("[data-event]");if(!i)return;let[s,d]=_(i.dataset.event);s==="input"&&I(i,d,e,r)},o),t.addEventListener("submit",e=>{let a=e.target?.closest?.("[data-action]");a&&(e.preventDefault(),r(a.dataset.action,new FormData(a)),a.hasAttribute("data-reset")&&a.reset())},o)}function G(t,r){if(typeof document>"u"){t.innerHTML=r;return}let n=document.createElement("div");n.innerHTML=r,z(t,n)}function z(t,r){let n=Array.from(t.childNodes),o=Array.from(r.childNodes);for(o.forEach((e,a)=>{let i=n[a];if(!i){t.appendChild(e.cloneNode(!0));return}if(i.nodeType!==e.nodeType||i.nodeName!==e.nodeName){t.replaceChild(e.cloneNode(!0),i);return}if(e.nodeType===3){i.nodeValue!==e.nodeValue&&(i.nodeValue=e.nodeValue);return}e.nodeType===1&&(W(i,e),z(i,e))});t.childNodes.length>o.length;)t.removeChild(t.lastChild)}function W(t,r){for(let{name:n,value:o}of Array.from(r.attributes))t.getAttribute(n)!==o&&t.setAttribute(n,o);for(let{name:n}of Array.from(t.attributes))r.hasAttribute(n)||t.removeAttribute(n)}function _(t){let r=t.split(":");return r.length===1?["click",r[0]]:[r[0],r[1]]}function I(t,r,n,o){let e=parseInt(t.dataset.debounce,10);if(e>0){K(t,"d",r,e,i=>o(r,i))(n);return}let a=parseInt(t.dataset.throttle,10);if(a>0){K(t,"t",r,a,i=>o(r,i))(n);return}o(r,n)}function L(t,r){if(!r)return t;let n=V(t);for(let[o,e]of Object.entries(r)){let{obj:a,key:i}=H(n,o);a===null||a[i]===void 0||(e.min!==void 0&&typeof a[i]=="number"&&(a[i]=Math.max(a[i],e.min)),e.max!==void 0&&typeof a[i]=="number"&&(a[i]=Math.min(a[i],e.max)))}return n}function X(t,r){if(!r)return[];let n=[];for(let[o,e]of Object.entries(r)){let{obj:a,key:i}=H(t,o),s=a?.[i];if(e.required&&!s){n.push({path:o,rule:"required",message:`${o} is required`});continue}s==null||s===""||(e.minLength!==void 0&&String(s).length<e.minLength&&n.push({path:o,rule:"minLength",message:`${o} must be at least ${e.minLength} characters`}),e.maxLength!==void 0&&String(s).length>e.maxLength&&n.push({path:o,rule:"maxLength",message:`${o} must be no more than ${e.maxLength} characters`}),e.min!==void 0&&Number(s)<e.min&&n.push({path:o,rule:"min",message:`${o} must be at least ${e.min}`}),e.max!==void 0&&Number(s)>e.max&&n.push({path:o,rule:"max",message:`${o} must be no more than ${e.max}`}),e.format==="email"&&!Y(String(s))&&n.push({path:o,rule:"format",message:`${o} must be a valid email address`}),e.format==="url"&&!et(String(s))&&n.push({path:o,rule:"format",message:`${o} must be a valid URL`}),e.format==="numeric"&&isNaN(Number(s))&&n.push({path:o,rule:"format",message:`${o} must be numeric`}),e.pattern&&!e.pattern.test(String(s))&&n.push({path:o,rule:"pattern",message:`${o} does not match the required format`}))}return n}function H(t,r){let n=r.split("."),o=n.pop(),e=t;for(let a of n){if(e==null||typeof e!="object")return{obj:null,key:o};e=e[a]}return{obj:e,key:o}}function V(t){return JSON.parse(JSON.stringify(t))}function Q(t){return`<div style="padding:1rem;color:#b91c1c;background:#fef2f2;border:1px solid #fca5a5;border-radius:.375rem;font-family:monospace;font-size:.875rem"><strong>View error</strong>${t?.message?`: ${t.message}`:""}</div>`}function Y(t){return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)}var P=new WeakMap;function K(t,r,n,o,e){P.has(t)||P.set(t,{});let a=P.get(t),i=`${r}:${n}:${o}`;return a[i]||(a[i]=r==="d"?Z(e,o):tt(e,o)),a[i]}function Z(t,r){let n;return function(...o){clearTimeout(n),n=setTimeout(()=>t(...o),r)}}function tt(t,r){let n=0;return function(...o){let e=Date.now();e-n>=r&&(n=e,t(...o))}}function et(t){try{return new URL(t),!0}catch{return!1}}function it(t,r){history.replaceState({pulse:!0,path:location.pathname},"",location.pathname);let n=null;async function o(e,a){try{let i=await fetch(e,{headers:{"X-Pulse-Navigate":"true"}});if(!i.ok){location.href=e;return}let{html:s,title:d,styles:g,scripts:v,hydrate:m,serverState:h,storeState:E}=await i.json();if(E&&typeof window<"u"&&window.__updatePulseStore__&&window.__updatePulseStore__(E),t.innerHTML=s,document.title=d||document.title,Array.isArray(g)){let w=new Set([...document.querySelectorAll('link[rel="stylesheet"]')].map(p=>p.getAttribute("href")));for(let p of g)if(!w.has(p)){let u=document.createElement("link");u.rel="stylesheet",u.href=p,document.head.appendChild(u)}}if(Array.isArray(v)){let w=new Set([...document.querySelectorAll("script[src]")].map(p=>p.getAttribute("src")));await Promise.all(v.filter(p=>!w.has(p)).map(p=>new Promise(u=>{let c=document.createElement("script");c.src=p,c.onload=u,c.onerror=u,document.head.appendChild(c)})))}if(nt(t),a&&history.pushState({pulse:!0,path:e},"",e),m&&r){n?.destroy(),t.dataset.pulseMounted="1",window.__PULSE_SERVER__=h||{};let{default:w}=await import(m);w&&(n=r(w,t,h||{}))}document.dispatchEvent(new CustomEvent("pulse:navigate")),window.scrollTo({top:0,behavior:"instant"});let y=t.querySelector("#main-content, main, h1")||t;y.hasAttribute("tabindex")||(y.setAttribute("tabindex","-1"),y.addEventListener("blur",()=>y.removeAttribute("tabindex"),{once:!0})),y.focus({preventScroll:!0})}catch{location.href=e}}return document.addEventListener("click",e=>{let a=e.target.closest("a");if(!a)return;let i=a.getAttribute("href");if(!i)return;let s;try{s=new URL(i,location.origin)}catch{return}s.origin===location.origin&&(e.ctrlKey||e.metaKey||e.shiftKey||e.altKey||a.target&&a.target!=="_self"||(e.preventDefault(),o(s.pathname+s.search,!0)))}),window.addEventListener("popstate",e=>{e.state?.pulse&&o(location.pathname+location.search,!1)}),{setMount:e=>{n=e}}}function nt(t){t.querySelectorAll("script:not([src])").forEach(r=>{let n=document.createElement("script");n.textContent=r.textContent,document.head.appendChild(n),n.remove()})}export{st as a,it as b};
@@ -1,49 +0,0 @@
1
- var l="pulse-toasts";var m=new Set(["success","error","warning","info"]),p=`
2
- #pulse-toasts {
3
- position: fixed;
4
- top: 1rem;
5
- right: 1rem;
6
- z-index: 9999;
7
- display: flex;
8
- flex-direction: column;
9
- gap: .5rem;
10
- pointer-events: none;
11
- max-width: min(24rem, calc(100vw - 2rem));
12
- }
13
- .pulse-toast {
14
- display: flex;
15
- align-items: flex-start;
16
- gap: .75rem;
17
- padding: .75rem 1rem;
18
- border-radius: .5rem;
19
- box-shadow: 0 4px 16px rgba(0,0,0,.2);
20
- font-size: .875rem;
21
- line-height: 1.4;
22
- pointer-events: all;
23
- opacity: 0;
24
- transform: translateX(calc(100% + 1.5rem));
25
- transition: opacity .2s ease, transform .2s ease;
26
- background: #1e293b;
27
- color: #f8fafc;
28
- }
29
- .pulse-toast--visible {
30
- opacity: 1;
31
- transform: translateX(0);
32
- }
33
- .pulse-toast--success { background: #166534; }
34
- .pulse-toast--error { background: #991b1b; }
35
- .pulse-toast--warning { background: #92400e; }
36
- .pulse-toast-message { flex: 1; }
37
- .pulse-toast-close {
38
- background: none;
39
- border: none;
40
- color: inherit;
41
- cursor: pointer;
42
- padding: 0 0 0 .25rem;
43
- font-size: 1.125rem;
44
- line-height: 1;
45
- opacity: .7;
46
- flex-shrink: 0;
47
- }
48
- .pulse-toast-close:hover { opacity: 1; }
49
- `,c=!1;function f(){if(c)return;c=!0;let e=document.createElement("style");e.textContent=p,document.head.appendChild(e)}function g(){let e=document.getElementById(l);return e||(f(),e=document.createElement("div"),e.id=l,e.setAttribute("aria-live","polite"),e.setAttribute("aria-atomic","false"),document.body.appendChild(e)),e}function b(e){if(typeof document>"u")return;let{message:a,variant:i="info",duration:r=4e3}=typeof e=="string"?{message:e}:e;if(!a)return;let u=m.has(i)?i:"info",n=g();for(;n.children.length>=5;)n.firstElementChild?.remove();let t=document.createElement("div");t.className=`pulse-toast pulse-toast--${u}`,t.setAttribute("role","status");let o=document.createElement("span");o.className="pulse-toast-message",o.textContent=a;let s=document.createElement("button");s.className="pulse-toast-close",s.setAttribute("aria-label","Dismiss notification"),s.textContent="\xD7",s.addEventListener("click",()=>d(t)),t.appendChild(o),t.appendChild(s),n.appendChild(t),requestAnimationFrame(()=>t.classList.add("pulse-toast--visible")),r>0&&setTimeout(()=>d(t),r)}function d(e){e.classList.remove("pulse-toast--visible"),e.addEventListener("transitionend",()=>e.remove(),{once:!0})}export{b as showToast};
@@ -1,59 +0,0 @@
1
- var s=[{section:"Framework",items:[{label:"Overview",href:"/"},{label:"FAQ",href:"/faq"},{label:"Project Structure",href:"/project-structure"},{label:"Spec Reference",href:"/spec"},{label:"Configuration",href:"/config"}]},{section:"Developer Guide",items:[{label:"Getting Started",href:"/getting-started"},{label:"How It Works",href:"/how-it-works"},{label:"Slash Commands",href:"/slash-commands"},{label:"Prompt Examples",href:"/prompt-examples"},{label:"Component Library",href:"/components"}]},{section:"State & Behaviour",items:[{label:"State",href:"/state"},{label:"Mutations",href:"/mutations"},{label:"Actions",href:"/actions"},{label:"Validation",href:"/validation"},{label:"Constraints",href:"/constraints"},{label:"Persist",href:"/persist"}]},{section:"Server",items:[{label:"Server Data",href:"/server-data"},{label:"Global Store",href:"/store"},{label:"Routing",href:"/routing"},{label:"Streaming SSR",href:"/streaming"},{label:"Caching",href:"/caching"},{label:"Guard",href:"/guard"},{label:"Raw Responses",href:"/raw-responses"},{label:"Server API",href:"/server-api"},{label:"Extending Pulse",href:"/extending"}]},{section:"Client",items:[{label:"Hydration",href:"/hydration"},{label:"Navigation",href:"/navigation"},{label:"Images",href:"/images"}]},{section:"Deployment",items:[{label:"Deployment",href:"/deploy"}]},{section:"Integrations",items:[{label:"Supabase",href:"/supabase"},{label:"Auth (Auth0)",href:"/auth"},{label:"Payments (Stripe)",href:"/stripe"}]},{section:"Reference",items:[{label:"Metadata & SEO",href:"/meta"},{label:"Performance",href:"/performance"},{label:"Accessibility",href:"/accessibility"},{label:"Testing",href:"/testing"}]},{section:"UI Components",items:[{label:"Alert",href:"/components/alert"},{label:"Avatar",href:"/components/avatar"},{label:"Badge",href:"/components/badge"},{label:"Breadcrumbs",href:"/components/breadcrumbs"},{label:"Button",href:"/components/button"},{label:"Card",href:"/components/card"},{label:"Checkbox",href:"/components/checkbox"},{label:"Carousel",href:"/components/carousel"},{label:"Charts",href:"/components/charts"},{label:"Empty",href:"/components/empty"},{label:"Fieldset",href:"/components/fieldset"},{label:"File Upload",href:"/components/file-upload"},{label:"Heading",href:"/components/heading"},{label:"Icons",href:"/components/icons"},{label:"Image",href:"/components/image"},{label:"Input",href:"/components/input"},{label:"List",href:"/components/list"},{label:"Modal",href:"/components/modal"},{label:"Progress",href:"/components/progress"},{label:"Prose",href:"/components/prose"},{label:"Pullquote",href:"/components/pullquote"},{label:"Radio",href:"/components/radio"},{label:"Rating",href:"/components/rating"},{label:"Search",href:"/components/search"},{label:"Segmented",href:"/components/segmented"},{label:"Select",href:"/components/select"},{label:"Slider",href:"/components/slider"},{label:"Spinner",href:"/components/spinner"},{label:"Stat",href:"/components/stat"},{label:"Stepper",href:"/components/stepper"},{label:"Table",href:"/components/table"},{label:"Textarea",href:"/components/textarea"},{label:"Timeline",href:"/components/timeline"},{label:"Toggle",href:"/components/toggle"},{label:"Tooltip",href:"/components/tooltip"}]},{section:"Landing Components",items:[{label:"Accordion",href:"/components/accordion"},{label:"App Badge",href:"/components/app-badge"},{label:"CTA",href:"/components/cta"},{label:"Feature",href:"/components/feature"},{label:"Hero",href:"/components/hero"},{label:"Nav",href:"/components/nav"},{label:"Pricing",href:"/components/pricing"},{label:"Testimonial",href:"/components/testimonial"}]},{section:"Layout Components",items:[{label:"Banner",href:"/components/banner"},{label:"Cluster",href:"/components/cluster"},{label:"Code Window",href:"/components/code-window"},{label:"Container",href:"/components/container"},{label:"Divider",href:"/components/divider"},{label:"Footer",href:"/components/footer"},{label:"Grid",href:"/components/grid"},{label:"Media",href:"/components/media"},{label:"Section",href:"/components/section"},{label:"Stack",href:"/components/stack"}]}];function p(e){let a=s.flatMap(t=>t.items),l=a.findIndex(t=>t.href===e);return{prev:l>0?a[l-1]:null,next:l<a.length-1?a[l+1]:null}}function n(e){return String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function c(e){return`
2
- <aside class="docs-sidebar" aria-label="Documentation navigation">
3
- <div class="sidebar-logo">
4
- <a href="/" class="logo-link" aria-label="Pulse home">
5
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">
6
- <path d="M13 2L4.5 13.5H11L10 22L19.5 10.5H13L13 2Z" fill="var(--accent)" stroke="var(--accent)" stroke-width="1" stroke-linejoin="round"/>
7
- </svg>
8
- <span class="logo-name">Pulse</span>
9
- </a>
10
- <span class="version-badge">v0.1</span>
11
- </div>
12
- <nav class="sidebar-nav">
13
- ${s.map(({section:l,items:t})=>{let r=t.map(o=>{let i=o.href===e;return`<a href="${n(o.href)}" class="nav-link${i?" active":""}"${i?' aria-current="page"':""}>${n(o.label)}</a>`}).join("");return`
14
- <div class="nav-section">
15
- <p class="nav-section-title">${n(l)}</p>
16
- ${r}
17
- </div>`}).join("")}
18
- </nav>
19
- </aside>`}function h(e,a){return!e&&!a?"":`
20
- <nav class="doc-prev-next" aria-label="Previous and next pages">
21
- <div class="prev-next-grid">
22
- ${e?`<a href="${n(e.href)}" class="prev-next-link prev-link">
23
- <span class="prev-next-label">\u2190 Previous</span>
24
- <span class="prev-next-title">${n(e.label)}</span>
25
- </a>`:"<div></div>"}
26
- ${a?`<a href="${n(a.href)}" class="prev-next-link next-link">
27
- <span class="prev-next-label">Next \u2192</span>
28
- <span class="prev-next-title">${n(a.label)}</span>
29
- </a>`:"<div></div>"}
30
- </div>
31
- </nav>`}function f({currentHref:e,content:a,prev:l=null,next:t=null}){return`
32
- <div class="sidebar-overlay" aria-hidden="true"></div>
33
- ${c(e)}
34
- <div class="docs-main">
35
- <header class="docs-header">
36
- <button class="mobile-menu-btn" aria-label="Toggle navigation menu" aria-expanded="false" aria-controls="docs-sidebar">
37
- <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
38
- <path fill-rule="evenodd" d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z" clip-rule="evenodd"/>
39
- </svg>
40
- </button>
41
- <a href="/" class="header-logo-mobile" aria-label="Pulse home">
42
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true">
43
- <path d="M13 2L4.5 13.5H11L10 22L19.5 10.5H13L13 2Z" fill="var(--accent)" stroke="var(--accent)" stroke-width="1" stroke-linejoin="round"/>
44
- </svg>
45
- </a>
46
- <a href="https://github.com/invisibleloop/pulse" class="header-github" aria-label="View on GitHub" target="_blank" rel="noopener noreferrer">
47
- <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
48
- <path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/>
49
- </svg>
50
- GitHub
51
- </a>
52
- </header>
53
- <main class="docs-content">
54
- ${a}
55
- ${h(l,t)}
56
- </main>
57
- </div>
58
- <script src="/menu.js"><\/script>
59
- <script src="/pulse-ui.js"><\/script>`}function m(e){return`<h1 class="doc-h1">${n(e)}</h1>`}function u(e){return`<p class="doc-lead">${e}</p>`}function v(e,a){return`<h2 class="doc-h2" id="${n(e)}"><a href="#${n(e)}" class="heading-anchor">${n(a)}</a></h2>`}function g(e,a){let l=a??e;return`<h3 class="doc-h3" id="${n(e)}"><a href="#${n(e)}" class="heading-anchor">${n(l)}</a></h3>`}function x(e,a=""){return`${a?`<div class="code-filename">${n(a)}</div>`:""}<pre class="code-block"><code>${e}</code></pre>`}function $(e,a){let l=e.map(r=>`<th>${r}</th>`).join(""),t=a.map(r=>`<tr>${r.map(o=>`<td>${o}</td>`).join("")}</tr>`).join("");return`<div class="table-wrap"><table><thead><tr>${l}</tr></thead><tbody>${t}</tbody></table></div>`}function S(e,a){let l={note:"\u2139",warning:"\u26A0",tip:"\u2726"};return`<div class="callout callout-${n(e)}"><span class="callout-icon" aria-hidden="true">${l[e]||"\u2139"}</span><div class="callout-body">${a}</div></div>`}export{p as a,f as b,m as c,u as d,v as e,g as f,x as g,$ as h,S as i};
@@ -1,5 +0,0 @@
1
- var k=new Set(["export","default","import","from","as","async","await","const","let","var","return","if","else","for","while","of","in","true","false","null","undefined","function","class","new","this","typeof","instanceof","try","catch","finally","throw"]);function r(n){return n.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function h(n,i){return`<span class="${n}">${r(i)}</span>`}function p(n,i="js"){return i==="bash"?g(n):i==="html"?w(n):c(n)}function c(n){let i="",t=0,l=n.length;for(;t<l;){let s=n[t],u=n[t+1];if(s==="/"&&u==="/"){let e=n.indexOf(`
2
- `,t),f=e===-1?n.slice(t):n.slice(t,e);i+=h("tok-cmt",f),t+=f.length;continue}if(s==="/"&&u==="*"){let e=n.indexOf("*/",t+2),f=e===-1?n.slice(t):n.slice(t,e+2);i+=h("tok-cmt",f),t+=f.length;continue}if(s==="`"){let e="`";t++;let f=0;for(;t<l;){if(n[t]==="\\"){e+=n[t]+(n[t+1]||""),t+=2;continue}if(n[t]==="$"&&n[t+1]==="{"){e+="${",t+=2,f++;continue}if(n[t]==="}"&&f>0){e+="}",t++,f--;continue}if(n[t]==="`"&&f===0){e+="`",t++;break}e+=n[t++]}i+=h("tok-str",e);continue}if(s==='"'||s==="'"){let e=s;for(t++;t<l&&n[t]!==s&&n[t]!==`
3
- `;){if(n[t]==="\\"){e+=n[t]+(n[t+1]||""),t+=2;continue}e+=n[t++]}t<l&&n[t]===s&&(e+=n[t++]),i+=h("tok-str",e);continue}if(/\d/.test(s)&&(t===0||!/\w/.test(n[t-1]))){let e="";for(;t<l&&/[\d.xXa-fA-F]/.test(n[t]);)e+=n[t++];i+=h("tok-num",e);continue}if(/[a-zA-Z_$]/.test(s)){let e="";for(;t<l&&/[\w$]/.test(n[t]);)e+=n[t++];if(k.has(e))i+=h("tok-kw",e);else{let f=t;for(;f<l&&n[f]===" ";)f++;n[f]==="("?i+=h("tok-fn",e):i+=r(e)}continue}if(/[=!<>|&?.]/.test(s)){let e=s;/[=!<>|&?]/.test(s)&&/[=|&>?]/.test(u)&&(e=n.slice(t,t+2),n[t+2]==="="&&(e=n.slice(t,t+3))),i+=h("tok-op",e),t+=e.length;continue}if(/[{}()[\],;:]/.test(s)){i+=h("tok-punct",s),t++;continue}i+=r(s),t++}return i}function g(n){return n.split(`
4
- `).map(i=>{if(/^\s*#/.test(i))return h("tok-cmt",i);let t="",l=0;for(;l<i.length;){if(i[l]==="#"){t+=h("tok-cmt",i.slice(l));break}if(i[l]==='"'||i[l]==="'"){let s=i[l],u=s;for(l++;l<i.length&&i[l]!==s;)u+=i[l++];u+=i[l]===s?i[l++]:"",t+=h("tok-str",u);continue}if(i[l]==="-"&&/\w/.test(i[l+1]||"")){let s="-";for(l++;l<i.length&&/[-\w]/.test(i[l]);)s+=i[l++];t+=h("tok-op",s);continue}t+=r(i[l++])}return t}).join(`
5
- `)}function w(n){let i="",t=0,l=n.length;for(;t<l;){if(n.slice(t,t+4)==="<!--"){let s=n.indexOf("-->",t+4),u=s===-1?n.slice(t):n.slice(t,s+3);i+=h("tok-cmt",u),t+=u.length;continue}if(n[t]==="<"){let s=n.indexOf(">",t);if(s===-1){i+=r(n[t++]);continue}let e=n.slice(t,s+1).replace(/^(<\/?)([\w-]+)/,(f,o,a)=>r(o)+h("tok-kw",a)).replace(/([\w-]+)(=)/g,(f,o,a)=>h("tok-fn",o)+r(a)).replace(/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g,(f,o)=>h("tok-str",o));i+=e,t=s+1;continue}i+=r(n[t++])}return i}export{p as a};