@invisibleloop/pulse 0.20.5 โ†’ 0.20.7

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.
Files changed (164) hide show
  1. package/.claude/pulse-checklist.md +1 -2
  2. package/CLAUDE.md +1 -2
  3. package/docs/.claude/pulse-checklist.md +1 -2
  4. package/docs/public/coutumes.css +21 -44
  5. package/docs/public/docs.css +73 -6
  6. package/docs/public/home-brut.css +175 -191
  7. package/docs/public/pulse-ui.css +8 -1037
  8. package/docs/public/retrowheels.css +23 -0
  9. package/docs/public/shop.css +35 -1
  10. package/docs/public/sparkys.css +56 -23
  11. package/docs/public/theme.css +51 -7
  12. package/docs/server.js +0 -11
  13. package/docs/src/lib/component-page.js +2 -1
  14. package/docs/src/lib/nav.js +0 -11
  15. package/docs/src/pages/accessibility.js +2 -1
  16. package/docs/src/pages/actions.js +2 -1
  17. package/docs/src/pages/auth.js +2 -1
  18. package/docs/src/pages/caching.js +2 -1
  19. package/docs/src/pages/components/accordion.js +2 -1
  20. package/docs/src/pages/components/alert.js +2 -1
  21. package/docs/src/pages/components/app-badge.js +2 -1
  22. package/docs/src/pages/components/avatar.js +2 -1
  23. package/docs/src/pages/components/badge.js +2 -1
  24. package/docs/src/pages/components/breadcrumbs.js +2 -1
  25. package/docs/src/pages/components/button.js +2 -1
  26. package/docs/src/pages/components/card.js +2 -1
  27. package/docs/src/pages/components/charts.js +4 -2
  28. package/docs/src/pages/components/checkbox.js +2 -1
  29. package/docs/src/pages/components/cluster.js +2 -1
  30. package/docs/src/pages/components/container.js +2 -1
  31. package/docs/src/pages/components/cta.js +2 -1
  32. package/docs/src/pages/components/divider.js +2 -1
  33. package/docs/src/pages/components/empty.js +2 -1
  34. package/docs/src/pages/components/feature.js +2 -1
  35. package/docs/src/pages/components/fieldset.js +2 -1
  36. package/docs/src/pages/components/fileupload.js +2 -1
  37. package/docs/src/pages/components/footer.js +2 -1
  38. package/docs/src/pages/components/grid.js +11 -6
  39. package/docs/src/pages/components/heading.js +2 -1
  40. package/docs/src/pages/components/hero.js +2 -1
  41. package/docs/src/pages/components/icons.js +2 -1
  42. package/docs/src/pages/components/image.js +2 -1
  43. package/docs/src/pages/components/input.js +2 -1
  44. package/docs/src/pages/components/media.js +2 -1
  45. package/docs/src/pages/components/modal.js +2 -1
  46. package/docs/src/pages/components/nav.js +2 -1
  47. package/docs/src/pages/components/phone-frame.js +2 -1
  48. package/docs/src/pages/components/progress.js +2 -1
  49. package/docs/src/pages/components/prose.js +2 -1
  50. package/docs/src/pages/components/pullquote.js +2 -1
  51. package/docs/src/pages/components/radio.js +2 -1
  52. package/docs/src/pages/components/rating.js +2 -1
  53. package/docs/src/pages/components/search.js +2 -1
  54. package/docs/src/pages/components/section.js +2 -1
  55. package/docs/src/pages/components/segmented.js +2 -1
  56. package/docs/src/pages/components/select.js +2 -1
  57. package/docs/src/pages/components/slider.js +2 -1
  58. package/docs/src/pages/components/spinner.js +2 -1
  59. package/docs/src/pages/components/stack.js +2 -1
  60. package/docs/src/pages/components/stat.js +2 -1
  61. package/docs/src/pages/components/stepper.js +2 -1
  62. package/docs/src/pages/components/table.js +2 -1
  63. package/docs/src/pages/components/textarea.js +2 -1
  64. package/docs/src/pages/components/toggle.js +2 -1
  65. package/docs/src/pages/components/tooltip.js +2 -1
  66. package/docs/src/pages/components.js +2 -5
  67. package/docs/src/pages/config.js +2 -1
  68. package/docs/src/pages/constraints.js +2 -1
  69. package/docs/src/pages/deploy.js +2 -1
  70. package/docs/src/pages/error-pages.js +2 -1
  71. package/docs/src/pages/extending.js +2 -1
  72. package/docs/src/pages/faq.js +2 -1
  73. package/docs/src/pages/forms.js +2 -1
  74. package/docs/src/pages/getting-started.js +2 -1
  75. package/docs/src/pages/guard.js +2 -1
  76. package/docs/src/pages/home.js +24 -15
  77. package/docs/src/pages/how-it-works.js +22 -15
  78. package/docs/src/pages/hydration.js +2 -1
  79. package/docs/src/pages/images.js +2 -1
  80. package/docs/src/pages/markdown.js +2 -1
  81. package/docs/src/pages/meta.js +2 -1
  82. package/docs/src/pages/mutations.js +2 -1
  83. package/docs/src/pages/navigation.js +2 -1
  84. package/docs/src/pages/performance.js +2 -1
  85. package/docs/src/pages/persist.js +2 -1
  86. package/docs/src/pages/project-structure.js +2 -1
  87. package/docs/src/pages/prompt-examples.js +2 -1
  88. package/docs/src/pages/raw-responses.js +2 -1
  89. package/docs/src/pages/routing.js +2 -1
  90. package/docs/src/pages/server-api.js +2 -1
  91. package/docs/src/pages/server-data.js +2 -1
  92. package/docs/src/pages/slash-commands.js +2 -1
  93. package/docs/src/pages/spec.js +2 -1
  94. package/docs/src/pages/state.js +2 -1
  95. package/docs/src/pages/store.js +2 -1
  96. package/docs/src/pages/streaming.js +2 -1
  97. package/docs/src/pages/stripe.js +2 -1
  98. package/docs/src/pages/supabase.js +2 -1
  99. package/docs/src/pages/templates/coutumes.js +83 -72
  100. package/docs/src/pages/templates/mobile-app.js +36 -9
  101. package/docs/src/pages/templates/retrowheels.js +17 -4
  102. package/docs/src/pages/templates/shop.js +21 -10
  103. package/docs/src/pages/templates/sparkys.js +35 -20
  104. package/docs/src/pages/templates.js +2 -2
  105. package/docs/src/pages/testing.js +2 -1
  106. package/docs/src/pages/validation.js +2 -1
  107. package/examples/.claude/pulse-checklist.md +1 -2
  108. package/examples/examples.test.js +4 -4
  109. package/examples/pricing.js +21 -3
  110. package/examples/public/pulse-ui.css +8 -1037
  111. package/examples/retrowheels.css +23 -0
  112. package/examples/retrowheels.js +17 -4
  113. package/package.json +5 -1
  114. package/public/.pulse-ui-version +1 -1
  115. package/public/pulse-ui.css +8 -1037
  116. package/src/agent/checklist.md +1 -2
  117. package/src/agent/guide-components.md +43 -67
  118. package/src/agent/guide-design-gallery.md +14 -14
  119. package/src/agent/guide-design-references.md +21 -21
  120. package/src/agent/guide-styles.md +2 -2
  121. package/src/agent/guide-templates.md +6 -6
  122. package/src/agent/identity.md +1 -1
  123. package/src/agent/workflow.md +1 -2
  124. package/src/mcp/server.js +31 -24
  125. package/src/ui/index.js +0 -27
  126. package/src/ui/ui.test.js +0 -137
  127. package/types/charts.d.ts +61 -0
  128. package/types/ui.d.ts +1 -129
  129. package/docs/src/pages/components/banner.js +0 -44
  130. package/docs/src/pages/components/carousel.js +0 -71
  131. package/docs/src/pages/components/code-window.js +0 -56
  132. package/docs/src/pages/components/decorate.js +0 -74
  133. package/docs/src/pages/components/gallery.js +0 -102
  134. package/docs/src/pages/components/list.js +0 -111
  135. package/docs/src/pages/components/marquee.js +0 -76
  136. package/docs/src/pages/components/photo-card.js +0 -74
  137. package/docs/src/pages/components/pricing.js +0 -67
  138. package/docs/src/pages/components/testimonial.js +0 -48
  139. package/docs/src/pages/components/timeline.js +0 -226
  140. package/src/ui/banner.js +0 -41
  141. package/src/ui/brand-statement.js +0 -58
  142. package/src/ui/carousel.js +0 -59
  143. package/src/ui/code-window.js +0 -39
  144. package/src/ui/colophon.js +0 -29
  145. package/src/ui/contact-row.js +0 -41
  146. package/src/ui/decorate.js +0 -55
  147. package/src/ui/display-heading.js +0 -44
  148. package/src/ui/drop-cap.js +0 -29
  149. package/src/ui/editorial-hero.js +0 -63
  150. package/src/ui/entry-list.js +0 -36
  151. package/src/ui/gallery.js +0 -60
  152. package/src/ui/image-pair.js +0 -72
  153. package/src/ui/list.js +0 -36
  154. package/src/ui/marquee.js +0 -48
  155. package/src/ui/photo-card.js +0 -44
  156. package/src/ui/pricing.js +0 -56
  157. package/src/ui/product-card.js +0 -65
  158. package/src/ui/section-label.js +0 -38
  159. package/src/ui/service-card.js +0 -47
  160. package/src/ui/stat-grid.js +0 -40
  161. package/src/ui/testimonial.js +0 -59
  162. package/src/ui/timeline.js +0 -72
  163. package/src/ui/top-strip.js +0 -47
  164. package/src/ui/trust-list.js +0 -46
