@refrakt-md/lumina 0.4.0 → 0.5.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.
Files changed (71) hide show
  1. package/base.css +16 -0
  2. package/contracts/structures.json +1317 -3
  3. package/dist/config.d.ts +2 -3
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +4 -229
  6. package/dist/config.js.map +1 -1
  7. package/dist/transform.d.ts +2 -0
  8. package/dist/transform.d.ts.map +1 -1
  9. package/dist/transform.js +2 -0
  10. package/dist/transform.js.map +1 -1
  11. package/index.css +11 -0
  12. package/package.json +18 -11
  13. package/styles/elements/blockquote.css +8 -4
  14. package/styles/global.css +0 -7
  15. package/styles/layouts/blog.css +255 -0
  16. package/styles/layouts/default.css +11 -3
  17. package/styles/layouts/docs.css +67 -13
  18. package/styles/layouts/mobile.css +84 -0
  19. package/styles/runes/bento.css +2 -0
  20. package/styles/runes/codegroup.css +7 -2
  21. package/styles/runes/design-context.css +25 -0
  22. package/styles/runes/feature.css +20 -14
  23. package/styles/runes/form.css +1 -2
  24. package/styles/runes/grid.css +25 -7
  25. package/styles/runes/hero.css +15 -0
  26. package/styles/runes/map.css +113 -0
  27. package/styles/runes/palette.css +86 -0
  28. package/styles/runes/preview.css +187 -0
  29. package/styles/runes/sandbox.css +23 -0
  30. package/styles/runes/spacing.css +105 -0
  31. package/styles/runes/steps.css +7 -1
  32. package/styles/runes/swatch.css +28 -0
  33. package/styles/runes/symbol.css +164 -0
  34. package/styles/runes/tabs.css +6 -0
  35. package/styles/runes/testimonial.css +2 -3
  36. package/styles/runes/timeline.css +43 -24
  37. package/styles/runes/typography.css +91 -0
  38. package/svelte/elements.ts +1 -0
  39. package/{sveltekit → svelte}/index.ts +0 -8
  40. package/svelte/layouts/BlogLayout.svelte +173 -0
  41. package/svelte/layouts/DefaultLayout.svelte +67 -0
  42. package/svelte/layouts/DocsLayout.svelte +155 -0
  43. package/{sveltekit → svelte}/manifest.json +1 -1
  44. package/svelte/registry.ts +2 -0
  45. package/svelte/tokens.css +6 -0
  46. package/sveltekit/components/Accordion.svelte +0 -26
  47. package/sveltekit/components/Bento.svelte +0 -50
  48. package/sveltekit/components/Chart.svelte +0 -121
  49. package/sveltekit/components/CodeGroup.svelte +0 -88
  50. package/sveltekit/components/Comparison.svelte +0 -209
  51. package/sveltekit/components/DataTable.svelte +0 -154
  52. package/sveltekit/components/Details.svelte +0 -23
  53. package/sveltekit/components/Diagram.svelte +0 -45
  54. package/sveltekit/components/Embed.svelte +0 -36
  55. package/sveltekit/components/Form.svelte +0 -194
  56. package/sveltekit/components/Grid.svelte +0 -42
  57. package/sveltekit/components/Nav.svelte +0 -62
  58. package/sveltekit/components/Pricing.svelte +0 -20
  59. package/sveltekit/components/Reveal.svelte +0 -62
  60. package/sveltekit/components/Storyboard.svelte +0 -41
  61. package/sveltekit/components/Tabs.svelte +0 -75
  62. package/sveltekit/components/Testimonial.svelte +0 -26
  63. package/sveltekit/elements/Blockquote.svelte +0 -37
  64. package/sveltekit/elements/Pre.svelte +0 -77
  65. package/sveltekit/elements/Table.svelte +0 -40
  66. package/sveltekit/elements.ts +0 -11
  67. package/sveltekit/layouts/BlogLayout.svelte +0 -382
  68. package/sveltekit/layouts/DefaultLayout.svelte +0 -70
  69. package/sveltekit/layouts/DocsLayout.svelte +0 -133
  70. package/sveltekit/registry.ts +0 -59
  71. package/sveltekit/tokens.css +0 -71
