@sankhyatronics/sankhya-cms 0.0.9 → 0.1.1

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.
@@ -1741,7 +1741,7 @@
1741
1741
  "declarations": [
1742
1742
  {
1743
1743
  "kind": "class",
1744
- "description": "The footer, in two layouts:\n\n- **Bar** (default): a copyright line plus an optional \"powered by\" link — what the suite's\n authenticated apps use.\n- **Site**: when `columns`, `socialLinks`, `description` or `imageSrc` is set, the footer becomes a\n marketing-site footer — brand (logo/company name, description, social icons), link columns and a\n bottom bar with the copyright and \"powered by\".\n\nFires a cancelable `footer-link` event (detail `{ href }`) when an internal link is clicked.",
1744
+ "description": "The footer, in two layouts:\r\n\r\n- **Bar** (default): a copyright line plus an optional \"powered by\" link — what the suite's\r\n authenticated apps use.\r\n- **Site**: when `columns`, `socialLinks`, `description` or `imageSrc` is set, the footer becomes a\r\n marketing-site footer — brand (logo/company name, description, social icons), link columns and a\r\n bottom bar with the copyright and \"powered by\".\r\n\r\nFires a cancelable `footer-link` event (detail `{ href }`) when an internal link is clicked.",
1745
1745
  "name": "Footer",
1746
1746
  "members": [
1747
1747
  {
@@ -1751,7 +1751,7 @@
1751
1751
  "text": "boolean"
1752
1752
  },
1753
1753
  "default": "false",
1754
- "description": "Inverts the footer's color roles (light-on-dark instead of dark-on-light) — matches the\nsource component's `inverted` (from its shared `BaseProps`), same as `st-header`'s.",
1754
+ "description": "Inverts the footer's color roles (light-on-dark instead of dark-on-light) — matches the\r\nsource component's `inverted` (from its shared `BaseProps`), same as `st-header`'s.",
1755
1755
  "attribute": "inverted",
1756
1756
  "reflects": true
1757
1757
  },
@@ -1800,7 +1800,7 @@
1800
1800
  "text": "string"
1801
1801
  },
1802
1802
  "default": "''",
1803
- "description": "App version, rendered as `v{version}` after the copyright line — for apps that show their own\nversion in the footer rather than the header title.",
1803
+ "description": "App version, rendered as `v{version}` after the copyright line — for apps that show their own\r\nversion in the footer rather than the header title.",
1804
1804
  "attribute": "version"
1805
1805
  },
1806
1806
  {
@@ -1914,7 +1914,7 @@
1914
1914
  "text": "boolean"
1915
1915
  },
1916
1916
  "default": "false",
1917
- "description": "Inverts the footer's color roles (light-on-dark instead of dark-on-light) — matches the\nsource component's `inverted` (from its shared `BaseProps`), same as `st-header`'s.",
1917
+ "description": "Inverts the footer's color roles (light-on-dark instead of dark-on-light) — matches the\r\nsource component's `inverted` (from its shared `BaseProps`), same as `st-header`'s.",
1918
1918
  "fieldName": "inverted"
1919
1919
  },
1920
1920
  {
@@ -1957,7 +1957,7 @@
1957
1957
  "text": "string"
1958
1958
  },
1959
1959
  "default": "''",
1960
- "description": "App version, rendered as `v{version}` after the copyright line — for apps that show their own\nversion in the footer rather than the header title.",
1960
+ "description": "App version, rendered as `v{version}` after the copyright line — for apps that show their own\r\nversion in the footer rather than the header title.",
1961
1961
  "fieldName": "version"
1962
1962
  },