package/src/mcp/server.js CHANGED
@@ -455,15 +455,12 @@ Import from \`@invisibleloop/pulse/ui\`. Always include \`/pulse-ui.css\` in \`m
455
455
  | \`card\` | \`title\`, \`content\`, \`footer\`, \`variant\` (default/elevated/bordered/flat/glass/tinted) |
456
456
  | \`feature\` | \`icon\`, \`title\`, \`description\`, \`level\`, \`center\` |
457
457
  | \`stat\` | \`label\`, \`value\`, \`change\`, \`trend\`, \`center\` |
458
- | \`testimonial\` | \`quote\`, \`name\`, \`role\`, \`rating\` |
459
458
  | \`cta\` | \`title\`, \`subtitle\`, \`actions\` (HTML slot), \`eyebrow\` |
460
459
  | \`footer\` | \`logo\`, \`logoHref\`, \`links\`, \`legal\`, \`columns\` |
461
460
  | \`input\` | \`name\`, \`label\`, \`type\`, \`required\`, \`error\` |
462
461
  | \`modal\` | \`id\`, \`title\`, \`content\`, \`footer\` โ€” always in DOM, never conditional |
463
- | \`displayHeading\` | \`text\`, \`level\`, \`tracking\` โ€” editorial large type |
464
- | \`sectionLabel\` | \`eyebrow\`, \`heading\`, \`level\`, \`rule\` โ€” editorial section divider |
465
- | \`pullquote\` | \`quote\`, \`cite\`, \`size\` |
466
- | \`entryList\` | \`items\` ([{meta, title, description}]), \`metaLabel\` โ€” tabular editorial list |
462
+ | \`heading\` | \`text\`, \`level\`, \`size\` |
463
+ | \`pullquote\` | \`quote\`, \`cite\`, \`size\`, \`variant\` (default/editorial) |
467
464
 
468
465
  ---
469
466
 
@@ -1593,9 +1590,7 @@ Work through every item. Fix anything that fails. Refer to the spec source at ${
1593
1590
  - \`.hero\`, \`.-hero\`, \`__hero\` โ†’ should use \`hero()\` component
1594
1591
  - \`.card\`, \`.product-card\`, \`.service-card\` โ†’ should use \`card()\` component
1595
1592
  - Any two-column image + text layout โ†’ should use \`media()\` component
1596
- - \`.banner\`, \`.strip\`, \`.bar\` โ†’ should use \`banner()\` component
1597
1593
  - \`.feature\`, \`.feature-card\` โ†’ should use \`feature()\` component
1598
- - \`.testimonial\` โ†’ should use \`testimonial()\` component
1599
1594
 
1600
1595
  **Before flagging a pattern match**, ask: *can the \`hero()\` (or relevant) component actually reproduce this layout?* If the design uses full-viewport height, custom gradient glows, clamp-scaled display type, asymmetric layout, or other features the component doesn't support โ€” that is a **creative override**, not a violation. Check the auto-checked items above: if "creative override declared" is shown, these checks are advisory only and Lighthouse is the pass bar.
1601
1596
 
@@ -1804,7 +1799,8 @@ const ARCHETYPES = {
1804
1799
  description: 'Data-rich admin or analytics page with metrics, charts, and tabular data',
1805
1800
  stateHint: "{ period: 'week', loading: false }",
1806
1801
  serverHint: "{ stats: async (ctx) => fetchStats(), rows: async (ctx) => fetchRows() }",
1807
- scaffold: `import { nav, stat, card, grid, barChart, table, section, container, badge, empty, spinner } from '@invisibleloop/pulse/ui'
1802
+ scaffold: `import { nav, stat, card, grid, table, section, container, badge, empty, spinner } from '@invisibleloop/pulse/ui'
1803
+ import { barChart } from '@invisibleloop/pulse/charts'
1808
1804
 
1809
1805
  export default {
1810
1806
  route: '/dashboard',
@@ -1841,7 +1837,7 @@ export default {
1841
1837
 
1842
1838
  landing: {
1843
1839
  keywords: ['landing', 'marketing', 'homepage', 'product', 'saas', 'startup', 'website', 'launch', 'hero', 'promotional'],
1844
- components: ['nav', 'hero', 'feature', 'stat', 'pricing', 'testimonial', 'cta', 'footer', 'grid', 'section', 'container', 'button', 'appBadge'],
1840
+ components: ['nav', 'hero', 'feature', 'stat', 'cta', 'footer', 'grid', 'section', 'container', 'button', 'appBadge'],
1845
1841
  description: 'Marketing or product landing page with hero, features, and calls to action',
1846
1842
  stateHint: 'null โ€” purely server-rendered, no client state needed',
1847
1843
  serverHint: 'none โ€” all content is static in the view',
@@ -2120,7 +2116,7 @@ export default {
2120
2116
 
2121
2117
  profile: {
2122
2118
  keywords: ['profile', 'user page', 'bio', 'portfolio', 'about', 'member', 'author', 'personal page', 'public profile'],
2123
- components: ['nav', 'avatar', 'card', 'stat', 'badge', 'grid', 'section', 'container', 'button', 'timeline', 'prose'],
2119
+ components: ['nav', 'avatar', 'card', 'stat', 'badge', 'grid', 'section', 'container', 'button', 'prose'],
2124
2120
  description: 'User or entity profile page with metadata and activity',
2125
2121
  stateHint: 'null โ€” purely server-rendered',
2126
2122
  serverHint: "{ user: async (ctx) => getUser(ctx.params.id), activity: async (ctx) => getActivity(ctx.params.id) }",
@@ -2164,11 +2160,24 @@ export default {
2164
2160
 
2165
2161
  pricing: {
2166
2162
  keywords: ['pricing', 'plans', 'subscription', 'tiers', 'billing', 'upgrade', 'compare', 'packages'],
2167
- components: ['nav', 'pricing', 'feature', 'cta', 'accordion', 'footer', 'section', 'grid', 'button', 'badge'],
2163
+ components: ['nav', 'card', 'grid', 'feature', 'cta', 'accordion', 'footer', 'section', 'button', 'badge'],
2168
2164
  description: 'Pricing page with plan comparison, feature lists, and upgrade CTA',
2169
2165
  stateHint: "{ billing: 'monthly' } โ€” for toggle between monthly/annual",
2170
2166
  serverHint: 'none โ€” content is typically static',
2171
- scaffold: `import { nav, pricing, cta, accordion, footer, section, container, button } from '@invisibleloop/pulse/ui'
2167
+ scaffold: `import { nav, card, grid, cta, accordion, footer, section, container, button, badge, iconCheck } from '@invisibleloop/pulse/ui'
2168
+
2169
+ const plan = (p) => card({
2170
+ variant: p.featured ? 'elevated' : 'bordered',
2171
+ content: \`
2172
+ \${p.featured ? badge({ label: 'Most popular' }) : ''}
2173
+ <h3 class="u-text-xl u-font-bold u-mt-2">\${p.name}</h3>
2174
+ <p class="u-text-3xl u-font-bold u-mt-2">\${p.price}<span class="u-text-sm u-text-muted">\${p.period}</span></p>
2175
+ <ul class="u-mt-4 u-flex u-flex-col u-gap-2">
2176
+ \${p.features.map(f => \`<li class="u-flex u-gap-2 u-items-center">\${iconCheck({ size: 16 })} \${f}</li>\`).join('')}
2177
+ </ul>
2178
+ \`,
2179
+ footer: p.cta,
2180
+ })
2172
2181
 
2173
2182
  export default {
2174
2183
  route: '/pricing',
@@ -2186,13 +2195,11 @@ export default {
2186
2195
  \${section({ content: container({ content: \`
2187
2196
  <h1 class="u-text-4xl u-font-bold u-text-center u-mb-2">Simple pricing</h1>
2188
2197
  <p class="u-text-center u-text-muted u-mb-8">No hidden fees. Cancel any time.</p>
2189
- \${pricing({
2190
- plans: [
2191
- { name: 'Free', price: '\$0', period: 'forever', features: ['Feature A', 'Feature B'], cta: button({ label: 'Get started', variant: 'secondary' }) },
2192
- { name: 'Pro', price: '\$12', period: '/month', features: ['Everything in Free', 'Feature C', 'Feature D'], featured: true, cta: button({ label: 'Start free trial' }) },
2193
- { name: 'Team', price: '\$49', period: '/month', features: ['Everything in Pro', 'Feature E', 'Unlimited seats'], cta: button({ label: 'Contact sales', variant: 'secondary' }) },
2194
- ]
2195
- })}
2198
+ \${grid({ cols: 3, content: [
2199
+ { name: 'Free', price: '\$0', period: '/forever', features: ['Feature A', 'Feature B'], cta: button({ label: 'Get started', variant: 'secondary' }) },
2200
+ { name: 'Pro', price: '\$12', period: '/month', features: ['Everything in Free', 'Feature C', 'Feature D'], featured: true, cta: button({ label: 'Start free trial' }) },
2201
+ { name: 'Team', price: '\$49', period: '/month', features: ['Everything in Pro', 'Feature E', 'Unlimited seats'], cta: button({ label: 'Contact sales', variant: 'secondary' }) },
2202
+ ].map(plan).join('') })}
2196
2203
  \${accordion({ items: [
2197
2204
  { title: 'Can I cancel any time?', content: 'Yes โ€” no lock-in, cancel from your account settings.' },
2198
2205
  { title: 'What payment methods do you accept?', content: 'Visa, Mastercard, Amex, and PayPal.' },
@@ -3038,15 +3045,15 @@ Accepts antiStyle ("what should this NOT look like?") and inspiration (a site or
3038
3045
  if (vibe) {
3039
3046
  lines.push(`- [ ] Vibe: \`meta.vibe: '${vibe}'\` โ€” sets data-vibe on body, activating geometry/type presets`)
