import { css, html, nothing } from 'lit';
import { property } from 'lit/decorators.js';
import { tarotCatalogEndpoints } from '../generated/endpoint-defaults.js';
import type { ListCardsResponse } from '../types/index.js';
import { RoxyDataElement } from '../utils/base-element.js';
import { baseStyles } from '../utils/base-styles.js';
import { customElement } from '../utils/define.js';
import { arcanaText, suitText } from '../utils/tarot.js';
/** A single card row from the catalog response. Kept spec-derived so the tile never reads a field the API does not return. */
type CatalogCard = ListCardsResponse['cards'][number];
/**
* Tarot catalog. Renders GET /tarot/cards as a responsive gallery of the deck: each tile carries the Rider-Waite-Smith artwork, the card name, and an arcana and suit caption. A picked tile emits `roxy-symbol-select` ({ id, name }) for a host that pairs its own ``, and in self-fetch mode the deck opens on load under its arcana and suit filters, pages on with Show more, and opens the picked card under the gallery itself.
*/
@customElement('roxy-tarot-catalog')
export class RoxyTarotCatalog extends RoxyDataElement {
static endpoints = tarotCatalogEndpoints;
static styles = [
baseStyles,
css`
.wrap {
display: grid;
/* Never an implicit auto column: it floors at min-content, so one long
* unbreakable string widens the track past the padded card. */
grid-template-columns: minmax(0, 1fr);
gap: var(--roxy-space-md, 1rem);
}
.head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--roxy-space-sm, 0.5rem);
flex-wrap: wrap;
}
.title {
margin: 0;
font-size: var(--roxy-text-lg, 1.125rem);
font-weight: var(--roxy-weight-bold, 600);
color: var(--roxy-fg, #0a0a0a);
}
.count {
color: var(--roxy-muted, #71717a);
font-size: var(--roxy-text-sm, 0.875rem);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
gap: var(--roxy-space-md, 1rem);
margin: 0;
padding: 0;
list-style: none;
}
.tile {
display: grid;
gap: var(--roxy-space-xs, 0.25rem);
width: 100%;
text-align: left;
font: inherit;
color: inherit;
background: var(--roxy-surface, #fff);
border: 1px solid var(--roxy-border, #e4e4e7);
border-radius: var(--roxy-radius-md, 8px);
padding: var(--roxy-space-sm, 0.5rem);
box-shadow: var(--roxy-shadow-sm);
cursor: pointer;
transition: border-color 0.12s ease;
}
.tile[aria-pressed='true'],
.tile:hover,
.tile:focus-visible {
border-color: var(--roxy-accent, #f59e0b);
outline: none;
}
.detail {
margin-top: var(--roxy-space-md, 1rem);
}
.art {
aspect-ratio: 2 / 3;
width: 100%;
border-radius: var(--roxy-radius-sm, 4px);
object-fit: cover;
background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 35%, transparent);
}
.name {
margin: 0;
font-size: var(--roxy-text-sm, 0.875rem);
font-weight: var(--roxy-weight-bold, 600);
color: var(--roxy-fg, #0a0a0a);
}
.meta {
margin: 0;
font-size: var(--roxy-text-xs, 0.75rem);
color: var(--roxy-muted, #71717a);
}
`,
];
/**
* Override the auto-derived gallery heading. Empty by default, in which case the heading is "Tarot deck".
*/
@property({ type: String, reflect: true })
heading = '';
protected rowDetail = { tag: 'roxy-tarot-card', path: 'tarot/cards/{id}' };
protected listKey = 'cards';
protected renderData(d: ListCardsResponse) {
const cards = d.cards ?? [];
if (cards.length === 0) return this.renderEmpty();
const title = this.heading || this.t('Tarot deck');
const total = typeof d.total === 'number' ? d.total : cards.length;
const locale = this.effectiveLang();
return html`
${title}
${total === 1 ? this.t('1 card') : this.t('{{count}} cards', { count: total })}
${cards.map(
(c) => html`
this.pickRow({ id: c.id, name: c.name })}
>
${
c.imageUrl
? html` `
: html`
`
}
${c.name}
${cardMeta(locale, c)}
`,
)}
${this.renderMore()}
${this.openedRow ? html`${this.openedRow}
` : nothing}
`;
}
}
/** Caption line for a catalog tile: the published arcana name, and the suit for a minor card. */
function cardMeta(locale: string | undefined, c: CatalogCard): string {
const arcana = arcanaText(locale, c.arcana);
return c.suit ? `${arcana} ยท ${suitText(locale, c.suit)}` : arcana;
}
declare global {
interface HTMLElementTagNameMap {
'roxy-tarot-catalog': RoxyTarotCatalog;
}
}