@pebble-software/bonsai 0.0.3 → 0.0.4

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 (75) hide show
  1. package/README.md +21 -45
  2. package/dist/index.css +453 -0
  3. package/dist/index.d.ts +100 -4
  4. package/package.json +17 -8
  5. package/dist/components/core/bonsai.d.ts +0 -17
  6. package/dist/components/ui/a.d.ts +0 -3
  7. package/dist/components/ui/accordion.d.ts +0 -17
  8. package/dist/components/ui/alert-dialog.d.ts +0 -23
  9. package/dist/components/ui/alert.d.ts +0 -12
  10. package/dist/components/ui/avatar.d.ts +0 -21
  11. package/dist/components/ui/badge.d.ts +0 -10
  12. package/dist/components/ui/breadcrumb.d.ts +0 -10
  13. package/dist/components/ui/button.d.ts +0 -14
  14. package/dist/components/ui/calendar.d.ts +0 -9
  15. package/dist/components/ui/card.d.ts +0 -15
  16. package/dist/components/ui/chart.d.ts +0 -44
  17. package/dist/components/ui/checkbox.d.ts +0 -7
  18. package/dist/components/ui/collapsible.d.ts +0 -5
  19. package/dist/components/ui/combobox.d.ts +0 -31
  20. package/dist/components/ui/command.d.ts +0 -23
  21. package/dist/components/ui/dialog.d.ts +0 -23
  22. package/dist/components/ui/drawer.d.ts +0 -22
  23. package/dist/components/ui/dropdown-menu.d.ts +0 -29
  24. package/dist/components/ui/form.d.ts +0 -24
  25. package/dist/components/ui/h.d.ts +0 -10
  26. package/dist/components/ui/input-group.d.ts +0 -18
  27. package/dist/components/ui/input-otp.d.ts +0 -11
  28. package/dist/components/ui/input.d.ts +0 -3
  29. package/dist/components/ui/label.d.ts +0 -10
  30. package/dist/components/ui/loader.d.ts +0 -14
  31. package/dist/components/ui/map.d.ts +0 -30
  32. package/dist/components/ui/p.d.ts +0 -11
  33. package/dist/components/ui/popover.d.ts +0 -9
  34. package/dist/components/ui/progress.d.ts +0 -31
  35. package/dist/components/ui/scroll-area.d.ts +0 -13
  36. package/dist/components/ui/select.d.ts +0 -17
  37. package/dist/components/ui/separator.d.ts +0 -8
  38. package/dist/components/ui/sidebar.d.ts +0 -70
  39. package/dist/components/ui/signature-pad.d.ts +0 -7
  40. package/dist/components/ui/skeleton.d.ts +0 -7
  41. package/dist/components/ui/switch.d.ts +0 -8
  42. package/dist/components/ui/table.d.ts +0 -14
  43. package/dist/components/ui/tabs.d.ts +0 -15
  44. package/dist/components/ui/textarea.d.ts +0 -5
  45. package/dist/components/ui/toast.d.ts +0 -46
  46. package/dist/components/ui/tooltip.d.ts +0 -15
  47. package/dist/hooks/haptics.d.ts +0 -8
  48. package/dist/hooks/use-mobile.d.ts +0 -1
  49. package/dist/lib/ast/cache.d.ts +0 -8
  50. package/dist/lib/ast/evaluator.d.ts +0 -2
  51. package/dist/lib/ast/helpers.d.ts +0 -2
  52. package/dist/lib/ast/index.d.ts +0 -4
  53. package/dist/lib/ast/setup.d.ts +0 -2
  54. package/dist/lib/ast/types.d.ts +0 -1
  55. package/dist/lib/ast/utils.d.ts +0 -3
  56. package/dist/lib/haptics.d.ts +0 -8
  57. package/dist/lib/ui/registry.d.ts +0 -8
  58. package/dist/lib/utils.d.ts +0 -3
  59. package/dist/tests/ast.bugs.test.d.ts +0 -1
  60. package/dist/tests/ast.engine.test.d.ts +0 -1
  61. package/dist/tests/ast.helpers.test.d.ts +0 -1
  62. package/dist/tests/fixtures/examples.d.ts +0 -8
  63. package/dist/tests/helpers.d.ts +0 -4
  64. package/dist/tests/render.bindings.test.d.ts +0 -1
  65. package/dist/tests/render.controlled.test.d.ts +0 -1
  66. package/dist/tests/render.examples.test.d.ts +0 -1
  67. package/dist/tests/render.granularity.test.d.ts +0 -1
  68. package/dist/tests/render.ssr-client.test.d.ts +0 -1
  69. package/dist/tests/render.structural.test.d.ts +0 -1
  70. package/dist/tests/schema.contract.test.d.ts +0 -1
  71. package/dist/tests/schema.examples.test.d.ts +0 -1
  72. package/dist/tests/schema.kitchen-sink.test.d.ts +0 -1
  73. package/dist/tests/setup.d.ts +0 -1
  74. package/dist/tests/store.paths.test.d.ts +0 -1
  75. package/dist/tests/ui.contracts.test.d.ts +0 -1
