@patientos/website-kit 0.1.0

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.
@@ -0,0 +1,63 @@
1
+ /** sRGB channels, 0–255. */
2
+ interface Rgb {
3
+ r: number;
4
+ g: number;
5
+ b: number;
6
+ }
7
+ /**
8
+ * Parse `#rgb` / `#rrggbb` (case-insensitive, `#` optional). Anything else — a named
9
+ * colour, `rgb()`, an 8-digit hex with alpha, junk — returns null, so the caller falls
10
+ * back rather than guessing at a colour it cannot reason about.
11
+ */
12
+ declare function parseHexColor(hex: string): Rgb | null;
13
+ /**
14
+ * WCAG 2.x relative luminance: linearise each sRGB channel, then weight by the
15
+ * coefficients in the spec (§ "relative luminance").
16
+ */
17
+ declare function relativeLuminance(rgb: Rgb): number;
18
+ /**
19
+ * The WCAG contrast ratio between two colours, 1 (identical) … 21 (black on white).
20
+ * An unparseable colour yields 1 — "no contrast", which fails every threshold, so a bad
21
+ * input can never be mistaken for a passing one.
22
+ */
23
+ declare function contrastRatio(a: string, b: string): number;
24
+ /** White, and the app's near-black ink — the only two foregrounds we will emit. */
25
+ declare const WHITE = "#ffffff";
26
+ declare const INK = "#1b2733";
27
+ /**
28
+ * The foreground to put ON a clinic brand colour: whichever of white / near-black has the
29
+ * higher contrast against it.
30
+ *
31
+ * Two candidates, not a spectrum: these are the two the app's own surfaces already use,
32
+ * so a themed island still looks like the product. An unparseable background falls back
33
+ * to the ink (the safer default on the light surfaces these islands live on).
34
+ */
35
+ declare function deriveForeground(bg: string): typeof WHITE | typeof INK;
36
+ /** A brand background together with a foreground that is legible ON it. */
37
+ interface PrimaryPair {
38
+ bg: string;
39
+ fg: typeof WHITE | typeof INK;
40
+ }
41
+ /**
42
+ * The background + foreground for a brand surface that CARRIES TEXT — the flow's primary
43
+ * CTA, the one control a patient must find on every screen.
44
+ *
45
+ * `deriveForeground` picks the better of two candidates but never checks that it clears
46
+ * anything, and mid-luminance brands — most clinic pinks, magentas, teals and oranges —
47
+ * fail BOTH: white on #e92095 is 4.11:1, on #d946ef 4.39:1, under a 4.5 floor. So here the
48
+ * BACKGROUND moves too, until the pair passes.
49
+ *
50
+ * The walk only ever DARKENS, and pins white. Lightening would satisfy the ink candidate,
51
+ * but this same token is the ink on the light card (`--sk-flow-accent-ink`, the step dots,
52
+ * the booking link) where a pastel fails in turn, and a washed-out CTA reads as disabled.
53
+ * Only mid-luminance brands can fail both candidates, so there is always headroom below —
54
+ * over the whole sRGB cube the walk converges in at most 5 steps. Scaling every channel by
55
+ * ONE factor holds the R:G:B ratio, so the hue is unchanged (≤0.2° of byte rounding) and
56
+ * #e92095 comes back as the clinic's pink, two steps darker.
57
+ *
58
+ * A brand that already clears the floor is returned EXACTLY as it came in — this must not
59
+ * move a single colour that was never broken.
60
+ */
61
+ declare function derivePrimaryPair(brand: string): PrimaryPair;
62
+
63
+ export { type PrimaryPair, type Rgb, contrastRatio, deriveForeground, derivePrimaryPair, parseHexColor, relativeLuminance };
@@ -0,0 +1,15 @@
1
+ import {
2
+ contrastRatio,
3
+ deriveForeground,
4
+ derivePrimaryPair,
5
+ parseHexColor,
6
+ relativeLuminance
7
+ } from "./chunk-LCHW6XF4.js";
8
+ import "./chunk-MLKGABMK.js";
9
+ export {
10
+ contrastRatio,
11
+ deriveForeground,
12
+ derivePrimaryPair,
13
+ parseHexColor,
14
+ relativeLuminance
15
+ };
@@ -0,0 +1,189 @@
1
+ export { BPOINT_SCRIPT_ORIGIN, BPOINT_SCRIPT_URL, CONSULT_CHUNK_PATH, THIRD_PARTY_SCRIPT_ORIGINS, TURNSTILE_SCRIPT_ORIGIN, TURNSTILE_SCRIPT_URL } from './script-sources.js';
2
+ import * as React from 'react';
3
+ export { ISLAND_ATTR, ISLAND_PROPS_ATTR, Island, IslandProps, IslandRegistry, mountIslandsWith, readIslandProps } from './islands.js';
4
+ export { ISLANDS, IslandName, mountIslands } from './islands-registry.js';
5
+ export { B as BookingBlock, a as BookingBlockMarkerProps, b as BookingBlockProps, c as BookingTypeOption, C as Cart, d as CartMarkerProps, e as CartProps, f as CertificateFunnel, g as CertificateFunnelMarkerProps, h as CertificateFunnelProps, i as Checkout, j as CheckoutMarkerProps, k as CheckoutProps, F as FunnelPublicApi, l as FunnelServiceOption, S as Store, m as StoreMarkerProps, n as StoreProps } from './checkout-block-CrO7OxI9.js';
6
+ export { P as PortalAccount, a as PortalAccountMarkerProps, b as PortalAccountProps, c as PortalAccountSurface, d as PortalPanel, e as PortalPanelMarkerProps, f as PortalPanelProps, g as PortalSurface } from './portal-account-EhOtLV5u.js';
7
+ export { PatientSurfaceTheme, patientSurfaceTheme } from './patient-surface-theme.js';
8
+ export { Rgb, contrastRatio, deriveForeground, parseHexColor, relativeLuminance } from './contrast.js';
9
+ export { AnswerValue, Answers, ShowWhenCondition, evaluateShowWhen, visibleQuestions } from './show-when.js';
10
+
11
+ /**
12
+ * Theme tokens for the website-kit.
13
+ *
14
+ * This kit is intentionally INDEPENDENT of the dashboard's Tailwind/shadcn
15
+ * theme (design decision D2). Tokens are plain CSS strings (colours / lengths)
16
+ * and are projected onto `--sk-*` custom properties at runtime. No Tailwind,
17
+ * no shadcn, no `app.css`.
18
+ */
19
+ type WebsiteTheme = {
20
+ /** Primary brand colour (buttons, links, accents). */
21
+ brand: string;
22
+ /**
23
+ * Text/icon colour that sits on top of `brand`. **DERIVED, never supplied** —
24
+ * `resolveTheme` always recomputes it from `brand` by WCAG contrast
25
+ * (`deriveForeground`) and IGNORES any value passed in. Trusting a supplied
26
+ * foreground is how you ship white text on a pale-yellow brand; the token bag is
27
+ * written by the marketing seat (in practice an AI or an agency) with no reviewer
28
+ * in the loop, and these islands render medical surfaces. The field stays on the
29
+ * type because it is what `themeToCssVars` emits and what the CSS reads.
30
+ */
31
+ brandForeground: string;
32
+ /** Neutral surface colour (cards, bordered blocks). */
33
+ neutral: string;
34
+ /** Text colour that sits on top of `neutral`. */
35
+ neutralForeground: string;
36
+ /** Secondary accent colour. */
37
+ accent: string;
38
+ /** Page background. */
39
+ background: string;
40
+ /** Default body text colour. */
41
+ foreground: string;
42
+ /** Muted / secondary text colour. */
43
+ muted: string;
44
+ /** Body font stack. */
45
+ fontFamily: string;
46
+ /** Heading font stack. */
47
+ fontFamilyHeading: string;
48
+ /** Base body font size (rem/px string). */
49
+ fontSizeBase: string;
50
+ /** Small / caption size. */
51
+ fontSizeSm: string;
52
+ /** Large lead size. */
53
+ fontSizeLg: string;
54
+ /** Section heading size. */
55
+ fontSizeH2: string;
56
+ /** Hero heading size. */
57
+ fontSizeH1: string;
58
+ /** Base spacing unit (length string); the grid is multiples of this. */
59
+ spacingUnit: string;
60
+ /** Corner radius for cards/buttons. */
61
+ radius: string;
62
+ };
63
+ declare const defaultTheme: WebsiteTheme;
64
+ /**
65
+ * Maps each theme key to its `--sk-*` custom property. The short aliases
66
+ * (`--sk-font`, `--sk-font-heading`) are what the compiled CSS reads.
67
+ */
68
+ declare function themeToCssVars(theme: WebsiteTheme): Record<string, string>;
69
+ /**
70
+ * Merge a partial theme over the defaults, then DERIVE `brandForeground` from the
71
+ * resolved `brand` (PAT-767).
72
+ *
73
+ * The derivation is unconditional: a supplied `brandForeground` is accepted by the
74
+ * type and then discarded. A clinic can choose its brand colour; it cannot choose
75
+ * unreadable text on top of it. An unparseable brand falls back to the app's ink
76
+ * (`deriveForeground`), so the result is always a real, legible colour.
77
+ */
78
+ declare function resolveTheme(theme?: Partial<WebsiteTheme>): WebsiteTheme;
79
+ type ThemeProviderProps = {
80
+ theme?: Partial<WebsiteTheme>;
81
+ children?: React.ReactNode;
82
+ } & Omit<React.HTMLAttributes<HTMLDivElement>, 'children' | 'style'>;
83
+ /**
84
+ * Wraps children in a `[data-sk-theme]` root that carries the `--sk-*` tokens
85
+ * as inline CSS vars. Pair with the compiled `website-kit.css` for styling.
86
+ */
87
+ declare function ThemeProvider({ theme, children, className, ...rest }: ThemeProviderProps): React.ReactElement;
88
+
89
+ type ContainerProps = React.HTMLAttributes<HTMLDivElement>;
90
+ /** Centered, max-width content column with horizontal gutters. */
91
+ declare function Container({ className, children, ...rest }: ContainerProps): React.ReactElement;
92
+ type RowProps = React.HTMLAttributes<HTMLDivElement>;
93
+ /** Horizontal flex row; wraps and gaps on the spacing unit. */
94
+ declare function Row({ className, children, ...rest }: RowProps): React.ReactElement;
95
+ type StackProps = React.HTMLAttributes<HTMLDivElement>;
96
+ /** Vertical flex stack; gaps on the spacing unit. */
97
+ declare function Stack({ className, children, ...rest }: StackProps): React.ReactElement;
98
+ type WebsiteShellProps = {
99
+ theme?: Partial<WebsiteTheme>;
100
+ title?: string;
101
+ children?: React.ReactNode;
102
+ };
103
+ /**
104
+ * The ROOT layout for a generated clinic website. It emits its OWN minimal HTML
105
+ * document — it does NOT import or reference the dashboard shell or `app.css`
106
+ * (design decision D11). The kit's compiled stylesheet is loaded by `<link>`,
107
+ * theme tokens are injected as `:root` CSS vars, and a hydration entry script
108
+ * is referenced. Paths (`/website-kit.css`, `/main.js`) are what the isolate's
109
+ * bundler serves.
110
+ */
111
+ declare function WebsiteShell({ theme, title, children, }: WebsiteShellProps): React.ReactElement;
112
+
113
+ type ButtonVariant = 'solid' | 'outline' | 'ghost';
114
+ type ButtonProps = {
115
+ href?: string;
116
+ variant?: ButtonVariant;
117
+ children?: React.ReactNode;
118
+ } & React.ButtonHTMLAttributes<HTMLButtonElement> & Pick<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'target' | 'rel'>;
119
+ /**
120
+ * Renders an `<a>` when `href` is set, otherwise a `<button>`. Styled by the
121
+ * `.sk-button` classes; pass `variant` to switch the visual treatment.
122
+ */
123
+ declare function Button({ href, variant, className, children, target, rel, ...rest }: ButtonProps): React.ReactElement;
124
+ type HeroProps = {
125
+ heading: string;
126
+ subheading?: string;
127
+ /** Background image URL only — no upload/file handling (v1 non-goal). */
128
+ image?: string;
129
+ /** Optional call-to-action rendered below the copy. */
130
+ cta?: React.ReactNode;
131
+ } & Omit<React.HTMLAttributes<HTMLElement>, 'children'>;
132
+ declare function Hero({ heading, subheading, image, cta, className, style, ...rest }: HeroProps): React.ReactElement;
133
+ type SectionProps = {
134
+ title?: string;
135
+ /** Optional intro copy below the title. */
136
+ intro?: string;
137
+ children?: React.ReactNode;
138
+ } & React.HTMLAttributes<HTMLElement>;
139
+ declare function Section({ title, intro, children, className, ...rest }: SectionProps): React.ReactElement;
140
+ type Service = {
141
+ title: string;
142
+ description: string;
143
+ /** Icon URL only — no upload/file handling (v1 non-goal). */
144
+ icon?: string;
145
+ };
146
+ type ServiceGridProps = {
147
+ services: Service[];
148
+ } & React.HTMLAttributes<HTMLUListElement>;
149
+ declare function ServiceGrid({ services, className, ...rest }: ServiceGridProps): React.ReactElement;
150
+ type TeamMember = {
151
+ name: string;
152
+ role: string;
153
+ /** Photo URL only — no upload/file handling (v1 non-goal). */
154
+ photo?: string;
155
+ };
156
+ type TeamGridProps = {
157
+ members: TeamMember[];
158
+ } & React.HTMLAttributes<HTMLUListElement>;
159
+ declare function TeamGrid({ members, className, ...rest }: TeamGridProps): React.ReactElement;
160
+ type HoursRow = {
161
+ day: string;
162
+ hours: string;
163
+ };
164
+ type HoursProps = {
165
+ rows: HoursRow[];
166
+ } & React.HTMLAttributes<HTMLDListElement>;
167
+ declare function Hours({ rows, className, ...rest }: HoursProps): React.ReactElement;
168
+ type MapEmbedProps = {
169
+ /** A full embed URL. Mutually exclusive with `address`. */
170
+ src?: string;
171
+ /** A street address; turned into a Google Maps embed query. */
172
+ address?: string;
173
+ /** CSS height (length string). */
174
+ height?: string;
175
+ title?: string;
176
+ } & Omit<React.HTMLAttributes<HTMLDivElement>, 'title'>;
177
+ declare function MapEmbed({ src, address, height, title, className, style, ...rest }: MapEmbedProps): React.ReactElement;
178
+ type FaqItem = {
179
+ q: string;
180
+ a: string;
181
+ };
182
+ type FAQProps = {
183
+ items: FaqItem[];
184
+ } & React.HTMLAttributes<HTMLDivElement>;
185
+ declare function FAQ({ items, className, ...rest }: FAQProps): React.ReactElement;
186
+
187
+ declare const WEBSITE_KIT_VERSION = "0.1.0";
188
+
189
+ export { Button, type ButtonProps, type ButtonVariant, Container, type ContainerProps, FAQ, type FAQProps, type FaqItem, Hero, type HeroProps, Hours, type HoursProps, type HoursRow, MapEmbed, type MapEmbedProps, Row, type RowProps, Section, type SectionProps, type Service, ServiceGrid, type ServiceGridProps, Stack, type StackProps, TeamGrid, type TeamGridProps, type TeamMember, ThemeProvider, type ThemeProviderProps, WEBSITE_KIT_VERSION, WebsiteShell, type WebsiteShellProps, type WebsiteTheme, defaultTheme, resolveTheme, themeToCssVars };