@refrakt-md/lumina 0.4.0 → 0.5.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.
Files changed (88) hide show
  1. package/base.css +18 -0
  2. package/contracts/structures.json +1360 -3
  3. package/dist/config.d.ts +2 -3
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +6 -229
  6. package/dist/config.js.map +1 -1
  7. package/dist/icons.d.ts +3 -0
  8. package/dist/icons.d.ts.map +1 -0
  9. package/dist/icons.js +86 -0
  10. package/dist/icons.js.map +1 -0
  11. package/dist/transform.d.ts +2 -0
  12. package/dist/transform.d.ts.map +1 -1
  13. package/dist/transform.js +2 -0
  14. package/dist/transform.js.map +1 -1
  15. package/index.css +12 -0
  16. package/package.json +18 -11
  17. package/styles/elements/blockquote.css +8 -4
  18. package/styles/elements/code.css +40 -0
  19. package/styles/global.css +0 -7
  20. package/styles/layouts/blog.css +267 -0
  21. package/styles/layouts/default.css +26 -6
  22. package/styles/layouts/docs.css +84 -18
  23. package/styles/layouts/mobile.css +84 -0
  24. package/styles/runes/bento.css +2 -0
  25. package/styles/runes/codegroup.css +7 -2
  26. package/styles/runes/design-context.css +25 -0
  27. package/styles/runes/feature.css +30 -14
  28. package/styles/runes/form.css +1 -2
  29. package/styles/runes/grid.css +25 -7
  30. package/styles/runes/hero.css +15 -0
  31. package/styles/runes/map.css +113 -0
  32. package/styles/runes/palette.css +91 -0
  33. package/styles/runes/preview.css +188 -0
  34. package/styles/runes/sandbox.css +23 -0
  35. package/styles/runes/spacing.css +110 -0
  36. package/styles/runes/steps.css +21 -3
  37. package/styles/runes/swatch.css +28 -0
  38. package/styles/runes/symbol.css +164 -0
  39. package/styles/runes/tabs.css +6 -0
  40. package/styles/runes/testimonial.css +2 -3
  41. package/styles/runes/timeline.css +43 -24
  42. package/styles/runes/typography.css +104 -0
  43. package/svelte/elements.ts +1 -0
  44. package/{sveltekit → svelte}/index.ts +0 -8
  45. package/svelte/layouts/BlogLayout.svelte +173 -0
  46. package/svelte/layouts/DefaultLayout.svelte +67 -0
  47. package/svelte/layouts/DocsLayout.svelte +155 -0
  48. package/{sveltekit → svelte}/manifest.json +1 -1
  49. package/svelte/registry.ts +2 -0
  50. package/svelte/tokens.css +6 -0
  51. package/dist/lib/engine.d.ts +0 -13
  52. package/dist/lib/engine.d.ts.map +0 -1
  53. package/dist/lib/engine.js +0 -218
  54. package/dist/lib/engine.js.map +0 -1
  55. package/dist/lib/helpers.d.ts +0 -14
  56. package/dist/lib/helpers.d.ts.map +0 -1
  57. package/dist/lib/helpers.js +0 -26
  58. package/dist/lib/helpers.js.map +0 -1
  59. package/dist/lib/types.d.ts +0 -74
  60. package/dist/lib/types.d.ts.map +0 -1
  61. package/dist/lib/types.js +0 -2
  62. package/dist/lib/types.js.map +0 -1
  63. package/sveltekit/components/Accordion.svelte +0 -26
  64. package/sveltekit/components/Bento.svelte +0 -50
  65. package/sveltekit/components/Chart.svelte +0 -121
  66. package/sveltekit/components/CodeGroup.svelte +0 -88
  67. package/sveltekit/components/Comparison.svelte +0 -209
  68. package/sveltekit/components/DataTable.svelte +0 -154
  69. package/sveltekit/components/Details.svelte +0 -23
  70. package/sveltekit/components/Diagram.svelte +0 -45
  71. package/sveltekit/components/Embed.svelte +0 -36
  72. package/sveltekit/components/Form.svelte +0 -194
  73. package/sveltekit/components/Grid.svelte +0 -42
  74. package/sveltekit/components/Nav.svelte +0 -62
  75. package/sveltekit/components/Pricing.svelte +0 -20
  76. package/sveltekit/components/Reveal.svelte +0 -62
  77. package/sveltekit/components/Storyboard.svelte +0 -41
  78. package/sveltekit/components/Tabs.svelte +0 -75
  79. package/sveltekit/components/Testimonial.svelte +0 -26
  80. package/sveltekit/elements/Blockquote.svelte +0 -37
  81. package/sveltekit/elements/Pre.svelte +0 -77
  82. package/sveltekit/elements/Table.svelte +0 -40
  83. package/sveltekit/elements.ts +0 -11
  84. package/sveltekit/layouts/BlogLayout.svelte +0 -382
  85. package/sveltekit/layouts/DefaultLayout.svelte +0 -70
  86. package/sveltekit/layouts/DocsLayout.svelte +0 -133
  87. package/sveltekit/registry.ts +0 -59
  88. package/sveltekit/tokens.css +0 -71