1963
1963
  {
@@ -296,4 +296,4 @@ Footer = __decorateClass([
296
296
  export {
297
297
  Footer
298
298
  };
299
- //# sourceMappingURL=chunk-YYK4GQUW.js.map
299
+ //# sourceMappingURL=chunk-CDC4P77V.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/footer/Footer.ts"],"sourcesContent":["import { LitElement, PropertyValues, css, html, nothing } from 'lit';\r\nimport { customElement, property } from 'lit/decorators.js';\r\nimport 'iconify-icon';\r\n\r\nimport { controlBaseStyles, rootFontFamilyStyles } from '../shared/styles';\r\n\r\nexport interface FooterLink {\r\n label: string;\r\n href: string;\r\n}\r\n\r\nexport interface FooterColumn {\r\n title: string;\r\n links: FooterLink[];\r\n}\r\n\r\nexport interface FooterSocialLink {\r\n /** Iconify icon name, e.g. `mdi:github`. */\r\n icon: string;\r\n href: string;\r\n}\r\n\r\n/** Fired when an internal footer link is activated — cancelable, so an SPA can `preventDefault()` and route instead. */\r\nexport interface FooterLinkEventDetail {\r\n href: string;\r\n}\r\n\r\n/**\r\n * The footer, in two layouts:\r\n *\r\n * - **Bar** (default): a copyright line plus an optional \"powered by\" link — what the suite's\r\n * authenticated apps use.\r\n * - **Site**: when `columns`, `socialLinks`, `description` or `imageSrc` is set, the footer becomes a\r\n * marketing-site footer — brand (logo/company name, description, social icons), link columns and a\r\n * bottom bar with the copyright and \"powered by\".\r\n *\r\n * Fires a cancelable `footer-link` event (detail `{ href }`) when an internal link is clicked.\r\n */\r\n@customElement('st-footer')\r\nexport class Footer extends LitElement {\r\n static styles = css`\r\n ${controlBaseStyles}\r\n\r\n :host {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: var(--st-space-16);\r\n flex-wrap: wrap;\r\n /* !important on padding/border-top only: a shadow host's own :host rule always loses to ANY\r\n * rule the consuming page has for the same property on that element, regardless of\r\n * specificity — so a page that resets margin/padding/border on every element (e.g. Tailwind\r\n * Preflight, which every app but Template Hub loads) silently zeroes this footer's padding\r\n * and top border unless these two win the conflict outright via !important. */\r\n padding: var(--st-space-16) var(--st-space-28) !important;\r\n border-top: 1px solid var(--st-color-border) !important;\r\n color: var(--st-color-text-muted);\r\n font-size: var(--st-text-xs);\r\n ${rootFontFamilyStyles}\r\n }\r\n\r\n .powered-by {\r\n color: inherit;\r\n text-decoration: none;\r\n font-weight: var(--st-font-weight-semibold);\r\n }\r\n\r\n .powered-by:hover {\r\n color: var(--st-color-text-strong);\r\n }\r\n\r\n :host([inverted]) {\r\n background: var(--st-color-text-strong);\r\n color: var(--st-color-on-solid);\r\n border-top-color: transparent !important;\r\n }\r\n\r\n /* Site layout */\r\n :host([site]) {\r\n display: block;\r\n padding: 64px var(--st-space-28) 32px !important;\r\n font-size: var(--st-text-sm);\r\n }\r\n\r\n .site {\r\n max-width: 1200px;\r\n margin: 0 auto;\r\n }\r\n\r\n .grid {\r\n display: grid;\r\n grid-template-columns: 2fr repeat(3, 1fr);\r\n gap: 64px;\r\n margin-bottom: 64px;\r\n }\r\n\r\n .brand {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 16px;\r\n }\r\n\r\n .logo {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n font-size: 1.25rem;\r\n font-weight: 700;\r\n color: var(--st-color-text-strong);\r\n }\r\n\r\n .logo-img {\r\n height: 32px;\r\n width: auto;\r\n }\r\n\r\n .description {\r\n margin: 0;\r\n max-width: 300px;\r\n line-height: 1.6;\r\n }\r\n\r\n .social {\r\n display: flex;\r\n gap: 16px;\r\n margin-top: 16px;\r\n }\r\n\r\n .social-link,\r\n .link {\r\n color: inherit;\r\n text-decoration: none;\r\n transition: color 0.2s ease;\r\n }\r\n\r\n .social-link:hover,\r\n .link:hover {\r\n color: var(--st-color-text-strong);\r\n }\r\n\r\n .column-title {\r\n margin: 0 0 24px;\r\n font-size: var(--st-text-xs);\r\n font-weight: var(--st-font-weight-semibold);\r\n color: var(--st-color-text-strong);\r\n text-transform: uppercase;\r\n letter-spacing: 0.05em;\r\n }\r\n\r\n .links {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 16px;\r\n }\r\n\r\n .bottom {\r\n padding-top: 32px;\r\n border-top: 1px solid var(--st-color-border);\r\n font-size: var(--st-text-xs);\r\n }\r\n\r\n .bottom-text {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n }\r\n\r\n .bottom-text .powered-by {\r\n font-size: 0.85em;\r\n font-weight: inherit;\r\n opacity: 0.8;\r\n }\r\n\r\n @media (max-width: 1024px) {\r\n .grid {\r\n grid-template-columns: repeat(3, 1fr);\r\n gap: 48px;\r\n }\r\n }\r\n\r\n @media (max-width: 768px) {\r\n .grid {\r\n grid-template-columns: 1fr;\r\n gap: 40px;\r\n }\r\n\r\n .bottom {\r\n text-align: center;\r\n }\r\n }\r\n\r\n :host([site][inverted]) .logo,\r\n :host([site][inverted]) .column-title,\r\n :host([site][inverted]) .social-link:hover,\r\n :host([site][inverted]) .link:hover {\r\n color: var(--st-color-on-solid);\r\n }\r\n\r\n :host([site][inverted]) .bottom {\r\n border-top-color: color-mix(in oklch, var(--st-color-on-solid) 20%, var(--st-color-text-strong));\r\n }\r\n `;\r\n\r\n /** Inverts the footer's color roles (light-on-dark instead of dark-on-light) — matches the\r\n * source component's `inverted` (from its shared `BaseProps`), same as `st-header`'s. */\r\n @property({ type: Boolean, reflect: true })\r\n inverted = false;\r\n\r\n /** Used to build the default `copyright` line (`© {year} {companyName}`) when `copyright` isn't set explicitly. */\r\n @property({ type: String, attribute: 'company-name' })\r\n companyName = '';\r\n\r\n /** Overrides the default computed copyright line entirely. */\r\n @property({ type: String })\r\n copyright = '';\r\n\r\n @property({ type: String, attribute: 'powered-by' })\r\n poweredBy = '';\r\n\r\n @property({ type: String, attribute: 'powered-by-link' })\r\n poweredByLink = '';\r\n\r\n /** App version, rendered as `v{version}` after the copyright line — for apps that show their own\r\n * version in the footer rather than the header title. */\r\n @property({ type: String })\r\n version = '';\r\n\r\n /** Logo shown in the brand block (site layout). */\r\n @property({ type: String, attribute: 'image-src' })\r\n imageSrc = '';\r\n\r\n /** Short blurb under the logo (site layout). */\r\n @property({ type: String })\r\n description = '';\r\n\r\n /** Link columns (site layout). */\r\n @property({ attribute: false })\r\n columns: FooterColumn[] = [];\r\n\r\n /** Social icon links under the description (site layout). */\r\n @property({ attribute: false })\r\n socialLinks: FooterSocialLink[] = [];\r\n\r\n private get isSite(): boolean {\r\n return !!(this.columns.length || this.socialLinks.length || this.description || this.imageSrc);\r\n }\r\n\r\n private get resolvedCopyright(): string {\r\n if (this.copyright) return this.copyright;\r\n if (this.companyName) return `© ${new Date().getFullYear()} ${this.companyName}`;\r\n return `© ${new Date().getFullYear()}`;\r\n }\r\n\r\n protected willUpdate(changed: PropertyValues): void {\r\n if (changed.size) this.toggleAttribute('site', this.isSite);\r\n }\r\n\r\n private onLinkClick(event: MouseEvent, href: string): void {\r\n const select = new CustomEvent<FooterLinkEventDetail>('footer-link', {\r\n detail: { href },\r\n bubbles: true,\r\n composed: true,\r\n cancelable: true\r\n });\r\n this.dispatchEvent(select);\r\n if (select.defaultPrevented) event.preventDefault();\r\n }\r\n\r\n private renderLink(link: FooterLink) {\r\n return /^https?:/i.test(link.href)\r\n ? html`<a part=\"link\" class=\"link\" href=${link.href} target=\"_blank\" rel=\"noopener noreferrer\">${link.label}</a>`\r\n : html`<a part=\"link\" class=\"link\" href=${link.href} @click=${(event: MouseEvent) => this.onLinkClick(event, link.href)}>${link.label}</a>`;\r\n }\r\n\r\n private renderPoweredBy() {\r\n return this.poweredBy\r\n ? html`\r\n <a part=\"powered-by\" class=\"powered-by\" href=${this.poweredByLink || '#'} target=\"_blank\" rel=\"noopener noreferrer\">\r\n ${this.poweredBy}\r\n </a>\r\n `\r\n : nothing;\r\n }\r\n\r\n private renderSite() {\r\n return html`\r\n <div class=\"site\" part=\"site\">\r\n <div class=\"grid\">\r\n <div class=\"brand\" part=\"brand\">\r\n ${this.imageSrc || this.companyName\r\n ? html`<div class=\"logo\">\r\n ${this.imageSrc ? html`<img class=\"logo-img\" src=${this.imageSrc} alt=${this.companyName || 'Logo'} />` : nothing}\r\n ${this.companyName ? html`<span>${this.companyName}</span>` : nothing}\r\n </div>`\r\n : nothing}\r\n ${this.description ? html`<p class=\"description\">${this.description}</p>` : nothing}\r\n ${this.socialLinks.length\r\n ? html`<div class=\"social\">\r\n ${this.socialLinks.map(\r\n social => html`<a part=\"social\" class=\"social-link\" href=${social.href} target=\"_blank\" rel=\"noopener noreferrer\" aria-label=${social.icon}>\r\n <iconify-icon icon=${social.icon} width=\"24\" height=\"24\"></iconify-icon>\r\n </a>`\r\n )}\r\n </div>`\r\n : nothing}\r\n </div>\r\n ${this.columns.map(\r\n column => html`<div class=\"column\" part=\"column\">\r\n <h4 class=\"column-title\">${column.title}</h4>\r\n <div class=\"links\">${column.links.map(link => this.renderLink(link))}</div>\r\n </div>`\r\n )}\r\n </div>\r\n <div class=\"bottom\" part=\"bottom\">\r\n <div class=\"bottom-text\">\r\n <span part=\"copyright\">${this.resolvedCopyright}</span>\r\n ${this.version ? html`<span part=\"version\">v${this.version}</span>` : nothing} ${this.renderPoweredBy()}\r\n </div>\r\n </div>\r\n </div>\r\n `;\r\n }\r\n\r\n render() {\r\n if (this.isSite) return this.renderSite();\r\n return html`\r\n <span part=\"copyright\">${this.resolvedCopyright}</span>\r\n ${this.version ? html`<span part=\"version\">v${this.version}</span>` : nothing} ${this.renderPoweredBy()}\r\n `;\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
@@ -119,6 +119,13 @@ Hero.styles = css`
119
119
  margin: 0 auto;
120
120
  text-align: center;
121
121
  align-items: center;
122
+ /* Set here, not only on :host: text sits on the image/overlay, so a colour class the page puts
123
+ on the host (e.g. a theme text colour) must not turn it dark. */
124
+ color: var(--st-hero-text-color, var(--st-color-on-solid));
125
+ }
126
+
127
+ :host([inverted]) .content {
128
+ color: var(--st-hero-text-color, var(--st-color-text-strong));
122
129
  }
123
130
 
124
131
  :host([text-alignment='left']) .content { text-align: left; align-items: flex-start; }
@@ -218,4 +225,4 @@ Hero = __decorateClass([
218
225
  export {
219
226
  Hero
220
227
  };
221
- //# sourceMappingURL=chunk-TIU73O7I.js.map
228
+ //# sourceMappingURL=chunk-U7YKCCJG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hero/Hero.ts"],"sourcesContent":["import { LitElement, css, html, nothing } from 'lit';\r\nimport { customElement, property } from 'lit/decorators.js';\r\n\r\nimport { controlBaseStyles, rootFontFamilyStyles } from '../shared/styles';\r\nimport { ctaLinkStyles, paddingScaleStyles } from '../shared/section-styles';\r\nimport type { HeroActionEventDetail } from './HeroEvents';\r\n\r\n/**\r\n * Full-width hero banner: background image with a darkening overlay, title, optional subtitle and\r\n * a call-to-action link. Port of `SankhyaUI`'s React `Hero`; the framer-motion entrance is redone\r\n * as CSS keyframes (skipped under `prefers-reduced-motion`). The CTA fires a cancelable `action`\r\n * event (detail `{ href }`) before navigating.\r\n */\r\n@customElement('st-hero')\r\nexport class Hero extends LitElement {\r\n static styles = css`\r\n ${controlBaseStyles}\r\n ${paddingScaleStyles}\r\n\r\n @keyframes st-hero-zoom {\r\n from { opacity: 0; transform: scale(1.1); }\r\n to { opacity: 1; transform: scale(1); }\r\n }\r\n @keyframes st-hero-rise {\r\n from { opacity: 0; transform: translateY(30px); }\r\n to { opacity: 1; transform: none; }\r\n }\r\n @keyframes st-hero-fade {\r\n from { opacity: 0; }\r\n to { opacity: 1; }\r\n }\r\n\r\n :host {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n position: relative;\r\n overflow: hidden;\r\n width: 100%;\r\n box-sizing: border-box;\r\n background-color: var(--st-color-text-strong);\r\n background-repeat: no-repeat;\r\n background-size: cover;\r\n background-position: var(--st-hero-position, center center);\r\n color: var(--st-color-on-solid);\r\n ${rootFontFamilyStyles}\r\n animation: st-hero-zoom 1.5s ease-out;\r\n }\r\n\r\n :host([inverted]) {\r\n background-color: var(--st-color-surface);\r\n color: var(--st-color-text-strong);\r\n }\r\n\r\n .overlay {\r\n position: absolute;\r\n inset: 0;\r\n z-index: 1;\r\n background: linear-gradient(\r\n to bottom,\r\n color-mix(in oklch, var(--st-color-text-strong) 40%, transparent),\r\n color-mix(in oklch, var(--st-color-text-strong) 20%, transparent),\r\n color-mix(in oklch, var(--st-color-text-strong) 40%, transparent)\r\n );\r\n }\r\n\r\n :host([inverted]) .overlay {\r\n display: none;\r\n }\r\n\r\n .content {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--st-space-20);\r\n position: relative;\r\n z-index: 2;\r\n width: 80%;\r\n margin: 0 auto;\r\n text-align: center;\r\n align-items: center;\r\n /* Set here, not only on :host: text sits on the image/overlay, so a colour class the page puts\r\n on the host (e.g. a theme text colour) must not turn it dark. */\r\n color: var(--st-hero-text-color, var(--st-color-on-solid));\r\n }\r\n\r\n :host([inverted]) .content {\r\n color: var(--st-hero-text-color, var(--st-color-text-strong));\r\n }\r\n\r\n :host([text-alignment='left']) .content { text-align: left; align-items: flex-start; }\r\n :host([text-alignment='right']) .content { text-align: right; align-items: flex-end; }\r\n :host([text-alignment='justify']) .content { text-align: justify; align-items: stretch; }\r\n\r\n .title {\r\n font-size: var(--st-cms-text-display);\r\n font-weight: var(--st-font-weight-bold);\r\n line-height: 1.1;\r\n color: inherit;\r\n margin: 0;\r\n animation: st-hero-rise 0.8s 0.3s ease-out backwards;\r\n }\r\n\r\n .subtitle {\r\n font-size: var(--st-cms-text-xl);\r\n line-height: 1.5;\r\n color: inherit;\r\n margin: 0;\r\n animation: st-hero-fade 0.8s 0.5s ease-out backwards;\r\n }\r\n\r\n .actions {\r\n display: flex;\r\n justify-content: center;\r\n width: 100%;\r\n }\r\n\r\n .cta {\r\n ${ctaLinkStyles}\r\n background: var(--st-button-primary-bg);\r\n color: var(--st-button-primary-color);\r\n font-weight: var(--st-button-font-weight);\r\n font-size: var(--st-text-md);\r\n animation: st-hero-rise 0.6s 0.7s ease-out backwards;\r\n }\r\n\r\n .cta:hover {\r\n transform: scale(1.05);\r\n }\r\n\r\n .cta:active {\r\n transform: scale(0.98);\r\n }\r\n\r\n @media (min-width: 768px) {\r\n .content { padding: var(--st-space-32); }\r\n }\r\n @media (min-width: 1024px) {\r\n .content { padding: var(--st-space-48); }\r\n }\r\n\r\n @media (prefers-reduced-motion: reduce) {\r\n :host, .title, .subtitle, .cta { animation: none; }\r\n .cta { transition: none; }\r\n }\r\n `;\r\n\r\n @property({ type: String })\r\n title = '';\r\n\r\n @property({ type: String })\r\n subtitle = '';\r\n\r\n @property({ type: String, attribute: 'image-src' })\r\n imageSrc = '';\r\n\r\n @property({ type: String, attribute: 'action-label' })\r\n actionLabel = '';\r\n\r\n @property({ type: String })\r\n href = '';\r\n\r\n @property({ type: String, reflect: true, attribute: 'text-alignment' })\r\n textAlignment: 'left' | 'center' | 'right' | 'justify' = 'left';\r\n\r\n @property({ type: String, reflect: true })\r\n padding: 'none' | 'small' | 'medium' | 'large' = 'none';\r\n\r\n @property({ type: Boolean, reflect: true })\r\n inverted = false;\r\n\r\n @property({ type: String, attribute: 'focal-point' })\r\n focalPoint: 'top' | 'left' | 'right' | 'bottom' | 'center' = 'center';\r\n\r\n private static positions = {\r\n top: 'center top',\r\n left: 'left center',\r\n right: 'right center',\r\n bottom: 'center bottom',\r\n center: 'center center'\r\n } as const;\r\n\r\n protected willUpdate(): void {\r\n this.style.backgroundImage = this.imageSrc ? `url(${JSON.stringify(this.imageSrc)})` : '';\r\n this.style.setProperty('--st-hero-position', Hero.positions[this.focalPoint] ?? Hero.positions.center);\r\n }\r\n\r\n private onClick = (event: MouseEvent): void => {\r\n const action = new CustomEvent<HeroActionEventDetail>('action', {\r\n detail: { href: this.href },\r\n bubbles: true,\r\n composed: true,\r\n cancelable: true\r\n });\r\n this.dispatchEvent(action);\r\n if (action.defaultPrevented) event.preventDefault();\r\n };\r\n\r\n render() {\r\n return html`\r\n <div class=\"overlay\" part=\"overlay\"></div>\r\n <div class=\"content\" part=\"content\">\r\n <h1 class=\"title\" part=\"title\">${this.title}</h1>\r\n ${this.subtitle ? html`<p class=\"subtitle\" part=\"subtitle\">${this.subtitle}</p>` : nothing}\r\n ${this.actionLabel\r\n ? html`<div class=\"actions\" part=\"actions\">\r\n <a class=\"cta\" part=\"action\" href=${this.href || nothing} @click=${this.onClick}>${this.actionLabel}</a>\r\n </div>`\r\n : nothing}\r\n <slot></slot>\r\n </div>\r\n `;\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
@@ -1,15 +1,15 @@
1
- /**
2
- * Marketing-scale tokens for the CMS section components (hero, features, stats, ...).
3
- * theme.css is tuned for dense app chrome (its largest text step is 20px), so
4
- * the display sizes a content page needs live here. Import after theme.css (shipped in this package);
5
- * override any of these at :root to rescale the CMS components.
6
- */
7
- :root {
8
- --st-cms-text-display: 3.75rem;
9
- --st-cms-text-4xl: 2.25rem;
10
- --st-cms-text-3xl: 1.875rem;
11
- --st-cms-text-xl: 1.25rem;
12
- --st-cms-text-lg: 1.125rem;
13
- --st-cms-section-padding: 4rem;
14
- --st-cms-container-max: 1200px;
15
- }
1
+ /**
2
+ * Marketing-scale tokens for the CMS section components (hero, features, stats, ...).
3
+ * theme.css is tuned for dense app chrome (its largest text step is 20px), so
4
+ * the display sizes a content page needs live here. Import after theme.css (shipped in this package);
5
+ * override any of these at :root to rescale the CMS components.
6
+ */
7
+ :root {
8
+ --st-cms-text-display: 3.75rem;
9
+ --st-cms-text-4xl: 2.25rem;
10
+ --st-cms-text-3xl: 1.875rem;
11
+ --st-cms-text-xl: 1.25rem;
12
+ --st-cms-text-lg: 1.125rem;
13
+ --st-cms-section-padding: 4rem;
14
+ --st-cms-container-max: 1200px;
15
+ }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Footer
3
- } from "../chunk-YYK4GQUW.js";
3
+ } from "../chunk-CDC4P77V.js";
4
4
  import "../chunk-RHAOQ6K4.js";