@@ -0,0 +1,164 @@
1
+ /* Symbol */
2
+ .rf-symbol {
3
+ border: 1px solid var(--rf-color-border);
4
+ border-radius: var(--rf-radius-lg);
5
+ margin: 1.5rem 0;
6
+ overflow: hidden;
7
+ }
8
+ .rf-symbol__header {
9
+ display: flex;
10
+ align-items: center;
11
+ gap: 0.5rem;
12
+ padding: 0.75rem 1.25rem;
13
+ background: var(--rf-color-surface-hover);
14
+ border-bottom: 1px solid var(--rf-color-border);
15
+ flex-wrap: wrap;
16
+ }
17
+ .rf-symbol__kind-badge {
18
+ font-size: 0.6875rem;
19
+ font-weight: 700;
20
+ text-transform: uppercase;
21
+ letter-spacing: 0.05em;
22
+ padding: 0.125rem 0.5rem;
23
+ border-radius: var(--rf-radius-sm);
24
+ color: var(--rf-color-primary);
25
+ background: var(--rf-color-primary-bg, rgba(14, 165, 233, 0.1));
26
+ }
27
+ .rf-symbol__lang-badge {
28
+ font-size: 0.6875rem;
29
+ font-weight: 500;
30
+ padding: 0.125rem 0.5rem;
31
+ border-radius: var(--rf-radius-sm);
32
+ color: var(--rf-color-muted);
33
+ background: var(--rf-color-surface-active);
34
+ }
35
+ .rf-symbol__since-badge {
36
+ font-size: 0.6875rem;
37
+ color: var(--rf-color-success);
38
+ background: var(--rf-color-success-bg);
39
+ padding: 0.125rem 0.5rem;
40
+ border-radius: var(--rf-radius-sm);
41
+ }
42
+ .rf-symbol__deprecated-badge {
43
+ font-size: 0.6875rem;
44
+ color: var(--rf-color-warning);
45
+ background: var(--rf-color-warning-bg);
46
+ padding: 0.125rem 0.5rem;
47
+ border-radius: var(--rf-radius-sm);
48
+ }
49
+ .rf-symbol__source-link {
50
+ margin-left: auto;
51
+ font-size: 0.75rem;
52
+ color: var(--rf-color-muted);
53
+ text-decoration: none;
54
+ }
55
+ .rf-symbol__source-link:hover {
56
+ color: var(--rf-color-primary);
57
+ }
58
+ .rf-symbol__body {
59
+ padding: 1.25rem;
60
+ }
61
+ .rf-symbol__body > header {
62
+ margin-bottom: 1rem;
63
+ }
64
+ .rf-symbol__body > header h2,
65
+ .rf-symbol__body > header h3,
66
+ .rf-symbol__body > header h4 {
67
+ margin-top: 0;
68
+ }
69
+ .rf-symbol__body pre {
70
+ border-radius: var(--rf-radius-md);
71
+ margin: 1rem 0;
72
+ }
73
+ .rf-symbol__body > div > ul {
74
+ list-style: none;
75
+ padding-left: 0;
76
+ margin: 1rem 0;
77
+ }
78
+ .rf-symbol__body > div > ul > li {
79
+ padding: 0.375rem 0;
80
+ border-bottom: 1px solid var(--rf-color-border);
81
+ font-size: 0.9375rem;
82
+ }
83
+ .rf-symbol__body > div > ul > li:last-child {
84
+ border-bottom: none;
85
+ }
86
+ .rf-symbol__body > div > ul > li > ul {
87
+ margin-top: 0.375rem;
88
+ padding-left: 1.25rem;
89
+ list-style: none;
90
+ }
91
+ .rf-symbol__body > div > ul > li > ul > li {
92
+ padding: 0.25rem 0;
93
+ font-size: 0.875rem;
94
+ color: var(--rf-color-muted);
95
+ }
96
+ .rf-symbol__body blockquote {
97
+ border-left: 3px solid var(--rf-color-border);
98
+ padding: 0.5rem 1rem;
99
+ margin: 0.75rem 0;
100
+ border-radius: 0 var(--rf-radius-sm) var(--rf-radius-sm) 0;
101
+ font-size: 0.9375rem;
102
+ }
103
+
104
+ /* Deprecated symbol */
105
+ [data-deprecated] .rf-symbol__body > header h2,
106
+ [data-deprecated] .rf-symbol__body > header h3,
107
+ [data-deprecated] .rf-symbol__body > header h4 {
108
+ text-decoration: line-through;
109
+ opacity: 0.7;
110
+ }
111
+
112
+ /* SymbolGroup */
113
+ .rf-symbol-group {
114
+ margin: 1.5rem 0 0;
115
+ padding-top: 1rem;
116
+ border-top: 1px solid var(--rf-color-border);
117
+ }
118
+ .rf-symbol-group:first-child {
119
+ margin-top: 0;
120
+ padding-top: 0;
121
+ border-top: none;
122
+ }
123
+ .rf-symbol-group h3 {
124
+ font-size: 0.75rem;
125
+ font-weight: 700;
126
+ text-transform: uppercase;
127
+ letter-spacing: 0.05em;
128
+ color: var(--rf-color-muted);
129
+ margin: 0 0 1rem;
130
+ }
131
+
132
+ /* SymbolMember */
133
+ .rf-symbol-member {
134
+ margin: 1.25rem 0;
135
+ padding: 1rem;
136
+ border: 1px solid var(--rf-color-border);
137
+ border-radius: var(--rf-radius-md);
138
+ background: var(--rf-color-surface);
139
+ }
140
+ .rf-symbol-member h4 {
141
+ margin: 0 0 0.5rem;
142
+ font-family: var(--rf-font-mono);
143
+ font-size: 1rem;
144
+ }
145
+ .rf-symbol-member pre {
146
+ border-radius: var(--rf-radius-sm);
147
+ margin: 0.75rem 0;
148
+ }
149
+ .rf-symbol-member ul {
150
+ list-style: none;
151
+ padding-left: 0;
152
+ margin: 0.75rem 0;
153
+ }
154
+ .rf-symbol-member li {
155
+ padding: 0.25rem 0;
156
+ font-size: 0.875rem;
157
+ }
158
+ .rf-symbol-member blockquote {
159
+ border-left: 3px solid var(--rf-color-border);
160
+ padding: 0.375rem 0.75rem;
161
+ margin: 0.5rem 0;
162
+ border-radius: 0 var(--rf-radius-sm) var(--rf-radius-sm) 0;
163
+ font-size: 0.875rem;
164
+ }
@@ -6,6 +6,12 @@
6
6
  margin: 1.5rem 0;
