@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 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,3 @@
1
+ /** Stable, non-reversible identity from a credential. */
2
+ export declare function distinctIdFor(credential: string | undefined): string;
3
+ export declare function captureEvent(event: string, distinctId: string, properties?: Record<string, unknown>): void;
@@ -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
+ }
@@ -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, disconnect and reconnect the ` +
270
- `OpenEphemeris connector to re-authorize.`, 401, "auth_required", false, LOGIN_SIGNUP_URL);
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). Keep every number below in sync with
277
- // that file; do NOT hand-edit these figures independently.
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 enable overages ` +
298
- `in their dashboard or upgrade at ${UPGRADE_URL}`;
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");
@@ -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 forwarded = req.headers["x-forwarded-for"];
33
- if (typeof forwarded === "string") {
34
- ip = forwarded.split(",")[0].trim();
35
- }
36
- else if (Array.isArray(forwarded) && forwarded.length > 0) {
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 `ephemeris_natal_chart` with:\n" +
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
@@ -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**a NASA JPL-backed astronomical computation engine.\n\n" +
183
- "I have access to 50+ precision tools covering:\n\n" +
184
- " **Natal Charts** full planetary positions, house cusps, aspects, dignities\n" +
185
- " **Transits & Progressions** — real-time and secondary progressions\n" +
186
- " **Synastry & Composites** — relationship compatibility analysis\n" +
187
- " **Human Design** — bodygraph charts, gates, channels, types & profiles\n" +
188
- " **Astrocartography (ACG)** power lines, city hits, local space lines\n" +
189
- " **Eclipses** solar/lunar eclipse lookup with visibility data\n" +
190
- " **Vedic/Jyotish** D-charts, dashas, nakshatras, Ashtakavarga\n" +
191
- "• **Chinese BaZi** — Four Pillars, Ten Gods, Luck Pillars\n" +
192
- " **Venus Star Points** 8-year Venus cycle phases\n" +
193
- " **Electional Astrology**station tracking, timing windows\n" +
194
- " **Chart Wheels** visual PNG/SVG wheels and bi-wheels\n" +
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 is a precision astronomical computation engine. " +
241
- "Use format='llm' on any tool for compact, token-efficient output. " +
242
- "All computations use JPL DE440 ephemerides for sub-arcsecond accuracy. " +
243
- "See the 'welcome_to_open_ephemeris' prompt for a full capability overview.",
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 server = createMcpServer();
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 electromagnetic (attraction), companionship (sameness), dominance (one defines), and " +
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: "Generate an interactive natal chart wheel with clickable planets, houses, and aspects. " +
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: {
@@ -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()) {
@@ -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";
@@ -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.\n\n" +
19
- "CREDIT COST: 1 credit per subject.\n\n" +
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.\n\n" +
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",
@@ -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>Computing composite ephemeris…</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(Vy), Cy(e4), Dv(qv, e4, zy, By, ky, Ny()), yy(), Oy(e4, xy);
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(Vy);
7877
+ clearTimeout(Hy);
7878
7878
  let t2 = hy(e4);
7879
7879
  if (!t2) {
7880
- fy("Ask your AI assistant to generate a bi-wheel chart to get started.");
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
- var Vy = setTimeout(() => {
7906
- qv.hidden && fy("Chart data is taking longer than expected.", () => {
7907
- cy(), Vy = setTimeout(() => {
7908
- qv.hidden && fy("Still no response from the server. Please close and re-open the chart.");
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 {
@@ -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>Rendering bodygraph…</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", "img"), 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);
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("Still no response from the server. Please close and re-open the bodygraph.");
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
  }
@@ -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>Computing ephemeris…</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("Still no response from the server. Please close and re-open the chart.");
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("Connection failed: ".concat(e4 instanceof Error ? e4.message : String(e4)));
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>
@@ -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>Computing lunar phase…</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_("Still no response from the server. Please close and re-open the moon phase view.");
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openephemeris/mcp-server",
3
- "version": "3.20.0",
3
+ "version": "3.21.0",
4
4
  "description": "Model Context Protocol server for the Open Ephemeris astronomical computation API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",