@roxyapi/ui 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -122,16 +122,15 @@ Every chart endpoint accepts `timezone` as either a decimal-hour offset (`5.5` f
122
122
 
123
123
  ### 4. Secret key in the browser
124
124
 
125
- There are two key classes. **Secret keys are unprefixed** and grant full access; they belong server-side only (Node, Bun, Hono, Next.js route handlers, Workers, Edge functions). **Publishable keys** are prefixed `pk_live_*` or `pk_test_*` and are safe in the browser; they are locked to an origin allowlist at the API gateway. For widgets, embeds, vanilla HTML, and `data-publishable-key` use the publishable key. For the typed SDK on a server, use the secret key.
125
+ Secret keys (`sk_*`) grant full account access and are server side only. Call `createRoxy(process.env.ROXY_API_KEY!)` on your server (Node, Bun, Hono, Next.js route handlers, Workers, Edge functions), then send the response, not the key, to the component. Never ship a secret key in a client bundle.
126
126
 
127
127
  ```ts
128
- // Server (Next.js route handler, Workers, Bun): secret key
128
+ // Secret key: server side only
129
129
  const roxy = createRoxy(process.env.ROXY_API_KEY!);
130
-
131
- // Browser (widgets auto-mount): publishable key
132
- <div data-roxy-widget="natal-chart" data-publishable-key="pk_live_xxx" ...></div>
133
130
  ```
134
131
 
132
+ For direct client-side calls, use a **publishable key** (`pk_live_*` / `pk_test_*`) instead. Publishable keys are browser-safe: mint one at `roxyapi.com/account`, register the origins you embed on, and the API gateway returns 403 for any other origin. See the client-side pattern below.
133
+
135
134
  ### 5. Missing `'use client'` in Next.js App Router
136
135
 
137
136
  The React components in `@roxyapi/ui-react` mount Custom Elements, which need the DOM. In the App Router, files that import them must declare `'use client'` at the top. Server Components can fetch with the SDK; the client component renders.
@@ -194,30 +193,30 @@ import type { NatalChartResponse } from '@roxyapi/sdk';
194
193
 
195
194
  ### Pattern 1: vanilla HTML, no build step
196
195
 
196
+ Fetch on your server with the secret key, then inline the response into the component as a child `<script type="application/json" class="roxy-data">`. The component reads it on load. No key in the browser.
197
+
197
198
  ```html
198
199
  <script
199
200
  src="https://cdn.jsdelivr.net/npm/@roxyapi/ui@latest/dist/cdn/roxy-ui.js"
200
201
  crossorigin="anonymous"
201
202
  ></script>
202
203
 
203
- <roxy-natal-chart id="chart"></roxy-natal-chart>
204
-
205
- <script type="module">
206
- import { createRoxy } from 'https://cdn.jsdelivr.net/npm/@roxyapi/sdk@latest/dist/factory.js';
207
- const roxy = createRoxy('pk_live_xxx');
208
- const { data } = await roxy.astrology.generateNatalChart({
209
- body: { date: '1990-01-15', time: '14:30:00', latitude: 19.07, longitude: 72.88, timezone: 5.5 },
210
- });
211
- document.getElementById('chart').data = data;
212
- </script>
204
+ <roxy-natal-chart>
205
+ <script type="application/json" class="roxy-data">
206
+ { "planets": [ ... ], "houses": [ ... ], "aspects": [ ... ] }
207
+ </script>
208
+ </roxy-natal-chart>
213
209
  ```
214
210
 
215
- ### Pattern 2: React, with the typed SDK
211
+ Setting the JavaScript `data` property always wins over the inlined JSON, so the same element also drives dynamic pages.
212
+
213
+ ### Pattern 2: React, interactive
214
+
215
+ `<RoxyLocationSearch>` runs in the browser. On select, call your own route, which holds the secret key, and set the returned data on the chart. The key never reaches the client.
216
216
 
217
217
  ```tsx
218
218
  'use client';
219
219
 
220
- import { createRoxy } from '@roxyapi/sdk';
221
220
  import {
222
221
  RoxyNatalChart,
223
222
  RoxyLocationSearch,
@@ -225,18 +224,18 @@ import {
225
224
  } from '@roxyapi/ui-react';
226
225
  import { useState } from 'react';
227
226
 
228
- const roxy = createRoxy(process.env.NEXT_PUBLIC_ROXY_API_KEY!);
229
-
230
227
  export function BirthChartView() {
231
228
  const [chart, setChart] = useState<RoxyNatalChartProps['data']>(undefined);
232
229
 
233
230
  const onLocationSelect = async (e: CustomEvent<{ latitude?: number; longitude?: number; timezone?: number | string }>) => {
234
231
  const { latitude, longitude, timezone } = e.detail;
235
232
  if (latitude == null || longitude == null) return;
236
- const { data } = await roxy.astrology.generateNatalChart({
237
- body: { date: '1990-01-15', time: '14:30:00', latitude, longitude, timezone },
233
+ // Your route calls roxy.astrology.generateNatalChart with the secret key.
234
+ const res = await fetch('/api/natal-chart', {
235
+ method: 'POST',
236
+ body: JSON.stringify({ date: '1990-01-15', time: '14:30:00', latitude, longitude, timezone }),
238
237
  });
239
- setChart(data);
238
+ setChart(await res.json());
240
239
  };
241
240
 
242
241
  return (
@@ -248,9 +247,11 @@ export function BirthChartView() {
248
247
  }
249
248
  ```
250
249
 
250
+ For a static chart with no picker, fetch in a Server Component and pass `data` to a client component (Pattern 6).
251
+
251
252
  ### Pattern 3: schema-driven form
252
253
 
253
- `<roxy-endpoint-form>` reads the OpenAPI spec and renders the inputs for any endpoint. Listen for the `roxy-submit` event with the validated payload.
254
+ `<roxy-endpoint-form>` reads the OpenAPI spec and renders the inputs for any endpoint. On `roxy-submit`, POST the validated values to your own route, which calls the SDK with the secret key, then set the returned data on the target component.
254
255
 
255
256
  ```html
256
257
  <roxy-endpoint-form
@@ -258,41 +259,42 @@ export function BirthChartView() {
258
259
  method="POST"
259
260
  submit-label="Generate kundli"
260
261
  ></roxy-endpoint-form>
262
+ <roxy-vedic-kundli chart-style="south"></roxy-vedic-kundli>
261
263
 
262
264
  <script type="module">
263
- import { createRoxy } from 'https://cdn.jsdelivr.net/npm/@roxyapi/sdk@latest/dist/factory.js';
264
- const roxy = createRoxy('pk_live_xxx');
265
265
  const form = document.querySelector('roxy-endpoint-form');
266
266
  form.addEventListener('roxy-submit', async (e) => {
267
- const { values } = e.detail;
268
- const { data: kundli } = await roxy.vedicAstrology.generateBirthChart({ body: values });
269
- document.querySelector('roxy-vedic-kundli').data = kundli;
267
+ // Your route calls roxy.vedicAstrology.generateBirthChart with the secret key.
268
+ const res = await fetch('/api/kundli', { method: 'POST', body: JSON.stringify(e.detail.values) });
269
+ document.querySelector('roxy-vedic-kundli').data = await res.json();
270
270
  });
271
271
  </script>
272
272
  ```
273
273
 
274
- ### Pattern 4: widgets auto-mount (no JavaScript wiring)
274
+ ### Pattern 4: fully client-side with a publishable key (no server)
275
275
 
276
- Use a publishable key (`pk_live_*` or `pk_test_*`) for client-side embeds. Get one at <https://roxyapi.com/account>. Publishable keys are origin-restricted at the API gateway. Register the customer domain (e.g. `https://customer.com`) when creating the key, and the gateway will reject requests from any other origin. Never use a secret key in client-side code (secret keys are unprefixed and live server-side only).
276
+ When you do not want a backend at all, mint a **publishable key** (`pk_live_*`) at `roxyapi.com/account`, register the origins you embed on, and call RoxyAPI directly from the browser. The publishable key is safe to ship in client code: it is origin-restricted (any other origin gets 403) and cannot read your account. `<roxy-location-search>` accepts the key via its `publishable-key` attribute and fetches geocoding itself; for the data endpoints you make a normal browser `fetch` with the key in the `X-API-Key` header, then assign the response to the rendering component.
277
277
 
278
278
  ```html
279
- <script
280
- src="https://cdn.jsdelivr.net/npm/@roxyapi/ui@latest/dist/cdn/widgets.js"
281
- defer
282
- ></script>
279
+ <roxy-location-search publishable-key="pk_live_..."></roxy-location-search>
280
+ <roxy-natal-chart></roxy-natal-chart>
283
281
 
284
- <div
285
- data-roxy-widget="natal-chart"
286
- data-publishable-key="pk_live_xxx"
287
- data-date="1990-01-15"
288
- data-time="14:30:00"
289
- data-latitude="19.07"
290
- data-longitude="72.88"
291
- data-timezone="5.5"
292
- ></div>
282
+ <script type="module">
283
+ const chart = document.querySelector('roxy-natal-chart');
284
+ document.querySelector('roxy-location-search').addEventListener('roxy-location-select', async (e) => {
285
+ const { latitude, longitude, timezone } = e.detail;
286
+ if (latitude == null || longitude == null) return;
287
+ const res = await fetch('https://roxyapi.com/api/v2/astrology/natal-chart', {
288
+ method: 'POST',
289
+ headers: { 'X-API-Key': 'pk_live_...', 'Content-Type': 'application/json' },
290
+ body: JSON.stringify({ date: '1990-01-15', time: '14:30:00', latitude, longitude, timezone }),
291
+ });
292
+ chart.data = await res.json(); // pass the unwrapped response, not an envelope
293
+ });
294
+ </script>
293
295
  ```
294
296
 
295
- The auto-mount script reads `data-*` attributes, calls the matching endpoint, and renders the matching component.
297
+ Rendering components do not fetch on their own; you set `data`. Only `<roxy-location-search>` (and `<roxy-endpoint-form>` for the spec) fetch internally. A zero-wiring `data-*` auto-mount that fetches and renders with no script is still on the roadmap.
296
298
 
297
299
  ### Pattern 5: MCP tool-call response
298
300
 
@@ -360,13 +362,13 @@ This is how the WordPress plugin renders: PHP fetches the response server-side,
360
362
 
361
363
  ## Theming and dark mode
362
364
 
363
- Components react to three signals in priority order. No events to dispatch. No JS bridge to write.
365
+ Components react to three signals in priority order. No events to dispatch. No JS bridge to write. The CDN bundle (`dist/cdn/roxy-ui.js`) auto-loads the design tokens, so a single script tag yields full theming and dark mode with nothing else to add. The npm and React paths inherit the same tokens through the components; only set up `tokens.css` yourself if you import per component without the full bundle.
364
366
 
365
367
  | Signal | Where | Effect |
366
368
  |---|---|---|
367
369
  | `prefers-color-scheme: dark` | OS | Default. Follows user system setting. |
368
370
  | `data-theme="light"` or `data-theme="dark"` | `<html>` / `<body>` / any ancestor / the component itself | Wins over OS. Per-element override scope works. |
369
- | `.dark` class | Any ancestor | Equivalent to `data-theme="dark"`. Use when the host stack already ships a `.dark` toggle (Tailwind, shadcn). |
371
+ | `.dark` class | The component itself or any ancestor (typically `<html>`) | Same effect as `data-theme="dark"`. Use when the host stack already ships a `.dark` toggle (Tailwind, shadcn). |
370
372
 
371
373
  To toggle at runtime:
372
374
 
@@ -396,7 +398,7 @@ Every visible aspect of the chart is driven by `--roxy-*` CSS custom properties
396
398
 
397
399
  ## Domain ordering
398
400
 
399
- When listing domains in user-visible copy, use the canonical order: Western astrology, Vedic astrology, numerology, tarot, biorhythm, I Ching, crystals, dreams, angel numbers. Location is utility, not a selling domain.
401
+ When listing domains in user-visible copy, use the canonical order: Western astrology, Vedic astrology, numerology, tarot, human design, forecast, biorhythm, I Ching, crystals, dreams, angel numbers. Location is utility, not a selling domain.
400
402
 
401
403
  ## What not to ship
402
404
 
package/README.md CHANGED
@@ -55,7 +55,7 @@ Light, dark, your brand. Override one CSS variable and every component updates.
55
55
  ```css
56
56
  :root {
57
57
  /* Surface */
58
- --roxy-bg: #fafafa;
58
+ --roxy-bg: #ffffff;
59
59
  --roxy-fg: #0a0a0a;
60
60
  --roxy-muted: #71717a;
61
61
  --roxy-border: #e4e4e7;
@@ -66,13 +66,13 @@ Light, dark, your brand. Override one CSS variable and every component updates.
66
66
 
67
67
  /* Status (each has a -fg variant for WCAG-AA text contrast) */
68
68
  --roxy-success: #16a34a;
69
- --roxy-warning: #f59e0b;
69
+ --roxy-warning: #ea580c;
70
70
  --roxy-danger: #dc2626;
71
- --roxy-info: #2563eb;
71
+ --roxy-info: #0284c7;
72
72
 
73
73
  /* Shape + motion */
74
- --roxy-radius-md: 12px;
75
- --roxy-shadow-md: 0 4px 12px rgba(0,0,0,0.08);
74
+ --roxy-radius-md: 8px;
75
+ --roxy-shadow-md: 0 4px 6px -1px rgba(0,0,0,0.08), 0 2px 4px -2px rgba(0,0,0,0.06);
76
76
  --roxy-motion-duration: 200ms; /* 0ms when prefers-reduced-motion */
77
77
  }
78
78
 
@@ -221,42 +221,24 @@ Tables, cards, forms, and helper components in the [live demo](https://roxyapi.g
221
221
 
222
222
  ## Start with one component
223
223
 
224
- Vanilla HTML. No build step. Replace `YOUR_API_KEY` with a publishable key from <https://roxyapi.com/account>.
224
+ Fetch with the typed SDK, pass `data` to the component. No glue code.
225
225
 
226
- ```html
227
- <script
228
- src="https://cdn.jsdelivr.net/npm/@roxyapi/ui@latest/dist/cdn/roxy-ui.js"
229
- crossorigin="anonymous"
230
- defer
231
- ></script>
232
- <roxy-natal-chart id="chart"></roxy-natal-chart>
233
- <script type="module">
234
- import { createRoxy } from 'https://cdn.jsdelivr.net/npm/@roxyapi/sdk@latest/dist/factory.js';
235
- const roxy = createRoxy('YOUR_API_KEY');
236
- const { data } = await roxy.astrology.generateNatalChart({
237
- body: { date: '1990-01-15', time: '14:30:00', latitude: 19.07, longitude: 72.88, timezone: 5.5 },
238
- });
239
- document.getElementById('chart').data = data;
240
- </script>
241
- ```
226
+ ```tsx
227
+ import { createRoxy } from '@roxyapi/sdk';
228
+ import { RoxyHoroscopeCard } from '@roxyapi/ui-react';
242
229
 
243
- > **Unwrap `data` before passing to the component.** The SDK returns `{ data, error, request, response }`. Pass the envelope and the chart renders `[object Object]`. This is the most common integration bug.
230
+ const roxy = createRoxy(process.env.ROXY_API_KEY!);
244
231
 
245
- Want a Vedic kundli instead? Same shape, different SDK method:
232
+ const { data } = await roxy.astrology.getDailyHoroscope({ path: { sign: 'aries' } });
246
233
 
247
- ```html
248
- <roxy-vedic-kundli id="kundli" chart-style="south"></roxy-vedic-kundli>
249
- <script type="module">
250
- import { createRoxy } from 'https://cdn.jsdelivr.net/npm/@roxyapi/sdk@latest/dist/factory.js';
251
- const roxy = createRoxy('YOUR_API_KEY');
252
- const { data } = await roxy.vedicAstrology.generateBirthChart({
253
- body: { date: '1990-01-15', time: '14:30:00', latitude: 19.07, longitude: 72.88, timezone: 5.5 },
254
- });
255
- document.getElementById('kundli').data = data;
256
- </script>
234
+ return <RoxyHoroscopeCard data={data} />;
257
235
  ```
258
236
 
259
- In production, geocode the user's city with `<roxy-location-search>` (see [Quick start](#quick-start)) instead of hardcoding coordinates.
237
+ Then expand into natal charts, kundli, dasha, tarot, and every other domain. The SDK returns `data`, the component renders it; the same pairing holds for all 32 components.
238
+
239
+ > **Pass `data`, not the envelope.** The SDK returns `{ data, error, request, response }`. Pass `data`, or the component renders `[object Object]`. This is the most common integration bug.
240
+
241
+ The key stays on your server. Vanilla HTML or a server-rendered page fetches the same way, then [inlines the JSON into the component](#server-rendered-no-javascript-wiring): no build step, no key in the browser. Try every component in the [live demo](https://roxyapi.github.io/ui/), each with Preview, Code, and shadcn tabs and a live color customizer.
260
242
 
261
243
  ## Install
262
244
 
@@ -312,7 +294,12 @@ Always call `/location/search` first. Every chart endpoint expects latitude, lon
312
294
 
313
295
  Server-rendered and cached pages (WordPress, JSX SSR, static HTML) cannot always run JavaScript to set the `data` property per element. Render the response into a child `<script type="application/json" class="roxy-data">` on the server instead. The component reads it on load. No per-element script, no API key in the browser.
314
296
 
297
+ Load the bundle once anywhere on the page. It registers every `roxy-*` element and loads the design tokens, so every component on the page renders themed, in light or dark, from that single tag. Nothing else to add.
298
+
315
299
  ```html
300
+ <!-- Once per page: defines every roxy-* element -->
301
+ <script src="https://cdn.jsdelivr.net/npm/@roxyapi/ui@latest/dist/cdn/roxy-ui.js" crossorigin="anonymous" defer></script>
302
+
316
303
  <roxy-natal-chart>
317
304
  <script type="application/json" class="roxy-data">
318
305
  { "planets": [ ... ], "houses": [ ... ], "aspects": [ ... ] }
@@ -464,7 +451,48 @@ const { data: cc } = await roxy.tarot.castCelticCross({
464
451
  <RoxyTarotSpread data={cc} />
465
452
  ```
466
453
 
467
- ### 5. Biorhythm (daily, forecast)
454
+ ### 5. Human Design (bodygraph)
455
+
456
+ The breakout 2026 self-knowledge category, computed from the same ephemeris as Western astrology plus the I Ching gate wheel and chakra-style centers. Self-discovery apps, dating and compatibility products, and AI coaching bots ship the full bodygraph first. No coordinates needed; Human Design uses the birth instant, not the observer location.
457
+
458
+ ```tsx
459
+ import { RoxyBodygraph } from '@roxyapi/ui-react';
460
+
461
+ // Full bodygraph. The head term every Human Design app leads with ("human design chart").
462
+ // Type, strategy, authority, profile, the nine centers, channels, and every gate
463
+ // activation in one call. Pass the birth instant only, no latitude or longitude.
464
+ const { data: bodygraph } = await roxy.humanDesign.generateBodygraph({
465
+ body: { date: '1990-01-15', time: '14:30:00', timezone: 5.5 },
466
+ });
467
+ <RoxyBodygraph data={bodygraph} />
468
+ ```
469
+
470
+ ### 6. Forecast (transits, cross-domain timeline)
471
+
472
+ The first cross-domain, stateless forecast in the catalog: one call merges Western transits, Vedic Vimshottari dasha boundaries, and biorhythm critical days into a single significance-scored, time-ordered timeline. Forecast feeds, transit alerts, and timing tools are the buyers. Acquire on the high-volume `astrology transits` search, convert on the cross-domain timeline no competitor ships. No coordinates needed.
473
+
474
+ ```tsx
475
+ import { RoxyForecastTimeline } from '@roxyapi/ui-react';
476
+
477
+ // Transit forecast. The demand leader. Western transit-to-natal aspects, sign
478
+ // ingresses, and retrograde stations over the window.
479
+ const { data: transits } = await roxy.forecast.forecastTransits({
480
+ body: { birthData: { date: '1990-01-15', time: '14:30:00', timezone: 5.5 } },
481
+ });
482
+ <RoxyForecastTimeline data={transits} />
483
+
484
+ // Cross-domain timeline. The same window merged with Vedic dasha boundaries and
485
+ // biorhythm critical days into one significance-scored timeline.
486
+ const { data: timeline } = await roxy.forecast.generateTimeline({
487
+ body: {
488
+ birthData: { date: '1990-01-15', time: '14:30:00', timezone: 5.5 },
489
+ domains: ['western', 'vedic', 'biorhythm'],
490
+ },
491
+ });
492
+ <RoxyForecastTimeline data={timeline} />
493
+ ```
494
+
495
+ ### 7. Biorhythm (daily, forecast)
468
496
 
469
497
  Zero competition domain. Steady search volume with the top Google result being a static calculator page. Pure land-grab for wellness, productivity, sports, and couples apps.
470
498
 
@@ -485,7 +513,7 @@ const { data: forecast } = await roxy.biorhythm.getForecast({
485
513
  <RoxyBiorhythmChart data={forecast} mode="forecast" />
486
514
  ```
487
515
 
488
- ### 6. I Ching (cast a reading, hexagram lookup)
516
+ ### 8. I Ching (cast a reading, hexagram lookup)
489
517
 
490
518
  Meditation apps, decision-making tools, and wisdom chatbots. `i ching API` and `hexagram API` are the keywords.
491
519
 
@@ -505,12 +533,11 @@ const { data: random } = await roxy.iching.getRandomHexagram();
505
533
 
506
534
  ## API keys
507
535
 
508
- Get keys at <https://roxyapi.com/account>.
536
+ Get a key at <https://roxyapi.com/account>.
509
537
 
510
- - **Secret key** (server-side only). Use in Node, Bun, Hono, Next.js route handlers, Workers. Never commit, never ship in client bundles.
511
- - **Publishable key** (`pk_live_*` / `pk_test_*`). Safe in browsers, locked to the origins you register on the key. Use with the widgets auto-mount script for WordPress, Shopify, static HTML, embed scenarios. The API gateway rejects requests from any origin not on the allowlist.
538
+ Two key types. **Secret keys** (`sk_*`) grant full account access: use them server side only (Node, Bun, Hono, Next.js route handlers, Workers). Never commit one, never ship one in a client bundle. **Publishable keys** (`pk_live_*` / `pk_test_*`) are browser-safe: mint one, register the origins you embed on, and any other origin gets a 403 at the gateway. Use a publishable key when you call RoxyAPI directly from the browser with no backend.
512
539
 
513
- For the SDK examples on this page, set `ROXY_API_KEY` to a secret key in your server env. For the widgets auto-mount path (`data-publishable-key="pk_live_xxx"`), use a publishable key with your domain registered on it.
540
+ Set `ROXY_API_KEY` to your secret key in your server env for the server-side SDK examples on this page. For direct client-side embedding with no backend, use a publishable key (see the fully client-side pattern in [`AGENTS.md`](AGENTS.md)).
514
541
 
515
542
  ## Distribution
516
543
 
@@ -520,7 +547,7 @@ For the SDK examples on this page, set `ROXY_API_KEY` to a secret key in your se
520
547
  | npm `@roxyapi/ui-react` | `npmjs.com/package/@roxyapi/ui-react` |
521
548
  | jsDelivr CDN (full bundle) | `cdn.jsdelivr.net/npm/@roxyapi/ui@latest/dist/cdn/roxy-ui.js` |
522
549
  | jsDelivr CDN (per component) | `cdn.jsdelivr.net/npm/@roxyapi/ui@latest/dist/cdn/components/{name}.js` |
523
- | Widgets auto-mount | `cdn.jsdelivr.net/npm/@roxyapi/ui@latest/dist/cdn/widgets.js` |
550
+ | Widgets auto-mount (with browser keys, coming soon) | `cdn.jsdelivr.net/npm/@roxyapi/ui@latest/dist/cdn/widgets.js` |
524
551
  | shadcn registry | `npx shadcn@latest add https://cdn.jsdelivr.net/gh/RoxyAPI/ui@latest/registry/{name}.json` |
525
552
 
526
553
  ## Components
@@ -574,7 +601,7 @@ For the SDK examples on this page, set `ROXY_API_KEY` to a secret key in your se
574
601
 
575
602
  ## Theming
576
603
 
577
- Every component reads from `--roxy-*` CSS custom properties. Override globally on `:root` or per element. Light + dark defaults, container queries for responsive layouts at 320px and up. See [THEMING.md](https://github.com/RoxyAPI/ui/blob/main/packages/ui/THEMING.md) for the full token reference.
604
+ Every component reads from `--roxy-*` CSS custom properties. Override globally on `:root` or per element. Light + dark defaults, container queries for responsive layouts at 320px and up. The CDN bundle auto-loads these tokens; your `:root { --roxy-* }` overrides always win over the defaults. See [THEMING.md](https://github.com/RoxyAPI/ui/blob/main/packages/ui/THEMING.md) for the full token reference.
578
605
 
579
606
  ```css
580
607
  :root {
@@ -657,7 +684,7 @@ Persist the choice in `localStorage` from your own code; the components do not o
657
684
  <details>
658
685
  <summary><strong>How big is each component? What is the bundle cost?</strong></summary>
659
686
 
660
- Per-component bundles run 6-10 KB gzipped, capped at 30 KB by CI. The full bundle (every component, helpers, base styles) stays well under the 150 KB CI cap, around 45 KB gzipped today. The React package loads the runtime on mount, so a route that renders one chart pays for one component, not the whole catalog. Pin a concrete version in production for byte-stable cache hits.
687
+ Per-component bundles run 6-10 KB gzipped, capped at 30 KB by CI. The full bundle (every component, helpers, base styles, and the inlined design tokens) stays well under the 150 KB CI cap, around 54 KB gzipped today. The React package loads the runtime on mount, so a route that renders one chart pays for one component, not the whole catalog. Pin a concrete version in production for byte-stable cache hits.
661
688
  </details>
662
689
 
663
690
  <details>
@@ -770,7 +797,7 @@ Components ship in Shadow DOM for style isolation; Tailwind utilities are scoped
770
797
  <details>
771
798
  <summary><strong>What is the security model for API keys?</strong></summary>
772
799
 
773
- Two key classes. Secret keys (unprefixed) live server-side only and grant full access. Publishable keys (`pk_live_*` / `pk_test_*`) are browser-safe and locked to an origin allowlist registered on the key. The API gateway rejects requests from any other origin and counts the failed attempt against the rate limit, so a stolen key cannot be brute-fired from elsewhere.
800
+ Two key types. Secret keys (`sk_*`) live server side only and grant full access, so never ship one in a client bundle: fetch on your server and pass the rendered response, not the key, to the browser. Publishable keys (`pk_live_*` / `pk_test_*`) are browser-safe for direct client-side embedding: they carry an origin allowlist, so a key leaked to any other origin returns 403 instead of working. Mint either at `roxyapi.com/account`.
774
801
 
775
802
  For CSP, allow `script-src https://cdn.jsdelivr.net` if loading the bundle from the CDN. Subresource Integrity hashes are available via the jsDelivr SRI API for any pinned version.
776
803
  </details>
@@ -781,11 +808,11 @@ For CSP, allow `script-src https://cdn.jsdelivr.net` if loading the bundle from
781
808
  Semver. Pre-1.0, minor bumps may include breaking changes (we will note them in the changelog). Patch bumps are always backwards-compatible. Pin a concrete version in production code:
782
809
 
783
810
  ```bash
784
- npm install @roxyapi/ui@0.1.x
811
+ npm install @roxyapi/ui@0.8.x
785
812
  ```
786
813
 
787
814
  ```html
788
- <script src="https://cdn.jsdelivr.net/npm/@roxyapi/ui@0.1.5/dist/cdn/roxy-ui.js"></script>
815
+ <script src="https://cdn.jsdelivr.net/npm/@roxyapi/ui@0.8.0/dist/cdn/roxy-ui.js"></script>
789
816
  ```
790
817
 
791
818
  The `@latest` URL on this page is for paste-friendly marketing; production code should pin.
package/THEMING.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Theming Roxy UI
2
2
 
3
- Every Roxy UI component reads its colors, fonts, spacing, and motion from a single set of CSS custom properties on `:host`. Override them at `:root` to brand the whole library, or scope to one element to skin a single component.
3
+ Every Roxy UI component reads its colors, fonts, spacing, and motion from a single set of `--roxy-*` CSS custom properties. Override them at `:root` to brand the whole library, or scope to one element to skin a single component. Custom properties inherit through the Shadow DOM boundary, so a value set on `:root` or any light-DOM ancestor reaches every component. The CDN bundle auto-loads the token defaults; your `:root` overrides always win over them.
4
4
 
5
5
  ## Token reference
6
6
 
@@ -99,18 +99,20 @@ roxy-natal-chart {
99
99
 
100
100
  ### Dark mode
101
101
 
102
- Three opt-in mechanisms work out of the box.
102
+ Three opt-in mechanisms work out of the box. The CDN bundle auto-loads the tokens, so all three work from one script tag; on the npm path the full `@roxyapi/ui` import pulls the same tokens in.
103
103
 
104
104
  ```css
105
105
  /* System preference: nothing to do */
106
106
 
107
- /* data-theme on the document */
107
+ /* data-theme on the document, an ancestor, or the element itself */
108
108
  :root[data-theme='dark'] { /* automatic */ }
109
109
 
110
- /* Tailwind dark class on an ancestor */
111
- .dark roxy-natal-chart { /* automatic */ }
110
+ /* Tailwind dark class on the document, an ancestor, or the element itself */
111
+ .dark { /* automatic */ }
112
112
  ```
113
113
 
114
+ Tokens set on the `:root` / `.dark` / `[data-theme]` light-DOM element inherit through the shadow boundary into every component, so a `.dark` class anywhere above a component themes it. Per-element scope works too: `<roxy-natal-chart data-theme="dark">` runs one chart in dark on an otherwise light page.
115
+
114
116
  ### Map Tailwind tokens
115
117
 
116
118
  Tailwind users can map our tokens to theirs in five lines of `globals.css`. Pick the syntax that matches your Tailwind version.