import { css, html, LitElement, nothing } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { SIGN_GLYPH } from '../tokens/index.js'; import type { GetDailyHoroscopeResponse, GetMonthlyHoroscopeResponse, GetWeeklyHoroscopeResponse, } from '../types/index.js'; import { baseStyles } from '../utils/base-styles.js'; import { capitalize } from '../utils/string.js'; type HoroscopeData = | GetDailyHoroscopeResponse | GetWeeklyHoroscopeResponse | GetMonthlyHoroscopeResponse; /** * Daily, weekly, or monthly horoscope card. Pass `data` from * /astrology/horoscope/{sign}/{daily|weekly|monthly}. */ @customElement('roxy-horoscope-card') export class RoxyHoroscopeCard 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); } .head { display: flex; align-items: center; gap: var(--roxy-space-md, 1rem); } .glyph { font-size: 2.25rem; color: var(--roxy-accent-fg, #b45309); line-height: 1; } .title { font-size: var(--roxy-text-xl, 1.5rem); font-weight: var(--roxy-weight-bold, 600); margin: 0; letter-spacing: var(--roxy-tracking-tight); text-transform: capitalize; } .date { font-size: var(--roxy-text-sm, 0.875rem); color: var(--roxy-muted, #71717a); } .energy { margin-left: auto; font-variant-numeric: tabular-nums; font-size: var(--roxy-text-sm, 0.875rem); color: var(--roxy-secondary, #475569); } .energy-bar { display: inline-block; width: 6rem; height: 6px; background: var(--roxy-border, #e4e4e7); border-radius: var(--roxy-radius-full, 9999px); overflow: hidden; margin-left: 6px; vertical-align: middle; } .energy-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)); } .overview { font-size: var(--roxy-text-base, 1rem); color: var(--roxy-fg, #0a0a0a); margin: 0; } .sections { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: var(--roxy-space-md, 1rem); } .section h3 { margin: 0 0 var(--roxy-space-xs, 0.25rem) 0; font-size: var(--roxy-text-xs, 0.75rem); color: var(--roxy-muted, #71717a); font-weight: var(--roxy-weight-bold, 600); text-transform: uppercase; letter-spacing: 0.06em; } .section p { margin: 0; font-size: var(--roxy-text-sm, 0.875rem); color: var(--roxy-fg, #0a0a0a); } .lucky { display: flex; flex-wrap: wrap; gap: var(--roxy-space-sm, 0.5rem); border-top: 1px solid var(--roxy-border, #e4e4e7); padding-top: var(--roxy-space-md, 1rem); font-size: var(--roxy-text-sm, 0.875rem); color: var(--roxy-secondary, #475569); } .lucky strong { color: var(--roxy-fg, #0a0a0a); font-weight: var(--roxy-weight-bold, 600); } .compat-wrap { width: 100%; display: flex; align-items: center; flex-wrap: wrap; gap: var(--roxy-space-xs, 0.25rem); } .compat { display: flex; flex-wrap: wrap; gap: var(--roxy-space-xs, 0.25rem); } .compat span { background: color-mix(in srgb, var(--roxy-accent, #f59e0b) 16%, transparent); color: var(--roxy-fg, #0a0a0a); padding: 2px 8px; border-radius: var(--roxy-radius-full, 9999px); font-size: var(--roxy-text-xs, 0.75rem); text-transform: capitalize; } `, ]; @property({ attribute: false }) data: HoroscopeData | null = null; @property({ type: String, reflect: true }) period: 'daily' | 'weekly' | 'monthly' = 'daily'; render() { const d = this.data; if (!d) return html`
No horoscope data
`; const sign = d.sign ?? ''; const glyph = sign ? (SIGN_GLYPH[capitalize(sign)] ?? '') : ''; const energy = 'energyRating' in d && typeof d.energyRating === 'number' ? d.energyRating : null; const dateLabel = ('date' in d && d.date) || ('week' in d && d.week) || ('month' in d && d.month) || ''; return html`

${sign} ${this.period}

${dateLabel ? html`
${dateLabel}
` : nothing}
${ energy !== null ? html` Energy ${energy}/10 ` : nothing }
${d.overview ? html`

${d.overview}

` : nothing}
${ d.love ? html`

Love

${d.love}

` : nothing } ${ d.career ? html`

Career

${d.career}

` : nothing } ${ d.health ? html`

Health

${d.health}

` : nothing } ${ d.finance ? html`

Finance

${d.finance}

` : nothing } ${ 'advice' in d && d.advice ? html`

Advice

${d.advice}

` : nothing }
${(() => { const luckyNumber = 'luckyNumber' in d && d.luckyNumber !== undefined ? d.luckyNumber : undefined; const luckyColor = 'luckyColor' in d && d.luckyColor ? d.luckyColor : ''; const luckyNumbers = 'luckyNumbers' in d && d.luckyNumbers ? d.luckyNumbers : []; const luckyDays = 'luckyDays' in d && d.luckyDays ? d.luckyDays : []; const compatibleSigns = d.compatibleSigns ?? []; if ( luckyNumber === undefined && !luckyColor && luckyNumbers.length === 0 && luckyDays.length === 0 && compatibleSigns.length === 0 ) return nothing; return html`
${ luckyNumber !== undefined ? html`Lucky number ${luckyNumber}` : nothing } ${ luckyColor ? html`Lucky color ${luckyColor}` : nothing } ${ luckyNumbers.length ? html`Lucky numbers ${luckyNumbers.join(', ')}` : nothing } ${ luckyDays.length ? html`Lucky days ${luckyDays.join(', ')}` : nothing } ${ compatibleSigns.length ? html` Best with ${compatibleSigns.map( (s) => html`${s}`, )} ` : nothing }
`; })()}
`; } } declare global { interface HTMLElementTagNameMap { 'roxy-horoscope-card': RoxyHoroscopeCard; } }