@openephemeris/mcp-server 3.20.0 → 3.21.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/CHANGELOG.md +25 -0
- package/dist/analytics.d.ts +3 -0
- package/dist/analytics.js +44 -0
- package/dist/backend/client.js +17 -8
- package/dist/oauth/dcr.js +25 -0
- package/dist/oauth/rate-limit.js +10 -11
- package/dist/prompts.js +7 -5
- package/dist/server-sse.js +35 -26
- package/dist/tools/apps/bi-wheel-app.js +3 -1
- package/dist/tools/apps/bodygraph-app.js +6 -3
- package/dist/tools/apps/chart-wheel-app.js +4 -2
- package/dist/tools/apps/location-tools.js +2 -2
- package/dist/tools/apps/moon-phase-app.js +1 -0
- package/dist/tools/auth.js +6 -1
- package/dist/tools/index.d.ts +11 -2
- package/dist/tools/index.js +5 -2
- package/dist/tools/specialized/ephemeris_extended.js +4 -2
- package/dist/tools/specialized/natal.js +2 -1
- package/dist/ui/bi-wheel.html +11 -9
- package/dist/ui/bodygraph.html +4 -4
- package/dist/ui/chart-wheel.html +3 -3
- package/dist/ui/moon-phase.html +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,31 @@ Version numbering follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [3.21.0] — 2026-07-09
|
|
11
|
+
|
|
12
|
+
Launch-readiness wave from the three-perspective master review (directory vetting, first-run UX, revenue red-team).
|
|
13
|
+
|
|
14
|
+
### Security (directory-listing conditions)
|
|
15
|
+
- **`auth_*` device tools removed from the HTTP transport.** They mutate process-global state (shared credential file, env inspection) — on the multi-tenant remote server one user's `auth_logout` cleared shared credentials and `auth_status` could report the process service-key config to any tenant. Now `stdioOnly` and hidden/blocked on `/mcp`; unchanged for local stdio installs.
|
|
16
|
+
- **Rate limiters no longer trust `X-Forwarded-For`** (leftmost value is attacker-controlled and let callers rotate fake IPs past every throttle). Client IP now comes from Fly's trusted `Fly-Client-IP` hop, with a spoof-resistance test.
|
|
17
|
+
- **DCR redirect hardening:** `redirect_uris` must be https or http loopback (RFC 8252) — non-conforming registrations are rejected.
|
|
18
|
+
|
|
19
|
+
### Conversion & instrumentation
|
|
20
|
+
- **PostHog events from the MCP server** (`src/analytics.ts`, fire-and-forget, no-ops without `POSTHOG_API_KEY`): `mcp_session_init` (auth type), `mcp_tool_call` (tool, duration), `mcp_tool_error` (tool, HTTP status — makes 402 paywall hits and 429s countable). Identity is a SHA-256 prefix of the credential, never the raw key; all events tagged `source: mcp`.
|
|
21
|
+
- **402 paywall fixed:** paid tiers are no longer told to "enable overages" (overage billing is switched off) — they get the next tier with exact price and a `/pricing?plan=…` deep link; free-tier 402s deep-link to the wallet top-up and `/pricing?plan=developer`.
|
|
22
|
+
- **Credit costs disclosed** on the five app tools that lacked them (verified against `usage_meter.go`); `ephemeris_natal_batch` now states its Startup-tier gating and warns that N subjects = N credits.
|
|
23
|
+
- **Return hooks:** `current_sky_snapshot` and `transit_forecast` prompts now end with when to check back (next Moon sign change / next exact transit).
|
|
24
|
+
|
|
25
|
+
### First-run UX
|
|
26
|
+
- **Routing:** `explore_natal_chart` is now explicitly the PRIMARY tool for chart requests; `ephemeris_natal_chart` marks itself raw-JSON and defers to it; the `natal_chart_reading` prompt and the astrology-mcp landing page now point at the interactive explorers.
|
|
27
|
+
- **Welcome rewritten** from a 12-bullet brochure to a two-option hook (instant no-birth-data sky snapshot, or an interactive birth chart); server `instructions` now steer the model to `explore_*` tools for anything visual.
|
|
28
|
+
- **Loader copy de-jargoned** ("Computing ephemeris…" → "Mapping the sky at your birth moment…" etc.); every terminal iframe error now offers "Tap to try again" wired to a real retry; `auth_login` leads with "free tier, no credit card needed"; expired-OAuth copy tells users exactly where to reconnect.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
- **Local visual harness (`npm run harness`) was broken** by an orphaned vite process squatting port 5180: `visual-check.mjs` now kills the full process tree on Windows (`taskkill /T`), and the harness uses `strictPort` so a squatted port fails loudly instead of silently drifting. Harness index/fixtures cleaned of the deleted dormant apps; the dead bodygraph fixture was rebuilt with the real Go-SVG contract — which exposed and fixed two genuine a11y bugs (`role="img"` on an SVG containing buttons; hardcoded low-contrast gold on action buttons).
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
10
35
|
## [3.20.0] — 2026-07-09
|
|
11
36
|
|
|
12
37
|
Second wave of the transport/iframe review: legacy transport retirement, resumability, and remaining hardening/quality items.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* analytics.ts — fire-and-forget PostHog events for the remote MCP server.
|
|
3
|
+
*
|
|
4
|
+
* The launch was previously unmeasurable from the MCP side: no way to see how
|
|
5
|
+
* many sessions initialized, which tools drove usage, or how many users hit
|
|
6
|
+
* the 402 paywall. These events close that gap. Everything is best-effort:
|
|
7
|
+
* analytics must never slow down or fail a tool call.
|
|
8
|
+
*
|
|
9
|
+
* Configuration (Fly secrets / env):
|
|
10
|
+
* POSTHOG_API_KEY — the project API key (phc_...); absent → all no-ops.
|
|
11
|
+
* POSTHOG_HOST — capture host, default https://us.i.posthog.com.
|
|
12
|
+
*
|
|
13
|
+
* Identity: we never send raw API keys or JWTs. The distinct_id is a SHA-256
|
|
14
|
+
* prefix of the credential, stable per user across sessions but useless for
|
|
15
|
+
* authentication.
|
|
16
|
+
*/
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
18
|
+
const POSTHOG_HOST = process.env.POSTHOG_HOST || "https://us.i.posthog.com";
|
|
19
|
+
function apiKey() {
|
|
20
|
+
return process.env.POSTHOG_API_KEY || undefined;
|
|
21
|
+
}
|
|
22
|
+
/** Stable, non-reversible identity from a credential. */
|
|
23
|
+
export function distinctIdFor(credential) {
|
|
24
|
+
if (!credential)
|
|
25
|
+
return "anonymous";
|
|
26
|
+
return "mcp_" + createHash("sha256").update(credential).digest("hex").slice(0, 24);
|
|
27
|
+
}
|
|
28
|
+
export function captureEvent(event, distinctId, properties = {}) {
|
|
29
|
+
const key = apiKey();
|
|
30
|
+
if (!key)
|
|
31
|
+
return;
|
|
32
|
+
// Fire-and-forget: no await, swallow every failure.
|
|
33
|
+
fetch(`${POSTHOG_HOST}/capture/`, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: { "Content-Type": "application/json" },
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
api_key: key,
|
|
38
|
+
event,
|
|
39
|
+
distinct_id: distinctId,
|
|
40
|
+
properties: { source: "mcp", ...properties },
|
|
41
|
+
timestamp: new Date().toISOString(),
|
|
42
|
+
}),
|
|
43
|
+
}).catch(() => { });
|
|
44
|
+
}
|
package/dist/backend/client.js
CHANGED
|
@@ -266,25 +266,33 @@ export class BackendClient {
|
|
|
266
266
|
}
|
|
267
267
|
return new BackendError(`Authentication required: ${msg}. Run auth.login to connect your account, ` +
|
|
268
268
|
`or set OPENEPHEMERIS_API_KEY in your MCP server config. If you connected ` +
|
|
269
|
-
`through Claude's connector settings,
|
|
270
|
-
`OpenEphemeris
|
|
269
|
+
`through Claude's connector settings, your session expired: in Claude's connector ` +
|
|
270
|
+
`settings, toggle OpenEphemeris off and on to reconnect — this only takes a moment.`, 401, "auth_required", false, LOGIN_SIGNUP_URL);
|
|
271
271
|
}
|
|
272
272
|
if (status === 402) {
|
|
273
273
|
// PRICING SSOT: apps/web/config/billing-plans.ts — Explorer grant
|
|
274
274
|
// (EXPLORER.creditsNumeric = 150, one-time), Pro/`developer`
|
|
275
275
|
// (monthlyPrice = 29, credits = 75,000), and CREDIT_TOPUPS
|
|
276
|
-
// ($5→150, $10→350, $20→900)
|
|
277
|
-
//
|
|
276
|
+
// ($5→150, $10→350, $20→900), Startup ($79, 500k), Scale ($199, 2M).
|
|
277
|
+
// Overage billing is OFF (OVERAGE_BILLING_AVAILABLE=false) — never
|
|
278
|
+
// suggest enabling overages; suggest the next tier instead. Keep every
|
|
279
|
+
// number below in sync with that file; do NOT hand-edit independently.
|
|
278
280
|
const dataObj = data && typeof data === "object" ? data : {};
|
|
279
|
-
const walletUrl = dataObj.upgrade?.wallets_url || "https://openephemeris.com/wallet";
|
|
281
|
+
const walletUrl = dataObj.upgrade?.wallets_url || "https://openephemeris.com/dashboard?tab=wallet";
|
|
280
282
|
const currentTier = dataObj.upgrade?.current_tier || dataObj.tier || "explorer";
|
|
283
|
+
// Next-tier upgrade path (SSOT: BILLING_PLANS order in billing-plans.ts).
|
|
284
|
+
const NEXT_TIER = {
|
|
285
|
+
developer: { plan: "startup", label: "Startup ($79/mo, 500,000 credits)" },
|
|
286
|
+
startup: { plan: "scale", label: "Scale ($199/mo, 2,000,000 credits)" },
|
|
287
|
+
scale: { plan: "enterprise", label: "Enterprise (custom volume)" },
|
|
288
|
+
};
|
|
281
289
|
let upsellMsg = "";
|
|
282
290
|
if (currentTier === 'explorer') {
|
|
283
291
|
upsellMsg =
|
|
284
292
|
`Let the user know warmly: Their 150 free credits (one-time grant) are used up. ` +
|
|
285
293
|
`They can top up their wallet starting at $5 for 150 credits at ` +
|
|
286
294
|
`${walletUrl} — or upgrade to Pro ($29/mo) ` +
|
|
287
|
-
`for 75,000 credits/month at ${UPGRADE_URL}`;
|
|
295
|
+
`for 75,000 credits/month at ${UPGRADE_URL}?plan=developer`;
|
|
288
296
|
}
|
|
289
297
|
else if (currentTier === 'payg') {
|
|
290
298
|
upsellMsg =
|
|
@@ -293,9 +301,10 @@ export class BackendClient {
|
|
|
293
301
|
`$10 for 350, or $20 for 900.`;
|
|
294
302
|
}
|
|
295
303
|
else {
|
|
304
|
+
const next = NEXT_TIER[currentTier] || NEXT_TIER.developer;
|
|
296
305
|
upsellMsg =
|
|
297
|
-
`Let the user know warmly: Their monthly credits are exhausted. They can
|
|
298
|
-
|
|
306
|
+
`Let the user know warmly: Their monthly credits are exhausted. They can upgrade to ` +
|
|
307
|
+
`${next.label} at ${UPGRADE_URL}?plan=${next.plan}`;
|
|
299
308
|
}
|
|
300
309
|
return new BackendError(`⚠️ CREDIT LIMIT REACHED — DO NOT FABRICATE DATA.\n\n` +
|
|
301
310
|
`${msg}\n\n` +
|
package/dist/oauth/dcr.js
CHANGED
|
@@ -32,6 +32,31 @@ oauthDcrRouter.post("/oauth/register", (req, res) => {
|
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
34
|
const redirect_uris = body.redirect_uris;
|
|
35
|
+
// OAuth 2.1 hardening: public clients may only register https redirect
|
|
36
|
+
// targets or http loopback addresses (RFC 8252) — reject anything else so a
|
|
37
|
+
// malicious registration can't point authorization codes at http or
|
|
38
|
+
// custom-scheme interceptors.
|
|
39
|
+
const isAllowedRedirect = (uri) => {
|
|
40
|
+
try {
|
|
41
|
+
const u = new URL(uri);
|
|
42
|
+
if (u.protocol === "https:")
|
|
43
|
+
return true;
|
|
44
|
+
if (u.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(u.hostname))
|
|
45
|
+
return true;
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const badUri = redirect_uris.find((u) => typeof u !== "string" || !isAllowedRedirect(u));
|
|
53
|
+
if (badUri !== undefined) {
|
|
54
|
+
res.status(400).json({
|
|
55
|
+
error: "invalid_redirect_uri",
|
|
56
|
+
error_description: `redirect_uris must be https URLs or http loopback addresses (got: ${String(badUri)}).`,
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
35
60
|
const client_name = body.client_name || "Unknown Client";
|
|
36
61
|
// Deterministic client_id: SHA-256 of sorted redirect URIs
|
|
37
62
|
const uriFingerprint = [...redirect_uris].sort().join("\n");
|
package/dist/oauth/rate-limit.js
CHANGED
|
@@ -26,19 +26,18 @@ export function createRateLimiter(options) {
|
|
|
26
26
|
if (cleanupTimer.unref)
|
|
27
27
|
cleanupTimer.unref();
|
|
28
28
|
return (req, res, next) => {
|
|
29
|
-
// Determine client IP
|
|
29
|
+
// Determine client IP. The leftmost X-Forwarded-For value is
|
|
30
|
+
// attacker-controlled (a client can prepend anything), which would let a
|
|
31
|
+
// caller rotate fake IPs to bypass every limiter. Fly's proxy sets
|
|
32
|
+
// Fly-Client-IP to the true peer address — trust only that; otherwise
|
|
33
|
+
// fall back to the socket address.
|
|
30
34
|
let ip;
|
|
31
35
|
if (trustProxy) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
ip = forwarded[0].split(",")[0].trim();
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
ip = req.ip || req.socket.remoteAddress || "unknown";
|
|
41
|
-
}
|
|
36
|
+
const flyClientIp = req.headers["fly-client-ip"];
|
|
37
|
+
ip =
|
|
38
|
+
(typeof flyClientIp === "string" && flyClientIp.trim()) ||
|
|
39
|
+
req.socket.remoteAddress ||
|
|
40
|
+
"unknown";
|
|
42
41
|
}
|
|
43
42
|
else {
|
|
44
43
|
ip = req.ip || req.socket.remoteAddress || "unknown";
|
package/dist/prompts.js
CHANGED
|
@@ -76,13 +76,11 @@ export const PROMPTS = [
|
|
|
76
76
|
"- Format: pass `datetime` as local ISO 8601 (e.g. `1990-04-15T14:30:00`) " +
|
|
77
77
|
"and `timezone` as the IANA name returned by the lookup. Do NOT append Z to a local birth time.\n\n" +
|
|
78
78
|
"## Step 3 — Tool Call\n" +
|
|
79
|
-
"Make a single call to `
|
|
79
|
+
"Make a single call to `explore_natal_chart` (the interactive chart wheel) with:\n" +
|
|
80
80
|
"- `datetime`: local ISO 8601 (no Z)\n" +
|
|
81
81
|
"- `timezone`: IANA name (e.g. `America/Chicago`)\n" +
|
|
82
82
|
"- `latitude` / `longitude`: decimal coordinates\n" +
|
|
83
|
-
"- `house_system`: user's choice or `'placidus'`\n" +
|
|
84
|
-
"- `format`: `'llm'`\n" +
|
|
85
|
-
"- `include_visual`: `true` (renders the interactive chart wheel)\n\n" +
|
|
83
|
+
"- `house_system`: user's choice or `'placidus'`\n\n" +
|
|
86
84
|
"Deliver the full reading from this single response. Do not call additional tools before presenting the reading.\n\n" +
|
|
87
85
|
"## Step 4 — Structure the Reading\n" +
|
|
88
86
|
"Deliver the interpretation in this sequence:\n\n" +
|
|
@@ -180,6 +178,8 @@ export const PROMPTS = [
|
|
|
180
178
|
"**Tone**: Empowering but honest. Frame transits as conditions that can be worked with, " +
|
|
181
179
|
"not inevitable forces. Avoid fatalism, but also avoid implying the person has total control over transit-level events. " +
|
|
182
180
|
"Difficult transits warrant clear, specific preparation — not vague reassurance.\n" +
|
|
181
|
+
"End the forecast with one natural sentence telling them when to check back — " +
|
|
182
|
+
"the next exact transit date from the data (e.g. 'Check back around March 14, when Saturn perfects the square to your Sun').\n" +
|
|
183
183
|
"**Disclaimer**: Astrological interpretation is symbolic and interpretive, not predictive or diagnostic. " +
|
|
184
184
|
"Never present transit analysis as a substitute for professional advice.\n\n" +
|
|
185
185
|
"## Step 5 — Follow-Up Offers\n" +
|
|
@@ -746,7 +746,9 @@ export const PROMPTS = [
|
|
|
746
746
|
"If they shared a concern or life area in Step 1: a dedicated paragraph applying today's sky " +
|
|
747
747
|
"specifically to their situation.\n\n" +
|
|
748
748
|
"**Tone**: Bright, grounded, and modern. Like a weather briefing, but for energy and intention. " +
|
|
749
|
-
"Keep it practical — every insight should connect to a real decision or action
|
|
749
|
+
"Keep it practical — every insight should connect to a real decision or action.\n\n" +
|
|
750
|
+
"Close with one natural sentence noting when the sky next shifts and it's worth checking back — " +
|
|
751
|
+
"the next Moon sign change or next lunar phase date from the data.",
|
|
750
752
|
},
|
|
751
753
|
// ─────────────────────────────────────────────────────────────────────────
|
|
752
754
|
// 11. Solar Return Year Ahead
|
package/dist/server-sse.js
CHANGED
|
@@ -21,6 +21,7 @@ import axios from "axios";
|
|
|
21
21
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
22
22
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
23
23
|
import { InMemoryEventStore } from "./event-store.js";
|
|
24
|
+
import { captureEvent, distinctIdFor } from "./analytics.js";
|
|
24
25
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, isInitializeRequest, } from "@modelcontextprotocol/sdk/types.js";
|
|
25
26
|
import { initTools, toolRegistry, formatToolResponse, formatToolError, modelVisibleTools } from "./tools/index.js";
|
|
26
27
|
import { BackendClient, runWithClient } from "./backend/client.js";
|
|
@@ -179,30 +180,26 @@ const WELCOME_PROMPT_CONTENT = [
|
|
|
179
180
|
role: "assistant",
|
|
180
181
|
content: {
|
|
181
182
|
type: "text",
|
|
182
|
-
text: "🌟 **Welcome to Open Ephemeris
|
|
183
|
-
"
|
|
184
|
-
"
|
|
185
|
-
"
|
|
186
|
-
"
|
|
187
|
-
"
|
|
188
|
-
"
|
|
189
|
-
"
|
|
190
|
-
"
|
|
191
|
-
"
|
|
192
|
-
"
|
|
193
|
-
"
|
|
194
|
-
"
|
|
195
|
-
"• **Time Lords** — Firdaria, Profections, Zodiacal Releasing\n\n" +
|
|
196
|
-
"**Tips:** Use `format=\"llm\"` on any tool to get ~50% smaller, token-optimized output. " +
|
|
197
|
-
"All computations are sub-arcsecond precision using JPL DE440 ephemerides.\n\n" +
|
|
198
|
-
"Just tell me what you'd like to explore!",
|
|
183
|
+
text: "🌟 **Welcome!** You're connected to Open Ephemeris — every chart here is computed " +
|
|
184
|
+
"from NASA JPL data, not guessed.\n\n" +
|
|
185
|
+
"Two great ways to start:\n\n" +
|
|
186
|
+
"1. **\"What's in the sky right now?\"** — instant, no birth details needed " +
|
|
187
|
+
"(live planets, moon phase, retrogrades).\n" +
|
|
188
|
+
"2. **\"Draw my birth chart\"** — an interactive wheel you can click and explore. " +
|
|
189
|
+
"Just share your birth date, exact time, and city.\n\n" +
|
|
190
|
+
"From there we can go deep: relationship synastry, Human Design bodygraphs, " +
|
|
191
|
+
"astrocartography (where on Earth your chart comes alive), Vedic, BaZi, transits, " +
|
|
192
|
+
"eclipses, and more.\n\n" +
|
|
193
|
+
"_(For the model: prefer explore_natal_chart / explore_moon_phase and the other " +
|
|
194
|
+
"explore_* tools for anything the user should SEE — they render interactive visuals. " +
|
|
195
|
+
"Use format=\"llm\" on data tools for compact output.)_",
|
|
199
196
|
},
|
|
200
197
|
},
|
|
201
198
|
];
|
|
202
199
|
// ---------------------------------------------------------------------------
|
|
203
200
|
// MCP Server factory — one per SSE/HTTP session
|
|
204
201
|
// ---------------------------------------------------------------------------
|
|
205
|
-
function createMcpServer() {
|
|
202
|
+
function createMcpServer(analyticsId = "anonymous") {
|
|
206
203
|
const server = new Server({
|
|
207
204
|
name: "openephemeris-mcp",
|
|
208
205
|
title: "Open Ephemeris",
|
|
@@ -237,14 +234,18 @@ function createMcpServer() {
|
|
|
237
234
|
},
|
|
238
235
|
},
|
|
239
236
|
},
|
|
240
|
-
instructions: "Open Ephemeris
|
|
241
|
-
"
|
|
242
|
-
"
|
|
243
|
-
"
|
|
237
|
+
instructions: "Open Ephemeris computes real astronomy (JPL DE440, sub-arcsecond) — never guess " +
|
|
238
|
+
"or approximate positions yourself; always call a tool. " +
|
|
239
|
+
"For anything the user should SEE (natal chart, bi-wheel, bodygraph, moon phase), " +
|
|
240
|
+
"prefer the explore_* tools — they render interactive visuals inline. " +
|
|
241
|
+
"ephemeris_* tools return data; use format='llm' on them for compact output. " +
|
|
242
|
+
"If the user has no birth data handy, start with the sky right now — " +
|
|
243
|
+
"explore_moon_phase and ephemeris_retrograde_status need none. " +
|
|
244
|
+
"See the 'welcome_to_open_ephemeris' prompt for orientation.",
|
|
244
245
|
});
|
|
245
246
|
// --- Tool handlers ---
|
|
246
247
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
247
|
-
tools: modelVisibleTools().map((tool) => ({
|
|
248
|
+
tools: modelVisibleTools("http").map((tool) => ({
|
|
248
249
|
name: tool.name,
|
|
249
250
|
description: tool.description,
|
|
250
251
|
inputSchema: tool.inputSchema,
|
|
@@ -279,7 +280,9 @@ function createMcpServer() {
|
|
|
279
280
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
280
281
|
const toolName = request.params.name;
|
|
281
282
|
const tool = toolRegistry[toolName];
|
|
282
|
-
if (!tool) {
|
|
283
|
+
if (!tool || tool.stdioOnly) {
|
|
284
|
+
// stdioOnly tools (device auth) mutate process-global state and must not
|
|
285
|
+
// be callable on the multi-tenant HTTP transport, even by name.
|
|
283
286
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
284
287
|
}
|
|
285
288
|
try {
|
|
@@ -290,11 +293,15 @@ function createMcpServer() {
|
|
|
290
293
|
// composite ~9.3s) with a wide margin.
|
|
291
294
|
const result = await withTimeout(tool.handler(request.params.arguments ?? {}), TOOL_CALL_TIMEOUT_MS, toolName);
|
|
292
295
|
const durationMs = Date.now() - startTime;
|
|
296
|
+
captureEvent("mcp_tool_call", analyticsId, { tool: toolName, duration_ms: durationMs });
|
|
293
297
|
return formatToolResponse(toolName, result, durationMs);
|
|
294
298
|
}
|
|
295
299
|
catch (error) {
|
|
296
300
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
297
301
|
console.error(`[MCP] ❌ Failed: ${toolName} - ${errorMessage}`);
|
|
302
|
+
// status makes 402 (paywall) and 429 (rate limit) countable in PostHog.
|
|
303
|
+
const status = error?.response?.status;
|
|
304
|
+
captureEvent("mcp_tool_error", analyticsId, { tool: toolName, status: status ?? "unknown" });
|
|
298
305
|
// All failures surface as isError: true so the host can recover
|
|
299
306
|
// (retry / OAuth refresh). The remote transport has no device-auth flow,
|
|
300
307
|
// so formatToolError's device-auth exception never fires here — a 401
|
|
@@ -547,7 +554,7 @@ export async function createSseApp() {
|
|
|
547
554
|
// Spec: https://smithery.ai/docs/build/publish#server-scanning
|
|
548
555
|
// ---------------------------------------------------------------------------
|
|
549
556
|
app.get("/.well-known/mcp/server-card.json", (_req, res) => {
|
|
550
|
-
const tools = modelVisibleTools().map((tool) => ({
|
|
557
|
+
const tools = modelVisibleTools("http").map((tool) => ({
|
|
551
558
|
name: tool.name,
|
|
552
559
|
description: tool.description,
|
|
553
560
|
inputSchema: tool.inputSchema,
|
|
@@ -716,7 +723,9 @@ export async function createSseApp() {
|
|
|
716
723
|
},
|
|
717
724
|
});
|
|
718
725
|
const client = new BackendClient({ baseURL: BACKEND_URL, apiKey, jwt });
|
|
719
|
-
const
|
|
726
|
+
const analyticsId = distinctIdFor(apiKey ?? jwt);
|
|
727
|
+
captureEvent("mcp_session_init", analyticsId, { auth: apiKey ? "api_key" : "oauth" });
|
|
728
|
+
const server = createMcpServer(analyticsId);
|
|
720
729
|
transport.onclose = () => {
|
|
721
730
|
if (transport.sessionId) {
|
|
722
731
|
httpSessions.delete(transport.sessionId);
|
|
@@ -406,7 +406,9 @@ function buildBiWheelSummary(innerPlanets, outerPlanets, crossAspects, mode, loc
|
|
|
406
406
|
// ── Tool: explore_bi_wheel ─────────────────────────────────────────────────────
|
|
407
407
|
registerTool({
|
|
408
408
|
name: "explore_bi_wheel",
|
|
409
|
-
description: "Generate an interactive bi-wheel chart comparing two astrological chart positions
|
|
409
|
+
description: "Generate an interactive bi-wheel chart comparing two astrological chart positions.\n\n" +
|
|
410
|
+
"CREDIT COST: 2 credits per call for most modes (1 per wheel computed); " +
|
|
411
|
+
"solar_return/lunar_return modes cost 6 (5 for the return + 1 for the natal wheel).\n\n" +
|
|
410
412
|
"The inner wheel is always Person 1's natal chart. The outer ring depends on mode:\n" +
|
|
411
413
|
"• synastry — Person 2's natal chart (two-person compatibility). 1 credit.\n" +
|
|
412
414
|
"• transit — Transiting planets for a given date. 1 credit.\n" +
|
|
@@ -356,7 +356,8 @@ function buildHdSummary(payload, location) {
|
|
|
356
356
|
// ── Tool: explore_human_design ────────────────────────────────────────────
|
|
357
357
|
registerTool({
|
|
358
358
|
name: "explore_human_design",
|
|
359
|
-
description: "Generate an interactive Human Design Bodygraph with clickable centers, gates, and channels
|
|
359
|
+
description: "Generate an interactive Human Design Bodygraph with clickable centers, gates, and channels.\n\n" +
|
|
360
|
+
"CREDIT COST: 2 credits per call.\n\n" +
|
|
360
361
|
"Returns an embedded visual bodygraph explorer that lets you click any center or gate " +
|
|
361
362
|
"for instant Human Design interpretation. " +
|
|
362
363
|
"Shows defined/undefined centers, active gates, channels, Type, Profile, Authority, " +
|
|
@@ -1325,7 +1326,8 @@ registerTool({
|
|
|
1325
1326
|
// Premium (Developer tier). Calls POST /human-design/transit-chart.
|
|
1326
1327
|
registerTool({
|
|
1327
1328
|
name: "explore_human_design_transit",
|
|
1328
|
-
description: "Overlay the current (or a chosen) planetary transit on a person's natal Human Design bodygraph
|
|
1329
|
+
description: "Overlay the current (or a chosen) planetary transit on a person's natal Human Design bodygraph.\n\n" +
|
|
1330
|
+
"CREDIT COST: 3 credits per call.\n\n" +
|
|
1329
1331
|
"Highlights the channels a transit temporarily COMPLETES with the natal chart and any centers it " +
|
|
1330
1332
|
"newly defines — the core of a Human Design transit reading. " +
|
|
1331
1333
|
"Returns an interactive overlay bodygraph in MCP Apps-capable hosts (Claude Desktop), with a text " +
|
|
@@ -1415,7 +1417,8 @@ registerTool({
|
|
|
1415
1417
|
registerTool({
|
|
1416
1418
|
name: "explore_human_design_connection",
|
|
1417
1419
|
description: "Compare two people's Human Design charts and classify every connected channel by HD connection " +
|
|
1418
|
-
"theory
|
|
1420
|
+
"theory. CREDIT COST: 3 credits per call. Classifications: " +
|
|
1421
|
+
"electromagnetic (attraction), companionship (sameness), dominance (one defines), and " +
|
|
1419
1422
|
"compromise (friction). Returns an interactive two-person overlay bodygraph in MCP Apps-capable " +
|
|
1420
1423
|
"hosts, with a text summary fallback elsewhere. Premium (Developer tier). NASA JPL DE440 ephemerides.",
|
|
1421
1424
|
inputSchema: {
|
|
@@ -104,9 +104,11 @@ function buildNatalBody(datetime, lat, lon, houseSystem, timezone) {
|
|
|
104
104
|
// ── Tool: explore_natal_chart ──────────────────────────────────────────────
|
|
105
105
|
registerTool({
|
|
106
106
|
name: "explore_natal_chart",
|
|
107
|
-
description: "
|
|
107
|
+
description: "PRIMARY tool for any natal/birth-chart request. Renders an interactive, clickable chart " +
|
|
108
|
+
"wheel — prefer this over ephemeris_natal_chart when the user wants to SEE a chart. " +
|
|
108
109
|
"Returns an embedded visual chart explorer that lets you click any planet, house, or aspect " +
|
|
109
|
-
"line for instant astrological interpretation
|
|
110
|
+
"line for instant astrological interpretation.\n\n" +
|
|
111
|
+
"CREDIT COST: 1 credit per call.\n\n" +
|
|
110
112
|
"Supports house system switching (Placidus, Whole Sign, Equal, Koch). " +
|
|
111
113
|
"The chart is computed using NASA JPL DE440 ephemerides for sub-arcsecond precision. " +
|
|
112
114
|
"Use this instead of ephemeris_chart_wheel for a richer, interactive experience in " +
|
|
@@ -3,7 +3,7 @@ import { getActiveClient } from "../../backend/client.js";
|
|
|
3
3
|
import { OUTPUT_SCHEMA_JSON } from "../output-schemas.js";
|
|
4
4
|
registerTool({
|
|
5
5
|
name: "location_search",
|
|
6
|
-
description: "App-only tool for getting location autocomplete suggestions. Returns display name, region (state/province), latitude, longitude, and IANA timezone. Optional bias params (country/region/near) improve ranking; a trailing \"City, ST\" qualifier in the query is also honored.",
|
|
6
|
+
description: "App-only tool for getting location autocomplete suggestions. CREDIT COST: 1 credit per call. Returns display name, region (state/province), latitude, longitude, and IANA timezone. Optional bias params (country/region/near) improve ranking; a trailing \"City, ST\" qualifier in the query is also honored.",
|
|
7
7
|
inputSchema: {
|
|
8
8
|
type: "object",
|
|
9
9
|
properties: {
|
|
@@ -63,7 +63,7 @@ registerTool({
|
|
|
63
63
|
});
|
|
64
64
|
registerTool({
|
|
65
65
|
name: "timezone_resolve",
|
|
66
|
-
description: "App-only tool for resolving IANA timezone by latitude and longitude.",
|
|
66
|
+
description: "App-only tool for resolving IANA timezone by latitude and longitude. CREDIT COST: 1 credit per call.",
|
|
67
67
|
inputSchema: {
|
|
68
68
|
type: "object",
|
|
69
69
|
properties: {
|
|
@@ -98,6 +98,7 @@ registerTool({
|
|
|
98
98
|
" • Void-of-Course status with timing details\n" +
|
|
99
99
|
" • Lunar age (days in the synodic cycle)\n" +
|
|
100
100
|
" • Upcoming New Moon and Full Moon dates\n\n" +
|
|
101
|
+
"CREDIT COST: 3 credits per call (phase + void-of-course + aspects, 1 each).\n\n" +
|
|
101
102
|
"Use this for a rich, interactive lunar phase experience in MCP Apps-capable hosts (Claude Desktop). " +
|
|
102
103
|
"Falls back to a text summary in other hosts.",
|
|
103
104
|
inputSchema: {
|
package/dist/tools/auth.js
CHANGED
|
@@ -8,7 +8,8 @@ const credentialManager = new CredentialManager();
|
|
|
8
8
|
// ────────────────────────────────────────────────────────
|
|
9
9
|
registerTool({
|
|
10
10
|
name: "auth_login",
|
|
11
|
-
description: "Start the device authorization flow to connect this MCP server to your OpenEphemeris account
|
|
11
|
+
description: "Start the device authorization flow to connect this MCP server to your OpenEphemeris account " +
|
|
12
|
+
"(free tier, no credit card needed). " +
|
|
12
13
|
"Returns a verification URL and code for the user to enter in their browser. " +
|
|
13
14
|
"The MCP server will then automatically receive credentials and all API calls will be " +
|
|
14
15
|
"linked to the user's account (tier, credits, rate limits). " +
|
|
@@ -24,6 +25,7 @@ registerTool({
|
|
|
24
25
|
readOnlyHint: false,
|
|
25
26
|
destructiveHint: false,
|
|
26
27
|
},
|
|
28
|
+
stdioOnly: true,
|
|
27
29
|
handler: async () => {
|
|
28
30
|
// Check if already authenticated
|
|
29
31
|
const status = credentialManager.getStatus();
|
|
@@ -74,6 +76,7 @@ registerTool({
|
|
|
74
76
|
expires_in: startResult.expires_in,
|
|
75
77
|
message: `Please visit ${startResult.verification_uri} and enter the code: ${startResult.user_code}. ` +
|
|
76
78
|
`Alternatively, open this direct link: ${startResult.verification_uri_complete}. ` +
|
|
79
|
+
`If you don't have an account yet, you can create one at that link — free tier, no credit card needed. ` +
|
|
77
80
|
`The server is polling in the background — once you authorize, all subsequent ` +
|
|
78
81
|
`tool calls will be linked to your account automatically.`,
|
|
79
82
|
instructions_for_agent: "Present the verification URL and code to the user clearly. " +
|
|
@@ -103,6 +106,7 @@ registerTool({
|
|
|
103
106
|
readOnlyHint: true,
|
|
104
107
|
destructiveHint: false,
|
|
105
108
|
},
|
|
109
|
+
stdioOnly: true,
|
|
106
110
|
handler: async () => {
|
|
107
111
|
// Check env var methods first
|
|
108
112
|
const hasApiKey = !!(process.env.OPENEPHEMERIS_API_KEY ||
|
|
@@ -188,6 +192,7 @@ registerTool({
|
|
|
188
192
|
readOnlyHint: false,
|
|
189
193
|
destructiveHint: true,
|
|
190
194
|
},
|
|
195
|
+
stdioOnly: true,
|
|
191
196
|
handler: async () => {
|
|
192
197
|
const status = credentialManager.getStatus();
|
|
193
198
|
if (!status.authenticated && !credentialManager.load()) {
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -30,13 +30,22 @@ export interface ToolDefinition {
|
|
|
30
30
|
visibility?: Array<"app" | "model">;
|
|
31
31
|
};
|
|
32
32
|
};
|
|
33
|
+
/**
|
|
34
|
+
* Tools that operate on process-global state (device-auth credential file,
|
|
35
|
+
* env-var inspection) are only safe on the single-user stdio transport. The
|
|
36
|
+
* multi-tenant HTTP server must not expose them: one user's auth_logout
|
|
37
|
+
* would clear shared credentials, and auth_status could leak the process's
|
|
38
|
+
* service-key configuration to every connected tenant.
|
|
39
|
+
*/
|
|
40
|
+
stdioOnly?: boolean;
|
|
33
41
|
handler: (args: any) => Promise<any>;
|
|
34
42
|
}
|
|
35
43
|
/**
|
|
36
44
|
* Returns tools that should be exposed to Claude in the ListTools response.
|
|
37
|
-
* Filters out tools that have visibility restricted to the UI (app-only)
|
|
45
|
+
* Filters out tools that have visibility restricted to the UI (app-only),
|
|
46
|
+
* and — for the HTTP transport — tools marked stdioOnly.
|
|
38
47
|
*/
|
|
39
|
-
export declare function modelVisibleTools(): ToolDefinition[];
|
|
48
|
+
export declare function modelVisibleTools(transport?: "stdio" | "http"): ToolDefinition[];
|
|
40
49
|
export declare const toolRegistry: Record<string, ToolDefinition>;
|
|
41
50
|
export declare function registerTool(tool: ToolDefinition): void;
|
|
42
51
|
export type ToolProfile = "dev" | "legacy";
|
package/dist/tools/index.js
CHANGED
|
@@ -14,10 +14,13 @@ export const SERVER_VERSION = (() => {
|
|
|
14
14
|
})();
|
|
15
15
|
/**
|
|
16
16
|
* Returns tools that should be exposed to Claude in the ListTools response.
|
|
17
|
-
* Filters out tools that have visibility restricted to the UI (app-only)
|
|
17
|
+
* Filters out tools that have visibility restricted to the UI (app-only),
|
|
18
|
+
* and — for the HTTP transport — tools marked stdioOnly.
|
|
18
19
|
*/
|
|
19
|
-
export function modelVisibleTools() {
|
|
20
|
+
export function modelVisibleTools(transport = "stdio") {
|
|
20
21
|
return Object.values(toolRegistry).filter((t) => {
|
|
22
|
+
if (transport === "http" && t.stdioOnly)
|
|
23
|
+
return false;
|
|
21
24
|
const vis = t._meta?.ui?.visibility;
|
|
22
25
|
// If visibility is explicitly ["app"] (no "model"), hide from model
|
|
23
26
|
if (vis && !vis.includes("model") && vis.includes("app"))
|
|
@@ -15,8 +15,10 @@ function buildSubject(name, datetime, lat, lon) {
|
|
|
15
15
|
registerTool({
|
|
16
16
|
name: "ephemeris_natal_batch",
|
|
17
17
|
description: "Calculate natal charts for multiple subjects in a single request. " +
|
|
18
|
-
"Supports up to 100 subjects. Returns enhanced natal chart data for each
|
|
19
|
-
"
|
|
18
|
+
"Supports up to 100 subjects. Returns enhanced natal chart data for each. " +
|
|
19
|
+
"Requires the Startup tier ($79/mo) or higher.\n\n" +
|
|
20
|
+
"CREDIT COST: 1 credit per subject — N subjects = N credits " +
|
|
21
|
+
"(100 subjects = 100 credits). Confirm with the user before large batches.\n\n" +
|
|
20
22
|
"EXAMPLE: Two people batch:\n" +
|
|
21
23
|
" subjects: [\n" +
|
|
22
24
|
" { name: 'Alice', datetime: '1990-04-15T14:30:00', latitude: 41.88, longitude: -87.63 },\n" +
|
|
@@ -14,7 +14,8 @@ registerTool({
|
|
|
14
14
|
description: "Calculate a full natal (birth) chart for a person. Returns planetary positions, house cusps, " +
|
|
15
15
|
"aspects, and chart patterns. Use format='llm' for a compact, token-efficient output ideal for " +
|
|
16
16
|
"interpretation (available on all tiers). The result includes all major planets, Chiron and major asteroids like Ceres, angles (ASC/MC/DSC/IC), " +
|
|
17
|
-
"essential dignities, retrograde status, house system data, and major aspect grid. Asteroids are automatically included
|
|
17
|
+
"essential dignities, retrograde status, house system data, and major aspect grid. Asteroids are automatically included. " +
|
|
18
|
+
"Returns raw JSON. For a user-facing visual chart, use explore_natal_chart instead.\n\n" +
|
|
18
19
|
"CREDIT COST: 1 credit per call.\n\n" +
|
|
19
20
|
"EXAMPLE: Calculate the natal chart for someone born April 15, 1990 at 2:30 PM in Chicago:\n" +
|
|
20
21
|
" datetime='1990-04-15T14:30:00', latitude=41.8781, longitude=-87.6298",
|
package/dist/ui/bi-wheel.html
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<div id="app">
|
|
18
18
|
<div id="loading-state">
|
|
19
19
|
<svg class="oe-loader" viewBox="0 0 100 100"><circle cx="50" cy="50" r="45"/></svg>
|
|
20
|
-
<p>
|
|
20
|
+
<p>Aligning two skies…</p>
|
|
21
21
|
</div>
|
|
22
22
|
<div id="chart-status" class="chart-status" aria-live="polite"></div>
|
|
23
23
|
<div id="chart-container" hidden>
|
|
@@ -7792,7 +7792,7 @@ function Ny() {
|
|
|
7792
7792
|
}
|
|
7793
7793
|
function Py(e4) {
|
|
7794
7794
|
var t2, n2, r2, i2, a2, o2, s2;
|
|
7795
|
-
$ = e4, clearTimeout(
|
|
7795
|
+
$ = e4, clearTimeout(Hy), Cy(e4), Dv(qv, e4, zy, By, ky, Ny()), yy(), Oy(e4, xy);
|
|
7796
7796
|
let c2 = V_(e4.inner), l2 = V_(e4.outer), u2 = (n2 = (r2 = (i2 = ((t2 = e4.inner_houses) == null ? [] : t2)[0]) == null ? void 0 : i2.cusp_lon) == null ? e4.ascendant : r2) == null ? 0 : n2;
|
|
7797
7797
|
Ly(xv((a2 = e4.cross_aspects) == null ? [] : a2), c2, l2, u2), e4.house_system && (ay = e4.house_system);
|
|
7798
7798
|
let d2 = document.getElementById("bi-action-bar");
|
|
@@ -7874,10 +7874,10 @@ function By(e4) {
|
|
|
7874
7874
|
My({ type: "cross_aspect", data: { planet1: e4.planet1, wheel1: e4.wheel1, planet2: e4.planet2, wheel2: e4.wheel2, aspect_type: e4.type, orb: e4.orb, applying: e4.applying, planet1_sign: isNaN(i2) ? null : o2[i2], planet2_sign: isNaN(a2) ? null : o2[a2], mode: (t2 = $ == null ? void 0 : $.mode) == null ? "synastry" : t2 } });
|
|
7875
7875
|
}
|
|
7876
7876
|
ty.ontoolresult = (e4) => {
|
|
7877
|
-
clearTimeout(
|
|
7877
|
+
clearTimeout(Hy);
|
|
7878
7878
|
let t2 = hy(e4);
|
|
7879
7879
|
if (!t2) {
|
|
7880
|
-
fy("
|
|
7880
|
+
fy("The chart didn't load. Tap to try again.", Vy);
|
|
7881
7881
|
return;
|
|
7882
7882
|
}
|
|
7883
7883
|
if (!ny) {
|
|
@@ -7902,12 +7902,14 @@ ty.ontoolresult = (e4) => {
|
|
|
7902
7902
|
}
|
|
7903
7903
|
Py(t2), ey.removeAttribute("hidden");
|
|
7904
7904
|
};
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
}, 3e4), ty.sendMessage({ role: "user", content: [{ type: "text", text: "Please regenerate the bi-wheel chart." }] });
|
|
7905
|
+
function Vy() {
|
|
7906
|
+
clearTimeout(Hy), cy(), Hy = setTimeout(() => {
|
|
7907
|
+
qv.hidden && fy("The chart didn't load. Tap to try again.", Vy);
|
|
7908
|
+
}, 3e4), ty.sendMessage({ role: "user", content: [{ type: "text", text: "Please regenerate the bi-wheel chart." }] }).catch(() => {
|
|
7910
7909
|
});
|
|
7910
|
+
}
|
|
7911
|
+
var Hy = setTimeout(() => {
|
|
7912
|
+
qv.hidden && fy("Chart data is taking longer than expected.", Vy);
|
|
7911
7913
|
}, 3e4);
|
|
7912
7914
|
cy(), Wv(ty, { loadingEl: Jv, contentEl: qv, statusFn: sy, paramControls: ny }), (async () => {
|
|
7913
7915
|
try {
|
package/dist/ui/bodygraph.html
CHANGED
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
<title>Human Design Bodygraph Explorer</title>
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
<style>:root{--bg:#010102;--surface:#030408;--surface-2:#080b11;--border:#13161d;--border-strong:#242933;--text:#e2e4e9;--text-secondary:#777a80;--muted:#5b5e62;--gold:#cc9c42;--gold-dim:#7b6540;--gold-glow:rgba(204,156,66,.2);--gold-subtle:rgba(204,156,66,.1);--hd-head:#855dd7;--hd-ajna:#3a61c4;--hd-throat:#94582a;--hd-g:#c99f00;--hd-heart:#cc272e;--hd-spleen:#3d9028;--hd-sp:#cb5a1a;--hd-sacral:#cc243d;--hd-root:#6f3600;--ch-personality:#79a1fc;--ch-design:#ac7d1b;--ch-both:#cc9c42;--ch-inactive:#1f2228;--success:#1da871;--error:#c53443;--radius:10px;--radius-sm:6px;--radius-pill:99px;--shadow-md:0 8px 40px rgba(0,0,0,.65), 0 0 0 1px var(--border-strong);--shadow-gold:0 0 28px rgba(204,156,66,.15), 0 0 0 1px rgba(204,156,66,.2)}@supports (color:color(display-p3 0 0 0)){:root{--bg:color(display-p3 .00306656 .00431968 .00888397);--surface:color(display-p3 .013072 .016989 .0298495);--surface-2:color(display-p3 .0345774 .0431759 .0653236);--border:color(display-p3 .0763929 .0859556 .109705);--border-strong:color(display-p3 .144788 .159634 .196542);--text:color(display-p3 .889546 .895785 .910689);--text-secondary:color(display-p3 .470023 .47923 .50135);--muted:color(display-p3 .359661 .366689 .383567);--gold:color(display-p3 .76996 .617341 .321369);--gold-dim:color(display-p3 .469305 .400291 .270966);--gold-glow:color(display-p3 .76996 .617341 .321369/.2);--gold-subtle:color(display-p3 .76996 .617341 .321369/.1);--hd-head:color(display-p3 .496597 .372031 .817324);--hd-ajna:color(display-p3 .26389 .378111 .742207);--hd-throat:color(display-p3 .548969 .355242 .201432);--hd-g:color(display-p3 .777513 .62422 -.00225984);--hd-heart:color(display-p3 .737101 .219083 .211834);--hd-spleen:color(display-p3 .328385 .55681 .222852);--hd-sp:color(display-p3 .739686 .377576 .178173);--hd-sacral:color(display-p3 .735577 .213396 .258614);--hd-root:color(display-p3 .406873 .223096 .0609673);--ch-personality:color(display-p3 .508025 .628538 .962447);--ch-design:color(display-p3 .645333 .49681 .196492);--ch-both:color(display-p3 .76996 .617341 .321369);--ch-inactive:color(display-p3 .122731 .131518 .153019);--success:color(display-p3 .313719 .651039 .459895);--error:color(display-p3 .710345 .252818 .279408);--shadow-md:0 8px 40px color(display-p3 0 0 0/.65), 0 0 0 1px var(--border-strong);--shadow-gold:0 0 28px color(display-p3 .76996 .617341 .321369/.15), 0 0 0 1px color(display-p3 .76996 .617341 .321369/.2)}}@supports (color:lab(0% 0 0)){:root{--bg:lab(.302122% .0219196 -.522962);--surface:lab(1.18159% .0276566 -1.4848);--surface-2:lab(3.00584% .0228807 -3.15676);--border:lab(7.15581% -.0557452 -5.11242);--border-strong:lab(16.3713% -.0768304 -7.37782);--text:lab(90.6942% -.142396 -2.26941);--text-secondary:lab(51.2352% -.205308 -3.76425);--muted:lab(39.6443% -.165641 -3.01217);--gold:lab(67.5798% 11.9379 52.333);--gold-dim:lab(44.394% 5.21129 24.4453);--gold-glow:lab(67.5798% 11.9379 52.333/.2);--gold-subtle:lab(67.5798% 11.9379 52.333/.1);--hd-head:lab(48.5159% 37.0934 -58.1781);--hd-ajna:lab(42.6654% 12.3083 -56.648);--hd-throat:lab(43.8304% 22.8022 36.6206);--hd-g:lab(67.813% 9.5214 93.33);--hd-heart:lab(45.7636% 63.3818 40.1799);--hd-spleen:lab(53.0764% -40.8778 44.314);--hd-sp:lab(52.4466% 43.5944 55.3775);--hd-sacral:lab(45.6635% 64.206 31.2074);--hd-root:lab(29.855% 23.6799 41.1297);--ch-personality:lab(66.4233% 5.15944 -50.1959);--ch-design:lab(55.9353% 12.6865 55.2442);--ch-both:lab(67.5798% 11.9379 52.333);--ch-inactive:lab(12.9398% -.133917 -4.46223);--success:lab(61.2436% -46.7762 18.3644);--error:lab(45.8893% 57.7361 27.393);--shadow-md:0 8px 40px lab(0% 0 0/.65), 0 0 0 1px var(--border-strong);--shadow-gold:0 0 28px lab(67.5798% 11.9379 52.333/.15), 0 0 0 1px lab(67.5798% 11.9379 52.333/.2)}}@media (prefers-color-scheme:light){:root{--bg:#f4f5f8;--surface:#fbfcfe;--surface-2:#eceef3;--border:#d5d7dd;--border-strong:#babec6;--text:#181b1f;--text-secondary:#4a4d54;--muted:#606369;--gold:#8a6000;--gold-dim:#967c4f;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}@supports (color:color(display-p3 0 0 0)){:root{--bg:color(display-p3 .956515 .960735 .970805);--surface:color(display-p3 .98376 .986943 .994535);--surface-2:color(display-p3 .928401 .934691 .949715);--border:color(display-p3 .836244 .844458 .864107);--border-strong:color(display-p3 .731542 .743532 .772301);--text:color(display-p3 .096883 .103987 .12134);--text-secondary:color(display-p3 .291758 .301882 .326359);--muted:color(display-p3 .379924 .388781 .410092);--gold:color(display-p3 .524671 .380491 .0387678);--gold-dim:color(display-p3 .572756 .489268 .332751)}}@supports (color:lab(0% 0 0)){:root{--bg:lab(96.503% -.0977516 -1.51461);--surface:lab(98.8273% -.0742972 -1.13657);--surface-2:lab(94.1743% -.142723 -2.26961);--border:lab(86.0453% -.183761 -3.02231);--border-strong:lab(76.7468% -.254452 -4.52154);--text:lab(9.4703% -.11944 -3.722);--text-secondary:lab(32.6639% -.205338 -4.49569);--muted:lab(41.9548% -.197008 -3.7598);--gold:lab(44.2758% 13.6718 62.9124);--gold-dim:lab(53.685% 6.10271 28.558)}}html,body{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}#bodygraph-container{background:var(--surface)}.chart-status,.tooltip{background:rgba(251,252,254,.92);background:color(display-p3 .98376 .986943 .994535/.92);background:lab(98.8273% -.0742972 -1.13657/.92)}#fs-bottom-bar{background:var(--surface-2)}}*,:before,:after{box-sizing:border-box;margin:0;padding:0}[hidden]{display:none!important}html,body{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;background:var(--bg);height:100%;color:var(--text);background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.030'/%3E%3C/svg%3E");font-family:Geist,Space Grotesk,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Noto Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:13px;line-height:1.5;overflow-x:hidden}#app{flex-direction:column;gap:8px;min-height:0;padding:8px 12px;display:flex}#loading-state{min-height:300px;color:var(--muted);flex-direction:column;justify-content:center;align-items:center;gap:14px;display:flex}#loading-state p{letter-spacing:.06em;text-transform:uppercase;font-size:11px}.spinner{border:2px solid var(--border-strong);border-top-color:var(--gold);border-radius:50%;width:32px;height:32px;animation:1.1s cubic-bezier(.4,0,.6,1) infinite spin}@keyframes spin{to{transform:rotate(360deg)}}#bodygraph-container{border-radius:var(--radius);border:1px solid var(--border-strong);width:100%;max-width:680px;box-shadow:var(--shadow-gold);background:#010102;background:color(display-p3 .00306656 .00431968 .00888397);background:lab(.302122% .0219196 -.522962);margin:0 auto;transition:box-shadow .3s;position:relative;overflow:visible}#bodygraph-container:hover{box-shadow:0 0 40px rgba(204,156,66,.22),0 0 0 1px rgba(204,156,66,.3);box-shadow:0 0 40px color(display-p3 .76996 .617341 .321369/.22),0 0 0 1px color(display-p3 .76996 .617341 .321369/.3);box-shadow:0 0 40px lab(67.5798% 11.9379 52.333/.22),0 0 0 1px lab(67.5798% 11.9379 52.333/.3)}#bodygraph-container svg{width:100%;height:auto;max-height:min(62vh,560px);display:block}.center-group{cursor:pointer;transition:filter .16s}.center-group:hover{filter:brightness(1.2)drop-shadow(0 0 6px rgba(204,156,66,.35));filter:brightness(1.2)drop-shadow(0 0 6px color(display-p3 .76996 .617341 .321369/.35));filter:brightness(1.2)drop-shadow(0 0 6px lab(67.5798% 11.9379 52.333/.35))}.channel-line{cursor:pointer}.channel-line:hover .channel-vis{filter:drop-shadow(0 0 4px var(--gold-glow));stroke:var(--gold)!important;stroke-width:5px!important}.gate-badge{cursor:pointer}.gate-badge:hover circle{stroke:var(--gold)!important;stroke-opacity:1!important}.gate-badge:hover text{fill:var(--gold)!important}#info-panel{background:var(--surface);border:1px solid var(--border-strong);border-radius:var(--radius);grid-template-columns:1fr 1fr;gap:6px 12px;width:100%;max-width:680px;margin:0 auto;padding:10px 14px;display:grid}.info-row{flex-direction:column;gap:1px;display:flex}.info-label{letter-spacing:.08em;text-transform:uppercase;color:var(--muted);font-size:9px;font-weight:700}.info-value{color:var(--text);font-size:12px;font-weight:500}.channel-legend{flex-wrap:wrap;justify-content:center;gap:12px;width:100%;max-width:680px;margin:0 auto;padding:4px 0;display:flex}.legend-item{color:var(--text-secondary);letter-spacing:.04em;align-items:center;gap:5px;font-size:9px;font-weight:500;display:flex}.legend-swatch{border-radius:2px;flex-shrink:0;width:18px;height:3px}#controls-panel{background:0 0;width:100%;max-width:680px;margin:0 auto;padding:4px 0}.controls-row{align-items:center;gap:6px;display:flex}.controls-label{letter-spacing:.08em;text-transform:uppercase;color:var(--muted);flex-shrink:0;font-size:9px;font-weight:700}.controls-value{color:var(--text-secondary);font-size:11px}.chart-hint{text-align:center;letter-spacing:.04em;width:100%;max-width:680px;color:var(--muted);margin:0 auto;padding:4px 0 2px;font-size:10px}.error-state{min-height:200px;color:var(--muted);text-align:center;flex-direction:column;justify-content:center;align-items:center;gap:8px;padding:24px;display:flex}.error-icon{opacity:.5;font-size:24px}.error-retry-btn{border:1px solid var(--border,rgba(128,128,160,.3));color:var(--accent,var(--text,inherit));font:inherit;letter-spacing:.02em;cursor:pointer;background:0 0;border-radius:999px;margin-top:4px;padding:7px 20px;font-size:11px;font-weight:600;transition:background .18s,border-color .18s}.error-retry-btn:hover{background:rgba(128,128,160,.12)}@media (max-width:420px){#app{gap:8px;padding:8px}#info-panel{grid-template-columns:1fr}}#bodygraph-container.is-loading{opacity:.55;pointer-events:none;cursor:wait;transition:opacity .15s}.chart-status{-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);border-radius:var(--radius-pill);color:var(--gold);letter-spacing:.04em;z-index:900;opacity:0;pointer-events:none;background:rgba(5,7,13,.92);background:color(display-p3 .0216592 .0280553 .0483601/.92);background:lab(1.95095% .0425801 -2.41514/.92);border:1px solid rgba(204,156,66,.35);border:1px solid color(display-p3 .76996 .617341 .321369/.35);border:1px solid lab(67.5798% 11.9379 52.333/.35);max-height:0;padding:5px 18px;font-size:11px;font-weight:600;transition:max-height .24s cubic-bezier(.16,1,.3,1),opacity .24s,transform .24s cubic-bezier(.16,1,.3,1);position:fixed;top:10px;left:50%;overflow:hidden;transform:translate(-50%)translateY(-10px);box-shadow:0 4px 20px rgba(0,0,0,.5),0 0 0 1px rgba(204,156,66,.08);box-shadow:0 4px 20px color(display-p3 0 0 0/.5),0 0 0 1px color(display-p3 .76996 .617341 .321369/.08);box-shadow:0 4px 20px lab(0% 0 0/.5),0 0 0 1px lab(67.5798% 11.9379 52.333/.08)}.chart-status.visible{opacity:1;max-height:44px;transform:translate(-50%)translateY(0)}@keyframes gold-pulse{0%,to{border-color:rgba(204,156,66,.35);border-color:color(display-p3 .76996 .617341 .321369/.35);border-color:lab(67.5798% 11.9379 52.333/.35)}50%{border-color:rgba(204,156,66,.65);border-color:color(display-p3 .76996 .617341 .321369/.65);border-color:lab(67.5798% 11.9379 52.333/.65)}}.hd-popup{background:var(--surface-2);border:1px solid var(--border-strong);z-index:1000;opacity:0;pointer-events:none;border-radius:12px;width:min(340px,92vw);padding:14px 16px 12px;transition:opacity .2s cubic-bezier(.16,1,.3,1),transform .2s cubic-bezier(.16,1,.3,1);position:fixed;box-shadow:0 12px 48px rgba(0,0,0,.7),0 0 0 1px rgba(204,156,66,.15);box-shadow:0 12px 48px color(display-p3 0 0 0/.7),0 0 0 1px color(display-p3 .76996 .617341 .321369/.15);box-shadow:0 12px 48px lab(0% 0 0/.7),0 0 0 1px lab(67.5798% 11.9379 52.333/.15)}.hd-popup.visible{opacity:1;pointer-events:all}.hd-popup-header{align-items:flex-start;gap:10px;display:flex}.hd-popup-icon{color:var(--gold);text-align:center;flex-shrink:0;min-width:28px;font-size:18px;font-weight:700;line-height:1}.hd-popup-title{color:var(--text);flex:1;font-size:13px;font-weight:700}.hd-popup-sub{color:var(--text-secondary);letter-spacing:.01em;margin-top:2px;font-size:11px;font-weight:400}.hd-popup-close{cursor:pointer;color:var(--muted);background:0 0;border:none;border-radius:4px;flex-shrink:0;padding:2px 4px;font-size:15px;line-height:1;transition:color .12s}.hd-popup-close:hover{color:var(--text)}.hd-popup-body{border-top:1px solid var(--border);flex-direction:column;gap:6px;margin-top:10px;padding-top:10px;display:flex}.hd-popup-row{align-items:baseline;gap:8px;font-size:11px;line-height:1.45;display:flex}.hd-popup-label{letter-spacing:.06em;text-transform:uppercase;color:var(--text-secondary);flex-shrink:0;min-width:56px;padding-top:1px;font-size:9px;font-weight:700}.hd-popup-val{color:var(--text);font-weight:500}.hd-popup-gates{flex-wrap:wrap;gap:4px;font-weight:500;display:flex}.hd-popup-gate-chip{border:1px solid var(--border-strong);border-radius:var(--radius-pill,99px);background:var(--surface);color:var(--text);white-space:nowrap;padding:1px 7px;font-size:10px;display:inline-block}.hd-popup-note{color:var(--text-secondary);letter-spacing:.01em;margin-top:4px;font-size:10px;font-style:italic;line-height:1.5}.hd-detail-panel{scrollbar-width:thin;scrollbar-color:var(--border-strong) transparent;flex:1;min-height:0;padding:2px 0 8px;overflow-y:auto}.hd-detail-panel .hd-popup-header{border-bottom:1px solid var(--border);margin-bottom:8px;padding-bottom:8px}.hd-detail-panel .hd-popup-close{opacity:.5}.hd-detail-panel .hd-popup-close:hover{opacity:1;color:var(--text)}.ask-claude-btn{border-radius:var(--radius-sm);width:100%;color:var(--gold);letter-spacing:.04em;cursor:pointer;background:rgba(204,156,66,.12);background:color(display-p3 .76996 .617341 .321369/.12);background:lab(67.5798% 11.9379 52.333/.12);border:1px solid rgba(204,156,66,.4);border:1px solid color(display-p3 .76996 .617341 .321369/.4);border:1px solid lab(67.5798% 11.9379 52.333/.4);justify-content:center;align-items:center;gap:6px;margin-top:12px;padding:9px 16px;font-family:Geist,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Noto Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:12px;font-weight:600;transition:background .16s,border-color .16s,box-shadow .16s;display:flex}.ask-claude-btn:hover:not(:disabled){background:rgba(204,156,66,.2);background:color(display-p3 .76996 .617341 .321369/.2);background:lab(67.5798% 11.9379 52.333/.2);border-color:rgba(204,156,66,.65);border-color:color(display-p3 .76996 .617341 .321369/.65);border-color:lab(67.5798% 11.9379 52.333/.65);box-shadow:0 0 0 3px rgba(204,156,66,.12);box-shadow:0 0 0 3px color(display-p3 .76996 .617341 .321369/.12);box-shadow:0 0 0 3px lab(67.5798% 11.9379 52.333/.12)}.ask-claude-btn:disabled{opacity:.45;cursor:not-allowed}.ask-claude-btn.sending{color:var(--gold-dim);opacity:.8;cursor:wait;pointer-events:none;background:rgba(204,156,66,.08);background:color(display-p3 .76996 .617341 .321369/.08);background:lab(67.5798% 11.9379 52.333/.08);border-color:rgba(204,156,66,.25);border-color:color(display-p3 .76996 .617341 .321369/.25);border-color:lab(67.5798% 11.9379 52.333/.25)}.ask-claude-btn.sent{color:var(--success);pointer-events:none;background:rgba(29,168,113,.15);background:color(display-p3 .313719 .651039 .459895/.15);background:lab(61.2436% -46.7762 18.3644/.15);border-color:rgba(29,168,113,.5);border-color:color(display-p3 .313719 .651039 .459895/.5);border-color:lab(61.2436% -46.7762 18.3644/.5)}.ask-claude-btn.error{color:var(--error);cursor:pointer;background:rgba(197,52,67,.15);background:color(display-p3 .710345 .252818 .279408/.15);background:lab(45.8893% 57.7361 27.393/.15);border-color:rgba(197,52,67,.5);border-color:color(display-p3 .710345 .252818 .279408/.5);border-color:lab(45.8893% 57.7361 27.393/.5)}.interactive-text{cursor:pointer;transition:color .15s,text-shadow .15s;position:relative}.interactive-text:hover{color:var(--gold);text-shadow:0 0 8px rgba(204,156,66,.4);text-shadow:0 0 8px color(display-p3 .76996 .617341 .321369/.4);text-shadow:0 0 8px lab(67.5798% 11.9379 52.333/.4)}.interactive-text:focus{color:var(--gold);text-shadow:0 0 8px rgba(204,156,66,.6);text-shadow:0 0 8px color(display-p3 .76996 .617341 .321369/.6);text-shadow:0 0 8px lab(67.5798% 11.9379 52.333/.6);outline:none}.planet-activation,.variable-arrow{outline:none;transition:opacity .15s}.planet-activation:hover,.variable-arrow:hover{opacity:.7}.planet-activation:focus-visible{outline:2px solid var(--gold);outline-offset:4px}.variable-arrow:focus-visible{outline:2px solid var(--gold);outline-offset:4px}.center-group:focus-visible{outline:2px solid var(--gold);outline-offset:4px}.gate-badge:focus-visible{outline:2px solid var(--gold);outline-offset:4px}.channel:focus-visible{outline:2px solid var(--gold);outline-offset:4px}[data-gate]:focus-visible{outline:2px solid var(--gold);outline-offset:4px}[data-channel]:focus-visible{outline:2px solid var(--gold);outline-offset:4px}[data-center]:focus-visible{outline:2px solid var(--gold);outline-offset:4px}.tooltip{-webkit-backdrop-filter:blur(14px)saturate(1.6);backdrop-filter:blur(14px)saturate(1.6);border:1px solid var(--border-strong);border-radius:var(--radius);pointer-events:none;z-index:1000;opacity:0;background:rgba(7,9,14,.92);background:color(display-p3 .0286832 .0352525 .0537785/.92);background:lab(2.44746% .00379235 -2.33402/.92);max-width:240px;padding:8px 12px;font-size:12px;transition:opacity .12s;position:fixed;box-shadow:0 8px 32px rgba(0,0,0,.55),inset 0 .5px rgba(255,255,255,.04);box-shadow:0 8px 32px color(display-p3 0 0 0/.55),inset 0 .5px color(display-p3 1 1 1/.04);box-shadow:0 8px 32px lab(0% 0 0/.55),inset 0 .5px lab(100% 0 0/.04)}.tooltip.visible{opacity:1}.tooltip-title{color:var(--text);margin-bottom:2px;font-weight:700}.tooltip-detail{color:var(--text-secondary);line-height:1.4}.tooltip-hint{color:var(--gold-dim);letter-spacing:.03em;margin-top:4px;font-size:10px}.pan-zoom-controls{z-index:800;flex-direction:column;gap:6px;display:flex;position:absolute;bottom:12px;right:12px}.pan-zoom-controls[hidden]{display:none!important}.pan-btn{background:var(--surface-2);border:1px solid var(--border-strong);color:var(--text);border-radius:var(--radius-sm);cursor:pointer;width:36px;height:36px;box-shadow:var(--shadow-md);justify-content:center;align-items:center;font-family:inherit;font-size:18px;transition:all .15s;display:flex}.pan-btn.reset{width:auto;padding:0 10px;font-size:11px;font-weight:500}.pan-btn:hover{background:var(--surface);border-color:var(--gold-dim);color:var(--gold)}.pan-btn:active{transform:scale(.95)}.pan-btn--fullscreen{width:36px;height:36px}body.is-fullscreen{height:100vh;overflow:hidden}body.is-fullscreen #app{flex-direction:column;gap:0;width:100vw;max-width:100%;height:100vh;padding:0;display:flex;overflow:hidden}body.is-fullscreen #bodygraph-container{min-height:0;max-width:unset;width:100%;height:auto;box-shadow:none;background:0 0;border:none;flex:1;justify-content:center;align-items:center;margin:0;padding:16px 24px 0;display:flex}body.is-fullscreen #loading-state{flex:1;height:auto;min-height:0}body.is-fullscreen #bodygraph-container svg{width:auto;max-width:100%;height:100%;max-height:100%}body.is-fullscreen .pan-zoom-controls{top:16px;right:16px}#fs-bottom-bar{border-top:1px solid var(--border);background:#020306;background:color(display-p3 .0100648 .0127997 .0215356);background:lab(.889429% .0122339 -1.01076);flex-shrink:0;grid-template-rows:1fr;grid-template-columns:220px 1fr 220px;height:240px;min-height:180px;max-height:280px;display:grid}#fs-bottom-bar [data-column=left],#fs-bottom-bar [data-column=right]{scrollbar-width:thin;scrollbar-color:var(--border-strong) transparent;flex-direction:column;gap:4px;padding:10px 14px 12px;display:flex;overflow-x:hidden;overflow-y:auto}#fs-bottom-bar [data-column=left]{border-right:1px solid var(--border)}#fs-bottom-bar [data-column=center]{pointer-events:none}#fs-bottom-bar [data-column=right]{border-left:1px solid var(--border);overflow-y:auto}#fs-bottom-bar [data-column]>*+*{border-top:1px solid var(--border);margin-top:4px;padding-top:8px}#fs-bottom-bar .hd-action-bar{flex-direction:column;padding:8px 0 0}#fs-bottom-bar .action-btn{justify-content:center;width:100%}.cancel-toast{background:var(--surface-2,#1e1e2e);border:1px solid var(--border-strong,#444);color:var(--text-muted,#aaa);border-radius:var(--radius-sm,6px);opacity:0;z-index:9999;pointer-events:none;box-shadow:var(--shadow-md,0 4px 12px rgba(0,0,0,.3));padding:8px 16px;font-family:inherit;font-size:12px;transition:opacity .3s,transform .3s;position:fixed;bottom:20px;left:50%;transform:translate(-50%)translateY(20px)}.cancel-toast.visible{opacity:1;transform:translate(-50%)translateY(0)}.oe-loader{width:64px;height:64px;stroke:var(--gold);stroke-width:2px;fill:none;stroke-dasharray:200;stroke-dashoffset:200px;filter:drop-shadow(0 0 4px var(--gold-glow));animation:2.5s cubic-bezier(.16,1,.3,1) infinite oe-draw}@keyframes oe-draw{0%{stroke-dashoffset:200px;stroke-opacity:.1}50%{stroke-dashoffset:0;stroke-opacity:1;filter:drop-shadow(0 0 8px var(--gold))}to{stroke-dashoffset:-200px;stroke-opacity:.1}}.info-drawer{background:var(--surface-2);border:1px solid var(--border-strong);border-radius:var(--radius);width:100%;box-shadow:var(--shadow-md);opacity:0;pointer-events:none;max-height:0;padding:14px 16px 12px;transition:max-height .26s cubic-bezier(.16,1,.3,1),opacity .2s,padding .2s;position:relative;overflow:hidden}.info-drawer:before{content:"";background:var(--border-strong);border-radius:2px;flex-shrink:0;width:36px;height:4px;margin:0 auto 10px;display:block}.info-drawer.open{opacity:1;pointer-events:all;max-height:500px;padding:14px 16px 12px}.hd-action-bar{flex-wrap:wrap;gap:8px;padding:10px 16px 6px;display:flex}.action-btn{color:#d4af37;letter-spacing:.02em;cursor:pointer;white-space:nowrap;background:rgba(212,175,55,.08);border:1px solid rgba(212,175,55,.3);border-radius:8px;align-items:center;gap:6px;padding:7px 16px;font-size:12px;font-weight:600;transition:background .2s,border-color .2s,transform .1s;display:inline-flex}.action-btn:hover{background:rgba(212,175,55,.15);border-color:rgba(212,175,55,.5);transform:translateY(-1px)}.action-btn:active{background:rgba(212,175,55,.2);transform:translateY(0)}.action-btn--primary{color:#f0d060;background:linear-gradient(135deg,rgba(212,175,55,.2),rgba(180,140,30,.25));border-color:rgba(212,175,55,.5)}.action-btn--primary:hover{background:linear-gradient(135deg,rgba(212,175,55,.3),rgba(180,140,30,.35));border-color:rgba(212,175,55,.7)}@media (prefers-color-scheme:light){:root{--bg:#f7f5f1;--surface:#fff;--surface-2:#f1eee9;--border:#dbd7d0;--border-strong:#bdb6ac;--text:#101215;--text-secondary:#4f5359;--muted:#6c6f73;--gold:#8d5f00;--gold-dim:#6d4b00;--gold-glow:rgba(141,95,0,.25);--gold-subtle:rgba(141,95,0,.1);--success:#007145;--error:#b1002b;--shadow-md:0 4px 24px rgba(0,0,0,.12), 0 0 0 1px var(--border-strong);--shadow-gold:0 0 16px rgba(141,95,0,.18), 0 0 0 1px rgba(141,95,0,.22)}@supports (color:color(display-p3 0 0 0)){:root{--bg:color(display-p3 .967616 .959866 .945308);--surface:color(display-p3 1 1 1);--surface-2:color(display-p3 .943798 .93351 .914195);--border:color(display-p3 .855616 .842987 .819292);--border-strong:color(display-p3 .73602 .716348 .67952);--text:color(display-p3 .0633107 .0687189 .081922);--text-secondary:color(display-p3 .313002 .323251 .348016);--muted:color(display-p3 .426318 .433563 .450948);--gold:color(display-p3 .542549 .371783 -.107551);--gold-dim:color(display-p3 .414099 .299019 .034461);--gold-glow:color(display-p3 .542549 .371783 -.107551/.25);--gold-subtle:color(display-p3 .542549 .371783 -.107551/.1);--success:color(display-p3 -.0793084 .456092 .255685);--error:color(display-p3 .642198 .101724 .18242);--shadow-md:0 4px 24px color(display-p3 0 0 0/.12), 0 0 0 1px var(--border-strong);--shadow-gold:0 0 16px color(display-p3 .542549 .371783 -.107551/.18), 0 0 0 1px color(display-p3 .542549 .371783 -.107551/.22)}}@supports (color:lab(0% 0 0)){:root{--bg:lab(96.5376% .34076 2.2589);--surface:lab(100% 0 0);--surface-2:lab(94.2232% .459731 3.01526);--border:lab(86.1085% .583261 3.77473);--border-strong:lab(74.5232% .975847 6.06879);--text:lab(5.23809% -.0738502 -2.43738);--text-secondary:lab(34.9842% -.210047 -4.49809);--muted:lab(46.6045% -.170082 -3.01461);--gold:lab(44.1995% 17.0662 84.3373);--gold-dim:lab(35.0077% 11.3181 50.9422);--gold-glow:lab(44.1995% 17.0662 84.3373/.25);--gold-subtle:lab(44.1995% 17.0662 84.3373/.1);--success:lab(41.7514% -55.3249 21.5039);--error:lab(37.5238% 64.2774 32.6097);--shadow-md:0 4px 24px lab(0% 0 0/.12), 0 0 0 1px var(--border-strong);--shadow-gold:0 0 16px lab(44.1995% 17.0662 84.3373/.18), 0 0 0 1px lab(44.1995% 17.0662 84.3373/.22)}}html,body{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;background-image:none}#bodygraph-container{border-color:var(--border-strong);background:#fff;background:color(display-p3 1 1 1);background:lab(100% 0 0)}.chart-status{background:rgba(247,245,241,.92);background:color(display-p3 .967616 .959866 .945308/.92);background:lab(96.5376% .34076 2.2589/.92);border-color:rgba(141,95,0,.35);border-color:color(display-p3 .542549 .371783 -.107551/.35);border-color:lab(44.1995% 17.0662 84.3373/.35)}.tooltip{border-color:var(--border-strong);background:rgba(255,255,255,.96);background:color(display-p3 1 1 1/.96);background:lab(100% 0 0/.96)}#info-panel{background:var(--surface);border-color:var(--border)}.hd-popup{background:var(--surface);border-color:var(--border-strong);box-shadow:0 12px 48px rgba(0,0,0,.18), 0 0 0 1px var(--border-strong)}@supports (color:color(display-p3 0 0 0)){.hd-popup{box-shadow:0 12px 48px color(display-p3 0 0 0/.18), 0 0 0 1px var(--border-strong)}}@supports (color:lab(0% 0 0)){.hd-popup{box-shadow:0 12px 48px lab(0% 0 0/.18), 0 0 0 1px var(--border-strong)}}#fs-bottom-bar{background:#f7f5f1;background:color(display-p3 .967616 .959866 .945308);background:lab(96.5376% .34076 2.2589);border-top-color:#caced4;border-top-color:color(display-p3 .796183 .806346 .830691);border-top-color:lab(82.5562% -.22167 -3.77336)}#fs-bottom-bar [data-column=left],#fs-bottom-bar [data-column=right]{background:#f1eeea;background:color(display-p3 .941472 .933762 .919279);background:lab(94.2176% .341028 2.25905)}#fs-bottom-bar [data-column=left]{border-right-color:#caced4;border-right-color:color(display-p3 .796183 .806346 .830691);border-right-color:lab(82.5562% -.22167 -3.77336)}#fs-bottom-bar [data-column=right]{border-left-color:#caced4;border-left-color:color(display-p3 .796183 .806346 .830691);border-left-color:lab(82.5562% -.22167 -3.77336)}#fs-bottom-bar [data-column]>*+*{border-top-color:#caced4;border-top-color:color(display-p3 .796183 .806346 .830691);border-top-color:lab(82.5562% -.22167 -3.77336)}}svg.has-highlight [data-center],svg.has-highlight [data-channel],svg.has-highlight [data-gate]{opacity:.45;transition:opacity .2s,filter .2s}svg.has-highlight [data-highlighted=true]{opacity:1;filter:drop-shadow(0 0 4px rgba(212,160,23,.55))}svg.has-highlight [data-highlighted-primary=true]{opacity:1;filter:drop-shadow(0 0 8px rgba(212,160,23,.95))}svg.has-highlight [data-highlighted-primary=true] path,svg.has-highlight [data-highlighted-primary=true] circle,svg.has-highlight [data-highlighted-primary=true] polygon,svg.has-highlight [data-highlighted-primary=true] rect{stroke-width:calc(var(--stroke-w,1.5) * 1.4)}.param-controls{background:var(--surface);border-top:1px solid var(--border);flex-shrink:0;padding:6px 12px}.param-controls-grid{flex-wrap:wrap;align-items:center;gap:6px 10px;display:flex}.param-group{flex-direction:row;align-items:center;gap:6px;display:flex}.param-group label{text-transform:uppercase;color:var(--text-secondary);letter-spacing:.06em;white-space:nowrap;font-size:10px;font-weight:700}.param-group input[type=date],.param-group input[type=time],.param-group input[type=text]{background:var(--surface-2);border:1px solid var(--border-strong);color:var(--text);color-scheme:inherit;border-radius:var(--radius-sm);width:120px;height:24px;padding:3px 6px;font-family:inherit;font-size:11px}@media (prefers-reduced-motion:no-preference){.param-group input[type=date],.param-group input[type=time],.param-group input[type=text]{transition:border-color .15s}}.param-group input:focus{border-color:var(--gold-dim);outline:none}.loc-searching{border-color:var(--gold-dim)!important}@media (prefers-reduced-motion:no-preference){.loc-searching{animation:1s ease-in-out infinite alternate loc-pulse}}@keyframes loc-pulse{0%{border-color:var(--gold-dim)}to{border-color:rgba(204,156,66,.55);border-color:color(display-p3 .76996 .617341 .321369/.55);border-color:lab(67.5798% 11.9379 52.333/.55)}}.param-group-loc{flex:1;position:relative}.param-group-loc input{width:100%}.loc-suggestions{background:var(--surface-2);border:1px solid var(--border-strong);border-radius:var(--radius-sm);z-index:100;max-height:200px;margin-top:4px;position:absolute;top:100%;left:0;right:0;overflow-y:auto;box-shadow:0 4px 12px rgba(0,0,0,.35);box-shadow:0 4px 12px color(display-p3 0 0 0/.35);box-shadow:0 4px 12px lab(0% 0 0/.35)}.loc-item{color:var(--text);cursor:pointer;white-space:nowrap;text-overflow:ellipsis;border-bottom:1px solid var(--border);padding:8px 10px;font-size:12px;overflow:hidden}.loc-item:last-child{border-bottom:none}@media (prefers-reduced-motion:no-preference){.loc-item{transition:background .1s,color .1s}}.loc-item:hover{background:var(--gold-subtle);color:var(--gold)}.loc-status{color:var(--text-secondary);letter-spacing:.01em;padding:8px 10px;font-size:11px;font-style:italic}.loc-status-error{color:#d76a5a;color:color(display-p3 .788241 .439621 .373983);color:lab(58.1265% 42.6447 29.9439);font-style:normal;font-weight:600}.param-actions{align-items:center;gap:6px;margin-left:auto;display:flex}.param-btn-icon{border:1px solid var(--border-strong);color:var(--text-secondary);border-radius:var(--radius-sm);cursor:pointer;background:0 0;justify-content:center;align-items:center;width:24px;height:24px;display:flex}@media (prefers-reduced-motion:no-preference){.param-btn-icon{transition:background .15s,border-color .15s,color .15s}}.param-btn-icon:hover{background:var(--surface-2);color:var(--text);border-color:var(--gold-dim)}.param-btn-primary{color:var(--gold);border-radius:var(--radius-sm);cursor:pointer;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;background:rgba(204,156,66,.12);background:color(display-p3 .76996 .617341 .321369/.12);background:lab(67.5798% 11.9379 52.333/.12);border:1px solid rgba(204,156,66,.4);border:1px solid color(display-p3 .76996 .617341 .321369/.4);border:1px solid lab(67.5798% 11.9379 52.333/.4);height:24px;padding:3px 10px;font-family:inherit;font-size:10px;font-weight:600}@media (prefers-reduced-motion:no-preference){.param-btn-primary{transition:background .15s,border-color .15s}}.param-btn-primary:hover:not(:disabled){background:rgba(204,156,66,.2);background:color(display-p3 .76996 .617341 .321369/.2);background:lab(67.5798% 11.9379 52.333/.2);border-color:rgba(204,156,66,.65);border-color:color(display-p3 .76996 .617341 .321369/.65);border-color:lab(67.5798% 11.9379 52.333/.65)}.param-btn-primary:disabled{opacity:.5;cursor:not-allowed}@media (prefers-color-scheme:light){.param-group input[type=date],.param-group input[type=time],.param-group input[type=text]{border-color:var(--border-strong);color:var(--text);--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;background:#fdfcf9;background:color(display-p3 .990365 .986474 .97916);background:lab(98.849% .167578 1.1277)}.loc-suggestions{border-color:var(--border-strong);background:#fdfcf9;background:color(display-p3 .990365 .986474 .97916);background:lab(98.849% .167578 1.1277)}.param-btn-primary{background:rgba(160,112,0,.12);background:color(display-p3 .615135 .441554 -.0204711/.12);background:lab(51.2092% 16.2654 78.3504/.12);border-color:rgba(160,112,0,.4);border-color:color(display-p3 .615135 .441554 -.0204711/.4);border-color:lab(51.2092% 16.2654 78.3504/.4)}.param-btn-primary:hover:not(:disabled){background:rgba(160,112,0,.22);background:color(display-p3 .615135 .441554 -.0204711/.22);background:lab(51.2092% 16.2654 78.3504/.22);border-color:rgba(160,112,0,.65);border-color:color(display-p3 .615135 .441554 -.0204711/.65);border-color:lab(51.2092% 16.2654 78.3504/.65)}}.param-summary-bar{border-bottom:1px solid var(--border);flex-wrap:wrap;align-items:baseline;gap:2px 0;margin-bottom:4px;padding:3px 4px 5px;font-size:10px;line-height:1.3;display:flex}.param-summary-bar[hidden]{display:none}.summary-name{color:var(--gold,#d6a54d);letter-spacing:.01em;padding-right:2px;font-size:12px;font-weight:700}@supports (color:color(display-p3 0 0 0)){.summary-name{color:var(--gold,color(display-p3 .808073 .654274 .358094))}}@supports (color:lab(0% 0 0)){.summary-name{color:var(--gold,lab(71.071% 11.7474 51.7651))}}.summary-datetime{font-variant-numeric:tabular-nums;color:var(--text-secondary);align-items:baseline;gap:4px;font-family:JetBrains Mono,Fira Code,ui-monospace,monospace;font-size:11px;display:inline-flex}.summary-time-sep{color:var(--border-strong);opacity:.7;font-family:inherit;font-size:9px}.summary-loc{color:var(--text-secondary);text-overflow:ellipsis;white-space:nowrap;max-width:200px;padding-left:2px;font-size:11px;font-style:italic;overflow:hidden}.summary-sep{background:var(--border-strong);opacity:.5;vertical-align:middle;flex-shrink:0;width:1px;height:11px;margin:0 7px;display:inline-block}
|
|
13
|
+
<style>:root{--bg:#010102;--surface:#030408;--surface-2:#080b11;--border:#13161d;--border-strong:#242933;--text:#e2e4e9;--text-secondary:#777a80;--muted:#5b5e62;--gold:#cc9c42;--gold-dim:#7b6540;--gold-glow:rgba(204,156,66,.2);--gold-subtle:rgba(204,156,66,.1);--hd-head:#855dd7;--hd-ajna:#3a61c4;--hd-throat:#94582a;--hd-g:#c99f00;--hd-heart:#cc272e;--hd-spleen:#3d9028;--hd-sp:#cb5a1a;--hd-sacral:#cc243d;--hd-root:#6f3600;--ch-personality:#79a1fc;--ch-design:#ac7d1b;--ch-both:#cc9c42;--ch-inactive:#1f2228;--success:#1da871;--error:#c53443;--radius:10px;--radius-sm:6px;--radius-pill:99px;--shadow-md:0 8px 40px rgba(0,0,0,.65), 0 0 0 1px var(--border-strong);--shadow-gold:0 0 28px rgba(204,156,66,.15), 0 0 0 1px rgba(204,156,66,.2)}@supports (color:color(display-p3 0 0 0)){:root{--bg:color(display-p3 .00306656 .00431968 .00888397);--surface:color(display-p3 .013072 .016989 .0298495);--surface-2:color(display-p3 .0345774 .0431759 .0653236);--border:color(display-p3 .0763929 .0859556 .109705);--border-strong:color(display-p3 .144788 .159634 .196542);--text:color(display-p3 .889546 .895785 .910689);--text-secondary:color(display-p3 .470023 .47923 .50135);--muted:color(display-p3 .359661 .366689 .383567);--gold:color(display-p3 .76996 .617341 .321369);--gold-dim:color(display-p3 .469305 .400291 .270966);--gold-glow:color(display-p3 .76996 .617341 .321369/.2);--gold-subtle:color(display-p3 .76996 .617341 .321369/.1);--hd-head:color(display-p3 .496597 .372031 .817324);--hd-ajna:color(display-p3 .26389 .378111 .742207);--hd-throat:color(display-p3 .548969 .355242 .201432);--hd-g:color(display-p3 .777513 .62422 -.00225984);--hd-heart:color(display-p3 .737101 .219083 .211834);--hd-spleen:color(display-p3 .328385 .55681 .222852);--hd-sp:color(display-p3 .739686 .377576 .178173);--hd-sacral:color(display-p3 .735577 .213396 .258614);--hd-root:color(display-p3 .406873 .223096 .0609673);--ch-personality:color(display-p3 .508025 .628538 .962447);--ch-design:color(display-p3 .645333 .49681 .196492);--ch-both:color(display-p3 .76996 .617341 .321369);--ch-inactive:color(display-p3 .122731 .131518 .153019);--success:color(display-p3 .313719 .651039 .459895);--error:color(display-p3 .710345 .252818 .279408);--shadow-md:0 8px 40px color(display-p3 0 0 0/.65), 0 0 0 1px var(--border-strong);--shadow-gold:0 0 28px color(display-p3 .76996 .617341 .321369/.15), 0 0 0 1px color(display-p3 .76996 .617341 .321369/.2)}}@supports (color:lab(0% 0 0)){:root{--bg:lab(.302122% .0219196 -.522962);--surface:lab(1.18159% .0276566 -1.4848);--surface-2:lab(3.00584% .0228807 -3.15676);--border:lab(7.15581% -.0557452 -5.11242);--border-strong:lab(16.3713% -.0768304 -7.37782);--text:lab(90.6942% -.142396 -2.26941);--text-secondary:lab(51.2352% -.205308 -3.76425);--muted:lab(39.6443% -.165641 -3.01217);--gold:lab(67.5798% 11.9379 52.333);--gold-dim:lab(44.394% 5.21129 24.4453);--gold-glow:lab(67.5798% 11.9379 52.333/.2);--gold-subtle:lab(67.5798% 11.9379 52.333/.1);--hd-head:lab(48.5159% 37.0934 -58.1781);--hd-ajna:lab(42.6654% 12.3083 -56.648);--hd-throat:lab(43.8304% 22.8022 36.6206);--hd-g:lab(67.813% 9.5214 93.33);--hd-heart:lab(45.7636% 63.3818 40.1799);--hd-spleen:lab(53.0764% -40.8778 44.314);--hd-sp:lab(52.4466% 43.5944 55.3775);--hd-sacral:lab(45.6635% 64.206 31.2074);--hd-root:lab(29.855% 23.6799 41.1297);--ch-personality:lab(66.4233% 5.15944 -50.1959);--ch-design:lab(55.9353% 12.6865 55.2442);--ch-both:lab(67.5798% 11.9379 52.333);--ch-inactive:lab(12.9398% -.133917 -4.46223);--success:lab(61.2436% -46.7762 18.3644);--error:lab(45.8893% 57.7361 27.393);--shadow-md:0 8px 40px lab(0% 0 0/.65), 0 0 0 1px var(--border-strong);--shadow-gold:0 0 28px lab(67.5798% 11.9379 52.333/.15), 0 0 0 1px lab(67.5798% 11.9379 52.333/.2)}}@media (prefers-color-scheme:light){:root{--bg:#f4f5f8;--surface:#fbfcfe;--surface-2:#eceef3;--border:#d5d7dd;--border-strong:#babec6;--text:#181b1f;--text-secondary:#4a4d54;--muted:#606369;--gold:#8a6000;--gold-dim:#967c4f;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}@supports (color:color(display-p3 0 0 0)){:root{--bg:color(display-p3 .956515 .960735 .970805);--surface:color(display-p3 .98376 .986943 .994535);--surface-2:color(display-p3 .928401 .934691 .949715);--border:color(display-p3 .836244 .844458 .864107);--border-strong:color(display-p3 .731542 .743532 .772301);--text:color(display-p3 .096883 .103987 .12134);--text-secondary:color(display-p3 .291758 .301882 .326359);--muted:color(display-p3 .379924 .388781 .410092);--gold:color(display-p3 .524671 .380491 .0387678);--gold-dim:color(display-p3 .572756 .489268 .332751)}}@supports (color:lab(0% 0 0)){:root{--bg:lab(96.503% -.0977516 -1.51461);--surface:lab(98.8273% -.0742972 -1.13657);--surface-2:lab(94.1743% -.142723 -2.26961);--border:lab(86.0453% -.183761 -3.02231);--border-strong:lab(76.7468% -.254452 -4.52154);--text:lab(9.4703% -.11944 -3.722);--text-secondary:lab(32.6639% -.205338 -4.49569);--muted:lab(41.9548% -.197008 -3.7598);--gold:lab(44.2758% 13.6718 62.9124);--gold-dim:lab(53.685% 6.10271 28.558)}}html,body{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}#bodygraph-container{background:var(--surface)}.chart-status,.tooltip{background:rgba(251,252,254,.92);background:color(display-p3 .98376 .986943 .994535/.92);background:lab(98.8273% -.0742972 -1.13657/.92)}#fs-bottom-bar{background:var(--surface-2)}}*,:before,:after{box-sizing:border-box;margin:0;padding:0}[hidden]{display:none!important}html,body{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;background:var(--bg);height:100%;color:var(--text);background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.030'/%3E%3C/svg%3E");font-family:Geist,Space Grotesk,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Noto Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:13px;line-height:1.5;overflow-x:hidden}#app{flex-direction:column;gap:8px;min-height:0;padding:8px 12px;display:flex}#loading-state{min-height:300px;color:var(--muted);flex-direction:column;justify-content:center;align-items:center;gap:14px;display:flex}#loading-state p{letter-spacing:.06em;text-transform:uppercase;font-size:11px}.spinner{border:2px solid var(--border-strong);border-top-color:var(--gold);border-radius:50%;width:32px;height:32px;animation:1.1s cubic-bezier(.4,0,.6,1) infinite spin}@keyframes spin{to{transform:rotate(360deg)}}#bodygraph-container{border-radius:var(--radius);border:1px solid var(--border-strong);width:100%;max-width:680px;box-shadow:var(--shadow-gold);background:#010102;background:color(display-p3 .00306656 .00431968 .00888397);background:lab(.302122% .0219196 -.522962);margin:0 auto;transition:box-shadow .3s;position:relative;overflow:visible}#bodygraph-container:hover{box-shadow:0 0 40px rgba(204,156,66,.22),0 0 0 1px rgba(204,156,66,.3);box-shadow:0 0 40px color(display-p3 .76996 .617341 .321369/.22),0 0 0 1px color(display-p3 .76996 .617341 .321369/.3);box-shadow:0 0 40px lab(67.5798% 11.9379 52.333/.22),0 0 0 1px lab(67.5798% 11.9379 52.333/.3)}#bodygraph-container svg{width:100%;height:auto;max-height:min(62vh,560px);display:block}.center-group{cursor:pointer;transition:filter .16s}.center-group:hover{filter:brightness(1.2)drop-shadow(0 0 6px rgba(204,156,66,.35));filter:brightness(1.2)drop-shadow(0 0 6px color(display-p3 .76996 .617341 .321369/.35));filter:brightness(1.2)drop-shadow(0 0 6px lab(67.5798% 11.9379 52.333/.35))}.channel-line{cursor:pointer}.channel-line:hover .channel-vis{filter:drop-shadow(0 0 4px var(--gold-glow));stroke:var(--gold)!important;stroke-width:5px!important}.gate-badge{cursor:pointer}.gate-badge:hover circle{stroke:var(--gold)!important;stroke-opacity:1!important}.gate-badge:hover text{fill:var(--gold)!important}#info-panel{background:var(--surface);border:1px solid var(--border-strong);border-radius:var(--radius);grid-template-columns:1fr 1fr;gap:6px 12px;width:100%;max-width:680px;margin:0 auto;padding:10px 14px;display:grid}.info-row{flex-direction:column;gap:1px;display:flex}.info-label{letter-spacing:.08em;text-transform:uppercase;color:var(--muted);font-size:9px;font-weight:700}.info-value{color:var(--text);font-size:12px;font-weight:500}.channel-legend{flex-wrap:wrap;justify-content:center;gap:12px;width:100%;max-width:680px;margin:0 auto;padding:4px 0;display:flex}.legend-item{color:var(--text-secondary);letter-spacing:.04em;align-items:center;gap:5px;font-size:9px;font-weight:500;display:flex}.legend-swatch{border-radius:2px;flex-shrink:0;width:18px;height:3px}#controls-panel{background:0 0;width:100%;max-width:680px;margin:0 auto;padding:4px 0}.controls-row{align-items:center;gap:6px;display:flex}.controls-label{letter-spacing:.08em;text-transform:uppercase;color:var(--muted);flex-shrink:0;font-size:9px;font-weight:700}.controls-value{color:var(--text-secondary);font-size:11px}.chart-hint{text-align:center;letter-spacing:.04em;width:100%;max-width:680px;color:var(--muted);margin:0 auto;padding:4px 0 2px;font-size:10px}.error-state{min-height:200px;color:var(--muted);text-align:center;flex-direction:column;justify-content:center;align-items:center;gap:8px;padding:24px;display:flex}.error-icon{opacity:.5;font-size:24px}.error-retry-btn{border:1px solid var(--border,rgba(128,128,160,.3));color:var(--accent,var(--text,inherit));font:inherit;letter-spacing:.02em;cursor:pointer;background:0 0;border-radius:999px;margin-top:4px;padding:7px 20px;font-size:11px;font-weight:600;transition:background .18s,border-color .18s}.error-retry-btn:hover{background:rgba(128,128,160,.12)}@media (max-width:420px){#app{gap:8px;padding:8px}#info-panel{grid-template-columns:1fr}}#bodygraph-container.is-loading{opacity:.55;pointer-events:none;cursor:wait;transition:opacity .15s}.chart-status{-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);border-radius:var(--radius-pill);color:var(--gold);letter-spacing:.04em;z-index:900;opacity:0;pointer-events:none;background:rgba(5,7,13,.92);background:color(display-p3 .0216592 .0280553 .0483601/.92);background:lab(1.95095% .0425801 -2.41514/.92);border:1px solid rgba(204,156,66,.35);border:1px solid color(display-p3 .76996 .617341 .321369/.35);border:1px solid lab(67.5798% 11.9379 52.333/.35);max-height:0;padding:5px 18px;font-size:11px;font-weight:600;transition:max-height .24s cubic-bezier(.16,1,.3,1),opacity .24s,transform .24s cubic-bezier(.16,1,.3,1);position:fixed;top:10px;left:50%;overflow:hidden;transform:translate(-50%)translateY(-10px);box-shadow:0 4px 20px rgba(0,0,0,.5),0 0 0 1px rgba(204,156,66,.08);box-shadow:0 4px 20px color(display-p3 0 0 0/.5),0 0 0 1px color(display-p3 .76996 .617341 .321369/.08);box-shadow:0 4px 20px lab(0% 0 0/.5),0 0 0 1px lab(67.5798% 11.9379 52.333/.08)}.chart-status.visible{opacity:1;max-height:44px;transform:translate(-50%)translateY(0)}@keyframes gold-pulse{0%,to{border-color:rgba(204,156,66,.35);border-color:color(display-p3 .76996 .617341 .321369/.35);border-color:lab(67.5798% 11.9379 52.333/.35)}50%{border-color:rgba(204,156,66,.65);border-color:color(display-p3 .76996 .617341 .321369/.65);border-color:lab(67.5798% 11.9379 52.333/.65)}}.hd-popup{background:var(--surface-2);border:1px solid var(--border-strong);z-index:1000;opacity:0;pointer-events:none;border-radius:12px;width:min(340px,92vw);padding:14px 16px 12px;transition:opacity .2s cubic-bezier(.16,1,.3,1),transform .2s cubic-bezier(.16,1,.3,1);position:fixed;box-shadow:0 12px 48px rgba(0,0,0,.7),0 0 0 1px rgba(204,156,66,.15);box-shadow:0 12px 48px color(display-p3 0 0 0/.7),0 0 0 1px color(display-p3 .76996 .617341 .321369/.15);box-shadow:0 12px 48px lab(0% 0 0/.7),0 0 0 1px lab(67.5798% 11.9379 52.333/.15)}.hd-popup.visible{opacity:1;pointer-events:all}.hd-popup-header{align-items:flex-start;gap:10px;display:flex}.hd-popup-icon{color:var(--gold);text-align:center;flex-shrink:0;min-width:28px;font-size:18px;font-weight:700;line-height:1}.hd-popup-title{color:var(--text);flex:1;font-size:13px;font-weight:700}.hd-popup-sub{color:var(--text-secondary);letter-spacing:.01em;margin-top:2px;font-size:11px;font-weight:400}.hd-popup-close{cursor:pointer;color:var(--muted);background:0 0;border:none;border-radius:4px;flex-shrink:0;padding:2px 4px;font-size:15px;line-height:1;transition:color .12s}.hd-popup-close:hover{color:var(--text)}.hd-popup-body{border-top:1px solid var(--border);flex-direction:column;gap:6px;margin-top:10px;padding-top:10px;display:flex}.hd-popup-row{align-items:baseline;gap:8px;font-size:11px;line-height:1.45;display:flex}.hd-popup-label{letter-spacing:.06em;text-transform:uppercase;color:var(--text-secondary);flex-shrink:0;min-width:56px;padding-top:1px;font-size:9px;font-weight:700}.hd-popup-val{color:var(--text);font-weight:500}.hd-popup-gates{flex-wrap:wrap;gap:4px;font-weight:500;display:flex}.hd-popup-gate-chip{border:1px solid var(--border-strong);border-radius:var(--radius-pill,99px);background:var(--surface);color:var(--text);white-space:nowrap;padding:1px 7px;font-size:10px;display:inline-block}.hd-popup-note{color:var(--text-secondary);letter-spacing:.01em;margin-top:4px;font-size:10px;font-style:italic;line-height:1.5}.hd-detail-panel{scrollbar-width:thin;scrollbar-color:var(--border-strong) transparent;flex:1;min-height:0;padding:2px 0 8px;overflow-y:auto}.hd-detail-panel .hd-popup-header{border-bottom:1px solid var(--border);margin-bottom:8px;padding-bottom:8px}.hd-detail-panel .hd-popup-close{opacity:.5}.hd-detail-panel .hd-popup-close:hover{opacity:1;color:var(--text)}.ask-claude-btn{border-radius:var(--radius-sm);width:100%;color:var(--gold);letter-spacing:.04em;cursor:pointer;background:rgba(204,156,66,.12);background:color(display-p3 .76996 .617341 .321369/.12);background:lab(67.5798% 11.9379 52.333/.12);border:1px solid rgba(204,156,66,.4);border:1px solid color(display-p3 .76996 .617341 .321369/.4);border:1px solid lab(67.5798% 11.9379 52.333/.4);justify-content:center;align-items:center;gap:6px;margin-top:12px;padding:9px 16px;font-family:Geist,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Noto Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:12px;font-weight:600;transition:background .16s,border-color .16s,box-shadow .16s;display:flex}.ask-claude-btn:hover:not(:disabled){background:rgba(204,156,66,.2);background:color(display-p3 .76996 .617341 .321369/.2);background:lab(67.5798% 11.9379 52.333/.2);border-color:rgba(204,156,66,.65);border-color:color(display-p3 .76996 .617341 .321369/.65);border-color:lab(67.5798% 11.9379 52.333/.65);box-shadow:0 0 0 3px rgba(204,156,66,.12);box-shadow:0 0 0 3px color(display-p3 .76996 .617341 .321369/.12);box-shadow:0 0 0 3px lab(67.5798% 11.9379 52.333/.12)}.ask-claude-btn:disabled{opacity:.45;cursor:not-allowed}.ask-claude-btn.sending{color:var(--gold-dim);opacity:.8;cursor:wait;pointer-events:none;background:rgba(204,156,66,.08);background:color(display-p3 .76996 .617341 .321369/.08);background:lab(67.5798% 11.9379 52.333/.08);border-color:rgba(204,156,66,.25);border-color:color(display-p3 .76996 .617341 .321369/.25);border-color:lab(67.5798% 11.9379 52.333/.25)}.ask-claude-btn.sent{color:var(--success);pointer-events:none;background:rgba(29,168,113,.15);background:color(display-p3 .313719 .651039 .459895/.15);background:lab(61.2436% -46.7762 18.3644/.15);border-color:rgba(29,168,113,.5);border-color:color(display-p3 .313719 .651039 .459895/.5);border-color:lab(61.2436% -46.7762 18.3644/.5)}.ask-claude-btn.error{color:var(--error);cursor:pointer;background:rgba(197,52,67,.15);background:color(display-p3 .710345 .252818 .279408/.15);background:lab(45.8893% 57.7361 27.393/.15);border-color:rgba(197,52,67,.5);border-color:color(display-p3 .710345 .252818 .279408/.5);border-color:lab(45.8893% 57.7361 27.393/.5)}.interactive-text{cursor:pointer;transition:color .15s,text-shadow .15s;position:relative}.interactive-text:hover{color:var(--gold);text-shadow:0 0 8px rgba(204,156,66,.4);text-shadow:0 0 8px color(display-p3 .76996 .617341 .321369/.4);text-shadow:0 0 8px lab(67.5798% 11.9379 52.333/.4)}.interactive-text:focus{color:var(--gold);text-shadow:0 0 8px rgba(204,156,66,.6);text-shadow:0 0 8px color(display-p3 .76996 .617341 .321369/.6);text-shadow:0 0 8px lab(67.5798% 11.9379 52.333/.6);outline:none}.planet-activation,.variable-arrow{outline:none;transition:opacity .15s}.planet-activation:hover,.variable-arrow:hover{opacity:.7}.planet-activation:focus-visible{outline:2px solid var(--gold);outline-offset:4px}.variable-arrow:focus-visible{outline:2px solid var(--gold);outline-offset:4px}.center-group:focus-visible{outline:2px solid var(--gold);outline-offset:4px}.gate-badge:focus-visible{outline:2px solid var(--gold);outline-offset:4px}.channel:focus-visible{outline:2px solid var(--gold);outline-offset:4px}[data-gate]:focus-visible{outline:2px solid var(--gold);outline-offset:4px}[data-channel]:focus-visible{outline:2px solid var(--gold);outline-offset:4px}[data-center]:focus-visible{outline:2px solid var(--gold);outline-offset:4px}.tooltip{-webkit-backdrop-filter:blur(14px)saturate(1.6);backdrop-filter:blur(14px)saturate(1.6);border:1px solid var(--border-strong);border-radius:var(--radius);pointer-events:none;z-index:1000;opacity:0;background:rgba(7,9,14,.92);background:color(display-p3 .0286832 .0352525 .0537785/.92);background:lab(2.44746% .00379235 -2.33402/.92);max-width:240px;padding:8px 12px;font-size:12px;transition:opacity .12s;position:fixed;box-shadow:0 8px 32px rgba(0,0,0,.55),inset 0 .5px rgba(255,255,255,.04);box-shadow:0 8px 32px color(display-p3 0 0 0/.55),inset 0 .5px color(display-p3 1 1 1/.04);box-shadow:0 8px 32px lab(0% 0 0/.55),inset 0 .5px lab(100% 0 0/.04)}.tooltip.visible{opacity:1}.tooltip-title{color:var(--text);margin-bottom:2px;font-weight:700}.tooltip-detail{color:var(--text-secondary);line-height:1.4}.tooltip-hint{color:var(--gold-dim);letter-spacing:.03em;margin-top:4px;font-size:10px}.pan-zoom-controls{z-index:800;flex-direction:column;gap:6px;display:flex;position:absolute;bottom:12px;right:12px}.pan-zoom-controls[hidden]{display:none!important}.pan-btn{background:var(--surface-2);border:1px solid var(--border-strong);color:var(--text);border-radius:var(--radius-sm);cursor:pointer;width:36px;height:36px;box-shadow:var(--shadow-md);justify-content:center;align-items:center;font-family:inherit;font-size:18px;transition:all .15s;display:flex}.pan-btn.reset{width:auto;padding:0 10px;font-size:11px;font-weight:500}.pan-btn:hover{background:var(--surface);border-color:var(--gold-dim);color:var(--gold)}.pan-btn:active{transform:scale(.95)}.pan-btn--fullscreen{width:36px;height:36px}body.is-fullscreen{height:100vh;overflow:hidden}body.is-fullscreen #app{flex-direction:column;gap:0;width:100vw;max-width:100%;height:100vh;padding:0;display:flex;overflow:hidden}body.is-fullscreen #bodygraph-container{min-height:0;max-width:unset;width:100%;height:auto;box-shadow:none;background:0 0;border:none;flex:1;justify-content:center;align-items:center;margin:0;padding:16px 24px 0;display:flex}body.is-fullscreen #loading-state{flex:1;height:auto;min-height:0}body.is-fullscreen #bodygraph-container svg{width:auto;max-width:100%;height:100%;max-height:100%}body.is-fullscreen .pan-zoom-controls{top:16px;right:16px}#fs-bottom-bar{border-top:1px solid var(--border);background:#020306;background:color(display-p3 .0100648 .0127997 .0215356);background:lab(.889429% .0122339 -1.01076);flex-shrink:0;grid-template-rows:1fr;grid-template-columns:220px 1fr 220px;height:240px;min-height:180px;max-height:280px;display:grid}#fs-bottom-bar [data-column=left],#fs-bottom-bar [data-column=right]{scrollbar-width:thin;scrollbar-color:var(--border-strong) transparent;flex-direction:column;gap:4px;padding:10px 14px 12px;display:flex;overflow-x:hidden;overflow-y:auto}#fs-bottom-bar [data-column=left]{border-right:1px solid var(--border)}#fs-bottom-bar [data-column=center]{pointer-events:none}#fs-bottom-bar [data-column=right]{border-left:1px solid var(--border);overflow-y:auto}#fs-bottom-bar [data-column]>*+*{border-top:1px solid var(--border);margin-top:4px;padding-top:8px}#fs-bottom-bar .hd-action-bar{flex-direction:column;padding:8px 0 0}#fs-bottom-bar .action-btn{justify-content:center;width:100%}.cancel-toast{background:var(--surface-2,#1e1e2e);border:1px solid var(--border-strong,#444);color:var(--text-muted,#aaa);border-radius:var(--radius-sm,6px);opacity:0;z-index:9999;pointer-events:none;box-shadow:var(--shadow-md,0 4px 12px rgba(0,0,0,.3));padding:8px 16px;font-family:inherit;font-size:12px;transition:opacity .3s,transform .3s;position:fixed;bottom:20px;left:50%;transform:translate(-50%)translateY(20px)}.cancel-toast.visible{opacity:1;transform:translate(-50%)translateY(0)}.oe-loader{width:64px;height:64px;stroke:var(--gold);stroke-width:2px;fill:none;stroke-dasharray:200;stroke-dashoffset:200px;filter:drop-shadow(0 0 4px var(--gold-glow));animation:2.5s cubic-bezier(.16,1,.3,1) infinite oe-draw}@keyframes oe-draw{0%{stroke-dashoffset:200px;stroke-opacity:.1}50%{stroke-dashoffset:0;stroke-opacity:1;filter:drop-shadow(0 0 8px var(--gold))}to{stroke-dashoffset:-200px;stroke-opacity:.1}}.info-drawer{background:var(--surface-2);border:1px solid var(--border-strong);border-radius:var(--radius);width:100%;box-shadow:var(--shadow-md);opacity:0;pointer-events:none;max-height:0;padding:14px 16px 12px;transition:max-height .26s cubic-bezier(.16,1,.3,1),opacity .2s,padding .2s;position:relative;overflow:hidden}.info-drawer:before{content:"";background:var(--border-strong);border-radius:2px;flex-shrink:0;width:36px;height:4px;margin:0 auto 10px;display:block}.info-drawer.open{opacity:1;pointer-events:all;max-height:500px;padding:14px 16px 12px}.hd-action-bar{flex-wrap:wrap;gap:8px;padding:10px 16px 6px;display:flex}.action-btn{color:var(--gold);letter-spacing:.02em;cursor:pointer;white-space:nowrap;background:rgba(212,175,55,.08);border:1px solid rgba(212,175,55,.3);border-radius:8px;align-items:center;gap:6px;padding:7px 16px;font-size:12px;font-weight:600;transition:background .2s,border-color .2s,transform .1s;display:inline-flex}.action-btn:hover{background:rgba(212,175,55,.15);border-color:rgba(212,175,55,.5);transform:translateY(-1px)}.action-btn:active{background:rgba(212,175,55,.2);transform:translateY(0)}.action-btn--primary{color:var(--gold);background:linear-gradient(135deg,rgba(212,175,55,.2),rgba(180,140,30,.25));border-color:rgba(212,175,55,.5)}.action-btn--primary:hover{background:linear-gradient(135deg,rgba(212,175,55,.3),rgba(180,140,30,.35));border-color:rgba(212,175,55,.7)}@media (prefers-color-scheme:light){:root{--bg:#f7f5f1;--surface:#fff;--surface-2:#f1eee9;--border:#dbd7d0;--border-strong:#bdb6ac;--text:#101215;--text-secondary:#4f5359;--muted:#6c6f73;--gold:#8d5f00;--gold-dim:#6d4b00;--gold-glow:rgba(141,95,0,.25);--gold-subtle:rgba(141,95,0,.1);--success:#007145;--error:#b1002b;--shadow-md:0 4px 24px rgba(0,0,0,.12), 0 0 0 1px var(--border-strong);--shadow-gold:0 0 16px rgba(141,95,0,.18), 0 0 0 1px rgba(141,95,0,.22)}@supports (color:color(display-p3 0 0 0)){:root{--bg:color(display-p3 .967616 .959866 .945308);--surface:color(display-p3 1 1 1);--surface-2:color(display-p3 .943798 .93351 .914195);--border:color(display-p3 .855616 .842987 .819292);--border-strong:color(display-p3 .73602 .716348 .67952);--text:color(display-p3 .0633107 .0687189 .081922);--text-secondary:color(display-p3 .313002 .323251 .348016);--muted:color(display-p3 .426318 .433563 .450948);--gold:color(display-p3 .542549 .371783 -.107551);--gold-dim:color(display-p3 .414099 .299019 .034461);--gold-glow:color(display-p3 .542549 .371783 -.107551/.25);--gold-subtle:color(display-p3 .542549 .371783 -.107551/.1);--success:color(display-p3 -.0793084 .456092 .255685);--error:color(display-p3 .642198 .101724 .18242);--shadow-md:0 4px 24px color(display-p3 0 0 0/.12), 0 0 0 1px var(--border-strong);--shadow-gold:0 0 16px color(display-p3 .542549 .371783 -.107551/.18), 0 0 0 1px color(display-p3 .542549 .371783 -.107551/.22)}}@supports (color:lab(0% 0 0)){:root{--bg:lab(96.5376% .34076 2.2589);--surface:lab(100% 0 0);--surface-2:lab(94.2232% .459731 3.01526);--border:lab(86.1085% .583261 3.77473);--border-strong:lab(74.5232% .975847 6.06879);--text:lab(5.23809% -.0738502 -2.43738);--text-secondary:lab(34.9842% -.210047 -4.49809);--muted:lab(46.6045% -.170082 -3.01461);--gold:lab(44.1995% 17.0662 84.3373);--gold-dim:lab(35.0077% 11.3181 50.9422);--gold-glow:lab(44.1995% 17.0662 84.3373/.25);--gold-subtle:lab(44.1995% 17.0662 84.3373/.1);--success:lab(41.7514% -55.3249 21.5039);--error:lab(37.5238% 64.2774 32.6097);--shadow-md:0 4px 24px lab(0% 0 0/.12), 0 0 0 1px var(--border-strong);--shadow-gold:0 0 16px lab(44.1995% 17.0662 84.3373/.18), 0 0 0 1px lab(44.1995% 17.0662 84.3373/.22)}}html,body{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;background-image:none}#bodygraph-container{border-color:var(--border-strong);background:#fff;background:color(display-p3 1 1 1);background:lab(100% 0 0)}.chart-status{background:rgba(247,245,241,.92);background:color(display-p3 .967616 .959866 .945308/.92);background:lab(96.5376% .34076 2.2589/.92);border-color:rgba(141,95,0,.35);border-color:color(display-p3 .542549 .371783 -.107551/.35);border-color:lab(44.1995% 17.0662 84.3373/.35)}.tooltip{border-color:var(--border-strong);background:rgba(255,255,255,.96);background:color(display-p3 1 1 1/.96);background:lab(100% 0 0/.96)}#info-panel{background:var(--surface);border-color:var(--border)}.hd-popup{background:var(--surface);border-color:var(--border-strong);box-shadow:0 12px 48px rgba(0,0,0,.18), 0 0 0 1px var(--border-strong)}@supports (color:color(display-p3 0 0 0)){.hd-popup{box-shadow:0 12px 48px color(display-p3 0 0 0/.18), 0 0 0 1px var(--border-strong)}}@supports (color:lab(0% 0 0)){.hd-popup{box-shadow:0 12px 48px lab(0% 0 0/.18), 0 0 0 1px var(--border-strong)}}#fs-bottom-bar{background:#f7f5f1;background:color(display-p3 .967616 .959866 .945308);background:lab(96.5376% .34076 2.2589);border-top-color:#caced4;border-top-color:color(display-p3 .796183 .806346 .830691);border-top-color:lab(82.5562% -.22167 -3.77336)}#fs-bottom-bar [data-column=left],#fs-bottom-bar [data-column=right]{background:#f1eeea;background:color(display-p3 .941472 .933762 .919279);background:lab(94.2176% .341028 2.25905)}#fs-bottom-bar [data-column=left]{border-right-color:#caced4;border-right-color:color(display-p3 .796183 .806346 .830691);border-right-color:lab(82.5562% -.22167 -3.77336)}#fs-bottom-bar [data-column=right]{border-left-color:#caced4;border-left-color:color(display-p3 .796183 .806346 .830691);border-left-color:lab(82.5562% -.22167 -3.77336)}#fs-bottom-bar [data-column]>*+*{border-top-color:#caced4;border-top-color:color(display-p3 .796183 .806346 .830691);border-top-color:lab(82.5562% -.22167 -3.77336)}}svg.has-highlight [data-center],svg.has-highlight [data-channel],svg.has-highlight [data-gate]{opacity:.45;transition:opacity .2s,filter .2s}svg.has-highlight [data-highlighted=true]{opacity:1;filter:drop-shadow(0 0 4px rgba(212,160,23,.55))}svg.has-highlight [data-highlighted-primary=true]{opacity:1;filter:drop-shadow(0 0 8px rgba(212,160,23,.95))}svg.has-highlight [data-highlighted-primary=true] path,svg.has-highlight [data-highlighted-primary=true] circle,svg.has-highlight [data-highlighted-primary=true] polygon,svg.has-highlight [data-highlighted-primary=true] rect{stroke-width:calc(var(--stroke-w,1.5) * 1.4)}.param-controls{background:var(--surface);border-top:1px solid var(--border);flex-shrink:0;padding:6px 12px}.param-controls-grid{flex-wrap:wrap;align-items:center;gap:6px 10px;display:flex}.param-group{flex-direction:row;align-items:center;gap:6px;display:flex}.param-group label{text-transform:uppercase;color:var(--text-secondary);letter-spacing:.06em;white-space:nowrap;font-size:10px;font-weight:700}.param-group input[type=date],.param-group input[type=time],.param-group input[type=text]{background:var(--surface-2);border:1px solid var(--border-strong);color:var(--text);color-scheme:inherit;border-radius:var(--radius-sm);width:120px;height:24px;padding:3px 6px;font-family:inherit;font-size:11px}@media (prefers-reduced-motion:no-preference){.param-group input[type=date],.param-group input[type=time],.param-group input[type=text]{transition:border-color .15s}}.param-group input:focus{border-color:var(--gold-dim);outline:none}.loc-searching{border-color:var(--gold-dim)!important}@media (prefers-reduced-motion:no-preference){.loc-searching{animation:1s ease-in-out infinite alternate loc-pulse}}@keyframes loc-pulse{0%{border-color:var(--gold-dim)}to{border-color:rgba(204,156,66,.55);border-color:color(display-p3 .76996 .617341 .321369/.55);border-color:lab(67.5798% 11.9379 52.333/.55)}}.param-group-loc{flex:1;position:relative}.param-group-loc input{width:100%}.loc-suggestions{background:var(--surface-2);border:1px solid var(--border-strong);border-radius:var(--radius-sm);z-index:100;max-height:200px;margin-top:4px;position:absolute;top:100%;left:0;right:0;overflow-y:auto;box-shadow:0 4px 12px rgba(0,0,0,.35);box-shadow:0 4px 12px color(display-p3 0 0 0/.35);box-shadow:0 4px 12px lab(0% 0 0/.35)}.loc-item{color:var(--text);cursor:pointer;white-space:nowrap;text-overflow:ellipsis;border-bottom:1px solid var(--border);padding:8px 10px;font-size:12px;overflow:hidden}.loc-item:last-child{border-bottom:none}@media (prefers-reduced-motion:no-preference){.loc-item{transition:background .1s,color .1s}}.loc-item:hover{background:var(--gold-subtle);color:var(--gold)}.loc-status{color:var(--text-secondary);letter-spacing:.01em;padding:8px 10px;font-size:11px;font-style:italic}.loc-status-error{color:#d76a5a;color:color(display-p3 .788241 .439621 .373983);color:lab(58.1265% 42.6447 29.9439);font-style:normal;font-weight:600}.param-actions{align-items:center;gap:6px;margin-left:auto;display:flex}.param-btn-icon{border:1px solid var(--border-strong);color:var(--text-secondary);border-radius:var(--radius-sm);cursor:pointer;background:0 0;justify-content:center;align-items:center;width:24px;height:24px;display:flex}@media (prefers-reduced-motion:no-preference){.param-btn-icon{transition:background .15s,border-color .15s,color .15s}}.param-btn-icon:hover{background:var(--surface-2);color:var(--text);border-color:var(--gold-dim)}.param-btn-primary{color:var(--gold);border-radius:var(--radius-sm);cursor:pointer;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;background:rgba(204,156,66,.12);background:color(display-p3 .76996 .617341 .321369/.12);background:lab(67.5798% 11.9379 52.333/.12);border:1px solid rgba(204,156,66,.4);border:1px solid color(display-p3 .76996 .617341 .321369/.4);border:1px solid lab(67.5798% 11.9379 52.333/.4);height:24px;padding:3px 10px;font-family:inherit;font-size:10px;font-weight:600}@media (prefers-reduced-motion:no-preference){.param-btn-primary{transition:background .15s,border-color .15s}}.param-btn-primary:hover:not(:disabled){background:rgba(204,156,66,.2);background:color(display-p3 .76996 .617341 .321369/.2);background:lab(67.5798% 11.9379 52.333/.2);border-color:rgba(204,156,66,.65);border-color:color(display-p3 .76996 .617341 .321369/.65);border-color:lab(67.5798% 11.9379 52.333/.65)}.param-btn-primary:disabled{opacity:.5;cursor:not-allowed}@media (prefers-color-scheme:light){.param-group input[type=date],.param-group input[type=time],.param-group input[type=text]{border-color:var(--border-strong);color:var(--text);--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;background:#fdfcf9;background:color(display-p3 .990365 .986474 .97916);background:lab(98.849% .167578 1.1277)}.loc-suggestions{border-color:var(--border-strong);background:#fdfcf9;background:color(display-p3 .990365 .986474 .97916);background:lab(98.849% .167578 1.1277)}.param-btn-primary{background:rgba(160,112,0,.12);background:color(display-p3 .615135 .441554 -.0204711/.12);background:lab(51.2092% 16.2654 78.3504/.12);border-color:rgba(160,112,0,.4);border-color:color(display-p3 .615135 .441554 -.0204711/.4);border-color:lab(51.2092% 16.2654 78.3504/.4)}.param-btn-primary:hover:not(:disabled){background:rgba(160,112,0,.22);background:color(display-p3 .615135 .441554 -.0204711/.22);background:lab(51.2092% 16.2654 78.3504/.22);border-color:rgba(160,112,0,.65);border-color:color(display-p3 .615135 .441554 -.0204711/.65);border-color:lab(51.2092% 16.2654 78.3504/.65)}}.param-summary-bar{border-bottom:1px solid var(--border);flex-wrap:wrap;align-items:baseline;gap:2px 0;margin-bottom:4px;padding:3px 4px 5px;font-size:10px;line-height:1.3;display:flex}.param-summary-bar[hidden]{display:none}.summary-name{color:var(--gold,#d6a54d);letter-spacing:.01em;padding-right:2px;font-size:12px;font-weight:700}@supports (color:color(display-p3 0 0 0)){.summary-name{color:var(--gold,color(display-p3 .808073 .654274 .358094))}}@supports (color:lab(0% 0 0)){.summary-name{color:var(--gold,lab(71.071% 11.7474 51.7651))}}.summary-datetime{font-variant-numeric:tabular-nums;color:var(--text-secondary);align-items:baseline;gap:4px;font-family:JetBrains Mono,Fira Code,ui-monospace,monospace;font-size:11px;display:inline-flex}.summary-time-sep{color:var(--border-strong);opacity:.7;font-family:inherit;font-size:9px}.summary-loc{color:var(--text-secondary);text-overflow:ellipsis;white-space:nowrap;max-width:200px;padding-left:2px;font-size:11px;font-style:italic;overflow:hidden}.summary-sep{background:var(--border-strong);opacity:.5;vertical-align:middle;flex-shrink:0;width:1px;height:11px;margin:0 7px;display:inline-block}
|
|
14
14
|
/*$vite$:1*/</style>
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="app">
|
|
18
18
|
<div id="loading-state">
|
|
19
19
|
<svg class="oe-loader" viewBox="0 0 100 100"><circle cx="50" cy="50" r="45"/></svg>
|
|
20
|
-
<p>
|
|
20
|
+
<p>Drawing your bodygraph…</p>
|
|
21
21
|
</div>
|
|
22
22
|
<div id="chart-status" class="chart-status" aria-live="polite"></div>
|
|
23
23
|
<div id="bodygraph-container" hidden>
|
|
@@ -6678,7 +6678,7 @@ function x_(e4, t2, n2, r2) {
|
|
|
6678
6678
|
l2.style.cssText = "width:100%;height:100%;", l2.innerHTML = t2;
|
|
6679
6679
|
let u2 = l2.querySelector("svg");
|
|
6680
6680
|
if (!u2) return console.error("[wire-go-svg] No <svg> element found in payload"), null;
|
|
6681
|
-
u2.setAttribute("width", "100%"), u2.setAttribute("height", "100%"), u2.setAttribute("role", "
|
|
6681
|
+
u2.setAttribute("width", "100%"), u2.setAttribute("height", "100%"), u2.setAttribute("role", "group"), u2.setAttribute("aria-label", "Human Design Bodygraph"), u2.addEventListener("click", (e5) => e5.stopPropagation()), e4.appendChild(u2), v_(u2, "pan-zoom-controls"), new Set((a2 = n2.personality_gates) == null ? [] : a2), new Set((o2 = n2.design_gates) == null ? [] : o2);
|
|
6682
6682
|
let d2 = /* @__PURE__ */ new Set();
|
|
6683
6683
|
for (let e5 of (s2 = n2.centers) == null ? [] : s2) e5.defined && d2.add(e5.name);
|
|
6684
6684
|
let f2 = {};
|
|
@@ -7261,7 +7261,7 @@ function uv(e4, t2) {
|
|
|
7261
7261
|
}
|
|
7262
7262
|
function dv() {
|
|
7263
7263
|
lv(), V_.setAttribute("hidden", ""), B_.removeAttribute("hidden"), cv = setTimeout(() => {
|
|
7264
|
-
uv("
|
|
7264
|
+
uv("The bodygraph didn't load. Tap to try again.", dv);
|
|
7265
7265
|
}, 3e4), Q.sendMessage({ role: "user", content: [{ type: "text", text: "Please regenerate the Human Design bodygraph." }] }).catch(() => {
|
|
7266
7266
|
});
|
|
7267
7267
|
}
|
package/dist/ui/chart-wheel.html
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<div id="app">
|
|
18
18
|
<div id="loading-state">
|
|
19
19
|
<svg class="oe-loader" viewBox="0 0 100 100"><circle cx="50" cy="50" r="45"/></svg>
|
|
20
|
-
<p>
|
|
20
|
+
<p>Mapping the sky at your birth moment…</p>
|
|
21
21
|
</div>
|
|
22
22
|
<div id="chart-container" hidden>
|
|
23
23
|
<div id="pan-zoom-controls" class="pan-zoom-controls" hidden>
|
|
@@ -7870,7 +7870,7 @@ function Iy(e4, t2) {
|
|
|
7870
7870
|
}
|
|
7871
7871
|
function Ly() {
|
|
7872
7872
|
Fy(), $v.setAttribute("hidden", ""), Qv.removeAttribute("hidden"), Py = setTimeout(() => {
|
|
7873
|
-
Iy("
|
|
7873
|
+
Iy("The chart didn't load. Tap to try again.", Ly);
|
|
7874
7874
|
}, 3e4), sy.sendMessage({ role: "user", content: [{ type: "text", text: "Please regenerate the chart wheel." }] }).catch(() => {
|
|
7875
7875
|
});
|
|
7876
7876
|
}
|
|
@@ -7882,7 +7882,7 @@ Yv(sy, { loadingEl: Qv, contentEl: $v, statusFn: fy, paramControls: ly }), Py =
|
|
|
7882
7882
|
let e4 = sy.getHostCapabilities();
|
|
7883
7883
|
uy = !!(e4 != null && e4.modelContext), dy = !!(e4 != null && e4.sampling), Jv(sy, document.getElementById("pan-zoom-controls"), ["aspect-filter", "controls-panel", "param-controls-container", "chart-info", "patterns-strip", "info-panel", "cw-action-bar"]), Zv(sy), $ && py($);
|
|
7884
7884
|
} catch (e4) {
|
|
7885
|
-
console.error("[ChartWheel] \u274C app.connect() FAILED:", e4), Iy("
|
|
7885
|
+
console.error("[ChartWheel] \u274C app.connect() FAILED:", e4), Iy("The chart didn't load. Tap to try again.", Ly);
|
|
7886
7886
|
}
|
|
7887
7887
|
})();
|
|
7888
7888
|
</script>
|
package/dist/ui/moon-phase.html
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
<div id="app">
|
|
22
22
|
<div id="loading-state">
|
|
23
23
|
<svg class="oe-loader" viewBox="0 0 100 100"><circle cx="50" cy="50" r="45"/></svg>
|
|
24
|
-
<p>
|
|
24
|
+
<p>Reading tonight's moon…</p>
|
|
25
25
|
</div>
|
|
26
26
|
<div id="chart-status" class="chart-status" aria-live="polite"></div>
|
|
27
27
|
<!-- Moon phase doesn't need zoom — image is fixed. Container is kept so
|
|
@@ -6762,7 +6762,7 @@ function D_(e4, t2) {
|
|
|
6762
6762
|
}
|
|
6763
6763
|
function O_() {
|
|
6764
6764
|
E_(), __.setAttribute("hidden", ""), g_.removeAttribute("hidden"), T_ = setTimeout(() => {
|
|
6765
|
-
D_("
|
|
6765
|
+
D_("The moon phase didn't load. Tap to try again.", O_);
|
|
6766
6766
|
}, 3e4), y_.sendMessage({ role: "user", content: [{ type: "text", text: "Please regenerate the moon phase." }] }).catch(() => {
|
|
6767
6767
|
});
|
|
6768
6768
|
}
|