5
5
  import "../chunk-EUXUH3YW.js";
6
6
  export {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Hero
3
- } from "../chunk-TIU73O7I.js";
3
+ } from "../chunk-U7YKCCJG.js";
4
4
  import "../chunk-3VE6VFJ4.js";
5
5
  import "../chunk-RHAOQ6K4.js";
6
6
  import "../chunk-EUXUH3YW.js";
package/dist/index.js CHANGED
@@ -22,14 +22,14 @@ import {
22
22
  } from "./chunk-E52WO7R7.js";
23
23
  import {
24
24
  Footer
25
- } from "./chunk-YYK4GQUW.js";
25
+ } from "./chunk-CDC4P77V.js";
26
26
  import {
27
27
  Header
28
28
  } from "./chunk-23IHTBAB.js";
29
29
  import "./chunk-3TCP25JG.js";
30
30
  import {
31
31
  Hero
32
- } from "./chunk-TIU73O7I.js";
32
+ } from "./chunk-U7YKCCJG.js";
33
33
  import {
34
34
  IconButton
35
35
  } from "./chunk-XXPBFHWR.js";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/schema/node.ts","../../src/schema/nodeUtils.ts","../../src/schema/components.ts","../../src/schema/validate.ts"],"sourcesContent":["/** Dependency-free node types, safe to import from the content API layer. */\r\n\r\n/** Action reference in JSON, resolved against the `handlers` prop of `DynamicRenderer`. */\r\nexport type ActionRef = `@action:${string}`;\r\n\r\n/** Keys the renderer owns; everything else on a node is passed to the component as a prop. */\r\nexport interface NodeEnvelope<TNode> {\r\n id?: string;\r\n /** Named slot in the parent (e.g. Header: \"utility\"). Omit for the default slot. */\r\n slot?: string;\r\n /** Editor-only label, never passed to the component. */\r\n label?: string;\r\n children?: TNode[];\r\n}\r\n\r\n/**\r\n * A node as it appears in JSON: flat, no `data` wrapper.\r\n * `{ \"type\": \"Hero\", \"id\": \"home-hero\", \"title\": \"...\", \"children\": [...] }`\r\n */\r\nexport interface ComponentNode extends NodeEnvelope<ComponentNode> {\r\n type: string;\r\n [prop: string]: unknown;\r\n}\r\n\r\n/** A page is a list of nodes. */\r\nexport type PageContent = ComponentNode[];\r\n\r\nexport function isComponentNode(value: unknown): value is ComponentNode {\r\n return (\r\n typeof value === 'object' &&\r\n value !== null &&\r\n !Array.isArray(value) &&\r\n typeof (value as { type?: unknown }).type === 'string' &&\r\n (value as { type: string }).type.trim().length > 0\r\n );\r\n}\r\n","import type { ComponentNode } from './node';\n\n/**\n * Returns a copy of `nodes` where the node with the given `id` has `props` merged in.\n * Use it to inject runtime state (e.g. the selected language) into fetched JSON without mutating it.\n */\nexport function patchNodeById(nodes: ComponentNode[], id: string, props: Record<string, unknown>): ComponentNode[] {\n return nodes.map(node => {\n const patched = node.id === id ? { ...node, ...props } : node;\n return patched.children\n ? { ...patched, children: patchNodeById(patched.children, id, props) }\n : patched;\n });\n}\n","/**\n * The JSON `type` of every base component, and the Lit element that renders it.\n * This is the contract page JSON is written against — renderers (React, or any other binding)\n * map each `type` to their own component for that element.\n */\nexport const componentTags = {\n BentoGrid: 'st-bento-grid',\n Card: 'st-card',\n Carousel: 'st-carousel',\n ContentBlock: 'st-content-block',\n CookieConsent: 'st-cookie-consent',\n Dropdown: 'st-dropdown',\n FeatureItem: 'st-feature-item',\n FeatureSplit: 'st-feature-split',\n FeaturesSection: 'st-features-section',\n Footer: 'st-footer',\n Header: 'st-header',\n Hero: 'st-hero',\n IconButton: 'st-icon-button',\n ItemsAccordion: 'st-items-accordion',\n MenuGrid: 'st-menu-grid',\n MenuGridItem: 'st-menu-grid-item',\n MenuItem: 'st-menu-item',\n PromoBanner: 'st-promo-banner',\n Select: 'st-select',\n Stats: 'st-stats',\n Testimonials: 'st-testimonials'\n} as const;\n\n/** Every `type` the base set knows. */\nexport type ComponentType = keyof typeof componentTags;\n\nexport function isBaseComponentType(type: string): type is ComponentType {\n return Object.hasOwn(componentTags, type);\n}\n","import { isBaseComponentType } from './components';\nimport type { ComponentNode } from './node';\n\n/** Keys from the pre-flat schema; their presence means the JSON still needs `scripts/migrate-json.mjs`. */\nconst LEGACY_KEYS = ['data', 'embeddedView', 'data-position', 'image', 'contentUrl'] as const;\n\n/**\n * Returns the schema violations for a node and its descendants (empty when valid).\n * `isKnownType` defaults to the base component set; pass a wider predicate when a renderer also\n * registers custom components.\n */\nexport function validateNode(node: unknown, where = 'node', isKnownType: (type: string) => boolean = isBaseComponentType): string[] {\n if (typeof node !== 'object' || node === null || Array.isArray(node)) return [`${where}: node must be an object`];\n const n = node as ComponentNode;\n const found: string[] = [];\n if (typeof n.type !== 'string' || !isKnownType(n.type)) found.push(`${where}: unknown type \"${String(n.type)}\"`);\n for (const legacy of LEGACY_KEYS) {\n if (legacy in n) found.push(`${where}: legacy key \"${legacy}\"`);\n }\n if (Array.isArray(n.items) && n.items.some(item => typeof item === 'object' && item !== null && 'type' in item)) {\n found.push(`${where}: \"items\" must hold plain data; nest components under \"children\"`);\n }\n if (n.children !== undefined) {\n if (!Array.isArray(n.children)) found.push(`${where}: children must be an array`);\n else n.children.forEach((child, i) => found.push(...validateNode(child, `${where}.children[${i}]`, isKnownType)));\n }\n return found;\n}\n\n/** Validates a page: a node or a list of nodes. */\nexport function validatePage(page: unknown, isKnownType?: (type: string) => boolean): string[] {\n const nodes = Array.isArray(page) ? page : [page];\n return nodes.flatMap((node, i) => validateNode(node, `[${i}]`, isKnownType));\n}\n"],"mappings":";;;AA2BO,SAAS,gBAAgB,OAAwC;AACpE,SACI,OAAO,UAAU,YACjB,UAAU,QACV,CAAC,MAAM,QAAQ,KAAK,KACpB,OAAQ,MAA6B,SAAS,YAC7C,MAA2B,KAAK,KAAK,EAAE,SAAS;AAEzD;;;AC7BO,SAAS,cAAc,OAAwB,IAAY,OAAiD;AAC/G,SAAO,MAAM,IAAI,UAAQ;AACrB,UAAM,UAAU,KAAK,OAAO,KAAK,EAAE,GAAG,MAAM,GAAG,MAAM,IAAI;AACzD,WAAO,QAAQ,WACT,EAAE,GAAG,SAAS,UAAU,cAAc,QAAQ,UAAU,IAAI,KAAK,EAAE,IACnE;AAAA,EACV,CAAC;AACL;;;ACRO,IAAM,gBAAgB;AAAA,EACzB,WAAW;AAAA,EACX,MAAM;AAAA,EACN,UAAU;AAAA,EACV,cAAc;AAAA,EACd,eAAe;AAAA,EACf,UAAU;AAAA,EACV,aAAa;AAAA,EACb,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,cAAc;AAAA,EACd,UAAU;AAAA,EACV,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,cAAc;AAClB;AAKO,SAAS,oBAAoB,MAAqC;AACrE,SAAO,OAAO,OAAO,eAAe,IAAI;AAC5C;;;AC9BA,IAAM,cAAc,CAAC,QAAQ,gBAAgB,iBAAiB,SAAS,YAAY;AAO5E,SAAS,aAAa,MAAe,QAAQ,QAAQ,cAAyC,qBAA+B;AAChI,MAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM,QAAQ,IAAI,EAAG,QAAO,CAAC,GAAG,KAAK,0BAA0B;AAChH,QAAM,IAAI;AACV,QAAM,QAAkB,CAAC;AACzB,MAAI,OAAO,EAAE,SAAS,YAAY,CAAC,YAAY,EAAE,IAAI,EAAG,OAAM,KAAK,GAAG,KAAK,mBAAmB,OAAO,EAAE,IAAI,CAAC,GAAG;AAC/G,aAAW,UAAU,aAAa;AAC9B,QAAI,UAAU,EAAG,OAAM,KAAK,GAAG,KAAK,iBAAiB,MAAM,GAAG;AAAA,EAClE;AACA,MAAI,MAAM,QAAQ,EAAE,KAAK,KAAK,EAAE,MAAM,KAAK,UAAQ,OAAO,SAAS,YAAY,SAAS,QAAQ,UAAU,IAAI,GAAG;AAC7G,UAAM,KAAK,GAAG,KAAK,kEAAkE;AAAA,EACzF;AACA,MAAI,EAAE,aAAa,QAAW;AAC1B,QAAI,CAAC,MAAM,QAAQ,EAAE,QAAQ,EAAG,OAAM,KAAK,GAAG,KAAK,6BAA6B;AAAA,QAC3E,GAAE,SAAS,QAAQ,CAAC,OAAO,MAAM,MAAM,KAAK,GAAG,aAAa,OAAO,GAAG,KAAK,aAAa,CAAC,KAAK,WAAW,CAAC,CAAC;AAAA,EACpH;AACA,SAAO;AACX;AAGO,SAAS,aAAa,MAAe,aAAmD;AAC3F,QAAM,QAAQ,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI;AAChD,SAAO,MAAM,QAAQ,CAAC,MAAM,MAAM,aAAa,MAAM,IAAI,CAAC,KAAK,WAAW,CAAC;AAC/E;","names":[]}