3040
3047
  const vibeGuide = {
3041
- warm: 'Pair with rounded imagery (photoCard with tilt), warmer accent tones, section variant: paper or alt',
3048
+ warm: 'Pair with rounded imagery (uiImage with rounded corners), warmer accent tones, section variant: paper or alt',
3042
3049
  editorial: 'Use layout: overlap on hero for dramatic imagery. Serif headings via --font-display. section variant: diagonal for transitions.',
3043
- playful: 'gallery with rounded images, photoCard with tilt, marquee for social proof, bright accent',
3050
+ playful: 'grid of uiImage with rounded corners, bright accent, raw HTML marquee-style strip if needed (creative override)',
3044
3051
  minimal: 'hero align: left (no center), section variant: spotlight, clean whitespace, monochrome palette',
3045
3052
  bold: 'hero gradient with strong color, large stat components, section variant: dark for contrast sections',
3046
3053
  brutalist: 'raw section wrappers, oversized heading with zero padding, no shadows, high contrast accent (red/lime)',
3047
- retro: 'decorate(pattern: "dots") backgrounds, badge eyebrows, thick dividers, earthy amber/cream palette',
3048
- corporate: 'feature grid with checkmarks, testimonial with company name+logo, trust logo marquee, stat bar',
3049
- neon: 'section(variant: "spotlight") for glow sections, codeWindow for terminal/API examples, stat with glowing accent',
3054
+ retro: 'badge eyebrows, thick dividers, earthy amber/cream palette, raw HTML dot-pattern background (creative override)',
3055
+ corporate: 'feature grid with checkmarks, card() with logo+quote for testimonials, stat bar',
3056
+ neon: 'section(variant: "spotlight") for glow sections, raw HTML terminal/API code block (creative override) for dev examples, stat with glowing accent',
3050
3057
  paper: 'pullquote prominently, prose with generous line-height, avatar for author, container(size: "sm")',
3051
3058
  }
