@classic-homes/theme-svelte 0.1.5 → 0.1.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 (27) hide show
  1. package/dist/lib/components/CardFooter.svelte +20 -2
  2. package/dist/lib/components/CardFooter.svelte.d.ts +20 -2
  3. package/dist/lib/components/CardHeader.svelte +22 -2
  4. package/dist/lib/components/CardHeader.svelte.d.ts +5 -4
  5. package/dist/lib/components/HeaderSearch.svelte +340 -0
  6. package/dist/lib/components/HeaderSearch.svelte.d.ts +37 -0
  7. package/dist/lib/components/PageHeader.svelte +6 -0
  8. package/dist/lib/components/PageHeader.svelte.d.ts +1 -1
  9. package/dist/lib/components/layout/AuthLayout.svelte +133 -0
  10. package/dist/lib/components/layout/AuthLayout.svelte.d.ts +48 -0
  11. package/dist/lib/components/layout/DashboardLayout.svelte +39 -60
  12. package/dist/lib/components/layout/DashboardLayout.svelte.d.ts +6 -1
  13. package/dist/lib/components/layout/ErrorLayout.svelte +206 -0
  14. package/dist/lib/components/layout/ErrorLayout.svelte.d.ts +52 -0
  15. package/dist/lib/components/layout/Footer.svelte +58 -53
  16. package/dist/lib/components/layout/FormPageLayout.svelte +2 -8
  17. package/dist/lib/components/layout/Header.svelte +232 -41
  18. package/dist/lib/components/layout/Header.svelte.d.ts +71 -5
  19. package/dist/lib/components/layout/PublicLayout.svelte +54 -80
  20. package/dist/lib/components/layout/PublicLayout.svelte.d.ts +3 -1
  21. package/dist/lib/components/layout/sidebar/SidebarNavItem.svelte +16 -7
  22. package/dist/lib/components/layout/sidebar/SidebarSection.svelte +4 -4
  23. package/dist/lib/index.d.ts +4 -1
  24. package/dist/lib/index.js +3 -0
  25. package/dist/lib/schemas/common.d.ts +2 -2
  26. package/dist/lib/types/layout.d.ts +187 -1
  27. package/package.json +1 -1
@@ -0,0 +1,48 @@
1
+ /**
2
+ * AuthLayout - Layout for authentication pages
3
+ *
4
+ * Features:
5
+ * - Centered card layout for auth forms
6
+ * - Logo with optional subtitle
7
+ * - Footer links (privacy, terms, etc.)
8
+ * - Optional background decoration
9
+ * - Responsive design
10
+ * - Composes AppShell for consistent base structure
11
+ *
12
+ * Use this layout for: login, signup, password reset, forgot password,
13
+ * email verification, 2FA, and other authentication flows.
14
+ */
15
+ import type { Snippet } from 'svelte';
16
+ interface FooterLink {
17
+ /** Link label */
18
+ label: string;
19
+ /** Link URL */
20
+ href: string;
21
+ /** Opens in new tab */
22
+ external?: boolean;
23
+ }
24
+ interface Props {
25
+ /** Custom logo snippet */
26
+ logo?: Snippet;
27
+ /** Logo subtitle for default logo (e.g., "Sign in to your account") */
28
+ logoSubtitle?: string;
29
+ /** Logo environment indicator for default logo */
30
+ logoEnvironment?: 'local' | 'dev' | 'demo';
31
+ /** Footer links (privacy policy, terms, etc.) */
32
+ footerLinks?: FooterLink[];
33
+ /** Custom footer content (replaces footer links) */
34
+ footer?: Snippet;
35
+ /** Show decorative background */
36
+ showBackground?: boolean;
37
+ /** Background variant */
38
+ backgroundVariant?: 'default' | 'gradient' | 'pattern';
39
+ /** Maximum width of content card */
40
+ maxWidth?: 'sm' | 'md' | 'lg';
41
+ /** Additional classes for the container */
42
+ class?: string;
43
+ /** Main content (auth form) */
44
+ children: Snippet;
45
+ }
46
+ declare const AuthLayout: import("svelte").Component<Props, {}, "">;
47
+ type AuthLayout = ReturnType<typeof AuthLayout>;
48
+ export default AuthLayout;
@@ -11,11 +11,28 @@
11
11
  * - Integrated with sidebar store for state management
12
12
  */
13
13
  import type { Snippet } from 'svelte';