1
+ {"version":3,"sources":["../../src/schema/node.ts","../../src/schema/nodeUtils.ts","../../src/schema/components.ts","../../src/schema/validate.ts"],"sourcesContent":["/** Dependency-free node types, safe to import from the content API layer. */\r\n\r\n/** Action reference in JSON, resolved against the `handlers` prop of `DynamicRenderer`. */\r\nexport type ActionRef = `@action:${string}`;\r\n\r\n/** Keys the renderer owns; everything else on a node is passed to the component as a prop. */\r\nexport interface NodeEnvelope<TNode> {\r\n id?: string;\r\n /** Named slot in the parent (e.g. Header: \"utility\"). Omit for the default slot. */\r\n slot?: string;\r\n /** Editor-only label, never passed to the component. */\r\n label?: string;\r\n children?: TNode[];\r\n}\r\n\r\n/**\r\n * A node as it appears in JSON: flat, no `data` wrapper.\r\n * `{ \"type\": \"Hero\", \"id\": \"home-hero\", \"title\": \"...\", \"children\": [...] }`\r\n */\r\nexport interface ComponentNode extends NodeEnvelope<ComponentNode> {\r\n type: string;\r\n [prop: string]: unknown;\r\n}\r\n\r\n/** A page is a list of nodes. */\r\nexport type PageContent = ComponentNode[];\r\n\r\nexport function isComponentNode(value: unknown): value is ComponentNode {\r\n return (\r\n typeof value === 'object' &&\r\n value !== null &&\r\n !Array.isArray(value) &&\r\n typeof (value as { type?: unknown }).type === 'string' &&\r\n (value as { type: string }).type.trim().length > 0\r\n );\r\n}\r\n","import type { ComponentNode } from './node';\r\n\r\n/**\r\n * Returns a copy of `nodes` where the node with the given `id` has `props` merged in.\r\n * Use it to inject runtime state (e.g. the selected language) into fetched JSON without mutating it.\r\n */\r\nexport function patchNodeById(nodes: ComponentNode[], id: string, props: Record<string, unknown>): ComponentNode[] {\r\n return nodes.map(node => {\r\n const patched = node.id === id ? { ...node, ...props } : node;\r\n return patched.children\r\n ? { ...patched, children: patchNodeById(patched.children, id, props) }\r\n : patched;\r\n });\r\n}\r\n","/**\r\n * The JSON `type` of every base component, and the Lit element that renders it.\r\n * This is the contract page JSON is written against — renderers (React, or any other binding)\r\n * map each `type` to their own component for that element.\r\n */\r\nexport const componentTags = {\r\n BentoGrid: 'st-bento-grid',\r\n Card: 'st-card',\r\n Carousel: 'st-carousel',\r\n ContentBlock: 'st-content-block',\r\n CookieConsent: 'st-cookie-consent',\r\n Dropdown: 'st-dropdown',\r\n FeatureItem: 'st-feature-item',\r\n FeatureSplit: 'st-feature-split',\r\n FeaturesSection: 'st-features-section',\r\n Footer: 'st-footer',\r\n Header: 'st-header',\r\n Hero: 'st-hero',\r\n IconButton: 'st-icon-button',\r\n ItemsAccordion: 'st-items-accordion',\r\n MenuGrid: 'st-menu-grid',\r\n MenuGridItem: 'st-menu-grid-item',\r\n MenuItem: 'st-menu-item',\r\n PromoBanner: 'st-promo-banner',\r\n Select: 'st-select',\r\n Stats: 'st-stats',\r\n Testimonials: 'st-testimonials'\r\n} as const;\r\n\r\n/** Every `type` the base set knows. */\r\nexport type ComponentType = keyof typeof componentTags;\r\n\r\nexport function isBaseComponentType(type: string): type is ComponentType {\r\n return Object.hasOwn(componentTags, type);\r\n}\r\n","import { isBaseComponentType } from './components';\r\nimport type { ComponentNode } from './node';\r\n\r\n/** Keys from the pre-flat schema; their presence means the JSON still needs `scripts/migrate-json.mjs`. */\r\nconst LEGACY_KEYS = ['data', 'embeddedView', 'data-position', 'image', 'contentUrl'] as const;\r\n\r\n/**\r\n * Returns the schema violations for a node and its descendants (empty when valid).\r\n * `isKnownType` defaults to the base component set; pass a wider predicate when a renderer also\r\n * registers custom components.\r\n */\r\nexport function validateNode(node: unknown, where = 'node', isKnownType: (type: string) => boolean = isBaseComponentType): string[] {\r\n if (typeof node !== 'object' || node === null || Array.isArray(node)) return [`${where}: node must be an object`];\r\n const n = node as ComponentNode;\r\n const found: string[] = [];\r\n if (typeof n.type !== 'string' || !isKnownType(n.type)) found.push(`${where}: unknown type \"${String(n.type)}\"`);\r\n for (const legacy of LEGACY_KEYS) {\r\n if (legacy in n) found.push(`${where}: legacy key \"${legacy}\"`);\r\n }\r\n if (Array.isArray(n.items) && n.items.some(item => typeof item === 'object' && item !== null && 'type' in item)) {\r\n found.push(`${where}: \"items\" must hold plain data; nest components under \"children\"`);\r\n }\r\n if (n.children !== undefined) {\r\n if (!Array.isArray(n.children)) found.push(`${where}: children must be an array`);\r\n else n.children.forEach((child, i) => found.push(...validateNode(child, `${where}.children[${i}]`, isKnownType)));\r\n }\r\n return found;\r\n}\r\n\r\n/** Validates a page: a node or a list of nodes. */\r\nexport function validatePage(page: unknown, isKnownType?: (type: string) => boolean): string[] {\r\n const nodes = Array.isArray(page) ? page : [page];\r\n return nodes.flatMap((node, i) => validateNode(node, `[${i}]`, isKnownType));\r\n}\r\n"],"mappings":";;;AA2BO,SAAS,gBAAgB,OAAwC;AACpE,SACI,OAAO,UAAU,YACjB,UAAU,QACV,CAAC,MAAM,QAAQ,KAAK,KACpB,OAAQ,MAA6B,SAAS,YAC7C,MAA2B,KAAK,KAAK,EAAE,SAAS;AAEzD;;;AC7BO,SAAS,cAAc,OAAwB,IAAY,OAAiD;AAC/G,SAAO,MAAM,IAAI,UAAQ;AACrB,UAAM,UAAU,KAAK,OAAO,KAAK,EAAE,GAAG,MAAM,GAAG,MAAM,IAAI;AACzD,WAAO,QAAQ,WACT,EAAE,GAAG,SAAS,UAAU,cAAc,QAAQ,UAAU,IAAI,KAAK,EAAE,IACnE;AAAA,EACV,CAAC;AACL;;;ACRO,IAAM,gBAAgB;AAAA,EACzB,WAAW;AAAA,EACX,MAAM;AAAA,EACN,UAAU;AAAA,EACV,cAAc;AAAA,EACd,eAAe;AAAA,EACf,UAAU;AAAA,EACV,aAAa;AAAA,EACb,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,cAAc;AAAA,EACd,UAAU;AAAA,EACV,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,cAAc;AAClB;AAKO,SAAS,oBAAoB,MAAqC;AACrE,SAAO,OAAO,OAAO,eAAe,IAAI;AAC5C;;;AC9BA,IAAM,cAAc,CAAC,QAAQ,gBAAgB,iBAAiB,SAAS,YAAY;AAO5E,SAAS,aAAa,MAAe,QAAQ,QAAQ,cAAyC,qBAA+B;AAChI,MAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM,QAAQ,IAAI,EAAG,QAAO,CAAC,GAAG,KAAK,0BAA0B;AAChH,QAAM,IAAI;AACV,QAAM,QAAkB,CAAC;AACzB,MAAI,OAAO,EAAE,SAAS,YAAY,CAAC,YAAY,EAAE,IAAI,EAAG,OAAM,KAAK,GAAG,KAAK,mBAAmB,OAAO,EAAE,IAAI,CAAC,GAAG;AAC/G,aAAW,UAAU,aAAa;AAC9B,QAAI,UAAU,EAAG,OAAM,KAAK,GAAG,KAAK,iBAAiB,MAAM,GAAG;AAAA,EAClE;AACA,MAAI,MAAM,QAAQ,EAAE,KAAK,KAAK,EAAE,MAAM,KAAK,UAAQ,OAAO,SAAS,YAAY,SAAS,QAAQ,UAAU,IAAI,GAAG;AAC7G,UAAM,KAAK,GAAG,KAAK,kEAAkE;AAAA,EACzF;AACA,MAAI,EAAE,aAAa,QAAW;AAC1B,QAAI,CAAC,MAAM,QAAQ,EAAE,QAAQ,EAAG,OAAM,KAAK,GAAG,KAAK,6BAA6B;AAAA,QAC3E,GAAE,SAAS,QAAQ,CAAC,OAAO,MAAM,MAAM,KAAK,GAAG,aAAa,OAAO,GAAG,KAAK,aAAa,CAAC,KAAK,WAAW,CAAC,CAAC;AAAA,EACpH;AACA,SAAO;AACX;AAGO,SAAS,aAAa,MAAe,aAAmD;AAC3F,QAAM,QAAQ,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI;AAChD,SAAO,MAAM,QAAQ,CAAC,MAAM,MAAM,aAAa,MAAM,IAAI,CAAC,KAAK,WAAW,CAAC;AAC/E;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/theming/index.ts"],"sourcesContent":["/**\n * Theme runtime for `themes.css`: the list of themes, and applying one to `<html>`.\n * Framework-free and storage-free — where the chosen theme is remembered is up to the host app.\n */\n\nexport interface ThemeOption {\n value: string;\n label: string;\n /** Approximate color for a picker swatch, not the palette itself (that's `themes.css`). */\n swatch: string;\n dark: boolean;\n}\n\nexport const DEFAULT_THEME = 'default';\n\n/** `'default'` applies no class (the base palette); every other value is a class in `themes.css`. */\nexport const THEMES: readonly ThemeOption[] = [\n { value: 'default', label: 'Default', swatch: '#94a3b8', dark: false },\n { value: 'dark', label: 'Dark', swatch: '#1f2937', dark: true },\n { value: 'red', label: 'Red', swatch: '#ef4444', dark: false },\n { value: 'amber', label: 'Amber', swatch: '#f59e0b', dark: false },\n { value: 'lime', label: 'Lime', swatch: '#84cc16', dark: false },\n { value: 'teal', label: 'Teal', swatch: '#14b8a6', dark: false },\n { value: 'cyan', label: 'Cyan', swatch: '#06b6d4', dark: false },\n { value: 'sky', label: 'Sky', swatch: '#0ea5e9', dark: false },\n { value: 'blue', label: 'Blue', swatch: '#3b82f6', dark: false },\n { value: 'indigo', label: 'Indigo', swatch: '#6366f1', dark: false },\n { value: 'fuchsia', label: 'Fuchsia', swatch: '#d946ef', dark: false },\n { value: 'pink', label: 'Pink', swatch: '#ec4899', dark: false }\n];\n\nexport function isTheme(value: unknown): value is string {\n return typeof value === 'string' && THEMES.some(theme => theme.value === value);\n}\n\nconst THEME_CLASSES = THEMES.filter(theme => theme.value !== DEFAULT_THEME).map(theme => theme.value);\nconst DARK_QUERY = '(prefers-color-scheme: dark)';\n\n/** The theme to use when nobody has chosen one: `dark` if the OS prefers it, else `default`. */\nexport function systemTheme(): string {\n if (typeof window === 'undefined' || !window.matchMedia) return DEFAULT_THEME;\n return window.matchMedia(DARK_QUERY).matches ? 'dark' : DEFAULT_THEME;\n}\n\n/** Calls `onChange` when the OS light/dark setting flips; returns an unsubscribe function. */\nexport function watchSystemTheme(onChange: (theme: string) => void): () => void {\n if (typeof window === 'undefined' || !window.matchMedia) return () => {};\n const query = window.matchMedia(DARK_QUERY);\n const listener = () => onChange(systemTheme());\n query.addEventListener('change', listener);\n return () => query.removeEventListener('change', listener);\n}\n\n/**\n * Applies a theme to `<html>`: swaps only the theme classes (other classes on the element are left\n * alone) and sets `color-scheme` so native controls and scrollbars match. `null`/`undefined` means\n * \"nobody chose\", which follows the OS; an unknown value falls back to `default`. Returns the\n * theme actually applied.\n */\nexport function applyTheme(theme: string | null | undefined): string {\n const resolved = theme === null || theme === undefined ? systemTheme() : isTheme(theme) ? theme : DEFAULT_THEME;\n if (typeof document === 'undefined') return resolved;\n const root = document.documentElement;\n root.classList.remove(...THEME_CLASSES);\n if (resolved !== DEFAULT_THEME) root.classList.add(resolved);\n root.style.colorScheme = THEMES.find(option => option.value === resolved)?.dark ? 'dark' : 'light';\n return resolved;\n}\n"],"mappings":";;;AAaO,IAAM,gBAAgB;AAGtB,IAAM,SAAiC;AAAA,EAC5C,EAAE,OAAO,WAAW,OAAO,WAAW,QAAQ,WAAW,MAAM,MAAM;AAAA,EACrE,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,WAAW,MAAM,KAAK;AAAA,EAC9D,EAAE,OAAO,OAAO,OAAO,OAAO,QAAQ,WAAW,MAAM,MAAM;AAAA,EAC7D,EAAE,OAAO,SAAS,OAAO,SAAS,QAAQ,WAAW,MAAM,MAAM;AAAA,EACjE,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,WAAW,MAAM,MAAM;AAAA,EAC/D,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,WAAW,MAAM,MAAM;AAAA,EAC/D,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,WAAW,MAAM,MAAM;AAAA,EAC/D,EAAE,OAAO,OAAO,OAAO,OAAO,QAAQ,WAAW,MAAM,MAAM;AAAA,EAC7D,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,WAAW,MAAM,MAAM;AAAA,EAC/D,EAAE,OAAO,UAAU,OAAO,UAAU,QAAQ,WAAW,MAAM,MAAM;AAAA,EACnE,EAAE,OAAO,WAAW,OAAO,WAAW,QAAQ,WAAW,MAAM,MAAM;AAAA,EACrE,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,WAAW,MAAM,MAAM;AACjE;AAEO,SAAS,QAAQ,OAAiC;AACvD,SAAO,OAAO,UAAU,YAAY,OAAO,KAAK,WAAS,MAAM,UAAU,KAAK;AAChF;AAEA,IAAM,gBAAgB,OAAO,OAAO,WAAS,MAAM,UAAU,aAAa,EAAE,IAAI,WAAS,MAAM,KAAK;AACpG,IAAM,aAAa;AAGZ,SAAS,cAAsB;AACpC,MAAI,OAAO,WAAW,eAAe,CAAC,OAAO,WAAY,QAAO;AAChE,SAAO,OAAO,WAAW,UAAU,EAAE,UAAU,SAAS;AAC1D;AAGO,SAAS,iBAAiB,UAA+C;AAC9E,MAAI,OAAO,WAAW,eAAe,CAAC,OAAO,WAAY,QAAO,MAAM;AAAA,EAAC;AACvE,QAAM,QAAQ,OAAO,WAAW,UAAU;AAC1C,QAAM,WAAW,MAAM,SAAS,YAAY,CAAC;AAC7C,QAAM,iBAAiB,UAAU,QAAQ;AACzC,SAAO,MAAM,MAAM,oBAAoB,UAAU,QAAQ;AAC3D;AAQO,SAAS,WAAW,OAA0C;AACnE,QAAM,WAAW,UAAU,QAAQ,UAAU,SAAY,YAAY,IAAI,QAAQ,KAAK,IAAI,QAAQ;AAClG,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,OAAO,SAAS;AACtB,OAAK,UAAU,OAAO,GAAG,aAAa;AACtC,MAAI,aAAa,cAAe,MAAK,UAAU,IAAI,QAAQ;AAC3D,OAAK,MAAM,cAAc,OAAO,KAAK,YAAU,OAAO,UAAU,QAAQ,GAAG,OAAO,SAAS;AAC3F,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../src/theming/index.ts"],"sourcesContent":["/**\r\n * Theme runtime for `themes.css`: the list of themes, and applying one to `<html>`.\r\n * Framework-free and storage-free — where the chosen theme is remembered is up to the host app.\r\n */\r\n\r\nexport interface ThemeOption {\r\n value: string;\r\n label: string;\r\n /** Approximate color for a picker swatch, not the palette itself (that's `themes.css`). */\r\n swatch: string;\r\n dark: boolean;\r\n}\r\n\r\nexport const DEFAULT_THEME = 'default';\r\n\r\n/** `'default'` applies no class (the base palette); every other value is a class in `themes.css`. */\r\nexport const THEMES: readonly ThemeOption[] = [\r\n { value: 'default', label: 'Default', swatch: '#94a3b8', dark: false },\r\n { value: 'dark', label: 'Dark', swatch: '#1f2937', dark: true },\r\n { value: 'red', label: 'Red', swatch: '#ef4444', dark: false },\r\n { value: 'amber', label: 'Amber', swatch: '#f59e0b', dark: false },\r\n { value: 'lime', label: 'Lime', swatch: '#84cc16', dark: false },\r\n { value: 'teal', label: 'Teal', swatch: '#14b8a6', dark: false },\r\n { value: 'cyan', label: 'Cyan', swatch: '#06b6d4', dark: false },\r\n { value: 'sky', label: 'Sky', swatch: '#0ea5e9', dark: false },\r\n { value: 'blue', label: 'Blue', swatch: '#3b82f6', dark: false },\r\n { value: 'indigo', label: 'Indigo', swatch: '#6366f1', dark: false },\r\n { value: 'fuchsia', label: 'Fuchsia', swatch: '#d946ef', dark: false },\r\n { value: 'pink', label: 'Pink', swatch: '#ec4899', dark: false }\r\n];\r\n\r\nexport function isTheme(value: unknown): value is string {\r\n return typeof value === 'string' && THEMES.some(theme => theme.value === value);\r\n}\r\n\r\nconst THEME_CLASSES = THEMES.filter(theme => theme.value !== DEFAULT_THEME).map(theme => theme.value);\r\nconst DARK_QUERY = '(prefers-color-scheme: dark)';\r\n\r\n/** The theme to use when nobody has chosen one: `dark` if the OS prefers it, else `default`. */\r\nexport function systemTheme(): string {\r\n if (typeof window === 'undefined' || !window.matchMedia) return DEFAULT_THEME;\r\n return window.matchMedia(DARK_QUERY).matches ? 'dark' : DEFAULT_THEME;\r\n}\r\n\r\n/** Calls `onChange` when the OS light/dark setting flips; returns an unsubscribe function. */\r\nexport function watchSystemTheme(onChange: (theme: string) => void): () => void {\r\n if (typeof window === 'undefined' || !window.matchMedia) return () => {};\r\n const query = window.matchMedia(DARK_QUERY);\r\n const listener = () => onChange(systemTheme());\r\n query.addEventListener('change', listener);\r\n return () => query.removeEventListener('change', listener);\r\n}\r\n\r\n/**\r\n * Applies a theme to `<html>`: swaps only the theme classes (other classes on the element are left\r\n * alone) and sets `color-scheme` so native controls and scrollbars match. `null`/`undefined` means\r\n * \"nobody chose\", which follows the OS; an unknown value falls back to `default`. Returns the\r\n * theme actually applied.\r\n */\r\nexport function applyTheme(theme: string | null | undefined): string {\r\n const resolved = theme === null || theme === undefined ? systemTheme() : isTheme(theme) ? theme : DEFAULT_THEME;\r\n if (typeof document === 'undefined') return resolved;\r\n const root = document.documentElement;\r\n root.classList.remove(...THEME_CLASSES);\r\n if (resolved !== DEFAULT_THEME) root.classList.add(resolved);\r\n root.style.colorScheme = THEMES.find(option => option.value === resolved)?.dark ? 'dark' : 'light';\r\n return resolved;\r\n}\r\n"],"mappings":";;;AAaO,IAAM,gBAAgB;AAGtB,IAAM,SAAiC;AAAA,EAC5C,EAAE,OAAO,WAAW,OAAO,WAAW,QAAQ,WAAW,MAAM,MAAM;AAAA,EACrE,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,WAAW,MAAM,KAAK;AAAA,EAC9D,EAAE,OAAO,OAAO,OAAO,OAAO,QAAQ,WAAW,MAAM,MAAM;AAAA,EAC7D,EAAE,OAAO,SAAS,OAAO,SAAS,QAAQ,WAAW,MAAM,MAAM;AAAA,EACjE,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,WAAW,MAAM,MAAM;AAAA,EAC/D,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,WAAW,MAAM,MAAM;AAAA,EAC/D,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,WAAW,MAAM,MAAM;AAAA,EAC/D,EAAE,OAAO,OAAO,OAAO,OAAO,QAAQ,WAAW,MAAM,MAAM;AAAA,EAC7D,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,WAAW,MAAM,MAAM;AAAA,EAC/D,EAAE,OAAO,UAAU,OAAO,UAAU,QAAQ,WAAW,MAAM,MAAM;AAAA,EACnE,EAAE,OAAO,WAAW,OAAO,WAAW,QAAQ,WAAW,MAAM,MAAM;AAAA,EACrE,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,WAAW,MAAM,MAAM;AACjE;AAEO,SAAS,QAAQ,OAAiC;AACvD,SAAO,OAAO,UAAU,YAAY,OAAO,KAAK,WAAS,MAAM,UAAU,KAAK;AAChF;AAEA,IAAM,gBAAgB,OAAO,OAAO,WAAS,MAAM,UAAU,aAAa,EAAE,IAAI,WAAS,MAAM,KAAK;AACpG,IAAM,aAAa;AAGZ,SAAS,cAAsB;AACpC,MAAI,OAAO,WAAW,eAAe,CAAC,OAAO,WAAY,QAAO;AAChE,SAAO,OAAO,WAAW,UAAU,EAAE,UAAU,SAAS;AAC1D;AAGO,SAAS,iBAAiB,UAA+C;AAC9E,MAAI,OAAO,WAAW,eAAe,CAAC,OAAO,WAAY,QAAO,MAAM;AAAA,EAAC;AACvE,QAAM,QAAQ,OAAO,WAAW,UAAU;AAC1C,QAAM,WAAW,MAAM,SAAS,YAAY,CAAC;AAC7C,QAAM,iBAAiB,UAAU,QAAQ;AACzC,SAAO,MAAM,MAAM,oBAAoB,UAAU,QAAQ;AAC3D;AAQO,SAAS,WAAW,OAA0C;AACnE,QAAM,WAAW,UAAU,QAAQ,UAAU,SAAY,YAAY,IAAI,QAAQ,KAAK,IAAI,QAAQ;AAClG,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,OAAO,SAAS;AACtB,OAAK,UAAU,OAAO,GAAG,aAAa;AACtC,MAAI,aAAa,cAAe,MAAK,UAAU,IAAI,QAAQ;AAC3D,OAAK,MAAM,cAAc,OAAO,KAAK,YAAU,OAAO,UAAU,QAAQ,GAAG,OAAO,SAAS;AAC3F,SAAO;AACT;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sankhyatronics/sankhya-cms",
3
- "version": "0.0.9",
3
+ "version": "0.1.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sankhyatronics/SankhyaCMS.git",
@@ -126,22 +126,32 @@
126
126
  "custom-elements.json"
