retypeset-odyssey 0.1.4 → 0.1.7

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "retypeset-odyssey",
3
3
  "type": "module",
4
- "version": "0.1.4",
4
+ "version": "0.1.7",
5
5
  "packageManager": "pnpm@10.26.0",
6
6
  "repository": "https://github.com/lifeodyssey/retypeset-odyssey",
7
7
  "exports": {
@@ -47,7 +47,8 @@ const { journals, lang } = Astro.props
47
47
 
48
48
  {/* journal description */}
49
49
  <div class="heti hidden lg:block">
50
- <p>{getJournalDescription(journal, 'list')}</p>
50
+ {/* Pre-`<!-- more -->` excerpt rendered as raw markdown HTML. */}
51
+ <Fragment set:html={getJournalDescription(journal, 'list')} />
51
52
  </div>
52
53
  </li>
53
54
  )
@@ -47,7 +47,8 @@ const { notes, lang } = Astro.props
47
47
 
48
48
  {/* note description */}
49
49
  <div class="heti hidden lg:block">
50
- <p>{getNoteDescription(note, 'list')}</p>
50
+ {/* Pre-`<!-- more -->` excerpt rendered as raw markdown HTML. */}
51
+ <Fragment set:html={getNoteDescription(note, 'list')} />
51
52
  </div>
52
53
  </li>
53
54
  )
@@ -78,7 +78,10 @@ const isHome = isHomePage(Astro.url.pathname)
78
78
  class="heti hidden"
79
79
  lg="mt-2.25 block"
80
80
  >
81
- <p>{getPostDescription(post, 'list')}</p>
81
+ {/* Pre-`<!-- more -->` excerpt rendered as raw markdown HTML
82
+ so blockquotes / lists / paragraphs keep their structure.
83
+ The `heti` parent provides Chinese typographic styling. */}
84
+ <Fragment set:html={getPostDescription(post, 'list')} />
82
85
  </div>
83
86
  )}
84
87
  </li>
@@ -4,19 +4,41 @@ import { getPageInfo } from '@/utils/page'
4
4
 
5
5
  const { currentLang } = getPageInfo(Astro.url.pathname)
6
6
  const currentUI = ui[currentLang as keyof typeof ui] ?? {}
7
+
8
+ // Input placeholder — librarian's note tone, doubles as empty-state hint.
9
+ const placeholderText: Record<string, string> = {
10
+ 'zh': '试着搜「agent」或「translate」…',
11
+ 'en': 'Try “agent” or “translate”…',
12
+ 'ja': '「agent」や「translate」で試してみてください…',
13
+ }
14
+ const inputPlaceholder = placeholderText[currentLang] ?? placeholderText.en
15
+
16
+ // Close button aria-label by language — kept as an inline lookup so we
17
+ // don't have to widen the theme's Translation interface in ui.ts for a
18
+ // single component.
19
+ const closeLabels: Record<string, string> = {
20
+ 'zh': '关闭',
21
+ 'en': 'Close',
22
+ 'ja': '閉じる',
23
+ }
24
+ const closeLabel = closeLabels[currentLang] ?? closeLabels.en
7
25
  ---
8
26
 
9
- <dialog id="search-modal" class="search-modal">
10
- <div class="modal-content">
11
- <div class="modal-header">
12
- <h2 class="modal-title">{currentUI.search || 'Search'}</h2>
13
- <button id="close-search-modal" class="close-btn" aria-label="Close">
14
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
15
- <path d="M18 6L6 18M6 6l12 12" />
16
- </svg>
17
- </button>
18
- </div>
19
- <div id="search-container" data-placeholder={currentUI.searchPlaceholder} data-no-results={currentUI.searchNoResults} data-results-found={currentUI.searchResultsFound}></div>
27
+ <dialog id="search-modal" class="search-modal" aria-label={currentUI.search || 'Search'}>
28
+ <div class="modal-card">
29
+ <button
30
+ type="button"
31
+ id="close-search-modal"
32
+ class="modal-close"
33
+ aria-label={closeLabel}
34
+ >×</button>
35
+ <div
36
+ id="search-container"
37
+ class="modal-results"
38
+ data-placeholder={inputPlaceholder}
39
+ data-no-results={currentUI.searchNoResults}
40
+ data-results-found={currentUI.searchResultsFound}
41
+ ></div>
20
42
  </div>
21
43
  </dialog>
22
44
 