3052
3059
  if (vibeGuide[vibe]) lines.push(` **${vibe} component suggestions:** ${vibeGuide[vibe]}`)
package/src/ui/index.js CHANGED
@@ -33,9 +33,7 @@ export { empty } from './empty.js'
33
33
  export { table } from './table.js'
34
34
  export { hero } from './hero.js'
35
35
  export { phoneFrame } from './phone-frame.js'
36
- export { testimonial } from './testimonial.js'
37
36
  export { feature } from './feature.js'
38
- export { pricing } from './pricing.js'
39
37
  export { accordion } from './accordion.js'
40
38
  export { nav } from './nav.js'
41
39
  export { appBadge } from './app-badge.js'
@@ -45,52 +43,27 @@ export { grid } from './grid.js'
45
43
  export { stack } from './stack.js'
46
44
  export { cluster } from './cluster.js'
47
45
  export { divider } from './divider.js'
48
- export { banner } from './banner.js'
49
46
  export { media } from './media.js'
50
47
  export { tooltip } from './tooltip.js'
51
48
  export { modal, modalTrigger } from './modal.js'
52
- export { carousel } from './carousel.js'
53
49
  export { cta } from './cta.js'
54
- export { codeWindow } from './code-window.js'
55
50
  export { footer } from './footer.js'
