fix: mobile gutters, header search tiers, suggest wiring + empty-state, news pagination, hero CTAs, ghost button
- padding-block on .page/.page-head/.article: container gutter survived only >=1176px (shorthand padding zeroed it) — mobile content sat flush left - restore <=1024px tiers: field hidden, magnifier + menu visible (header no longer overflows at 375px) - re-wire hdr-q suggestions lost in the field restore; newsSearch inits null-safe - focused-empty search state: recent queries (localStorage, cap 4) + latest articles (Baymard autocomplete guidance) - suggest panel 480px (Baymard/DocSearch width convention) - /news page 1 = 25 items (featured + 24 = even 3-col rows), later pages 24, custom slicing - hero CTAs: primary Browse news (/news, spec-first real service), ghost Explore the data (external); mock Proposals out of hero - .btn.btn--ghost compound selector — .btn base defined later was filling ghosts with Cherenkov Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jWfn3RwPfFGTtBddm36Uy
This commit is contained in:
@@ -108,8 +108,9 @@ const links = [
|
||||
</header>
|
||||
<script is:inline src="/search-client.js"></script>
|
||||
<script is:inline>
|
||||
// Header search suggestions (icon-reveal row); the form is a plain
|
||||
// GET /search, so everything below is progressive enhancement.
|
||||
// Header search suggestions (desktop field + mobile reveal); the forms
|
||||
// are plain GET /search, so everything below is progressive enhancement.
|
||||
newsSearch({ input: 'hdr-q', list: 'hdr-suggest', status: 'hdr-suggest-status', limit: 6, unit: 'suggestion' });
|
||||
newsSearch({ input: 'pop-q', list: 'pop-suggest', status: 'pop-suggest-status', limit: 6, unit: 'suggestion' });
|
||||
var pop = document.querySelector('.search-pop');
|
||||
pop.addEventListener('toggle', function () {
|
||||
|
||||
@@ -44,11 +44,14 @@ const services = [
|
||||
<div class="eyebrow">Cherenkov Telescope Array Observatory</div>
|
||||
<h1>CTAO Science Portal</h1>
|
||||
<p>Exploring the Universe at the Highest Energies</p>
|
||||
{/* Search lives in the header (one search location); the hero carries the
|
||||
two core scientist tasks per SPEC §3.1 instead — classic hero-CTA pair. */}
|
||||
{/* Hero actions = the two REAL things a logged-out visitor can do today:
|
||||
news (SPEC §3.1 lists general information/announcements FIRST; §3.3.1
|
||||
"displays the main information"; calls for proposals arrive VIA news)
|
||||
and the external Data Explorer. Proposals stays in nav + tiles — a
|
||||
mock doesn't earn hero billing. Search lives in the header. */}
|
||||
<div class="hero-cta">
|
||||
<a class="btn external" href="https://padc-ctao-data-explorer.obspm.fr/" target="_blank" rel="noopener">Explore the data</a>
|
||||
<a class="btn btn--ghost" href="/proposals">Submit a proposal</a>
|
||||
<a class="btn" href="/news">Browse news & announcements</a>
|
||||
<a class="btn btn--ghost external" href="https://padc-ctao-data-explorer.obspm.fr/" target="_blank" rel="noopener">Explore the data</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -2,13 +2,34 @@
|
||||
import { getCollection } from 'astro:content';
|
||||
import Base from '../../layouts/Base.astro';
|
||||
|
||||
// Paginated archive via Astro's built-in paginate(): the [...page] rest route
|
||||
// makes page 1 the bare /news URL, then /news/2 … /news/N.
|
||||
export async function getStaticPaths({ paginate }) {
|
||||
// Paginated archive; the [...page] rest route makes page 1 the bare /news URL,
|
||||
// then /news/2 … /news/N. Custom slicing instead of paginate(): page 1 holds
|
||||
// 25 items (1 featured lead + 24 grid = even 3-column rows), later pages 24 —
|
||||
// paginate() cannot vary page size.
|
||||
export async function getStaticPaths() {
|
||||
const posts = (await getCollection('news', ({ data }) => !data.draft)).sort(
|
||||
(a, b) => b.data.date.valueOf() - a.data.date.valueOf(),
|
||||
);
|
||||
return paginate(posts, { pageSize: 24 });
|
||||
const FIRST = 25, REST = 24;
|
||||
const chunks = [posts.slice(0, FIRST)];
|
||||
for (let i = FIRST; i < posts.length; i += REST) chunks.push(posts.slice(i, i + REST));
|
||||
const lastPage = chunks.length;
|
||||
const hrefOf = (n) => (n === 1 ? '/news' : `/news/${n}`);
|
||||
return chunks.map((data, i) => {
|
||||
const n = i + 1;
|
||||
return {
|
||||
params: { page: n === 1 ? undefined : String(n) },
|
||||
props: { page: {
|
||||
data,
|
||||
currentPage: n,
|
||||
lastPage,
|
||||
url: {
|
||||
prev: n > 1 ? hrefOf(n - 1) : undefined,
|
||||
next: n < lastPage ? hrefOf(n + 1) : undefined,
|
||||
},
|
||||
} },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const { page } = Astro.props;
|
||||
|
||||
+20
-7
@@ -221,7 +221,9 @@ img { max-width: 100%; height: auto; display: block; }
|
||||
}
|
||||
.nav-search input::placeholder { color: rgba(255, 255, 255, 0.65); } /* 6.9:1 */
|
||||
.nav-search input:focus { border-color: #fff; }
|
||||
.nav-search .suggest { left: auto; right: 0; width: min(380px, 92vw); top: calc(100% + 10px); }
|
||||
/* Autocomplete panel: never narrower than the field, wide enough for a
|
||||
typical title in ≤2 lines (Baymard autocomplete; DocSearch ~500px) */
|
||||
.nav-search .suggest { left: auto; right: 0; width: min(480px, calc(100vw - 2 * var(--gutter))); top: calc(100% + 10px); }
|
||||
/* ≤1024px replacement: a 44px magnifier <details> reveals a search row under
|
||||
the header (NN/g mobile-search pattern) — see the menu media block. */
|
||||
.search-pop { display: none; }
|
||||
@@ -248,6 +250,8 @@ img { max-width: 100%; height: auto; display: block; }
|
||||
}
|
||||
@media (max-width: 1024px) {
|
||||
.nav > nav { display: none; }
|
||||
.nav-search { display: none; } /* field is desktop-only (NN/g tiers) */
|
||||
.search-pop { display: block; } /* magnifier reveal takes over ≤1024px */
|
||||
.menu { display: block; }
|
||||
.menu summary {
|
||||
display: inline-flex; align-items: center; gap: 8px; min-height: 44px;
|
||||
@@ -309,15 +313,17 @@ img { max-width: 100%; height: auto; display: block; }
|
||||
one quiet ghost. Links styled as buttons because they navigate. */
|
||||
.hero-cta { display: flex; flex-wrap: wrap; gap: var(--space-s); margin-top: var(--space-l); }
|
||||
a.btn { display: inline-flex; align-items: center; gap: 6px; text-decoration: none; }
|
||||
.btn--ghost { background: transparent; color: #fff; border: 1px solid rgba(255, 255, 255, 0.55); }
|
||||
.btn--ghost:hover { background: rgba(255, 255, 255, 0.12); color: #fff; border-color: #fff; }
|
||||
/* Compound selector: outranks the later-defined .btn base (source-order tie
|
||||
would otherwise fill the ghost with Cherenkov) */
|
||||
.btn.btn--ghost { background: transparent; color: #fff; border: 1px solid rgba(255, 255, 255, 0.55); }
|
||||
.btn.btn--ghost:hover { background: rgba(255, 255, 255, 0.12); color: #fff; border-color: #fff; }
|
||||
|
||||
/* Section-landing head (news/search/proposals/dashboard/support) — white page,
|
||||
display-scale Galaxy h1 + standfirst; the navy band is reserved for / and /login. */
|
||||
.page-head { padding: var(--space-band) 0 var(--space-l); }
|
||||
.page-head { padding-block: var(--space-band) var(--space-l); }
|
||||
.page-head h1 { color: var(--galaxy); font-size: var(--fs-h1); margin: 0 0 10px; }
|
||||
.page-head .standfirst { margin: 0; max-width: 62ch; }
|
||||
.page-head + .page { padding-top: 0; } /* white-on-white: head already spaces */
|
||||
.page-head + .page { padding-block-start: 0; } /* white-on-white: head already spaces */
|
||||
|
||||
/* Search field (/search page) — large pill input with inline magnifier; real GET form */
|
||||
.search-form { position: relative; display: flex; gap: 8px; max-width: 560px; }
|
||||
@@ -340,6 +346,13 @@ a.btn { display: inline-flex; align-items: center; gap: 6px; text-decoration: no
|
||||
.suggest:empty { display: none; }
|
||||
.suggest li { display: flex; align-items: flex-start; gap: var(--space-s); padding: 0 12px; border-radius: calc(var(--radius) - 8px); }
|
||||
.suggest li:hover, .suggest li:focus-within { background: var(--moon); }
|
||||
/* Focused-empty state labels ("Recent" / "Latest news") — quiet, non-interactive */
|
||||
li.suggest-label {
|
||||
color: var(--muted); font-size: var(--fs-xs); font-weight: 600;
|
||||
text-transform: uppercase; letter-spacing: 0.08em; padding-top: 8px;
|
||||
}
|
||||
.suggest li.suggest-label:hover { background: none; }
|
||||
.search-results li.suggest-label { background: none; border: 0; box-shadow: none; padding: 8px 0 0; }
|
||||
/* The link holds <mark>-split text nodes — it must lay out as flowing text,
|
||||
never as a flex row (flex would turn every fragment into its own item and
|
||||
stop titles from wrapping). ≥44px target comes from the block padding. */
|
||||
@@ -441,7 +454,7 @@ span.page-step { color: var(--muted); } /* disabled end stop — non-interactive
|
||||
|
||||
/* Article — 70ch column; on ≥1200px a sticky "On this page" rail sits to the right */
|
||||
.article-layout { display: grid; grid-template-columns: minmax(0, 70ch); }
|
||||
.article { padding: var(--space-l) 0 var(--space-xl); max-width: 70ch; }
|
||||
.article { padding-block: var(--space-l) var(--space-xl); max-width: 70ch; }
|
||||
.article .back { color: var(--muted); font-size: var(--fs-s); }
|
||||
.article h1 { color: var(--galaxy); font-size: var(--fs-h1); margin: 14px 0 10px; }
|
||||
.article .meta {
|
||||
@@ -541,7 +554,7 @@ span.page-step { color: var(--muted); } /* disabled end stop — non-interactive
|
||||
.post-nav a:hover .post-nav-title { color: var(--link); }
|
||||
|
||||
/* Generic page section — one rhythm token for all pages */
|
||||
.page { padding: var(--space-xl) 0; }
|
||||
.page { padding-block: var(--space-xl); }
|
||||
.panel {
|
||||
background: #fff; border: 1px solid var(--border); border-radius: var(--radius);
|
||||
padding: var(--space-m); box-shadow: var(--shadow-ambient);
|
||||
|
||||
Reference in New Issue
Block a user