import { css, html, LitElement, nothing } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import type { CompatibilityResponse } from '../types/index.js'; import { baseStyles } from '../utils/base-styles.js'; import { formatNumber, formatPercent } from '../utils/format.js'; const STANDARD_CATEGORIES = [ 'Varna', 'Vasya', 'Tara', 'Yoni', 'Maitri', 'Gana', 'Bhakoot', 'Nadi', ]; /** * 36-point Ashtakoota score card. Renders /vedic-astrology/compatibility. */ @customElement('roxy-guna-milan') export class RoxyGunaMilan 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; gap: var(--roxy-space-md, 1rem); } .score-header { display: flex; align-items: center; gap: 1rem; } .score-info { flex: 1; } .score-bar { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: var(--roxy-space-md, 1rem); } .total { font-size: 2.25rem; font-weight: var(--roxy-weight-bold, 600); color: var(--roxy-accent-fg, #b45309); font-variant-numeric: tabular-nums; line-height: 1; } .over { color: var(--roxy-muted, #71717a); font-size: var(--roxy-text-base, 1rem); } .recommendation { font-size: var(--roxy-text-sm, 0.875rem); color: var(--roxy-secondary, #475569); } .score-ring { width: 120px; height: 120px; flex-shrink: 0; } .score-ring svg { width: 100%; height: 100%; } .score-ring .ring-text { font-size: 22px; font-weight: 700; fill: var(--roxy-fg, #0a0a0a); font-family: var(--roxy-font-sans); } .score-ring .ring-max { font-size: 10px; fill: var(--roxy-muted, #71717a); font-family: var(--roxy-font-sans); } table { width: 100%; border-collapse: collapse; font-size: var(--roxy-text-sm, 0.875rem); } th, td { padding: var(--roxy-space-sm, 0.5rem); border-bottom: 1px solid var(--roxy-border, #e4e4e7); text-align: left; } th { color: var(--roxy-muted, #71717a); font-weight: var(--roxy-weight-bold, 600); text-transform: uppercase; font-size: var(--roxy-text-xs, 0.75rem); letter-spacing: 0.06em; } td.score { text-align: right; font-variant-numeric: tabular-nums; color: var(--roxy-fg, #0a0a0a); font-weight: var(--roxy-weight-bold, 600); } td.bar-cell { width: 30%; } .mini-bar { height: 8px; background: var(--roxy-border, #e4e4e7); border-radius: var(--roxy-radius-full, 9999px); overflow: hidden; } .mini-bar > span { display: block; height: 100%; background: var(--roxy-accent, #f59e0b); transition: width var(--roxy-motion-duration, 200ms) var(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1)); } .tags { display: flex; flex-wrap: wrap; gap: var(--roxy-space-xs, 0.25rem); } .tags span { padding: 2px 8px; border-radius: var(--roxy-radius-full, 9999px); font-size: var(--roxy-text-xs, 0.75rem); } .tags .dosha { background: color-mix(in srgb, var(--roxy-danger, #dc2626) 16%, transparent); color: var(--roxy-danger-fg, #991b1b); } .tags .cancel { background: color-mix(in srgb, var(--roxy-success, #16a34a) 18%, transparent); color: var(--roxy-success-fg, #166534); } `, ]; @property({ attribute: false }) data: CompatibilityResponse | null = null; render() { const d = this.data; if (!d) return html`
No Guna Milan data
`; const breakdown = (d.breakdown ?? []).filter( (b) => b?.category !== undefined, ); const score = d.total ?? 0; const max = d.maxScore ?? 36; const pct = (score / max) * 100; const trackColor = 'color-mix(in srgb, var(--roxy-border) 50%, transparent)'; const fillColor = pct >= 70 ? 'var(--roxy-success)' : pct >= 50 ? 'var(--roxy-warning)' : 'var(--roxy-danger)'; // SVG circle with r=45: circumference = 2 * pi * 45 = 282.74 // dasharray segments = pct * 2.827, (100 - pct) * 2.827 const dashFill = pct * 2.827; const dashGap = (100 - pct) * 2.827; return html`
${formatNumber(d.total, 1)} / ${d.maxScore} ${ typeof d.percentage === 'number' ? html` ${formatPercent(d.percentage, 1)} ` : nothing }
${ d.recommendation ? html`${d.recommendation}` : nothing }
${ breakdown.length > 0 ? html` ${breakdown.map((b) => { const score = b.score ?? 0; const maxScore = b.maxScore ?? defaultMax(b.category); const pct = maxScore ? (score / maxScore) * 100 : 0; return html``; })}
Category Progress Score
${b.category}
${formatNumber(score, 1)} / ${maxScore}
` : nothing } ${ (d.doshas?.length ?? 0) > 0 || (d.doshaCancellations?.length ?? 0) > 0 ? html`
${d.doshas?.map((x) => html`${x}`)} ${d.doshaCancellations?.map( (x) => html`${x.dosha} cancelled`, )}
` : nothing }
`; } } function defaultMax(name?: string): number { if (!name) return 1; switch (name.toLowerCase()) { case 'varna': return 1; case 'vasya': return 2; case 'tara': return 3; case 'yoni': return 4; case 'maitri': return 5; case 'gana': return 6; case 'bhakoot': return 7; case 'nadi': return 8; default: return 1; } } export const GUNA_CATEGORIES = STANDARD_CATEGORIES; declare global { interface HTMLElementTagNameMap { 'roxy-guna-milan': RoxyGunaMilan; } }