56
- export { timeline, timelineItem } from './timeline.js'
57
51
  export { toggle } from './switch.js'
58
52
  export { checkbox } from './checkbox.js'
59
53
  export { radio, radioGroup } from './radio.js'
60
54
  export { rating } from './rating.js'
61
55
  export { spinner } from './spinner.js'
62
56
  export { progress } from './progress.js'
63
- export { barChart, lineChart, donutChart, sparkline } from './charts.js'
64
57
  export { slider } from './slider.js'
65
58
  export { segmented } from './segmented.js'
66
59
  export { fileUpload } from './fileupload.js'
67
60
  export { breadcrumbs } from './breadcrumbs.js'
68
61
  export { stepper } from './stepper.js'
69
62
  export { uiImage } from './uiimage.js'
70
- export { gallery } from './gallery.js'
71
- export { photoCard } from './photo-card.js'
72
- export { productCard } from './product-card.js'
73
- export { editorialHero } from './editorial-hero.js'
74
- export { imagePair } from './image-pair.js'
75
- export { brandStatement } from './brand-statement.js'
76
- export { marquee } from './marquee.js'
77
- export { serviceCard } from './service-card.js'
78
- export { contactRow } from './contact-row.js'
79
- export { trustList } from './trust-list.js'
80
- export { topStrip } from './top-strip.js'
81
- export { statGrid } from './stat-grid.js'
82
- export { decorate } from './decorate.js'
83
63
  export { pullquote } from './pullquote.js'
84
64
  export { prose } from './prose.js'
85
65
  export { heading } from './heading.js'
86
66
 