7
7
  background: var(--rf-color-bg);
8
8
  }
9
+ .rf-tabs__tabs,
10
+ .rf-tabs__panels {
11
+ list-style: none;
12
+ padding: 0;
13
+ margin: 0;
14
+ }
9
15
  .rf-tabs__bar {
10
16
  display: flex;
11
17
  background: var(--rf-color-surface);
@@ -19,8 +19,7 @@
19
19
  .rf-testimonial__star--filled {
20
20
  color: var(--rf-color-warning);
21
21
  }
22
- .rf-testimonial blockquote,
23
- .rf-testimonial .rf-blockquote {
22
+ .rf-testimonial blockquote {
24
23
  border: none;
25
24
  padding: 0;
26
25
  margin: 0 0 1rem;
@@ -31,7 +30,7 @@
31
30
  background: none;
32
31
  border-radius: 0;
33
32
  }
34
- .rf-testimonial .rf-blockquote__quote-mark {
33
+ .rf-testimonial blockquote::before {
35
34
  display: none;
36
35
  }
37
36
  .rf-testimonial blockquote p {
@@ -6,35 +6,14 @@
6
6
  list-style: none;
7
7
  padding: 0;
8
8
  margin: 0;
9
- position: relative;
10
- }
11
- .rf-timeline--vertical ol {
12
- padding-left: 2rem;
13
- border-left: 2px solid var(--rf-color-border);
14
- }
15
- .rf-timeline__entries {
16
- display: contents;
17
9
  }
18
10
  .rf-timeline-entry {
19
11
  position: relative;
20
- padding: 0 0 2rem;
21
12
  }
22
13
  .rf-timeline-entry:last-child {
23
14
  padding-bottom: 0;
24
15
  }
25
- .rf-timeline-entry::before {
26
- content: '';
27
- position: absolute;
28
- left: -2.5625rem;
29
- top: 0.35rem;
30
- width: 12px;
31
- height: 12px;
32
- border-radius: 50%;
33
- background: var(--rf-color-primary);
34
- border: 2px solid var(--rf-color-bg);
35
- box-shadow: 0 0 0 2px var(--rf-color-primary);
36
- }
37
- .rf-timeline-entry__body time {
16
+ .rf-timeline-entry > time {
38
17
  display: block;
39
18
  font-size: 0.8rem;
40
19
  font-weight: 600;
@@ -42,7 +21,7 @@
42
21
  letter-spacing: 0.02em;
43
22
  margin-bottom: 0.25rem;
44
23
  }
45
- .rf-timeline-entry__body span {
24
+ .rf-timeline-entry > span {
46
25
  display: block;
47
26
  font-size: 1.05rem;
48
27
  font-weight: 700;
@@ -57,11 +36,51 @@
57
36
  .rf-timeline-entry__body p:last-child {
58
37
  margin-bottom: 0;
59
38
  }
39
+
40
+ /* Vertical timeline */
41
+ .rf-timeline--vertical .rf-timeline-entry {
42
+ padding-left: 2rem;
43
+ padding-bottom: 2rem;
44
+ border-left: 2px solid var(--rf-color-border);
45
+ margin-left: 0.375rem;
46
+ }
47
+ .rf-timeline--vertical .rf-timeline-entry:last-child {
48
+ border-left-color: transparent;
49
+ }
50
+ .rf-timeline--vertical .rf-timeline-entry::before {
51
+ content: '';
52
+ position: absolute;
53
+ left: -0.4375rem;
54
+ top: 0.25rem;
55
+ width: 0.75rem;
56
+ height: 0.75rem;
57
+ border-radius: 50%;
58
+ background: var(--rf-color-primary);
59
+ border: 2px solid var(--rf-color-bg);
60
+ box-shadow: 0 0 0 2px var(--rf-color-primary);
61
+ }
62
+
63
+ /* Horizontal timeline */
60
64
  .rf-timeline--horizontal ol {
61
65
  display: flex;
62
66
  gap: 2rem;
63
67
  overflow-x: auto;
64
68
  padding: 2rem 0 1rem;
65
- border-left: none;
69
+ }
70
+ .rf-timeline--horizontal .rf-timeline-entry {
71
+ min-width: 12rem;
72
+ padding-top: 1.5rem;
66
73
  border-top: 2px solid var(--rf-color-border);
67
74
  }
75
+ .rf-timeline--horizontal .rf-timeline-entry::before {
76
+ content: '';
77
+ position: absolute;
78
+ top: -0.4375rem;
79
+ left: 0.5rem;
80
+ width: 0.75rem;
81
+ height: 0.75rem;
82
+ border-radius: 50%;
83
+ background: var(--rf-color-primary);
84
+ border: 2px solid var(--rf-color-bg);
85
+ box-shadow: 0 0 0 2px var(--rf-color-primary);
86
+ }
@@ -0,0 +1,91 @@
1
+ /* Typography — font specimen display */
2
+ .rf-typography {
3
+ margin: 1.5rem 0;
4
+ }
5
+ .rf-typography__title {
6
+ font-size: 1.125rem;
7
+ font-weight: 600;
8
+ margin-bottom: 1rem;
9
+ }
10
+ .rf-typography__specimens {
11
+ display: flex;
12
+ flex-direction: column;
13
+ gap: 2rem;
14
+ }
15
+ .rf-typography__specimen {
16
+ border: 1px solid var(--rf-color-border, #e5e7eb);
17
+ border-radius: var(--rf-radius-lg, 12px);
18
+ padding: 1.5rem;
19
+ }
20
+ .rf-typography__specimen-header {
21
+ display: flex;
22
+ align-items: baseline;
23
+ gap: 0.75rem;
24
+ margin-bottom: 1.25rem;
25
+ padding-bottom: 0.75rem;
26
+ border-bottom: 1px solid var(--rf-color-border, #e5e7eb);
27
+ }
28
+ .rf-typography__specimen-role {
29
+ font-size: 0.75rem;
30
+ font-weight: 600;
31
+ text-transform: uppercase;
32
+ letter-spacing: 0.05em;
33
+ color: var(--rf-color-primary, #0ea5e9);
34
+ background: var(--rf-color-primary-bg, rgba(14, 165, 233, 0.1));
35
+ padding: 0.125rem 0.5rem;
36
+ border-radius: var(--rf-radius-sm, 4px);
37
+ }
38
+ .rf-typography__specimen-family {
39
+ font-size: 0.9rem;
40
+ color: var(--rf-color-text-muted, #6b7280);
41
+ }
42
+ .rf-typography__sizes {
43
+ display: flex;
44
+ flex-direction: column;
45
+ gap: 0.5rem;
46
+ margin-bottom: 1rem;
47
+ }
48
+ .rf-typography__size-sample {
49
+ display: flex;
50
+ align-items: baseline;
51
+ gap: 1rem;
52
+ line-height: 1.3;
53
+ overflow: hidden;
54
+ text-overflow: ellipsis;
55
+ white-space: nowrap;
56
+ }
57
+ .rf-typography__size-label {
58
+ flex-shrink: 0;
59
+ font-size: 0.7rem;
60
+ font-family: var(--rf-font-mono, monospace);
61
+ color: var(--rf-color-text-muted, #6b7280);
62
+ min-width: 3rem;
63
+ text-align: right;
64
+ }
65
+ .rf-typography__weights {
66
+ display: flex;
67
+ gap: 1.5rem;
68
+ flex-wrap: wrap;
69
+ padding-top: 1rem;
70
+ border-top: 1px solid var(--rf-color-border, #e5e7eb);
71
+ }
72
+ .rf-typography__weight-sample {
73
+ display: flex;
74
+ flex-direction: column;
75
+ gap: 0.25rem;
76
+ }
77
+ .rf-typography__weight-label {
78
+ font-size: 0.7rem;
79
+ font-family: var(--rf-font-mono, monospace);
80
+ color: var(--rf-color-text-muted, #6b7280);
81
+ }
82
+ .rf-typography__charset {
83
+ margin-top: 1rem;
84
+ padding-top: 1rem;
85
+ border-top: 1px solid var(--rf-color-border, #e5e7eb);
86
+ font-size: 0.875rem;
87
+ line-height: 1.8;
88
+ letter-spacing: 0.05em;
89
+ word-break: break-all;
90
+ color: var(--rf-color-text-muted, #6b7280);
91
+ }
@@ -0,0 +1 @@
1
+ export { elements } from '@refrakt-md/theme-base/svelte/elements';
@@ -22,11 +22,3 @@ export { registry };
22
22
  export { default as DocsLayout } from './layouts/DocsLayout.svelte';
23
23
  export { default as DefaultLayout } from './layouts/DefaultLayout.svelte';
24
24
  export { default as BlogLayout } from './layouts/BlogLayout.svelte';
25
-
26
- // Interactive components for advanced usage
27
- export { default as Tabs } from './components/Tabs.svelte';
28
- export { default as DataTable } from './components/DataTable.svelte';
29
- export { default as Form } from './components/Form.svelte';
30
- export { default as Reveal } from './components/Reveal.svelte';
31
- export { default as Diagram } from './components/Diagram.svelte';
32
- export { default as Nav } from './components/Nav.svelte';
@@ -0,0 +1,173 @@
1
+ <script lang="ts">
2
+ import { Renderer } from '@refrakt-md/svelte';
3
+
4
+ let { title, frontmatter, regions, renderable, pages, url }: {
5
+ title: string;
6
+ description: string;
7
+ frontmatter?: Record<string, unknown>;
8
+ regions: Record<string, { name: string; mode: string; content: any[] }>;
9
+ renderable: any;
10
+ pages: Array<{
11
+ url: string;
12
+ title: string;
13
+ draft: boolean;
14
+ description?: string;
15
+ date?: string;
16
+ author?: string;
17
+ tags?: string[];
18
+ image?: string;
19
+ }>;
20
+ url: string;
21
+ } = $props();
22
+
23
+ const date = $derived(frontmatter?.date as string | undefined);
24
+ const author = $derived(frontmatter?.author as string | undefined);
25
+ const tags = $derived(frontmatter?.tags as string[] | undefined);
26
+
27
+ // Index page has no date; individual posts always have one
28
+ const isIndex = $derived(!date);
29
+
30
+ const posts = $derived(
31
+ isIndex
32
+ ? pages
33
+ .filter(p => p.url.startsWith('/blog/') && p.url !== '/blog' && !p.draft && p.date)
34
+ .sort((a, b) => (b.date ?? '').localeCompare(a.date ?? ''))
35
+ : []
36
+ );
37
+
38
+ const hasSidebar = $derived(!!regions.sidebar);
39
+
40
+ let menuOpen = $state(false);
41
+
42
+ function onKeydown(e: KeyboardEvent) {
43
+ if (e.key === 'Escape') menuOpen = false;
44
+ }
45
+
46
+ $effect(() => {
47
+ url;
48
+ menuOpen = false;
49
+ });
50
+
51
+ // Lock body scroll when panel is open
52
+ $effect(() => {
53
+ document.body.style.overflow = menuOpen ? 'hidden' : '';
54
+ });
55
+
56
+ function formatDate(iso: string): string {
57
+ const d = new Date(iso + 'T00:00:00');
58
+ return d.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
59
+ }
60
+ </script>
61
+
62
+ <svelte:window onkeydown={onKeydown} />
63
+
64
+ {#if regions.header}
65
+ <header class="rf-blog-header">
66
+ <div class="rf-blog-header__inner">
67
+ <Renderer node={regions.header.content} />
68
+ <button class="rf-mobile-menu-btn" onclick={() => menuOpen = true} aria-label="Open menu">
69
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
70
+ <circle cx="10" cy="4" r="1.5"/>
71
+ <circle cx="10" cy="10" r="1.5"/>
72
+ <circle cx="10" cy="16" r="1.5"/>
73
+ </svg>
74
+ </button>
75
+ </div>
76
+ </header>
77
+ {/if}
78
+
79
+ {#if menuOpen}
80
+ <div class="rf-mobile-panel" role="dialog" aria-label="Navigation menu">
81
+ <div class="rf-mobile-panel__header">
82
+ <span class="rf-mobile-panel__title">Menu</span>
83
+ <button class="rf-mobile-panel__close" onclick={() => menuOpen = false} aria-label="Close menu">
84
+ <svg width="20" height="20" viewBox="0 0 20 20" stroke="currentColor" stroke-width="2" fill="none">
85
+ <line x1="4" y1="4" x2="16" y2="16"/><line x1="16" y1="4" x2="4" y2="16"/>
86
+ </svg>
87
+ </button>
88
+ </div>
89
+ <nav class="rf-mobile-panel__nav">
90
+ {#if regions.header}
91
+ <Renderer node={regions.header.content} />
92
+ {/if}
93
+ </nav>
94
+ </div>
95
+ {/if}
96
+
97
+ <div class="rf-blog" class:rf-blog--has-sidebar={hasSidebar}>
98
+ {#if isIndex}
99
+ <div class="rf-blog-index">
100
+ <h1 class="rf-blog-index__title">{title}</h1>
101
+
102
+ <div class="rf-blog-index__body">
103
+ <Renderer node={renderable} />
104
+ </div>
105
+
106
+ <div class="rf-blog-index__posts">
107
+ {#each posts as post}
108
+ <a href={post.url} class="rf-blog-card">
109
+ <h2 class="rf-blog-card__title">{post.title}</h2>
110
+ <div class="rf-blog-card__meta">
111
+ {#if post.date}
112
+ <time datetime={post.date}>{formatDate(post.date)}</time>
113
+ {/if}
114
+ {#if post.author}
115
+ <span class="rf-blog-card__author">{post.author}</span>
116
+ {/if}
117
+ </div>
118
+ {#if post.description}
119
+ <p class="rf-blog-card__desc">{post.description}</p>
120
+ {/if}
121
+ {#if post.tags && post.tags.length > 0}
122
+ <div class="rf-blog-card__tags">
123
+ {#each post.tags as tag}
124
+ <span class="rf-blog-article__tag">{tag}</span>
125
+ {/each}
126
+ </div>
127
+ {/if}
128
+ <span class="rf-blog-card__link">Read more &rarr;</span>
129
+ </a>
130
+ {/each}
131
+ </div>
132
+ </div>
133
+ {:else}
134
+ <article class="rf-blog-article">
135
+ <header class="rf-blog-article__header">
136
+ <h1 class="rf-blog-article__title">{title}</h1>
137
+ {#if date || author}
138
+ <div class="rf-blog-article__meta">
139
+ {#if date}
140
+ <time datetime={date}>{formatDate(date)}</time>
141
+ {/if}
142
+ {#if author}
143
+ <span class="rf-blog-article__author">{author}</span>
144
+ {/if}
145
+ </div>
146
+ {/if}
147
+ {#if tags && tags.length > 0}
148
+ <div class="rf-blog-article__tags">
149
+ {#each tags as tag}
150
+ <span class="rf-blog-article__tag">{tag}</span>
151
+ {/each}
152
+ </div>
153
+ {/if}
154
+ </header>
155
+
156
+ <div class="rf-blog-article__body">
157
+ <Renderer node={renderable} />
158
+ </div>
159
+ </article>
160
+
161
+ {#if regions.sidebar}
162
+ <aside class="rf-blog-sidebar">
163
+ <Renderer node={regions.sidebar.content} />
164
+ </aside>
165
+ {/if}
166
+ {/if}
167
+ </div>
168
+
169
+ {#if regions.footer}
170
+ <footer class="rf-blog-footer">
171
+ <Renderer node={regions.footer.content} />
172
+ </footer>
173
+ {/if}
@@ -0,0 +1,67 @@
1
+ <script lang="ts">
2
+ import { Renderer } from '@refrakt-md/svelte';
3
+
4
+ let { regions, renderable, url }: {
5
+ title: string;
6
+ description: string;
7
+ regions: Record<string, { name: string; mode: string; content: any[] }>;
8
+ renderable: any;
9
+ url: string;
10
+ pages: any[];
11
+ } = $props();
12
+
13
+ let menuOpen = $state(false);
14
+
15
+ function onKeydown(e: KeyboardEvent) {
16
+ if (e.key === 'Escape') menuOpen = false;
17
+ }
18
+
19
+ $effect(() => {
20
+ url;
21
+ menuOpen = false;
22
+ });
23
+
24
+ // Lock body scroll when panel is open
25
+ $effect(() => {
26
+ document.body.style.overflow = menuOpen ? 'hidden' : '';
27
+ });
28
+ </script>
29
+
30
+ <svelte:window onkeydown={onKeydown} />
31
+
32
+ {#if regions.header}
33
+ <header class="rf-header">
34
+ <div class="rf-header__inner">
35
+ <Renderer node={regions.header.content} />
36
+ <button class="rf-mobile-menu-btn" onclick={() => menuOpen = true} aria-label="Open menu">
37
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
38
+ <circle cx="10" cy="4" r="1.5"/>
39
+ <circle cx="10" cy="10" r="1.5"/>
40
+ <circle cx="10" cy="16" r="1.5"/>
41
+ </svg>
42
+ </button>
43
+ </div>
44
+ </header>
45
+ {/if}
46
+
47
+ {#if menuOpen}
48
+ <div class="rf-mobile-panel" role="dialog" aria-label="Navigation menu">
49
+ <div class="rf-mobile-panel__header">
50
+ <span class="rf-mobile-panel__title">Menu</span>
51
+ <button class="rf-mobile-panel__close" onclick={() => menuOpen = false} aria-label="Close menu">
52
+ <svg width="20" height="20" viewBox="0 0 20 20" stroke="currentColor" stroke-width="2" fill="none">
53
+ <line x1="4" y1="4" x2="16" y2="16"/><line x1="16" y1="4" x2="4" y2="16"/>
54
+ </svg>
55
+ </button>
56
+ </div>
57
+ <nav class="rf-mobile-panel__nav">
58
+ {#if regions.header}
59
+ <Renderer node={regions.header.content} />
60
+ {/if}
61
+ </nav>
62
+ </div>
63
+ {/if}
64
+
65
+ <main class="rf-page-content">
66
+ <Renderer node={renderable} />
67
+ </main>