]*class="cat-tool[^"]*"[^>]*>\s*([\s\S]*?)<\/h3>\s*
([\s\S]*?)<\/p>/g
+ /]*class="cat-tool[^"]*"[^>]*>\s*([\s\S]*?)<\/h3>\s*
([\s\S]*?)<\/p>/g,
)) {
put(m[1], m[2], "cat-tool", "cat.mdx");
catParsed++;
@@ -93,7 +93,7 @@ expectCount(
"cat-tool",
cat,
/]*class="[^"]*\bcat-tool\b[^"]*"[^>]*>/g,
- catParsed
+ catParsed,
);
// --- B.A.D.: terms, actors, campaigns, families, featured animations -----
@@ -102,7 +102,7 @@ const bad = readFileSync(paths.bad, "utf8");
// feature missing its heading would silently borrow the next card's.
let featParsed = 0;
for (const m of bad.matchAll(
- /(?:(?!<\/div>)[\s\S])*?
([\s\S]*?)<\/h3>\s*
([\s\S]*?)<\/p>/g
+ /
(?:(?!<\/div>)[\s\S])*?
([\s\S]*?)<\/h3>\s*
([\s\S]*?)<\/p>/g,
)) {
put(m[1], m[2], "bad-feature", "bad.mdx");
featParsed++;
@@ -112,7 +112,7 @@ expectCount(
"bad-feature",
bad,
/
]*class="[^"]*\bbad-feature__text\b[^"]*"[^>]*>/g,
- featParsed
+ featParsed,
);
const badKinds = [
["bad-term", "bad-term"],
@@ -123,7 +123,7 @@ const badKinds = [
for (const [cls, source] of badKinds) {
const re = new RegExp(
`
]*class="${cls}[^"]*"[^>]*>\\s*([\\s\\S]*?)<\\/h3>\\s*
([\\s\\S]*?)<\\/p>`,
- "g"
+ "g",
);
let parsed = 0;
for (const m of bad.matchAll(re)) {
@@ -135,14 +135,16 @@ for (const [cls, source] of badKinds) {
cls,
bad,
new RegExp(`]*class="[^"]*\\b${cls}\\b[^"]*"[^>]*>`, "g"),
- parsed
+ parsed,
);
}
// --- C.L.E.A.R.: definition lists (wins on collisions) --------------------
const clear = readFileSync(paths.clear, "utf8");
let clearParsed = 0;
-for (const m of clear.matchAll(/([\s\S]*?)<\/dt>\s*([\s\S]*?)<\/dd>/g)) {
+for (const m of clear.matchAll(
+ /([\s\S]*?)<\/dt>\s*([\s\S]*?)<\/dd>/g,
+)) {
put(m[1], m[2], "clear", "clear.mdx");
clearParsed++;
}
@@ -150,13 +152,13 @@ const dtCount = countMatches(clear, /]/g);
const ddCount = countMatches(clear, /]/g);
if (dtCount !== ddCount) {
problems.push(
- `clear.mdx: ${dtCount} tag(s) but ${ddCount} tag(s); every term needs exactly one definition`
+ `clear.mdx: ${dtCount} tag(s) but ${ddCount} tag(s); every term needs exactly one definition`,
);
}
if (clearParsed !== dtCount) {
problems.push(
`clear.mdx: ${dtCount} tag(s) but only ${clearParsed} dt/dd pair(s) parsed; ` +
- "a term is missing its definition or has stray markup between and "
+ "a term is missing its definition or has stray markup between and ",
);
}
@@ -166,53 +168,59 @@ if (clearParsed !== dtCount) {
// for digit-initial terms (2FA, 802.1X), which no A-Z letter can file.
const letterSections = [
...clear.matchAll(
- /]*\bid="([A-Z]|0-9)")(?=[^>]*\bclass="[^"]*\bclear-section\b[^"]*")[^>]*>([\s\S]*?)<\/section>/g
+ /]*\bid="([A-Z]|0-9)")(?=[^>]*\bclass="[^"]*\bclear-section\b[^"]*")[^>]*>([\s\S]*?)<\/section>/g,
),
];
const sectionLetters = letterSections.map((m) => m[1]);
for (let i = 1; i < sectionLetters.length; i++) {
if (sectionLetters[i] <= sectionLetters[i - 1]) {
problems.push(
- `clear.mdx: letter section "${sectionLetters[i]}" is duplicated or out of order (0-9 first, then A-Z)`
+ `clear.mdx: letter section "${sectionLetters[i]}" is duplicated or out of order (0-9 first, then A-Z)`,
);
}
}
const navLetters = [
...clear.matchAll(
- /]*\bhref="#([A-Z]|0-9)")(?=[^>]*\bclass="[^"]*\bclear-nav__letter\b[^"]*")[^>]*>/g
+ /]*\bhref="#([A-Z]|0-9)")(?=[^>]*\bclass="[^"]*\bclear-nav__letter\b[^"]*")[^>]*>/g,
),
].map((m) => m[1]);
if (navLetters.join("") !== sectionLetters.join("")) {
problems.push(
- `clear.mdx: letter nav [${navLetters.join("")}] does not match the letter sections [${sectionLetters.join("")}]`
+ `clear.mdx: letter nav [${navLetters.join("")}] does not match the letter sections [${sectionLetters.join("")}]`,
);
}
// Mirrors normalizeKey() in glossary.ts so "Pass-the-Hash" sorts as words.
-const sortKey = (t) => t.toLowerCase().replace(/-/g, " ").replace(/\s+/g, " ").trim();
+const sortKey = (t) =>
+ t.toLowerCase().replace(/-/g, " ").replace(/\s+/g, " ").trim();
let sectionedTerms = 0;
for (const [, letter, body] of letterSections) {
let prev = null;
for (const dt of body.matchAll(/([\s\S]*?)<\/dt>/g)) {
sectionedTerms++;
const term = clean(dt[1]);
- const initial = term.replace(/^[^0-9A-Za-z]+/, "").charAt(0).toUpperCase();
+ const initial = term
+ .replace(/^[^0-9A-Za-z]+/, "")
+ .charAt(0)
+ .toUpperCase();
const home = /[0-9]/.test(initial) ? "0-9" : initial;
if (home !== letter) {
problems.push(
- `clear.mdx: "${term}" is filed under ${letter} but belongs under ${home || '"?"'}`
+ `clear.mdx: "${term}" is filed under ${letter} but belongs under ${home || '"?"'}`,
);
}
const key = sortKey(term);
if (prev && key < prev.key) {
problems.push(
- `clear.mdx: section ${letter} is out of alphabetical order: "${term}" follows "${prev.term}"`
+ `clear.mdx: section ${letter} is out of alphabetical order: "${term}" follows "${prev.term}"`,
);
}
prev = { key, term };
}
}
if (sectionedTerms !== dtCount) {
- problems.push(`clear.mdx: ${dtCount - sectionedTerms} term(s) sit outside the letter sections`);
+ problems.push(
+ `clear.mdx: ${dtCount - sectionedTerms} term(s) sit outside the letter sections`,
+ );
}
// --- Duplicate terms --------------------------------------------------------
@@ -233,10 +241,15 @@ for (const [file, fileTerms] of perFileTerms) {
for (const { spellings, sources } of byKey.values()) {
if (sources.length < 2) continue;
const cards = sources.filter((s) => s !== "bad-feature");
- if (file === "bad.mdx" && sources.length === 2 && cards.length === 1 && spellings.length === 1)
+ if (
+ file === "bad.mdx" &&
+ sources.length === 2 &&
+ cards.length === 1 &&
+ spellings.length === 1
+ )
continue;
problems.push(
- `${file}: "${spellings.join('" / "')}" is defined ${sources.length} times (${sources.join(", ")})`
+ `${file}: "${spellings.join('" / "')}" is defined ${sources.length} times (${sources.join(", ")})`,
);
}
}
@@ -261,7 +274,11 @@ for (const bySpelling of spellingsByKey.values()) {
// --- Anchor ids: unique per page -------------------------------------------
// Duplicate ids break the letter/section anchors and any aria references.
-for (const [file, src] of [["cat.mdx", cat], ["bad.mdx", bad], ["clear.mdx", clear]]) {
+for (const [file, src] of [
+ ["cat.mdx", cat],
+ ["bad.mdx", bad],
+ ["clear.mdx", clear],
+]) {
const idCounts = new Map();
for (const m of src.matchAll(/\bid="([^"]+)"/g)) {
idCounts.set(m[1], (idCounts.get(m[1]) ?? 0) + 1);
@@ -272,7 +289,9 @@ for (const [file, src] of [["cat.mdx", cat], ["bad.mdx", bad], ["clear.mdx", cle
}
if (problems.length > 0) {
- console.error(`extract-glossary: ${problems.length} structural problem(s) in the glossary MDX:`);
+ console.error(
+ `extract-glossary: ${problems.length} structural problem(s) in the glossary MDX:`,
+ );
for (const p of problems) console.error(` - ${p}`);
process.exit(1);
}
@@ -283,18 +302,21 @@ const body = sorted
.map((k) => {
const e = entries.get(k);
return ` ${JSON.stringify(k)}: {\n "term": ${JSON.stringify(
- e.term
+ e.term,
)},\n "definition": ${JSON.stringify(
- e.definition
+ e.definition,
)},\n "source": ${JSON.stringify(e.source)}\n }`;
})
.join(",\n");
const record = `export const glossary: Record = {\n${body}\n};`;
const current = readFileSync(paths.out, "utf8");
-const marker = /export const glossary: Record = \{[\s\S]*?\n\};/;
+const marker =
+ /export const glossary: Record = \{[\s\S]*?\n\};/;
if (!marker.test(current)) {
- console.error("extract-glossary: could not find the glossary record in glossary.ts");
+ console.error(
+ "extract-glossary: could not find the glossary record in glossary.ts",
+ );
process.exit(2);
}
const next = current.replace(marker, record);
@@ -303,12 +325,14 @@ if (process.argv.includes("--check")) {
if (next !== current) {
console.error(
"extract-glossary: src/data/glossary.ts is out of sync with the glossary MDX files.\n" +
- "Run `node scripts/extract-glossary.mjs` and commit the result."
+ "Run `node scripts/extract-glossary.mjs` and commit the result.",
);
process.exit(1);
}
console.log(`extract-glossary: in sync (${sorted.length} entries).`);
} else {
writeFileSync(paths.out, next);
- console.log(`extract-glossary: wrote ${sorted.length} entries to src/data/glossary.ts`);
+ console.log(
+ `extract-glossary: wrote ${sorted.length} entries to src/data/glossary.ts`,
+ );
}
diff --git a/src/components/Header.astro b/src/components/Header.astro
index 4a638f1..d84dbb9 100644
--- a/src/components/Header.astro
+++ b/src/components/Header.astro
@@ -171,27 +171,17 @@ const isActiveSection = (item: NavItem): boolean =>
@@ -244,10 +234,12 @@ const isActiveSection = (item: NavItem): boolean =>
}
/* Eleven top-level items (the five guide pages fold into the Guides
- dropdown) fit only from ~1350px up, even with compact spacing; below
- that the hamburger menu takes over (the nav used to paint over the
- search and GitHub controls when it was shown any narrower). */
- @media (min-width: 1350px) {
+ dropdown) need about 1200px of content width even at the tightest
+ spacing with an icon-only search, so the desktop nav starts at 1240px
+ (laptops keep it; Windows scrollbars eat ~17px of the media-query
+ width). Below that the hamburger menu takes over, since the nav used
+ to paint over the search and GitHub controls when shown any narrower. */
+ @media (min-width: 1240px) {
.header__nav { display: flex; }
}
@@ -266,8 +258,17 @@ const isActiveSection = (item: NavItem): boolean =>
white-space: nowrap;
}
+ /* Tightest tier, for laptop widths: minimal spacing and a slightly
+ smaller label size; SiteSearch drops to icon-only over the same range. */
+ @media (min-width: 1240px) and (max-width: 1449px) {
+ .header__inner { padding: 0 1.5rem; gap: 1rem; }
+ .header__left { gap: 0.85rem; }
+ .header__link { padding: 0.45rem 0.3rem; font-size: 0.86rem; }
+ .header__nav { gap: 0.1rem; }
+ }
+
/* Compact spacing until the viewport can hold the roomier layout. */
- @media (min-width: 1350px) and (max-width: 1549px) {
+ @media (min-width: 1450px) and (max-width: 1549px) {
.header__left { gap: 1.25rem; }
.header__link { padding: 0.45rem 0.35rem; }
.header__nav { gap: 0.15rem; }
@@ -468,7 +469,7 @@ const isActiveSection = (item: NavItem): boolean =>
cursor: pointer;
}
- @media (min-width: 1350px) {
+ @media (min-width: 1240px) {
.header__menu-btn { display: none; }
}
@@ -479,7 +480,7 @@ const isActiveSection = (item: NavItem): boolean =>
/* 44px touch-target floor for the header's icon controls on touch devices
and narrow screens (WCAG 2.5.5; the audit measured ~32px). */
- @media (max-width: 1349px), (pointer: coarse) {
+ @media (max-width: 1239px), (pointer: coarse) {
.header__icon,
.header__menu-btn {
min-width: 2.75rem;
@@ -572,11 +573,6 @@ const isActiveSection = (item: NavItem): boolean =>
color: var(--assured-accent-soft);
background: rgba(20, 184, 166, 0.12);
}
-
- .header__mobile-sublink--sub {
- margin-left: 1rem;
- font-size: 0.82rem;
- }