import { css, html, LitElement, nothing, svg } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { TRIGRAM_GLYPH } from '../tokens/index.js'; import type { CastReadingResponse, GetDailyHexagramResponse, GetHexagramResponse, GetRandomHexagramResponse, Hexagram, LookupHexagramResponse, } from '../types/index.js'; import { baseStyles } from '../utils/base-styles.js'; type HexagramData = | GetHexagramResponse | GetRandomHexagramResponse | LookupHexagramResponse | GetDailyHexagramResponse | CastReadingResponse; /** * I Ching hexagram card. Renders /iching/hexagrams/{number}, /iching/cast, * /iching/daily, /iching/daily/cast. */ @customElement('roxy-hexagram') export class RoxyHexagram extends LitElement { static styles = [ baseStyles, css` .card { background: var(--roxy-bg, #fff); 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; grid-template-columns: 6rem 1fr; gap: var(--roxy-space-lg, 1.5rem); } @container (max-width: 480px) { .card { grid-template-columns: 1fr; } } .glyphs { display: grid; gap: var(--roxy-space-md, 1rem); justify-items: center; } .symbol { font-size: 3rem; line-height: 1; color: var(--roxy-accent-fg, #b45309); } .lines { display: grid; gap: 4px; width: 4rem; } .line { display: flex; gap: 4px; justify-content: center; align-items: center; height: 8px; } .seg { display: block; height: 6px; background: var(--roxy-fg, #0a0a0a); border-radius: 1px; } .line.broken .seg { width: 1.4rem; } .line.solid .seg { width: 3rem; } .line.changing .seg { background: var(--roxy-accent, #f59e0b); } .title { margin: 0; font-size: var(--roxy-text-xl, 1.5rem); font-weight: var(--roxy-weight-bold, 600); letter-spacing: var(--roxy-tracking-tight); } .subtitle { color: var(--roxy-muted, #71717a); font-size: var(--roxy-text-sm, 0.875rem); margin: 0 0 var(--roxy-space-sm, 0.5rem); } .trigrams { display: flex; gap: var(--roxy-space-md, 1rem); margin-bottom: var(--roxy-space-sm, 0.5rem); color: var(--roxy-secondary, #475569); font-size: var(--roxy-text-xs, 0.75rem); text-transform: uppercase; letter-spacing: 0.06em; } .tri-glyph { font-size: var(--roxy-text-xl, 1.5rem); color: var(--roxy-accent-fg, #b45309); margin-right: 4px; vertical-align: middle; } .judgment, .image, .message { margin: 0 0 var(--roxy-space-sm, 0.5rem); font-size: var(--roxy-text-sm, 0.875rem); color: var(--roxy-fg, #0a0a0a); } .judgment::before { content: 'Judgment. '; font-weight: var(--roxy-weight-bold, 600); color: var(--roxy-secondary, #475569); } .image::before { content: 'Image. '; font-weight: var(--roxy-weight-bold, 600); color: var(--roxy-secondary, #475569); } .changing { margin-top: var(--roxy-space-md, 1rem); padding-top: var(--roxy-space-md, 1rem); border-top: 1px solid var(--roxy-border, #e4e4e7); color: var(--roxy-accent-fg, #b45309); font-size: var(--roxy-text-sm, 0.875rem); } `, ]; @property({ attribute: false }) data: HexagramData | null = null; @property({ type: String, reflect: true }) mode: 'lookup' | 'cast' | 'daily' = 'lookup'; private resolveHexagram(): { hex: Hexagram; lines?: number[]; changingLinePositions?: number[]; dailyMessage?: string; resultingHexagram?: Hexagram; } | null { const d = this.data; if (!d) return null; if ('hexagram' in d && d.hexagram) { if ('lines' in d) { const cast = d as CastReadingResponse; return { hex: cast.hexagram as Hexagram, lines: cast.lines, changingLinePositions: cast.changingLinePositions, resultingHexagram: cast.resultingHexagram as Hexagram | undefined, }; } const daily = d as GetDailyHexagramResponse; return { hex: daily.hexagram as Hexagram, dailyMessage: daily.dailyMessage, }; } return { hex: d as Hexagram }; } render() { const resolved = this.resolveHexagram(); if (!resolved) return html`
No hexagram data
`; const { hex: h, lines: castLines, changingLinePositions, dailyMessage, resultingHexagram, } = resolved; const lines = castLines ?? this.derivedLines(h); const changing = new Set(changingLinePositions ?? []); return html`
${h.symbol ? html`
${h.symbol}
` : nothing}

${h.number ? html`${h.number}. ` : nothing}${h.english ?? h.chinese ?? 'Hexagram'}

${h.chinese ? html`${h.chinese}` : nothing} ${h.pinyin ? html` ยท ${h.pinyin}` : nothing}

${ h.upperTrigram ? html`
Upper ${TRIGRAM_GLYPH[h.upperTrigram] ?? ''}${h.upperTrigram}
` : nothing } ${ h.lowerTrigram ? html`
Lower ${TRIGRAM_GLYPH[h.lowerTrigram] ?? ''}${h.lowerTrigram}
` : nothing }
${h.judgment ? html`

${h.judgment}

` : nothing} ${h.image ? html`

${h.image}

` : nothing} ${dailyMessage ? html`

${dailyMessage}

` : nothing} ${ h.interpretation?.general ? html`

${h.interpretation.general}

` : nothing } ${ changing.size > 0 ? html`
Changing lines: ${Array.from(changing) .sort((a, b) => a - b) .join(', ')}. ${ resultingHexagram?.english ? html` Becomes hexagram ${resultingHexagram.number} ${resultingHexagram.english}.` : nothing }
` : nothing }
`; } /** When the API only ships symbol+number with no line array, render six solid yang. */ private derivedLines(h: Hexagram): number[] { // Map each character of the unicode hexagram block (U+4DC0..) to broken/solid const cp = h.symbol.codePointAt(0) ?? 0; if (cp >= 0x4dc0 && cp <= 0x4dff) { const offset = cp - 0x4dc0; const lines: number[] = []; for (let i = 0; i < 6; i++) { const broken = (offset >> i) & 1; lines.push(broken ? 8 : 7); } return lines; } return Array.from({ length: 6 }, () => 7); } } declare global { interface HTMLElementTagNameMap { 'roxy-hexagram': RoxyHexagram; } }