127
127
  ],
128
128
  "sideEffects": true,
129
+ "scripts": {
130
+ "build": "tsup src/index.ts src/schema/index.ts src/theming/index.ts src/shared/styles.ts src/shared/icons.ts src/bento-grid/index.ts src/card/index.ts src/carousel/index.ts src/content-block/index.ts src/cookie-consent/index.ts src/dropdown/index.ts src/feature-split/index.ts src/features-section/index.ts src/footer/index.ts src/header/index.ts src/hero/index.ts src/icon-button/index.ts src/items-accordion/index.ts src/markdown/index.ts src/menu-grid/index.ts src/menu-grid-item/index.ts src/menu-item/index.ts src/promo-banner/index.ts src/select/index.ts src/stats/index.ts src/testimonials/index.ts --format esm --dts --sourcemap --clean && node scripts/copy-theme.mjs && cem analyze",
131
+ "dev": "tsup src/index.ts --watch --format esm --dts",
132
+ "typecheck": "tsc --noEmit",
133
+ "lint": "oxlint",
134
+ "clean": "rimraf dist",
135
+ "test": "vitest run",
136
+ "migrate-json": "node scripts/migrate-json.mjs",
137
+ "analyze": "cem analyze"
138
+ },
129
139
  "dependencies": {
130
140
  "dompurify": "^3.4.16",
131
141
  "iconify-icon": "^3.0.3",
132
- "lit": "3.3.3",
142
+ "lit": "^3.3.3",
133
143
  "marked": "^18.1.0"
134
144
  },
