@roxyapi/ui 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/components/yoga-list.ts", "../../src/utils/base-styles.ts", "../../src/utils/debounce.ts"],
4
- "sourcesContent": ["import { css, html, LitElement, nothing } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport type { GetYogaResponse, ListYogasResponse } from '../types/index.js';\nimport { baseStyles } from '../utils/base-styles.js';\nimport { debounce } from '../utils/debounce.js';\n\ntype YogaListData =\n\t| ListYogasResponse\n\t| GetYogaResponse\n\t| { yogas: Array<GetYogaResponse> };\n\n/**\n * Yoga catalog and detail renderer. Accepts three data modes:\n * - Catalog: ListYogasResponse (yogas array of {id, name} + total)\n * - Detail: GetYogaResponse (single yoga with description, result, quality)\n * - Detail array: { yogas: Array<GetYogaResponse> } for pre-filtered sets\n *\n * Catalog and detail-array modes include a live search filter.\n */\n@customElement('roxy-yoga-list')\nexport class RoxyYogaList extends LitElement {\n\tstatic styles = [\n\t\tbaseStyles,\n\t\tcss`\n\t\t\t.wrap {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgap: var(--roxy-space-md, 1rem);\n\t\t\t}\n\t\t\t.head {\n\t\t\t\tdisplay: flex;\n\t\t\t\tjustify-content: space-between;\n\t\t\t\talign-items: baseline;\n\t\t\t\tflex-wrap: wrap;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t}\n\t\t\t.title {\n\t\t\t\tfont-size: var(--roxy-text-lg, 1.125rem);\n\t\t\t\tfont-weight: var(--roxy-weight-bold, 600);\n\t\t\t\tmargin: 0;\n\t\t\t}\n\t\t\t.count {\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t}\n\t\t\t.search-wrap {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t}\n\t\t\t.search {\n\t\t\t\twidth: 100%;\n\t\t\t\tmax-width: 280px;\n\t\t\t\tpadding: 0.35em 0.75em;\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t\tfont-family: var(--roxy-font-sans);\n\t\t\t\tborder: 1px solid var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t\t\t\tbackground: var(--roxy-bg, #fff);\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t\toutline: none;\n\t\t\t}\n\t\t\t.search::placeholder {\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t\topacity: 0.65;\n\t\t\t}\n\t\t\t.search:focus {\n\t\t\t\tborder-color: var(--roxy-accent, #f59e0b);\n\t\t\t\tbox-shadow: 0 0 0 2px color-mix(in srgb, var(--roxy-accent, #f59e0b) 30%, transparent);\n\t\t\t}\n\t\t\t.grid {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t\tgrid-template-columns: repeat(auto-fill, minmax(180px, 1fr));\n\t\t\t}\n\t\t\t.yoga-chip {\n\t\t\t\tpadding: 0.4em 0.8em;\n\t\t\t\tborder: 1px solid var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t\tbackground: var(--roxy-bg, #fff);\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t\tword-break: break-word;\n\t\t\t}\n\t\t\t.yoga-chip .yoga-id {\n\t\t\t\tdisplay: block;\n\t\t\t\tfont-size: 0.7em;\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t\topacity: 0.75;\n\t\t\t\tmargin-top: 0.15em;\n\t\t\t}\n\t\t\t.detail-card {\n\t\t\t\tborder: 1px solid var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t\t\t\tpadding: var(--roxy-space-md, 1rem);\n\t\t\t\tbackground: var(--roxy-bg, #fff);\n\t\t\t\tdisplay: grid;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t}\n\t\t\t.detail-name {\n\t\t\t\tfont-size: var(--roxy-text-lg, 1.125rem);\n\t\t\t\tfont-weight: var(--roxy-weight-bold, 600);\n\t\t\t\tmargin: 0;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t\tflex-wrap: wrap;\n\t\t\t}\n\t\t\t.quality-chip {\n\t\t\t\tdisplay: inline-block;\n\t\t\t\tfont-size: var(--roxy-text-xs, 0.75rem);\n\t\t\t\tfont-weight: 600;\n\t\t\t\tpadding: 0.15em 0.6em;\n\t\t\t\tborder-radius: 999px;\n\t\t\t}\n\t\t\t.quality-Positive {\n\t\t\t\tbackground: color-mix(in srgb, var(--roxy-success, #22c55e) 18%, transparent);\n\t\t\t\tcolor: var(--roxy-success-fg, #15803d);\n\t\t\t\tborder: 1px solid color-mix(in srgb, var(--roxy-success, #22c55e) 40%, transparent);\n\t\t\t}\n\t\t\t.quality-Negative {\n\t\t\t\tbackground: color-mix(in srgb, var(--roxy-danger, #ef4444) 18%, transparent);\n\t\t\t\tcolor: var(--roxy-danger-fg, #b91c1c);\n\t\t\t\tborder: 1px solid color-mix(in srgb, var(--roxy-danger, #ef4444) 40%, transparent);\n\t\t\t}\n\t\t\t.quality-Both {\n\t\t\t\tbackground: color-mix(in srgb, var(--roxy-warning, #f59e0b) 18%, transparent);\n\t\t\t\tcolor: var(--roxy-warning-fg, #b45309);\n\t\t\t\tborder: 1px solid color-mix(in srgb, var(--roxy-warning, #f59e0b) 40%, transparent);\n\t\t\t}\n\t\t\t.description {\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tmargin: 0;\n\t\t\t\tline-height: var(--roxy-leading-normal, 1.5);\n\t\t\t}\n\t\t\tdetails {\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t}\n\t\t\tdetails summary {\n\t\t\t\tcursor: pointer;\n\t\t\t\tcolor: var(--roxy-accent-fg, #b45309);\n\t\t\t\tfont-weight: 500;\n\t\t\t\tpadding: 0.25em 0;\n\t\t\t\tlist-style: none;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tgap: 0.4em;\n\t\t\t}\n\t\t\tdetails summary::before {\n\t\t\t\tcontent: '+';\n\t\t\t\tfont-size: 1.1em;\n\t\t\t\tline-height: 1;\n\t\t\t}\n\t\t\tdetails[open] summary::before {\n\t\t\t\tcontent: '-';\n\t\t\t}\n\t\t\tdetails .result-body {\n\t\t\t\tpadding-top: var(--roxy-space-xs, 0.25rem);\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t\tline-height: var(--roxy-leading-normal, 1.5);\n\t\t\t}\n\t\t\t.no-results {\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t\tpadding: var(--roxy-space-md, 1rem) 0;\n\t\t\t\ttext-align: center;\n\t\t\t}\n\t\t\t.detail-grid {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t}\n\t\t`,\n\t];\n\n\t@property({ attribute: false })\n\tdata: YogaListData | null = null;\n\n\t@state()\n\tprivate filter = '';\n\n\tprivate readonly handleInput = debounce((e: Event) => {\n\t\tthis.filter = (e.target as HTMLInputElement).value;\n\t}, 200);\n\n\tprivate renderQualityChip(quality: string) {\n\t\tconst cls = `quality-chip quality-${quality}`;\n\t\treturn html`<span class=${cls}>${quality}</span>`;\n\t}\n\n\tprivate renderDetailCard(yoga: GetYogaResponse) {\n\t\treturn html`<div class=\"detail-card\">\n\t\t\t<p class=\"detail-name\">\n\t\t\t\t${yoga.name}\n\t\t\t\t${yoga.quality ? this.renderQualityChip(yoga.quality) : nothing}\n\t\t\t</p>\n\t\t\t${\n\t\t\t\tyoga.description\n\t\t\t\t\t? html`<p class=\"description\">${yoga.description}</p>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t\t${\n\t\t\t\tyoga.result\n\t\t\t\t\t? html`<details>\n\t\t\t\t\t\t<summary>Effects</summary>\n\t\t\t\t\t\t<div class=\"result-body\">${yoga.result}</div>\n\t\t\t\t\t</details>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t</div>`;\n\t}\n\n\trender() {\n\t\tif (!this.data)\n\t\t\treturn html`<div class=\"roxy-empty\" role=\"status\">No yoga data</div>`;\n\n\t\tconst d = this.data;\n\t\tconst lc = this.filter.toLowerCase();\n\n\t\t// Detail mode: single GetYogaResponse\n\t\tif (\n\t\t\t'description' in d &&\n\t\t\ttypeof (d as GetYogaResponse).description === 'string'\n\t\t) {\n\t\t\tconst yoga = d as GetYogaResponse;\n\t\t\treturn html`<div class=\"wrap\">${this.renderDetailCard(yoga)}</div>`;\n\t\t}\n\n\t\t// Detail-array mode: { yogas: Array<GetYogaResponse> } where items have description\n\t\tif ('yogas' in d && Array.isArray((d as { yogas: unknown[] }).yogas)) {\n\t\t\tconst allYogas = (\n\t\t\t\td as { yogas: Array<GetYogaResponse | { id: string; name: string }> }\n\t\t\t).yogas;\n\t\t\tconst isDetailArray = allYogas.length > 0 && 'description' in allYogas[0];\n\n\t\t\tif (isDetailArray) {\n\t\t\t\tconst detailYogas = allYogas as GetYogaResponse[];\n\t\t\t\tconst filtered = lc\n\t\t\t\t\t? detailYogas.filter((y) => y.name.toLowerCase().includes(lc))\n\t\t\t\t\t: detailYogas;\n\t\t\t\tconst total = (d as ListYogasResponse).total;\n\t\t\t\treturn html`<div class=\"wrap\">\n\t\t\t\t\t<div class=\"head\">\n\t\t\t\t\t\t<h2 class=\"title\">Yoga catalog</h2>\n\t\t\t\t\t\t${\n\t\t\t\t\t\t\ttotal !== undefined\n\t\t\t\t\t\t\t\t? html`<span class=\"count\">${total} total</span>`\n\t\t\t\t\t\t\t\t: nothing\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"search-wrap\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tclass=\"search\"\n\t\t\t\t\t\t\ttype=\"search\"\n\t\t\t\t\t\t\tplaceholder=\"Filter yogas...\"\n\t\t\t\t\t\t\taria-label=\"Filter yoga list by name\"\n\t\t\t\t\t\t\t.value=${this.filter}\n\t\t\t\t\t\t\t@input=${this.handleInput}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"detail-grid\"\n\t\t\t\t\t\trole=\"region\"\n\t\t\t\t\t\taria-live=\"polite\"\n\t\t\t\t\t\taria-label=\"Yoga results\"\n\t\t\t\t\t>\n\t\t\t\t\t\t${\n\t\t\t\t\t\t\tfiltered.length > 0\n\t\t\t\t\t\t\t\t? filtered.map((y) => this.renderDetailCard(y))\n\t\t\t\t\t\t\t\t: html`<p class=\"no-results\">No yogas match your search.</p>`\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t</div>`;\n\t\t\t}\n\n\t\t\t// Catalog mode: ListYogasResponse with {id, name} items\n\t\t\tconst catalogYogas = allYogas as Array<{ id: string; name: string }>;\n\t\t\tconst filtered = lc\n\t\t\t\t? catalogYogas.filter((y) => y.name.toLowerCase().includes(lc))\n\t\t\t\t: catalogYogas;\n\t\t\tconst total = (d as ListYogasResponse).total;\n\t\t\treturn html`<div class=\"wrap\">\n\t\t\t\t<div class=\"head\">\n\t\t\t\t\t<h2 class=\"title\">Yoga catalog</h2>\n\t\t\t\t\t${\n\t\t\t\t\t\ttotal !== undefined\n\t\t\t\t\t\t\t? html`<span class=\"count\">${total} total</span>`\n\t\t\t\t\t\t\t: nothing\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t\t<div class=\"search-wrap\">\n\t\t\t\t\t<input\n\t\t\t\t\t\tclass=\"search\"\n\t\t\t\t\t\ttype=\"search\"\n\t\t\t\t\t\tplaceholder=\"Filter yogas...\"\n\t\t\t\t\t\taria-label=\"Filter yoga list by name\"\n\t\t\t\t\t\t.value=${this.filter}\n\t\t\t\t\t\t@input=${this.handleInput}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tclass=\"grid\"\n\t\t\t\t\trole=\"region\"\n\t\t\t\t\taria-live=\"polite\"\n\t\t\t\t\taria-label=\"Yoga results\"\n\t\t\t\t>\n\t\t\t\t\t${\n\t\t\t\t\t\tfiltered.length > 0\n\t\t\t\t\t\t\t? filtered.map(\n\t\t\t\t\t\t\t\t\t(y) => html`<div class=\"yoga-chip\">\n\t\t\t\t\t\t\t\t\t${y.name}\n\t\t\t\t\t\t\t\t\t<span class=\"yoga-id\">${y.id}</span>\n\t\t\t\t\t\t\t\t</div>`,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t: html`<p class=\"no-results\">No yogas match your search.</p>`\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t</div>`;\n\t\t}\n\n\t\treturn html`<div class=\"roxy-empty\" role=\"status\">No yoga data</div>`;\n\t}\n}\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'roxy-yoga-list': RoxyYogaList;\n\t}\n}\n", "import { css } from 'lit';\n\n/**\n * Shared host styles every component pulls in. Sets default font, color,\n * container query support, and the entry fade-in.\n */\nexport const baseStyles = css`\n\t:host {\n\t\tdisplay: block;\n\t\tcontainer-type: inline-size;\n\t\tfont-family: var(\n\t\t\t--roxy-font-sans,\n\t\t\tsystem-ui,\n\t\t\t-apple-system,\n\t\t\tBlinkMacSystemFont,\n\t\t\t'Segoe UI',\n\t\t\tRoboto,\n\t\t\tsans-serif\n\t\t);\n\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\tbackground: transparent;\n\t\tfont-size: var(--roxy-text-base, 1rem);\n\t\tline-height: var(--roxy-leading-normal, 1.5);\n\t\tanimation: roxy-fade-in var(--roxy-motion-duration, 200ms)\n\t\t\tvar(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1)) both;\n\t}\n\n\t*,\n\t*::before,\n\t*::after {\n\t\tbox-sizing: border-box;\n\t}\n\n\t@keyframes roxy-fade-in {\n\t\tfrom {\n\t\t\topacity: 0;\n\t\t\ttransform: translateY(2px);\n\t\t}\n\t\tto {\n\t\t\topacity: 1;\n\t\t\ttransform: translateY(0);\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t:host {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-skeleton {\n\t\tbackground: linear-gradient(\n\t\t\t90deg,\n\t\t\tvar(--roxy-border, #e4e4e7) 0%,\n\t\t\tcolor-mix(in srgb, var(--roxy-border, #e4e4e7) 60%, transparent) 50%,\n\t\t\tvar(--roxy-border, #e4e4e7) 100%\n\t\t);\n\t\tbackground-size: 200% 100%;\n\t\tanimation: roxy-shimmer 1.4s ease-in-out infinite;\n\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t}\n\n\t@keyframes roxy-shimmer {\n\t\t0% {\n\t\t\tbackground-position: 200% 0;\n\t\t}\n\t\t100% {\n\t\t\tbackground-position: -200% 0;\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t.roxy-skeleton {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-empty {\n\t\tpadding: var(--roxy-space-lg, 1.5rem);\n\t\tcolor: var(--roxy-muted, #71717a);\n\t\ttext-align: center;\n\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t}\n\n\t:host(:focus-within) .roxy-card {\n\t\toutline: 2px solid var(--roxy-ring, rgba(245, 158, 11, 0.4));\n\t\toutline-offset: 2px;\n\t}\n`;\n", "/**\n * Lightweight debounce for input handlers. Used by location search.\n *\n * The returned function exposes a `.cancel()` method so callers can clear a\n * pending invocation when the host element disconnects, preventing the timer\n * from firing on a detached node and mutating reactive state after teardown.\n */\nexport interface Debounced<F extends (...args: never[]) => unknown> {\n\t(...args: Parameters<F>): void;\n\tcancel: () => void;\n}\n\nexport function debounce<F extends (...args: never[]) => unknown>(\n\tfn: F,\n\twait: number,\n): Debounced<F> {\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tconst debounced = ((...args: Parameters<F>) => {\n\t\tif (timer) clearTimeout(timer);\n\t\ttimer = setTimeout(() => {\n\t\t\ttimer = undefined;\n\t\t\tfn(...args);\n\t\t}, wait);\n\t}) as Debounced<F>;\n\tdebounced.cancel = () => {\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\ttimer = undefined;\n\t\t}\n\t};\n\treturn debounced;\n}\n"],
5
- "mappings": "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",
3
+ "sources": ["../../src/components/yoga-list.ts", "../../src/utils/base-styles.ts"],
4
+ "sourcesContent": ["import { css, html, LitElement, nothing } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport type { GetYogaResponse, ListYogasResponse } from '../types/index.js';\nimport { baseStyles } from '../utils/base-styles.js';\n\ntype YogaListData =\n\t| ListYogasResponse\n\t| GetYogaResponse\n\t| { yogas: Array<GetYogaResponse> };\n\n/**\n * Yoga catalog and detail renderer. Accepts three data modes:\n * - Catalog: ListYogasResponse (yogas array of {id, name} + total)\n * - Detail: GetYogaResponse (single yoga with description, result, quality)\n * - Detail array: { yogas: Array<GetYogaResponse> } for pre-filtered sets\n *\n * Catalog and detail-array modes include a live search filter.\n */\n@customElement('roxy-yoga-list')\nexport class RoxyYogaList extends LitElement {\n\tstatic styles = [\n\t\tbaseStyles,\n\t\tcss`\n\t\t\t.wrap {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgap: var(--roxy-space-md, 1rem);\n\t\t\t}\n\t\t\t.head {\n\t\t\t\tdisplay: flex;\n\t\t\t\tjustify-content: space-between;\n\t\t\t\talign-items: baseline;\n\t\t\t\tflex-wrap: wrap;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t}\n\t\t\t.title {\n\t\t\t\tfont-size: var(--roxy-text-lg, 1.125rem);\n\t\t\t\tfont-weight: var(--roxy-weight-bold, 600);\n\t\t\t\tmargin: 0;\n\t\t\t}\n\t\t\t.count {\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t}\n\t\t\t.search-wrap {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t}\n\t\t\t.search {\n\t\t\t\twidth: 100%;\n\t\t\t\tmax-width: 280px;\n\t\t\t\tpadding: 0.35em 0.75em;\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t\tfont-family: var(--roxy-font-sans);\n\t\t\t\tborder: 1px solid var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t\t\t\tbackground: var(--roxy-bg, #fff);\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t\toutline: none;\n\t\t\t}\n\t\t\t.search::placeholder {\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t\topacity: 0.65;\n\t\t\t}\n\t\t\t.search:focus {\n\t\t\t\tborder-color: var(--roxy-accent, #f59e0b);\n\t\t\t\tbox-shadow: 0 0 0 2px color-mix(in srgb, var(--roxy-accent, #f59e0b) 30%, transparent);\n\t\t\t}\n\t\t\t.grid {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t\tgrid-template-columns: repeat(auto-fill, minmax(180px, 1fr));\n\t\t\t}\n\t\t\t.yoga-chip {\n\t\t\t\tpadding: 0.4em 0.8em;\n\t\t\t\tborder: 1px solid var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t\tbackground: var(--roxy-bg, #fff);\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t\tword-break: break-word;\n\t\t\t}\n\t\t\t.yoga-chip .yoga-id {\n\t\t\t\tdisplay: block;\n\t\t\t\tfont-size: 0.7em;\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t\topacity: 0.75;\n\t\t\t\tmargin-top: 0.15em;\n\t\t\t}\n\t\t\t.detail-card {\n\t\t\t\tborder: 1px solid var(--roxy-border, #e4e4e7);\n\t\t\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t\t\t\tpadding: var(--roxy-space-md, 1rem);\n\t\t\t\tbackground: var(--roxy-bg, #fff);\n\t\t\t\tdisplay: grid;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t}\n\t\t\t.detail-name {\n\t\t\t\tfont-size: var(--roxy-text-lg, 1.125rem);\n\t\t\t\tfont-weight: var(--roxy-weight-bold, 600);\n\t\t\t\tmargin: 0;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t\tflex-wrap: wrap;\n\t\t\t}\n\t\t\t.quality-chip {\n\t\t\t\tdisplay: inline-block;\n\t\t\t\tfont-size: var(--roxy-text-xs, 0.75rem);\n\t\t\t\tfont-weight: 600;\n\t\t\t\tpadding: 0.15em 0.6em;\n\t\t\t\tborder-radius: 999px;\n\t\t\t}\n\t\t\t.quality-Positive {\n\t\t\t\tbackground: color-mix(in srgb, var(--roxy-success, #22c55e) 18%, transparent);\n\t\t\t\tcolor: var(--roxy-success-fg, #15803d);\n\t\t\t\tborder: 1px solid color-mix(in srgb, var(--roxy-success, #22c55e) 40%, transparent);\n\t\t\t}\n\t\t\t.quality-Negative {\n\t\t\t\tbackground: color-mix(in srgb, var(--roxy-danger, #ef4444) 18%, transparent);\n\t\t\t\tcolor: var(--roxy-danger-fg, #b91c1c);\n\t\t\t\tborder: 1px solid color-mix(in srgb, var(--roxy-danger, #ef4444) 40%, transparent);\n\t\t\t}\n\t\t\t.quality-Both {\n\t\t\t\tbackground: color-mix(in srgb, var(--roxy-warning, #f59e0b) 18%, transparent);\n\t\t\t\tcolor: var(--roxy-warning-fg, #b45309);\n\t\t\t\tborder: 1px solid color-mix(in srgb, var(--roxy-warning, #f59e0b) 40%, transparent);\n\t\t\t}\n\t\t\t.description {\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tmargin: 0;\n\t\t\t\tline-height: var(--roxy-leading-normal, 1.5);\n\t\t\t}\n\t\t\tdetails {\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t}\n\t\t\tdetails summary {\n\t\t\t\tcursor: pointer;\n\t\t\t\tcolor: var(--roxy-accent-fg, #b45309);\n\t\t\t\tfont-weight: 500;\n\t\t\t\tpadding: 0.25em 0;\n\t\t\t\tlist-style: none;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tgap: 0.4em;\n\t\t\t}\n\t\t\tdetails summary::before {\n\t\t\t\tcontent: '+';\n\t\t\t\tfont-size: 1.1em;\n\t\t\t\tline-height: 1;\n\t\t\t}\n\t\t\tdetails[open] summary::before {\n\t\t\t\tcontent: '-';\n\t\t\t}\n\t\t\tdetails .result-body {\n\t\t\t\tpadding-top: var(--roxy-space-xs, 0.25rem);\n\t\t\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\t\t\tline-height: var(--roxy-leading-normal, 1.5);\n\t\t\t}\n\t\t\t.no-results {\n\t\t\t\tcolor: var(--roxy-muted, #71717a);\n\t\t\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t\t\t\tpadding: var(--roxy-space-md, 1rem) 0;\n\t\t\t\ttext-align: center;\n\t\t\t}\n\t\t\t.detail-grid {\n\t\t\t\tdisplay: grid;\n\t\t\t\tgap: var(--roxy-space-sm, 0.5rem);\n\t\t\t}\n\t\t`,\n\t];\n\n\t@property({ attribute: false })\n\tdata: YogaListData | null = null;\n\n\t@state()\n\tprivate filter = '';\n\n\tprivate readonly handleInput = (e: Event) => {\n\t\tthis.filter = (e.target as HTMLInputElement).value;\n\t};\n\n\tprivate renderQualityChip(quality: string) {\n\t\tconst cls = `quality-chip quality-${quality}`;\n\t\treturn html`<span class=${cls}>${quality}</span>`;\n\t}\n\n\tprivate renderDetailCard(yoga: GetYogaResponse) {\n\t\treturn html`<div class=\"detail-card\">\n\t\t\t<p class=\"detail-name\">\n\t\t\t\t${yoga.name}\n\t\t\t\t${yoga.quality ? this.renderQualityChip(yoga.quality) : nothing}\n\t\t\t</p>\n\t\t\t${\n\t\t\t\tyoga.description\n\t\t\t\t\t? html`<p class=\"description\">${yoga.description}</p>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t\t${\n\t\t\t\tyoga.result\n\t\t\t\t\t? html`<details>\n\t\t\t\t\t\t<summary>Effects</summary>\n\t\t\t\t\t\t<div class=\"result-body\">${yoga.result}</div>\n\t\t\t\t\t</details>`\n\t\t\t\t\t: nothing\n\t\t\t}\n\t\t</div>`;\n\t}\n\n\trender() {\n\t\tif (!this.data)\n\t\t\treturn html`<div class=\"roxy-empty\" role=\"status\">No yoga data</div>`;\n\n\t\tconst d = this.data;\n\t\tconst lc = this.filter.toLowerCase();\n\n\t\t// Detail mode: single GetYogaResponse\n\t\tif (\n\t\t\t'description' in d &&\n\t\t\ttypeof (d as GetYogaResponse).description === 'string'\n\t\t) {\n\t\t\tconst yoga = d as GetYogaResponse;\n\t\t\treturn html`<div class=\"wrap\">${this.renderDetailCard(yoga)}</div>`;\n\t\t}\n\n\t\t// Detail-array mode: { yogas: Array<GetYogaResponse> } where items have description\n\t\tif ('yogas' in d && Array.isArray((d as { yogas: unknown[] }).yogas)) {\n\t\t\tconst allYogas = (\n\t\t\t\td as { yogas: Array<GetYogaResponse | { id: string; name: string }> }\n\t\t\t).yogas;\n\t\t\tconst isDetailArray = allYogas.length > 0 && 'description' in allYogas[0];\n\n\t\t\tif (isDetailArray) {\n\t\t\t\tconst detailYogas = allYogas as GetYogaResponse[];\n\t\t\t\tconst filtered = lc\n\t\t\t\t\t? detailYogas.filter((y) => y.name.toLowerCase().includes(lc))\n\t\t\t\t\t: detailYogas;\n\t\t\t\tconst total = (d as ListYogasResponse).total;\n\t\t\t\treturn html`<div class=\"wrap\">\n\t\t\t\t\t<div class=\"head\">\n\t\t\t\t\t\t<h2 class=\"title\">Yoga catalog</h2>\n\t\t\t\t\t\t${\n\t\t\t\t\t\t\ttotal !== undefined\n\t\t\t\t\t\t\t\t? html`<span class=\"count\">${total} total</span>`\n\t\t\t\t\t\t\t\t: nothing\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"search-wrap\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tclass=\"search\"\n\t\t\t\t\t\t\ttype=\"search\"\n\t\t\t\t\t\t\tplaceholder=\"Filter yogas...\"\n\t\t\t\t\t\t\taria-label=\"Filter yoga list by name\"\n\t\t\t\t\t\t\t.value=${this.filter}\n\t\t\t\t\t\t\t@input=${this.handleInput}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"detail-grid\"\n\t\t\t\t\t\trole=\"region\"\n\t\t\t\t\t\taria-live=\"polite\"\n\t\t\t\t\t\taria-label=\"Yoga results\"\n\t\t\t\t\t>\n\t\t\t\t\t\t${\n\t\t\t\t\t\t\tfiltered.length > 0\n\t\t\t\t\t\t\t\t? filtered.map((y) => this.renderDetailCard(y))\n\t\t\t\t\t\t\t\t: html`<p class=\"no-results\">No yogas match your search.</p>`\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t</div>`;\n\t\t\t}\n\n\t\t\t// Catalog mode: ListYogasResponse with {id, name} items\n\t\t\tconst catalogYogas = allYogas as Array<{ id: string; name: string }>;\n\t\t\tconst filtered = lc\n\t\t\t\t? catalogYogas.filter((y) => y.name.toLowerCase().includes(lc))\n\t\t\t\t: catalogYogas;\n\t\t\tconst total = (d as ListYogasResponse).total;\n\t\t\treturn html`<div class=\"wrap\">\n\t\t\t\t<div class=\"head\">\n\t\t\t\t\t<h2 class=\"title\">Yoga catalog</h2>\n\t\t\t\t\t${\n\t\t\t\t\t\ttotal !== undefined\n\t\t\t\t\t\t\t? html`<span class=\"count\">${total} total</span>`\n\t\t\t\t\t\t\t: nothing\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t\t<div class=\"search-wrap\">\n\t\t\t\t\t<input\n\t\t\t\t\t\tclass=\"search\"\n\t\t\t\t\t\ttype=\"search\"\n\t\t\t\t\t\tplaceholder=\"Filter yogas...\"\n\t\t\t\t\t\taria-label=\"Filter yoga list by name\"\n\t\t\t\t\t\t.value=${this.filter}\n\t\t\t\t\t\t@input=${this.handleInput}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tclass=\"grid\"\n\t\t\t\t\trole=\"region\"\n\t\t\t\t\taria-live=\"polite\"\n\t\t\t\t\taria-label=\"Yoga results\"\n\t\t\t\t>\n\t\t\t\t\t${\n\t\t\t\t\t\tfiltered.length > 0\n\t\t\t\t\t\t\t? filtered.map(\n\t\t\t\t\t\t\t\t\t(y) => html`<div class=\"yoga-chip\">\n\t\t\t\t\t\t\t\t\t${y.name}\n\t\t\t\t\t\t\t\t\t<span class=\"yoga-id\">${y.id}</span>\n\t\t\t\t\t\t\t\t</div>`,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t: html`<p class=\"no-results\">No yogas match your search.</p>`\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t</div>`;\n\t\t}\n\n\t\treturn html`<div class=\"roxy-empty\" role=\"status\">No yoga data</div>`;\n\t}\n}\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'roxy-yoga-list': RoxyYogaList;\n\t}\n}\n", "import { css } from 'lit';\n\n/**\n * Shared host styles every component pulls in. Sets default font, color,\n * container query support, and the entry fade-in.\n */\nexport const baseStyles = css`\n\t:host {\n\t\tdisplay: block;\n\t\tcontainer-type: inline-size;\n\t\tfont-family: var(\n\t\t\t--roxy-font-sans,\n\t\t\tsystem-ui,\n\t\t\t-apple-system,\n\t\t\tBlinkMacSystemFont,\n\t\t\t'Segoe UI',\n\t\t\tRoboto,\n\t\t\tsans-serif\n\t\t);\n\t\tcolor: var(--roxy-fg, #0a0a0a);\n\t\tbackground: transparent;\n\t\tfont-size: var(--roxy-text-base, 1rem);\n\t\tline-height: var(--roxy-leading-normal, 1.5);\n\t\tanimation: roxy-fade-in var(--roxy-motion-duration, 200ms)\n\t\t\tvar(--roxy-motion-easing, cubic-bezier(0.4, 0, 0.2, 1)) both;\n\t}\n\n\t*,\n\t*::before,\n\t*::after {\n\t\tbox-sizing: border-box;\n\t}\n\n\t@keyframes roxy-fade-in {\n\t\tfrom {\n\t\t\topacity: 0;\n\t\t\ttransform: translateY(2px);\n\t\t}\n\t\tto {\n\t\t\topacity: 1;\n\t\t\ttransform: translateY(0);\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t:host {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-skeleton {\n\t\tbackground: linear-gradient(\n\t\t\t90deg,\n\t\t\tvar(--roxy-border, #e4e4e7) 0%,\n\t\t\tcolor-mix(in srgb, var(--roxy-border, #e4e4e7) 60%, transparent) 50%,\n\t\t\tvar(--roxy-border, #e4e4e7) 100%\n\t\t);\n\t\tbackground-size: 200% 100%;\n\t\tanimation: roxy-shimmer 1.4s ease-in-out infinite;\n\t\tborder-radius: var(--roxy-radius-md, 8px);\n\t}\n\n\t@keyframes roxy-shimmer {\n\t\t0% {\n\t\t\tbackground-position: 200% 0;\n\t\t}\n\t\t100% {\n\t\t\tbackground-position: -200% 0;\n\t\t}\n\t}\n\n\t@media (prefers-reduced-motion: reduce) {\n\t\t.roxy-skeleton {\n\t\t\tanimation: none;\n\t\t}\n\t}\n\n\t.roxy-empty {\n\t\tpadding: var(--roxy-space-lg, 1.5rem);\n\t\tcolor: var(--roxy-muted, #71717a);\n\t\ttext-align: center;\n\t\tfont-size: var(--roxy-text-sm, 0.875rem);\n\t}\n\n\t:host(:focus-within) .roxy-card {\n\t\toutline: 2px solid var(--roxy-ring, rgba(245, 158, 11, 0.4));\n\t\toutline-offset: 2px;\n\t}\n`;\n"],
5
+ "mappings": "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",
6
6
  "names": ["css", "filtered", "total", "css"]
