import { css, html, nothing } from 'lit';
import { customElement } from 'lit/decorators.js';
import type { CalculateVariablesResponse } from '../types/index.js';
import { RoxyDataElement } from '../utils/base-element.js';
import { baseStyles } from '../utils/base-styles.js';
/**
* Human Design variables (the four arrows / PHS). Renders /human-design/variables: the four transformation arrows laid out as they sit on the bodygraph head (top-left/right Determination + Motivation, bottom-left/right Environment + Perspective), each showing its left/right direction, the digestion/environment/awareness/perspective labels, and its color/tone/base. A low-confidence calculation (near a color/tone boundary) is flagged.
*/
@customElement('roxy-hd-variables')
export class RoxyHdVariables extends RoxyDataElement {
static styles = [
baseStyles,
css`
.wrap {
background: var(--roxy-surface, #fff);
color: var(--roxy-fg, #0a0a0a);
border: 1px solid var(--roxy-border, #e4e4e7);
border-radius: var(--roxy-radius-md, 8px);
padding: var(--roxy-space-lg, 1.5rem);
box-shadow: var(--roxy-shadow-sm);
display: grid;
gap: var(--roxy-space-md, 1rem);
}
.title {
margin: 0;
font-size: var(--roxy-text-lg, 1.125rem);
font-weight: var(--roxy-weight-bold, 600);
}
.grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--roxy-space-md, 1rem);
}
.arrow {
border: 1px solid var(--roxy-border, #e4e4e7);
border-radius: var(--roxy-radius-md, 8px);
padding: var(--roxy-space-md, 1rem);
display: grid;
gap: var(--roxy-space-xs, 0.25rem);
}
.arrow-head {
display: flex;
align-items: center;
gap: var(--roxy-space-sm, 0.5rem);
}
.glyph {
font-size: 1.75rem;
line-height: 1;
color: var(--roxy-accent-ink, #b45309);
}
.name {
font-weight: var(--roxy-weight-bold, 600);
}
.layer {
font-size: var(--roxy-text-xs, 0.75rem);
color: var(--roxy-muted, #71717a);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.labels {
font-size: var(--roxy-text-sm, 0.875rem);
color: var(--roxy-fg, #0a0a0a);
}
.ctb {
font-size: var(--roxy-text-xs, 0.75rem);
color: var(--roxy-muted, #71717a);
font-variant-numeric: tabular-nums;
}
.note {
font-size: var(--roxy-text-xs, 0.75rem);
color: var(--roxy-warning-fg, #9a3412);
}
@container (max-width: 28rem) {
.grid {
grid-template-columns: 1fr;
}
}
`,
];
protected renderData(d: CalculateVariablesResponse) {
// Place the arrows by their bodygraph `position`, not response order: the
// 2-col grid fills row-major, so sorting to Top left, Top right, Bottom left,
// Bottom right keeps the design column (Determination + Environment) on the
// left and the personality column (Motivation + Perspective) on the right,
// which is the whole point of the four-arrow layout.
const arrows = [...(d.arrows ?? [])].sort(
(a, b) => quadrantOrder(a.position) - quadrantOrder(b.position),
);
return html`
Variables
${arrows.map((a) => this.renderArrow(a))}
${
d.confident === false
? html`
Low confidence: a birth time near a color or tone boundary${typeof d.confidenceMarginDeg === 'number' ? ` (within ${d.confidenceMarginDeg}°)` : ''}. Verify the exact birth time.
`
: nothing
}
`;
}
private renderArrow(
a: NonNullable[number],
) {
// A left arrow is strategic/active, a right arrow receptive/passive in HD.
const glyph = a.direction === 'left' ? '←' : '→';
return html`
${glyph}${a.name ?? ''}
${a.layer ? html`${a.layer}` : nothing}
${[a.directionLabel, a.colorLabel].filter(Boolean).join(' · ')}
${
typeof a.color === 'number'
? html`Color ${a.color} · Tone ${a.tone} · Base ${a.base}${a.activation?.planet ? ` · ${a.activation.planet}${a.activation.side ? ` (${a.activation.side})` : ''}` : ''}`
: nothing
}
${
a.confident === false
? html`Knife-edge: could flip with a more precise birth time.`
: nothing
}
`;
}
protected renderEmpty() {
return html`
No variables data
`;
}
}
/** Canonical bodygraph reading order for the four arrows, so the 2-col grid lays them out by quadrant. Unknown positions sort last. */
function quadrantOrder(position: string | undefined): number {
switch (position) {
case 'Top left':
return 0;
case 'Top right':
return 1;
case 'Bottom left':
return 2;
case 'Bottom right':
return 3;
default:
return 99;
}
}
declare global {
interface HTMLElementTagNameMap {
'roxy-hd-variables': RoxyHdVariables;
}
}