domma-cms 0.6.12 → 0.6.14
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/admin/css/admin.css +1 -1
- package/admin/js/lib/markdown-toolbar.js +39 -35
- package/admin/js/views/page-editor.js +27 -25
- package/config/plugins.json +4 -0
- package/config/site.json +2 -2
- package/package.json +2 -2
- package/plugins/analytics/stats.json +2 -2
- package/plugins/site-search/admin/templates/site-search.html +67 -0
- package/plugins/site-search/admin/views/site-search.js +45 -0
- package/plugins/site-search/config.js +10 -0
- package/plugins/site-search/plugin.js +188 -0
- package/plugins/site-search/plugin.json +37 -0
- package/plugins/site-search/public/inject-body.html +17 -0
- package/plugins/site-search/public/inject-head.html +1 -0
- package/plugins/site-search/public/search.css +1 -0
- package/plugins/site-search/public/search.js +1 -0
- package/public/css/site.css +1 -1
- package/server/services/markdown.js +76 -6
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"/":
|
|
2
|
+
"/": 140,
|
|
3
3
|
"/about": 71,
|
|
4
4
|
"/blog": 36,
|
|
5
5
|
"/contact": 30,
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"/resources/dependencies": 2,
|
|
17
17
|
"/resources/components": 6,
|
|
18
18
|
"/gdpr": 3,
|
|
19
|
-
"/scratch":
|
|
19
|
+
"/scratch": 69,
|
|
20
20
|
"/getting-started": 3,
|
|
21
21
|
"/resources/pro": 1,
|
|
22
22
|
"/todo": 23,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="search"></span> Site Search</h1>
|
|
3
|
+
<div>
|
|
4
|
+
<button id="save-settings-btn" class="btn btn-primary">
|
|
5
|
+
<span data-icon="save"></span> Save
|
|
6
|
+
</button>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div class="card mb-4">
|
|
11
|
+
<div class="card-header"><h2>Settings</h2></div>
|
|
12
|
+
<div class="card-body">
|
|
13
|
+
|
|
14
|
+
<div class="row mb-3">
|
|
15
|
+
<div class="col">
|
|
16
|
+
<label class="form-label">Search placeholder text</label>
|
|
17
|
+
<input id="field-placeholder" type="text" class="form-input" placeholder="Search pages...">
|
|
18
|
+
<span class="form-hint">Displayed inside the search input when empty.</span>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="row mb-3">
|
|
23
|
+
<div class="col">
|
|
24
|
+
<label class="form-check-label">
|
|
25
|
+
<input id="field-keyboard-shortcut" type="checkbox">
|
|
26
|
+
Enable keyboard shortcut (Ctrl+K / ⌘K)
|
|
27
|
+
</label>
|
|
28
|
+
<span class="form-hint">Allows visitors to open the search overlay using a keyboard shortcut.</span>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="row mb-3">
|
|
33
|
+
<div class="col-6">
|
|
34
|
+
<label class="form-label">Max results</label>
|
|
35
|
+
<input id="field-max-results" type="number" class="form-input" min="1" max="50" placeholder="10">
|
|
36
|
+
<span class="form-hint">Maximum number of results to return per search (1–50).</span>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="col-6">
|
|
39
|
+
<label class="form-label">Minimum query length</label>
|
|
40
|
+
<input id="field-min-query-length" type="number" class="form-input" min="1" max="5" placeholder="2">
|
|
41
|
+
<span class="form-hint">Minimum characters required before searching (1–5).</span>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="row">
|
|
46
|
+
<div class="col-6">
|
|
47
|
+
<label class="form-label">Debounce delay (ms)</label>
|
|
48
|
+
<input id="field-debounce-ms" type="number" class="form-input" min="100" max="1000" placeholder="300">
|
|
49
|
+
<span class="form-hint">Delay after typing stops before search fires (100–1000ms).</span>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div class="card mb-4">
|
|
57
|
+
<div class="card-header"><h2>How it works</h2></div>
|
|
58
|
+
<div class="card-body">
|
|
59
|
+
<p class="text-muted mb-2">Site Search adds a search icon to the public navbar. Visitors can click it or use
|
|
60
|
+
the keyboard shortcut to open a full-screen overlay with live results.</p>
|
|
61
|
+
<ul class="text-muted" style="padding-left:1.25rem;line-height:1.8;">
|
|
62
|
+
<li>Results are weighted: title matches score highest, followed by tags, description, and page content.</li>
|
|
63
|
+
<li>Draft pages and private pages are excluded from results.</li>
|
|
64
|
+
<li>Use <kbd>↑</kbd> <kbd>↓</kbd> to navigate results, <kbd>↵</kbd> to open, <kbd>Esc</kbd> to close.</li>
|
|
65
|
+
</ul>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Site Search Plugin — Admin Settings View
|
|
3
|
+
*/
|
|
4
|
+
import {apiRequest} from '/admin/js/api.js';
|
|
5
|
+
|
|
6
|
+
export const siteSearchView = {
|
|
7
|
+
templateUrl: '/plugins/site-search/admin/templates/site-search.html',
|
|
8
|
+
|
|
9
|
+
async onMount($container) {
|
|
10
|
+
let settings = {};
|
|
11
|
+
try {
|
|
12
|
+
settings = await apiRequest('/plugins/site-search/settings');
|
|
13
|
+
} catch {
|
|
14
|
+
E.toast('Could not load settings.', {type: 'error'});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
$container.find('#field-placeholder').val(settings.placeholder || 'Search pages...');
|
|
18
|
+
$container.find('#field-keyboard-shortcut').prop('checked', settings.keyboardShortcut !== false);
|
|
19
|
+
$container.find('#field-max-results').val(settings.maxResults ?? 10);
|
|
20
|
+
$container.find('#field-min-query-length').val(settings.minQueryLength ?? 2);
|
|
21
|
+
$container.find('#field-debounce-ms').val(settings.debounceMs ?? 300);
|
|
22
|
+
|
|
23
|
+
$container.find('#save-settings-btn').off('click').on('click', async () => {
|
|
24
|
+
const data = {
|
|
25
|
+
placeholder: $container.find('#field-placeholder').val() || 'Search pages...',
|
|
26
|
+
keyboardShortcut: $container.find('#field-keyboard-shortcut').prop('checked'),
|
|
27
|
+
maxResults: parseInt($container.find('#field-max-results').val(), 10) || 10,
|
|
28
|
+
minQueryLength: parseInt($container.find('#field-min-query-length').val(), 10) || 2,
|
|
29
|
+
debounceMs: parseInt($container.find('#field-debounce-ms').val(), 10) || 300
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
await apiRequest('/plugins/site-search/settings', {
|
|
34
|
+
method: 'PUT',
|
|
35
|
+
body: JSON.stringify(data)
|
|
36
|
+
});
|
|
37
|
+
E.toast('Settings saved.', {type: 'success'});
|
|
38
|
+
} catch {
|
|
39
|
+
E.toast('Failed to save settings.', {type: 'error'});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
Domma.icons.scan();
|
|
44
|
+
}
|
|
45
|
+
};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Site Search Plugin — Server
|
|
3
|
+
*
|
|
4
|
+
* Endpoints (prefix: /api/plugins/site-search):
|
|
5
|
+
* GET /search?q=term — public: full-text search across all published pages
|
|
6
|
+
* GET /settings — public: return plugin settings (used by inject script)
|
|
7
|
+
* PUT /settings — admin-auth: save user overrides
|
|
8
|
+
*/
|
|
9
|
+
import {listPages} from '../../server/services/content.js';
|
|
10
|
+
import {getPluginSettings, savePluginState} from '../../server/services/plugins.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Strip markdown syntax and shortcodes from content for plain-text searching.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} content
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
function stripMarkdown(content) {
|
|
19
|
+
if (!content) return '';
|
|
20
|
+
return content
|
|
21
|
+
// Remove shortcodes (self-closing and wrapping)
|
|
22
|
+
.replace(/\[[a-z][^\]]*\/\]/gi, '')
|
|
23
|
+
.replace(/\[[a-z][^\]]*\][\s\S]*?\[\/[a-z]+\]/gi, '')
|
|
24
|
+
.replace(/\[[a-z][^\]]*\]/gi, '')
|
|
25
|
+
// Remove HTML tags
|
|
26
|
+
.replace(/<[^>]+>/g, '')
|
|
27
|
+
// Remove headings (keep text)
|
|
28
|
+
.replace(/^#{1,6}\s+/gm, '')
|
|
29
|
+
// Remove bold / italic
|
|
30
|
+
.replace(/\*{1,3}([^*]+)\*{1,3}/g, '$1')
|
|
31
|
+
.replace(/_{1,3}([^_]+)_{1,3}/g, '$1')
|
|
32
|
+
// Remove images
|
|
33
|
+
.replace(/!\[[^\]]*\]\([^)]*\)/g, '')
|
|
34
|
+
// Remove links (keep text)
|
|
35
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
|
|
36
|
+
// Remove code blocks
|
|
37
|
+
.replace(/```[\s\S]*?```/g, '')
|
|
38
|
+
.replace(/`[^`]+`/g, '')
|
|
39
|
+
// Remove blockquotes marker
|
|
40
|
+
.replace(/^>\s+/gm, '')
|
|
41
|
+
// Collapse whitespace
|
|
42
|
+
.replace(/\s+/g, ' ')
|
|
43
|
+
.trim();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Build a snippet of ~length chars around the first occurrence of term in content.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} rawContent - raw markdown content
|
|
50
|
+
* @param {string} term
|
|
51
|
+
* @param {number} length
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
function buildSnippet(rawContent, term, length = 120) {
|
|
55
|
+
const text = stripMarkdown(rawContent);
|
|
56
|
+
if (!text) return '';
|
|
57
|
+
|
|
58
|
+
const lower = text.toLowerCase();
|
|
59
|
+
const idx = lower.indexOf(term.toLowerCase());
|
|
60
|
+
|
|
61
|
+
if (idx === -1) {
|
|
62
|
+
return text.length > length ? text.slice(0, length) + '…' : text;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const half = Math.floor(length / 2);
|
|
66
|
+
const start = Math.max(0, idx - half);
|
|
67
|
+
const end = Math.min(text.length, start + length);
|
|
68
|
+
const snippet = text.slice(start, end);
|
|
69
|
+
|
|
70
|
+
return (start > 0 ? '…' : '') + snippet + (end < text.length ? '…' : '');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Score a page against a search query using weighted field matching.
|
|
75
|
+
*
|
|
76
|
+
* @param {object} page
|
|
77
|
+
* @param {string} query
|
|
78
|
+
* @param {string[]} words - query split into words
|
|
79
|
+
* @returns {number}
|
|
80
|
+
*/
|
|
81
|
+
function scorePage(page, query, words) {
|
|
82
|
+
let score = 0;
|
|
83
|
+
const title = (page.title || '').toLowerCase();
|
|
84
|
+
const description = (page.description || '').toLowerCase();
|
|
85
|
+
const tagsRaw = Array.isArray(page.tags) ? page.tags : [];
|
|
86
|
+
const tags = tagsRaw.map(t => t.toLowerCase());
|
|
87
|
+
const stripped = stripMarkdown(page.content || '').toLowerCase();
|
|
88
|
+
const q = query.toLowerCase();
|
|
89
|
+
|
|
90
|
+
// Title scoring
|
|
91
|
+
if (title === q) {
|
|
92
|
+
score += 100;
|
|
93
|
+
} else if (title.includes(q)) {
|
|
94
|
+
score += 60;
|
|
95
|
+
} else if (words.every(w => title.includes(w))) {
|
|
96
|
+
score += 40;
|
|
97
|
+
} else if (words.some(w => title.includes(w))) {
|
|
98
|
+
score += 20;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Tags scoring
|
|
102
|
+
if (tags.includes(q)) {
|
|
103
|
+
score += 30;
|
|
104
|
+
} else if (tags.some(t => t.includes(q))) {
|
|
105
|
+
score += 20;
|
|
106
|
+
} else if (words.every(w => tags.some(t => t.includes(w)))) {
|
|
107
|
+
score += 15;
|
|
108
|
+
} else if (words.some(w => tags.some(t => t.includes(w)))) {
|
|
109
|
+
score += 8;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Description scoring
|
|
113
|
+
if (description.includes(q)) {
|
|
114
|
+
score += 25;
|
|
115
|
+
} else if (words.some(w => description.includes(w))) {
|
|
116
|
+
score += 10;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Content scoring
|
|
120
|
+
if (stripped.includes(q)) {
|
|
121
|
+
score += 10;
|
|
122
|
+
} else if (words.every(w => stripped.includes(w))) {
|
|
123
|
+
score += 5;
|
|
124
|
+
} else if (words.some(w => stripped.includes(w))) {
|
|
125
|
+
score += 2;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return score;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export default async function siteSearchPlugin(fastify, options) {
|
|
132
|
+
const {authenticate, requireAdmin} = options.auth;
|
|
133
|
+
|
|
134
|
+
// -------------------------------------------------------------------------
|
|
135
|
+
// Public search endpoint
|
|
136
|
+
// -------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
fastify.get('/search', async (request, reply) => {
|
|
139
|
+
const settings = getPluginSettings('site-search');
|
|
140
|
+
const minLen = settings.minQueryLength ?? 2;
|
|
141
|
+
const maxResults = settings.maxResults ?? 10;
|
|
142
|
+
|
|
143
|
+
const q = (request.query.q || '').trim();
|
|
144
|
+
if (q.length < minLen) {
|
|
145
|
+
return reply.status(400).send({error: `Query must be at least ${minLen} characters`});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const pages = await listPages();
|
|
149
|
+
const words = q.toLowerCase().split(/\s+/).filter(Boolean);
|
|
150
|
+
|
|
151
|
+
const results = [];
|
|
152
|
+
for (const page of pages) {
|
|
153
|
+
// Filter out drafts and private pages
|
|
154
|
+
if (page.status === 'draft') continue;
|
|
155
|
+
if (page.visibility === 'private') continue;
|
|
156
|
+
|
|
157
|
+
const score = scorePage(page, q, words);
|
|
158
|
+
if (score === 0) continue;
|
|
159
|
+
|
|
160
|
+
results.push({
|
|
161
|
+
title: page.title || 'Untitled',
|
|
162
|
+
url: page.urlPath,
|
|
163
|
+
description: page.description || '',
|
|
164
|
+
tags: Array.isArray(page.tags) ? page.tags : [],
|
|
165
|
+
snippet: buildSnippet(page.content || '', q),
|
|
166
|
+
score
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
results.sort((a, b) => b.score - a.score);
|
|
171
|
+
|
|
172
|
+
return {results: results.slice(0, maxResults)};
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// -------------------------------------------------------------------------
|
|
176
|
+
// Settings routes
|
|
177
|
+
// -------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
fastify.get('/settings', async () => {
|
|
180
|
+
return getPluginSettings('site-search');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
fastify.put('/settings', {preHandler: [authenticate, requireAdmin]}, async (request) => {
|
|
184
|
+
const body = request.body || {};
|
|
185
|
+
savePluginState('site-search', {settings: body});
|
|
186
|
+
return {ok: true};
|
|
187
|
+
});
|
|
188
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "site-search",
|
|
3
|
+
"displayName": "Site Search",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Full-text search for the public site. Search icon in navbar, Cmd+K shortcut, weighted results.",
|
|
6
|
+
"author": "Darryl Waterhouse",
|
|
7
|
+
"date": "2026-03-19",
|
|
8
|
+
"icon": "search",
|
|
9
|
+
"admin": {
|
|
10
|
+
"sidebar": [
|
|
11
|
+
{
|
|
12
|
+
"id": "site-search",
|
|
13
|
+
"text": "Site Search",
|
|
14
|
+
"icon": "search",
|
|
15
|
+
"url": "#/plugins/site-search",
|
|
16
|
+
"section": "#/plugins/site-search"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"routes": [
|
|
20
|
+
{
|
|
21
|
+
"path": "/plugins/site-search",
|
|
22
|
+
"view": "plugin-site-search",
|
|
23
|
+
"title": "Site Search - Domma CMS"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"views": {
|
|
27
|
+
"plugin-site-search": {
|
|
28
|
+
"entry": "site-search/admin/views/site-search.js",
|
|
29
|
+
"exportName": "siteSearchView"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"inject": {
|
|
34
|
+
"headLate": "public/inject-head.html",
|
|
35
|
+
"bodyEnd": "public/inject-body.html"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!-- site-search: load settings then initialise search UI -->
|
|
2
|
+
<script>
|
|
3
|
+
(function () {
|
|
4
|
+
fetch('/api/plugins/site-search/settings')
|
|
5
|
+
.then(function (r) {
|
|
6
|
+
return r.json();
|
|
7
|
+
})
|
|
8
|
+
.then(function (cfg) {
|
|
9
|
+
window.__SITE_SEARCH__ = cfg;
|
|
10
|
+
var s = document.createElement('script');
|
|
11
|
+
s.src = '/plugins/site-search/public/search.js';
|
|
12
|
+
document.body.appendChild(s);
|
|
13
|
+
})
|
|
14
|
+
.catch(function () {
|
|
15
|
+
});
|
|
16
|
+
}());
|
|
17
|
+
</script>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<link rel="stylesheet" href="/plugins/site-search/public/search.css">
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.site-search-trigger{display:inline-flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:6px 8px;border-radius:var(--dm-radius, 6px);color:inherit;opacity:.75;transition:opacity .15s,background .15s;font-size:var(--dm-font-size-sm, 14px)}.site-search-trigger:hover{opacity:1;background:var(--dm-surface-hover, rgba(0, 0, 0, .06))}.site-search-trigger svg,.site-search-trigger span[data-icon]{width:18px;height:18px}.site-search-shortcut-hint{display:inline-block;font-size:11px;line-height:1;padding:2px 5px;border-radius:4px;border:1px solid var(--dm-border, rgba(0, 0, 0, .15));color:var(--dm-text-muted, #888);margin-left:4px;vertical-align:middle;font-family:var(--dm-font-mono, monospace)}.site-search-overlay{position:fixed;inset:0;z-index:10000;background:#00000073;backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);display:flex;align-items:flex-start;justify-content:center;padding-top:10vh;animation:ss-overlay-in .15s ease}@keyframes ss-overlay-in{0%{opacity:0}to{opacity:1}}.site-search-panel{width:100%;max-width:600px;margin:0 16px;border-radius:var(--dm-radius-lg, 10px);box-shadow:0 20px 60px #00000059;background:var(--dm-bg, #fff);overflow:hidden;animation:ss-panel-in .15s ease}@keyframes ss-panel-in{0%{transform:translateY(-12px);opacity:0}to{transform:translateY(0);opacity:1}}.site-search-header{display:flex;align-items:center;padding:12px 16px;border-bottom:1px solid var(--dm-border, rgba(0, 0, 0, .1));gap:8px}.site-search-header span[data-icon],.site-search-header svg{width:18px;height:18px;flex-shrink:0;opacity:.5}.site-search-input{flex:1;border:none;outline:none;background:transparent;font-size:var(--dm-font-size-lg, 16px);color:var(--dm-text, #111);font-family:var(--dm-font-sans, sans-serif);padding:0}.site-search-input::placeholder{color:var(--dm-text-muted, #aaa)}.site-search-close-btn{display:inline-flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;color:var(--dm-text-muted, #999);border-radius:var(--dm-radius, 6px);padding:4px 8px;font-size:12px;font-family:var(--dm-font-mono, monospace);border:1px solid var(--dm-border, rgba(0, 0, 0, .15));transition:color .15s;flex-shrink:0}.site-search-close-btn:hover{color:var(--dm-text, #111)}.site-search-results{max-height:60vh;overflow-y:auto;padding:8px 0}.site-search-result{display:block;padding:10px 16px;text-decoration:none;color:var(--dm-text, #111);border-left:3px solid transparent;transition:background .1s,border-color .1s;cursor:pointer}.site-search-result:hover,.site-search-result.active{background:var(--dm-surface-hover, rgba(0, 0, 0, .05));border-left-color:var(--dm-primary, #5b6af0)}.site-search-result-title{font-weight:600;font-size:var(--dm-font-size-sm, 14px);margin-bottom:2px;color:var(--dm-text, #111)}.site-search-result-snippet{font-size:12px;color:var(--dm-text-muted, #888);line-height:1.4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.site-search-result-snippet mark{background:var(--dm-primary-subtle, rgba(91, 106, 240, .15));color:inherit;border-radius:2px;padding:0 1px}.site-search-loading,.site-search-empty{padding:24px 16px;text-align:center;color:var(--dm-text-muted, #888);font-size:var(--dm-font-size-sm, 14px)}.site-search-loading-dots{display:inline-flex;gap:4px}.site-search-loading-dots span{width:6px;height:6px;border-radius:50%;background:var(--dm-text-muted, #aaa);animation:ss-dot-pulse 1.2s ease-in-out infinite}.site-search-loading-dots span:nth-child(2){animation-delay:.2s}.site-search-loading-dots span:nth-child(3){animation-delay:.4s}@keyframes ss-dot-pulse{0%,80%,to{transform:scale(.6);opacity:.4}40%{transform:scale(1);opacity:1}}.site-search-results:not(:empty){border-top:1px solid var(--dm-border, rgba(0, 0, 0, .07))}.site-search-footer{padding:6px 16px;border-top:1px solid var(--dm-border, rgba(0, 0, 0, .07));display:flex;gap:12px;font-size:11px;color:var(--dm-text-muted, #aaa)}.site-search-footer kbd{display:inline-block;padding:1px 4px;border-radius:3px;border:1px solid var(--dm-border, rgba(0, 0, 0, .2));font-family:var(--dm-font-mono, monospace);font-size:10px}@media(max-width:640px){.site-search-overlay{padding-top:5vh;align-items:flex-start}.site-search-panel{margin:0 8px;border-radius:var(--dm-radius, 6px)}.site-search-results{max-height:70vh}.site-search-shortcut-hint{display:none}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(){"use strict";var f=window.__SITE_SEARCH__||{},S=f.placeholder||"Search pages...",b=f.keyboardShortcut!==!1,N=f.debounceMs||300,d=null,o=null,c=null,m=null,u=!1;function h(){var e=document.querySelector("#site-navbar, .navbar");if(e){var t=e.querySelector(".navbar-actions");if(!t){var a=e.querySelector(".navbar-container")||e;t=document.createElement("div"),t.className="navbar-actions",a.appendChild(t)}if(!t.querySelector(".site-search-trigger")){var s=/Mac|iPhone|iPad|iPod/.test(navigator.platform||navigator.userAgent),r=s?"\u2318K":"Ctrl+K",n=document.createElement("button");n.className="navbar-action-link site-search-trigger",n.setAttribute("aria-label","Search"),n.setAttribute("type","button");var i=document.createElement("span");if(i.setAttribute("data-icon","search"),n.appendChild(i),b){var l=document.createElement("span");l.className="site-search-shortcut-hint",l.textContent=r,n.appendChild(l)}t.insertBefore(n,t.firstChild),n.addEventListener("click",E),window.Domma&&Domma.icons&&Domma.icons.scan&&Domma.icons.scan(n)}}}function g(){if(document.querySelector("#site-navbar .navbar-actions, .navbar .navbar-actions")){h();return}var e=new MutationObserver(function(){var t=document.querySelector("#site-navbar, .navbar");t&&(e.disconnect(),h())});e.observe(document.body,{childList:!0,subtree:!0}),setTimeout(function(){e.disconnect(),h()},5e3)}function w(){var e=document.createElement("div");e.className="site-search-overlay",e.setAttribute("role","dialog"),e.setAttribute("aria-modal","true"),e.setAttribute("aria-label","Site search");var t=document.createElement("div");t.className="site-search-panel",t.setAttribute("role","search");var a=document.createElement("div");a.className="site-search-header";var s=document.createElement("span");s.setAttribute("data-icon","search"),a.appendChild(s),o=document.createElement("input"),o.className="site-search-input",o.type="search",o.setAttribute("autocomplete","off"),o.setAttribute("autocorrect","off"),o.setAttribute("spellcheck","false"),o.setAttribute("aria-label","Search"),o.placeholder=S,a.appendChild(o);var r=document.createElement("button");r.className="site-search-close-btn",r.setAttribute("type","button"),r.setAttribute("aria-label","Close search"),r.textContent="Esc",a.appendChild(r),t.appendChild(a),c=document.createElement("div"),c.className="site-search-results",c.setAttribute("role","listbox"),c.setAttribute("aria-live","polite"),t.appendChild(c);var n=document.createElement("div");return n.className="site-search-footer",n.innerHTML="<span><kbd>\u2191</kbd><kbd>\u2193</kbd> navigate</span><span><kbd>\u21B5</kbd> open</span><span><kbd>Esc</kbd> close</span>",t.appendChild(n),e.appendChild(t),e.addEventListener("click",function(i){i.target===e&&v()}),r.addEventListener("click",v),o.addEventListener("input",function(){clearTimeout(m);var i=o.value.trim();if(!i){C();return}m=setTimeout(function(){k(i)},N)}),o.addEventListener("keydown",D),window.Domma&&Domma.icons&&Domma.icons.scan&&Domma.icons.scan(a),e}function E(){u||(u=!0,d=w(),document.body.appendChild(d),document.body.style.overflow="hidden",setTimeout(function(){o&&o.focus()},30))}function v(){u&&(u=!1,clearTimeout(m),d&&d.parentNode&&d.parentNode.removeChild(d),d=null,o=null,c=null,document.body.style.overflow="")}function k(e){c&&(L(),fetch("/api/plugins/site-search/search?q="+encodeURIComponent(e)).then(function(t){return t.json()}).then(function(t){T(t.results||[],e)}).catch(function(){C();var t=document.createElement("div");t.className="site-search-empty",t.textContent="Search unavailable. Please try again.",c.appendChild(t)}))}function L(){c&&(c.innerHTML='<div class="site-search-loading"><div class="site-search-loading-dots"><span></span><span></span><span></span></div></div>')}function C(){c&&(c.innerHTML="")}function T(e,t){if(c){if(c.innerHTML="",!e.length){var a=document.createElement("div");a.className="site-search-empty",a.textContent='No results found for "'+t+'"',c.appendChild(a);return}for(var s=document.createDocumentFragment(),r=0;r<e.length;r++){var n=e[r],i=document.createElement("a");i.className="site-search-result",i.href=n.url||"/",i.setAttribute("role","option"),i.setAttribute("tabindex","-1"),i.setAttribute("data-index",String(r));var l=document.createElement("div");if(l.className="site-search-result-title",y(l,n.title||"Untitled",t),i.appendChild(l),n.snippet){var p=document.createElement("div");p.className="site-search-result-snippet",y(p,n.snippet,t),i.appendChild(p)}i.addEventListener("click",v),s.appendChild(i)}c.appendChild(s)}}function y(e,t,a){if(!a){e.textContent=t;return}var s=a.trim().split(/\s+/).filter(Boolean);if(!s.length){e.textContent=t;return}var r;try{r=new RegExp("("+s.map(function(p){return p.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}).join("|")+")","gi")}catch{e.textContent=t;return}for(var n=t.split(r),i=0;i<n.length;i++){if(r.test(n[i])){var l=document.createElement("mark");l.textContent=n[i],e.appendChild(l)}else e.appendChild(document.createTextNode(n[i]));r.lastIndex=0}}function D(e){if(e.key==="Escape"){e.preventDefault(),v();return}if(c){var t=c.querySelectorAll(".site-search-result");if(t.length){for(var a=c.querySelector(".site-search-result.active"),s=-1,r=0;r<t.length;r++)if(t[r]===a){s=r;break}if(e.key==="ArrowDown")e.preventDefault(),A(t,s<t.length-1?s+1:0);else if(e.key==="ArrowUp")e.preventDefault(),A(t,s>0?s-1:t.length-1);else if(e.key==="Enter"&&a){e.preventDefault();var n=a.getAttribute("href");v(),window.location.href=n}}}}function A(e,t){for(var a=0;a<e.length;a++)e[a].classList.toggle("active",a===t);e[t]&&e[t].scrollIntoView({block:"nearest"})}b&&document.addEventListener("keydown",function(e){if((e.metaKey||e.ctrlKey)&&e.key==="k"){var t=document.activeElement&&document.activeElement.tagName;if((t==="INPUT"||t==="TEXTAREA"||t==="SELECT")&&!u)return;e.preventDefault(),u?v():E()}}),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",g):g()})();
|
package/public/css/site.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
body,button,input,select,textarea{font-family:Roboto,sans-serif}.site-main{min-height:calc(100vh - 60px);padding-top:2rem;padding-bottom:4rem}.site-main.with-sidebar{display:grid;grid-template-columns:260px 1fr;gap:0}.site-sidebar{min-height:100%;border-right:1px solid var(--border-color, rgba(255,255,255,.08))}.site-content{overflow:hidden}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}.container{max-width:860px;margin:0 auto;padding:0 1.5rem}.page-title{font-size:2rem;font-weight:700;margin-bottom:1.5rem;line-height:1.2}.page-body{line-height:1.7;font-size:1rem}.page-body h1,.page-body h2,.page-body h3,.page-body h4{margin-top:2rem;margin-bottom:.75rem;font-weight:600}.page-body h2{font-size:1.5rem}.page-body h3{font-size:1.25rem}.page-body p{margin-bottom:1rem}.page-body ul,.page-body ol{margin-bottom:1rem;padding-left:1.5rem}.page-body a{color:var(--primary, #5b8cff)}.page-body a:hover{text-decoration:underline}.page-body code{font-family:Fira Code,Courier New,monospace;font-size:.9em;background:#ffffff0f;padding:.15em .35em;border-radius:3px}.page-body pre{background:#0000004d;border:1px solid rgba(255,255,255,.08);border-radius:6px;padding:1rem;overflow-x:auto;margin-bottom:1rem}.page-body pre code{background:none;padding:0}.page-body img{max-width:100%;border-radius:6px}.page-body blockquote{border-left:3px solid var(--primary, #5b8cff);margin:1.5rem 0;padding:.75rem 1rem;background:#5b8cff0f;border-radius:0 6px 6px 0}h3.accordion-header{margin:0}.accordion-button{all:unset;display:flex;align-items:center;justify-content:space-between;width:100%;cursor:pointer;font:inherit}.page-body .card-header h2{margin:0;font-size:1rem;font-weight:600;line-height:1.4}.card[data-collapsible] .card-header{cursor:pointer;user-select:none;display:flex;align-items:center;justify-content:space-between}.card[data-collapsible] .card-header:after{content:"\25be";font-size:1.1em;line-height:1;display:inline-block;transition:transform .25s ease;flex-shrink:0}.card[data-collapsible].is-collapsed .card-header:after{transform:rotate(-90deg)}.card[data-collapsible] .card-body{overflow:hidden;max-height:4000px;opacity:1;transition:max-height .3s ease,opacity .25s ease}.card[data-collapsible].is-collapsed .card-body{max-height:0;opacity:0}.navbar-link span[data-icon],.navbar-link svg,.navbar-dropdown-toggle span[data-icon],.navbar-dropdown-toggle svg,.navbar-dropdown-item span[data-icon],.navbar-dropdown-item svg{width:13px!important;height:13px!important;margin-right:10px!important}.navbar-dropdown-toggle{font-size:var(--dm-font-size-base)}@media(min-width:993px){.navbar-dropdown-toggle{font-size:var(--dm-font-size-sm)}}@media(min-width:1201px){.navbar-dropdown-toggle{font-size:var(--dm-font-size-xs)}}.dm-reduced-motion *,.dm-reduced-motion *:before,.dm-reduced-motion *:after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important;scroll-behavior:auto!important}.page-footer{border-top:1px solid var(--border-color, rgba(255,255,255,.08));padding:1.5rem 0}.footer-inner{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:1rem}.footer-inner p{margin:0;color:var(--text-muted, #888);font-size:.875rem}.footer-links{display:flex;gap:1.25rem}.footer-links a{color:var(--text-muted, #888);font-size:.875rem;text-decoration:none}.footer-links a:hover{color:var(--text, #eee)}.footer-social{display:flex;gap:.5rem;align-items:center}.footer-social-link{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;color:var(--text-muted, #888);transition:color .15s}.footer-social-link:hover{color:var(--text, #eee)}.footer-social-link svg{width:1rem;height:1rem}.footer-motion-switch{font-size:.8rem;color:var(--text-muted, #888);white-space:nowrap}.footer-motion-switch .form-switch-label{color:var(--text-muted, #888)}.footer-motion-switch .form-switch-input{width:2rem;height:1.125rem}.footer-motion-switch .form-switch-input:after{width:.875rem;height:.875rem}.footer-motion-switch .form-switch-input:checked:after{transform:translate(.875rem)}.dm-slideover-header{display:flex;align-items:center;justify-content:space-between;padding:.875rem 1.25rem;border-bottom:1px solid var(--border-color, rgba(255, 255, 255, .08));flex-shrink:0}.dm-slideover-title{margin:0;font-size:1rem;font-weight:600;line-height:1.4}.dm-slideover-body{padding:1.25rem;overflow-y:auto;flex:1}@media(max-width:768px){.site-main.with-sidebar{grid-template-columns:1fr}.site-sidebar{display:none}}.dm-spacer{display:block;width:100%}.hero-breakout{width:calc(100vw - 2rem);margin-left:calc(50% - 50vw + 1rem);margin-right:calc(50% - 50vw + 1rem)}.site-main:has(.page-body>.hero-breakout:first-child){padding-top:0}body[data-layout=landing]>.site-main{padding-top:0}body[data-layout=landing]>.site-main .container{max-width:none;padding:0}body[data-layout=landing] .page-body{padding-left:1.5rem;padding-right:1.5rem}body[data-layout=landing] .page-body>p,body[data-layout=landing] .page-body>h1,body[data-layout=landing] .page-body>h2,body[data-layout=landing] .page-body>h3,body[data-layout=landing] .page-body>ul,body[data-layout=landing] .page-body>ol,body[data-layout=landing] .page-body>blockquote{max-width:860px;margin-left:auto;margin-right:auto}body[data-layout=landing] .page-body .hero-breakout{width:calc(100% + 3rem);margin-left:-1.5rem;margin-right:-1.5rem}body[data-layout=landing] .page-body .grid-breakout{width:calc(100% + 3rem);margin-left:-1.5rem;margin-right:-1.5rem;padding-left:1.5rem;padding-right:1.5rem}.page-body .card{transition:transform .2s ease,box-shadow .2s ease}.page-body .card:hover{transform:translateY(-3px);box-shadow:0 8px 24px #00000059}.page-body .card-header-icon-inline{display:flex;align-items:center;gap:.6rem}.page-body .card-header-icon-inline [data-icon]{flex-shrink:0;line-height:0}.page-body .card-header-icon-inline [data-icon] svg,.page-body .card-header-icon-inline>svg{display:block;width:1.25rem;height:1.25rem}.page-body .card-header-icon-stacked{display:flex;flex-direction:column;align-items:center;text-align:center;gap:.35rem;padding-top:.25rem}.page-body .card-header-icon-stacked [data-icon],.page-body .card-header-icon-stacked svg{width:2rem;height:2rem}.hero.hero-dark{background:linear-gradient(135deg,#1f2937,#111827);color:#e2e8f0}.hero .hero-content{position:relative;z-index:2}.hero.hero-left .hero-content{text-align:left;align-items:flex-start;max-width:62%}@media(max-width:768px){.hero.hero-left .hero-content{max-width:100%}}.hero .hero-cta{display:flex;gap:.85rem;flex-wrap:wrap;margin-top:1.75rem}.hero .hero-cta a{display:inline-flex;align-items:center;gap:.4rem;padding:.55rem 1.35rem;border-radius:6px;font-size:.95rem;font-weight:500;text-decoration:none;transition:background .2s ease,border-color .2s ease,transform .15s ease,box-shadow .2s ease}.hero .hero-cta a:first-child{background:#ffffffeb;color:#111;border:1px solid transparent}.hero .hero-cta a:first-child:hover{background:#fff;box-shadow:0 4px 16px #00000040;transform:translateY(-2px)}.hero .hero-cta a:last-child{background:transparent;color:#fff;border:1px solid rgba(255,255,255,.4)}.hero .hero-cta a:last-child:hover{border-color:#ffffffbf;background:#ffffff14;transform:translateY(-2px)}.hero .hero-label{display:inline-block;margin-bottom:.9rem;padding:.2rem .8rem;border-radius:999px;font-size:.72rem;font-weight:600;letter-spacing:.07em;text-transform:uppercase;color:#ffffffb3;border:1px solid rgba(255,255,255,.22)}.grid-breakout{width:calc(100vw - 2rem);margin-left:calc(50% - 50vw + 1rem);margin-right:calc(50% - 50vw + 1rem)}.dm-breadcrumbs{position:fixed;z-index:200;display:inline-flex;align-items:center;gap:.2rem;padding:.3rem .8rem;border-radius:999px;backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);background:#00000047;border:1px solid rgba(255,255,255,.11);box-shadow:0 2px 10px #00000038;font-size:.72rem;font-weight:500;letter-spacing:.01em;line-height:1.4;max-width:calc(100vw - 2rem);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dm-breadcrumbs .dm-breadcrumbs-item{color:#ffffffa6}.dm-breadcrumbs .dm-breadcrumbs-link{display:inline-flex;align-items:center;gap:.25rem;color:#ffffff8c;text-decoration:none;transition:color .15s}.dm-breadcrumbs .dm-breadcrumbs-home-icon{flex-shrink:0;vertical-align:middle}.dm-breadcrumbs .dm-breadcrumbs-link:hover{color:#fffffff2}.dm-breadcrumbs .dm-breadcrumbs-current{color:#ffffffeb;font-weight:600}.dm-breadcrumbs .dm-breadcrumbs-separator{color:#ffffff47;font-size:.8em;line-height:1;margin:0 .05rem}[data-mode=light] .dm-breadcrumbs{background:#ffffff8c;border-color:#00000012;box-shadow:0 2px 10px #00000014}[data-mode=light] .dm-breadcrumbs .dm-breadcrumbs-item,[data-mode=light] .dm-breadcrumbs .dm-breadcrumbs-link{color:#0000008c}[data-mode=light] .dm-breadcrumbs .dm-breadcrumbs-link:hover{color:#000000e6}[data-mode=light] .dm-breadcrumbs .dm-breadcrumbs-current{color:#000000d9}[data-mode=light] .dm-breadcrumbs .dm-breadcrumbs-separator{color:#00000040}.dm-collection-display{margin:1.5rem 0}.dm-collection-list{display:flex;flex-direction:column;gap:0}.dm-collection-list-item{padding:1rem 0;border-bottom:1px solid var(--border-color, rgba(255, 255, 255, .08))}.dm-collection-list-item:last-child{border-bottom:none}.dm-collection-list-item strong{display:block;font-size:1rem;margin-bottom:.25rem}.dm-collection-list-item p{margin:0;color:var(--text-muted, #888);font-size:.9rem}.dm-collection-empty p{color:var(--text-muted, #888);font-style:italic}.hero-gradient-purple{background:linear-gradient(135deg,#ede9fe,#ddd6fe);color:#1e1b4b}.hero-gradient-blue{background:linear-gradient(135deg,#dbeafe,#bfdbfe);color:#1e3a5f}.hero-gradient-green{background:linear-gradient(135deg,#d1fae5,#a7f3d0);color:#064e3b}.hero-gradient-sunset{background:linear-gradient(135deg,#fef3c7,#fde68a);color:#78350f}.hero-gradient-ocean{background:linear-gradient(135deg,#e0f2fe,#bae6fd);color:#0c4a6e}.hero-gradient-rose{background:linear-gradient(135deg,#fce7f3,#fbcfe8);color:#831843}.hero-gradient-forest{background:linear-gradient(135deg,#dcfce7,#bbf7d0);color:#14532d}.hero-gradient-night{background:linear-gradient(135deg,#334155,#1e293b);color:#e2e8f0}.hero-gradient-ocean-light{background:linear-gradient(135deg,#e0f2fe,#caf0f8);color:#1e293b}.hero-gradient-ocean-dark{background:linear-gradient(135deg,#0c4a6e,#164e63);color:#e2e8f0}.hero-gradient-forest-light{background:linear-gradient(135deg,#d1fae5,#c6f6dc);color:#1e293b}.hero-gradient-forest-dark{background:linear-gradient(135deg,#1a4731,#166534);color:#e2e8f0}.hero-gradient-sunset-light{background:linear-gradient(135deg,#fde8d8,#fddcc9);color:#1e293b}.hero-gradient-sunset-dark{background:linear-gradient(135deg,#6b3727,#7c4036);color:#f5ede8}.hero-gradient-royal-light{background:linear-gradient(135deg,#e8f0fd,#dce8fc);color:#1e293b}.hero-gradient-royal-dark{background:linear-gradient(135deg,#1e3465,#263d7a);color:#e2e8f0}.hero-gradient-lemon-light{background:linear-gradient(135deg,#fefce8,#fef9c3);color:#1e293b}.hero-gradient-lemon-dark{background:linear-gradient(135deg,#5c4d1a,#6b5920);color:#fefce8}.hero-gradient-silver-light{background:linear-gradient(135deg,#f1f5f9,#e2e8f0);color:#1e293b}.hero-gradient-silver-dark{background:linear-gradient(135deg,#2d3748,#374151);color:#e2e8f0}.hero-gradient-charcoal-light{background:linear-gradient(135deg,#eceff1,#e1e7eb);color:#1e293b}.hero-gradient-charcoal-dark{background:linear-gradient(135deg,#2c3843,#374451);color:#e2e8f0}.hero-gradient-christmas-light{background:linear-gradient(135deg,#fde8ea,#fdd5d8);color:#1e293b}.hero-gradient-christmas-dark{background:linear-gradient(135deg,#5c0f1d,#7a1525);color:#fde8ea}.hero-gradient-unicorn-light{background:linear-gradient(135deg,#f5e8fd,#edd6fb);color:#1e293b}.hero-gradient-unicorn-dark{background:linear-gradient(135deg,#3d1a5a,#4a2068);color:#f5e8fd}.hero-gradient-dreamy-light{background:linear-gradient(135deg,#f5ede8,#eeddd4);color:#1e293b}.hero-gradient-dreamy-dark{background:linear-gradient(135deg,#3d2820,#503328);color:#f5ede8}.hero-gradient-grayve-light{background:linear-gradient(135deg,#e0f7f9,#cbf2f5);color:#1e293b}.hero-gradient-grayve-dark{background:linear-gradient(135deg,#00363d,#00444d);color:#e0f7f9}.hero-gradient-mint-light{background:linear-gradient(135deg,#d8f5ea,#c5efdd);color:#1e293b}.hero-gradient-mint-dark{background:linear-gradient(135deg,#134d33,#195f3f);color:#d8f5ea}.hero-gradient-wedding-light{background:linear-gradient(135deg,#faf3e0,#f5e9c7);color:#1e293b}.hero-gradient-wedding-dark{background:linear-gradient(135deg,#5c4418,#6f5320);color:#faf3e0}.tabs-centered{text-align:center}.tabs-centered .tab-list{display:inline-flex}.tabs-centered .tab-content{text-align:left}
|
|
1
|
+
body,button,input,select,textarea{font-family:Roboto,sans-serif}.site-main{min-height:calc(100vh - 60px);padding-top:2rem;padding-bottom:4rem}.site-main.with-sidebar{display:grid;grid-template-columns:260px 1fr;gap:0}.site-sidebar{min-height:100%;border-right:1px solid var(--border-color, rgba(255,255,255,.08))}.site-content{overflow:hidden}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}.container{max-width:860px;margin:0 auto;padding:0 1.5rem}.page-title{font-size:clamp(1.5rem,4vw,2rem);font-weight:700;margin-bottom:1.5rem;line-height:1.2}.page-body{line-height:1.7;font-size:1rem}.page-body h1,.page-body h2,.page-body h3,.page-body h4{margin-top:2rem;margin-bottom:.75rem;font-weight:600}.page-body h2{font-size:clamp(1.2rem,3vw,1.5rem)}.page-body h3{font-size:clamp(1.1rem,2.5vw,1.25rem)}.page-body p{margin-bottom:1rem}.page-body ul,.page-body ol{margin-bottom:1rem;padding-left:1.5rem}.page-body a{color:var(--primary, #5b8cff)}.page-body a:hover{text-decoration:underline}.page-body code{font-family:Fira Code,Courier New,monospace;font-size:.9em;background:#ffffff0f;padding:.15em .35em;border-radius:3px}.page-body pre{background:#0000004d;border:1px solid rgba(255,255,255,.08);border-radius:6px;padding:1rem;overflow-x:auto;margin-bottom:1rem}.page-body pre code{background:none;padding:0}.page-body img{max-width:100%;border-radius:6px}.page-body blockquote{border-left:3px solid var(--primary, #5b8cff);margin:1.5rem 0;padding:.75rem 1rem;background:#5b8cff0f;border-radius:0 6px 6px 0}h3.accordion-header{margin:0}.accordion-button{all:unset;display:flex;align-items:center;justify-content:space-between;width:100%;cursor:pointer;font:inherit}.page-body .card-header h2{margin:0;font-size:1rem;font-weight:600;line-height:1.4}.card[data-collapsible] .card-header{cursor:pointer;user-select:none;display:flex;align-items:center;justify-content:space-between}.card[data-collapsible] .card-header:after{content:"\25be";font-size:1.1em;line-height:1;display:inline-block;transition:transform .25s ease;flex-shrink:0}.card[data-collapsible].is-collapsed .card-header:after{transform:rotate(-90deg)}.card[data-collapsible] .card-body{overflow:hidden;max-height:4000px;opacity:1;transition:max-height .3s ease,opacity .25s ease}.card[data-collapsible].is-collapsed .card-body{max-height:0;opacity:0}.navbar-link span[data-icon],.navbar-link svg,.navbar-dropdown-toggle span[data-icon],.navbar-dropdown-toggle svg,.navbar-dropdown-item span[data-icon],.navbar-dropdown-item svg{width:13px!important;height:13px!important;margin-right:10px!important}.navbar-dropdown-toggle{font-size:var(--dm-font-size-base)}@media(min-width:993px){.navbar-dropdown-toggle{font-size:var(--dm-font-size-sm)}}@media(min-width:1201px){.navbar-dropdown-toggle{font-size:var(--dm-font-size-xs)}}.dm-reduced-motion *,.dm-reduced-motion *:before,.dm-reduced-motion *:after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important;scroll-behavior:auto!important}.page-footer{border-top:1px solid var(--border-color, rgba(255,255,255,.08));padding:1.5rem 0}.footer-inner{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:1rem}.footer-inner p{margin:0;color:var(--text-muted, #888);font-size:.875rem}.footer-links{display:flex;gap:1.25rem}.footer-links a{color:var(--text-muted, #888);font-size:.875rem;text-decoration:none}.footer-links a:hover{color:var(--text, #eee)}.footer-social{display:flex;gap:.5rem;align-items:center}.footer-social-link{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;color:var(--text-muted, #888);transition:color .15s}.footer-social-link:hover{color:var(--text, #eee)}.footer-social-link svg{width:1rem;height:1rem}.footer-motion-switch{font-size:.8rem;color:var(--text-muted, #888);white-space:nowrap}.footer-motion-switch .form-switch-label{color:var(--text-muted, #888)}.footer-motion-switch .form-switch-input{width:2rem;height:1.125rem}.footer-motion-switch .form-switch-input:after{width:.875rem;height:.875rem}.footer-motion-switch .form-switch-input:checked:after{transform:translate(.875rem)}.dm-slideover-header{display:flex;align-items:center;justify-content:space-between;padding:.875rem 1.25rem;border-bottom:1px solid var(--border-color, rgba(255, 255, 255, .08));flex-shrink:0}.dm-slideover-title{margin:0;font-size:1rem;font-weight:600;line-height:1.4}.dm-slideover-body{padding:1.25rem;overflow-y:auto;flex:1}@media(max-width:768px){.site-main.with-sidebar{grid-template-columns:1fr}.site-sidebar{display:none}}.dm-spacer{display:block;width:100%}.hero-breakout{width:calc(100vw - 2rem);margin-left:calc(50% - 50vw + 1rem);margin-right:calc(50% - 50vw + 1rem)}.site-main:has(.page-body>.hero-breakout:first-child){padding-top:0}body[data-layout=landing]>.site-main{padding-top:0}body[data-layout=landing]>.site-main .container{max-width:none;padding:0}body[data-layout=landing] .page-body{padding-left:1.5rem;padding-right:1.5rem}body[data-layout=landing] .page-body>p,body[data-layout=landing] .page-body>h1,body[data-layout=landing] .page-body>h2,body[data-layout=landing] .page-body>h3,body[data-layout=landing] .page-body>ul,body[data-layout=landing] .page-body>ol,body[data-layout=landing] .page-body>blockquote{max-width:860px;margin-left:auto;margin-right:auto}body[data-layout=landing] .page-body .hero-breakout{width:calc(100% + 3rem);margin-left:-1.5rem;margin-right:-1.5rem}body[data-layout=landing] .page-body .grid-breakout{width:calc(100% + 3rem);margin-left:-1.5rem;margin-right:-1.5rem;padding-left:1.5rem;padding-right:1.5rem}.page-body .card{transition:transform .2s ease,box-shadow .2s ease}.page-body .card:hover{transform:translateY(-3px);box-shadow:0 8px 24px #00000059}.page-body .card-header-icon-inline{display:flex;align-items:center;gap:.6rem}.page-body .card-header-icon-inline [data-icon]{flex-shrink:0;line-height:0}.page-body .card-header-icon-inline [data-icon] svg,.page-body .card-header-icon-inline>svg{display:block;width:1.25rem;height:1.25rem}.page-body .card-header-icon-stacked{display:flex;flex-direction:column;align-items:center;text-align:center;gap:.35rem;padding-top:.25rem}.page-body .card-header-icon-stacked [data-icon],.page-body .card-header-icon-stacked svg{width:2rem;height:2rem}.hero.hero-dark{background:linear-gradient(135deg,#1f2937,#111827);color:#e2e8f0}.hero .hero-content{position:relative;z-index:2}.hero.hero-left .hero-content{text-align:left;align-items:flex-start;max-width:62%}@media(max-width:768px){.hero.hero-left .hero-content{max-width:100%}}.hero .hero-cta{display:flex;gap:.85rem;flex-wrap:wrap;margin-top:1.75rem}.hero .hero-cta a{display:inline-flex;align-items:center;gap:.4rem;padding:.55rem 1.35rem;border-radius:6px;font-size:.95rem;font-weight:500;text-decoration:none;transition:background .2s ease,border-color .2s ease,transform .15s ease,box-shadow .2s ease}.hero .hero-cta a:first-child{background:#ffffffeb;color:#111;border:1px solid transparent}.hero .hero-cta a:first-child:hover{background:#fff;box-shadow:0 4px 16px #00000040;transform:translateY(-2px)}.hero .hero-cta a:last-child{background:transparent;color:#fff;border:1px solid rgba(255,255,255,.4)}.hero .hero-cta a:last-child:hover{border-color:#ffffffbf;background:#ffffff14;transform:translateY(-2px)}.hero .hero-label{display:inline-block;margin-bottom:.9rem;padding:.2rem .8rem;border-radius:999px;font-size:.72rem;font-weight:600;letter-spacing:.07em;text-transform:uppercase;color:#ffffffb3;border:1px solid rgba(255,255,255,.22)}.grid-breakout{width:calc(100vw - 2rem);margin-left:calc(50% - 50vw + 1rem);margin-right:calc(50% - 50vw + 1rem)}.dm-breadcrumbs{position:fixed;z-index:200;display:inline-flex;align-items:center;gap:.2rem;padding:.3rem .8rem;border-radius:999px;backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);background:#00000047;border:1px solid rgba(255,255,255,.11);box-shadow:0 2px 10px #00000038;font-size:.72rem;font-weight:500;letter-spacing:.01em;line-height:1.4;max-width:calc(100vw - 2rem);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dm-breadcrumbs .dm-breadcrumbs-item{color:#ffffffa6}.dm-breadcrumbs .dm-breadcrumbs-link{display:inline-flex;align-items:center;gap:.25rem;color:#ffffff8c;text-decoration:none;transition:color .15s}.dm-breadcrumbs .dm-breadcrumbs-home-icon{flex-shrink:0;vertical-align:middle}.dm-breadcrumbs .dm-breadcrumbs-link:hover{color:#fffffff2}.dm-breadcrumbs .dm-breadcrumbs-current{color:#ffffffeb;font-weight:600}.dm-breadcrumbs .dm-breadcrumbs-separator{color:#ffffff47;font-size:.8em;line-height:1;margin:0 .05rem}[data-mode=light] .dm-breadcrumbs{background:#ffffff8c;border-color:#00000012;box-shadow:0 2px 10px #00000014}[data-mode=light] .dm-breadcrumbs .dm-breadcrumbs-item,[data-mode=light] .dm-breadcrumbs .dm-breadcrumbs-link{color:#0000008c}[data-mode=light] .dm-breadcrumbs .dm-breadcrumbs-link:hover{color:#000000e6}[data-mode=light] .dm-breadcrumbs .dm-breadcrumbs-current{color:#000000d9}[data-mode=light] .dm-breadcrumbs .dm-breadcrumbs-separator{color:#00000040}.dm-collection-display{margin:1.5rem 0}.dm-collection-list{display:flex;flex-direction:column;gap:0}.dm-collection-list-item{padding:1rem 0;border-bottom:1px solid var(--border-color, rgba(255, 255, 255, .08))}.dm-collection-list-item:last-child{border-bottom:none}.dm-collection-list-item strong{display:block;font-size:1rem;margin-bottom:.25rem}.dm-collection-list-item p{margin:0;color:var(--text-muted, #888);font-size:.9rem}.dm-collection-empty p{color:var(--text-muted, #888);font-style:italic}.hero-gradient-purple{background:linear-gradient(135deg,#ede9fe,#ddd6fe);color:#1e1b4b}.hero-gradient-blue{background:linear-gradient(135deg,#dbeafe,#bfdbfe);color:#1e3a5f}.hero-gradient-green{background:linear-gradient(135deg,#d1fae5,#a7f3d0);color:#064e3b}.hero-gradient-sunset{background:linear-gradient(135deg,#fef3c7,#fde68a);color:#78350f}.hero-gradient-ocean{background:linear-gradient(135deg,#e0f2fe,#bae6fd);color:#0c4a6e}.hero-gradient-rose{background:linear-gradient(135deg,#fce7f3,#fbcfe8);color:#831843}.hero-gradient-forest{background:linear-gradient(135deg,#dcfce7,#bbf7d0);color:#14532d}.hero-gradient-night{background:linear-gradient(135deg,#334155,#1e293b);color:#e2e8f0}.hero-gradient-ocean-light{background:linear-gradient(135deg,#e0f2fe,#caf0f8);color:#1e293b}.hero-gradient-ocean-dark{background:linear-gradient(135deg,#0c4a6e,#164e63);color:#e2e8f0}.hero-gradient-forest-light{background:linear-gradient(135deg,#d1fae5,#c6f6dc);color:#1e293b}.hero-gradient-forest-dark{background:linear-gradient(135deg,#1a4731,#166534);color:#e2e8f0}.hero-gradient-sunset-light{background:linear-gradient(135deg,#fde8d8,#fddcc9);color:#1e293b}.hero-gradient-sunset-dark{background:linear-gradient(135deg,#6b3727,#7c4036);color:#f5ede8}.hero-gradient-royal-light{background:linear-gradient(135deg,#e8f0fd,#dce8fc);color:#1e293b}.hero-gradient-royal-dark{background:linear-gradient(135deg,#1e3465,#263d7a);color:#e2e8f0}.hero-gradient-lemon-light{background:linear-gradient(135deg,#fefce8,#fef9c3);color:#1e293b}.hero-gradient-lemon-dark{background:linear-gradient(135deg,#5c4d1a,#6b5920);color:#fefce8}.hero-gradient-silver-light{background:linear-gradient(135deg,#f1f5f9,#e2e8f0);color:#1e293b}.hero-gradient-silver-dark{background:linear-gradient(135deg,#2d3748,#374151);color:#e2e8f0}.hero-gradient-charcoal-light{background:linear-gradient(135deg,#eceff1,#e1e7eb);color:#1e293b}.hero-gradient-charcoal-dark{background:linear-gradient(135deg,#2c3843,#374451);color:#e2e8f0}.hero-gradient-christmas-light{background:linear-gradient(135deg,#fde8ea,#fdd5d8);color:#1e293b}.hero-gradient-christmas-dark{background:linear-gradient(135deg,#5c0f1d,#7a1525);color:#fde8ea}.hero-gradient-unicorn-light{background:linear-gradient(135deg,#f5e8fd,#edd6fb);color:#1e293b}.hero-gradient-unicorn-dark{background:linear-gradient(135deg,#3d1a5a,#4a2068);color:#f5e8fd}.hero-gradient-dreamy-light{background:linear-gradient(135deg,#f5ede8,#eeddd4);color:#1e293b}.hero-gradient-dreamy-dark{background:linear-gradient(135deg,#3d2820,#503328);color:#f5ede8}.hero-gradient-grayve-light{background:linear-gradient(135deg,#e0f7f9,#cbf2f5);color:#1e293b}.hero-gradient-grayve-dark{background:linear-gradient(135deg,#00363d,#00444d);color:#e0f7f9}.hero-gradient-mint-light{background:linear-gradient(135deg,#d8f5ea,#c5efdd);color:#1e293b}.hero-gradient-mint-dark{background:linear-gradient(135deg,#134d33,#195f3f);color:#d8f5ea}.hero-gradient-wedding-light{background:linear-gradient(135deg,#faf3e0,#f5e9c7);color:#1e293b}.hero-gradient-wedding-dark{background:linear-gradient(135deg,#5c4418,#6f5320);color:#faf3e0}.tabs-centered{text-align:center}.tabs-centered .tab-list{display:inline-flex}.tabs-centered .tab-content{text-align:left}.site-main{overflow-x:hidden}@media(max-width:768px){.hero .hero-cta a,.dm-so-trigger,.dm-cta-trigger{min-height:44px;padding:.6rem 1.25rem}}
|
|
@@ -90,7 +90,7 @@ function renderCollectionBlocks(entries, blockTemplate, emptyMsg, ctaOpts, cols)
|
|
|
90
90
|
|
|
91
91
|
const validCols = ['2', '3', '4', '5', '6'].includes(String(cols)) ? cols : '';
|
|
92
92
|
const wrapperClass = validCols
|
|
93
|
-
? `dm-collection-display dm-collection-blocks grid
|
|
93
|
+
? `dm-collection-display dm-collection-blocks grid ${responsiveGridCols(validCols).join(' ')} gap-4`
|
|
94
94
|
: 'dm-collection-display dm-collection-blocks';
|
|
95
95
|
|
|
96
96
|
return `<div class="${wrapperClass}">\n${items.join('\n')}\n</div>`;
|
|
@@ -145,7 +145,7 @@ function renderCollectionCards(entries, visibleFields, titleField, columns, empt
|
|
|
145
145
|
}
|
|
146
146
|
return `<div class="card">${title ? `<div class="card-header">${title}</div>` : ''}<div class="card-body">${body || ' '}</div>${footer}</div>`;
|
|
147
147
|
}).join('\n');
|
|
148
|
-
return `<div class="dm-collection-display grid
|
|
148
|
+
return `<div class="dm-collection-display grid ${responsiveGridCols(cols).join(' ')} gap-4">\n${cards}\n</div>`;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
function renderCollectionList(entries, visibleFields, titleField, emptyMsg, ctaOpts) {
|
|
@@ -174,6 +174,37 @@ function renderCollectionList(entries, visibleFields, titleField, emptyMsg, ctaO
|
|
|
174
174
|
return `<div class="dm-collection-display dm-collection-list">\n${items}\n</div>`;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Render a collection as a Domma accordion.
|
|
179
|
+
* @param {object[]} entries
|
|
180
|
+
* @param {string} titleField - entry data field to use as header text
|
|
181
|
+
* @param {string} bodyField - entry data field to use as body content (Markdown)
|
|
182
|
+
* @param {boolean} multiple - allow multiple panels open simultaneously
|
|
183
|
+
* @param {string} emptyMsg
|
|
184
|
+
* @returns {string}
|
|
185
|
+
*/
|
|
186
|
+
function renderCollectionAccordion(entries, titleField, bodyField, multiple, emptyMsg) {
|
|
187
|
+
if (!entries.length) {
|
|
188
|
+
return `<div class="dm-collection-display dm-collection-empty"><p>${escapeHtmlText(emptyMsg)}</p></div>`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const multiAttr = multiple ? ' data-multi="true"' : '';
|
|
192
|
+
const items = entries.map(e => {
|
|
193
|
+
const title = escapeHtmlText(String(e.data?.[titleField] ?? e.id ?? '(untitled)'));
|
|
194
|
+
const bodyVal = String(e.data?.[bodyField] ?? '');
|
|
195
|
+
const bodyHtml = marked.parse(bodyVal);
|
|
196
|
+
return (
|
|
197
|
+
`<div class="accordion-item">\n` +
|
|
198
|
+
` <h3 class="accordion-header"><button class="accordion-button" type="button">${title}` +
|
|
199
|
+
`<span class="accordion-icon" data-icon="chevron-down"></span></button></h3>\n` +
|
|
200
|
+
` <div class="accordion-body"><div class="accordion-content">${bodyHtml}</div></div>\n` +
|
|
201
|
+
`</div>`
|
|
202
|
+
);
|
|
203
|
+
}).join('\n');
|
|
204
|
+
|
|
205
|
+
return `<div class="dm-collection-display accordion"${multiAttr}>\n${items}\n</div>`;
|
|
206
|
+
}
|
|
207
|
+
|
|
177
208
|
/**
|
|
178
209
|
* Process [view slug="..." display="table|cards|list" /] shortcodes.
|
|
179
210
|
* Executes the View's aggregation pipeline and renders results using the
|
|
@@ -244,6 +275,11 @@ async function processViewBlocks(markdown) {
|
|
|
244
275
|
replacement = renderCollectionCards(entries, fields, titleField, columns, emptyMsg, ctaOpts);
|
|
245
276
|
} else if (display === 'list') {
|
|
246
277
|
replacement = renderCollectionList(entries, fields, titleField, emptyMsg, ctaOpts);
|
|
278
|
+
} else if (display === 'accordion') {
|
|
279
|
+
const accordionTitleField = attrs['title-field'] || 'title';
|
|
280
|
+
const bodyField = attrs['body-field'] || 'description';
|
|
281
|
+
const multiple = attrs.multiple === 'true';
|
|
282
|
+
replacement = renderCollectionAccordion(entries, accordionTitleField, bodyField, multiple, emptyMsg);
|
|
247
283
|
} else if (display === 'block') {
|
|
248
284
|
const blockName = attrs.block || viewConfig?.display?.block || '';
|
|
249
285
|
if (blockName) {
|
|
@@ -333,6 +369,11 @@ async function processCollectionBlocks(markdown) {
|
|
|
333
369
|
replacement = renderCollectionCards(entries, fields, titleField, columns, emptyMsg, ctaOpts);
|
|
334
370
|
} else if (display === 'list') {
|
|
335
371
|
replacement = renderCollectionList(entries, fields, titleField, emptyMsg, ctaOpts);
|
|
372
|
+
} else if (display === 'accordion') {
|
|
373
|
+
const accordionTitleField = attrs['title-field'] || 'title';
|
|
374
|
+
const bodyField = attrs['body-field'] || 'description';
|
|
375
|
+
const multiple = attrs.multiple === 'true';
|
|
376
|
+
replacement = renderCollectionAccordion(entries, accordionTitleField, bodyField, multiple, emptyMsg);
|
|
336
377
|
} else if (display === 'block') {
|
|
337
378
|
const blockName = attrs.block || '';
|
|
338
379
|
if (blockName) {
|
|
@@ -454,14 +495,36 @@ function processPluginShortcodes(markdown) {
|
|
|
454
495
|
* @param {string} markdown
|
|
455
496
|
* @returns {string}
|
|
456
497
|
*/
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Return the responsive Domma grid-cols class list for a given column count.
|
|
501
|
+
* Outputs mobile-first stacking: single column on small screens, then
|
|
502
|
+
* intermediate and full column counts at md/lg breakpoints.
|
|
503
|
+
*
|
|
504
|
+
* @param {string|number} cols
|
|
505
|
+
* @returns {string[]}
|
|
506
|
+
*/
|
|
507
|
+
function responsiveGridCols(cols) {
|
|
508
|
+
const map = {
|
|
509
|
+
'1': ['grid-cols-1'],
|
|
510
|
+
'2': ['grid-cols-1', 'grid-cols-md-2'],
|
|
511
|
+
'3': ['grid-cols-1', 'grid-cols-md-2', 'grid-cols-lg-3'],
|
|
512
|
+
'4': ['grid-cols-1', 'grid-cols-md-2', 'grid-cols-lg-4'],
|
|
513
|
+
'5': ['grid-cols-1', 'grid-cols-md-2', 'grid-cols-lg-5'],
|
|
514
|
+
'6': ['grid-cols-1', 'grid-cols-md-3', 'grid-cols-lg-6'],
|
|
515
|
+
};
|
|
516
|
+
return map[String(cols)] ?? [`grid-cols-${cols}`];
|
|
517
|
+
}
|
|
518
|
+
|
|
457
519
|
function processGridBlocks(markdown) {
|
|
458
520
|
const {scrubbed, restore} = scrubCodeRegions(markdown);
|
|
459
521
|
// Pass 1: [col span="N"]...[/col] → <div class="col-span-N">...</div>
|
|
522
|
+
// [col]...[/col] → <div class="col">...</div> (enables flex stacking in [row])
|
|
460
523
|
let result = scrubbed.replace(
|
|
461
524
|
/\[col([^\]]*)\]([\s\S]*?)\[\/col\]/gi,
|
|
462
525
|
(_, attrStr, body) => {
|
|
463
526
|
const attrs = parseShortcodeAttrs(attrStr);
|
|
464
|
-
|
|
527
|
+
const cls = attrs.span ? ` class="col-span-${attrs.span}"` : ' class="col"';
|
|
465
528
|
const id = attrs.id ? ` id="${escapeAttr(attrs.id)}"` : '';
|
|
466
529
|
return `<div${cls}${id}>${marked.parse(processCardBlocks(body.trim()))}</div>`;
|
|
467
530
|
}
|
|
@@ -480,13 +543,20 @@ function processGridBlocks(markdown) {
|
|
|
480
543
|
}
|
|
481
544
|
);
|
|
482
545
|
|
|
483
|
-
|
|
546
|
+
// Pass 3: [grid cols="N" gap="N"]...[/grid] → <div class="grid grid-cols-1 grid-cols-md-N ...">
|
|
547
|
+
// responsive="false" disables the mobile-first stacking behaviour
|
|
484
548
|
result = result.replace(
|
|
485
549
|
/\[grid([^\]]*)\]([\s\S]*?)\[\/grid\]/gi,
|
|
486
550
|
(_, attrStr, inner) => {
|
|
487
551
|
const attrs = parseShortcodeAttrs(attrStr);
|
|
488
552
|
const classes = ['grid'];
|
|
489
|
-
|
|
553
|
+
if (attrs.cols) {
|
|
554
|
+
if (attrs.responsive === 'false') {
|
|
555
|
+
classes.push(`grid-cols-${attrs.cols}`);
|
|
556
|
+
} else {
|
|
557
|
+
classes.push(...responsiveGridCols(attrs.cols));
|
|
558
|
+
}
|
|
559
|
+
}
|
|
490
560
|
if (attrs.gap) classes.push(`gap-${attrs.gap}`);
|
|
491
561
|
if (attrs.class) classes.push(attrs.class);
|
|
492
562
|
if (attrs.fullwidth === 'true') classes.push('grid-breakout');
|
|
@@ -1277,7 +1347,7 @@ function processHeroBlocks(markdown) {
|
|
|
1277
1347
|
const blobs = 'blobs' in attrs;
|
|
1278
1348
|
const blobsType = attrs['blobs-type'] || 'float-blobs';
|
|
1279
1349
|
|
|
1280
|
-
|
|
1350
|
+
const classes = ['hero', 'hero-responsive'];
|
|
1281
1351
|
if (size) classes.push(`hero-${size}`);
|
|
1282
1352
|
if (variant) classes.push(`hero-${variant}`);
|
|
1283
1353
|
if (align) classes.push(`hero-${align}`);
|