@@ -24,317 +46,431 @@ const currentUI = ui[currentLang as keyof typeof ui] ?? {}
24
46
  <script is:inline src="/pagefind/pagefind-ui.js" type="text/javascript"></script>
25
47
 
26
48
  <script>
27
- let pagefindInitialized = false;
49
+ let pagefindInitialized = false
28
50
 
29
51
  function initPagefind() {
30
- if (pagefindInitialized) return;
31
- if (typeof PagefindUI === 'undefined') return;
52
+ if (pagefindInitialized) return
53
+ if (typeof (window as any).PagefindUI === 'undefined') return
32
54
 
33
- const container = document.getElementById('search-container');
34
- const placeholder = container?.dataset.placeholder || 'Type to search...';
55
+ const container = document.getElementById('search-container')
56
+ if (!container) return
57
+ const placeholder = container.dataset.placeholder || 'Type to search...'
35
58
 
36
- new PagefindUI({
59
+ new (window as any).PagefindUI({
37
60
  element: '#search-container',
38
61
  showImages: false,
39
- showSubResults: true
40
- });
62
+ showSubResults: true,
63
+ })
41
64
 
42
- // Set placeholder after initialization
43
- setTimeout(() => {
44
- const input = document.querySelector('.pagefind-ui__search-input') as HTMLInputElement;
45
- if (input) input.placeholder = placeholder;
46
- }, 50);
65
+ // Pagefind UI mounts its own input next tick. Adopt our placeholder
66
+ // (the librarian-note we want shown while empty). No separate empty-state
67
+ // block the placeholder itself carries that copy.
68
+ requestAnimationFrame(() => {
69
+ const input = document.querySelector('.pagefind-ui__search-input') as HTMLInputElement | null
70
+ if (!input) return
71
+ input.placeholder = placeholder
72
+ })
47
73
 
48
- pagefindInitialized = true;
74
+ pagefindInitialized = true
49
75
  }
50
76
 
51
77
  function openSearchModal() {
52
- const modal = document.getElementById('search-modal') as HTMLDialogElement;
53
- if (modal) {
54
- modal.showModal();
55
- initPagefind();
56
- setTimeout(() => {
57
- const input = modal.querySelector('.pagefind-ui__search-input') as HTMLInputElement;
58
- input?.focus();
59
- }, 100);
60
- }
78
+ const modal = document.getElementById('search-modal') as HTMLDialogElement | null
79
+ if (!modal) return
80
+ modal.showModal()
81
+ initPagefind()
82
+ setTimeout(() => {
83
+ const input = modal.querySelector('.pagefind-ui__search-input') as HTMLInputElement | null
84
+ input?.focus()
85
+ }, 80)
61
86
  }
62
87
 
63
88
  function closeSearchModal() {
64
- const modal = document.getElementById('search-modal') as HTMLDialogElement;
65
- modal?.close();
89
+ const modal = document.getElementById('search-modal') as HTMLDialogElement | null
90
+ modal?.close()
66
91
  }
67
92
 
93
+ // Trigger button on the chrome + close button if any consumer renders one.
68
94
  document.addEventListener('click', (e) => {
69
- const target = e.target as Element;
95
+ const target = e.target as Element
70
96
  if (target.closest('#search-button')) {
71
- e.preventDefault();
72
- openSearchModal();
97
+ e.preventDefault()
98
+ openSearchModal()
73
99
  }
74
100
  if (target.closest('#close-search-modal')) {
75
- closeSearchModal();
101
+ closeSearchModal()
76
102
  }
77
- });
78
-
103
+ })
104
+
105
+ // Cmd+K / Ctrl+K to toggle. Skip when the user is typing in some other
106
+ // editable field (so the OS / app shortcut still works there), unless
107
+ // that field is the search input itself (closing should work from inside).
108
+ document.addEventListener('keydown', (e) => {
109
+ if (!(e.metaKey || e.ctrlKey) || e.key.toLowerCase() !== 'k') return
110
+ const active = document.activeElement as HTMLElement | null
111
+ const inSearch = active?.classList.contains('pagefind-ui__search-input') ?? false
112
+ const inEditable = !!active && (
113
+ active.tagName === 'INPUT'
114
+ || active.tagName === 'TEXTAREA'
115
+ || active.isContentEditable
116
+ )
117
+ if (inEditable && !inSearch) return
118
+ e.preventDefault()
119
+ const modal = document.getElementById('search-modal') as HTMLDialogElement | null
120
+ if (modal?.open) closeSearchModal()
121
+ else openSearchModal()
122
+ })
123
+
124
+ // Backdrop click closes. Standard <dialog> idiom: clicks on modal content
125
+ // fire on inner elements (e.target = inner), clicks on the backdrop fire on
126
+ // the dialog itself (e.target = dialog). Coordinate-based checks like the
127
+ // previous bounding-rect math are unreliable because some browsers report
128
+ // negative/oversized rects for full-viewport dialogs.
79
129
  document.getElementById('search-modal')?.addEventListener('click', (e) => {
80
- const modal = e.currentTarget as HTMLDialogElement;
81
- const rect = modal.getBoundingClientRect();
82
- if (
83
- e.clientX < rect.left ||
84
- e.clientX > rect.right ||
85
- e.clientY < rect.top ||
86
- e.clientY > rect.bottom
87
- ) {
88
- closeSearchModal();
130
+ if (e.target === e.currentTarget) {
131
+ closeSearchModal()
89
132
  }
90
- });
133
+ })
91
134
 
