@refrakt-md/lumina 0.6.0 → 0.7.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.
- package/base.css +4 -0
- package/contracts/structures.json +1367 -739
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/index.css +13 -0
- package/package.json +5 -5
- package/styles/layouts/blog.css +10 -1
- package/styles/layouts/default.css +10 -1
- package/styles/layouts/docs.css +12 -3
- package/styles/layouts/search.css +210 -0
- package/styles/layouts/version-switcher.css +45 -0
- package/styles/runes/bond.css +77 -0
- package/styles/runes/budget.css +164 -0
- package/styles/runes/character.css +104 -0
- package/styles/runes/faction.css +81 -0
- package/styles/runes/itinerary.css +173 -0
- package/styles/runes/lore.css +68 -0
- package/styles/runes/mediatext.css +72 -0
- package/styles/runes/plot.css +80 -0
- package/styles/runes/pullquote.css +108 -0
- package/styles/runes/realm.css +82 -0
- package/styles/runes/textblock.css +64 -0
- package/svelte/elements.ts +1 -1
- package/svelte/index.ts +1 -5
- package/svelte/manifest.json +4 -21
- package/svelte/registry.ts +1 -1
- package/svelte/components/OnThisPage.svelte +0 -76
- package/svelte/layouts/BlogLayout.svelte +0 -173
- package/svelte/layouts/DefaultLayout.svelte +0 -67
- package/svelte/layouts/DocsLayout.svelte +0 -169
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/* Realm */
|
|
2
|
+
.rf-realm {
|
|
3
|
+
border: 1px solid var(--rf-color-border);
|
|
4
|
+
border-radius: var(--rf-radius-lg);
|
|
5
|
+
padding: 2rem;
|
|
6
|
+
margin: 1.5rem 0;
|
|
7
|
+
}
|
|
8
|
+
.rf-realm__badge {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-wrap: wrap;
|
|
11
|
+
gap: 0.5rem;
|
|
12
|
+
margin-bottom: 1rem;
|
|
13
|
+
}
|
|
14
|
+
.rf-realm__type-badge {
|
|
15
|
+
display: inline-block;
|
|
16
|
+
padding: 0.25rem 0.75rem;
|
|
17
|
+
border-radius: var(--rf-radius-full);
|
|
18
|
+
font-size: 0.75rem;
|
|
19
|
+
font-weight: 600;
|
|
20
|
+
text-transform: uppercase;
|
|
21
|
+
letter-spacing: 0.05em;
|
|
22
|
+
background: var(--rf-color-muted-bg);
|
|
23
|
+
color: var(--rf-color-muted);
|
|
24
|
+
}
|
|
25
|
+
.rf-realm__scale-badge {
|
|
26
|
+
display: inline-block;
|
|
27
|
+
padding: 0.25rem 0.75rem;
|
|
28
|
+
border-radius: var(--rf-radius-full);
|
|
29
|
+
font-size: 0.75rem;
|
|
30
|
+
font-weight: 600;
|
|
31
|
+
text-transform: uppercase;
|
|
32
|
+
letter-spacing: 0.05em;
|
|
33
|
+
background: var(--rf-color-muted-bg);
|
|
34
|
+
color: var(--rf-color-muted);
|
|
35
|
+
}
|
|
36
|
+
.rf-realm__scene {
|
|
37
|
+
width: 100%;
|
|
38
|
+
margin-bottom: 1rem;
|
|
39
|
+
border-radius: var(--rf-radius-md);
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
aspect-ratio: 16 / 9;
|
|
42
|
+
}
|
|
43
|
+
.rf-realm__scene img {
|
|
44
|
+
width: 100%;
|
|
45
|
+
height: 100%;
|
|
46
|
+
object-fit: cover;
|
|
47
|
+
}
|
|
48
|
+
.rf-realm__sections {
|
|
49
|
+
display: flex;
|
|
50
|
+
flex-direction: column;
|
|
51
|
+
gap: 1rem;
|
|
52
|
+
}
|
|
53
|
+
.rf-realm__content {
|
|
54
|
+
/* content wrapper */
|
|
55
|
+
}
|
|
56
|
+
.rf-realm__content ul,
|
|
57
|
+
.rf-realm__content ol {
|
|
58
|
+
padding-left: 1.5rem;
|
|
59
|
+
}
|
|
60
|
+
.rf-realm > span[property="name"] {
|
|
61
|
+
display: block;
|
|
62
|
+
font-size: 1.5rem;
|
|
63
|
+
font-weight: 700;
|
|
64
|
+
margin-bottom: 0.5rem;
|
|
65
|
+
color: var(--rf-color-heading);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Realm Section */
|
|
69
|
+
.rf-realm-section {
|
|
70
|
+
padding: 0.75rem 0;
|
|
71
|
+
border-top: 1px solid var(--rf-color-border);
|
|
72
|
+
}
|
|
73
|
+
.rf-realm-section__header {
|
|
74
|
+
font-size: 1.125rem;
|
|
75
|
+
font-weight: 600;
|
|
76
|
+
margin-bottom: 0.5rem;
|
|
77
|
+
color: var(--rf-color-heading);
|
|
78
|
+
}
|
|
79
|
+
.rf-realm-section__body ul,
|
|
80
|
+
.rf-realm-section__body ol {
|
|
81
|
+
padding-left: 1.5rem;
|
|
82
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/* TextBlock */
|
|
2
|
+
.rf-textblock {
|
|
3
|
+
margin: 1.5rem 0;
|
|
4
|
+
}
|
|
5
|
+
.rf-textblock__body {
|
|
6
|
+
display: block;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/* Drop cap — decorative first letter */
|
|
10
|
+
.rf-textblock--dropcap .rf-textblock__body > p:first-child::first-letter {
|
|
11
|
+
float: left;
|
|
12
|
+
font-size: 3.5em;
|
|
13
|
+
font-weight: 700;
|
|
14
|
+
line-height: 0.8;
|
|
15
|
+
padding-right: 0.1em;
|
|
16
|
+
margin-top: 0.05em;
|
|
17
|
+
color: var(--rf-color-primary);
|
|
18
|
+
font-family: Georgia, 'Times New Roman', serif;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Lead paragraph — emphasized opening text */
|
|
22
|
+
.rf-textblock--lead .rf-textblock__body > p:first-child {
|
|
23
|
+
font-size: 1.25rem;
|
|
24
|
+
line-height: 1.6;
|
|
25
|
+
font-weight: 400;
|
|
26
|
+
color: var(--rf-color-text);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Multi-column text flow */
|
|
30
|
+
.rf-textblock[data-columns="2"] .rf-textblock__body {
|
|
31
|
+
column-count: 2;
|
|
32
|
+
column-gap: 2rem;
|
|
33
|
+
column-rule: 1px solid var(--rf-color-border);
|
|
34
|
+
}
|
|
35
|
+
.rf-textblock[data-columns="3"] .rf-textblock__body {
|
|
36
|
+
column-count: 3;
|
|
37
|
+
column-gap: 2rem;
|
|
38
|
+
column-rule: 1px solid var(--rf-color-border);
|
|
39
|
+
}
|
|
40
|
+
.rf-textblock[data-columns="4"] .rf-textblock__body {
|
|
41
|
+
column-count: 4;
|
|
42
|
+
column-gap: 2rem;
|
|
43
|
+
column-rule: 1px solid var(--rf-color-border);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Text alignment */
|
|
47
|
+
.rf-textblock--center .rf-textblock__body { text-align: center; }
|
|
48
|
+
.rf-textblock--right .rf-textblock__body { text-align: right; }
|
|
49
|
+
.rf-textblock--justify .rf-textblock__body { text-align: justify; }
|
|
50
|
+
|
|
51
|
+
/* Responsive — columns collapse on smaller screens */
|
|
52
|
+
@media (max-width: 768px) {
|
|
53
|
+
.rf-textblock[data-columns="3"] .rf-textblock__body,
|
|
54
|
+
.rf-textblock[data-columns="4"] .rf-textblock__body {
|
|
55
|
+
column-count: 2;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
@media (max-width: 480px) {
|
|
59
|
+
.rf-textblock[data-columns="2"] .rf-textblock__body,
|
|
60
|
+
.rf-textblock[data-columns="3"] .rf-textblock__body,
|
|
61
|
+
.rf-textblock[data-columns="4"] .rf-textblock__body {
|
|
62
|
+
column-count: 1;
|
|
63
|
+
}
|
|
64
|
+
}
|
package/svelte/elements.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { elements } from '@refrakt-md/
|
|
1
|
+
export { elements } from '@refrakt-md/svelte';
|
package/svelte/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { SvelteTheme } from '@refrakt-md/svelte';
|
|
|
2
2
|
import adapterManifest from './manifest.json';
|
|
3
3
|
import { registry } from './registry.js';
|
|
4
4
|
import { elements } from './elements.js';
|
|
5
|
-
import { defaultLayout, docsLayout, blogArticleLayout } from '@refrakt-md/
|
|
5
|
+
import { defaultLayout, docsLayout, blogArticleLayout } from '@refrakt-md/transform';
|
|
6
6
|
|
|
7
7
|
// Layout region metadata from the base theme manifest (packages/lumina/manifest.json).
|
|
8
8
|
// Merged with adapter manifest component paths to produce full LayoutDefinitions.
|
|
@@ -34,8 +34,4 @@ export const theme: SvelteTheme = {
|
|
|
34
34
|
elements,
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
// Backward-compatible named exports
|
|
38
37
|
export { registry };
|
|
39
|
-
export { default as DocsLayout } from './layouts/DocsLayout.svelte';
|
|
40
|
-
export { default as DefaultLayout } from './layouts/DefaultLayout.svelte';
|
|
41
|
-
export { default as BlogLayout } from './layouts/BlogLayout.svelte';
|
package/svelte/manifest.json
CHANGED
|
@@ -6,29 +6,12 @@
|
|
|
6
6
|
"designTokens": "tokens.css",
|
|
7
7
|
|
|
8
8
|
"layouts": {
|
|
9
|
-
"default": {
|
|
10
|
-
"docs": {
|
|
11
|
-
"blog": {
|
|
9
|
+
"default": {},
|
|
10
|
+
"docs": {},
|
|
11
|
+
"blog": {}
|
|
12
12
|
},
|
|
13
13
|
|
|
14
|
-
"components": {
|
|
15
|
-
"Hint": { "component": "components/Hint.svelte" },
|
|
16
|
-
"CallToAction": { "component": "components/CallToAction.svelte" },
|
|
17
|
-
"Feature": { "component": "components/Feature.svelte" },
|
|
18
|
-
"FeatureDefinition": { "component": "components/Feature.svelte" },
|
|
19
|
-
"Grid": { "component": "components/Grid.svelte" },
|
|
20
|
-
"Steps": { "component": "components/Steps.svelte" },
|
|
21
|
-
"Step": { "component": "components/Steps.svelte" },
|
|
22
|
-
"TabGroup": { "component": "components/Tabs.svelte" },
|
|
23
|
-
"Tab": { "component": "components/Tabs.svelte" },
|
|
24
|
-
"CodeGroup": { "component": "components/CodeGroup.svelte" },
|
|
25
|
-
"Pricing": { "component": "components/Pricing.svelte" },
|
|
26
|
-
"Tier": { "component": "components/Pricing.svelte" },
|
|
27
|
-
"PageSection": { "component": "components/PageSection.svelte" },
|
|
28
|
-
"Nav": { "component": "components/Nav.svelte" },
|
|
29
|
-
"NavGroup": { "component": "components/Nav.svelte" },
|
|
30
|
-
"NavItem": { "component": "components/Nav.svelte" }
|
|
31
|
-
},
|
|
14
|
+
"components": {},
|
|
32
15
|
|
|
33
16
|
"unsupportedRuneBehavior": "passthrough"
|
|
34
17
|
}
|
package/svelte/registry.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Re-export base registry — lumina uses the same interactive components
|
|
2
|
-
export { registry } from '@refrakt-md/
|
|
2
|
+
export { registry } from '@refrakt-md/svelte';
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
interface Heading {
|
|
3
|
-
level: number;
|
|
4
|
-
text: string;
|
|
5
|
-
id: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
interface Props {
|
|
9
|
-
headings: Heading[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
let { headings }: Props = $props();
|
|
13
|
-
|
|
14
|
-
// Filter to h2 and h3 only
|
|
15
|
-
const filtered = $derived(headings.filter(h => h.level >= 2 && h.level <= 3));
|
|
16
|
-
</script>
|
|
17
|
-
|
|
18
|
-
<nav class="rf-on-this-page" data-scrollspy>
|
|
19
|
-
<p class="rf-on-this-page__title">On this page</p>
|
|
20
|
-
<ul class="rf-on-this-page__list">
|
|
21
|
-
{#each filtered as heading}
|
|
22
|
-
<li class="rf-on-this-page__item" data-level={heading.level}>
|
|
23
|
-
<a href="#{heading.id}">{heading.text}</a>
|
|
24
|
-
</li>
|
|
25
|
-
{/each}
|
|
26
|
-
</ul>
|
|
27
|
-
</nav>
|
|
28
|
-
|
|
29
|
-
<style>
|
|
30
|
-
.rf-on-this-page {
|
|
31
|
-
font-size: 0.8rem;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.rf-on-this-page__title {
|
|
35
|
-
font-size: 0.7rem;
|
|
36
|
-
font-weight: 600;
|
|
37
|
-
text-transform: uppercase;
|
|
38
|
-
letter-spacing: 0.05em;
|
|
39
|
-
color: var(--rf-color-muted, #64748b);
|
|
40
|
-
margin: 0 0 0.75rem;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.rf-on-this-page__list {
|
|
44
|
-
list-style: none;
|
|
45
|
-
margin: 0;
|
|
46
|
-
padding: 0;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.rf-on-this-page__item {
|
|
50
|
-
border-left: 2px solid transparent;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
.rf-on-this-page__item[data-level="3"] {
|
|
54
|
-
padding-left: 0.75rem;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.rf-on-this-page__item a {
|
|
58
|
-
display: block;
|
|
59
|
-
padding: 0.25rem 0.75rem;
|
|
60
|
-
color: var(--rf-color-muted, #64748b);
|
|
61
|
-
text-decoration: none;
|
|
62
|
-
line-height: 1.4;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.rf-on-this-page__item a:hover {
|
|
66
|
-
color: var(--rf-color-text, #1a1a2e);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.rf-on-this-page__item[data-active] {
|
|
70
|
-
border-left-color: var(--rf-color-primary, #0ea5e9);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
.rf-on-this-page__item[data-active] a {
|
|
74
|
-
color: var(--rf-color-primary, #0ea5e9);
|
|
75
|
-
}
|
|
76
|
-
</style>
|
|
@@ -1,173 +0,0 @@
|
|
|
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 →</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}
|
|
@@ -1,67 +0,0 @@
|
|
|
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>
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { Renderer } from '@refrakt-md/svelte';
|
|
3
|
-
import OnThisPage from '../components/OnThisPage.svelte';
|
|
4
|
-
|
|
5
|
-
let { title, regions, renderable, url, pages, headings, frontmatter }: {
|
|
6
|
-
title: string;
|
|
7
|
-
description: string;
|
|
8
|
-
regions: Record<string, { name: string; mode: string; content: any[] }>;
|
|
9
|
-
renderable: any;
|
|
10
|
-
url: string;
|
|
11
|
-
pages: Array<{ url: string; title: string; draft: boolean }>;
|
|
12
|
-
headings?: Array<{ level: number; text: string; id: string }>;
|
|
13
|
-
frontmatter?: Record<string, unknown>;
|
|
14
|
-
} = $props();
|
|
15
|
-
|
|
16
|
-
const hasNav = $derived(!!regions.nav);
|
|
17
|
-
|
|
18
|
-
// Show TOC when 2+ qualifying headings (h2/h3) and not opted out via frontmatter
|
|
19
|
-
const tocHeadings = $derived((headings ?? []).filter(h => h.level >= 2 && h.level <= 3));
|
|
20
|
-
const showToc = $derived(tocHeadings.length >= 2 && frontmatter?.toc !== false);
|
|
21
|
-
|
|
22
|
-
// Mobile panel state
|
|
23
|
-
let menuOpen = $state(false);
|
|
24
|
-
let navOpen = $state(false);
|
|
25
|
-
|
|
26
|
-
// Helpers to walk the serialized nav tree (same patterns as Nav.svelte)
|
|
27
|
-
function isTag(node: any): node is { $$mdtype: 'Tag'; name: string; attributes: Record<string, any>; children: any[] } {
|
|
28
|
-
return node !== null && typeof node === 'object' && !Array.isArray(node) && node.$$mdtype === 'Tag';
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function getTextContent(node: any): string {
|
|
32
|
-
if (typeof node === 'string') return node;
|
|
33
|
-
if (typeof node === 'number') return String(node);
|
|
34
|
-
if (isTag(node)) return node.children.map(getTextContent).join('');
|
|
35
|
-
if (Array.isArray(node)) return node.map(getTextContent).join('');
|
|
36
|
-
return '';
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Build slug → group title map from nav region
|
|
40
|
-
function buildNavMap(content: any[]): Map<string, string> {
|
|
41
|
-
const map = new Map<string, string>();
|
|
42
|
-
function walk(nodes: any[], groupTitle: string) {
|
|
43
|
-
for (const node of nodes) {
|
|
44
|
-
if (!isTag(node)) continue;
|
|
45
|
-
if (node.attributes.typeof === 'NavGroup') {
|
|
46
|
-
const heading = node.children.find((c: any) => isTag(c) && /^h[1-6]$/.test(c.name));
|
|
47
|
-
walk(node.children, heading ? getTextContent(heading) : '');
|
|
48
|
-
} else if (node.attributes.typeof === 'NavItem') {
|
|
49
|
-
const slugSpan = node.children.find((c: any) => isTag(c) && c.name === 'span' && c.attributes.property === 'slug');
|
|
50
|
-
if (slugSpan && groupTitle) map.set(getTextContent(slugSpan), groupTitle);
|
|
51
|
-
} else if (node.children) {
|
|
52
|
-
walk(node.children, groupTitle);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
walk(content, '');
|
|
57
|
-
return map;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Breadcrumb: look up current page slug in nav group headings
|
|
61
|
-
const pageSlug = $derived((url || '').split('/').filter(Boolean).pop() || '');
|
|
62
|
-
const navMap = $derived(regions.nav ? buildNavMap(regions.nav.content) : new Map());
|
|
63
|
-
const breadcrumbCategory = $derived(navMap.get(pageSlug) || '');
|
|
64
|
-
|
|
65
|
-
function closeMenu() { menuOpen = false; }
|
|
66
|
-
|
|
67
|
-
function onKeydown(e: KeyboardEvent) {
|
|
68
|
-
if (e.key === 'Escape') {
|
|
69
|
-
menuOpen = false;
|
|
70
|
-
navOpen = false;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Close panels on navigation
|
|
75
|
-
$effect(() => {
|
|
76
|
-
url;
|
|
77
|
-
menuOpen = false;
|
|
78
|
-
navOpen = false;
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// Lock body scroll when a panel is open
|
|
82
|
-
$effect(() => {
|
|
83
|
-
document.body.style.overflow = (menuOpen || navOpen) ? 'hidden' : '';
|
|
84
|
-
});
|
|
85
|
-
</script>
|
|
86
|
-
|
|
87
|
-
<svelte:window onkeydown={onKeydown} />
|
|
88
|
-
|
|
89
|
-
{#if regions.header}
|
|
90
|
-
<header class="rf-docs-header">
|
|
91
|
-
<div class="rf-docs-header__inner">
|
|
92
|
-
<Renderer node={regions.header.content} />
|
|
93
|
-
<button class="rf-mobile-menu-btn" onclick={() => { navOpen = false; menuOpen = true; }} aria-label="Open menu">
|
|
94
|
-
<svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
|
|
95
|
-
<circle cx="10" cy="4" r="1.5"/>
|
|
96
|
-
<circle cx="10" cy="10" r="1.5"/>
|
|
97
|
-
<circle cx="10" cy="16" r="1.5"/>
|
|
98
|
-
</svg>
|
|
99
|
-
</button>
|
|
100
|
-
</div>
|
|
101
|
-
</header>
|
|
102
|
-
{/if}
|
|
103
|
-
|
|
104
|
-
<!-- Mobile header menu panel -->
|
|
105
|
-
{#if menuOpen}
|
|
106
|
-
<div class="rf-mobile-panel" role="dialog" aria-label="Navigation menu">
|
|
107
|
-
<div class="rf-mobile-panel__header">
|
|
108
|
-
<span class="rf-mobile-panel__title">Menu</span>
|
|
109
|
-
<button class="rf-mobile-panel__close" onclick={closeMenu} aria-label="Close menu">
|
|
110
|
-
<svg width="20" height="20" viewBox="0 0 20 20" stroke="currentColor" stroke-width="2" fill="none">
|
|
111
|
-
<line x1="4" y1="4" x2="16" y2="16"/><line x1="16" y1="4" x2="4" y2="16"/>
|
|
112
|
-
</svg>
|
|
113
|
-
</button>
|
|
114
|
-
</div>
|
|
115
|
-
<nav class="rf-mobile-panel__nav">
|
|
116
|
-
{#if regions.header}
|
|
117
|
-
<Renderer node={regions.header.content} />
|
|
118
|
-
{/if}
|
|
119
|
-
</nav>
|
|
120
|
-
</div>
|
|
121
|
-
{/if}
|
|
122
|
-
|
|
123
|
-
<!-- Mobile docs toolbar with hamburger + breadcrumbs -->
|
|
124
|
-
{#if hasNav}
|
|
125
|
-
<div class="rf-docs-toolbar">
|
|
126
|
-
<button class="rf-docs-toolbar__hamburger" onclick={() => { menuOpen = false; navOpen = !navOpen; }} aria-label="Toggle navigation">
|
|
127
|
-
<svg width="20" height="20" viewBox="0 0 20 20" stroke="currentColor" stroke-width="2" fill="none">
|
|
128
|
-
<line x1="3" y1="5" x2="17" y2="5"/><line x1="3" y1="10" x2="17" y2="10"/><line x1="3" y1="15" x2="17" y2="15"/>
|
|
129
|
-
</svg>
|
|
130
|
-
</button>
|
|
131
|
-
<div class="rf-docs-toolbar__breadcrumb">
|
|
132
|
-
{#if breadcrumbCategory}
|
|
133
|
-
<span class="rf-docs-breadcrumb-category">{breadcrumbCategory}</span>
|
|
134
|
-
<span class="rf-docs-breadcrumb-sep">›</span>
|
|
135
|
-
{/if}
|
|
136
|
-
<span class="rf-docs-breadcrumb-page">{title}</span>
|
|
137
|
-
</div>
|
|
138
|
-
</div>
|
|
139
|
-
{/if}
|
|
140
|
-
|
|
141
|
-
<!-- Mobile nav panel -->
|
|
142
|
-
{#if navOpen}
|
|
143
|
-
<div class="rf-mobile-panel rf-mobile-panel--nav" role="dialog" aria-label="Page navigation">
|
|
144
|
-
<div class="rf-mobile-panel__body">
|
|
145
|
-
{#if regions.nav}
|
|
146
|
-
<Renderer node={regions.nav.content} />
|
|
147
|
-
{/if}
|
|
148
|
-
</div>
|
|
149
|
-
</div>
|
|
150
|
-
{/if}
|
|
151
|
-
|
|
152
|
-
{#if regions.nav}
|
|
153
|
-
<aside class="rf-docs-sidebar">
|
|
154
|
-
<Renderer node={regions.nav.content} />
|
|
155
|
-
</aside>
|
|
156
|
-
{/if}
|
|
157
|
-
|
|
158
|
-
<main class="rf-docs-content" class:rf-docs-content--has-nav={hasNav}>
|
|
159
|
-
<div class="rf-docs-content__inner" class:rf-docs-content__inner--has-toc={showToc}>
|
|
160
|
-
<div class="rf-docs-content__body">
|
|
161
|
-
<Renderer node={renderable} />
|
|
162
|
-
</div>
|
|
163
|
-
{#if showToc}
|
|
164
|
-
<aside class="rf-docs-toc">
|
|
165
|
-
<OnThisPage headings={tocHeadings} />
|
|
166
|
-
</aside>
|
|
167
|
-
{/if}
|
|
168
|
-
</div>
|
|
169
|
-
</main>
|