@kodffe/ui 0.0.0-stage → 0.10.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.
- package/dist/_virtual/_rolldown/runtime.js +11 -0
- package/dist/accordion/accordion.d.ts +44 -0
- package/dist/accordion/accordion.js +84 -0
- package/dist/accordion/accordion.js.map +1 -0
- package/dist/alert/alert.d.ts +17 -0
- package/dist/alert/alert.js +59 -0
- package/dist/alert/alert.js.map +1 -0
- package/dist/app-shell/app-footer.d.ts +29 -0
- package/dist/app-shell/app-footer.js +39 -0
- package/dist/app-shell/app-footer.js.map +1 -0
- package/dist/app-shell/app-shell-context.d.ts +10 -0
- package/dist/app-shell/app-shell-context.js +12 -0
- package/dist/app-shell/app-shell-context.js.map +1 -0
- package/dist/app-shell/app-shell.d.ts +42 -0
- package/dist/app-shell/app-shell.js +191 -0
- package/dist/app-shell/app-shell.js.map +1 -0
- package/dist/app-shell/floating-actions.d.ts +19 -0
- package/dist/app-shell/floating-actions.js +59 -0
- package/dist/app-shell/floating-actions.js.map +1 -0
- package/dist/app-shell/workspace-switcher.d.ts +25 -0
- package/dist/app-shell/workspace-switcher.js +82 -0
- package/dist/app-shell/workspace-switcher.js.map +1 -0
- package/dist/badge/badge.d.ts +19 -0
- package/dist/badge/badge.js +59 -0
- package/dist/badge/badge.js.map +1 -0
- package/dist/banner/banner.d.ts +22 -0
- package/dist/banner/banner.js +80 -0
- package/dist/banner/banner.js.map +1 -0
- package/dist/breadcrumbs/breadcrumbs.d.ts +18 -0
- package/dist/breadcrumbs/breadcrumbs.js +45 -0
- package/dist/breadcrumbs/breadcrumbs.js.map +1 -0
- package/dist/button/button.d.ts +38 -0
- package/dist/button/button.js +83 -0
- package/dist/button/button.js.map +1 -0
- package/dist/card/card.d.ts +159 -0
- package/dist/card/card.js +390 -0
- package/dist/card/card.js.map +1 -0
- package/dist/card/insight-card.d.ts +19 -0
- package/dist/card/insight-card.js +51 -0
- package/dist/card/insight-card.js.map +1 -0
- package/dist/card/integration-card.d.ts +19 -0
- package/dist/card/integration-card.js +70 -0
- package/dist/card/integration-card.js.map +1 -0
- package/dist/charts/charts.d.ts +75 -0
- package/dist/charts/charts.js +453 -0
- package/dist/charts/charts.js.map +1 -0
- package/dist/choice/choice.d.ts +39 -0
- package/dist/choice/choice.js +143 -0
- package/dist/choice/choice.js.map +1 -0
- package/dist/combobox/combobox.d.ts +46 -0
- package/dist/combobox/combobox.js +196 -0
- package/dist/combobox/combobox.js.map +1 -0
- package/dist/command-palette/command-palette.d.ts +35 -0
- package/dist/command-palette/command-palette.js +164 -0
- package/dist/command-palette/command-palette.js.map +1 -0
- package/dist/confirm-dialog/confirm-dialog.d.ts +37 -0
- package/dist/confirm-dialog/confirm-dialog.js +106 -0
- package/dist/confirm-dialog/confirm-dialog.js.map +1 -0
- package/dist/content/assistant.d.ts +86 -0
- package/dist/content/assistant.js +213 -0
- package/dist/content/assistant.js.map +1 -0
- package/dist/content/content.d.ts +45 -0
- package/dist/content/content.js +136 -0
- package/dist/content/content.js.map +1 -0
- package/dist/data-display/avatar-sizes.d.ts +8 -0
- package/dist/data-display/avatar-sizes.js +12 -0
- package/dist/data-display/avatar-sizes.js.map +1 -0
- package/dist/data-display/avatar.d.ts +21 -0
- package/dist/data-display/avatar.js +53 -0
- package/dist/data-display/avatar.js.map +1 -0
- package/dist/data-display/chip.d.ts +20 -0
- package/dist/data-display/chip.js +64 -0
- package/dist/data-display/chip.js.map +1 -0
- package/dist/data-display/code-block.d.ts +14 -0
- package/dist/data-display/code-block.js +50 -0
- package/dist/data-display/code-block.js.map +1 -0
- package/dist/data-display/data-display.d.ts +73 -0
- package/dist/data-display/data-display.js +227 -0
- package/dist/data-display/data-display.js.map +1 -0
- package/dist/data-display/insights.d.ts +120 -0
- package/dist/data-display/insights.js +220 -0
- package/dist/data-display/insights.js.map +1 -0
- package/dist/data-table/cells.d.ts +86 -0
- package/dist/data-table/cells.js +163 -0
- package/dist/data-table/cells.js.map +1 -0
- package/dist/data-table/data-table.d.ts +112 -0
- package/dist/data-table/data-table.js +558 -0
- package/dist/data-table/data-table.js.map +1 -0
- package/dist/date-picker/date-picker.d.ts +61 -0
- package/dist/date-picker/date-picker.js +309 -0
- package/dist/date-picker/date-picker.js.map +1 -0
- package/dist/drawer/drawer.d.ts +32 -0
- package/dist/drawer/drawer.js +72 -0
- package/dist/drawer/drawer.js.map +1 -0
- package/dist/empty-state/empty-state.d.ts +25 -0
- package/dist/empty-state/empty-state.js +78 -0
- package/dist/empty-state/empty-state.js.map +1 -0
- package/dist/error-state/error-state.d.ts +19 -0
- package/dist/error-state/error-state.js +56 -0
- package/dist/error-state/error-state.js.map +1 -0
- package/dist/event-calendar/calendar-logic.test.d.ts +1 -0
- package/dist/event-calendar/dates.d.ts +35 -0
- package/dist/event-calendar/dates.js +55 -0
- package/dist/event-calendar/dates.js.map +1 -0
- package/dist/event-calendar/event-calendar.d.ts +125 -0
- package/dist/event-calendar/event-calendar.js +795 -0
- package/dist/event-calendar/event-calendar.js.map +1 -0
- package/dist/event-calendar/event-details.d.ts +50 -0
- package/dist/event-calendar/event-details.js +124 -0
- package/dist/event-calendar/event-details.js.map +1 -0
- package/dist/event-calendar/layout.d.ts +48 -0
- package/dist/event-calendar/layout.js +55 -0
- package/dist/event-calendar/layout.js.map +1 -0
- package/dist/file-upload/file-upload.d.ts +70 -0
- package/dist/file-upload/file-upload.js +307 -0
- package/dist/file-upload/file-upload.js.map +1 -0
- package/dist/filters/filters.d.ts +48 -0
- package/dist/filters/filters.js +103 -0
- package/dist/filters/filters.js.map +1 -0
- package/dist/form-field/form-field.d.ts +39 -0
- package/dist/form-field/form-field.js +88 -0
- package/dist/form-field/form-field.js.map +1 -0
- package/dist/hover-card/hover-card.d.ts +15 -0
- package/dist/hover-card/hover-card.js +27 -0
- package/dist/hover-card/hover-card.js.map +1 -0
- package/dist/hover-card/person-hover-card.d.ts +26 -0
- package/dist/hover-card/person-hover-card.js +85 -0
- package/dist/hover-card/person-hover-card.js.map +1 -0
- package/dist/icon/icon-tile.d.ts +20 -0
- package/dist/icon/icon-tile.js +55 -0
- package/dist/icon/icon-tile.js.map +1 -0
- package/dist/icon/icon.d.ts +14 -0
- package/dist/icon/icon.js +28 -0
- package/dist/icon/icon.js.map +1 -0
- package/dist/icon/icons.generated.d.ts +358 -0
- package/dist/icon/icons.generated.js +362 -0
- package/dist/icon/icons.generated.js.map +1 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +88 -0
- package/dist/input/extra-inputs.d.ts +122 -0
- package/dist/input/extra-inputs.js +351 -0
- package/dist/input/extra-inputs.js.map +1 -0
- package/dist/input/field-styles.d.ts +5 -0
- package/dist/input/field-styles.js +14 -0
- package/dist/input/field-styles.js.map +1 -0
- package/dist/input/input.d.ts +50 -0
- package/dist/input/input.js +107 -0
- package/dist/input/input.js.map +1 -0
- package/dist/layout/layout.d.ts +41 -0
- package/dist/layout/layout.js +90 -0
- package/dist/layout/layout.js.map +1 -0
- package/dist/lib/control-styles.d.ts +9 -0
- package/dist/lib/control-styles.js +16 -0
- package/dist/lib/control-styles.js.map +1 -0
- package/dist/lib/cx.d.ts +2 -0
- package/dist/lib/cx.js +8 -0
- package/dist/lib/cx.js.map +1 -0
- package/dist/lib/glyphs.d.ts +13 -0
- package/dist/lib/glyphs.js +67 -0
- package/dist/lib/glyphs.js.map +1 -0
- package/dist/lib/segmented-styles.d.ts +8 -0
- package/dist/lib/segmented-styles.js +12 -0
- package/dist/lib/segmented-styles.js.map +1 -0
- package/dist/lib/select-on-arrow.d.ts +7 -0
- package/dist/lib/select-on-arrow.js +21 -0
- package/dist/lib/select-on-arrow.js.map +1 -0
- package/dist/lib/skip-link.d.ts +5 -0
- package/dist/lib/skip-link.js +13 -0
- package/dist/lib/skip-link.js.map +1 -0
- package/dist/lib/use-media-query.d.ts +4 -0
- package/dist/lib/use-media-query.js +15 -0
- package/dist/lib/use-media-query.js.map +1 -0
- package/dist/lists/lists.d.ts +76 -0
- package/dist/lists/lists.js +133 -0
- package/dist/lists/lists.js.map +1 -0
- package/dist/locale-switch/locale-switch.d.ts +29 -0
- package/dist/locale-switch/locale-switch.js +62 -0
- package/dist/locale-switch/locale-switch.js.map +1 -0
- package/dist/logo/logo.d.ts +31 -0
- package/dist/logo/logo.js +157 -0
- package/dist/logo/logo.js.map +1 -0
- package/dist/logo/product-logo.d.ts +19 -0
- package/dist/logo/product-logo.js +46 -0
- package/dist/logo/product-logo.js.map +1 -0
- package/dist/marketing/marketing.d.ts +176 -0
- package/dist/marketing/marketing.js +451 -0
- package/dist/marketing/marketing.js.map +1 -0
- package/dist/marketing/newsletter-form.d.ts +21 -0
- package/dist/marketing/newsletter-form.js +100 -0
- package/dist/marketing/newsletter-form.js.map +1 -0
- package/dist/mascot/mascot.d.ts +28 -0
- package/dist/mascot/mascot.js +28 -0
- package/dist/mascot/mascot.js.map +1 -0
- package/dist/mascot/mascots.generated.d.ts +284 -0
- package/dist/mascot/mascots.generated.js +287 -0
- package/dist/mascot/mascots.generated.js.map +1 -0
- package/dist/master-detail/master-detail.d.ts +30 -0
- package/dist/master-detail/master-detail.js +71 -0
- package/dist/master-detail/master-detail.js.map +1 -0
- package/dist/menu/account-menu.d.ts +44 -0
- package/dist/menu/account-menu.js +78 -0
- package/dist/menu/account-menu.js.map +1 -0
- package/dist/menu/context-menu.d.ts +31 -0
- package/dist/menu/context-menu.js +111 -0
- package/dist/menu/context-menu.js.map +1 -0
- package/dist/menu/dropdown-menu.d.ts +49 -0
- package/dist/menu/dropdown-menu.js +152 -0
- package/dist/menu/dropdown-menu.js.map +1 -0
- package/dist/menu/menu-styles.d.ts +8 -0
- package/dist/menu/menu-styles.js +6 -0
- package/dist/menu/menu-styles.js.map +1 -0
- package/dist/menu/menubar.d.ts +31 -0
- package/dist/menu/menubar.js +125 -0
- package/dist/menu/menubar.js.map +1 -0
- package/dist/modal/modal.d.ts +34 -0
- package/dist/modal/modal.js +58 -0
- package/dist/modal/modal.js.map +1 -0
- package/dist/notification-center/notification-center.d.ts +51 -0
- package/dist/notification-center/notification-center.js +244 -0
- package/dist/notification-center/notification-center.js.map +1 -0
- package/dist/otp-input/otp-input.d.ts +23 -0
- package/dist/otp-input/otp-input.js +61 -0
- package/dist/otp-input/otp-input.js.map +1 -0
- package/dist/overlay/overlay.d.ts +53 -0
- package/dist/overlay/overlay.js +103 -0
- package/dist/overlay/overlay.js.map +1 -0
- package/dist/page-header/page-header.d.ts +55 -0
- package/dist/page-header/page-header.js +114 -0
- package/dist/page-header/page-header.js.map +1 -0
- package/dist/page-header/record-headers.d.ts +51 -0
- package/dist/page-header/record-headers.js +134 -0
- package/dist/page-header/record-headers.js.map +1 -0
- package/dist/page-header/section-header.d.ts +21 -0
- package/dist/page-header/section-header.js +44 -0
- package/dist/page-header/section-header.js.map +1 -0
- package/dist/page-navigation/page-navigation.d.ts +59 -0
- package/dist/page-navigation/page-navigation.js +136 -0
- package/dist/page-navigation/page-navigation.js.map +1 -0
- package/dist/pagination/pagination.d.ts +28 -0
- package/dist/pagination/pagination.js +102 -0
- package/dist/pagination/pagination.js.map +1 -0
- package/dist/phone-input/phone-input.d.ts +34 -0
- package/dist/phone-input/phone-input.js +171 -0
- package/dist/phone-input/phone-input.js.map +1 -0
- package/dist/popover/popover.d.ts +23 -0
- package/dist/popover/popover.js +40 -0
- package/dist/popover/popover.js.map +1 -0
- package/dist/progress/progress.d.ts +21 -0
- package/dist/progress/progress.js +49 -0
- package/dist/progress/progress.js.map +1 -0
- package/dist/segmented-control/segmented-control.d.ts +32 -0
- package/dist/segmented-control/segmented-control.js +29 -0
- package/dist/segmented-control/segmented-control.js.map +1 -0
- package/dist/select/select.d.ts +48 -0
- package/dist/select/select.js +136 -0
- package/dist/select/select.js.map +1 -0
- package/dist/settings/permission-matrix.d.ts +29 -0
- package/dist/settings/permission-matrix.js +101 -0
- package/dist/settings/permission-matrix.js.map +1 -0
- package/dist/settings/settings-row.d.ts +32 -0
- package/dist/settings/settings-row.js +63 -0
- package/dist/settings/settings-row.js.map +1 -0
- package/dist/side-nav/side-nav.d.ts +36 -0
- package/dist/side-nav/side-nav.js +123 -0
- package/dist/side-nav/side-nav.js.map +1 -0
- package/dist/site-header/site-header.d.ts +32 -0
- package/dist/site-header/site-header.js +99 -0
- package/dist/site-header/site-header.js.map +1 -0
- package/dist/skeleton/skeleton.d.ts +16 -0
- package/dist/skeleton/skeleton.js +19 -0
- package/dist/skeleton/skeleton.js.map +1 -0
- package/dist/slider/slider.d.ts +15 -0
- package/dist/slider/slider.js +48 -0
- package/dist/slider/slider.js.map +1 -0
- package/dist/spinner/spinner.d.ts +15 -0
- package/dist/spinner/spinner.js +42 -0
- package/dist/spinner/spinner.js.map +1 -0
- package/dist/stepper/step-dots.d.ts +16 -0
- package/dist/stepper/step-dots.js +26 -0
- package/dist/stepper/step-dots.js.map +1 -0
- package/dist/stepper/stepper.d.ts +26 -0
- package/dist/stepper/stepper.js +113 -0
- package/dist/stepper/stepper.js.map +1 -0
- package/dist/tabs/tabs.d.ts +31 -0
- package/dist/tabs/tabs.js +66 -0
- package/dist/tabs/tabs.js.map +1 -0
- package/dist/templates/app-templates.d.ts +126 -0
- package/dist/templates/app-templates.js +153 -0
- package/dist/templates/app-templates.js.map +1 -0
- package/dist/templates/site-templates.d.ts +49 -0
- package/dist/templates/site-templates.js +110 -0
- package/dist/templates/site-templates.js.map +1 -0
- package/dist/toast/toast-store.d.ts +54 -0
- package/dist/toast/toast-store.js +49 -0
- package/dist/toast/toast-store.js.map +1 -0
- package/dist/toast/toaster.d.ts +15 -0
- package/dist/toast/toaster.js +124 -0
- package/dist/toast/toaster.js.map +1 -0
- package/dist/tooltip/tooltip.d.ts +29 -0
- package/dist/tooltip/tooltip.js +54 -0
- package/dist/tooltip/tooltip.js.map +1 -0
- package/package.json +88 -3
- package/styles.css +521 -0
- package/README.md +0 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marketing.js","names":["_Fragment","inner","darkTone","tone","TONES","Section","rest","H","headingLevel","cx","spacing","className","eyebrow","title","description","centered","PageHero","compact","size","align","media","meta","actions","note","STAT_COLS","ProofBar","label","logos","stats","s","i","DoorCards","Link","doors","d","Icon","Glyph","ProcessSteps","steps","Testimonial","quote","Avatar","author","photo","role","company","Fragment","PricingTable","toolbar","plans","footnote","comparison","caption","planNames","n","row","v","j","Faq","json","items","Accordion","appearance","AccordionItem","AccordionTrigger","AccordionContent","structuredData","CtaBand","band","variant","mascot","Mascot","SiteFooter","brand","logo","tagline","social","navLabel","columns","c","l","locale","localeTitle","legal","aside","signOff","SignOff","children"],"sources":["../../src/marketing/marketing.tsx"],"sourcesContent":["import type { ElementType, HTMLAttributes, ReactNode } from 'react';\nimport { cx } from '../lib/cx';\nimport { Glyph } from '../lib/glyphs';\nimport { Icon, type IconName } from '../icon/icon';\nimport { Mascot, type MascotStandingState } from '../mascot/mascot';\nimport { Avatar } from '../data-display/avatar';\nimport { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '../accordion/accordion';\n\n/*\n * Server-safe sections: no state or handlers, so they render as Server Components in Next.js and ship no JS\n * of their own. `linkAs` accepts `next/link` straight from a Server Component. Interactive pieces are\n * client modules: `newsletter-form.tsx`, the Faq accordion and the Avatar used by Testimonial.\n * Don't re-export client modules from here: a re-export ships them to every page that uses a section.\n */\n\n/* Marketing sections for kodffe.com (wave 5). Layout rules: max width 1200px, 16px gutter on\n mobile, section spacing 96–160px desktop / 64–96px mobile, indigo and light sections alternate. */\n\nconst inner = 'mx-auto w-full max-w-[75rem] px-gutter';\n\nexport type SectionTone = 'default' | 'muted' | 'indigo' | 'brand';\n\n/** Indigo and brand sections switch to the dark token set (light text, mint links) for contrast. */\nconst darkTone = (tone: SectionTone) => (tone === 'indigo' || tone === 'brand' ? { 'data-theme': 'dark' } : {});\n\nconst TONES: Record<SectionTone, string> = {\n default: 'bg-bg text-fg',\n muted: 'bg-surface-muted text-fg',\n indigo: 'bg-primary-strong text-fg',\n /** Indigo with a mint glow: closing bands and highlights. */\n brand:\n 'bg-primary-strong bg-[radial-gradient(circle_at_85%_15%,color-mix(in_oklab,var(--kf-accent)_28%,transparent),transparent_55%)] text-fg',\n};\n\nexport interface SectionProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {\n tone?: SectionTone;\n /** Vertical spacing: `md` 64/96px, `lg` 96/160px. */\n spacing?: 'sm' | 'md' | 'lg';\n /** Small label above the title (\"How we work\"). */\n eyebrow?: ReactNode;\n title?: ReactNode;\n description?: ReactNode;\n /** Center the header (default left). */\n centered?: boolean;\n headingLevel?: 2 | 3;\n children?: ReactNode;\n}\n\n/** Page section with consistent spacing, width and an optional header. */\nexport function Section({\n tone = 'default',\n spacing = 'md',\n eyebrow,\n title,\n description,\n centered,\n headingLevel = 2,\n className,\n children,\n ...rest\n}: SectionProps) {\n const H = `h${headingLevel}` as const;\n return (\n <section\n {...darkTone(tone)}\n className={cx(\n 'font-sans',\n TONES[tone],\n spacing === 'sm' ? 'py-section-sm' : spacing === 'md' ? 'py-section-md' : 'py-section-lg',\n className,\n )}\n {...rest}\n >\n <div className={inner}>\n {(eyebrow || title || description) && (\n <header\n className={cx(\n 'kf-reveal mb-10 grid max-w-3xl gap-3 md:mb-14',\n centered && 'mx-auto justify-items-center text-center',\n )}\n >\n {eyebrow && (\n <p\n className={cx(\n 'm-0 text-sm font-bold tracking-wide uppercase',\n tone === 'default' || tone === 'muted' ? 'text-link' : 'text-accent',\n )}\n >\n {eyebrow}\n </p>\n )}\n {title && (\n <H className=\"m-0 font-display text-3xl font-extrabold tracking-tight text-balance md:text-5xl\">\n {title}\n </H>\n )}\n {description && <p className=\"m-0 max-w-2xl text-lg text-pretty text-fg-muted\">{description}</p>}\n </header>\n )}\n <div className=\"kf-reveal\">{children}</div>\n </div>\n </section>\n );\n}\n\n/* ---------------- PageHero ---------------- */\n\nexport interface PageHeroProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {\n eyebrow?: ReactNode;\n title: ReactNode;\n description?: ReactNode;\n /** Buttons; the primary action first. */\n actions?: ReactNode;\n /** Right column on desktop (illustration, product shot, interactive tool), below on mobile. */\n media?: ReactNode;\n /** Small line under the actions (\"No credit card · Cancel anytime\"). */\n note?: ReactNode;\n /** A line of facts under the title (\"Updated Nov 4, 2026 · 5 min read\"). */\n meta?: ReactNode;\n /** `default`: the home and landing hero. `compact`: less space and a smaller title (docs, legal, support pages). */\n size?: 'default' | 'compact';\n tone?: SectionTone;\n /** Use `h1` (default) once per page. */\n headingLevel?: 1 | 2;\n /**\n * Vertical alignment of the two columns. `start` keeps the headline still when the media grows\n * (an interactive tool that streams results); `center` (default) suits static media.\n */\n align?: 'center' | 'start';\n}\n\n/** Top of a page: one H1, one sentence, one primary action. */\nexport function PageHero({\n eyebrow,\n title,\n description,\n actions,\n media,\n note,\n meta,\n size = 'default',\n tone = 'default',\n headingLevel = 1,\n align = 'center',\n className,\n ...rest\n}: PageHeroProps) {\n const H = `h${headingLevel}` as const;\n const compact = size === 'compact';\n return (\n <section\n {...darkTone(tone)}\n className={cx('overflow-hidden font-sans', compact ? 'py-10 md:py-16' : 'py-16 md:py-28', TONES[tone], className)}\n {...rest}\n >\n <div\n className={cx(\n inner,\n 'grid gap-12',\n align === 'start' ? 'items-start' : 'items-center',\n !!media && 'md:grid-cols-[minmax(0,1.05fr)_minmax(0,1fr)] md:gap-16',\n )}\n >\n <div className=\"grid max-w-2xl gap-5\">\n {eyebrow && (\n <p\n className={cx(\n 'm-0 text-sm font-bold tracking-wide uppercase',\n tone === 'default' || tone === 'muted' ? 'text-link' : 'text-accent',\n )}\n >\n {eyebrow}\n </p>\n )}\n <H\n className={cx(\n 'm-0 font-display leading-[1.05] font-extrabold tracking-tight text-balance',\n compact ? 'text-3xl md:text-5xl' : 'text-4xl sm:text-5xl md:text-6xl',\n )}\n >\n {title}\n </H>\n {meta && <p className=\"m-0 text-sm text-fg-muted\">{meta}</p>}\n {description && <p className=\"m-0 max-w-xl text-lg text-pretty text-fg-muted md:text-xl\">{description}</p>}\n {actions && (\n <div className=\"mt-2 flex flex-col gap-3 *:w-full sm:flex-row sm:flex-wrap sm:*:w-auto\">{actions}</div>\n )}\n {note && <p className=\"m-0 text-sm text-fg-muted\">{note}</p>}\n </div>\n {media && <div className=\"min-w-0\">{media}</div>}\n </div>\n </section>\n );\n}\n\n/* ---------------- ProofBar ---------------- */\n\nexport interface ProofBarProps extends HTMLAttributes<HTMLElement> {\n /** Lead-in, e.g. \"Trusted by\" + logos or names. */\n label?: ReactNode;\n logos?: ReactNode;\n stats: { value: ReactNode; label: ReactNode }[];\n}\n\nconst STAT_COLS: Record<number, string> = { 2: 'sm:grid-cols-2', 3: 'sm:grid-cols-3', 4: 'sm:grid-cols-4' };\n\n/**\n * Instant trust under the hero: who trusts us and honest numbers. Without `label`/`logos` the stats span the\n * full width, one column per stat. Name clients or show logos only with their consent.\n */\nexport function ProofBar({ label, logos, stats, className, ...rest }: ProofBarProps) {\n return (\n <section aria-label=\"Proof\" className={cx('border-y border-border bg-surface py-8 font-sans', className)} {...rest}>\n <div className={cx(inner, 'flex flex-col items-center gap-8 md:flex-row md:justify-between')}>\n {(label || logos) && (\n <div className=\"flex flex-wrap items-center justify-center gap-x-6 gap-y-2 text-sm text-fg-muted\">\n {label && <span className=\"font-semibold\">{label}</span>}\n {logos}\n </div>\n )}\n <dl\n className={cx(\n 'm-0 grid w-full grid-cols-2 gap-x-6 gap-y-5 [&>*:last-child:nth-child(odd)]:col-span-2 sm:[&>*:last-child:nth-child(odd)]:col-span-1',\n STAT_COLS[stats.length] ?? 'sm:grid-cols-4',\n label || logos ? 'md:w-auto' : undefined,\n )}\n >\n {stats.map((s, i) => (\n <div key={i} className=\"grid justify-items-center gap-0.5 text-center md:justify-items-start md:text-left\">\n {/* <dt> before <dd> in the DOM (valid <dl>); the value is shown first. */}\n <dt className=\"text-sm text-fg-muted\">{s.label}</dt>\n <dd className=\"order-first m-0 font-display text-3xl font-extrabold tracking-tight text-fg tabular-nums\">\n {s.value}\n </dd>\n </div>\n ))}\n </dl>\n </div>\n </section>\n );\n}\n\n/* ---------------- DoorCards ---------------- */\n\nexport interface Door {\n title: ReactNode;\n description: ReactNode;\n href: string;\n icon: IconName;\n cta?: ReactNode;\n}\n\n/** Self-segmentation: the three ways to work with Kodffe (Products · Automation & AI · Teams). */\nexport function DoorCards({\n doors,\n linkAs: Link = 'a',\n className,\n}: {\n doors: Door[];\n linkAs?: ElementType;\n className?: string;\n}) {\n return (\n <ul className={cx('m-0 grid list-none gap-4 p-0 font-sans md:grid-cols-3', className)}>\n {doors.map((d, i) => (\n <li key={i}>\n <Link\n href={d.href}\n className=\"group relative flex h-full flex-col gap-4 rounded-xl border border-border bg-surface p-7 text-fg no-underline transition-[transform,box-shadow,border-color] duration-200 ease-standard hover:-translate-y-1 hover:border-primary hover:shadow-raised focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-focus motion-reduce:transition-none motion-reduce:hover:translate-y-0\"\n >\n <span className=\"grid size-14 place-items-center rounded-lg bg-gradient-brand text-on-primary\">\n <Icon name={d.icon} size={32} />\n </span>\n <span className=\"font-display text-2xl font-bold tracking-tight\">{d.title}</span>\n <span className=\"text-[15px] text-fg-muted\">{d.description}</span>\n <span className=\"mt-auto inline-flex items-center gap-1 pt-2 text-sm font-semibold text-link\">\n {d.cta ?? 'Explore'}\n <Glyph\n name=\"chevron-right\"\n size={16}\n className=\"transition-transform group-hover:translate-x-1 motion-reduce:transition-none\"\n />\n </span>\n </Link>\n </li>\n ))}\n </ul>\n );\n}\n\n/* ---------------- ProcessSteps ---------------- */\n\n/** Horizontal process (\"Audit → Sprint → Run & improve\"); stacks on mobile. */\nexport function ProcessSteps({\n steps,\n className,\n}: {\n steps: { title: ReactNode; description: ReactNode; meta?: ReactNode }[];\n className?: string;\n}) {\n return (\n <ol\n className={cx(\n 'm-0 grid list-none gap-6 p-0 font-sans md:grid-cols-[repeat(auto-fit,minmax(0,1fr))] md:gap-8',\n className,\n )}\n >\n {steps.map((s, i) => (\n <li key={i} className=\"relative grid content-start gap-3 md:pt-14\">\n <span\n aria-hidden=\"true\"\n className=\"hidden md:absolute md:inset-x-0 md:top-5 md:block md:h-0.5 md:bg-border md:first:left-5\"\n />\n <span className=\"relative z-10 grid size-10 place-items-center rounded-full bg-primary font-display text-lg font-extrabold text-on-primary md:absolute md:top-0\">\n {i + 1}\n </span>\n <h3 className=\"m-0 font-display text-xl font-bold tracking-tight\">{s.title}</h3>\n {s.meta && <p className=\"m-0 text-sm font-semibold text-link\">{s.meta}</p>}\n <p className=\"m-0 text-[15px] text-fg-muted\">{s.description}</p>\n </li>\n ))}\n </ol>\n );\n}\n\n/* ---------------- Testimonial ---------------- */\n\nexport interface TestimonialProps extends Omit<HTMLAttributes<HTMLElement>, 'role'> {\n quote: ReactNode;\n author: string;\n role: ReactNode;\n photo?: string;\n /** Company logo or name. */\n company?: ReactNode;\n size?: 'md' | 'lg';\n}\n\n/** Customer quote. Use real people only with their written consent. */\nexport function Testimonial({\n quote,\n author,\n role,\n photo,\n company,\n size = 'lg',\n className,\n ...rest\n}: TestimonialProps) {\n return (\n <figure className={cx('m-0 grid max-w-4xl gap-8 font-sans', className)} {...rest}>\n <span aria-hidden=\"true\" className=\"font-display text-7xl leading-[0.5] font-extrabold text-accent\">\n “\n </span>\n <blockquote\n className={cx(\n 'm-0 font-display font-semibold tracking-tight text-balance',\n size === 'lg' ? 'text-2xl leading-snug md:text-3xl' : 'text-xl leading-snug',\n )}\n >\n {quote}\n </blockquote>\n <figcaption className=\"flex items-center gap-4\">\n <Avatar name={author} src={photo} size=\"lg\" aria-hidden=\"true\" />\n <div className=\"grid\">\n <span className=\"font-semibold\">{author}</span>\n <span className=\"text-sm text-fg-muted\">\n {role}\n {company && <> · {company}</>}\n </span>\n </div>\n </figcaption>\n </figure>\n );\n}\n\n/* ---------------- PricingTable ---------------- */\n\nexport interface PricingTableProps {\n /** Usually `PlanCard`s. */\n plans: ReactNode[];\n /** Optional comparison rows: feature name + one cell per plan (true/false or text). */\n comparison?: { feature: ReactNode; values: (boolean | ReactNode)[] }[];\n planNames?: string[];\n /** Billing toggle (monthly/yearly) or currency switch above the plans. */\n toolbar?: ReactNode;\n /** Small print under the plans (\"Prices in USD, taxes not included.\"). */\n footnote?: ReactNode;\n caption?: string;\n className?: string;\n}\n\n/** Plans side by side, plus an optional feature comparison (scrolls horizontally on mobile). */\nexport function PricingTable({\n plans,\n comparison,\n planNames = [],\n toolbar,\n footnote,\n caption = 'Plan comparison',\n className,\n}: PricingTableProps) {\n return (\n <div className={cx('grid gap-10 font-sans', className)}>\n {toolbar && <div className=\"flex justify-center\">{toolbar}</div>}\n <div className=\"grid items-stretch gap-6 pt-3 md:grid-cols-[repeat(auto-fit,minmax(16rem,1fr))]\">{plans}</div>\n {footnote && <p className=\"m-0 -mt-4 text-center text-sm text-fg-muted\">{footnote}</p>}\n {comparison && (\n <div\n tabIndex={0}\n role=\"region\"\n aria-label={caption}\n className=\"overflow-x-auto rounded-lg border border-border focus-visible:outline-3 focus-visible:outline-focus\"\n >\n <table className=\"w-full min-w-[36rem] border-collapse text-sm\">\n <caption className=\"sr-only\">{caption}</caption>\n <thead className=\"bg-surface-muted\">\n <tr>\n <th scope=\"col\" className=\"px-4 py-3 text-left font-semibold text-fg-muted\">\n <span className=\"sr-only\">Feature</span>\n </th>\n {planNames.map((n) => (\n <th key={n} scope=\"col\" className=\"px-4 py-3 text-center font-semibold\">\n {n}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {comparison.map((row, i) => (\n <tr key={i} className=\"border-t border-border\">\n <th scope=\"row\" className=\"px-4 py-3 text-left font-medium\">\n {row.feature}\n </th>\n {row.values.map((v, j) => (\n <td key={j} className=\"px-4 py-3 text-center\">\n {v === true ? (\n <>\n <Glyph name=\"check\" size={18} className=\"inline text-success-icon\" />\n <span className=\"sr-only\">Included</span>\n </>\n ) : v === false ? (\n <>\n <Glyph name=\"minus\" size={18} className=\"inline text-fg-muted/60\" />\n <span className=\"sr-only\">Not included</span>\n </>\n ) : (\n v\n )}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </div>\n );\n}\n\n/* ---------------- Faq ---------------- */\n\nexport interface FaqProps {\n items: { question: string; answer: string }[];\n /** Adds FAQPage structured data (JSON-LD) for search results. */\n structuredData?: boolean;\n /** `divided` (default): rows split by lines. `separated`: each question in its own card. */\n appearance?: 'divided' | 'separated';\n className?: string;\n}\n\n/** Questions and answers with optional FAQPage JSON-LD for SEO. */\nexport function Faq({ items, structuredData = true, appearance = 'divided', className }: FaqProps) {\n const json = {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: items.map((i) => ({\n '@type': 'Question',\n name: i.question,\n acceptedAnswer: { '@type': 'Answer', text: i.answer },\n })),\n };\n return (\n <div className={cx('max-w-3xl', className)}>\n <Accordion type=\"single\" collapsible appearance={appearance === 'separated' ? 'card' : 'divided'}>\n {items.map((i) => (\n <AccordionItem key={i.question} value={i.question}>\n <AccordionTrigger size=\"lg\">{i.question}</AccordionTrigger>\n {/* forceMount: answers are in the server HTML (indexable, and they match the FAQPage JSON-LD). */}\n <AccordionContent forceMount size=\"lg\">\n {i.answer}\n </AccordionContent>\n </AccordionItem>\n ))}\n </Accordion>\n {structuredData && (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(json).replace(/</g, '\\\\u003c') }}\n />\n )}\n </div>\n );\n}\n\n/* ---------------- CtaBand ---------------- */\n\nexport interface CtaBandProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {\n title: ReactNode;\n description?: ReactNode;\n actions: ReactNode;\n /** A short label above the title (\"Ready when you are\"). */\n eyebrow?: ReactNode;\n /** Small print under the actions (\"Free 30-minute call · No commitment\"). */\n note?: ReactNode;\n mascot?: MascotStandingState | false;\n /** `band` (default): brand gradient, closes a page. `card`: an outlined card for calls to action inside content. */\n variant?: 'band' | 'card';\n}\n\n/** Closing call to action on brand gradient, with the mascot; or an outlined card inside content. */\nexport function CtaBand({ title, description, actions, eyebrow, note, mascot = 'happy', variant = 'band', className, ...rest }: CtaBandProps) {\n const band = variant === 'band';\n return (\n <section className={cx('font-sans', band && 'px-gutter py-section-md', className)} {...rest}>\n <div\n {...(band ? { 'data-theme': 'dark' } : {})}\n className={cx(\n band ? cx(TONES.brand, 'rounded-2xl px-6 py-12 md:px-14 md:py-16') : 'rounded-xl border border-border bg-surface p-6 md:p-8',\n 'mx-auto grid max-w-[75rem] items-center gap-8 overflow-hidden md:grid-cols-[auto_1fr_auto]',\n )}\n >\n {mascot && <Mascot state={mascot} size={band ? 88 : 64} title=\"\" className={cx('hidden md:block', band ? 'text-accent' : 'text-primary')} />}\n <div className=\"grid gap-3\">\n {eyebrow && <p className={cx('m-0 text-sm font-bold tracking-wide uppercase', band ? 'text-accent' : 'text-link')}>{eyebrow}</p>}\n <h2 className={cx('m-0 font-display font-extrabold tracking-tight text-balance', band ? 'text-3xl md:text-4xl' : 'text-2xl md:text-3xl')}>\n {title}\n </h2>\n {description && <p className=\"m-0 max-w-xl text-lg text-fg-muted\">{description}</p>}\n </div>\n <div className=\"grid gap-2\">\n <div className=\"flex flex-col gap-3 *:w-full sm:flex-row sm:*:w-auto md:flex-col\">{actions}</div>\n {note && <p className=\"m-0 text-center text-sm text-fg-muted\">{note}</p>}\n </div>\n </div>\n </section>\n );\n}\n\n/* ---------------- SiteFooter + SignOff ---------------- */\n\nexport interface FooterColumn {\n title: string;\n links: { label: string; href: string; external?: boolean }[];\n}\n\nexport interface SiteFooterProps {\n logo: ReactNode;\n tagline?: ReactNode;\n columns: FooterColumn[];\n /** Social links (icon buttons with `aria-label`). */\n social?: ReactNode;\n /** Locale switch or contact line. */\n aside?: ReactNode;\n legal: ReactNode;\n linkAs?: ElementType;\n /** Shows the oversized sign-off slogan after the footer. */\n signOff?: ReactNode;\n /** Accessible name of the sitemap navigation (localize it). */\n navLabel?: string;\n /** A language switch shown as its own column, after the sitemap (e.g. `LocaleSwitch variant=\"select\"`). */\n locale?: ReactNode;\n /** Its column title (\"Language\"). */\n localeTitle?: string;\n /** `default`: the page background. `brand`: a dark brand footer with accent column titles (product sites). */\n tone?: 'default' | 'brand';\n className?: string;\n}\n\n/** Site footer: brand, sitemap columns, social, legal, and the sign-off slogan. */\nexport function SiteFooter({\n logo,\n tagline,\n columns,\n social,\n aside,\n legal,\n linkAs: Link = 'a',\n signOff,\n navLabel = 'Footer',\n locale,\n localeTitle = 'Language',\n tone = 'default',\n className,\n}: SiteFooterProps) {\n const brand = tone === 'brand';\n return (\n <footer\n // The brand footer is dark in both themes: the dark token set keeps text and links AA.\n {...(brand ? { 'data-theme': 'dark' } : {})}\n className={cx('pt-16 font-sans text-fg md:pt-24', brand ? 'bg-primary-strong' : 'bg-bg', className)}\n >\n <div className={cx(inner, 'grid gap-12 md:grid-cols-[minmax(0,1.3fr)_minmax(0,3fr)]')}>\n <div className=\"grid content-start gap-4\">\n <div className={cx('text-3xl', brand ? 'text-fg' : 'text-logo')}>{logo}</div>\n {tagline && <p className=\"m-0 max-w-xs text-fg-muted\">{tagline}</p>}\n {social && <div className=\"flex gap-1\">{social}</div>}\n </div>\n <nav aria-label={navLabel} className=\"grid grid-cols-2 gap-8 sm:grid-cols-3 md:grid-cols-4\">\n {columns.map((c) => (\n <div key={c.title} className=\"grid content-start gap-3\">\n <h2 className={cx('m-0 text-sm font-bold', brand && 'text-accent')}>{c.title}</h2>\n <ul className=\"m-0 grid list-none gap-2.5 p-0\">\n {c.links.map((l) => (\n <li key={l.href}>\n <Link\n href={l.href}\n {...(l.external ? { target: '_blank', rel: 'noopener noreferrer' } : {})}\n className=\"rounded-xs text-sm text-fg-muted no-underline hover:text-fg hover:underline focus-visible:outline-3 focus-visible:outline-focus\"\n >\n {l.label}\n </Link>\n </li>\n ))}\n </ul>\n </div>\n ))}\n {locale && (\n <div className=\"grid content-start gap-3\">\n <h2 className={cx('m-0 text-sm font-bold', brand && 'text-accent')}>{localeTitle}</h2>\n <div>{locale}</div>\n </div>\n )}\n </nav>\n </div>\n <div\n className={cx(\n inner,\n 'mt-14 flex flex-col gap-4 border-t border-border py-6 text-sm text-fg-muted sm:flex-row sm:items-center sm:justify-between',\n )}\n >\n <div>{legal}</div>\n {aside}\n </div>\n {signOff && <SignOff>{signOff}</SignOff>}\n </footer>\n );\n}\n\n/**\n * Oversized sign-off (\"Made with code and coffee in Ecuador.\") as the last thing on the page:\n * Manrope 800, ~200px space above (120px on mobile), mint period.\n */\nexport function SignOff({\n children = 'Made with code and coffee in Ecuador',\n className,\n}: {\n children?: ReactNode;\n className?: string;\n}) {\n return (\n <p\n className={cx(\n 'm-0 overflow-hidden px-4 pt-[120px] pb-6 text-center font-display text-[clamp(2.25rem,9vw,8.5rem)] leading-[0.95] font-extrabold tracking-tighter text-balance text-fg md:pt-[200px] md:pb-10',\n className,\n )}\n >\n {children}\n <span className=\"text-accent\">.</span>\n </p>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export interface NewsletterFormProps {
|
|
3
|
+
/** Subscribe the email (e.g. Brevo via your API route). Throw to show an error. */
|
|
4
|
+
onSubscribe: (email: string) => Promise<void>;
|
|
5
|
+
title?: ReactNode;
|
|
6
|
+
description?: ReactNode;
|
|
7
|
+
labels?: Partial<typeof NEWS_LABELS>;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
declare const NEWS_LABELS: {
|
|
11
|
+
email: string;
|
|
12
|
+
placeholder: string;
|
|
13
|
+
submit: string;
|
|
14
|
+
success: string;
|
|
15
|
+
invalid: string;
|
|
16
|
+
error: string;
|
|
17
|
+
privacy: string;
|
|
18
|
+
};
|
|
19
|
+
/** Email capture with inline validation and success/error states. */
|
|
20
|
+
export declare function NewsletterForm({ onSubscribe, title, description, labels: overrides, className }: NewsletterFormProps): import("react").JSX.Element;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cx as e } from "../lib/cx.js";
|
|
3
|
+
import { Mascot as t } from "../mascot/mascot.js";
|
|
4
|
+
import { Button as n } from "../button/button.js";
|
|
5
|
+
import { Input as r } from "../input/input.js";
|
|
6
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
7
|
+
import { useId as o, useState as s } from "react";
|
|
8
|
+
//#region src/marketing/newsletter-form.tsx
|
|
9
|
+
var c = {
|
|
10
|
+
email: "Work email",
|
|
11
|
+
placeholder: "you@company.com",
|
|
12
|
+
submit: "Subscribe",
|
|
13
|
+
success: "You’re in. Check your inbox to confirm.",
|
|
14
|
+
invalid: "Enter a valid email, like you@company.com",
|
|
15
|
+
error: "We could not subscribe you. Try again.",
|
|
16
|
+
privacy: "One email a month. Unsubscribe anytime."
|
|
17
|
+
};
|
|
18
|
+
function l({ onSubscribe: l, title: u, description: d, labels: f, className: p }) {
|
|
19
|
+
let m = {
|
|
20
|
+
...c,
|
|
21
|
+
...f
|
|
22
|
+
}, [h, g] = s(""), [_, v] = s("idle"), y = o(), b = async (e) => {
|
|
23
|
+
if (e.preventDefault(), !/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(h)) return v("invalid");
|
|
24
|
+
v("loading");
|
|
25
|
+
try {
|
|
26
|
+
await l(h), v("success");
|
|
27
|
+
} catch {
|
|
28
|
+
v("error");
|
|
29
|
+
}
|
|
30
|
+
}, x = _ === "invalid" ? m.invalid : _ === "error" ? m.error : void 0;
|
|
31
|
+
return /* @__PURE__ */ a("div", {
|
|
32
|
+
className: e("grid max-w-xl gap-4 font-sans", p),
|
|
33
|
+
children: [
|
|
34
|
+
u && /* @__PURE__ */ i("h3", {
|
|
35
|
+
className: "m-0 font-display text-2xl font-bold tracking-tight",
|
|
36
|
+
children: u
|
|
37
|
+
}),
|
|
38
|
+
d && /* @__PURE__ */ i("p", {
|
|
39
|
+
className: "m-0 text-fg-muted",
|
|
40
|
+
children: d
|
|
41
|
+
}),
|
|
42
|
+
_ === "success" ? /* @__PURE__ */ a("p", {
|
|
43
|
+
role: "status",
|
|
44
|
+
className: "m-0 flex items-center gap-3 rounded-md bg-success-surface px-4 py-3 font-medium text-success-fg",
|
|
45
|
+
children: [/* @__PURE__ */ i(t, {
|
|
46
|
+
state: "success",
|
|
47
|
+
size: 40,
|
|
48
|
+
title: "",
|
|
49
|
+
className: "text-success-icon"
|
|
50
|
+
}), m.success]
|
|
51
|
+
}) : /* @__PURE__ */ a("form", {
|
|
52
|
+
onSubmit: b,
|
|
53
|
+
noValidate: !0,
|
|
54
|
+
className: "grid gap-2",
|
|
55
|
+
children: [
|
|
56
|
+
/* @__PURE__ */ i("label", {
|
|
57
|
+
htmlFor: y,
|
|
58
|
+
className: "sr-only",
|
|
59
|
+
children: m.email
|
|
60
|
+
}),
|
|
61
|
+
/* @__PURE__ */ a("div", {
|
|
62
|
+
className: "flex flex-col gap-2 sm:flex-row",
|
|
63
|
+
children: [/* @__PURE__ */ i(r, {
|
|
64
|
+
id: y,
|
|
65
|
+
type: "email",
|
|
66
|
+
autoComplete: "email",
|
|
67
|
+
placeholder: m.placeholder,
|
|
68
|
+
value: h,
|
|
69
|
+
onChange: (e) => {
|
|
70
|
+
g(e.target.value), (_ === "invalid" || _ === "error") && v("idle");
|
|
71
|
+
},
|
|
72
|
+
invalid: !!x,
|
|
73
|
+
"aria-describedby": x ? `${y}-msg` : `${y}-note`,
|
|
74
|
+
className: "flex-1"
|
|
75
|
+
}), /* @__PURE__ */ i(n, {
|
|
76
|
+
type: "submit",
|
|
77
|
+
variant: "accent",
|
|
78
|
+
loading: _ === "loading",
|
|
79
|
+
children: m.submit
|
|
80
|
+
})]
|
|
81
|
+
}),
|
|
82
|
+
x ? /* @__PURE__ */ i("p", {
|
|
83
|
+
id: `${y}-msg`,
|
|
84
|
+
role: "alert",
|
|
85
|
+
className: "m-0 text-sm font-medium text-danger-fg",
|
|
86
|
+
children: x
|
|
87
|
+
}) : /* @__PURE__ */ i("p", {
|
|
88
|
+
id: `${y}-note`,
|
|
89
|
+
className: "m-0 text-sm text-fg-muted",
|
|
90
|
+
children: m.privacy
|
|
91
|
+
})
|
|
92
|
+
]
|
|
93
|
+
})
|
|
94
|
+
]
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
export { l as NewsletterForm };
|
|
99
|
+
|
|
100
|
+
//# sourceMappingURL=newsletter-form.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"newsletter-form.js","names":["NEWS_LABELS","NewsletterForm","overrides","labels","email","setEmail","useState","state","setState","id","useId","submit","onSubscribe","message","cx","className","title","description","Mascot","Input","Button"],"sources":["../../src/marketing/newsletter-form.tsx"],"sourcesContent":["'use client';\n\nimport { useId, useState, type FormEvent, type ReactNode } from 'react';\nimport { cx } from '../lib/cx';\nimport { Mascot } from '../mascot/mascot';\nimport { Button } from '../button/button';\nimport { Input } from '../input/input';\n\n/* ---------------- NewsletterForm ---------------- */\n\nexport interface NewsletterFormProps {\n /** Subscribe the email (e.g. Brevo via your API route). Throw to show an error. */\n onSubscribe: (email: string) => Promise<void>;\n title?: ReactNode;\n description?: ReactNode;\n labels?: Partial<typeof NEWS_LABELS>;\n className?: string;\n}\n\nconst NEWS_LABELS = {\n email: 'Work email',\n placeholder: 'you@company.com',\n submit: 'Subscribe',\n success: 'You’re in. Check your inbox to confirm.',\n invalid: 'Enter a valid email, like you@company.com',\n error: 'We could not subscribe you. Try again.',\n privacy: 'One email a month. Unsubscribe anytime.',\n};\n\n/** Email capture with inline validation and success/error states. */\nexport function NewsletterForm({ onSubscribe, title, description, labels: overrides, className }: NewsletterFormProps) {\n const labels = { ...NEWS_LABELS, ...overrides };\n const [email, setEmail] = useState('');\n const [state, setState] = useState<'idle' | 'loading' | 'success' | 'error' | 'invalid'>('idle');\n const id = useId();\n const submit = async (e: FormEvent) => {\n e.preventDefault();\n if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]{2,}$/.test(email)) return setState('invalid');\n setState('loading');\n try {\n await onSubscribe(email);\n setState('success');\n } catch {\n setState('error');\n }\n };\n const message = state === 'invalid' ? labels.invalid : state === 'error' ? labels.error : undefined;\n return (\n <div className={cx('grid max-w-xl gap-4 font-sans', className)}>\n {title && <h3 className=\"m-0 font-display text-2xl font-bold tracking-tight\">{title}</h3>}\n {description && <p className=\"m-0 text-fg-muted\">{description}</p>}\n {state === 'success' ? (\n <p\n role=\"status\"\n className=\"m-0 flex items-center gap-3 rounded-md bg-success-surface px-4 py-3 font-medium text-success-fg\"\n >\n <Mascot state=\"success\" size={40} title=\"\" className=\"text-success-icon\" />\n {labels.success}\n </p>\n ) : (\n <form onSubmit={submit} noValidate className=\"grid gap-2\">\n <label htmlFor={id} className=\"sr-only\">\n {labels.email}\n </label>\n <div className=\"flex flex-col gap-2 sm:flex-row\">\n <Input\n id={id}\n type=\"email\"\n autoComplete=\"email\"\n placeholder={labels.placeholder}\n value={email}\n onChange={(e) => {\n setEmail(e.target.value);\n if (state === 'invalid' || state === 'error') setState('idle');\n }}\n invalid={!!message}\n aria-describedby={message ? `${id}-msg` : `${id}-note`}\n className=\"flex-1\"\n />\n <Button type=\"submit\" variant=\"accent\" loading={state === 'loading'}>\n {labels.submit}\n </Button>\n </div>\n {message ? (\n <p id={`${id}-msg`} role=\"alert\" className=\"m-0 text-sm font-medium text-danger-fg\">\n {message}\n </p>\n ) : (\n <p id={`${id}-note`} className=\"m-0 text-sm text-fg-muted\">\n {labels.privacy}\n </p>\n )}\n </form>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;AAmBA,IAAMA,IAAc;CAClB,OAAO;CACP,aAAa;CACb,QAAQ;CACR,SAAS;CACT,SAAS;CACT,OAAO;CACP,SAAS;AACX;AAGA,SAAgBC,EAAe,EAAE,gBAAa,UAAO,gBAAa,QAAQC,GAAW,gBAAkC;CACrH,IAAMC,IAAS;EAAE,GAAGH;EAAa,GAAGE;CAAU,GACxC,CAACE,GAAOC,KAAYC,EAAS,EAAE,GAC/B,CAACC,GAAOC,KAAYF,EAA+D,MAAM,GACzFG,IAAKC,EAAM,GACXC,IAAS,OAAO,MAAiB;EAErC,IADA,EAAE,eAAe,GACb,CAAC,gCAAgC,KAAKP,CAAK,GAAG,OAAOI,EAAS,SAAS;EAC3E,EAAS,SAAS;EAClB,IAAI;GAEF,AADA,MAAMI,EAAYR,CAAK,GACvBI,EAAS,SAAS;EACpB,QAAQ;GACN,EAAS,OAAO;EAClB;CACF,GACMK,IAAUN,MAAU,YAAYJ,EAAO,UAAUI,MAAU,UAAUJ,EAAO,QAAQ,KAAA;CAC1F,OACE,kBAAC,OAAD;EAAK,WAAWW,EAAG,iCAAiCC,CAAS;EAA7D,UAAA;GACGC,KAAS,kBAAC,MAAD;IAAI,WAAU;IAAsD,UAAAA;GAAU,CAAA;GACvFC,KAAe,kBAAC,KAAD;IAAG,WAAU;IAAqB,UAAAA;GAAe,CAAA;GAChEV,MAAU,YACT,kBAAC,KAAD;IACE,MAAK;IACL,WAAU;IAFZ,UAAA,CAIE,kBAACW,GAAD;KAAQ,OAAM;KAAU,MAAM;KAAI,OAAM;KAAG,WAAU;IAAqB,CAAA,GACzEf,EAAO,OACP;GAEH,CAAA,IAAA,kBAAC,QAAD;IAAM,UAAUQ;IAAQ,YAAA;IAAW,WAAU;IAA7C,UAAA;KACE,kBAAC,SAAD;MAAO,SAASF;MAAI,WAAU;MAC3B,UAAAN,EAAO;KACH,CAAA;KACP,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAACgB,GAAD;OACM;OACJ,MAAK;OACL,cAAa;OACb,aAAahB,EAAO;OACpB,OAAOC;OACP,WAAW,MAAM;QAEf,AADAC,EAAS,EAAE,OAAO,KAAK,IACnBE,MAAU,aAAaA,MAAU,YAASC,EAAS,MAAM;OAC/D;OACA,SAAS,CAAC,CAACK;OACX,oBAAkBA,IAAU,GAAGJ,EAAG,QAAQ,GAAGA,EAAG;OAChD,WAAU;MACX,CAAA,GACD,kBAACW,GAAD;OAAQ,MAAK;OAAS,SAAQ;OAAS,SAASb,MAAU;OACvD,UAAAJ,EAAO;MACF,CAAA,CACL;;KACJU,IACC,kBAAC,KAAD;MAAG,IAAI,GAAGJ,EAAG;MAAO,MAAK;MAAQ,WAAU;MACxC,UAAAI;KACA,CAAA,IAEH,kBAAC,KAAD;MAAG,IAAI,GAAGJ,EAAG;MAAQ,WAAU;MAC5B,UAAAN,EAAO;KACP,CAAA;IAED;;EAEL;;AAET"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { SVGProps } from 'react';
|
|
2
|
+
import { type MascotLyingState, type MascotStandingState } from './mascots.generated';
|
|
3
|
+
export type { MascotLyingState, MascotStandingState };
|
|
4
|
+
export declare const mascotStandingStates: MascotStandingState[];
|
|
5
|
+
export declare const mascotLyingStates: MascotLyingState[];
|
|
6
|
+
interface BaseProps extends Omit<SVGProps<SVGSVGElement>, 'children' | 'dangerouslySetInnerHTML'> {
|
|
7
|
+
/** Height in px or any CSS length. Defaults to 1em. Mascots need 40 px or more; use `Spinner` for small places. */
|
|
8
|
+
size?: number | string;
|
|
9
|
+
/** Play the animation. Always off with `prefers-reduced-motion`. Turn it off only where motion distracts. */
|
|
10
|
+
animated?: boolean;
|
|
11
|
+
/** Accessible name. Defaults to the state's name; pass `""` when nearby text already says it. */
|
|
12
|
+
title?: string;
|
|
13
|
+
}
|
|
14
|
+
export type MascotProps = BaseProps & ({
|
|
15
|
+
/** `standing` (default): the face with the U mouth, for emotions and feedback. */
|
|
16
|
+
pose?: 'standing';
|
|
17
|
+
state?: MascotStandingState;
|
|
18
|
+
} | {
|
|
19
|
+
/** `lying`: the mark lying down (reads OD), for motion and flow next to text. */
|
|
20
|
+
pose: 'lying';
|
|
21
|
+
state: MascotLyingState;
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* The Kodffe mascot (ADR-0015): the logo's eye and open D, always animated. One geometry, generated by
|
|
25
|
+
* docs/design/icons/generate.py, also published as `@kodffe/tokens/icons/kf-v-*.svg` and `kf-h-*.svg`.
|
|
26
|
+
* Use it at 40 px or more and never inside buttons.
|
|
27
|
+
*/
|
|
28
|
+
export declare function Mascot({ pose, state, size, animated, title, className, style, ...rest }: MascotProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { cx as e } from "../lib/cx.js";
|
|
2
|
+
import { MASCOTS as t } from "./mascots.generated.js";
|
|
3
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
4
|
+
//#region src/mascot/mascot.tsx
|
|
5
|
+
var r = Object.keys(t.standing), i = Object.keys(t.lying);
|
|
6
|
+
function a({ pose: r = "standing", state: i, size: a = "1em", animated: o = !0, title: s, className: c, style: l, ...u }) {
|
|
7
|
+
let d = t[r], f = i && d[i] || d[r === "lying" ? "od" : "idle"], p = s ?? f.label, m = p ? {
|
|
8
|
+
role: "img",
|
|
9
|
+
"aria-label": p
|
|
10
|
+
} : { "aria-hidden": !0 };
|
|
11
|
+
return /* @__PURE__ */ n("svg", {
|
|
12
|
+
viewBox: f.viewBox,
|
|
13
|
+
focusable: "false",
|
|
14
|
+
className: e("kf-mascot inline-block shrink-0", !o && "kf-mascot--still", c),
|
|
15
|
+
style: {
|
|
16
|
+
height: a,
|
|
17
|
+
width: "auto",
|
|
18
|
+
...l
|
|
19
|
+
},
|
|
20
|
+
...m,
|
|
21
|
+
...u,
|
|
22
|
+
dangerouslySetInnerHTML: { __html: `<style>${f.style}</style>${f.body}` }
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { a as Mascot, i as mascotLyingStates, r as mascotStandingStates };
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=mascot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mascot.js","names":["mascotStandingStates","MASCOTS","mascotLyingStates","Mascot","rest","set","pose","m","state","label","title","a11y","cx","animated","className","size","style"],"sources":["../../src/mascot/mascot.tsx"],"sourcesContent":["import type { SVGProps } from 'react';\nimport { cx } from '../lib/cx';\nimport { MASCOTS, type MascotLyingState, type MascotStandingState } from './mascots.generated';\n\nexport type { MascotLyingState, MascotStandingState };\nexport const mascotStandingStates = Object.keys(MASCOTS.standing) as MascotStandingState[];\nexport const mascotLyingStates = Object.keys(MASCOTS.lying) as MascotLyingState[];\n\ninterface BaseProps extends Omit<SVGProps<SVGSVGElement>, 'children' | 'dangerouslySetInnerHTML'> {\n /** Height in px or any CSS length. Defaults to 1em. Mascots need 40 px or more; use `Spinner` for small places. */\n size?: number | string;\n /** Play the animation. Always off with `prefers-reduced-motion`. Turn it off only where motion distracts. */\n animated?: boolean;\n /** Accessible name. Defaults to the state's name; pass `\"\"` when nearby text already says it. */\n title?: string;\n}\n\nexport type MascotProps = BaseProps &\n (\n | {\n /** `standing` (default): the face with the U mouth, for emotions and feedback. */\n pose?: 'standing';\n state?: MascotStandingState;\n }\n | {\n /** `lying`: the mark lying down (reads OD), for motion and flow next to text. */\n pose: 'lying';\n state: MascotLyingState;\n }\n );\n\n/**\n * The Kodffe mascot (ADR-0015): the logo's eye and open D, always animated. One geometry, generated by\n * docs/design/icons/generate.py, also published as `@kodffe/tokens/icons/kf-v-*.svg` and `kf-h-*.svg`.\n * Use it at 40 px or more and never inside buttons.\n */\nexport function Mascot({\n pose = 'standing',\n state,\n size = '1em',\n animated = true,\n title,\n className,\n style,\n ...rest\n}: MascotProps) {\n const set = MASCOTS[pose] as Record<string, { viewBox: string; label: string; style: string; body: string }>;\n // Both poses have the same states; an unknown state (e.g. from untyped content) falls back to the pose's default.\n const m = (state && set[state]) || set[pose === 'lying' ? 'od' : 'idle']!;\n const label = title ?? m.label;\n const a11y = label ? { role: 'img', 'aria-label': label } : { 'aria-hidden': true as const };\n return (\n <svg\n viewBox={m.viewBox}\n focusable=\"false\"\n className={cx('kf-mascot inline-block shrink-0', !animated && 'kf-mascot--still', className)}\n style={{ height: size, width: 'auto', ...style }}\n {...a11y}\n {...rest}\n // Trusted, build-time markup from our own generator (no user input).\n dangerouslySetInnerHTML={{ __html: `<style>${m.style}</style>${m.body}` }}\n />\n );\n}\n"],"mappings":";;;;AAKA,IAAaA,IAAuB,OAAO,KAAKC,EAAQ,QAAQ,GACnDC,IAAoB,OAAO,KAAKD,EAAQ,KAAK;AA8B1D,SAAgBE,EAAO,EACrB,UAAO,YACP,UACA,UAAO,OACP,cAAW,IACX,UACA,cACA,UACA,GAAGC,KACW;CACd,IAAMC,IAAMJ,EAAQK,IAEdC,IAAKC,KAASH,EAAIG,MAAWH,EAAIC,MAAS,UAAU,OAAO,SAC3DG,IAAQC,KAASH,EAAE,OACnBI,IAAOF,IAAQ;EAAE,MAAM;EAAO,cAAcA;CAAM,IAAI,EAAE,eAAe,GAAc;CAC3F,OACE,kBAAC,OAAD;EACE,SAASF,EAAE;EACX,WAAU;EACV,WAAWK,EAAG,mCAAmC,CAACC,KAAY,oBAAoBC,CAAS;EAC3F,OAAO;GAAE,QAAQC;GAAM,OAAO;GAAQ,GAAGC;EAAM;EAC/C,GAAIL;EACJ,GAAIP;EAEJ,yBAAyB,EAAE,QAAQ,UAAUG,EAAE,MAAM,UAAUA,EAAE,OAAO;CACzE,CAAA;AAEL"}
|