7
7
  }
package/dist/index.cjs CHANGED
@@ -4011,11 +4011,11 @@ var RoxyNatalChart = class extends import_lit18.LitElement {
4011
4011
  if (planets.length === 0) return import_lit18.nothing;
4012
4012
  return import_lit18.html`<section class="interpretations">
4013
4013
  <h3>Planet readings</h3>
4014
- ${planets.map((p) => {
4014
+ ${planets.map((p, idx) => {
4015
4015
  const interp = p.interpretation;
4016
4016
  const glyph = PLANET_GLYPH[capitalize(p.name)] ?? "";
4017
4017
  const deg = formatNumber(p.degree ?? 0, 1);
4018
- return import_lit18.html`<details class="interp-card">
4018
+ return import_lit18.html`<details class="interp-card" name="natal-planet-readings" ?open=${idx === 0}>
4019
4019
  <summary>${glyph} ${p.name} <small>${p.sign ?? ""} ${deg}</small></summary>
4020
4020
  <div class="interp-body">
4021
4021
  ${interp.summary ? import_lit18.html`<p class="interp-summary">${interp.summary}</p>` : import_lit18.nothing}
@@ -5737,7 +5737,7 @@ var RoxyTransitsTable = class extends import_lit25.LitElement {
5737
5737
  ${transitAspects?.length ? import_lit25.html`<div>
5738
5738
  <p class="section-label">Transit aspects</p>
5739
5739
  <div class="overflow-scroll">
5740
- ${this.renderAspectsTable(transitAspects)}
5740
+ ${this.renderAspectsList(transitAspects)}
5741
5741
  </div>
5742
5742
  </div>` : import_lit25.nothing}
5743
5743
  </div>`;
@@ -5797,48 +5797,38 @@ var RoxyTransitsTable = class extends import_lit25.LitElement {
5797
5797
  </tbody>
5798
5798
  </table>`;
5799
5799
  }
5800
- renderAspectsTable(aspects) {
5801
- return import_lit25.html`<table class="aspects-table">
5802
- <thead>
5803
- <tr>
5804
- <th scope="col">Transit Planet</th>
5805
- <th scope="col">Natal Planet</th>
5806
- <th scope="col">Type</th>
5807
- <th scope="col">Orb</th>
5808
- <th scope="col">Status</th>
5809
- <th scope="col">Strength</th>
5810
- <th scope="col" class="interp">Interpretation</th>
5811
- </tr>
5812
- </thead>
5813
- <tbody>
5814
- ${aspects.map((a) => {
5800
+ renderAspectsList(aspects) {
5801
+ return import_lit25.html`<div role="list" aria-label="Transit aspects">
5802
+ ${aspects.map((a, idx) => {
5815
5803
  const tGlyph = PLANET_GLYPH[capitalize(a.transitPlanet)] ?? "";
5816
5804
  const nGlyph = PLANET_GLYPH[capitalize(a.natalPlanet)] ?? "";
5817
- const natureClass = `nature-${(a.nature ?? "").toLowerCase()}`;
5818
- const summary = a.interpretation?.summary ?? "";
5819
- const truncated = summary.length > 120 ? `${summary.slice(0, 120)}...` : summary;
5820
- return import_lit25.html`<tr>
5821
- <td>
5822
- <div class="arrow-cell">
5823
- <span class="glyph" aria-hidden="true">${tGlyph}</span>
5824
- ${a.transitPlanet}
5825
- </div>
5826
- </td>
5827
- <td>
5828
- <div class="arrow-cell">
5829
- <span class="glyph" aria-hidden="true">${nGlyph}</span>
5830
- ${a.natalPlanet}
5831
- </div>
5832
- </td>
5833
- <td class=${natureClass}>${(a.type ?? "").toLowerCase()}</td>
5834
- <td class="num">${formatNumber(a.orb, 2)}</td>
5835
- <td>${a.isApplying ? "Applying" : "Separating"}</td>
5836
- <td class="num">${formatNumber(a.strength, 1)}</td>
5837
- <td class="interp" title=${summary}>${truncated}</td>
5838
- </tr>`;
5805
+ const nature = (a.nature ?? "neutral").toLowerCase();
5806
+ const interp = a.interpretation;
5807
+ const type = (a.type ?? "").toLowerCase();
5808
+ const status = a.isApplying ? "Applying" : "Separating";
5809
+ return import_lit25.html`<details class="aspect-card" role="listitem" name="transit-aspects" ?open=${idx === 0}>
5810
+ <summary>
5811
+ <span aria-hidden="true">${tGlyph}</span>
5812
+ ${a.transitPlanet}
5813
+ <span class="nature-badge ${nature}">${type}</span>
5814
+ <span aria-hidden="true">${nGlyph}</span>
5815
+ ${a.natalPlanet}
5816
+ <span class="meta">
5817
+ ${status} · orb ${formatNumber(a.orb, 2)}° · strength ${formatNumber(a.strength, 1)}
5818
+ </span>
5819
+ </summary>
5820
+ <div class="interp-body">
5821
+ ${interp?.summary ? import_lit25.html`<p>${interp.summary}</p>` : import_lit25.nothing}
5822
+ ${interp?.impact ? import_lit25.html`<p><strong>Impact:</strong> ${interp.impact}</p>` : import_lit25.nothing}
5823
+ ${interp?.timing ? import_lit25.html`<p><strong>Timing:</strong> ${interp.timing}</p>` : import_lit25.nothing}
5824
+ ${interp?.guidance ? import_lit25.html`<p><strong>Guidance:</strong> ${interp.guidance}</p>` : import_lit25.nothing}
5825
+ ${interp?.keywords?.length ? import_lit25.html`<div class="interp-keywords">
5826
+ ${interp.keywords.map((k) => import_lit25.html`<span class="kw">${k}</span>`)}
5827
+ </div>` : import_lit25.nothing}
5828
+ </div>
5829
+ </details>`;
5839
5830
  })}
5840
- </tbody>
5841
- </table>`;
5831
+ </div>`;
5842
5832
  }
5843
5833
  };
5844
5834
  RoxyTransitsTable.styles = [
@@ -5971,43 +5961,73 @@ RoxyTransitsTable.styles = [
5971
5961
  color: var(--roxy-muted, #71717a);
5972
5962
  }
5973
5963
 
5974
- .nature-harmonious {
5975
- color: var(--roxy-success-fg, #166534);
5964
+ .overflow-scroll {
5965
+ overflow-x: auto;
5966
+ -webkit-overflow-scrolling: touch;
5976
5967
  }
5977
5968
 
5978
- .nature-challenging {
5979
- color: var(--roxy-danger-fg, #991b1b);
5969
+ .aspect-card {
5970
+ border: 1px solid var(--roxy-border, #e4e4e7);
5971
+ border-radius: var(--roxy-radius-md, 8px);
5972
+ padding: var(--roxy-space-sm, 0.5rem) var(--roxy-space-md, 1rem);
5973
+ margin-bottom: var(--roxy-space-xs, 0.25rem);
5980
5974
  }
5981
-
5982
- .nature-neutral {
5975
+ .aspect-card summary {
5976
+ cursor: pointer;
5977
+ font-weight: 500;
5978
+ color: var(--roxy-fg, #0a0a0a);
5979
+ display: flex;
5980
+ flex-wrap: wrap;
5981
+ align-items: center;
5982
+ gap: 0.5em;
5983
+ }
5984
+ .aspect-card summary .meta {
5983
5985
  color: var(--roxy-muted, #71717a);
5986
+ font-weight: 400;
5987
+ font-size: var(--roxy-text-xs, 0.75rem);
5988
+ margin-left: auto;
5989
+ font-variant-numeric: tabular-nums;
5984
5990
  }
5985
-
5986
- .arrow-cell {
5987
- display: inline-flex;
5988
- align-items: center;
5989
- gap: 4px;
5990
- white-space: nowrap;
5991
+ .aspect-card .interp-body {
5992
+ margin-top: var(--roxy-space-xs, 0.25rem);
5993
+ color: var(--roxy-fg, #0a0a0a);
5994
+ font-size: var(--roxy-text-sm, 0.875rem);
5995
+ line-height: 1.45;
5991
5996
  }
5992
-
5993
- .interp {
5994
- color: var(--roxy-secondary, #475569);
5997
+ .aspect-card .interp-body p {
5998
+ margin: 0 0 var(--roxy-space-xs, 0.25rem);
5999
+ }
6000
+ .interp-keywords {
6001
+ display: flex;
6002
+ flex-wrap: wrap;
6003
+ gap: 0.25rem;
6004
+ margin-top: 0.5rem;
6005
+ }
6006
+ .interp-keywords .kw {
6007
+ padding: 1px 8px;
6008
+ border-radius: 9999px;
6009
+ background: color-mix(in srgb, var(--roxy-accent, #f59e0b) 14%, transparent);
6010
+ color: var(--roxy-accent-fg, #b45309);
5995
6011
  font-size: var(--roxy-text-xs, 0.75rem);
5996
- max-width: 22rem;
5997
- white-space: nowrap;
5998
- overflow: hidden;
5999
- text-overflow: ellipsis;
6000
6012
  }
6001
-
6002
- @container (max-width: 600px) {
6003
- .interp {
6004
- display: none;
6005
- }
6013
+ .nature-badge {
6014
+ display: inline-block;
6015
+ padding: 1px 8px;
6016
+ border-radius: 9999px;
6017
+ font-size: var(--roxy-text-xs, 0.75rem);
6018
+ font-weight: 600;
6006
6019
  }
6007
-
6008
- .overflow-scroll {
6009
- overflow-x: auto;
6010
- -webkit-overflow-scrolling: touch;
6020
+ .nature-badge.harmonious {
6021
+ background: color-mix(in srgb, var(--roxy-success, #16a34a) 12%, transparent);
6022
+ color: var(--roxy-success-fg, #166534);
6023
+ }
6024
+ .nature-badge.challenging {
6025
+ background: color-mix(in srgb, var(--roxy-danger, #dc2626) 12%, transparent);
6026
+ color: var(--roxy-danger-fg, #991b1b);
6027
+ }
6028
+ .nature-badge.neutral {
6029
+ background: color-mix(in srgb, var(--roxy-border, #e4e4e7) 60%, transparent);
6030
+ color: var(--roxy-fg, #0a0a0a);
6011
6031
  }
6012
6032
  `
6013
6033
  ];
@@ -6137,9 +6157,9 @@ var RoxyYogaList = class extends import_lit27.LitElement {
6137
6157
  super(...arguments);
6138
6158
  this.data = null;
6139
6159
  this.filter = "";
6140
- this.handleInput = debounce((e) => {
6160
+ this.handleInput = (e) => {
6141
6161
  this.filter = e.target.value;
6142
- }, 200);
6162
+ };
6143
6163
  }
6144
6164
  renderQualityChip(quality) {
6145
6165
  const cls = `quality-chip quality-${quality}`;
@@ -6680,7 +6700,7 @@ var ROXY_COMPONENTS = [
6680
6700
  ];
6681
6701
 
6682
6702
  // packages/ui/src/version.ts
6683
- var ROXY_UI_VERSION = "0.2.0";
6703
+ var ROXY_UI_VERSION = "0.2.2";
6684
6704
 
6685
6705
  // packages/ui/src/index.ts
6686
6706
  var ROXY_UI_COMPONENTS = ROXY_COMPONENTS.map((c) => c.slug);