87
- // Editorial components
88
- export { entryList } from './entry-list.js'
89
- export { displayHeading } from './display-heading.js'
90
- export { sectionLabel } from './section-label.js'
91
- export { dropCap } from './drop-cap.js'
92
- export { colophon } from './colophon.js'
93
- export { list } from './list.js'
94
67
  export {
95
68
  iconArrowLeft, iconArrowRight, iconArrowUp, iconArrowDown,
96
69
  iconChevronLeft, iconChevronRight, iconChevronUp, iconChevronDown,
package/src/ui/ui.test.js CHANGED
@@ -20,21 +20,16 @@ import { avatar } from './avatar.js'
20
20
  import { empty } from './empty.js'
21
21
  import { table } from './table.js'
22
22
  import { hero } from './hero.js'
23
- import { testimonial } from './testimonial.js'
24
23
  import { feature } from './feature.js'
25
- import { pricing } from './pricing.js'
26
24
  import { accordion } from './accordion.js'
27
25
  import { nav } from './nav.js'
28
26
  import { appBadge } from './app-badge.js'
29
- import { dropCap } from './drop-cap.js'
30
- import { gallery } from './gallery.js'
31
27
  import { container } from './container.js'
32
28
  import { section } from './section.js'
33
29
  import { grid } from './grid.js'
34
30
  import { stack } from './stack.js'
35
31
  import { cluster } from './cluster.js'
36
32
  import { divider } from './divider.js'
37
- import { banner } from './banner.js'
38
33
  import { media } from './media.js'
39
34
  import { fieldset } from './fieldset.js'
40
35
  import { slider } from './slider.js'
@@ -521,43 +516,6 @@ test('hero: escapes title and eyebrow', () => {
521
516
  assert.doesNotMatch(html, /<b>bad<\/b>/)
522
517
  })
523
518
 
524
- // โ”€โ”€โ”€ testimonial โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
525
-
526
- test('testimonial: renders quote, name, role', () => {
527
- const html = testimonial({ quote: 'Great app', name: 'Alice Smith', role: 'CEO' })
528
- assert.match(html, /Great app/)
529
- assert.match(html, /Alice Smith/)
530
- assert.match(html, /CEO/)
531
- })
532
-
533
- test('testimonial: renders stars when rating provided', () => {
534
- const html = testimonial({ quote: 'x', name: 'A', rating: 5 })
535
- assert.match(html, /ui-testimonial-rating/)
536
- assert.match(html, /โ˜…โ˜…โ˜…โ˜…โ˜…/)
537
- })
538
-
539
- test('testimonial: no stars when rating omitted', () => {
540
- assert.doesNotMatch(testimonial({ quote: 'x', name: 'A' }), /ui-testimonial-rating/)
541
- })
542
-
543
- test('testimonial: renders initials when src omitted', () => {
544
- const html = testimonial({ quote: 'x', name: 'Alice Smith' })
545
- assert.match(html, /ui-testimonial-avatar--initials/)
546
- assert.match(html, /AS/)
547
- })
548
-
549
- test('testimonial: renders img when src provided', () => {
550
- const html = testimonial({ quote: 'x', name: 'Alice', src: '/photo.jpg' })
551
- assert.match(html, /<img/)
552
- assert.match(html, /photo\.jpg/)
553
- })
554
-
555
- test('testimonial: escapes quote and name', () => {
556
- const html = testimonial({ quote: '<script>', name: '<b>X</b>' })
557
- assert.doesNotMatch(html, /<script>/)
558
- assert.doesNotMatch(html, /<b>X<\/b>/)
559
- })
560
-
561
519
  // โ”€โ”€โ”€ feature โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
562
520
 
563
521
  test('feature: renders title and description', () => {
@@ -578,43 +536,6 @@ test('feature: escapes title and description', () => {
578
536
  assert.doesNotMatch(html, /<b>bad<\/b>/)
579
537
  })
580
538
 
581
- // โ”€โ”€โ”€ pricing โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
582
-
583
- test('pricing: renders name, price, period', () => {
584
- const html = pricing({ name: 'Pro', price: '$9', period: '/month' })
585
- assert.match(html, /ui-pricing-name/)
586
- assert.match(html, /\$9/)
587
- assert.match(html, /\/month/)
588
- })
589
-
590
- test('pricing: renders feature list', () => {
591
- const html = pricing({ name: 'x', price: 'Free', features: ['Unlimited pages', 'Support'] })
592
- assert.match(html, /Unlimited pages/)
593
- assert.match(html, /Support/)
594
- assert.match(html, /ui-pricing-check/)
595
- })
596
-
597
- test('pricing: highlighted adds modifier class', () => {
598
- assert.match(pricing({ name: 'x', price: 'x', highlighted: true }), /ui-pricing--highlighted/)
599
- })
600
-
601
- test('pricing: badge renders when provided', () => {
602
- const html = pricing({ name: 'x', price: 'x', badge: 'Most popular' })
603
- assert.match(html, /ui-pricing-badge/)
604
- assert.match(html, /Most popular/)
605
- })
606
-
607
- test('pricing: action slot passes through raw HTML', () => {
608
- const html = pricing({ name: 'x', price: 'x', action: '<button>Buy</button>' })
609
- assert.match(html, /<button>Buy<\/button>/)
610
- })
611
-
612
- test('pricing: escapes name and description', () => {
613
- const html = pricing({ name: '<script>', description: '<b>x</b>' })
614
- assert.doesNotMatch(html, /<script>/)
615
- assert.doesNotMatch(html, /<b>x<\/b>/)
616
- })
617
-
618
539
  // โ”€โ”€โ”€ accordion โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
619
540
 
620
541
  test('accordion: renders details/summary', () => {
@@ -859,32 +780,6 @@ test('divider: escapes label', () => {
859
780
  assert.doesNotMatch(divider({ label: '<script>' }), /<script>/)
860
781
  })
861
782
 
862
- // โ”€โ”€โ”€ banner โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
863
-
864
- test('banner: renders content', () => {
865
- assert.match(banner({ content: 'Now available' }), /Now available/)
866
- })
867
-
868
- test('banner: info variant by default', () => {
869
- assert.match(banner({ content: '' }), /ui-banner--info/)
870
- })
871
-
872
- test('banner: promo variant adds class', () => {
873
- assert.match(banner({ content: '', variant: 'promo' }), /ui-banner--promo/)
874
- })
875
-
876
- test('banner: warning variant adds class', () => {
877
- assert.match(banner({ content: '', variant: 'warning' }), /ui-banner--warning/)
878
- })
879
-
880
- test('banner: unknown variant falls back to info', () => {
881
- assert.match(banner({ content: '', variant: 'fancy' }), /ui-banner--info/)
882
- })
883
-
884
- test('banner: has role=banner', () => {
885
- assert.match(banner({ content: '' }), /role="banner"/)
886
- })
887
-
888
783
  // โ”€โ”€โ”€ media โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
889
784
 
890
785
  test('media: renders image and content slots', () => {
@@ -1322,38 +1217,6 @@ test('uiImage: escapes src to prevent XSS', () => {
1322
1217
  assert.doesNotMatch(html, /<script>/)
1323
1218
  })
1324
1219
 
1325
- test('dropCap: inline style carries only the dynamic font-size โ€” alignment lives in CSS', () => {
1326
- // Regression: a relative inline line-height (lines * 0.8 โ†’ 2.4 for 3 lines)
1327
- // made the float box ~3x the glyph height, so serif caps floated below the
1328
- // opening line instead of top-aligning. line-height/float/margin now live in
1329
- // .ui-drop-cap-letter (pulse-ui.css) with a fixed ~cap-height value.
1330
- const html = dropCap({ letter: 'O', content: 'nce upon a time.' })
1331
- assert.match(html, /class="ui-drop-cap-letter"/)
1332
- assert.match(html, /style="font-size:3\.6em"/)
1333
- assert.doesNotMatch(html, /line-height/)
1334
- assert.doesNotMatch(html, /float:left/)
1335
- })
1336
-
1337
- test('dropCap: lines option scales the font-size', () => {
1338
- const html = dropCap({ letter: 'O', content: 'nce.', lines: 5 })
1339
- assert.match(html, /style="font-size:6em"/)
1340
- })
1341
-
1342
- test('gallery: images are lazy by default', () => {
1343
- const html = gallery({ images: [{ src: '/a.jpg', alt: 'A' }] })
1344
- assert.match(html, /loading="lazy"/)
1345
- })
1346
-
1347
- test('gallery: lazy:false renders eager images', () => {
1348
- const html = gallery({ images: [{ src: '/a.jpg', alt: 'A' }], lazy: false })
1349
- assert.doesNotMatch(html, /loading="lazy"/)
1350
- })
1351
-
1352
- test('dropCap: escapes letter and content', () => {
1353
- const html = dropCap({ letter: '<', content: '<script>alert(1)</script>' })
1354
- assert.doesNotMatch(html, /<script>/)
1355
- })
1356
-
1357
1220
  test('pulse-ui.css: form controls are at least 16px on touch devices (iOS focus-zoom)', () => {
1358
1221
  // iOS Safari zooms the page when a focused input's font-size is under 16px,
1359
1222
  // forcing the user to pinch back out. A (pointer: coarse) block must keep
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Pulse Charts โ€” SVG chart components
3
+ * @invisibleloop/pulse/charts
4
+ *
5
+ * All components are pure functions: (props?) => HTML string.
6
+ * Import only what you need โ€” no side effects on import.
7
+ */
8
+
9
+ type ChartColor = 'accent' | 'success' | 'warning' | 'error' | 'blue' | 'muted'
10
+
11
+ export interface ChartDataPoint {
12
+ label: string
13
+ value: number
14
+ }
15
+ export interface DonutDataPoint {
16
+ label: string
17
+ value: number
18
+ color?: string
19
+ }
20
+
21
+ export interface BarChartProps {
22
+ data?: ChartDataPoint[]
23
+ height?: number
24
+ color?: ChartColor
25
+ showValues?: boolean
26
+ showGrid?: boolean
27
+ gap?: number
28
+ class?: string
29
+ }
30
+ export function barChart(props?: BarChartProps): string
31
+
32
+ export interface LineChartProps {
33
+ data?: ChartDataPoint[]
34
+ height?: number
35
+ color?: ChartColor
36
+ area?: boolean
37
+ showDots?: boolean
38
+ showGrid?: boolean
39
+ class?: string
40
+ }
41
+ export function lineChart(props?: LineChartProps): string
42
+
43
+ export interface DonutChartProps {
44
+ data?: DonutDataPoint[]
45
+ size?: number
46
+ thickness?: number
47
+ label?: string
48
+ sublabel?: string
49
+ class?: string
50
+ }
51
+ export function donutChart(props?: DonutChartProps): string
52
+
53
+ export interface SparklineProps {
54
+ data?: number[]
55
+ width?: number
56
+ height?: number
57
+ color?: ChartColor
58
+ area?: boolean
59
+ class?: string
60
+ }
61
+ export function sparkline(props?: SparklineProps): string
package/types/ui.d.ts CHANGED
@@ -14,7 +14,6 @@ type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger'
14
14
  type Size = 'sm' | 'md' | 'lg'
15
15
  type AlertVariant = 'info' | 'success' | 'warning' | 'error'
16
16
  type BadgeVariant = 'default' | 'success' | 'warning' | 'error' | 'info'
17
- type ChartColor = 'accent' | 'success' | 'warning' | 'error' | 'blue' | 'muted'
18
17
 
19
18
  // ---------------------------------------------------------------------------
20
19
  // Form controls
@@ -223,17 +222,6 @@ export interface PullquoteProps {
223
222
  }
224
223
  export function pullquote(props?: PullquoteProps): string
225
224
 
226
- export interface ListItem {
227
- text: string
228
- href?: string
229
- }
230
- export interface ListProps {
231
- items?: Array<string | ListItem>
232
- ordered?: boolean
233
- class?: string
234
- }
235
- export function list(props?: ListProps): string
236
-
237
225
  // ---------------------------------------------------------------------------
238
226
  // Navigation
239
227
  // ---------------------------------------------------------------------------
@@ -360,29 +348,6 @@ export interface FeatureProps {
360
348
  }
361
349
  export function feature(props?: FeatureProps): string
362
350
 
363
- export interface TestimonialProps {
364
- quote?: string
365
- name?: string
366
- role?: string
367
- avatar?: string
368
- class?: string
369
- }
370
- export function testimonial(props?: TestimonialProps): string
371
-
372
- export interface PricingTier {
373
- name?: string
374
- price?: string
375
- period?: string
376
- features?: string[]
377
- action?: string
378
- featured?: boolean
379
- }
380
- export interface PricingProps {
381
- tiers?: PricingTier[]
382
- class?: string
383
- }
384
- export function pricing(props?: PricingProps): string
385
-
386
351
  export interface CtaProps {
387
352
  heading?: string
388
353
  content?: string
@@ -391,13 +356,6 @@ export interface CtaProps {
391
356
  }
392
357
  export function cta(props?: CtaProps): string
393
358
 
394
- export interface BannerProps {
395
- content?: string
396
- variant?: AlertVariant
397
- class?: string
398
- }
399
- export function banner(props?: BannerProps): string
400
-
401
359
  // ---------------------------------------------------------------------------
402
360
  // Media
403
361
  // ---------------------------------------------------------------------------
@@ -421,12 +379,6 @@ export interface UiImageProps {
421
379
  }
422
380
  export function uiImage(props?: UiImageProps): string
423
381
 
424
- export interface CarouselProps {
425
- items?: string[]
426
- class?: string
427
- }
428
- export function carousel(props?: CarouselProps): string
429
-
430
382
  // ---------------------------------------------------------------------------
431
383
  // Interactive UI
432
384
  // ---------------------------------------------------------------------------
@@ -514,25 +466,9 @@ export interface FileUploadProps {
514
466
  export function fileUpload(props?: FileUploadProps): string
515
467
 
516
468
  // ---------------------------------------------------------------------------
517
- // Timeline & stepper
469
+ // Stepper
518
470
  // ---------------------------------------------------------------------------
519
471
 
520
- export interface TimelineItemProps {
521
- title?: string
522
- content?: string
523
- date?: string
524
- icon?: string
525
- active?: boolean
526
- class?: string
527
- }
528
- export function timelineItem(props?: TimelineItemProps): string
529
-
530
- export interface TimelineProps {
531
- items?: string[]
532
- class?: string
533
- }
534
- export function timeline(props?: TimelineProps): string
535
-
536
472
  export interface StepperStep {
537
473
  label: string
538
474
  complete?: boolean
@@ -562,70 +498,6 @@ export interface FooterProps {
562
498
  }
563
499
  export function footer(props?: FooterProps): string
564
500
 
565
- export interface CodeWindowProps {
566
- code?: string
567
- language?: string
568
- filename?: string
569
- class?: string
570
- }
571
- export function codeWindow(props?: CodeWindowProps): string
572
-
573
- // ---------------------------------------------------------------------------
574
- // Charts
575
- // ---------------------------------------------------------------------------
576
-
577
- export interface ChartDataPoint {
578
- label: string
579
- value: number
580
- }
581
- export interface DonutDataPoint {
582
- label: string
583
- value: number
584
- color?: string
585
- }
586
-
587
- export interface BarChartProps {
588
- data?: ChartDataPoint[]
589
- height?: number
590
- color?: ChartColor
591
- showValues?: boolean
592
- showGrid?: boolean
593
- gap?: number
594
- class?: string
595
- }
596
- export function barChart(props?: BarChartProps): string
597
-
598
- export interface LineChartProps {
599
- data?: ChartDataPoint[]
600
- height?: number
601
- color?: ChartColor
602
- area?: boolean
603
- showDots?: boolean
604
- showGrid?: boolean
605
- class?: string
606
- }
607
- export function lineChart(props?: LineChartProps): string
608
-
609
- export interface DonutChartProps {
610
- data?: DonutDataPoint[]
611
- size?: number
612
- thickness?: number
613
- label?: string
614
- sublabel?: string
615
- class?: string
616
- }
617
- export function donutChart(props?: DonutChartProps): string
618
-
619
- export interface SparklineProps {
620
- data?: number[]
621
- width?: number
622
- height?: number
623
- color?: ChartColor
624
- area?: boolean
625
- class?: string
626
- }
627
- export function sparkline(props?: SparklineProps): string
628
-
629
501
  // ---------------------------------------------------------------------------
630
502
  // Icons
631
503
  // ---------------------------------------------------------------------------
@@ -1,44 +0,0 @@
1
- import { renderComponentPage, demo } from '../../lib/component-page.js'
2
- import { prevNext } from '../../lib/nav.js'
3
- import { table } from '../../lib/layout.js'
4
- import { banner, stack } from '../../../../src/ui/index.js'
5
-
6
- const { prev, next } = prevNext('/components/banner')
7
-
8
- export default {
9
- route: '/components/banner',
10
- meta: {
11
- title: 'Banner โ€” Pulse Docs',
12
- description: 'Banner component for Pulse UI.',
13
- styles: ['/pulse-ui.css', '/docs.css'],
14
- },
15
- view: () => renderComponentPage({
16
- currentHref: '/components/banner',
17
- prev,
18
- next,
19
- name: 'banner',
20
- description: 'Full-width announcement bar. Sits above the nav for promotions and launch announcements. The content slot accepts links and formatted text.',
21
- content: `
22
- ${demo(
23
- stack({
24
- gap: 'sm',
25
- content:
26
- banner({ variant: 'promo', content: '๐ŸŽ‰ Chippy Bird v2.0 is out โ€” <a href="#" style="color:inherit;text-decoration:underline">see what\'s new</a>' }) +
27
- banner({ variant: 'info', content: 'Android support is coming soon. <a href="#" style="color:inherit;text-decoration:underline">Join the waitlist</a>' }) +
28
- banner({ variant: 'warning', content: 'Scheduled maintenance Sunday 2โ€“4am UTC.' }),
29
- }),
30
- `banner({ variant: 'promo', content:
31
- '๐ŸŽ‰ v2.0 is out โ€” ' + button({ label: "See what's new", variant: 'ghost', size: 'sm', href: '/changelog' })
32
- })`
33
- )}
34
-
35
- ${table(
36
- ['Prop', 'Type', 'Default', 'Description'],
37
- [
38
- ['<code>content</code>', 'string (HTML)', 'โ€”', 'Raw HTML slot'],
39
- ['<code>variant</code>', 'string', "'info'", "'info' ยท 'promo' ยท 'warning'"],
40
- ]
41
- )}
42
- `,
43
- }),
44
- }