14
- import type { NavSection, NavItem, User, QuickLink, BackLink } from '../../types/layout.js';
15
- import { cn } from '../../utils.js';
14
+ import type {
15
+ NavSection,
16
+ NavItem,
17
+ User,
18
+ QuickLink,
19
+ BackLink,
20
+ HeaderSearchConfig,
21
+ } from '../../types/layout.js';
16
22
  import { sidebarStore } from '../../stores/sidebar.svelte.js';
17
23
  import AppShell from './AppShell.svelte';
18
24
  import Sidebar from './Sidebar.svelte';
25
+ import Header from './Header.svelte';
26
+
27
+ type Breakpoint = 'sm' | 'md' | 'lg';
28
+
29
+ // Breakpoint pixel values (max-width for mobile detection)
30
+ // These match Tailwind's breakpoint - 1 (e.g., lg starts at 1024px, so mobile is <= 1023px)
31
+ const breakpointPixels: Record<Breakpoint, number> = {
32
+ sm: 639,
33
+ md: 767,
34
+ lg: 1023,
35
+ };
19
36
 
20
37
  interface Props {
21
38
  /** Navigation sections for sidebar */
@@ -62,6 +79,10 @@
62
79
  searchable?: boolean;
63
80
  /** Placeholder text for search input */
64
81
  searchPlaceholder?: string;
82
+ /** Header search configuration */
83
+ headerSearch?: HeaderSearchConfig;
84
+ /** Breakpoint at which to switch between mobile and desktop layouts */
85
+ mobileBreakpoint?: Breakpoint;
65
86
  /** Main content */
66
87
  children: Snippet;
67
88
  }
@@ -89,6 +110,8 @@
89
110
  collapsedWidth = 64,
90
111
  searchable = false,
91
112
  searchPlaceholder = 'Search...',
113
+ headerSearch,
114
+ mobileBreakpoint = 'lg',
92
115
  children,
93
116
  }: Props = $props();
94
117
 
@@ -117,7 +140,7 @@
117
140
 