@@ -1,9 +1,12 @@
1
1
  /* Steps */
2
2
  .rf-steps {
3
3
  counter-reset: step;
4
+ margin: 1.5rem 0;
5
+ }
6
+ .rf-steps ol {
4
7
  list-style: none;
5
8
  padding-left: 0;
6
- margin: 1.5rem 0;
9
+ margin: 0;
7
10
  }
8
11
  .rf-step__main {
9
12
  display: contents;
@@ -27,22 +30,34 @@
27
30
  top: 0;
28
31
  width: 1.875rem;
29
32
  height: 1.875rem;
30
- background: var(--rf-color-primary);
31
- color: white;
33
+ background: var(--rf-color-border);
34
+ color: var(--rf-color-muted);
32
35
  border-radius: var(--rf-radius-full);
33
36
  display: flex;
34
37
  align-items: center;
35
38
  justify-content: center;
36
39
  font-weight: 650;
37
40
  font-size: 0.8rem;
41
+ font-variant-numeric: tabular-nums;
38
42
  box-shadow: 0 0 0 4px var(--rf-color-bg);
39
43
  }
44
+ .rf-step h1,
45
+ .rf-step h2,
40
46
  .rf-step h3,
47
+ .rf-step h4,
48
+ .rf-step h5,
49
+ .rf-step h6,
41
50
  .rf-step strong {
42
51
  display: block;
43
52
  margin-top: 0;
44
53
  margin-bottom: 0.375rem;
45
54
  }
55
+ .rf-step pre {
56
+ margin-top: 0;
57
+ }
58
+ .rf-step > p:first-of-type {
59
+ margin-top: 0;
60
+ }
46
61
  .rf-step p {
47
62
  color: var(--rf-color-muted);
48
63
  font-size: 0.925rem;
@@ -53,6 +68,9 @@
53
68
  grid-template-columns: 1fr 1fr;
54
69
  gap: 2rem;
55
70
  }
71
+ .rf-step--mirror .rf-step__showcase {
72
+ order: -1;
73
+ }
56
74
  .rf-step__showcase {
57
75
  border-radius: var(--rf-radius-md);
58
76
  overflow: hidden;
@@ -0,0 +1,28 @@
1
+ /* Swatch — inline color chip */
2
+ .rf-swatch {
3
+ display: inline-flex;
4
+ align-items: center;
5
+ gap: 0.375rem;
6
+ vertical-align: middle;
7
+ border: 1px solid rgba(0, 0, 0, 0.12);
8
+ border-radius: 9999px;
9
+ padding: 0.125em 0.625em 0.125em 0.375em;
10
+ margin: 0 0.15em;
11
+ }
12
+ .rf-swatch__chip {
13
+ display: inline-block;
14
+ width: 0.875em;
15
+ height: 0.875em;
16
+ border-radius: 50%;
17
+ border: 1px solid rgba(0, 0, 0, 0.1);
18
+ flex-shrink: 0;
19
+ }
20
+ .rf-swatch__label {
21
+ font-weight: 500;
22
+ }
23
+ .rf-swatch__value {
24
+ color: var(--rf-color-text-muted, #6b7280);
25
+ font-size: 0.75em;
26
+ font-family: var(--rf-font-mono, monospace);
27
+ vertical-align: middle;
28
+ }
@@ -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,104 @@
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
+ }
92
+
93
+ /* Context-aware: no outer border when inside design-context (parent already has one) */
94
+ .rf-typography--in-design-context {
95
+ margin: 0;
96
+ }
97
+ .rf-typography--in-design-context .rf-typography__specimen {
98
+ border: none;
99
+ border-radius: 0;
100
+ padding: 0;
101
+ }
102
+ .rf-typography--in-design-context .rf-typography__specimen + .rf-typography__specimen {
103
+ padding-top: 1.5rem;
104
+ }
@@ -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}