handoff pass: vendored sveltia bundle, astro pinned ^7.2.10, root README + refreshed deploy runbook, review fixes (coverless card grid, shared NewsCard/lib, real site origin, nginx security headers, linger step, dead config removed, preview.css sync, tsconfig)
This commit is contained in:
@@ -59,6 +59,7 @@ collections:
|
||||
- { name: category, label: "Category", widget: string, default: "news" }
|
||||
- { name: author, label: "Author", widget: string, default: "CTAO" }
|
||||
- { name: cover, label: "Cover image", widget: image, required: false }
|
||||
- { name: lang, label: "Language (if not English, e.g. pl)", widget: string, required: false }
|
||||
- { name: draft, label: "Draft (unpublished)", widget: boolean, default: false }
|
||||
- { name: body, label: "Body", widget: markdown }
|
||||
|
||||
|
||||
@@ -5,9 +5,17 @@
|
||||
dropped from selectors (the preview pane has no such wrapper).
|
||||
KEEP IN SYNC: when you touch `.prose` or the tokens in global.css, update
|
||||
the corresponding value here (a matching reminder sits next to `.prose`).
|
||||
Fonts: same families as the site (site inlines them; the preview loads them
|
||||
from Google Fonts — editors' browsers are online by definition). */
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400..700&family=Space+Grotesk:wght@500..700&display=swap");
|
||||
Fonts: same self-hosted woff2 files the site uses, same origin. (The
|
||||
Sveltia bundle still loads its own UI fonts from jsDelivr — only the
|
||||
PREVIEW pane is CDN-free.) */
|
||||
@font-face {
|
||||
font-family: "Inter"; font-weight: 400 700; font-display: swap;
|
||||
src: url("/fonts/inter-latin.woff2") format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Space Grotesk"; font-weight: 500 700; font-display: swap;
|
||||
src: url("/fonts/space-grotesk-latin.woff2") format("woff2");
|
||||
}
|
||||
|
||||
:root {
|
||||
/* tokens copied from global.css :root */
|
||||
@@ -22,6 +30,7 @@
|
||||
--fs-s: 0.9rem;
|
||||
--fs-l: 1.125rem;
|
||||
--fs-xl: 1.5rem;
|
||||
--fs-h1: clamp(2.25rem, 5vw, 3.5rem);
|
||||
--radius: 16px;
|
||||
}
|
||||
|
||||
@@ -42,7 +51,8 @@ a { color: var(--link); text-decoration: underline; text-underline-offset: 3px;
|
||||
|
||||
/* = global.css `.prose` rules, selectors unwrapped = */
|
||||
body { line-height: 1.65; overflow-wrap: break-word; }
|
||||
h1 { color: var(--galaxy); }
|
||||
a { overflow-wrap: anywhere; }
|
||||
h1 { color: var(--galaxy); font-size: var(--fs-h1); }
|
||||
h2 { color: var(--galaxy); margin: 44px 0 12px; font-size: var(--fs-xl); }
|
||||
h3 { color: var(--galaxy); margin: 30px 0 8px; font-size: var(--fs-l); }
|
||||
p { margin: 0 0 16px; text-wrap: pretty; }
|
||||
@@ -52,7 +62,7 @@ blockquote {
|
||||
margin: 22px 0; padding: 12px 20px; border-left: 3px solid var(--galaxy);
|
||||
background: var(--moon); color: var(--text);
|
||||
}
|
||||
img { max-width: 100%; height: auto; border: 1px solid var(--border); border-radius: var(--radius); }
|
||||
img { display: block; max-width: 100%; height: auto; border: 1px solid var(--border); border-radius: var(--radius); }
|
||||
img + em, p > em:only-child { color: var(--muted); font-size: var(--fs-s); }
|
||||
hr { border: 0; border-top: 1px solid var(--border); margin: 32px 0; }
|
||||
table { display: block; width: 100%; overflow-x: auto; border-collapse: collapse; margin: 0 0 16px; font-size: var(--fs-s); }
|
||||
|
||||
+29
-6
@@ -26,7 +26,15 @@
|
||||
let index = null;
|
||||
let timer;
|
||||
async function ensureIndex() {
|
||||
if (!index) index = await (await fetch('/search.json')).json();
|
||||
if (index) return index;
|
||||
try {
|
||||
index = await (await fetch('/search.json')).json();
|
||||
} catch {
|
||||
// Network hiccup: say so instead of failing silently; index stays
|
||||
// null so the next keystroke retries.
|
||||
status.textContent = 'Search is unavailable right now.';
|
||||
return null;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
// First title match wrapped in <mark> (Starlight search pattern) — built
|
||||
@@ -77,7 +85,9 @@
|
||||
frag.append(label('Recent'));
|
||||
for (const term of r) frag.append(row([term], '/search?q=' + encodeURIComponent(term), term));
|
||||
}
|
||||
const fresh = (await ensureIndex()).slice(0, Math.max(1, 5 - r.length));
|
||||
const idx = await ensureIndex();
|
||||
if (!idx) return;
|
||||
const fresh = idx.slice(0, Math.max(1, 5 - r.length));
|
||||
if (input.value.trim().length >= 2) return; // typed meanwhile — run() owns the list
|
||||
frag.append(label('Latest news'));
|
||||
for (const p of fresh) {
|
||||
@@ -95,6 +105,7 @@
|
||||
const q = input.value.trim().toLowerCase();
|
||||
if (q.length < 2) { list.replaceChildren(); idle(); return; }
|
||||
const idx = await ensureIndex();
|
||||
if (!idx) { list.replaceChildren(); return; }
|
||||
const hits = idx
|
||||
.filter((p) => (p.title + ' ' + p.description + ' ' + p.category).toLowerCase().includes(q))
|
||||
.slice(0, opts.limit);
|
||||
@@ -111,6 +122,17 @@
|
||||
li.append(small);
|
||||
frag.append(li);
|
||||
}
|
||||
// Dropdown only: an empty list would collapse (.suggest:empty) and the
|
||||
// panel would just vanish — show the no-results line sighted users too
|
||||
// (the status line above is sr-only in the header; /search shows a
|
||||
// visible notice instead).
|
||||
if (!hits.length && opts.dismiss) {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'suggest-hint';
|
||||
li.setAttribute('aria-hidden', 'true'); // the status line announces it
|
||||
li.textContent = 'No results for “' + input.value.trim() + '”';
|
||||
frag.append(li);
|
||||
}
|
||||
list.replaceChildren(frag);
|
||||
hint();
|
||||
}
|
||||
@@ -122,13 +144,14 @@
|
||||
const a = e.target.closest && e.target.closest('a');
|
||||
remember((a && a.dataset.q) || input.value);
|
||||
});
|
||||
// Dropdown dismiss (opt-in — /search keeps its results list). A suggestion
|
||||
// list is a NON-MODAL combobox popup (ARIA combobox pattern): interacting
|
||||
// outside the field+list closes it but the interaction is NOT swallowed —
|
||||
// Dropdown dismiss (opt-in — /search keeps its results list). The
|
||||
// suggestion list is a NON-MODAL popup (combobox-style; plain
|
||||
// Tab-reachable links, no roving arrow-key focus): interacting outside
|
||||
// the field+list closes it but the interaction is NOT swallowed —
|
||||
// unlike the modal header panels, which scrim-dismiss in Base.astro.
|
||||
if (opts.dismiss) {
|
||||
const box = input.closest('form') || input.parentElement;
|
||||
const hide = () => { clearTimeout(timer); list.innerHTML = ''; status.textContent = ''; };
|
||||
const hide = () => { clearTimeout(timer); list.replaceChildren(); status.textContent = ''; };
|
||||
document.addEventListener('pointerdown', (e) => { if (list.firstChild && !box.contains(e.target)) hide(); });
|
||||
// relatedTarget may be null mid-click on our own links — pointerdown covers that path
|
||||
box.addEventListener('focusout', (e) => { if (e.relatedTarget && !box.contains(e.relatedTarget)) hide(); });
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 357 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 177 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 829 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 122 KiB |
Vendored
+3418
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user