118
141
  $effect(() => {
119
142
  if (typeof window !== 'undefined') {
120
- const mediaQuery = window.matchMedia('(max-width: 1023px)');
143
+ const mediaQuery = window.matchMedia(`(max-width: ${breakpointPixels[mobileBreakpoint]}px)`);
121
144
  isMobile = mediaQuery.matches;
122
145
  sidebarStore.setMobile(isMobile);
123
146
 
@@ -205,62 +228,18 @@
205
228
  style="margin-left: {sidebarWidth}px;"
206
229
  >
207
230
  {#if showHeader}
208
- <header
209
- class="sticky top-0 z-30 flex h-16 items-center justify-between border-b border-black bg-background px-4 lg:px-6"
231
+ <Header
232
+ showMenuButton={isMobile}
233
+ menuOpen={sidebarStore.isOpen}
234
+ onMenuClick={() => sidebarStore.toggle()}
235
+ showCollapseButton={!isMobile}
236
+ sidebarCollapsed={!sidebarStore.isOpen}
237
+ onCollapseClick={() => sidebarStore.toggle()}
238
+ title={pageTitle}
239
+ start={headerStart}
240
+ search={headerSearch}
210
241
  >
211
- <div class="flex items-center gap-4">
212
- <!-- Mobile Menu Button -->
213
- {#if isMobile}
214
- <button
215
- class="rounded-md p-2 hover:bg-accent hover:text-accent-foreground lg:hidden"
216
- onclick={() => sidebarStore.toggle()}
217
- aria-label="Open menu"
218
- >
219
- <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
220
- <path
221
- stroke-linecap="round"
222
- stroke-linejoin="round"
223
- stroke-width="2"
224
- d="M4 6h16M4 12h16M4 18h16"
225
- />
226
- </svg>
227
- </button>
228
- {:else}
229
- <!-- Collapse Toggle Button (Desktop) -->
230
- <button
231
- class="rounded-md p-2 hover:bg-accent hover:text-accent-foreground"
232
- onclick={() => sidebarStore.toggle()}
233
- aria-label={sidebarStore.isOpen ? 'Collapse sidebar' : 'Expand sidebar'}
234
- >
235
- <svg
236
- class={cn('h-5 w-5 transition-transform', !sidebarStore.isOpen && 'rotate-180')}
237
- fill="none"
238
- viewBox="0 0 24 24"
239
- stroke="currentColor"
240
- >
241
- <path
242
- stroke-linecap="round"
243
- stroke-linejoin="round"
244
- stroke-width="2"
245
- d="M11 19l-7-7 7-7m8 14l-7-7 7-7"
246
- />
247
- </svg>
248
- </button>
249
- {/if}
250
-
251
- <!-- Page Title -->
252
- {#if pageTitle}
253
- <h2 class="text-lg font-semibold">{pageTitle}</h2>
254
- {/if}
255
-
256
- <!-- Custom Header Start -->
257
- {#if headerStart}
258
- {@render headerStart()}
259
- {/if}
260
- </div>
261
-
262
- <div class="flex items-center gap-4">
263
- <!-- Custom Header End -->
242
+ {#snippet end()}
264
243
  {#if headerEnd}
265
244
  {@render headerEnd()}
266
245
  {:else if user}
@@ -283,8 +262,8 @@
283
262
  </div>
284
263
  {/if}
285
264
  {/if}
286
- </div>
287
- </header>
265
+ {/snippet}
266
+ </Header>
288
267
  {/if}
289
268
 
290
269
  <!-- Main Content -->
@@ -10,7 +10,8 @@
10
10
  * - Integrated with sidebar store for state management
11
11
  */
12
12
  import type { Snippet } from 'svelte';
13
- import type { NavSection, NavItem, User, QuickLink, BackLink } from '../../types/layout.js';
13
+ import type { NavSection, NavItem, User, QuickLink, BackLink, HeaderSearchConfig } from '../../types/layout.js';
14
+ type Breakpoint = 'sm' | 'md' | 'lg';
14
15
  interface Props {
15
16
  /** Navigation sections for sidebar */
16
17
  navigation: NavSection[];
@@ -56,6 +57,10 @@ interface Props {
56
57
  searchable?: boolean;
57
58
  /** Placeholder text for search input */
58
59
  searchPlaceholder?: string;
60
+ /** Header search configuration */
61
+ headerSearch?: HeaderSearchConfig;
62
+ /** Breakpoint at which to switch between mobile and desktop layouts */
63
+ mobileBreakpoint?: Breakpoint;
59
64
  /** Main content */
60
65
  children: Snippet;
61
66
  }
@@ -0,0 +1,206 @@
1
+ <script lang="ts">
2
+ /**
3
+ * ErrorLayout - Layout for error pages
4
+ *
5
+ * Features:
6
+ * - Centered error message display
7
+ * - Large status code or custom illustration
8
+ * - Configurable title and description
9
+ * - Default messages for common HTTP status codes
10
+ * - Action buttons (Go Back, Go Home)
11
+ * - Optional logo and footer
12
+ * - Responsive design
13
+ * - Composes AppShell for consistent base structure
14
+ *
15
+ * Use this layout for: 404 Not Found, 500 Server Error, 403 Forbidden,
16
+ * maintenance pages, and other error states.
17
+ */
18
+ import type { Snippet } from 'svelte';
19
+ import { cn } from '../../utils.js';
20
+ import AppShell from './AppShell.svelte';
21
+ import LogoMain from '../LogoMain.svelte';
22
+ import Button from '../Button.svelte';
23
+
24
+ interface Props {
25
+ /** HTTP status code (404, 500, etc.) */
26
+ statusCode?: number;
27
+ /** Custom title (overrides default for status code) */
28
+ title?: string;
29
+ /** Error description/message */
30
+ description?: string;
31
+ /** Show logo at top */
32
+ showLogo?: boolean;
33
+ /** Custom logo snippet */
34
+ logo?: Snippet;
35
+ /** Show "Go Home" button */
36
+ showHomeButton?: boolean;
37
+ /** Home button URL */
38
+ homeUrl?: string;
39
+ /** Home button text */
40
+ homeText?: string;
41
+ /** Show "Go Back" button */
42
+ showBackButton?: boolean;
43
+ /** Go back button text */
44
+ backText?: string;
45
+ /** Custom illustration/icon snippet (replaces status code display) */
46
+ illustration?: Snippet;
47
+ /** Additional action buttons or content */
48
+ actions?: Snippet;
49
+ /** Footer content */
50
+ footer?: Snippet;
51
+ /** Additional classes for the container */
52
+ class?: string;
53
+ /** Main content (custom error content, replaces default display) */
54
+ children?: Snippet;
55
+ }
56
+
57
+ let {
58
+ statusCode,
59
+ title,
60
+ description,
61
+ showLogo = true,
62
+ logo,
63
+ showHomeButton = true,
64
+ homeUrl = '/',
65
+ homeText = 'Go Home',
66
+ showBackButton = true,
67
+ backText = 'Go Back',
68
+ illustration,
69
+ actions,
70
+ footer,
71
+ class: className,
72
+ children,
73
+ }: Props = $props();
74
+
75
+ // Default messages by status code
76
+ const defaultMessages: Record<number, { title: string; description: string }> = {
77
+ 400: {
78
+ title: 'Bad Request',
79
+ description: 'The request could not be understood. Please check your input and try again.',
80
+ },
81
+ 401: {
82
+ title: 'Unauthorized',
83
+ description: 'Please sign in to access this page.',
84
+ },
85
+ 403: {
86
+ title: 'Access Denied',
87
+ description: "You don't have permission to access this page.",
88
+ },
89
+ 404: {
90
+ title: 'Page Not Found',
91
+ description: "The page you're looking for doesn't exist or has been moved.",
92
+ },
93
+ 500: {
94
+ title: 'Server Error',
95
+ description: 'Something went wrong on our end. Please try again later.',
96
+ },
97
+ 502: {
98
+ title: 'Bad Gateway',
99
+ description: 'We received an invalid response from the server. Please try again later.',
100
+ },
101
+ 503: {
102
+ title: 'Service Unavailable',
103
+ description: "We're temporarily offline for maintenance. Please check back soon.",
104
+ },
105
+ 504: {
106
+ title: 'Gateway Timeout',
107
+ description: 'The server took too long to respond. Please try again later.',
108
+ },
109
+ };
110
+
111
+ const effectiveTitle = $derived(
112
+ title || (statusCode && defaultMessages[statusCode]?.title) || 'Something Went Wrong'
113
+ );
114
+
115
+ const effectiveDescription = $derived(
116
+ description ||
117
+ (statusCode && defaultMessages[statusCode]?.description) ||
118
+ 'An unexpected error occurred. Please try again.'
119
+ );
120
+
121
+ function goBack() {
122
+ if (typeof window !== 'undefined') {
123
+ window.history.back();
124
+ }
125
+ }
126
+ </script>
127
+
128
+ <AppShell>
129
+ <div
130
+ class={cn(
131
+ 'flex h-screen flex-col items-center justify-center overflow-auto bg-content-bg px-4 py-8 sm:px-6 lg:px-8',
132
+ className
133
+ )}
134
+ >
135
+ <!-- Logo -->
136
+ {#if showLogo}
137
+ <div class="mb-6">
138
+ {#if logo}
139
+ {@render logo()}
140
+ {:else}
141
+ <a href="/" class="block">
142
+ <LogoMain variant="horizontal" color="dark" size="md" />
143
+ </a>
144
+ {/if}
145
+ </div>
146
+ {/if}
147
+
148
+ <!-- Main Content -->
149
+ <main id="main-content" class="w-full max-w-md text-center">
150
+ {#if children}
151
+ {@render children()}
152
+ {:else}
153
+ <!-- Status Code or Illustration -->
154
+ {#if illustration}
155
+ <div class="mb-6">
156
+ {@render illustration()}
157
+ </div>
158
+ {:else if statusCode}
159
+ <div class="mb-6">
160
+ <span class="text-8xl font-bold text-muted-foreground/30 sm:text-9xl">
161
+ {statusCode}
162
+ </span>
163
+ </div>
164
+ {/if}
165
+
166
+ <!-- Title -->
167
+ <h1 class="mb-4 text-2xl font-bold text-foreground sm:text-3xl">
168
+ {effectiveTitle}
169
+ </h1>
170
+
171
+ <!-- Description -->
172
+ <p class="mb-8 text-muted-foreground">
173
+ {effectiveDescription}
174
+ </p>
175
+
176
+ <!-- Action Buttons -->
177
+ <div class="flex flex-col items-center justify-center gap-3 sm:flex-row">
178
+ {#if showBackButton}
179
+ <Button variant="outline" onclick={goBack}>
180
+ {backText}
181
+ </Button>
182
+ {/if}
183
+ {#if showHomeButton}
184
+ <Button href={homeUrl}>
185
+ {homeText}
186
+ </Button>
187
+ {/if}
188
+ </div>
189
+
190
+ <!-- Additional Actions -->
191
+ {#if actions}
192
+ <div class="mt-6">
193
+ {@render actions()}
194
+ </div>
195
+ {/if}
196
+ {/if}
197
+ </main>
198
+
199
+ <!-- Footer -->
200
+ {#if footer}
201
+ <footer class="mt-8">
202
+ {@render footer()}
203
+ </footer>
204
+ {/if}
205
+ </div>
206
+ </AppShell>
@@ -0,0 +1,52 @@
1
+ /**
2
+ * ErrorLayout - Layout for error pages
3
+ *
4
+ * Features:
5
+ * - Centered error message display
6
+ * - Large status code or custom illustration
7
+ * - Configurable title and description
8
+ * - Default messages for common HTTP status codes
9
+ * - Action buttons (Go Back, Go Home)
10
+ * - Optional logo and footer
11
+ * - Responsive design
12
+ * - Composes AppShell for consistent base structure
13
+ *
14
+ * Use this layout for: 404 Not Found, 500 Server Error, 403 Forbidden,
15
+ * maintenance pages, and other error states.
16
+ */
17
+ import type { Snippet } from 'svelte';
18
+ interface Props {
19
+ /** HTTP status code (404, 500, etc.) */
20
+ statusCode?: number;
21
+ /** Custom title (overrides default for status code) */
22
+ title?: string;
23
+ /** Error description/message */
24
+ description?: string;
25
+ /** Show logo at top */
26
+ showLogo?: boolean;
27
+ /** Custom logo snippet */
28
+ logo?: Snippet;
29
+ /** Show "Go Home" button */
30
+ showHomeButton?: boolean;
31
+ /** Home button URL */
32
+ homeUrl?: string;
33
+ /** Home button text */
34
+ homeText?: string;
35
+ /** Show "Go Back" button */
36
+ showBackButton?: boolean;
37
+ /** Go back button text */
38
+ backText?: string;
39
+ /** Custom illustration/icon snippet (replaces status code display) */
40
+ illustration?: Snippet;
41
+ /** Additional action buttons or content */
42
+ actions?: Snippet;
43
+ /** Footer content */
44
+ footer?: Snippet;
45
+ /** Additional classes for the container */
46
+ class?: string;
47
+ /** Main content (custom error content, replaces default display) */
48
+ children?: Snippet;
49
+ }
50
+ declare const ErrorLayout: import("svelte").Component<Props, {}, "">;
51
+ type ErrorLayout = ReturnType<typeof ErrorLayout>;
52
+ export default ErrorLayout;
@@ -42,72 +42,77 @@
42
42
  class: className,
43
43
  }: Props = $props();
44
44
 
45
- const currentYear = new Date().getFullYear();
46
- const defaultCopyright = `${currentYear} Classic Homes. All rights reserved.`;
47
-
48
45
  const isDark = $derived(variant === 'dark');
46
+ const displayCopyright = $derived(
47
+ copyright || `${new Date().getFullYear()} Classic Homes. All rights reserved.`
48
+ );
49
49
  </script>
50
50
 
51
51
  <footer
52
+ aria-label="Site footer"
52
53
  class={cn(
53
54
  // Base styles
54
- isDark ? 'bg-[#50504f] text-white' : 'bg-muted/30',
55
+ isDark ? 'bg-gray-600 text-white' : 'bg-muted/30',
55
56
  // Border styles - strong accent border or standard
56
- strongBorder ? 'border-t-[10px] border-[#787878]' : 'border-t border-black',
57
+ strongBorder ? 'border-t-[10px] border-gray-500' : 'border-t border-border',
57
58
  className
58
59
  )}
59
60
  >
60
61
  <div class="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
61
62
  {#if links.length > 0}
62
- <div class="grid grid-cols-2 gap-8 md:grid-cols-4">
63
- {#each links as section}
64
- <div>
65
- {#if section.title}
66
- <h3 class={cn('text-lg font-bold mb-4', isDark ? 'text-white' : 'text-foreground')}>
67
- {section.title}
68
- </h3>
69
- {/if}
70
- <ul class="mt-4 space-y-2">
71
- {#each section.items as item}
72
- <li>
73
- <a
74
- href={item.href}
75
- class={cn(
76
- 'text-sm transition-colors',
77
- isDark
78
- ? 'text-white hover:text-primary'
79
- : 'text-muted-foreground hover:text-foreground'
80
- )}
81
- target={item.external ? '_blank' : undefined}
82
- rel={item.external ? 'noopener noreferrer' : undefined}
83
- >
84
- {item.name}
85
- {#if item.external}
86
- <svg
87
- class="ml-1 inline-block h-3 w-3"
88
- fill="none"
89
- viewBox="0 0 24 24"
90
- stroke="currentColor"
91
- >
92
- <path
93
- stroke-linecap="round"
94
- stroke-linejoin="round"
95
- stroke-width="2"
96
- d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
97
- />
98
- </svg>
99
- {/if}
100
- </a>
101
- </li>
102
- {/each}
103
- </ul>
104
- </div>
105
- {/each}
106
- </div>
63
+ <nav aria-label="Footer navigation">
64
+ <div class="grid grid-cols-2 gap-6 sm:grid-cols-3 md:gap-8 lg:grid-cols-4">
65
+ {#each links as section}
66
+ <div>
67
+ {#if section.title}
68
+ <h3 class={cn('text-lg font-bold mb-4', isDark ? 'text-white' : 'text-foreground')}>
69
+ {section.title}
70
+ </h3>
71
+ {/if}
72
+ <ul class="mt-4 space-y-2">
73
+ {#each section.items as item}
74
+ <li>
75
+ <a
76
+ href={item.href}
77
+ class={cn(
78
+ 'text-sm transition-colors',
79
+ isDark
80
+ ? 'text-white hover:text-primary'
81
+ : 'text-muted-foreground hover:text-foreground'
82
+ )}
83
+ target={item.external ? '_blank' : undefined}
84
+ rel={item.external ? 'noopener noreferrer' : undefined}
85
+ >
86
+ {item.name}
87
+ {#if item.external}
88
+ <span class="sr-only">(opens in new tab)</span>
89
+ <svg
90
+ aria-hidden="true"
91
+ class="ml-1 inline-block h-3 w-3"
92
+ fill="none"
93
+ viewBox="0 0 24 24"
94
+ stroke="currentColor"
95
+ >
96
+ <path
97
+ stroke-linecap="round"
98
+ stroke-linejoin="round"
99
+ stroke-width="2"
100
+ d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
101
+ />
102
+ </svg>
103
+ {/if}
104
+ </a>
105
+ </li>
106
+ {/each}
107
+ </ul>
108
+ </div>
109
+ {/each}
110
+ </div>
111
+ </nav>
107
112
  {/if}
108
113
 
109
114
  {#if children}
110
- <div class={cn('mt-8 border-t pt-8', isDark ? 'border-gray-600' : 'border-black')}>
115
+ <div class={cn('mt-8 border-t pt-8', isDark ? 'border-gray-500' : 'border-border')}>
111
116
  {@render children()}
112
117
  </div>
113
118
  {/if}
@@ -115,7 +120,7 @@
115
120
  <div
116
121
  class={cn(
117
122
  'mt-8 flex flex-col items-center justify-between gap-4 border-t pt-8 md:flex-row',
118
- isDark ? 'border-gray-600' : 'border-black'
123
+ isDark ? 'border-gray-500' : 'border-border'
119
124
  )}
120
125
  >
121
126
  {#if showLogo}
@@ -123,7 +128,7 @@
123
128
  {/if}
124
129
 
125
130
  <p class={cn('text-center text-sm', isDark ? 'text-gray-400' : 'text-muted-foreground')}>
126
- {copyright || defaultCopyright}
131
+ {displayCopyright}
127
132
  </p>
128
133
  </div>
129
134
  </div>
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import type { Snippet } from 'svelte';
15
15
  import { cn } from '../../utils.js';
16
+ import PageHeader from '../PageHeader.svelte';
16
17
 
17
18
  interface Props {
18
19
  /** Page title (h1) */
@@ -53,14 +54,7 @@
53
54
  <div class={cn('min-h-screen py-8 sm:py-12', sageBackground && 'bg-brand-core-4-tint', className)}>
54
55
  <div class="mx-auto max-w-5xl px-4 sm:px-6 lg:px-8">
55
56
  <!-- Header Section -->
56
- <header class="mb-8 text-center">
57
- <h1 class="text-3xl font-light tracking-tight text-brand-core-2 md:text-4xl">
58
- {title}
59
- </h1>
60
- <p class="mt-3 text-base text-gray-600 md:text-lg">
61
- {description}
62
- </p>
63
- </header>
57
+ <PageHeader {title} subtitle={description} variant="form" />
64
58
 
65
59
  <!-- Notices Section -->
66
60
  {#if showNotices && notices}