package/README.md CHANGED
@@ -1,15 +1,7 @@
1
1
  # @pebble-software/bonsai
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/%40pebble-software%2Fbonsai)](https://www.npmjs.com/package/@pebble-software/bonsai)
4
- [![status: preview](https://img.shields.io/badge/status-preview-orange)](https://github.com/PebbleSoftwareInc/pebble-bonsai)
5
- [![react](https://img.shields.io/badge/react-18%20%7C%2019-61dafb)](https://react.dev)
6
-
7
- > ⚠️ **Active development / preview** — this package is pre-1.0 and evolving quickly. APIs, the configuration schema, and the component set may change between releases without a deprecation cycle. Pin an exact version and read release notes before upgrading. Feedback and issues are very welcome!
8
-
9
3
  The React runtime that turns a validated Bonsai configuration into a fully interactive, styled experience. It's built for UI that has to be delivered remotely (email, SMS, embedded experiences) but rendered locally with real React components, consistent styling, and predictable data flow.
10
4
 
11
- Bonsai is the rendering engine behind [Pebble Bonsai](https://github.com/PebbleSoftwareInc/pebble-bonsai), an open-source form and signature service — but the renderer is useful anywhere you need to ship UI as data: define a screen as a JSON template, bind it to your data with `{{ }}` expressions, and let one component render it on the server (SSR, PDF) or in the browser (fully interactive).
12
-
13
5
  ## Table of contents
14
6
 
15
7
  - [Features](#features)
@@ -28,31 +20,26 @@ Bonsai is the rendering engine behind [Pebble Bonsai](https://github.com/PebbleS
28
20
 
29
21
  ## Features
30
22
 
31
- - **Schema-driven UI** — every screen is a Zod-validated `Config` tree, so the renderer only ever processes known-safe structures.
32
- - **Universal rendering** — one component (`Bonsai`) picks server (SSR) or client (interactive) rendering automatically based on environment, with `web`, `pdf`, and `preview` render modes.
23
+ - **Schema-driven UI** — every screen is a Zod-validated `Config` tree from `@bonsai/schemas`, so the renderer only ever processes known-safe structures.
24
+ - **Universal rendering** — one component (`Bonsai`) picks server (SSR) or client (interactive) rendering automatically based on environment.
33
25
  - **Composable components** — schema component identifiers map to real React implementations via a `componentMap`, so the same schema can target different component sets.
34
26
  - **Isolated per-instance state** — each mounted `Bonsai` tree owns its own `valtio` store, scoped via React context. Nothing about which data renders lives in shared or module-level state.
35
27
  - **Fine-grained reactivity** — every leaf node reads the store itself and evaluates only its own bindings, so editing one field re-renders just that field and whatever depends on it.
36
28
  - **Structural nodes** — `each` and `if` handle looping and conditional rendering directly in the schema, with two-way binding preserved inside loops.
37
29
  - **Sandboxed expression language** — `{{ }}` bindings are evaluated by a small, fixed-surface formula language, never executable JavaScript.
38
- - **Tailwind CSS v4 design system** — a themeable token set (colors, typography, spacing, shadows) drives every component, and templates can layer on any Tailwind utility via `className`.
30
+ - **Tailwind CSS v4** theme with a `bonsai:` prefix to avoid collisions with a host app's own styles.
39
31
 
40
32
  ## Installation
41
33
 
42
34
  ```bash
43
- npm install @pebble-software/bonsai
44
- # or
45
35
  pnpm add @pebble-software/bonsai
46
- # or
47
- yarn add @pebble-software/bonsai
48
36
  ```
49
37
 
50
- Peer dependencies:
38
+ Peer expectations:
51
39
 
52
- - `react` and `react-dom` 18 or 19
40
+ - React 18 or newer
53
41
  - A bundler that understands ES modules (Vite, Next, Rspack, etc.)
54
-
55
- > **Preview note:** while the package is pre-1.0, pin an exact version (`npm install @pebble-software/bonsai --save-exact`) — minor releases may contain breaking changes.
42
+ - Tailwind CSS support if you intend to merge or override Bonsai styles
56
43
 
57
44
  ### Working in this monorepo
58
45
 
@@ -61,7 +48,7 @@ pnpm install
61
48
  pnpm --filter @pebble-software/bonsai run build
62
49
  ```
63
50
 
64
- The shared workspaces (`@bonsai/constants`, `@bonsai/schemas`) are aliased to their sources and bundled directly into `dist/` by Vite, so downstream consumers only ever install this one package.
51
+ The build script compiles the shared workspaces (`@bonsai/constants`, `@bonsai/schemas`) before bundling this package with Vite.
65
52
 
66
53
  ## Quick start
67
54
 
@@ -70,7 +57,7 @@ import { Bonsai, type Config } from "@pebble-software/bonsai";
70
57
 
71
58
  const template: Config = {
72
59
  type: "card",
73
- args: { className: "p-6 flex flex-col gap-4" },
60
+ args: { className: "bonsai:p-6 bonsai:flex bonsai:flex-col bonsai:gap-4" },
74
61
  children: [
75
62
  {
76
63
  type: "label",
@@ -111,7 +98,7 @@ export function Example() {
111
98
  | `useUpdateData()` | hook | Returns an `updateData(path, value)` function bound to the nearest `Bonsai` instance's store. Must be called from a component mounted inside it. |
112
99
  | `componentMap` | object | Maps schema component identifiers to React implementations. |
113
100
  | `controlledInputMap` | object | Defines how specific component types behave as controlled inputs (value prop, change handler, value extractor). |
114
- | `Config` | type | Runtime type describing a Bonsai node. |
101
+ | `Config` | type | Runtime type describing a Bonsai node, re-exported from `@bonsai/schemas`. |
115
102
  | `SignatureProps` | type | Props contract for the built-in signature capture component. |
116
103
 
117
104
  `Bonsai` accepts:
@@ -127,14 +114,11 @@ interface BonsaiProps {
127
114
  }
128
115
  ```
129
116
 
130
- - `render: "web"` (default) mounts the tree inside a `<form>` whose submit handler POSTs the captured data to `captureUrl`. `"preview"` renders the same form UI flagged with `data-mode="preview"` for host styling. `"pdf"` renders a plain `<div>` wrapper with no form — the layout used for print/PDF output.
131
- - `status` other than `"pending"` disables capture: the submit handler no-ops instead of POSTing.
132
-
133
117
  Each mounted instance owns its own store — there's no shared module-level state and no global `updateData`; `useUpdateData()` is the only way to reach a given instance's data, and only from within its subtree.
134
118
 
135
119
  ## Configuration schema
136
120
 
137
- The schema is defined in [`library/schemas/src/bonsai.schema.ts`](https://github.com/PebbleSoftwareInc/pebble-bonsai/blob/main/library/schemas/src/bonsai.schema.ts) and exported as `Config`.
121
+ The schema is defined in `@bonsai/schemas/src/bonsai.schema.ts` and exported as `Config`.
138
122
 
139
123
  - **Node shape**: each node is either a string literal (rendered directly, and itself eligible for `{{ }}` bindings) or an object with:
140
124
  - `type`: a value from `COMPONENT` (see [Component reference](#component-reference)), including the structural `each` and `if` types (see [Structural nodes](#structural-nodes-each--if)).
@@ -151,21 +135,18 @@ Example node:
151
135
  "args": {
152
136
  "id": "terms",
153
137
  "checked": "{{agreements.terms}}",
154
- "className": "mt-4"
138
+ "className": "bonsai:mt-4"
155
139
  },
156
140
  "children": ["I agree to the terms"]
157
141
  }
158
142
  ```
159
143
 
160
- Complete example templates live in the repo's [`examples/`](https://github.com/PebbleSoftwareInc/pebble-bonsai/tree/main/examples) directory, including a kitchen-sink template that exercises every component type.
161
-
162
144
  ## Dynamic data binding
163
145
 
164
146
  - Use `{{ }}` to declare bindings. A binding must span the *entire* string value — `"{{a}}"` is evaluated, `"{{a}} of {{b}}"` is not (put each placeholder in its own child/string instead). Whitespace inside `{{ }}` is trimmed automatically.
165
147
  - Paths support dot notation (`user.email`) and array indexing (`addresses[0].street`).
166
148
  - Missing keys resolve to `undefined`; use the `default(value, fallback)` helper for a fallback inline, or handle defaults in host data.
167
149
  - Any `args` prop or string child can contain a binding.
168
- - Besides your own `data`, the instance's top-level state fields — `status`, `expiry`, and `submitting` — are readable from bindings too (e.g. an `if` on `{{submitting}}`).
169
150
  - Inside an `each` row, an identifier resolves against the loop alias first (e.g. `applicant.name`), then falls back to the surrounding data for anything the alias doesn't shadow (e.g. `orgName` from outside the loop) — see [Structural nodes](#structural-nodes-each--if).
170
151
  - On the client, evaluating a node's bindings is also what subscribes that node to the store — see [Controlled inputs & state](#controlled-inputs--state).
171
152
 
@@ -282,7 +263,7 @@ Allowlisted methods: `map`, `filter`, `some`, `every`, `find`. Anything else —
282
263
 
283
264
  ## Component reference
284
265
 
285
- The full, authoritative list of component types is the `COMPONENT` enum in `@bonsai/constants` ([`library/constants/src/components.ts`](https://github.com/PebbleSoftwareInc/pebble-bonsai/blob/main/library/constants/src/components.ts)) — `componentMap` (`src/lib/ui/registry.ts`) has an entry for every value in it.
266
+ The full, authoritative list of component types is the `COMPONENT` enum in `@bonsai/constants` (`library/constants/src/components.ts`) — `componentMap` (`src/lib/ui/registry.ts`) has an entry for every value in it.
286
267
 
287
268
  | Category | Schema types |
288
269
  | --- | --- |
@@ -299,12 +280,10 @@ Controlled inputs (registered in `controlledInputMap` — `input`, `otp`, `check
299
280
 
300
281
  ## Styling
301
282
 
302
- Bonsai is styled with **Tailwind CSS v4**. Components emit plain Tailwind utility classes, and templates can pass any additional utilities through `className` on any node.
303
-
304
- - The design system lives in [`src/index.css`](https://github.com/PebbleSoftwareInc/pebble-bonsai/blob/main/apps/bonsai/src/index.css): an `@theme` token set (an OKLCH color palette with swappable accent colors, typography, shadows) plus base and utility layers, custom fonts (Sora, Fredoka, and Great Vibes for signatures) loaded via `@font-face`, and style overrides for the embedded map (MapLibre) and chart (Recharts) widgets.
305
- - Every rendered tree is wrapped in a `.bonsai-root` element carrying a `data-mode` attribute (`web` / `pdf` / `preview`), so hosts can scope their own overrides per render mode.
306
- - **Getting the stylesheet**: inside the Pebble Bonsai platform, CSS is compiled per-template at form-creation time — only the classes a template actually uses are emitted. When consuming this package standalone, compile the styles with your own Tailwind v4 build: import the Bonsai theme and add the package (and your template sources) as content via `@source` so the utilities your templates reference are generated. A prebuilt, ready-to-import stylesheet export is planned while the packaging story stabilizes during the preview period.
307
- - **Theming**: the theme is token-driven — redefine the CSS variables (e.g. `--color-main`, `--font-heading`) after the theme to restyle every component at once, or target `.bonsai-root` descendants for finer overrides.
283
+ - Tailwind CSS v4 is bundled with a `bonsai:` prefix to avoid class collisions. Host applications can safely include their own Tailwind build.
284
+ - `src/index.css` registers all theme tokens (colors, spacing, shadows, fonts) and safelists commonly generated utility classes.
285
+ - Custom fonts (e.g. for signatures) are declared locally and loaded via `@font-face`.
286
+ - To override styling, supply a `className` through the schema or wrap Bonsai components in host containers.
308
287
 
309
288
  ## Extending Bonsai
310
289
 
@@ -316,17 +295,14 @@ Bonsai is styled with **Tailwind CSS v4**. Components emit plain Tailwind utilit
316
295
  2. **Custom data handling**:
317
296
  - Use `useUpdateData()` from a component mounted inside a `Bonsai` instance's subtree for simple mutations.
318
297
  3. **Alternate styling**:
319
- - Redefine the theme's CSS variables before mounting Bonsai — see [Styling](#styling).
298
+ - Tailwind tokens can be overridden by redefining CSS variables before mounting Bonsai.
320
299
 
321
300
  ## Development
322
301
 
323
- Issues and pull requests are welcome at [PebbleSoftwareInc/pebble-bonsai](https://github.com/PebbleSoftwareInc/pebble-bonsai). This package lives at `apps/bonsai` in the monorepo.
324
-
325
302
  ```bash
326
- pnpm lint # Static analysis using ESLint
327
- pnpm test # Run the test suite (vitest)
328
- pnpm type-check # TypeScript without emit
329
- pnpm build # Bundles dist/ (ESM + type declarations) via Vite
303
+ pnpm lint # Static analysis using ESLint
304
+ pnpm test # Run the test suite (vitest)
305
+ pnpm build # Builds dependencies then bundles dist/ via Vite
330
306
  ```
331
307
 
332
- `dist/` contains the ESM bundle and TypeScript declarations consumed by downstream applications. The shared `@bonsai/constants` and `@bonsai/schemas` workspaces are bundled in, so the published package has no workspace dependencies.
308
+ `dist/` contains the ESM bundle and TypeScript declarations consumed by downstream applications.
package/dist/index.css ADDED
@@ -0,0 +1,453 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+
4
+ /* --- Fonts --- */
5
+ @font-face {
6
+ font-family: "Great Vibes";
7
+ src: url("https://brand.pebble.software/fonts/GreatVibes-Regular.ttf")
8
+ format("truetype");
9
+ font-weight: auto;
10
+ font-style: normal;
11
+ font-display: swap;
12
+ }
13
+ @font-face {
14
+ font-family: "Sora";
15
+ src: url("https://brand.pebble.software/fonts/Sora-VariableFont_wght.ttf")
16
+ format("truetype");
17
+ font-weight: 100 900;
18
+ font-style: normal;
19
+ font-display: swap;
20
+ }
21
+ @font-face {
22
+ font-family: "Fredoka";
23
+ src: url("https://brand.pebble.software/fonts/Fredoka-VariableFont_wdth,wght.ttf")
24
+ format("truetype");
25
+ font-weight: 300 700;
26
+ font-style: normal;
27
+ font-display: swap;
28
+ }
29
+
30
+ @theme {
31
+ /* --- Typography --- */
32
+ --font-body: "Sora", ui-sans-serif, system-ui, sans-serif;
33
+ --font-heading: "Fredoka", ui-monospace, monospace;
34
+ --font-signature: "Great Vibes", cursive;
35
+ --font-weight-base: 400;
36
+ --font-weight-heading: 600;
37
+
38
+ /* --- Core Colors (Default - Emerald) --- */
39
+ --color-main: var(--color-accent-emerald);
40
+ --color-main-secondary: var(--color-accent-emerald-secondary);
41
+ --color-main-foreground: oklch(0% 0 0);
42
+
43
+ --color-background: var(--color-white);
44
+ --color-foreground: oklch(0% 0 0);
45
+ --color-surface: oklch(100% 0 0);
46
+ --color-muted: oklch(0% 0 0 / 0.65);
47
+ --color-subtle: oklch(0% 0 0 / 0.5);
48
+
49
+ --color-border: oklch(0% 0 0);
50
+ --color-ring: oklch(0% 0 0);
51
+ --color-overlay: oklch(0% 0 0 / 0.8);
52
+
53
+ /* --- Accent Palette --- */
54
+ --color-accent-red: oklch(67.28% 0.2147 24.22);
55
+ --color-accent-red-secondary: oklch(93.3% 0.0339 17.77);
56
+
57
+ --color-accent-orange: oklch(72.27% 0.1894 50.19);
58
+ --color-accent-orange-secondary: oklch(95.38% 0.0357 72.89);
59
+
60
+ --color-accent-amber: oklch(84.08% 0.1725 84.2);
61
+ --color-accent-amber-secondary: oklch(96% 0.04 85);
62
+
63
+ --color-accent-yellow: oklch(86.03% 0.176 92.36);
64
+ --color-accent-yellow-secondary: oklch(96.79% 0.0654 102.26);
65
+
66
+ --color-accent-lime: oklch(83.29% 0.2331 132.51);
67
+ --color-accent-lime-secondary: oklch(95.37% 0.0549 125.19);
68
+
69
+ --color-accent-green: oklch(79.76% 0.2044 153.08);
70
+ --color-accent-green-secondary: oklch(96.47% 0.0401 157.79);
71
+
72
+ --color-accent-emerald: oklch(77.54% 0.1681 162.78);
73
+ --color-accent-emerald-secondary: oklch(95.31% 0.0496 169.04);
74
+
75
+ --color-accent-teal: oklch(78.57% 0.1422 180.36);
76
+ --color-accent-teal-secondary: oklch(95.08% 0.0481 184.07);
77
+
78
+ --color-accent-cyan: oklch(76.89% 0.139164 219.13);
79
+ --color-accent-cyan-secondary: oklch(94.61% 0.043 211.12);
80
+
81
+ --color-accent-sky: oklch(66.9% 0.18368 248.8066);
82
+ --color-accent-sky-secondary: oklch(94.27% 0.0268 242.57);
83
+
84
+ --color-accent-blue: oklch(67.47% 0.1726 259.49);
85
+ --color-accent-blue-secondary: oklch(93.46% 0.0305 255.11);
86
+
87
+ --color-accent-indigo: oklch(66.34% 0.1806 277.2);
88
+ --color-accent-indigo-secondary: oklch(92.13% 0.0388 282.36);
89
+
90
+ --color-accent-violet: oklch(70.28% 0.1753 295.36);
91
+ --color-accent-violet-secondary: oklch(93% 0.04 295);
92
+
93
+ --color-accent-purple: oklch(71.9% 0.198 310.03);
94
+ --color-accent-purple-secondary: oklch(94.11% 0.036556 308.0303);
95
+
96
+ --color-accent-fuchsia: oklch(73.43% 0.2332 321.41);
97
+ --color-accent-fuchsia-secondary: oklch(94% 0.04 321);
98
+
99
+ --color-accent-pink: oklch(71.5% 0.197 354.23);
100
+ --color-accent-pink-secondary: oklch(95.16% 0.0242 343.23);
101
+
102
+ --color-accent-rose: oklch(70.79% 0.1862 16.25);
103
+ --color-accent-rose-secondary: oklch(93.37% 0.0339 12.05);
104
+
105
+ /* --- Chart Colors (for Recharts graphs only) --- */
106
+ --color-chart-1: var(--color-accent-red);
107
+ --color-chart-2: var(--color-accent-orange);
108
+ --color-chart-3: var(--color-accent-amber);
109
+ --color-chart-4: var(--color-accent-yellow);
110
+ --color-chart-5: var(--color-accent-lime);
111
+ --color-chart-6: var(--color-accent-green);
112
+ --color-chart-7: var(--color-accent-emerald);
113
+ --color-chart-8: var(--color-accent-teal);
114
+ --color-chart-9: var(--color-accent-cyan);
115
+ --color-chart-10: var(--color-accent-sky);
116
+ --color-chart-11: var(--color-accent-blue);
117
+ --color-chart-12: var(--color-accent-indigo);
118
+ --color-chart-13: var(--color-accent-violet);
119
+ --color-chart-14: var(--color-accent-purple);
120
+ --color-chart-15: var(--color-accent-fuchsia);
121
+ --color-chart-16: var(--color-accent-pink);
122
+ --color-chart-17: var(--color-accent-rose);
123
+ --color-chart-active-dot: var(--color-foreground);
124
+
125
+ /* --- Borders --- */
126
+ --default-border-width: 2px;
127
+
128
+ /* --- Shadows & Spacing --- */
129
+ --radius-base: var(--radius-lg);
130
+ --spacing-boxShadowX: 3px;
131
+ --spacing-boxShadowY: 3px;
132
+
133
+ /* --- Drop Shadows --- */
134
+ --shadow-min: 1px 1px 0px 0px var(--color-border);
135
+ --shadow-xs: 2px 2px 0px 0px var(--color-border);
136
+ --shadow-sm: 3px 3px 0px 0px var(--color-border);
137
+ --shadow-md: 4px 4px 0px 0px var(--color-border);
138
+ --shadow-lg: 4.75px 4.75px 0px 0px var(--color-border);
139
+ --shadow-xl: 5.5px 5.5px 0px 0px var(--color-border);
140
+
141
+ --shadow-default: var(--shadow-md);
142
+
143
+ /* --- Inset Shadows --- */
144
+ --inset-shadow-xs: inset 1px 1px 0px 0px var(--color-border);
145
+ --inset-shadow-sm: inset 2px 2px 0px 0px var(--color-border);
146
+ --inset-shadow-md: inset 3px 3px 0px 0px var(--color-border);
147
+ --inset-shadow-lg: inset 4px 4px 0px 0px var(--color-border);
148
+ --inset-shadow-xl: inset 5px 5px 0px 0px var(--color-border);
149
+
150
+ /* --- Aspect Ratios --- */
151
+ --aspect-a4: 210/297;
152
+ --aspect-a4-landscape: 297/210;
153
+
154
+ /* --- Animations & Keyframes --- */
155
+ --animate-cloud-move: cloud-move 60s linear infinite alternate;
156
+ --animate-float: float 3s ease-in-out infinite;
157
+ --animate-subtle-float: subtle-float 3s ease-in-out infinite;
158
+ --animate-color-cycle: color-cycle 30s ease-in-out infinite;
159
+ --animate-color-cycle-vibrant: color-cycle-vibrant 30s ease-in-out infinite;
160
+ --animate-bg-color-cycle: bg-color-cycle 30s ease-in-out infinite;
161
+ --animate-bg-color-cycle-vibrant: bg-color-cycle-vibrant 30s ease-in-out
162
+ infinite;
163
+ --animate-font-color-cycle-vibrant: font-color-cycle-vibrant 30s ease-in-out
164
+ infinite;
165
+
166
+ @keyframes cloud-move {
167
+ 0% {
168
+ transform: translateX(0);
169
+ }
170
+ 100% {
171
+ transform: translateX(-100vw);
172
+ }
173
+ }
174
+ @keyframes float {
175
+ 0%,
176
+ 100% {
177
+ transform: translateY(0%);
178
+ }
179
+ 50% {
180
+ transform: translateY(-5%);
181
+ }
182
+ }
183
+ @keyframes subtle-float {
184
+ 0%,
185
+ 100% {
186
+ transform: translateY(0%);
187
+ }
188
+ 50% {
189
+ transform: translateY(-2%);
190
+ }
191
+ }
192
+
193
+ @keyframes bg-color-cycle {
194
+ 0% {
195
+ background-color: var(--color-accent-red-secondary);
196
+ }
197
+ 50% {
198
+ background-color: var(--color-accent-cyan-secondary);
199
+ }
200
+ 100% {
201
+ background-color: var(--color-accent-rose-secondary);
202
+ }
203
+ }
204
+ @keyframes bg-color-cycle-vibrant {
205
+ 0% {
206
+ background-color: var(--color-accent-red);
207
+ }
208
+ 50% {
209
+ background-color: var(--color-accent-cyan);
210
+ }
211
+ 100% {
212
+ background-color: var(--color-accent-rose);
213
+ }
214
+ }
215
+ @keyframes font-color-cycle-vibrant {
216
+ 0% {
217
+ color: var(--color-accent-red);
218
+ }
219
+ 50% {
220
+ color: var(--color-accent-cyan);
221
+ }
222
+ 100% {
223
+ color: var(--color-accent-rose);
224
+ }
225
+ }
226
+ }
227
+
228
+ /* --- Global Resets & Theme Overrides --- */
229
+ @layer base {
230
+ /* THEME DEFINITIONS
231
+ Usage: <html data-theme="red"> or <body data-theme="indigo">
232
+ Logic:
233
+ - Main: The vibrant accent color
234
+ - Background: The secondary pastel version
235
+ - Main-Foreground: White for dark accents, Black for light accents (EX. text on buttons)
236
+ */
237
+
238
+ [data-theme="red"] {
239
+ --color-main: var(--color-accent-red);
240
+ --color-main-secondary: var(--color-accent-red-secondary);
241
+ --color-background: var(--color-accent-red-secondary);
242
+ --color-main-foreground: oklch(100% 0 0);
243
+ }
244
+
245
+ [data-theme="orange"] {
246
+ --color-main: var(--color-accent-orange);
247
+ --color-main-secondary: var(--color-accent-orange-secondary);
248
+ --color-background: var(--color-accent-orange-secondary);
249
+ --color-main-foreground: oklch(0% 0 0);
250
+ }
251
+
252
+ [data-theme="amber"] {
253
+ --color-main: var(--color-accent-amber);
254
+ --color-main-secondary: var(--color-accent-amber-secondary);
255
+ --color-background: var(--color-accent-amber-secondary);
256
+ --color-main-foreground: oklch(0% 0 0);
257
+ }
258
+
259
+ [data-theme="yellow"] {
260
+ --color-main: var(--color-accent-yellow);
261
+ --color-main-secondary: var(--color-accent-yellow-secondary);
262
+ --color-background: var(--color-accent-yellow-secondary);
263
+ --color-main-foreground: oklch(0% 0 0);
264
+ }
265
+
266
+ [data-theme="lime"] {
267
+ --color-main: var(--color-accent-lime);
268
+ --color-main-secondary: var(--color-accent-lime-secondary);
269
+ --color-background: var(--color-accent-lime-secondary);
270
+ --color-main-foreground: oklch(0% 0 0);
271
+ }
272
+
273
+ [data-theme="green"] {
274
+ --color-main: var(--color-accent-green);
275
+ --color-main-secondary: var(--color-accent-green-secondary);
276
+ --color-background: var(--color-accent-green-secondary);
277
+ --color-main-foreground: oklch(0% 0 0);
278
+ }
279
+
280
+ [data-theme="emerald"] {
281
+ --color-main: var(--color-accent-emerald);
282
+ --color-main-secondary: var(--color-accent-emerald-secondary);
283
+ --color-background: var(--color-accent-emerald-secondary);
284
+ --color-main-foreground: oklch(0% 0 0);
285
+ }
286
+
287
+ [data-theme="teal"] {
288
+ --color-main: var(--color-accent-teal);
289
+ --color-main-secondary: var(--color-accent-teal-secondary);
290
+ --color-background: var(--color-accent-teal-secondary);
291
+ --color-main-foreground: oklch(0% 0 0);
292
+ }
293
+
294
+ [data-theme="cyan"] {
295
+ --color-main: var(--color-accent-cyan);
296
+ --color-main-secondary: var(--color-accent-cyan-secondary);
297
+ --color-background: var(--color-accent-cyan-secondary);
298
+ --color-main-foreground: oklch(0% 0 0);
299
+ }
300
+
301
+ [data-theme="sky"] {
302
+ --color-main: var(--color-accent-sky);
303
+ --color-main-secondary: var(--color-accent-sky-secondary);
304
+ --color-background: var(--color-accent-sky-secondary);
305
+ --color-main-foreground: oklch(0% 0 0);
306
+ }
307
+
308
+ [data-theme="blue"] {
309
+ --color-main: var(--color-accent-blue);
310
+ --color-main-secondary: var(--color-accent-blue-secondary);
311
+ --color-background: var(--color-accent-blue-secondary);
312
+ --color-main-foreground: oklch(100% 0 0);
313
+ }
314
+
315
+ [data-theme="indigo"] {
316
+ --color-main: var(--color-accent-indigo);
317
+ --color-main-secondary: var(--color-accent-indigo-secondary);
318
+ --color-background: var(--color-accent-indigo-secondary);
319
+ --color-main-foreground: oklch(100% 0 0);
320
+ }
321
+
322
+ [data-theme="violet"] {
323
+ --color-main: var(--color-accent-violet);
324
+ --color-main-secondary: var(--color-accent-violet-secondary);
325
+ --color-background: var(--color-accent-violet-secondary);
326
+ --color-main-foreground: oklch(100% 0 0);
327
+ }
328
+
329
+ [data-theme="purple"] {
330
+ --color-main: var(--color-accent-purple);
331
+ --color-main-secondary: var(--color-accent-purple-secondary);
332
+ --color-background: var(--color-accent-purple-secondary);
333
+ --color-main-foreground: oklch(100% 0 0);
334
+ }
335
+
336
+ [data-theme="fuchsia"] {
337
+ --color-main: var(--color-accent-fuchsia);
338
+ --color-main-secondary: var(--color-accent-fuchsia-secondary);
339
+ --color-background: var(--color-accent-fuchsia-secondary);
340
+ --color-main-foreground: oklch(0% 0 0);
341
+ }
342
+
343
+ [data-theme="pink"] {
344
+ --color-main: var(--color-accent-pink);
345
+ --color-main-secondary: var(--color-accent-pink-secondary);
346
+ --color-background: var(--color-accent-pink-secondary);
347
+ --color-main-foreground: oklch(0% 0 0);
348
+ }
349
+
350
+ [data-theme="rose"] {
351
+ --color-main: var(--color-accent-rose);
352
+ --color-main-secondary: var(--color-accent-rose-secondary);
353
+ --color-background: var(--color-accent-rose-secondary);
354
+ --color-main-foreground: oklch(100% 0 0);
355
+ }
356
+
357
+ html { }
358
+
359
+ body {
360
+ font-family: var(--font-body);
361
+ background-color: var(--color-background);
362
+ color: var(--color-foreground);
363
+ }
364
+
365
+ h1,
366
+ h2,
367
+ h3,
368
+ h4,
369
+ h5,
370
+ h6 {
371
+ font-family: var(--font-heading);
372
+ }
373
+ }
374
+
375
+ /* --- Utilities --- */
376
+ @layer utilities {
377
+ .neo-base {
378
+ @apply touch-manipulation select-none appearance-none outline-none;
379
+ }
380
+ .neo-popup-focus {
381
+ @apply focus-visible:after:absolute focus-visible:after:-inset-1 focus-visible:after:border-8 focus-visible:after:pointer-events-none
382
+ focus-visible:after:border-foreground focus-visible:after:rounded-[inherit] focus-visible:after:blur-[2px];
383
+ }
384
+ .neo-accordion-focus {
385
+ @apply focus:outline-none focus-visible:after:absolute focus-visible:after:-inset-px focus-visible:after:border-2 focus-visible:after:pointer-events-none
386
+ focus-visible:after:border-foreground/30 focus-visible:after:rounded-sm;
387
+ }
388
+ .neo-button-focus {
389
+ @apply focus:outline-none focus-visible:after:absolute focus-visible:after:-inset-px focus-visible:after:border-3 focus-visible:after:pointer-events-none
390
+ focus-visible:after:border-foreground/30 focus-visible:after:rounded-[inherit];
391
+ }
392
+ .neo-input-focus {
393
+ @apply outline-none hover:shadow-sm hover:-translate-[3px] focus-visible:shadow-sm focus-visible:-translate-[3px];
394
+ }
395
+ .neo-switch-focus {
396
+ @apply outline-none hover:shadow-xs hover:-translate-[2px] focus-visible:shadow-xs focus-visible:-translate-[2px];
397
+ }
398
+ .neo-chips-focus {
399
+ @apply translate-0 has-data-pressed:shadow-sm has-data-pressed:-translate-[3px] outline-none hover:shadow-sm hover:-translate-[3px] focus-visible:shadow-sm focus-visible:-translate-[3px];
400
+ }
401
+ .neo-checkbox-focus {
402
+ @apply hover:shadow-xs hover:-translate-0.5 focus-visible:shadow-xs focus-visible:-translate-0.5 active:translate-0 active:shadow-none;
403
+ }
404
+ .neo-sidebar-button-focus {
405
+ @apply border-2 border-transparent focus-visible:border-black hover:border-black hover:shadow-sm hover:-translate-[3px] focus-visible:shadow-sm focus-visible:-translate-[3px] active:shadow-none active:translate-0;
406
+ }
407
+ .neo-scrollbar-focus {
408
+ @apply active:z-14 hover:z-12 focus:[&_svg]:scale-110 focus:z-12 focus:[&_svg]:fill-main hover:[&_svg]:fill-main focus:[&_svg]:text-main hover:[&_svg]:text-main;
409
+ }
410
+
411
+ .no-scrollbar::-webkit-scrollbar {
412
+ display: none;
413
+ }
414
+ .no-scrollbar {
415
+ -ms-overflow-style: none;
416
+ scrollbar-width: none;
417
+ }
418
+ }
419
+
420
+ /* --- 3rd Party Overrides --- */
421
+ .recharts-wrapper > svg > g {
422
+ outline: none !important;
423
+ }
424
+ .recharts-wrapper .recharts-cartesian-grid-horizontal line:last-child {
425
+ stroke-opacity: 0;
426
+ }
427
+
428
+ .maplibregl-ctrl {
429
+ @apply bg-surface! outline-0! shadow-none! *:[button]:rounded-base *:[button]:bg-surface! *:[button]:hover:bg-main! *:[button]:transition-all *:[button]:duration-200 *:[button]:border-2! *:[button]:border-border! *:[button]:shadow-xs! *:[button]:-translate-0.5! *:[button]:hover:shadow-min! *:[button]:hover:-translate-px! *:[button]:focus-visible:-translate-px! *:[button]:focus-visible:shadow-min! *:[button]:active:shadow-none! *:[button]:active:translate-0! *:[button]:aria-disabled:shadow-none! *:[button]:aria-disabled:translate-0!;
430
+ }
431
+
432
+ .maplibregl-ctrl-zoom-in {
433
+ @apply -mb-0.5! rounded-b-none!;
434
+ }
435
+
436
+ .maplibregl-ctrl-zoom-out {
437
+ @apply rounded-t-none!;
438
+ }
439
+
440
+ .maplibregl-ctrl-fullscreen {
441
+ @apply p-1!;
442
+ }
443
+
444
+ .maplibregl-ctrl-scale {
445
+ @apply transition-[width] duration-500 delay-100 rounded-sm! border-2! border-border! hover:bg-main! bg-surface! font-bold! leading-none! py-1! shadow-sm! font-mono! text-xs!;
446
+ }
447
+
448
+ .maplibregl-user-location-dot {
449
+ @apply shadow-xl! bg-accent-blue! before:bg-accent-blue! after:border-border!;
450
+ }
451
+ .maplibregl-user-location-accuracy-circle {
452
+ @apply bg-accent-blue/20!;
453
+ }