92
- (window as any).openSearchModal = openSearchModal;
135
+ ;(window as any).openSearchModal = openSearchModal
93
136
  </script>
94
137
 
95
138
  <style is:global>
96
- /* Modal Base */
139
+ /* ============================================================
140
+ Search Modal — "white-paper" (variant 2C)
141
+ ------------------------------------------------------------
142
+ - Pure paper aesthetic: hairline frame, sharp 90° corners,
143
+ no shadow. Reads like a slip of paper pinned over the page,
144
+ not a SaaS popup.
145
+ - Typographic input: transparent, bottom-border only, serif.
146
+ The form bar isn't a "field" so much as a writing line.
147
+ - Yellow <mark> highlight is the theme's only accent token,
148
+ same as inline marks in body type.
149
+ - 0 `!important`. The `.search-modal .pagefind-ui …` chain
150
+ (specificity 0,3,1) outweighs Pagefind's defaults.
151
+ ============================================================ */
152
+
97
153
  .search-modal {
154
+ --sm-fg: oklch(var(--un-preset-theme-colors-primary));
155
+ --sm-fg-soft: oklch(var(--un-preset-theme-colors-secondary));
156
+ --sm-bg: oklch(var(--un-preset-theme-colors-background));
157
+ --sm-highlight: oklch(
158
+ var(--un-preset-theme-colors-highlight) /
159
+ var(--un-preset-theme-colors-highlight--alpha)
160
+ );
161
+ /* Hairline surfaces derived from foreground at low alpha — keeps
162
+ rules in the same hue family as text. */
163
+ --sm-rule: color-mix(in oklch, var(--sm-fg) 16%, transparent);
164
+ --sm-rule-soft: color-mix(in oklch, var(--sm-fg) 8%, transparent);
165
+ --sm-row-hover: color-mix(in oklch, var(--sm-fg) 5%, transparent);
166
+ }
167
+
168
+ /* ------------- dialog shell ---------------------------------- */
169
+
170
+ .search-modal {
171
+ /* Drawer pulled down from the top edge, not centered popup. */
98
172
  padding: 0;
99
- border: none;
100
- border-radius: 12px;
101
- max-width: 600px;
102
- width: 90vw;
103
- background: transparent;
104
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
173
+ margin: 0;
174
+ margin-inline: auto;
175
+ margin-top: max(8vh, 3rem);
176
+ border: 1px solid var(--sm-rule);
177
+ border-radius: 0; /* sharp paper corners */
178
+ width: min(560px, calc(100vw - 2rem));
179
+ max-width: 560px;
180
+ max-height: min(calc(100dvh - 16vh - 2rem), 560px);
181
+ background: var(--sm-bg);
182
+ color: var(--sm-fg);
183
+ /* No shadow — the paper aesthetic prefers a hairline frame over a lift. */
184
+ box-shadow: none;
185
+ overflow: visible;
105
186
  }
106
187
 
107
188
  .search-modal::backdrop {
108
- background: rgba(0, 0, 0, 0.5);
109
- backdrop-filter: blur(4px);
189
+ /* Quiet overlay; light enough to keep the page legible behind. */
190
+ background: color-mix(in oklch, black 18%, transparent);
191
+ backdrop-filter: blur(2px);
192
+ -webkit-backdrop-filter: blur(2px);
110
193
  }
111
194
 