135
145
  "devDependencies": {
136
- "@sankhyatronics/oxlint-config": "0.0.9",
137
- "@sankhyatronics/typescript-config": "0.0.9",
138
- "oxlint": "1.87.0",
139
- "rimraf": "6.1.3",
140
- "tsup": "8.5.1",
141
- "typescript": "6.0.3",
142
- "vitest": "5.0.3",
143
- "@types/node": "26.6.4",
144
- "@custom-elements-manifest/analyzer": "0.11.0"
146
+ "@sankhyatronics/oxlint-config": "workspace:*",
147
+ "@sankhyatronics/typescript-config": "workspace:*",
148
+ "oxlint": "catalog:",
149
+ "rimraf": "catalog:",
150
+ "tsup": "catalog:",
151
+ "typescript": "catalog:",
152
+ "vitest": "catalog:",
153
+ "@types/node": "catalog:",
154
+ "@custom-elements-manifest/analyzer": "catalog:"
145
155
  },
146
156
  "keywords": [
147
157
  "web-components",
@@ -150,14 +160,7 @@
150
160
  ],
151
161
  "license": "MIT",
152
162
  "customElements": "custom-elements.json",
153
- "scripts": {
154
- "build": "tsup src/index.ts src/schema/index.ts src/theming/index.ts src/shared/styles.ts src/shared/icons.ts src/bento-grid/index.ts src/card/index.ts src/carousel/index.ts src/content-block/index.ts src/cookie-consent/index.ts src/dropdown/index.ts src/feature-split/index.ts src/features-section/index.ts src/footer/index.ts src/header/index.ts src/hero/index.ts src/icon-button/index.ts src/items-accordion/index.ts src/markdown/index.ts src/menu-grid/index.ts src/menu-grid-item/index.ts src/menu-item/index.ts src/promo-banner/index.ts src/select/index.ts src/stats/index.ts src/testimonials/index.ts --format esm --dts --sourcemap --clean && node scripts/copy-theme.mjs && cem analyze",
155
- "dev": "tsup src/index.ts --watch --format esm --dts",
156
- "typecheck": "tsc --noEmit",
157
- "lint": "oxlint",
158
- "clean": "rimraf dist",
159
- "test": "vitest run",
160
- "migrate-json": "node scripts/migrate-json.mjs",
161
- "analyze": "cem analyze"
163
+ "publishConfig": {
164
+ "access": "public"
162
165
  }
163
- }
166
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Sankhyatronics
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hero/Hero.ts"],"sourcesContent":["import { LitElement, css, html, nothing } from 'lit';\r\nimport { customElement, property } from 'lit/decorators.js';\r\n\r\nimport { controlBaseStyles, rootFontFamilyStyles } from '../shared/styles';\r\nimport { ctaLinkStyles, paddingScaleStyles } from '../shared/section-styles';\r\nimport type { HeroActionEventDetail } from './HeroEvents';\r\n\r\n/**\r\n * Full-width hero banner: background image with a darkening overlay, title, optional subtitle and\r\n * a call-to-action link. Port of `SankhyaUI`'s React `Hero`; the framer-motion entrance is redone\r\n * as CSS keyframes (skipped under `prefers-reduced-motion`). The CTA fires a cancelable `action`\r\n * event (detail `{ href }`) before navigating.\r\n */\r\n@customElement('st-hero')\r\nexport class Hero extends LitElement {\r\n static styles = css`\r\n ${controlBaseStyles}\r\n ${paddingScaleStyles}\r\n\r\n @keyframes st-hero-zoom {\r\n from { opacity: 0; transform: scale(1.1); }\r\n to { opacity: 1; transform: scale(1); }\r\n }\r\n @keyframes st-hero-rise {\r\n from { opacity: 0; transform: translateY(30px); }\r\n to { opacity: 1; transform: none; }\r\n }\r\n @keyframes st-hero-fade {\r\n from { opacity: 0; }\r\n to { opacity: 1; }\r\n }\r\n\r\n :host {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n position: relative;\r\n overflow: hidden;\r\n width: 100%;\r\n box-sizing: border-box;\r\n background-color: var(--st-color-text-strong);\r\n background-repeat: no-repeat;\r\n background-size: cover;\r\n background-position: var(--st-hero-position, center center);\r\n color: var(--st-color-on-solid);\r\n ${rootFontFamilyStyles}\r\n animation: st-hero-zoom 1.5s ease-out;\r\n }\r\n\r\n :host([inverted]) {\r\n background-color: var(--st-color-surface);\r\n color: var(--st-color-text-strong);\r\n }\r\n\r\n .overlay {\r\n position: absolute;\r\n inset: 0;\r\n z-index: 1;\r\n background: linear-gradient(\r\n to bottom,\r\n color-mix(in oklch, var(--st-color-text-strong) 40%, transparent),\r\n color-mix(in oklch, var(--st-color-text-strong) 20%, transparent),\r\n color-mix(in oklch, var(--st-color-text-strong) 40%, transparent)\r\n );\r\n }\r\n\r\n :host([inverted]) .overlay {\r\n display: none;\r\n }\r\n\r\n .content {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--st-space-20);\r\n position: relative;\r\n z-index: 2;\r\n width: 80%;\r\n margin: 0 auto;\r\n text-align: center;\r\n align-items: center;\r\n }\r\n\r\n :host([text-alignment='left']) .content { text-align: left; align-items: flex-start; }\r\n :host([text-alignment='right']) .content { text-align: right; align-items: flex-end; }\r\n :host([text-alignment='justify']) .content { text-align: justify; align-items: stretch; }\r\n\r\n .title {\r\n font-size: var(--st-cms-text-display);\r\n font-weight: var(--st-font-weight-bold);\r\n line-height: 1.1;\r\n color: inherit;\r\n margin: 0;\r\n animation: st-hero-rise 0.8s 0.3s ease-out backwards;\r\n }\r\n\r\n .subtitle {\r\n font-size: var(--st-cms-text-xl);\r\n line-height: 1.5;\r\n color: inherit;\r\n margin: 0;\r\n animation: st-hero-fade 0.8s 0.5s ease-out backwards;\r\n }\r\n\r\n .actions {\r\n display: flex;\r\n justify-content: center;\r\n width: 100%;\r\n }\r\n\r\n .cta {\r\n ${ctaLinkStyles}\r\n background: var(--st-button-primary-bg);\r\n color: var(--st-button-primary-color);\r\n font-weight: var(--st-button-font-weight);\r\n font-size: var(--st-text-md);\r\n animation: st-hero-rise 0.6s 0.7s ease-out backwards;\r\n }\r\n\r\n .cta:hover {\r\n transform: scale(1.05);\r\n }\r\n\r\n .cta:active {\r\n transform: scale(0.98);\r\n }\r\n\r\n @media (min-width: 768px) {\r\n .content { padding: var(--st-space-32); }\r\n }\r\n @media (min-width: 1024px) {\r\n .content { padding: var(--st-space-48); }\r\n }\r\n\r\n @media (prefers-reduced-motion: reduce) {\r\n :host, .title, .subtitle, .cta { animation: none; }\r\n .cta { transition: none; }\r\n }\r\n `;\r\n\r\n @property({ type: String })\r\n title = '';\r\n\r\n @property({ type: String })\r\n subtitle = '';\r\n\r\n @property({ type: String, attribute: 'image-src' })\r\n imageSrc = '';\r\n\r\n @property({ type: String, attribute: 'action-label' })\r\n actionLabel = '';\r\n\r\n @property({ type: String })\r\n href = '';\r\n\r\n @property({ type: String, reflect: true, attribute: 'text-alignment' })\r\n textAlignment: 'left' | 'center' | 'right' | 'justify' = 'left';\r\n\r\n @property({ type: String, reflect: true })\r\n padding: 'none' | 'small' | 'medium' | 'large' = 'none';\r\n\r\n @property({ type: Boolean, reflect: true })\r\n inverted = false;\r\n\r\n @property({ type: String, attribute: 'focal-point' })\r\n focalPoint: 'top' | 'left' | 'right' | 'bottom' | 'center' = 'center';\r\n\r\n private static positions = {\r\n top: 'center top',\r\n left: 'left center',\r\n right: 'right center',\r\n bottom: 'center bottom',\r\n center: 'center center'\r\n } as const;\r\n\r\n protected willUpdate(): void {\r\n this.style.backgroundImage = this.imageSrc ? `url(${JSON.stringify(this.imageSrc)})` : '';\r\n this.style.setProperty('--st-hero-position', Hero.positions[this.focalPoint] ?? Hero.positions.center);\r\n }\r\n\r\n private onClick = (event: MouseEvent): void => {\r\n const action = new CustomEvent<HeroActionEventDetail>('action', {\r\n detail: { href: this.href },\r\n bubbles: true,\r\n composed: true,\r\n cancelable: true\r\n });\r\n this.dispatchEvent(action);\r\n if (action.defaultPrevented) event.preventDefault();\r\n };\r\n\r\n render() {\r\n return html`\r\n <div class=\"overlay\" part=\"overlay\"></div>\r\n <div class=\"content\" part=\"content\">\r\n <h1 class=\"title\" part=\"title\">${this.title}</h1>\r\n ${this.subtitle ? html`<p class=\"subtitle\" part=\"subtitle\">${this.subtitle}</p>` : nothing}\r\n ${this.actionLabel\r\n ? html`<div class=\"actions\" part=\"actions\">\r\n <a class=\"cta\" part=\"action\" href=${this.href || nothing} @click=${this.onClick}>${this.actionLabel}</a>\r\n </div>`\r\n : nothing}\r\n <slot></slot>\r\n </div>\r\n `;\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;;AAAA,SAAS,YAAY,KAAK,MAAM,eAAe;AAC/C,SAAS,eAAe,gBAAgB;AAajC,IAAM,OAAN,cAAmB,WAAW;AAAA,EAA9B;AAAA;AA8HL,iBAAQ;AAGR,oBAAW;AAGX,oBAAW;AAGX,uBAAc;AAGd,gBAAO;AAGP,yBAAyD;AAGzD,mBAAiD;AAGjD,oBAAW;AAGX,sBAA6D;AAe7D,SAAQ,UAAU,CAAC,UAA4B;AAC7C,YAAM,SAAS,IAAI,YAAmC,UAAU;AAAA,QAC9D,QAAQ,EAAE,MAAM,KAAK,KAAK;AAAA,QAC1B,SAAS;AAAA,QACT,UAAU;AAAA,QACV,YAAY;AAAA,MACd,CAAC;AACD,WAAK,cAAc,MAAM;AACzB,UAAI,OAAO,iBAAkB,OAAM,eAAe;AAAA,IACpD;AAAA;AAAA,EAdU,aAAmB;AAC3B,SAAK,MAAM,kBAAkB,KAAK,WAAW,OAAO,KAAK,UAAU,KAAK,QAAQ,CAAC,MAAM;AACvF,SAAK,MAAM,YAAY,sBAAsB,KAAK,UAAU,KAAK,UAAU,KAAK,KAAK,UAAU,MAAM;AAAA,EACvG;AAAA,EAaA,SAAS;AACP,WAAO;AAAA;AAAA;AAAA,yCAG8B,KAAK,KAAK;AAAA,UACzC,KAAK,WAAW,2CAA2C,KAAK,QAAQ,SAAS,OAAO;AAAA,UACxF,KAAK,cACH;AAAA,kDACsC,KAAK,QAAQ,OAAO,WAAW,KAAK,OAAO,IAAI,KAAK,WAAW;AAAA,sBAErG,OAAO;AAAA;AAAA;AAAA;AAAA,EAIjB;AACF;AA/La,KACJ,SAAS;AAAA,MACZ,iBAAiB;AAAA,MACjB,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QA4BhB,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAiEpB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAhGR,KAwJI,YAAY;AAAA,EACzB,KAAK;AAAA,EACL,MAAM;AAAA,EACN,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AACV;AAhCA;AAAA,EADC,SAAS,EAAE,MAAM,OAAO,CAAC;AAAA,GA7Hf,KA8HX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,OAAO,CAAC;AAAA,GAhIf,KAiIX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,QAAQ,WAAW,YAAY,CAAC;AAAA,GAnIvC,KAoIX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,QAAQ,WAAW,eAAe,CAAC;AAAA,GAtI1C,KAuIX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,OAAO,CAAC;AAAA,GAzIf,KA0IX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,QAAQ,SAAS,MAAM,WAAW,iBAAiB,CAAC;AAAA,GA5I3D,KA6IX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,QAAQ,SAAS,KAAK,CAAC;AAAA,GA/I9B,KAgJX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,SAAS,SAAS,KAAK,CAAC;AAAA,GAlJ/B,KAmJX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,QAAQ,WAAW,cAAc,CAAC;AAAA,GArJzC,KAsJX;AAtJW,OAAN;AAAA,EADN,cAAc,SAAS;AAAA,GACX;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/footer/Footer.ts"],"sourcesContent":["import { LitElement, PropertyValues, css, html, nothing } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport 'iconify-icon';\n\nimport { controlBaseStyles, rootFontFamilyStyles } from '../shared/styles';\n\nexport interface FooterLink {\n label: string;\n href: string;\n}\n\nexport interface FooterColumn {\n title: string;\n links: FooterLink[];\n}\n\nexport interface FooterSocialLink {\n /** Iconify icon name, e.g. `mdi:github`. */\n icon: string;\n href: string;\n}\n\n/** Fired when an internal footer link is activated — cancelable, so an SPA can `preventDefault()` and route instead. */\nexport interface FooterLinkEventDetail {\n href: string;\n}\n\n/**\n * The footer, in two layouts:\n *\n * - **Bar** (default): a copyright line plus an optional \"powered by\" link — what the suite's\n * authenticated apps use.\n * - **Site**: when `columns`, `socialLinks`, `description` or `imageSrc` is set, the footer becomes a\n * marketing-site footer — brand (logo/company name, description, social icons), link columns and a\n * bottom bar with the copyright and \"powered by\".\n *\n * Fires a cancelable `footer-link` event (detail `{ href }`) when an internal link is clicked.\n */\n@customElement('st-footer')\nexport class Footer extends LitElement {\n static styles = css`\n ${controlBaseStyles}\n\n :host {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--st-space-16);\n flex-wrap: wrap;\n /* !important on padding/border-top only: a shadow host's own :host rule always loses to ANY\n * rule the consuming page has for the same property on that element, regardless of\n * specificity — so a page that resets margin/padding/border on every element (e.g. Tailwind\n * Preflight, which every app but Template Hub loads) silently zeroes this footer's padding\n * and top border unless these two win the conflict outright via !important. */\n padding: var(--st-space-16) var(--st-space-28) !important;\n border-top: 1px solid var(--st-color-border) !important;\n color: var(--st-color-text-muted);\n font-size: var(--st-text-xs);\n ${rootFontFamilyStyles}\n }\n\n .powered-by {\n color: inherit;\n text-decoration: none;\n font-weight: var(--st-font-weight-semibold);\n }\n\n .powered-by:hover {\n color: var(--st-color-text-strong);\n }\n\n :host([inverted]) {\n background: var(--st-color-text-strong);\n color: var(--st-color-on-solid);\n border-top-color: transparent !important;\n }\n\n /* Site layout */\n :host([site]) {\n display: block;\n padding: 64px var(--st-space-28) 32px !important;\n font-size: var(--st-text-sm);\n }\n\n .site {\n max-width: 1200px;\n margin: 0 auto;\n }\n\n .grid {\n display: grid;\n grid-template-columns: 2fr repeat(3, 1fr);\n gap: 64px;\n margin-bottom: 64px;\n }\n\n .brand {\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n\n .logo {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 1.25rem;\n font-weight: 700;\n color: var(--st-color-text-strong);\n }\n\n .logo-img {\n height: 32px;\n width: auto;\n }\n\n .description {\n margin: 0;\n max-width: 300px;\n line-height: 1.6;\n }\n\n .social {\n display: flex;\n gap: 16px;\n margin-top: 16px;\n }\n\n .social-link,\n .link {\n color: inherit;\n text-decoration: none;\n transition: color 0.2s ease;\n }\n\n .social-link:hover,\n .link:hover {\n color: var(--st-color-text-strong);\n }\n\n .column-title {\n margin: 0 0 24px;\n font-size: var(--st-text-xs);\n font-weight: var(--st-font-weight-semibold);\n color: var(--st-color-text-strong);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n }\n\n .links {\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n\n .bottom {\n padding-top: 32px;\n border-top: 1px solid var(--st-color-border);\n font-size: var(--st-text-xs);\n }\n\n .bottom-text {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n\n .bottom-text .powered-by {\n font-size: 0.85em;\n font-weight: inherit;\n opacity: 0.8;\n }\n\n @media (max-width: 1024px) {\n .grid {\n grid-template-columns: repeat(3, 1fr);\n gap: 48px;\n }\n }\n\n @media (max-width: 768px) {\n .grid {\n grid-template-columns: 1fr;\n gap: 40px;\n }\n\n .bottom {\n text-align: center;\n }\n }\n\n :host([site][inverted]) .logo,\n :host([site][inverted]) .column-title,\n :host([site][inverted]) .social-link:hover,\n :host([site][inverted]) .link:hover {\n color: var(--st-color-on-solid);\n }\n\n :host([site][inverted]) .bottom {\n border-top-color: color-mix(in oklch, var(--st-color-on-solid) 20%, var(--st-color-text-strong));\n }\n `;\n\n /** Inverts the footer's color roles (light-on-dark instead of dark-on-light) — matches the\n * source component's `inverted` (from its shared `BaseProps`), same as `st-header`'s. */\n @property({ type: Boolean, reflect: true })\n inverted = false;\n\n /** Used to build the default `copyright` line (`© {year} {companyName}`) when `copyright` isn't set explicitly. */\n @property({ type: String, attribute: 'company-name' })\n companyName = '';\n\n /** Overrides the default computed copyright line entirely. */\n @property({ type: String })\n copyright = '';\n\n @property({ type: String, attribute: 'powered-by' })\n poweredBy = '';\n\n @property({ type: String, attribute: 'powered-by-link' })\n poweredByLink = '';\n\n /** App version, rendered as `v{version}` after the copyright line — for apps that show their own\n * version in the footer rather than the header title. */\n @property({ type: String })\n version = '';\n\n /** Logo shown in the brand block (site layout). */\n @property({ type: String, attribute: 'image-src' })\n imageSrc = '';\n\n /** Short blurb under the logo (site layout). */\n @property({ type: String })\n description = '';\n\n /** Link columns (site layout). */\n @property({ attribute: false })\n columns: FooterColumn[] = [];\n\n /** Social icon links under the description (site layout). */\n @property({ attribute: false })\n socialLinks: FooterSocialLink[] = [];\n\n private get isSite(): boolean {\n return !!(this.columns.length || this.socialLinks.length || this.description || this.imageSrc);\n }\n\n private get resolvedCopyright(): string {\n if (this.copyright) return this.copyright;\n if (this.companyName) return `© ${new Date().getFullYear()} ${this.companyName}`;\n return `© ${new Date().getFullYear()}`;\n }\n\n protected willUpdate(changed: PropertyValues): void {\n if (changed.size) this.toggleAttribute('site', this.isSite);\n }\n\n private onLinkClick(event: MouseEvent, href: string): void {\n const select = new CustomEvent<FooterLinkEventDetail>('footer-link', {\n detail: { href },\n bubbles: true,\n composed: true,\n cancelable: true\n });\n this.dispatchEvent(select);\n if (select.defaultPrevented) event.preventDefault();\n }\n\n private renderLink(link: FooterLink) {\n return /^https?:/i.test(link.href)\n ? html`<a part=\"link\" class=\"link\" href=${link.href} target=\"_blank\" rel=\"noopener noreferrer\">${link.label}</a>`\n : html`<a part=\"link\" class=\"link\" href=${link.href} @click=${(event: MouseEvent) => this.onLinkClick(event, link.href)}>${link.label}</a>`;\n }\n\n private renderPoweredBy() {\n return this.poweredBy\n ? html`\n <a part=\"powered-by\" class=\"powered-by\" href=${this.poweredByLink || '#'} target=\"_blank\" rel=\"noopener noreferrer\">\n ${this.poweredBy}\n </a>\n `\n : nothing;\n }\n\n private renderSite() {\n return html`\n <div class=\"site\" part=\"site\">\n <div class=\"grid\">\n <div class=\"brand\" part=\"brand\">\n ${this.imageSrc || this.companyName\n ? html`<div class=\"logo\">\n ${this.imageSrc ? html`<img class=\"logo-img\" src=${this.imageSrc} alt=${this.companyName || 'Logo'} />` : nothing}\n ${this.companyName ? html`<span>${this.companyName}</span>` : nothing}\n </div>`\n : nothing}\n ${this.description ? html`<p class=\"description\">${this.description}</p>` : nothing}\n ${this.socialLinks.length\n ? html`<div class=\"social\">\n ${this.socialLinks.map(\n social => html`<a part=\"social\" class=\"social-link\" href=${social.href} target=\"_blank\" rel=\"noopener noreferrer\" aria-label=${social.icon}>\n <iconify-icon icon=${social.icon} width=\"24\" height=\"24\"></iconify-icon>\n </a>`\n )}\n </div>`\n : nothing}\n </div>\n ${this.columns.map(\n column => html`<div class=\"column\" part=\"column\">\n <h4 class=\"column-title\">${column.title}</h4>\n <div class=\"links\">${column.links.map(link => this.renderLink(link))}</div>\n </div>`\n )}\n </div>\n <div class=\"bottom\" part=\"bottom\">\n <div class=\"bottom-text\">\n <span part=\"copyright\">${this.resolvedCopyright}</span>\n ${this.version ? html`<span part=\"version\">v${this.version}</span>` : nothing} ${this.renderPoweredBy()}\n </div>\n </div>\n </div>\n `;\n }\n\n render() {\n if (this.isSite) return this.renderSite();\n return html`\n <span part=\"copyright\">${this.resolvedCopyright}</span>\n ${this.version ? html`<span part=\"version\">v${this.version}</span>` : nothing} ${this.renderPoweredBy()}\n `;\n }\n}\n"],"mappings":"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","names":[]}