@roxyapi/ui 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -1
- package/README.md +30 -1
- package/dist/cdn/components/bodygraph.js +54 -0
- package/dist/cdn/components/bodygraph.js.map +7 -0
- package/dist/cdn/components/forecast-timeline.js +45 -0
- package/dist/cdn/components/forecast-timeline.js.map +7 -0
- package/dist/cdn/roxy-ui.js +49 -40
- package/dist/cdn/roxy-ui.js.map +4 -4
- package/dist/components/bodygraph.d.ts +27 -0
- package/dist/components/bodygraph.d.ts.map +1 -0
- package/dist/components/bodygraph.js +11 -0
- package/dist/components/bodygraph.js.map +7 -0
- package/dist/components/forecast-timeline.d.ts +38 -0
- package/dist/components/forecast-timeline.d.ts.map +1 -0
- package/dist/components/forecast-timeline.js +2 -0
- package/dist/components/forecast-timeline.js.map +7 -0
- package/dist/index.cjs +45 -36
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +52 -43
- package/dist/index.js.map +4 -4
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.json +2 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/types.gen.d.ts +3994 -6
- package/dist/types/types.gen.d.ts.map +1 -1
- package/dist/utils/bodygraph-render.d.ts +105 -0
- package/dist/utils/bodygraph-render.d.ts.map +1 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/bodygraph.ts +390 -0
- package/src/components/forecast-timeline.ts +336 -0
- package/src/index.ts +4 -0
- package/src/manifest.ts +26 -0
- package/src/types/index.ts +1 -1
- package/src/types/types.gen.ts +4079 -6
- package/src/utils/bodygraph-render.ts +641 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { TemplateResult } from 'lit';
|
|
2
|
+
/**
|
|
3
|
+
* Fixed geometry and renderer for the Human Design bodygraph. The diagram is a
|
|
4
|
+
* standard, invariant layout: nine centers in canonical positions and shapes,
|
|
5
|
+
* wired by 36 channels that each join two gates, with the 64 gates at fixed
|
|
6
|
+
* points on the center edges, all overlaid on a front-facing human silhouette so
|
|
7
|
+
* each center lands on the body part it governs. Only which centers are defined
|
|
8
|
+
* and which channels and gates are active changes per chart, so this module holds
|
|
9
|
+
* the geometry and the {@link RoxyBodygraph} component supplies the live state
|
|
10
|
+
* from the /human-design/bodygraph response.
|
|
11
|
+
*
|
|
12
|
+
* @remarks Every point is authored in one normalized 0 to 100 canonical grid (x
|
|
13
|
+
* left to right, y top to bottom) taken from the reference Jovian Archive
|
|
14
|
+
* bodygraph, then scaled into {@link BODYGRAPH_VIEWBOX} by a single transform so
|
|
15
|
+
* the chart and the body share one coordinate space and scale together. The grid
|
|
16
|
+
* is sized so the nine centers fill the figure exactly as in the canonical
|
|
17
|
+
* "nine centers on the human body" diagram: the Head center at the crown, Ajna at
|
|
18
|
+
* the forehead, Throat at the neck, G at the chest, Heart at the right chest,
|
|
19
|
+
* Spleen on the left torso, Solar Plexus on the right torso (its mirror), Sacral
|
|
20
|
+
* at the lower abdomen, and Root at the pelvis. Two structural truths the layout
|
|
21
|
+
* preserves: the Spleen (left) and the Solar Plexus (right) are mirror images at
|
|
22
|
+
* the Sacral height, so the chart is narrow at the top and wide at the bottom;
|
|
23
|
+
* and the Heart sits low and to the right of the G center, above the Solar
|
|
24
|
+
* Plexus. The reference chart spreads the two side centers to the page edge for
|
|
25
|
+
* channel clarity; here they are shifted inward by a fixed amount so they rest on
|
|
26
|
+
* the torso sides inside the figure, with every channel topology preserved. Center shapes follow the canonical orientations (Head triangle
|
|
27
|
+
* up, Ajna triangle down, Throat and Sacral and Root squares, G diamond, Heart
|
|
28
|
+
* triangle pointing right, Spleen triangle pointing right with its base on the
|
|
29
|
+
* far-left edge, Solar Plexus its mirror). The 36 channel gate pairs are the
|
|
30
|
+
* gold-standard set used by the RoxyAPI Human Design engine.
|
|
31
|
+
*/
|
|
32
|
+
export type BodygraphCenterId = 'head' | 'ajna' | 'throat' | 'g' | 'heart' | 'sacral' | 'solar-plexus' | 'spleen' | 'root';
|
|
33
|
+
interface Point {
|
|
34
|
+
x: number;
|
|
35
|
+
y: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* One center's drawable geometry: its semantic traditional color, the polygon
|
|
39
|
+
* point list of its canonical shape, and the anchor where its name label sits.
|
|
40
|
+
* Shapes are explicit point lists so the triangle and diamond orientations match
|
|
41
|
+
* the canonical diagram exactly. Label anchors sit in the empty margins outside
|
|
42
|
+
* each shape so they never collide with the gate numbers printed on the edges.
|
|
43
|
+
*/
|
|
44
|
+
interface CenterGeometry {
|
|
45
|
+
id: BodygraphCenterId;
|
|
46
|
+
label: string;
|
|
47
|
+
color: CenterColor;
|
|
48
|
+
points: Point[];
|
|
49
|
+
labelAnchor: Point;
|
|
50
|
+
labelAlign: 'start' | 'middle' | 'end';
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Traditional Human Design center color group. A defined center is filled with
|
|
54
|
+
* this semantic color (constant across light and dark, like chart data colors);
|
|
55
|
+
* an open center is left transparent with a thin theme-aware outline. The four
|
|
56
|
+
* groups mirror the canonical scheme: gold for the identity and inspiration
|
|
57
|
+
* centers (Head, G), green for the mental awareness center (Ajna), red for the
|
|
58
|
+
* life-force motors of will and vitality (Heart, Sacral), brown for the
|
|
59
|
+
* pressure, expression, and remaining awareness centers (Throat, Spleen, Solar
|
|
60
|
+
* Plexus, Root).
|
|
61
|
+
*/
|
|
62
|
+
export type CenterColor = 'gold' | 'green' | 'red' | 'brown';
|
|
63
|
+
/**
|
|
64
|
+
* The viewBox-space gate anchors ({@link GATE_POINTS}) and the gate to center
|
|
65
|
+
* index ({@link GATE_CENTER}). Exported so the geometry tests can assert the
|
|
66
|
+
* layout invariants (side-center mirror symmetry, central-column balance, gates
|
|
67
|
+
* inside their centers) without rendering.
|
|
68
|
+
*/
|
|
69
|
+
export declare const GATE_POINTS: Record<number, Point>, GATE_CENTER: Record<number, BodygraphCenterId>;
|
|
70
|
+
/** Horizontal axis of symmetry in viewBox units, the reflection axis for geometry tests. */
|
|
71
|
+
export declare const CHART_AXIS_X: number;
|
|
72
|
+
/**
|
|
73
|
+
* Center shapes in canonical orientation and color, labels anchored in the
|
|
74
|
+
* margins. Central-column centers are built centered on {@link AXIS}; the Solar
|
|
75
|
+
* Plexus shape is the Spleen reflected across the axis, so the side centers stay
|
|
76
|
+
* exact mirrors. The Heart is the deliberate off-axis exception.
|
|
77
|
+
*/
|
|
78
|
+
export declare const CENTER_GEOMETRY: readonly CenterGeometry[];
|
|
79
|
+
/**
|
|
80
|
+
* The 36 channels as ordered gate pairs. This is the canonical Human Design
|
|
81
|
+
* channel set; a channel is active only when both of its gates are activated,
|
|
82
|
+
* which the live response reports in its `channels` array. The static list lets
|
|
83
|
+
* the renderer draw every channel as a hanging (inactive) line and overlay the
|
|
84
|
+
* active ones, so an open bodygraph still shows its full wiring skeleton.
|
|
85
|
+
*/
|
|
86
|
+
export declare const CHANNEL_PAIRS: ReadonlyArray<readonly [number, number]>;
|
|
87
|
+
export interface BodygraphRenderInput {
|
|
88
|
+
definedCenters: Set<BodygraphCenterId>;
|
|
89
|
+
activeChannels: Set<string>;
|
|
90
|
+
activeGates: Set<number>;
|
|
91
|
+
gateTitles: Map<number, string>;
|
|
92
|
+
}
|
|
93
|
+
/** Build the lookup key for an active channel from its two gate numbers. */
|
|
94
|
+
export declare function channelKey(a: number, b: number): string;
|
|
95
|
+
export declare const BODYGRAPH_VIEWBOX = "0 0 472 472";
|
|
96
|
+
/**
|
|
97
|
+
* Render the full bodygraph SVG inner content for the given live state. The
|
|
98
|
+
* caller wraps it in an `<svg>` with {@link BODYGRAPH_VIEWBOX} and applies its
|
|
99
|
+
* own theming CSS. Draw order: body silhouette under channels under centers
|
|
100
|
+
* under gate numbers, so the body is the backdrop, the wiring sits behind the
|
|
101
|
+
* shapes, and the numbers stay legible on top.
|
|
102
|
+
*/
|
|
103
|
+
export declare function renderBodygraphSvg(input: BodygraphRenderInput): TemplateResult;
|
|
104
|
+
export {};
|
|
105
|
+
//# sourceMappingURL=bodygraph-render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bodygraph-render.d.ts","sourceRoot":"","sources":["../../src/utils/bodygraph-render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,KAAK,CAAC;AAG1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,MAAM,MAAM,iBAAiB,GAC1B,MAAM,GACN,MAAM,GACN,QAAQ,GACR,GAAG,GACH,OAAO,GACP,QAAQ,GACR,cAAc,GACd,QAAQ,GACR,MAAM,CAAC;AAEV,UAAU,KAAK;IACd,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACV;AAED;;;;;;GAMG;AACH,UAAU,cAAc;IACvB,EAAE,EAAE,iBAAiB,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,WAAW,CAAC;IACnB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,WAAW,EAAE,KAAK,CAAC;IACnB,UAAU,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;CACvC;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;AA8J7D;;;;;GAKG;AACH,eAAO,MAAgB,WAAW,yBAAY,WAAW,mCAAsB,CAAC;AAEhF,4FAA4F;AAC5F,eAAO,MAAM,YAAY,QAAa,CAAC;AAwBvC;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,cAAc,EA8FpD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAqClE,CAAC;AAqMF,MAAM,WAAW,oBAAoB;IACpC,cAAc,EAAE,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACvC,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,eAAO,MAAM,iBAAiB,gBAA4B,CAAC;AAE3D;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CACjC,KAAK,EAAE,oBAAoB,GACzB,cAAc,CAOhB"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const ROXY_UI_VERSION = "0.
|
|
1
|
+
export declare const ROXY_UI_VERSION = "0.8.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roxyapi/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Web components for the RoxyAPI catalog. Drop-in charts, tables, cards, forms for astrology, tarot, numerology, biorhythm, I Ching, crystals, dreams, angel numbers, and more. One key, beautiful in 30 minutes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { css, html, LitElement, nothing } from 'lit';
|
|
2
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
3
|
+
import { PLANET_GLYPH } from '../tokens/index.js';
|
|
4
|
+
import type { GenerateBodygraphResponse } from '../types/index.js';
|
|
5
|
+
import { baseStyles } from '../utils/base-styles.js';
|
|
6
|
+
import {
|
|
7
|
+
BODYGRAPH_VIEWBOX,
|
|
8
|
+
type BodygraphCenterId,
|
|
9
|
+
channelKey,
|
|
10
|
+
renderBodygraphSvg,
|
|
11
|
+
} from '../utils/bodygraph-render.js';
|
|
12
|
+
import { MarkupDataController } from '../utils/markup-data.js';
|
|
13
|
+
import { capitalize } from '../utils/string.js';
|
|
14
|
+
|
|
15
|
+
type GateActivation = GenerateBodygraphResponse['gates'][number];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Human Design bodygraph. Pass `data` from /human-design/bodygraph. Renders the
|
|
19
|
+
* nine centers in their canonical positions and shapes, filled when defined and
|
|
20
|
+
* outlined when open, the 36 channels as wiring between gates with active
|
|
21
|
+
* channels emphasized, and the activated gate numbers. A summary block lists
|
|
22
|
+
* type, strategy, authority, profile, definition, incarnation cross, signature,
|
|
23
|
+
* and not-self theme.
|
|
24
|
+
*
|
|
25
|
+
* The chart is theme-driven through `--roxy-*` custom properties on `:host`, so
|
|
26
|
+
* it adopts the host palette in light and dark without runtime color probing.
|
|
27
|
+
*/
|
|
28
|
+
@customElement('roxy-bodygraph')
|
|
29
|
+
export class RoxyBodygraph extends LitElement {
|
|
30
|
+
static styles = [
|
|
31
|
+
baseStyles,
|
|
32
|
+
css`
|
|
33
|
+
.wrap {
|
|
34
|
+
display: grid;
|
|
35
|
+
gap: var(--roxy-space-md, 1rem);
|
|
36
|
+
}
|
|
37
|
+
.head {
|
|
38
|
+
display: flex;
|
|
39
|
+
justify-content: space-between;
|
|
40
|
+
align-items: baseline;
|
|
41
|
+
flex-wrap: wrap;
|
|
42
|
+
gap: var(--roxy-space-sm, 0.5rem);
|
|
43
|
+
}
|
|
44
|
+
.title {
|
|
45
|
+
margin: 0;
|
|
46
|
+
font-size: var(--roxy-text-lg, 1.125rem);
|
|
47
|
+
font-weight: var(--roxy-weight-bold, 600);
|
|
48
|
+
}
|
|
49
|
+
.type-line {
|
|
50
|
+
color: var(--roxy-muted, #71717a);
|
|
51
|
+
font-size: var(--roxy-text-sm, 0.875rem);
|
|
52
|
+
}
|
|
53
|
+
.layout {
|
|
54
|
+
display: grid;
|
|
55
|
+
gap: var(--roxy-space-lg, 1.5rem);
|
|
56
|
+
grid-template-columns: minmax(0, 1fr);
|
|
57
|
+
align-items: start;
|
|
58
|
+
}
|
|
59
|
+
@container (min-width: 520px) {
|
|
60
|
+
.layout {
|
|
61
|
+
grid-template-columns: minmax(0, 340px) minmax(0, 1fr);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
svg {
|
|
66
|
+
display: block;
|
|
67
|
+
width: 100%;
|
|
68
|
+
max-width: 340px;
|
|
69
|
+
height: auto;
|
|
70
|
+
margin: 0 auto;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Body silhouette behind the chart. Theme-aware: a soft warm tint in
|
|
74
|
+
* light, a muted fill in dark, with a faint outline that follows the
|
|
75
|
+
* border token so it reads on either surface. */
|
|
76
|
+
.bg-body {
|
|
77
|
+
fill: color-mix(in srgb, var(--roxy-secondary, #475569) 8%, transparent);
|
|
78
|
+
stroke: var(--roxy-border, #e4e4e7);
|
|
79
|
+
stroke-width: 1;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Every channel is drawn as one line joining its two gates, so the
|
|
83
|
+
* wiring always reads as a connected diagram. The faint base shows the
|
|
84
|
+
* full 36-channel skeleton; .on thickens it when both gates are active
|
|
85
|
+
* (a defined channel); .bg-half lights a single gate's hanging end. */
|
|
86
|
+
.bg-channel {
|
|
87
|
+
stroke: var(--roxy-secondary, #475569);
|
|
88
|
+
stroke-width: 1.6;
|
|
89
|
+
opacity: 0.3;
|
|
90
|
+
}
|
|
91
|
+
.bg-channel.on {
|
|
92
|
+
stroke-width: 3.4;
|
|
93
|
+
stroke-linecap: round;
|
|
94
|
+
opacity: 1;
|
|
95
|
+
}
|
|
96
|
+
.bg-half {
|
|
97
|
+
stroke: var(--roxy-secondary, #475569);
|
|
98
|
+
stroke-width: 3.2;
|
|
99
|
+
stroke-linecap: round;
|
|
100
|
+
opacity: 0.9;
|
|
101
|
+
}
|
|
102
|
+
/* Thin leaders connect each center's margin label to its shape so the
|
|
103
|
+
* Heart and every other center is identifiable at a glance. */
|
|
104
|
+
.bg-leader {
|
|
105
|
+
stroke: var(--roxy-muted, #71717a);
|
|
106
|
+
stroke-width: 1;
|
|
107
|
+
opacity: 0.5;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Centers carry the traditional Human Design semantic colors when
|
|
111
|
+
* defined. These stay constant across light and dark, like chart data
|
|
112
|
+
* colors. Open centers are transparent with a thin theme-aware outline.
|
|
113
|
+
* The defined gate-cluster colors are chosen for >= 4.5:1 contrast with
|
|
114
|
+
* the white gate-number halo in both themes. */
|
|
115
|
+
.bg-center {
|
|
116
|
+
fill: transparent;
|
|
117
|
+
stroke: var(--roxy-secondary, #475569);
|
|
118
|
+
stroke-width: 1.8;
|
|
119
|
+
}
|
|
120
|
+
.bg-center.defined {
|
|
121
|
+
stroke: rgba(0, 0, 0, 0.45);
|
|
122
|
+
}
|
|
123
|
+
.bg-center.bg-gold.defined {
|
|
124
|
+
fill: #e0a200;
|
|
125
|
+
}
|
|
126
|
+
.bg-center.bg-green.defined {
|
|
127
|
+
fill: #2f8f00;
|
|
128
|
+
}
|
|
129
|
+
.bg-center.bg-red.defined {
|
|
130
|
+
fill: #c41f1f;
|
|
131
|
+
}
|
|
132
|
+
.bg-center.bg-brown.defined {
|
|
133
|
+
fill: #76502f;
|
|
134
|
+
}
|
|
135
|
+
.bg-center-label {
|
|
136
|
+
fill: var(--roxy-muted, #71717a);
|
|
137
|
+
font-size: 11px;
|
|
138
|
+
font-family: var(--roxy-font-sans);
|
|
139
|
+
}
|
|
140
|
+
/* Gate numbers sit on filled centers, so a halo (white stroke painted
|
|
141
|
+
* under the fill via paint-order) keeps them legible on any color. The
|
|
142
|
+
* size is tuned to the canonical gate spacing (the closest gates sit ~18
|
|
143
|
+
* viewBox units apart) so two-digit numbers never touch. */
|
|
144
|
+
.bg-gate {
|
|
145
|
+
fill: var(--roxy-fg, #0a0a0a);
|
|
146
|
+
font-size: 8px;
|
|
147
|
+
font-weight: 600;
|
|
148
|
+
font-family: var(--roxy-font-sans);
|
|
149
|
+
paint-order: stroke;
|
|
150
|
+
stroke: var(--roxy-bg, #fff);
|
|
151
|
+
stroke-width: 1.6px;
|
|
152
|
+
stroke-linejoin: round;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.summary {
|
|
156
|
+
display: grid;
|
|
157
|
+
gap: var(--roxy-space-md, 1rem);
|
|
158
|
+
}
|
|
159
|
+
.facts {
|
|
160
|
+
display: grid;
|
|
161
|
+
grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
|
|
162
|
+
gap: var(--roxy-space-sm, 0.5rem);
|
|
163
|
+
}
|
|
164
|
+
.fact {
|
|
165
|
+
border: 1px solid var(--roxy-border, #e4e4e7);
|
|
166
|
+
border-radius: var(--roxy-radius-md, 8px);
|
|
167
|
+
padding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);
|
|
168
|
+
background: var(--roxy-bg, #fff);
|
|
169
|
+
}
|
|
170
|
+
.fact span {
|
|
171
|
+
display: block;
|
|
172
|
+
color: var(--roxy-muted, #71717a);
|
|
173
|
+
font-size: var(--roxy-text-xs, 0.75rem);
|
|
174
|
+
text-transform: uppercase;
|
|
175
|
+
letter-spacing: 0.06em;
|
|
176
|
+
}
|
|
177
|
+
.fact strong {
|
|
178
|
+
font-size: var(--roxy-text-base, 1rem);
|
|
179
|
+
color: var(--roxy-fg, #0a0a0a);
|
|
180
|
+
}
|
|
181
|
+
.cross {
|
|
182
|
+
font-size: var(--roxy-text-sm, 0.875rem);
|
|
183
|
+
color: var(--roxy-fg, #0a0a0a);
|
|
184
|
+
border-left: 2px solid var(--roxy-accent, #f59e0b);
|
|
185
|
+
padding-left: var(--roxy-space-sm, 0.5rem);
|
|
186
|
+
margin: 0;
|
|
187
|
+
}
|
|
188
|
+
.cross .gates {
|
|
189
|
+
color: var(--roxy-muted, #71717a);
|
|
190
|
+
font-variant-numeric: tabular-nums;
|
|
191
|
+
}
|
|
192
|
+
.themes {
|
|
193
|
+
display: flex;
|
|
194
|
+
flex-wrap: wrap;
|
|
195
|
+
gap: var(--roxy-space-sm, 0.5rem);
|
|
196
|
+
}
|
|
197
|
+
.pill {
|
|
198
|
+
padding: 2px 10px;
|
|
199
|
+
border-radius: var(--roxy-radius-full, 9999px);
|
|
200
|
+
font-size: var(--roxy-text-xs, 0.75rem);
|
|
201
|
+
}
|
|
202
|
+
.pill--good {
|
|
203
|
+
background: color-mix(in srgb, var(--roxy-success, #16a34a) 16%, transparent);
|
|
204
|
+
color: var(--roxy-success-fg, #166534);
|
|
205
|
+
}
|
|
206
|
+
.pill--shadow {
|
|
207
|
+
background: color-mix(in srgb, var(--roxy-danger, #dc2626) 16%, transparent);
|
|
208
|
+
color: var(--roxy-danger-fg, #991b1b);
|
|
209
|
+
}
|
|
210
|
+
.legend {
|
|
211
|
+
display: flex;
|
|
212
|
+
flex-wrap: wrap;
|
|
213
|
+
gap: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);
|
|
214
|
+
font-size: var(--roxy-text-xs, 0.75rem);
|
|
215
|
+
color: var(--roxy-muted, #71717a);
|
|
216
|
+
}
|
|
217
|
+
/* Spans the row so the color key reads as "these are the colors a center
|
|
218
|
+
* takes when defined", not a claim about this chart. Without it a red
|
|
219
|
+
* Heart swatch reads as contradicting an open (outlined) Heart. */
|
|
220
|
+
.legend-caption {
|
|
221
|
+
flex-basis: 100%;
|
|
222
|
+
color: var(--roxy-muted, #71717a);
|
|
223
|
+
}
|
|
224
|
+
.legend .swatch {
|
|
225
|
+
display: inline-block;
|
|
226
|
+
width: 10px;
|
|
227
|
+
height: 10px;
|
|
228
|
+
border-radius: 2px;
|
|
229
|
+
margin-right: 4px;
|
|
230
|
+
vertical-align: middle;
|
|
231
|
+
border: 1px solid var(--roxy-secondary, #475569);
|
|
232
|
+
}
|
|
233
|
+
/* Defined-center swatches use the same semantic colors as the chart so
|
|
234
|
+
* the legend reads as a key, not decoration. Open uses the open-center
|
|
235
|
+
* outline only. */
|
|
236
|
+
.legend .swatch.defined {
|
|
237
|
+
border-color: rgba(0, 0, 0, 0.45);
|
|
238
|
+
}
|
|
239
|
+
.legend .swatch.bg-gold {
|
|
240
|
+
background: #e0a200;
|
|
241
|
+
}
|
|
242
|
+
.legend .swatch.bg-green {
|
|
243
|
+
background: #2f8f00;
|
|
244
|
+
}
|
|
245
|
+
.legend .swatch.bg-red {
|
|
246
|
+
background: #c41f1f;
|
|
247
|
+
}
|
|
248
|
+
.legend .swatch.bg-brown {
|
|
249
|
+
background: #76502f;
|
|
250
|
+
}
|
|
251
|
+
`,
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
constructor() {
|
|
255
|
+
super();
|
|
256
|
+
// Enables hydrating `data` from a direct-child
|
|
257
|
+
// <script type="application/json" class="roxy-data"> for server-rendered
|
|
258
|
+
// and cached consumers. The JavaScript `data` property still wins.
|
|
259
|
+
new MarkupDataController(this);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
@property({ attribute: false })
|
|
263
|
+
data: GenerateBodygraphResponse | null = null;
|
|
264
|
+
|
|
265
|
+
render() {
|
|
266
|
+
const d = this.data;
|
|
267
|
+
if (!d)
|
|
268
|
+
return html`<div class="roxy-empty" role="status">No bodygraph data</div>`;
|
|
269
|
+
|
|
270
|
+
const definedCenters = new Set<BodygraphCenterId>(
|
|
271
|
+
(d.centers ?? [])
|
|
272
|
+
.filter((c) => c.defined)
|
|
273
|
+
.map((c) => c.id as BodygraphCenterId),
|
|
274
|
+
);
|
|
275
|
+
const activeGates = new Set<number>(
|
|
276
|
+
(d.gates ?? []).map((g) => g.gate).filter((n): n is number => n != null),
|
|
277
|
+
);
|
|
278
|
+
const activeChannels = new Set<string>(
|
|
279
|
+
(d.channels ?? []).map((c) => channelKey(c.gateA, c.gateB)),
|
|
280
|
+
);
|
|
281
|
+
const gateTitles = this.buildGateTitles(d.gates ?? []);
|
|
282
|
+
|
|
283
|
+
return html`<div class="wrap">
|
|
284
|
+
<header class="head">
|
|
285
|
+
<h2 class="title">Bodygraph</h2>
|
|
286
|
+
${
|
|
287
|
+
d.type
|
|
288
|
+
? html`<div class="type-line">
|
|
289
|
+
${d.type}${d.profile ? html` · Profile ${d.profile}` : nothing}
|
|
290
|
+
</div>`
|
|
291
|
+
: nothing
|
|
292
|
+
}
|
|
293
|
+
</header>
|
|
294
|
+
<div class="layout">
|
|
295
|
+
<svg
|
|
296
|
+
viewBox=${BODYGRAPH_VIEWBOX}
|
|
297
|
+
preserveAspectRatio="xMidYMid meet"
|
|
298
|
+
role="img"
|
|
299
|
+
aria-label="Human Design bodygraph with nine centers, channels, and activated gates overlaid on a human silhouette"
|
|
300
|
+
>
|
|
301
|
+
<title>Human Design bodygraph</title>
|
|
302
|
+
<desc>
|
|
303
|
+
Nine energy centers in their canonical positions over a human
|
|
304
|
+
silhouette, each filled with its traditional color when defined and
|
|
305
|
+
outlined when open, wired by channels between activated gates.
|
|
306
|
+
</desc>
|
|
307
|
+
${renderBodygraphSvg({
|
|
308
|
+
definedCenters,
|
|
309
|
+
activeChannels,
|
|
310
|
+
activeGates,
|
|
311
|
+
gateTitles,
|
|
312
|
+
})}
|
|
313
|
+
</svg>
|
|
314
|
+
${this.renderSummary(d)}
|
|
315
|
+
</div>
|
|
316
|
+
</div>`;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private buildGateTitles(gates: GateActivation[]): Map<number, string> {
|
|
320
|
+
const titles = new Map<number, string>();
|
|
321
|
+
for (const g of gates) {
|
|
322
|
+
if (g.gate == null) continue;
|
|
323
|
+
const parts: string[] = [`Gate ${g.gate}`];
|
|
324
|
+
if (g.line != null) parts[0] += `.${g.line}`;
|
|
325
|
+
if (g.gateName) parts.push(g.gateName);
|
|
326
|
+
const planet = g.planet ? capitalize(g.planet) : '';
|
|
327
|
+
const glyph = planet ? (PLANET_GLYPH[planet] ?? planet) : '';
|
|
328
|
+
if (glyph) parts.push(`${glyph} ${g.side ?? ''}`.trim());
|
|
329
|
+
titles.set(g.gate, parts.join(' · '));
|
|
330
|
+
}
|
|
331
|
+
return titles;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private renderSummary(d: GenerateBodygraphResponse) {
|
|
335
|
+
const facts: Array<{ label: string; value?: string }> = [
|
|
336
|
+
{ label: 'Type', value: d.type },
|
|
337
|
+
{ label: 'Strategy', value: d.strategy },
|
|
338
|
+
{ label: 'Authority', value: d.authority },
|
|
339
|
+
{ label: 'Profile', value: d.profile },
|
|
340
|
+
{ label: 'Definition', value: d.definition },
|
|
341
|
+
];
|
|
342
|
+
const ic = d.incarnationCross;
|
|
343
|
+
return html`<div class="summary">
|
|
344
|
+
<div class="facts">
|
|
345
|
+
${facts.map((f) =>
|
|
346
|
+
f.value
|
|
347
|
+
? html`<div class="fact">
|
|
348
|
+
<span>${f.label}</span>
|
|
349
|
+
<strong>${f.value}</strong>
|
|
350
|
+
</div>`
|
|
351
|
+
: nothing,
|
|
352
|
+
)}
|
|
353
|
+
</div>
|
|
354
|
+
${
|
|
355
|
+
ic?.name
|
|
356
|
+
? html`<p class="cross">
|
|
357
|
+
${ic.name}
|
|
358
|
+
${
|
|
359
|
+
ic.gates?.length
|
|
360
|
+
? html`<span class="gates"> (${ic.gates.join(', ')})</span>`
|
|
361
|
+
: nothing
|
|
362
|
+
}
|
|
363
|
+
</p>`
|
|
364
|
+
: nothing
|
|
365
|
+
}
|
|
366
|
+
${
|
|
367
|
+
d.signature || d.notSelf
|
|
368
|
+
? html`<div class="themes">
|
|
369
|
+
${d.signature ? html`<span class="pill pill--good">Signature: ${d.signature}</span>` : nothing}
|
|
370
|
+
${d.notSelf ? html`<span class="pill pill--shadow">Not-self: ${d.notSelf}</span>` : nothing}
|
|
371
|
+
</div>`
|
|
372
|
+
: nothing
|
|
373
|
+
}
|
|
374
|
+
<div class="legend">
|
|
375
|
+
<span class="legend-caption">Center colors when defined. Open centers are outlined.</span>
|
|
376
|
+
<span><span class="swatch bg-gold defined"></span>Head, G</span>
|
|
377
|
+
<span><span class="swatch bg-green defined"></span>Ajna</span>
|
|
378
|
+
<span><span class="swatch bg-brown defined"></span>Throat, Spleen, Solar Plexus, Root</span>
|
|
379
|
+
<span><span class="swatch bg-red defined"></span>Heart, Sacral</span>
|
|
380
|
+
<span><span class="swatch"></span>Open center</span>
|
|
381
|
+
</div>
|
|
382
|
+
</div>`;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
declare global {
|
|
387
|
+
interface HTMLElementTagNameMap {
|
|
388
|
+
'roxy-bodygraph': RoxyBodygraph;
|
|
389
|
+
}
|
|
390
|
+
}
|