mdorigin 0.1.1 → 0.1.2

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.
@@ -23,6 +23,24 @@ export function renderDocument(options) {
23
23
  .map((item) => `<li><a href="${escapeHtml(item.href)}">${escapeHtml(item.label)}</a></li>`)
24
24
  .join('')}</ul></nav>`
25
25
  : '';
26
+ const searchToggleBlock = options.searchEnabled
27
+ ? [
28
+ '<div class="site-search" data-site-search>',
29
+ '<button type="button" class="site-search__toggle" aria-expanded="false" aria-controls="site-search-panel">Search</button>',
30
+ '<div id="site-search-panel" class="site-search__panel" hidden>',
31
+ '<form class="site-search__form" role="search" action="/api/search" method="get">',
32
+ '<label class="site-search__label" for="site-search-input">Search site</label>',
33
+ '<div class="site-search__controls">',
34
+ '<input id="site-search-input" class="site-search__input" type="search" name="q" placeholder="Search docs and skills" autocomplete="off">',
35
+ '<button type="submit" class="site-search__submit">Go</button>',
36
+ '</div>',
37
+ '<p class="site-search__hint">Search is powered by <code>/api/search</code>.</p>',
38
+ '</form>',
39
+ '<div class="site-search__results" data-site-search-results></div>',
40
+ '</div>',
41
+ '</div>',
42
+ ].join('')
43
+ : '';
26
44
  const footerNavBlock = options.footerNav && options.footerNav.length > 0
27
45
  ? `<nav class="site-footer__nav"><ul>${options.footerNav
28
46
  .map((item) => `<li><a href="${escapeHtml(item.href)}">${escapeHtml(item.label)}</a></li>`)
@@ -46,12 +64,20 @@ export function renderDocument(options) {
46
64
  const footerTextBlock = options.footerText
47
65
  ? `<p class="site-footer__text">${escapeHtml(options.footerText)}</p>`
48
66
  : '';
49
- const footerBlock = footerNavBlock || socialLinksBlock || footerTextBlock || editLinkBlock
50
- ? `<footer class="site-footer"><div class="site-footer__inner">${footerNavBlock}${socialLinksBlock}${footerTextBlock}${editLinkBlock}</div></footer>`
67
+ const footerMetaBlock = socialLinksBlock || editLinkBlock
68
+ ? `<div class="site-footer__meta">${socialLinksBlock}${editLinkBlock}</div>`
69
+ : '';
70
+ const footerBlock = footerNavBlock || footerTextBlock || footerMetaBlock
71
+ ? `<footer class="site-footer"><div class="site-footer__inner">${footerNavBlock}${footerTextBlock}${footerMetaBlock}</div></footer>`
51
72
  : '';
52
73
  const articleBody = options.template === 'catalog'
53
- ? renderCatalogArticle(options.body, options.catalogEntries ?? [])
74
+ ? renderCatalogArticle(options.body, options.catalogEntries ?? [], {
75
+ requestPath: options.catalogRequestPath ?? '/',
76
+ initialPostCount: options.catalogInitialPostCount ?? 10,
77
+ loadMoreStep: options.catalogLoadMoreStep ?? 10,
78
+ })
54
79
  : options.body;
80
+ const searchScript = options.searchEnabled ? renderSearchScript() : '';
55
81
  return [
56
82
  '<!doctype html>',
57
83
  '<html lang="en">',
@@ -66,11 +92,12 @@ export function renderDocument(options) {
66
92
  stylesheetBlock,
67
93
  '</head>',
68
94
  `<body data-theme="${options.theme}" data-template="${options.template}">`,
69
- `<header class="site-header"><div class="site-header__inner"><div class="site-header__brand"><p class="site-header__title"><a href="${brandHref}">${logoBlock}<span>${siteTitle}</span></a></p>${siteDescriptionBlock}</div>${navBlock}</div></header>`,
95
+ `<header class="site-header"><div class="site-header__inner"><div class="site-header__brand"><p class="site-header__title"><a href="${brandHref}">${logoBlock}<span>${siteTitle}</span></a></p>${siteDescriptionBlock}</div><div class="site-header__actions">${navBlock}${searchToggleBlock}</div></div></header>`,
70
96
  '<main>',
71
97
  `<article>${articleBody}</article>`,
72
98
  '</main>',
73
99
  footerBlock,
100
+ searchScript,
74
101
  '</body>',
75
102
  '</html>',
76
103
  ].join('');
@@ -102,18 +129,29 @@ function renderSocialIcon(icon) {
102
129
  function iconSvg(pathData) {
103
130
  return `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="${pathData}"></path></svg>`;
104
131
  }
105
- function renderCatalogArticle(body, entries) {
132
+ function renderCatalogArticle(body, entries, options) {
106
133
  if (entries.length === 0) {
107
134
  return body;
108
135
  }
109
136
  const directories = entries.filter((entry) => entry.kind === 'directory');
110
137
  const articles = entries.filter((entry) => entry.kind === 'article');
138
+ const initialPostCount = Math.max(1, options.initialPostCount);
139
+ const visibleArticles = articles.slice(0, initialPostCount);
140
+ const shouldLoadMore = articles.length > visibleArticles.length;
111
141
  return [
112
142
  `<div class="catalog-page__body">${body}</div>`,
113
143
  '<section class="catalog-page" aria-label="Catalog">',
114
144
  directories.length > 0 ? renderCatalogDirectories(directories) : '',
115
- articles.length > 0 ? renderCatalogArticles(articles) : '',
145
+ articles.length > 0
146
+ ? renderCatalogArticles(visibleArticles, {
147
+ requestPath: options.requestPath,
148
+ nextOffset: visibleArticles.length,
149
+ loadMoreStep: options.loadMoreStep,
150
+ hasMore: shouldLoadMore,
151
+ })
152
+ : '',
116
153
  '</section>',
154
+ shouldLoadMore ? renderCatalogLoadMoreScript() : '',
117
155
  ].join('');
118
156
  }
119
157
  function renderCatalogDirectories(entries) {
@@ -125,12 +163,166 @@ function renderCatalogDirectories(entries) {
125
163
  '</div>',
126
164
  ].join('');
127
165
  }
128
- function renderCatalogArticles(entries) {
166
+ export function renderCatalogArticleItems(entries) {
167
+ return entries
168
+ .map((entry) => `<a class="catalog-item" href="${escapeHtml(entry.href)}"><strong class="catalog-item__title">${escapeHtml(entry.title)}</strong>${entry.detail
169
+ ? `<span class="catalog-item__detail">${escapeHtml(entry.detail)}</span>`
170
+ : ''}</a>`)
171
+ .join('');
172
+ }
173
+ function renderCatalogArticles(entries, options) {
129
174
  return [
130
- '<div class="catalog-list">',
131
- ...entries.map((entry) => `<a class="catalog-item" href="${escapeHtml(entry.href)}"><strong class="catalog-item__title">${escapeHtml(entry.title)}</strong>${entry.detail
132
- ? `<span class="catalog-item__detail">${escapeHtml(entry.detail)}</span>`
133
- : ''}</a>`),
175
+ '<div class="catalog-list" data-catalog-articles>',
176
+ renderCatalogArticleItems(entries),
134
177
  '</div>',
178
+ options.hasMore
179
+ ? `<div class="catalog-load-more"><button type="button" class="catalog-load-more__button" data-catalog-load-more data-request-path="${escapeHtml(options.requestPath)}" data-next-offset="${escapeHtml(String(options.nextOffset))}" data-load-more-step="${escapeHtml(String(options.loadMoreStep))}">Load more</button></div>`
180
+ : '',
181
+ ].join('');
182
+ }
183
+ function renderCatalogLoadMoreScript() {
184
+ return `<script>
185
+ (() => {
186
+ const button = document.querySelector('[data-catalog-load-more]');
187
+ const list = document.querySelector('[data-catalog-articles]');
188
+ if (!(button instanceof HTMLButtonElement) || !(list instanceof HTMLElement)) {
189
+ return;
190
+ }
191
+
192
+ const loadMore = async () => {
193
+ const requestPath = button.dataset.requestPath;
194
+ const nextOffset = button.dataset.nextOffset;
195
+ const loadMoreStep = button.dataset.loadMoreStep;
196
+ if (!requestPath || !nextOffset || !loadMoreStep) {
197
+ return;
198
+ }
199
+
200
+ button.disabled = true;
201
+ const previousLabel = button.textContent;
202
+ button.textContent = 'Loading...';
203
+
204
+ try {
205
+ const url = new URL(requestPath, window.location.origin);
206
+ url.searchParams.set('catalog-format', 'posts');
207
+ url.searchParams.set('catalog-offset', nextOffset);
208
+ url.searchParams.set('catalog-limit', loadMoreStep);
209
+
210
+ const response = await fetch(url.toString(), {
211
+ headers: { Accept: 'application/json' },
212
+ });
213
+ if (!response.ok) {
214
+ throw new Error('Failed to load more posts');
215
+ }
216
+
217
+ const payload = await response.json();
218
+ if (typeof payload.itemsHtml === 'string' && payload.itemsHtml !== '') {
219
+ list.insertAdjacentHTML('beforeend', payload.itemsHtml);
220
+ }
221
+
222
+ if (payload.hasMore === true && typeof payload.nextOffset === 'number') {
223
+ button.dataset.nextOffset = String(payload.nextOffset);
224
+ button.disabled = false;
225
+ button.textContent = previousLabel ?? 'Load more';
226
+ return;
227
+ }
228
+
229
+ button.remove();
230
+ } catch {
231
+ button.disabled = false;
232
+ button.textContent = previousLabel ?? 'Load more';
233
+ }
234
+ };
235
+
236
+ button.addEventListener('click', () => {
237
+ void loadMore();
238
+ });
239
+ })();
240
+ </script>`;
241
+ }
242
+ function renderSearchScript() {
243
+ return [
244
+ '<script>',
245
+ '(function () {',
246
+ ' const root = document.querySelector("[data-site-search]");',
247
+ ' if (!root) return;',
248
+ ' const toggle = root.querySelector(".site-search__toggle");',
249
+ ' const panel = root.querySelector(".site-search__panel");',
250
+ ' const form = root.querySelector(".site-search__form");',
251
+ ' const input = root.querySelector(".site-search__input");',
252
+ ' const results = root.querySelector("[data-site-search-results]");',
253
+ ' if (!toggle || !panel || !form || !input || !results) return;',
254
+ ' let controller = null;',
255
+ ' function renderMessage(message) {',
256
+ ' results.innerHTML = `<p class="site-search__message">${escapeHtmlForScript(message)}</p>`;',
257
+ ' }',
258
+ ' function renderHits(hits) {',
259
+ ' if (!Array.isArray(hits) || hits.length === 0) {',
260
+ ' renderMessage("No results.");',
261
+ ' return;',
262
+ ' }',
263
+ ' results.innerHTML = hits.map((hit) => {',
264
+ ' const href = escapeHtmlForScript(hit.canonicalUrl || hit.docId || "#");',
265
+ ' const title = escapeHtmlForScript(hit.title || hit.relativePath || "Untitled");',
266
+ ' const summary = typeof hit.summary === "string" ? `<span class="site-search__item-summary">${escapeHtmlForScript(hit.summary)}</span>` : "";',
267
+ ' const excerpt = hit.bestMatch && typeof hit.bestMatch.excerpt === "string" ? `<span class="site-search__item-excerpt">${escapeHtmlForScript(hit.bestMatch.excerpt)}</span>` : "";',
268
+ ' return `<a class="site-search__item" href="${href}"><strong class="site-search__item-title">${title}</strong>${summary}${excerpt}</a>`;',
269
+ ' }).join("");',
270
+ ' }',
271
+ ' async function runSearch(query) {',
272
+ ' if (controller) controller.abort();',
273
+ ' controller = new AbortController();',
274
+ ' renderMessage("Searching...");',
275
+ ' try {',
276
+ ' const url = new URL("/api/search", window.location.origin);',
277
+ ' url.searchParams.set("q", query);',
278
+ ' url.searchParams.set("topK", "8");',
279
+ ' const response = await fetch(url, { signal: controller.signal });',
280
+ ' if (!response.ok) {',
281
+ ' renderMessage(`Search failed (${response.status}).`);',
282
+ ' return;',
283
+ ' }',
284
+ ' const payload = await response.json();',
285
+ ' renderHits(payload.hits);',
286
+ ' } catch (error) {',
287
+ ' if (error && typeof error === "object" && "name" in error && error.name === "AbortError") return;',
288
+ ' renderMessage("Search failed.");',
289
+ ' }',
290
+ ' }',
291
+ ' function setOpen(open) {',
292
+ ' toggle.setAttribute("aria-expanded", open ? "true" : "false");',
293
+ ' panel.hidden = !open;',
294
+ ' if (open) {',
295
+ ' input.focus();',
296
+ ' if (!results.innerHTML) renderMessage("Search docs, guides, and skills.");',
297
+ ' }',
298
+ ' }',
299
+ ' toggle.addEventListener("click", () => setOpen(panel.hidden));',
300
+ ' form.addEventListener("submit", (event) => {',
301
+ ' event.preventDefault();',
302
+ ' const query = input.value.trim();',
303
+ ' if (!query) {',
304
+ ' renderMessage("Enter a search query.");',
305
+ ' return;',
306
+ ' }',
307
+ ' void runSearch(query);',
308
+ ' });',
309
+ ' document.addEventListener("keydown", (event) => {',
310
+ ' if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {',
311
+ ' event.preventDefault();',
312
+ ' setOpen(true);',
313
+ ' }',
314
+ ' if (event.key === "Escape" && !panel.hidden) setOpen(false);',
315
+ ' });',
316
+ '})();',
317
+ '',
318
+ 'function escapeHtmlForScript(value) {',
319
+ ' return String(value)',
320
+ ' .replaceAll("&", "&amp;")',
321
+ ' .replaceAll("<", "&lt;")',
322
+ ' .replaceAll(">", "&gt;")',
323
+ ' .replaceAll(\'"\', "&quot;")',
324
+ ' .replaceAll("\\\'", "&#39;");',
325
+ '}',
326
+ '</script>',
135
327
  ].join('');
136
328
  }
@@ -1,13 +1,135 @@
1
1
  export function getBuiltInThemeStyles(theme) {
2
- switch (theme) {
3
- case 'atlas':
4
- return buildAtlasThemeStyles();
5
- case 'gazette':
6
- return buildGazetteThemeStyles();
7
- case 'paper':
8
- default:
9
- return buildPaperThemeStyles();
10
- }
2
+ const themeStyles = (() => {
3
+ switch (theme) {
4
+ case 'atlas':
5
+ return buildAtlasThemeStyles();
6
+ case 'gazette':
7
+ return buildGazetteThemeStyles();
8
+ case 'paper':
9
+ default:
10
+ return buildPaperThemeStyles();
11
+ }
12
+ })();
13
+ return `${themeStyles}\n${buildSharedSearchStyles()}`;
14
+ }
15
+ function buildSharedSearchStyles() {
16
+ return `
17
+ .site-header__actions {
18
+ min-width: 0;
19
+ display: flex;
20
+ align-items: flex-end;
21
+ justify-content: flex-end;
22
+ gap: 1rem;
23
+ }
24
+ .site-search {
25
+ position: relative;
26
+ }
27
+ .site-search__toggle {
28
+ appearance: none;
29
+ border: 1px solid var(--border);
30
+ background: color-mix(in srgb, var(--surface) 92%, transparent);
31
+ color: var(--muted);
32
+ border-radius: 999px;
33
+ padding: 0.45rem 0.82rem;
34
+ font: inherit;
35
+ font-size: 0.88rem;
36
+ cursor: pointer;
37
+ }
38
+ .site-search__toggle:hover {
39
+ color: var(--text);
40
+ }
41
+ .site-search__panel {
42
+ position: absolute;
43
+ top: calc(100% + 0.7rem);
44
+ right: 0;
45
+ width: min(32rem, calc(100vw - 2rem));
46
+ padding: 1rem;
47
+ border: 1px solid var(--border);
48
+ border-radius: 18px;
49
+ background: color-mix(in srgb, var(--surface) 95%, white 5%);
50
+ box-shadow: 0 18px 40px rgba(24, 18, 11, 0.14);
51
+ z-index: 2;
52
+ }
53
+ .site-search__label {
54
+ display: block;
55
+ font-size: 0.8rem;
56
+ text-transform: uppercase;
57
+ letter-spacing: 0.08em;
58
+ color: var(--muted);
59
+ margin-bottom: 0.55rem;
60
+ }
61
+ .site-search__controls {
62
+ display: grid;
63
+ grid-template-columns: minmax(0, 1fr) auto;
64
+ gap: 0.6rem;
65
+ }
66
+ .site-search__input,
67
+ .site-search__submit {
68
+ font: inherit;
69
+ }
70
+ .site-search__input {
71
+ width: 100%;
72
+ border: 1px solid var(--border);
73
+ border-radius: 12px;
74
+ background: var(--surface);
75
+ color: var(--text);
76
+ padding: 0.7rem 0.85rem;
77
+ }
78
+ .site-search__submit {
79
+ appearance: none;
80
+ border: 1px solid var(--text);
81
+ border-radius: 12px;
82
+ background: var(--text);
83
+ color: var(--surface);
84
+ padding: 0.7rem 0.95rem;
85
+ cursor: pointer;
86
+ }
87
+ .site-search__hint,
88
+ .site-search__message {
89
+ color: var(--muted);
90
+ font-size: 0.84rem;
91
+ }
92
+ .site-search__results {
93
+ margin-top: 0.9rem;
94
+ display: grid;
95
+ gap: 0.7rem;
96
+ }
97
+ .site-search__item {
98
+ display: block;
99
+ text-decoration: none;
100
+ border-top: 1px solid var(--border);
101
+ padding-top: 0.7rem;
102
+ }
103
+ .site-search__item:first-child {
104
+ border-top: 0;
105
+ padding-top: 0;
106
+ }
107
+ .site-search__item-title {
108
+ display: block;
109
+ color: var(--text);
110
+ }
111
+ .site-search__item-summary,
112
+ .site-search__item-excerpt {
113
+ display: block;
114
+ margin-top: 0.25rem;
115
+ color: var(--muted);
116
+ font-size: 0.88rem;
117
+ }
118
+ .site-search__item-excerpt {
119
+ font-size: 0.82rem;
120
+ }
121
+ @media (max-width: 720px) {
122
+ .site-header__actions {
123
+ flex-direction: column;
124
+ align-items: stretch;
125
+ }
126
+ .site-search__panel {
127
+ left: 0;
128
+ right: auto;
129
+ width: min(100%, 34rem);
130
+ }
131
+ }
132
+ `.trim();
11
133
  }
12
134
  function buildPaperThemeStyles() {
13
135
  return `
@@ -125,6 +247,13 @@ main {
125
247
  padding-top: 1rem;
126
248
  color: var(--muted);
127
249
  }
250
+ .site-footer__meta {
251
+ margin-top: 0.9rem;
252
+ display: flex;
253
+ align-items: center;
254
+ justify-content: space-between;
255
+ gap: 1rem;
256
+ }
128
257
  .site-footer__nav ul,
129
258
  .site-footer__social {
130
259
  list-style: none;
@@ -163,6 +292,19 @@ main {
163
292
  display: block;
164
293
  margin-top: 0.9rem;
165
294
  }
295
+ .site-footer__edit-link {
296
+ display: inline-block;
297
+ margin-top: 0;
298
+ font-size: 0.78rem;
299
+ color: var(--muted);
300
+ opacity: 0.72;
301
+ }
302
+ @media (max-width: 720px) {
303
+ .site-footer__meta {
304
+ flex-direction: column;
305
+ align-items: flex-start;
306
+ }
307
+ }
166
308
  article {
167
309
  background: color-mix(in srgb, var(--surface) 92%, white 8%);
168
310
  border: 1px solid var(--border);
@@ -253,6 +395,27 @@ hr { border: none; border-top: 1px solid var(--border); margin: 2rem 0; }
253
395
  .catalog-item:hover {
254
396
  text-decoration: none;
255
397
  }
398
+ .catalog-load-more {
399
+ margin-top: 1.2rem;
400
+ }
401
+ .catalog-load-more__button {
402
+ appearance: none;
403
+ border: 1px solid var(--border);
404
+ background: var(--surface);
405
+ color: var(--text);
406
+ border-radius: 999px;
407
+ padding: 0.65rem 1rem;
408
+ font: inherit;
409
+ font-size: 0.95rem;
410
+ cursor: pointer;
411
+ }
412
+ .catalog-load-more__button:hover {
413
+ background: color-mix(in srgb, var(--surface) 86%, var(--code-bg) 14%);
414
+ }
415
+ .catalog-load-more__button:disabled {
416
+ cursor: wait;
417
+ opacity: 0.7;
418
+ }
256
419
  @media (max-width: 720px) {
257
420
  html { font-size: 17px; }
258
421
  .site-header, main, .site-footer { padding-left: 1rem; padding-right: 1rem; }
@@ -386,6 +549,13 @@ main {
386
549
  padding-top: 1rem;
387
550
  color: var(--muted);
388
551
  }
552
+ .site-footer__meta {
553
+ margin-top: 1rem;
554
+ display: flex;
555
+ align-items: center;
556
+ justify-content: space-between;
557
+ gap: 1rem;
558
+ }
389
559
  .site-footer__nav ul,
390
560
  .site-footer__social {
391
561
  list-style: none;
@@ -424,6 +594,19 @@ main {
424
594
  display: block;
425
595
  margin-top: 0.95rem;
426
596
  }
597
+ .site-footer__edit-link {
598
+ display: inline-block;
599
+ margin-top: 0;
600
+ font-size: 0.78rem;
601
+ color: var(--muted);
602
+ opacity: 0.72;
603
+ }
604
+ @media (max-width: 720px) {
605
+ .site-footer__meta {
606
+ flex-direction: column;
607
+ align-items: flex-start;
608
+ }
609
+ }
427
610
  article {
428
611
  background: linear-gradient(180deg, rgba(255,255,255,0.96), rgba(247,250,252,0.98));
429
612
  border: 1px solid var(--border);
@@ -518,6 +701,27 @@ hr { border: none; border-top: 1px solid var(--border); margin: 2rem 0; }
518
701
  .catalog-item:hover {
519
702
  text-decoration: none;
520
703
  }
704
+ .catalog-load-more {
705
+ margin-top: 1.2rem;
706
+ }
707
+ .catalog-load-more__button {
708
+ appearance: none;
709
+ border: 1px solid var(--border);
710
+ background: var(--surface);
711
+ color: var(--text);
712
+ border-radius: 999px;
713
+ padding: 0.65rem 1rem;
714
+ font: inherit;
715
+ font-size: 0.95rem;
716
+ cursor: pointer;
717
+ }
718
+ .catalog-load-more__button:hover {
719
+ background: color-mix(in srgb, var(--surface) 75%, var(--accent) 25%);
720
+ }
721
+ .catalog-load-more__button:disabled {
722
+ cursor: wait;
723
+ opacity: 0.7;
724
+ }
521
725
  @media (max-width: 720px) {
522
726
  .site-header__inner, main, .site-footer { padding-left: 1rem; padding-right: 1rem; }
523
727
  .site-header__inner {
@@ -648,6 +852,13 @@ main {
648
852
  padding-top: 1rem;
649
853
  color: var(--muted);
650
854
  }
855
+ .site-footer__meta {
856
+ margin-top: 0.95rem;
857
+ display: flex;
858
+ align-items: center;
859
+ justify-content: space-between;
860
+ gap: 1rem;
861
+ }
651
862
  .site-footer__nav ul,
652
863
  .site-footer__social {
653
864
  list-style: none;
@@ -686,6 +897,19 @@ main {
686
897
  display: block;
687
898
  margin-top: 0.9rem;
688
899
  }
900
+ .site-footer__edit-link {
901
+ display: inline-block;
902
+ margin-top: 0;
903
+ font-size: 0.78rem;
904
+ color: var(--muted);
905
+ opacity: 0.72;
906
+ }
907
+ @media (max-width: 720px) {
908
+ .site-footer__meta {
909
+ flex-direction: column;
910
+ align-items: flex-start;
911
+ }
912
+ }
689
913
  article {
690
914
  background:
691
915
  linear-gradient(180deg, rgba(255,255,255,0.94), rgba(255,253,248,0.98)),
@@ -785,6 +1009,27 @@ hr { border: none; border-top: 1px solid var(--border); margin: 2rem 0; }
785
1009
  .catalog-item:hover {
786
1010
  text-decoration: none;
787
1011
  }
1012
+ .catalog-load-more {
1013
+ margin-top: 1.2rem;
1014
+ }
1015
+ .catalog-load-more__button {
1016
+ appearance: none;
1017
+ border: 1px solid var(--border);
1018
+ background: var(--surface);
1019
+ color: var(--text);
1020
+ border-radius: 999px;
1021
+ padding: 0.65rem 1rem;
1022
+ font: inherit;
1023
+ font-size: 0.95rem;
1024
+ cursor: pointer;
1025
+ }
1026
+ .catalog-load-more__button:hover {
1027
+ background: color-mix(in srgb, var(--surface) 84%, #fff 16%);
1028
+ }
1029
+ .catalog-load-more__button:disabled {
1030
+ cursor: wait;
1031
+ opacity: 0.7;
1032
+ }
788
1033
  @media (max-width: 720px) {
789
1034
  html { font-size: 17px; }
790
1035
  .site-header, main, .site-footer { padding-left: 1rem; padding-right: 1rem; }