112
- .modal-content {
113
- background: var(--c-bg);
114
- border-radius: 12px;
115
- overflow: hidden;
116
- }
117
-
118
- /* Header */
119
- .modal-header {
195
+ .search-modal .modal-card {
196
+ position: relative;
197
+ background: var(--sm-bg);
120
198
  display: flex;
121
- justify-content: space-between;
122
- align-items: center;
123
- padding: 1rem 1.25rem;
124
- border-bottom: 1px solid var(--c-divider, rgba(128, 128, 128, 0.2));
125
- }
126
-
127
- .modal-title {
128
- font-size: 1rem;
129
- font-weight: 600;
130
- color: var(--c-text-1);
131
- margin: 0;
199
+ flex-direction: column;
200
+ max-height: inherit;
201
+ overflow: hidden;
132
202
  }
133
203
 
134
- .close-btn {
135
- padding: 0.25rem;
204
+ /* Visible close affordance for mouse users — kept tiny so it doesn't break
205
+ the bone-minimal paper aesthetic. Positioned vertically aligned with the
206
+ input row's center, on the right edge — same slot the Pagefind clear
207
+ button used to occupy (now hidden), so it reads as "the input row's
208
+ right-hand control". */
209
+ .search-modal .modal-close {
210
+ position: absolute;
211
+ /* Input-row paddings: 1rem top + ~2.15rem input height + 0.875rem bottom.
212
+ Center of input ≈ 1rem + 2.15/2 ≈ 2.075rem from modal-card top.
213
+ Half the button height (1.5/2 = 0.75rem) backed off → ~1.325rem. */
214
+ top: 1.325rem;
215
+ right: 1rem;
216
+ z-index: 1;
217
+ width: 1.5rem;
218
+ height: 1.5rem;
219
+ padding: 0;
220
+ font-family: ui-sans-serif, system-ui, sans-serif;
221
+ font-size: 1.125rem;
222
+ line-height: 1;
223
+ color: var(--sm-fg-soft);
136
224
  background: transparent;
137
- border: none;
138
- color: var(--c-text-2);
225
+ border: 0;
226
+ border-radius: 2px;
139
227
  cursor: pointer;
140
- border-radius: 4px;
141
- display: flex;
142
- align-items: center;
143
- justify-content: center;
144
- transition: color 0.2s, background 0.2s;
228
+ transition: color 120ms, background-color 120ms;
145
229
  }
146
-
147
- .close-btn:hover {
148
- color: var(--c-text-1);
149
- background: rgba(128, 128, 128, 0.1);
230
+ .search-modal .modal-close:hover {
231
+ color: var(--sm-fg);
232
+ background: color-mix(in oklch, var(--sm-fg) 6%, transparent);
150
233
  }
151
-
152
- /* Pagefind Container */
153
- #search-container {
154
- padding: 0;
234
+ .search-modal .modal-close:focus-visible {
235
+ outline: 1px solid var(--sm-fg);
236
+ outline-offset: 1px;
155
237
  }
156
238
 
157
- /* Pagefind UI Overrides */
239
+ /* ============================================================
240
+ Pagefind UI overrides — scoped under .search-modal .pagefind-ui
241
+ (specificity 0,3,1) so we win without any !important.
242
+ ============================================================ */
243
+
158
244
  .search-modal .pagefind-ui {
159
- --pagefind-ui-scale: 1;
160
- --pagefind-ui-primary: var(--c-primary, #3b82f6);
161
- --pagefind-ui-text: var(--c-text-1);
162
- --pagefind-ui-background: var(--c-bg);
163
- --pagefind-ui-border: var(--c-divider, rgba(128, 128, 128, 0.2));
245
+ --pagefind-ui-primary: var(--sm-fg);
246
+ --pagefind-ui-text: var(--sm-fg);
247
+ --pagefind-ui-background: var(--sm-bg);
248
+ --pagefind-ui-border: var(--sm-rule);
249
+ --pagefind-ui-tag: var(--sm-bg);
164
250
  --pagefind-ui-border-width: 1px;
165
- --pagefind-ui-border-radius: 8px;
251
+ --pagefind-ui-border-radius: 0;
166
252
  --pagefind-ui-font: inherit;
167
253
  }
168
254
 
169
- .search-modal .pagefind-ui__form {
170
- padding: 1rem 1.25rem;
171
- border-bottom: 1px solid var(--c-divider, rgba(128, 128, 128, 0.2));
172
- }
173
-
174
- .search-modal .pagefind-ui__search-input {
175
- font-size: 0.95rem !important;
176
- padding: 0.625rem 1rem !important;
177
- height: auto !important;
178
- background: var(--c-bg-soft, rgba(128, 128, 128, 0.05)) !important;
179
- border: 1px solid var(--c-divider, rgba(128, 128, 128, 0.2)) !important;
180
- border-radius: 8px !important;
181
- color: var(--c-text-1) !important;
182
- transition: border-color 0.2s, box-shadow 0.2s !important;
183
- }
184
-
185
- .search-modal .pagefind-ui__search-input:focus {
186
- outline: none !important;
187
- border-color: var(--c-primary, #3b82f6) !important;
188
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
189
- }
190
-
191
- .search-modal .pagefind-ui__search-input::placeholder {
192
- color: var(--c-text-3, rgba(128, 128, 128, 0.6)) !important;
193
- }
194
-
195
- .search-modal .pagefind-ui__search-clear {
196
- padding: 0.5rem !important;
197
- right: 0.5rem !important;
198
- color: var(--c-text-3) !important;
199
- background: transparent !important;
200
- }
255
+ /* ------------- input row ------------------------------------- */
201
256
 
202
- .search-modal .pagefind-ui__search-clear:hover {
203
- color: var(--c-text-1) !important;
257
+ /* `position: relative` re-declared so the icon (Pagefind's `::before`)
258
+ anchors here. */
259
+ .search-modal .pagefind-ui .pagefind-ui__form {
260
+ position: relative;
261
+ padding: 1rem 1.25rem 0.875rem;
204
262
  }
205
263
 
206
- /* Results Area */
207
- .search-modal .pagefind-ui__results-area {
208
- padding: 0 !important;
209
- margin: 0 !important;
264
+ /* Re-place Pagefind's magnifying-glass icon. Extra `.pagefind-ui` is
265
+ not a no-op: Pagefind's own rule is
266
+ `.pagefind-ui__form.svelte-xxxxx::before` (0,2,1); ours is 0,3,1. */
267
+ .search-modal .pagefind-ui .pagefind-ui__form::before {
268
+ top: 50%;
269
+ left: 1.25rem;
270
+ transform: translateY(-50%);
271
+ width: 14px;
272
+ height: 14px;
273
+ opacity: 0.5;
210
274
  }
211
275
 
212
- .search-modal .pagefind-ui__message {
213
- padding: 1rem 1.25rem !important;
214
- font-size: 0.875rem !important;
215
- color: var(--c-text-2) !important;
216
- border-bottom: 1px solid var(--c-divider, rgba(128, 128, 128, 0.2)) !important;
276
+ /* Typographic input — no fill, no rounded chrome, just a writing line. */
277
+ .search-modal .pagefind-ui .pagefind-ui__search-input {
278
+ width: 100%;
279
+ height: auto;
280
+ padding: 0.375rem 2.25rem 0.375rem 1.625rem;
281
+ background: transparent;
282
+ border: 0;
283
+ border-bottom: 1px solid var(--sm-fg);
284
+ border-radius: 0;
285
+ box-shadow: none;
286
+ font-family: var(--at-font-serif, ui-serif, Georgia, "Times New Roman", serif);
287
+ font-size: 1rem;
288
+ line-height: 1.4;
289
+ color: var(--sm-fg);
290
+ transition: border-color 140ms cubic-bezier(0.22, 1, 0.36, 1);
217
291
  }
218
292
 
219
- .search-modal .pagefind-ui__results {
220
- max-height: 50vh;
221
- overflow-y: auto;
222
- padding: 0 !important;
293
+ .search-modal .pagefind-ui .pagefind-ui__search-input:focus,
294
+ .search-modal .pagefind-ui .pagefind-ui__search-input:focus-visible {
295
+ outline: none;
296
+ border-bottom-color: var(--sm-fg);
223
297
  }
224
298
 
225
- /* Individual Result */
226
- .search-modal .pagefind-ui__result {
227
- padding: 1rem 1.25rem !important;
228
- border-bottom: 1px solid var(--c-divider, rgba(128, 128, 128, 0.1)) !important;
229
- transition: background 0.15s !important;
299
+ .search-modal .pagefind-ui .pagefind-ui__search-input::placeholder {
300
+ color: color-mix(in oklch, var(--sm-fg-soft) 65%, transparent);
301
+ font-style: italic;
230
302
  }
231
303
 
232
- .search-modal .pagefind-ui__result:hover {
233
- background: var(--c-bg-soft, rgba(128, 128, 128, 0.05)) !important;
304
+ /* Defang Chrome autofill — its yellow/blue would shatter the paper feel. */
305
+ .search-modal .pagefind-ui .pagefind-ui__search-input:-webkit-autofill,
306
+ .search-modal .pagefind-ui .pagefind-ui__search-input:-webkit-autofill:focus {
307
+ -webkit-text-fill-color: var(--sm-fg);
308
+ box-shadow: 0 0 0 100px var(--sm-bg) inset;
309
+ transition: background-color 9999s ease-in-out 0s;
234
310
  }
235
311
 
236
- .search-modal .pagefind-ui__result:last-child {
237
- border-bottom: none !important;
312
+ /* Hide Pagefind's own clear-input × — we already render a close button
313
+ for the entire modal in the top-right corner (.modal-close), and a
314
+ second × pinned to the input itself would create a confusing double-
315
+ icon situation right when the user starts typing. Backspace / closing
316
+ the modal achieves the same thing. */
317
+ .search-modal .pagefind-ui .pagefind-ui__search-clear {
318
+ display: none;
238
319
  }
239
320
 
240
- .search-modal .pagefind-ui__result-inner {
241
- padding: 0 !important;
242
- }
321
+ /* ------------- results --------------------------------------- */
243
322
 
244
- .search-modal .pagefind-ui__result-title {
245
- margin-bottom: 0.375rem !important;
323
+ .search-modal .pagefind-ui .pagefind-ui__results-area {
324
+ padding: 0;
325
+ margin: 0;
246
326
  }
247
-
248
- .search-modal .pagefind-ui__result-link {
249
- color: var(--c-text-1) !important;
250
- font-weight: 600 !important;
251
- font-size: 0.95rem !important;
252
- text-decoration: none !important;
253
- transition: color 0.15s !important;
327
+ .search-modal .pagefind-ui .pagefind-ui__results {
328
+ max-height: min(60vh, 420px);
329
+ overflow-y: auto;
330
+ padding: 0;
331
+ margin: 0;
254
332
  }
255
-
256
- .search-modal .pagefind-ui__result-link:hover {
257
- color: var(--c-primary, #3b82f6) !important;
333
+ .search-modal .pagefind-ui .pagefind-ui__results::-webkit-scrollbar { width: 5px; }
334
+ .search-modal .pagefind-ui .pagefind-ui__results::-webkit-scrollbar-track { background: transparent; }
335
+ .search-modal .pagefind-ui .pagefind-ui__results::-webkit-scrollbar-thumb {
336
+ background: var(--sm-rule);
337
+ border-radius: 0;
338
+ }
339
+ .search-modal .pagefind-ui .pagefind-ui__results::-webkit-scrollbar-thumb:hover {
340
+ background: color-mix(in oklch, var(--sm-fg) 28%, transparent);
341
+ }
342
+
343
+ /* Result row — soft hairline rules between, no card chrome.
344
+ Focus / hover is a background tint only (kept under BAN 1). */
345
+ .search-modal .pagefind-ui .pagefind-ui__result {
346
+ padding: 0.75rem 1.25rem;
347
+ border-top: 1px solid var(--sm-rule-soft);
348
+ transition: background-color 120ms cubic-bezier(0.22, 1, 0.36, 1);
349
+ }
350
+ .search-modal .pagefind-ui .pagefind-ui__result:first-child {
351
+ border-top: 1px solid var(--sm-rule);
352
+ }
353
+ .search-modal .pagefind-ui .pagefind-ui__result:hover,
354
+ .search-modal .pagefind-ui .pagefind-ui__result:focus-within {
355
+ background: var(--sm-row-hover);
356
+ }
357
+ .search-modal .pagefind-ui .pagefind-ui__result-inner { padding: 0; }
358
+ .search-modal .pagefind-ui .pagefind-ui__result-title { margin: 0 0 0.1875rem; }
359
+
360
+ /* Title link — serif, typographic; :link/:visited covered so the
361
+ browser's default purple-blue for visited links cannot leak through. */
362
+ .search-modal .pagefind-ui .pagefind-ui__result-link,
363
+ .search-modal .pagefind-ui .pagefind-ui__result-link:link,
364
+ .search-modal .pagefind-ui .pagefind-ui__result-link:visited,
365
+ .search-modal .pagefind-ui .pagefind-ui__result-link:any-link {
366
+ display: block;
367
+ font-family: var(--at-font-serif, ui-serif, Georgia, serif);
368
+ font-size: 0.9375rem;
369
+ font-weight: 500;
370
+ line-height: 1.4;
371
+ color: var(--sm-fg);
372
+ text-decoration: none;
373
+ transition: color 120ms;
374
+ }
375
+ .search-modal .pagefind-ui .pagefind-ui__result-link:hover,
376
+ .search-modal .pagefind-ui .pagefind-ui__result-link:focus-visible {
377
+ color: var(--sm-fg);
378
+ text-decoration: underline;
379
+ text-decoration-color: var(--sm-fg);
380
+ text-underline-offset: 3px;
381
+ }
382
+
383
+ .search-modal .pagefind-ui .pagefind-ui__result-excerpt {
384
+ margin: 0;
385
+ font-size: 0.8125rem;
386
+ line-height: 1.6;
387
+ color: var(--sm-fg-soft);
258
388
  }
259
389
 
260
- .search-modal .pagefind-ui__result-link::before {
261
- content: "▸ " !important;
262
- color: var(--c-text-3) !important;
390
+ /* Sub-results (Pagefind's grouped sections) — kept slightly indented.
391
+ 1px left rule is allowed (BAN 1 only prohibits >1px decorative stripes). */
392
+ .search-modal .pagefind-ui .pagefind-ui__result-nested {
393
+ margin: 0.5rem 0 0;
394
+ padding-left: 0.75rem;
395
+ border-left: 1px solid var(--sm-rule);
263
396
  }
264
-
265
- .search-modal .pagefind-ui__result-excerpt {
266
- font-size: 0.85rem !important;
267
- line-height: 1.5 !important;
268
- color: var(--c-text-2) !important;
269
- margin-top: 0.25rem !important;
397
+ .search-modal .pagefind-ui .pagefind-ui__result-nested .pagefind-ui__result-link::before {
398
+ content: '↳ ';
399
+ color: var(--sm-fg-soft);
270
400
  }
271
401
 
272
- /* Highlight */
273
- .search-modal .pagefind-ui__result-excerpt mark,
402
+ /* Highlighted match — yellow, theme highlight token; same as body <mark>. */
403
+ .search-modal .pagefind-ui .pagefind-ui__result-excerpt mark,
274
404
  .search-modal mark {
275
- background: rgba(250, 204, 21, 0.4) !important;
276
- color: inherit !important;
277
- padding: 0.1em 0.2em !important;
278
- border-radius: 2px !important;
279
- }
280
-
281
- /* Sub Results */
282
- .search-modal .pagefind-ui__result-nested {
283
- margin-left: 1rem !important;
284
- padding-left: 0.75rem !important;
285
- border-left: 2px solid var(--c-divider, rgba(128, 128, 128, 0.2)) !important;
286
- margin-top: 0.5rem !important;
287
- }
288
-
289
- .search-modal .pagefind-ui__result-nested .pagefind-ui__result-link::before {
290
- content: "⤷ " !important;
291
- }
292
-
293
- /* Button */
294
- .search-modal .pagefind-ui__button {
295
- margin: 1rem 1.25rem !important;
296
- padding: 0.625rem 1rem !important;
297
- background: var(--c-bg-soft, rgba(128, 128, 128, 0.05)) !important;
298
- border: 1px solid var(--c-divider, rgba(128, 128, 128, 0.2)) !important;
299
- border-radius: 8px !important;
300
- color: var(--c-text-2) !important;
301
- font-size: 0.875rem !important;
302
- cursor: pointer !important;
303
- transition: all 0.15s !important;
304
- }
305
-
306
- .search-modal .pagefind-ui__button:hover {
307
- background: var(--c-bg-mute, rgba(128, 128, 128, 0.1)) !important;
308
- color: var(--c-text-1) !important;
309
- }
310
-
311
- /* Hide default drawer toggle */
312
- .search-modal .pagefind-ui__drawer {
313
- display: none !important;
314
- }
315
-
316
- /* Loading state */
317
- .search-modal .pagefind-ui__loading {
318
- padding: 2rem 1.25rem !important;
319
- text-align: center !important;
320
- color: var(--c-text-3) !important;
321
- }
322
-
323
- /* Scrollbar styling */
324
- .search-modal .pagefind-ui__results::-webkit-scrollbar {
325
- width: 6px;
326
- }
327
-
328
- .search-modal .pagefind-ui__results::-webkit-scrollbar-track {
405
+ background: var(--sm-highlight);
406
+ color: inherit;
407
+ padding: 0.05em 0.15em;
408
+ border-radius: 0;
409
+ font-weight: 500;
410
+ }
411
+
412
+ /* Pagefind status / message line */
413
+ .search-modal .pagefind-ui .pagefind-ui__message {
414
+ padding: 0.75rem 1.25rem;
415
+ font-size: 0.8125rem;
416
+ color: var(--sm-fg-soft);
417
+ font-style: italic;
418
+ border-top: 1px solid var(--sm-rule);
419
+ }
420
+
421
+ /* "Load more" button */
422
+ .search-modal .pagefind-ui .pagefind-ui__button {
423
+ display: block;
424
+ width: calc(100% - 2.5rem);
425
+ margin: 0.75rem 1.25rem;
426
+ padding: 0.5rem 0.875rem;
427
+ font: inherit;
428
+ font-size: 0.8125rem;
429
+ color: var(--sm-fg-soft);
329
430
  background: transparent;
431
+ border: 1px solid var(--sm-rule);
432
+ border-radius: 0;
433
+ cursor: pointer;
434
+ transition: background-color 120ms, color 120ms, border-color 120ms;
435
+ }
436
+ .search-modal .pagefind-ui .pagefind-ui__button:hover {
437
+ color: var(--sm-fg);
438
+ background: var(--sm-row-hover);
439
+ border-color: color-mix(in oklch, var(--sm-fg) 28%, transparent);
440
+ }
441
+
442
+ /* `.pagefind-ui__drawer` is the OUTER wrapper around results-area, NOT the
443
+ small "show filters" toggle (despite the name). Inherited from a legacy
444
+ override that set this to `display: none` — which silently collapsed all
445
+ results to 0×0px even though the DOM was populated. Force block layout
446
+ so it stacks below the form like prose. */
447
+ .search-modal .pagefind-ui .pagefind-ui__drawer { display: block; }
448
+
449
+ .search-modal .pagefind-ui .pagefind-ui__loading {
450
+ padding: 1.25rem 1.25rem;
451
+ text-align: center;
452
+ color: var(--sm-fg-soft);
453
+ font-style: italic;
454
+ }
455
+
456
+ /* Responsive — narrow screens get a bigger modal so excerpts have room. */
457
+ @media (max-width: 480px) {
458
+ .search-modal {
459
+ margin-top: max(5vh, 1.5rem);
460
+ width: calc(100vw - 1rem);
461
+ max-height: calc(100dvh - 10vh - 1rem);
462
+ }
463
+ .search-modal .pagefind-ui .pagefind-ui__results { max-height: 55vh; }
330
464
  }
331
465
 
332
- .search-modal .pagefind-ui__results::-webkit-scrollbar-thumb {
333
- background: var(--c-divider, rgba(128, 128, 128, 0.3));
334
- border-radius: 3px;
335
- }
336
-
337
- .search-modal .pagefind-ui__results::-webkit-scrollbar-thumb:hover {
338
- background: var(--c-text-3, rgba(128, 128, 128, 0.5));
466
+ @media (prefers-reduced-motion: reduce) {
467
+ .search-modal .pagefind-ui .pagefind-ui__search-input,
468
+ .search-modal .pagefind-ui .pagefind-ui__result,
469
+ .search-modal .pagefind-ui .pagefind-ui__result-link,
470
+ .search-modal .pagefind-ui .pagefind-ui__button,
471
+ .search-modal .pagefind-ui .pagefind-ui__search-clear,
472
+ .search-modal .pagefind-ui .pagefind-ui__search-clear::before {
473
+ transition: none;
474
+ }
339
475
  }
340
476
  </style>
@@ -125,9 +125,16 @@ function getEntryDescription(
125
125
 
126
126
  const renderedContent = markdownParser.render(cleanContent)
127
127
 
128
- // For 'list' scene with <!-- more --> marker, return full content without truncation
128
+ // For 'list' scene with an explicit <!-- more --> marker, return the
129
+ // *rendered HTML* (not stripped text) so blockquotes, lists, and
130
+ // paragraphs keep their structure on the homepage post list. The
131
+ // consumer (PostList / NoteList / JournalList) injects this via
132
+ // `<Fragment set:html={…} />` inside a `heti` typography wrapper.
133
+ // Other scenes (og, meta, feed) still receive plain text because they
134
+ // feed single-line metadata / RSS where HTML would leak through as raw
135
+ // tags.
129
136
  if (scene === 'list' && hasMoreMarker) {
130
- return cleanTextContent(renderedContent)
137
+ return renderedContent
131
138
  }
132
139
 
133
140
  // Otherwise, apply truncation