From 2f8c8e8f74527d3e52253f0239ed58d9b29283b3 Mon Sep 17 00:00:00 2001 From: Dinesh <13635627+HumbleBee14@users.noreply.github.com> Date: Tue, 14 Jul 2026 06:55:20 -0700 Subject: [PATCH 1/6] Site-wide quality pass: performance, SEO, a11y, and refactors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Performance - Replace React SearchModal/AnalyticsConsent islands with vanilla Astro components — drops react-dom (~56 KB gzip) from every static page - inlineStylesheets: 'auto' — shared CSS ships once as a cacheable file instead of being inlined into all 100+ pages - Load KaTeX CSS only on posts that contain math - Preload the three above-the-fold woff2 fonts - Add _headers: immutable caching for hashed assets, cache policy for /og/* and /_pagefind/*, baseline security headers SEO - rss.xml: drop trailing slash from item links (match canonicals) - Remove duplicate Organization/WebSite JSON-LD on the homepage; enrich the site-wide copy with discord sameAs + publisher - Add twitter:image:alt; tighten homepage title under 60 chars - Cover images get real alt text + dimensions (CLS fix) - Remove non-standard Host: line from robots.txt Accessibility - Lightbox: keyboard-openable images, dialog semantics, focus restore - Theme toggle: aria-pressed state + prefers-color-scheme fallback - Nav links: aria-current; author page heading hierarchy fixed Architecture - New PostRow.astro replaces blog-row markup duplicated across 5 pages - New lib/posts.ts (resolvePostAuthors, authorNames) replaces the author-resolution boilerplate repeated in 7 files - lib/prefs.ts now actually used by Nav + homepage accent scripts - lib/pagefind.ts shared loader (was duplicated in two components) - WritePortal: dialogs + doc utils extracted, casts centralized - create-pr.ts: fileExistsOnMain() helper replaces duplicated checks - formatDate/formatMonth accept Date directly - Comment cleanup: remove narration/decorative comments, keep WHY notes - [hidden] display reset so attribute-hidden UI always stays hidden --- astro.config.mjs | 4 +- functions/api/create-pr.ts | 67 ++-- public/_headers | 17 ++ public/robots.txt | 1 - src/components/AnalyticsConsent.astro | 61 ++++ src/components/AnalyticsConsent.tsx | 69 ----- src/components/BlogFilter.astro | 1 - src/components/IconLinkCard.astro | 1 - src/components/Nav.astro | 25 +- src/components/PostRow.astro | 39 +++ src/components/SearchInline.tsx | 23 +- src/components/SearchModal.astro | 326 ++++++++++++++++++++ src/components/SearchModal.tsx | 354 ---------------------- src/components/Stat.astro | 1 - src/components/Step.astro | 1 - src/env.d.ts | 6 + src/layouts/BaseLayout.astro | 24 +- src/lib/data.ts | 8 +- src/lib/pagefind.ts | 22 ++ src/lib/posts.ts | 17 ++ src/lib/site.ts | 1 - src/lib/socialIcons.ts | 2 - src/pages/authors/[handle]/articles.astro | 26 +- src/pages/authors/[handle]/index.astro | 33 +- src/pages/blog/[slug].astro | 58 ++-- src/pages/blog/index.astro | 30 +- src/pages/index.astro | 58 +--- src/pages/rss.xml.ts | 17 +- src/pages/tags/[tag].astro | 28 +- src/pages/topics/[id].astro | 25 +- src/styles/global.css | 8 +- src/write/WritePortal.tsx | 216 +++---------- src/write/dialogs/OpenExistingDialog.tsx | 65 ++++ src/write/dialogs/PublishDialog.tsx | 80 +++++ src/write/editor/docUtils.ts | 48 +++ 35 files changed, 886 insertions(+), 876 deletions(-) create mode 100644 public/_headers create mode 100644 src/components/AnalyticsConsent.astro delete mode 100644 src/components/AnalyticsConsent.tsx create mode 100644 src/components/PostRow.astro create mode 100644 src/components/SearchModal.astro delete mode 100644 src/components/SearchModal.tsx create mode 100644 src/lib/pagefind.ts create mode 100644 src/lib/posts.ts create mode 100644 src/write/dialogs/OpenExistingDialog.tsx create mode 100644 src/write/dialogs/PublishDialog.tsx create mode 100644 src/write/editor/docUtils.ts diff --git a/astro.config.mjs b/astro.config.mjs index 3a1afa2..6a969c0 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -163,7 +163,9 @@ export default defineConfig({ output: 'static', build: { - inlineStylesheets: 'always', + // 'auto' inlines only small styles; the shared design-system sheet is emitted + // as one cacheable /_astro/*.css instead of being duplicated into every page. + inlineStylesheets: 'auto', // Flat files (blog/x.html) instead of blog/x/index.html, so URLs stay clean // with no trailing slash (pairs with trailingSlash: 'never'). Avoids // Cloudflare's directory-style 308 redirect that appended the slash. diff --git a/functions/api/create-pr.ts b/functions/api/create-pr.ts index 0768516..ae0cdb5 100644 --- a/functions/api/create-pr.ts +++ b/functions/api/create-pr.ts @@ -80,6 +80,26 @@ async function gh( return res.status === 204 ? {} : ((await res.json()) as Record); } +async function fileExistsOnMain( + owner: string, + name: string, + path: string, + token: string, +): Promise { + const res = await fetch( + `https://api.github.com/repos/${owner}/${name}/contents/${encodeURI(path)}?ref=main`, + { + headers: { + accept: 'application/vnd.github+json', + authorization: `Bearer ${token}`, + 'user-agent': UA, + 'x-github-api-version': '2022-11-28', + }, + }, + ); + return res.ok; +} + export async function onRequestPost(context: { request: Request; env: Env }): Promise { const { request, env } = context; @@ -121,48 +141,23 @@ export async function onRequestPost(context: { request: Request; env: Env }): Pr // A brand-new post must not silently overwrite an existing one at the same slug. // Edits (loaded via the portal's "Open existing post") are meant to, so skip then. - if (!isEdit) { - const existing = await fetch( - `https://api.github.com/repos/${owner}/${name}/contents/${encodeURI( - `src/content/posts/${slug}/index.mdx`, - )}?ref=main`, - { - headers: { - accept: 'application/vnd.github+json', - authorization: `Bearer ${token}`, - 'user-agent': UA, - 'x-github-api-version': '2022-11-28', - }, - }, + if ( + !isEdit && + (await fileExistsOnMain(owner, name, `src/content/posts/${slug}/index.mdx`, token)) + ) { + return json( + { error: 'A post with this URL already exists. Change the URL slug and try again.' }, + 409, ); - if (existing.ok) { - return json( - { error: 'A post with this URL already exists. Change the URL slug and try again.' }, - 409, - ); - } } // A newly registered author must not overwrite an existing profile at the same handle. const authorFile = files.find((f) => f.path.startsWith('src/content/authors/')); - if (authorFile) { - const existing = await fetch( - `https://api.github.com/repos/${owner}/${name}/contents/${encodeURI(authorFile.path)}?ref=main`, - { - headers: { - accept: 'application/vnd.github+json', - authorization: `Bearer ${token}`, - 'user-agent': UA, - 'x-github-api-version': '2022-11-28', - }, - }, + if (authorFile && (await fileExistsOnMain(owner, name, authorFile.path, token))) { + return json( + { error: 'That author handle is already taken. Pick another handle and try again.' }, + 409, ); - if (existing.ok) { - return json( - { error: 'That author handle is already taken. Pick another handle and try again.' }, - 409, - ); - } } const ref = (await gh(`/repos/${owner}/${name}/git/ref/heads/main`, token)) as { diff --git a/public/_headers b/public/_headers new file mode 100644 index 0000000..ec53d96 --- /dev/null +++ b/public/_headers @@ -0,0 +1,17 @@ +/* + X-Content-Type-Options: nosniff + Referrer-Policy: strict-origin-when-cross-origin + X-Frame-Options: SAMEORIGIN + Permissions-Policy: camera=(), microphone=(), geolocation=() + +/_astro/* + Cache-Control: public, max-age=31536000, immutable + +/_pagefind/* + Cache-Control: public, max-age=3600 + +/og/* + Cache-Control: public, max-age=86400 + +/og-default.png + Cache-Control: public, max-age=86400 diff --git a/public/robots.txt b/public/robots.txt index 7f181a8..73a473e 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -3,4 +3,3 @@ Allow: / Disallow: /api/ Sitemap: https://mlsystems.dev/sitemap-index.xml -Host: https://mlsystems.dev diff --git a/src/components/AnalyticsConsent.astro b/src/components/AnalyticsConsent.astro new file mode 100644 index 0000000..708adc4 --- /dev/null +++ b/src/components/AnalyticsConsent.astro @@ -0,0 +1,61 @@ +--- +interface Props { + measurementId?: string; +} +const { measurementId } = Astro.props; +--- + +{ + measurementId && ( + + ) +} + + diff --git a/src/components/AnalyticsConsent.tsx b/src/components/AnalyticsConsent.tsx deleted file mode 100644 index 7700b2a..0000000 --- a/src/components/AnalyticsConsent.tsx +++ /dev/null @@ -1,69 +0,0 @@ -'use client'; - -import { useEffect, useState } from 'react'; - -declare global { - interface Window { - dataLayer?: unknown[]; - gtag?: (...args: unknown[]) => void; - } -} - -const STORAGE_KEY = 'mlsystems-analytics-consent'; - -type Consent = 'accepted' | 'declined'; - -function updateConsent(granted: boolean) { - if (typeof window === 'undefined' || !window.gtag) return; - window.gtag('consent', 'update', { - analytics_storage: granted ? 'granted' : 'denied', - }); -} - -export default function AnalyticsConsent({ measurementId }: { measurementId?: string }) { - const [consent, setConsent] = useState(null); - const [mounted, setMounted] = useState(false); - - useEffect(() => { - try { - const stored = localStorage.getItem(STORAGE_KEY) as Consent | null; - setConsent(stored === 'accepted' || stored === 'declined' ? stored : null); - } catch {} - setMounted(true); - }, []); - - function choose(next: Consent) { - setConsent(next); - try { - localStorage.setItem(STORAGE_KEY, next); - } catch {} - updateConsent(next === 'accepted'); - } - - if (!mounted || !measurementId || consent) return null; - - return ( - - ); -} diff --git a/src/components/BlogFilter.astro b/src/components/BlogFilter.astro index 5d4a3e7..51ed049 100644 --- a/src/components/BlogFilter.astro +++ b/src/components/BlogFilter.astro @@ -46,7 +46,6 @@ const { activeTopic, allCount, topicCounts } = Astro.props; const topic = link.dataset.topic!; const newUrl = topic === 'all' ? '/blog' : `/blog?topic=${topic}`; history.pushState(null, '', newUrl); - // Update active chip document .querySelectorAll('[data-blog-filter] a[data-topic]') .forEach((a) => { diff --git a/src/components/IconLinkCard.astro b/src/components/IconLinkCard.astro index 02cf3f8..8f591b5 100644 --- a/src/components/IconLinkCard.astro +++ b/src/components/IconLinkCard.astro @@ -1,5 +1,4 @@ --- -// Icon + label + action link card. Shared by About and Contact. // Surface + hover-lift come from the global .card / .card--interactive classes. interface Props { href?: string | null; diff --git a/src/components/Nav.astro b/src/components/Nav.astro index 75a6c8e..2831f9b 100644 --- a/src/components/Nav.astro +++ b/src/components/Nav.astro @@ -26,7 +26,11 @@ const isActive = (path: string) => currentPath === path || currentPath.startsWit + + diff --git a/src/components/SearchModal.tsx b/src/components/SearchModal.tsx deleted file mode 100644 index 9f4b737..0000000 --- a/src/components/SearchModal.tsx +++ /dev/null @@ -1,354 +0,0 @@ -'use client'; - -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { TOPICS } from '@/lib/data'; - -type PagefindResultData = { - url: string; - excerpt: string; - meta: { title?: string; topic?: string; date?: string; read?: string; authors?: string }; - sub_results?: { title: string; url: string; excerpt: string }[]; -}; - -type PagefindResult = { id: string; data: () => Promise }; - -type Pagefind = { - debouncedSearch: (q: string) => Promise<{ results: PagefindResult[] } | null>; - options: (o: Record) => Promise; -}; - -declare global { - interface Window { - __mlsPagefind?: Pagefind; - } -} - -async function loadPagefind(): Promise { - if (window.__mlsPagefind) return window.__mlsPagefind; - const url = `${window.location.origin}/_pagefind/pagefind.js`; - const mod = (await import(/* @vite-ignore */ url)) as Pagefind; - await mod.options({ excerptLength: 24 }); - window.__mlsPagefind = mod; - return mod; -} - -type Group = 'topic' | 'article' | 'tool' | 'author' | 'page'; - -type RowItem = { - group: Group; - url: string; - title: string; - excerpt?: string; - meta?: string; -}; - -function classifyUrl(url: string): Group { - if (url.startsWith('/blog/')) return 'article'; - if (url.startsWith('/playground/')) return 'tool'; - if (url.startsWith('/authors/')) return 'author'; - return 'page'; -} - -const GROUP_LABEL: Record = { - topic: 'Topics', - article: 'Articles', - tool: 'Tools', - author: 'Authors', - page: 'Pages', -}; - -export default function SearchModal() { - const [open, setOpen] = useState(false); - const [query, setQuery] = useState(''); - const [pageResults, setPageResults] = useState([]); - const [loading, setLoading] = useState(false); - const [activeIdx, setActiveIdx] = useState(0); - const inputRef = useRef(null); - const listRef = useRef(null); - - const close = useCallback(() => { - setOpen(false); - setQuery(''); - setPageResults([]); - setActiveIdx(0); - }, []); - - const topicMatches = useMemo(() => { - const q = query.trim().toLowerCase(); - if (!q) return []; - return TOPICS.filter( - (t) => t.name.toLowerCase().includes(q) || t.desc.toLowerCase().includes(q), - ) - .slice(0, 3) - .map((t) => ({ - group: 'topic' as const, - url: `/blog?topic=${t.id}`, - title: t.name, - excerpt: t.desc, - })); - }, [query]); - - const rows = useMemo(() => { - const fromPagefind: RowItem[] = pageResults.map((r) => ({ - group: classifyUrl(r.url), - url: r.url, - title: r.meta.title ?? r.url, - excerpt: r.excerpt, - meta: [r.meta.topic, r.meta.read].filter(Boolean).join(' · '), - })); - const merged = [...topicMatches, ...fromPagefind]; - const order: Group[] = ['topic', 'article', 'tool', 'author', 'page']; - return merged.sort((a, b) => order.indexOf(a.group) - order.indexOf(b.group)); - }, [topicMatches, pageResults]); - - useEffect(() => { - function onKey(e: KeyboardEvent) { - const mod = e.metaKey || e.ctrlKey; - if (mod && e.key.toLowerCase() === 'k') { - e.preventDefault(); - setOpen((v) => !v); - } else if (e.key === '/' && !open) { - const t = e.target as HTMLElement | null; - const tag = t?.tagName; - if (tag !== 'INPUT' && tag !== 'TEXTAREA' && !t?.isContentEditable) { - e.preventDefault(); - setOpen(true); - } - } else if (e.key === 'Escape' && open) { - close(); - } - } - document.addEventListener('keydown', onKey); - return () => document.removeEventListener('keydown', onKey); - }, [open, close]); - - useEffect(() => { - function onTrigger() { - setOpen(true); - } - document.addEventListener('mls:open-search', onTrigger); - return () => document.removeEventListener('mls:open-search', onTrigger); - }, []); - - useEffect(() => { - if (open) { - document.body.style.overflow = 'hidden'; - setTimeout(() => inputRef.current?.focus(), 30); - } else { - document.body.style.overflow = ''; - } - return () => { - document.body.style.overflow = ''; - }; - }, [open]); - - useEffect(() => { - let cancelled = false; - if (!query.trim()) { - setPageResults([]); - setLoading(false); - return; - } - setLoading(true); - (async () => { - try { - const pf = await loadPagefind(); - const res = await pf.debouncedSearch(query); - if (cancelled || !res) return; - const top = res.results.slice(0, 10); - const data = await Promise.all(top.map((r) => r.data())); - if (!cancelled) { - setPageResults(data); - setActiveIdx(0); - } - } catch { - if (!cancelled) setPageResults([]); - } finally { - if (!cancelled) setLoading(false); - } - })(); - return () => { - cancelled = true; - }; - }, [query]); - - function onKeyInput(e: React.KeyboardEvent) { - if (e.key === 'ArrowDown') { - e.preventDefault(); - setActiveIdx((i) => Math.min(i + 1, rows.length - 1)); - } else if (e.key === 'ArrowUp') { - e.preventDefault(); - setActiveIdx((i) => Math.max(i - 1, 0)); - } else if (e.key === 'Enter' && rows[activeIdx]) { - e.preventDefault(); - window.location.href = rows[activeIdx].url; - } - } - - useEffect(() => { - const el = listRef.current?.children[activeIdx] as HTMLElement | undefined; - el?.scrollIntoView({ block: 'nearest' }); - }, [activeIdx]); - - if (!open) return null; - - return ( -
- - )} -
- -
- {!query && ( -
-
-
Browse by topic
-
- {TOPICS.map((t) => ( - - {t.name} - - ))} -
-
-
-
Try searching
-
- {['attention', 'quantization', 'vLLM', 'FSDP', 'evals'].map((t) => ( - - ))} -
-
-
- )} - - {query && loading && rows.length === 0 &&
Searching…
} - - {query && !loading && rows.length === 0 && ( -
- No matches for {query}.{' '} - - Browse the archive → - -
- )} - - {rows.length > 0 && ( - - )} -
- -
- - - navigate - - - open - - - esc close - - - Full search → - -
- - - ); -} diff --git a/src/components/Stat.astro b/src/components/Stat.astro index 10a4dbf..70e5ff9 100644 --- a/src/components/Stat.astro +++ b/src/components/Stat.astro @@ -1,5 +1,4 @@ --- -// A single number + label stat (hero counters, etc.). interface Props { value: number | string; label: string; diff --git a/src/components/Step.astro b/src/components/Step.astro index ec7917b..a68cc28 100644 --- a/src/components/Step.astro +++ b/src/components/Step.astro @@ -1,5 +1,4 @@ --- -// A numbered process step (see the Contribute page). interface Props { n: string; title: string; diff --git a/src/env.d.ts b/src/env.d.ts index f964fe0..40c1907 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -1 +1,7 @@ /// + +interface Window { + dataLayer?: unknown[]; + gtag?: (...args: unknown[]) => void; + __mlsPagefind?: import('@/lib/pagefind').Pagefind; +} diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 58df2c1..bdf5f2c 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -5,8 +5,8 @@ import { APPEARANCE, SITE, SOCIALS } from '@/lib/site'; import Nav from '@/components/Nav.astro'; import Footer from '@/components/Footer.astro'; -import AnalyticsConsent from '@/components/AnalyticsConsent.tsx'; -import SearchModal from '@/components/SearchModal.tsx'; +import AnalyticsConsent from '@/components/AnalyticsConsent.astro'; +import SearchModal from '@/components/SearchModal.astro'; import '@/styles/global.css'; // Self-hosted fonts (latin subset). No third-party request; @fontsource sets font-display: swap. @@ -26,6 +26,11 @@ import '@fontsource/jetbrains-mono/latin-400.css'; import '@fontsource/jetbrains-mono/latin-500.css'; import '@fontsource/jetbrains-mono/latin-600.css'; +// Above-the-fold faces on every page — preloaded to avoid the first-paint font swap. +import playfairWoff2 from '@fontsource/playfair-display/files/playfair-display-latin-400-normal.woff2?url'; +import hankenWoff2 from '@fontsource/hanken-grotesk/files/hanken-grotesk-latin-400-normal.woff2?url'; +import monoWoff2 from '@fontsource/jetbrains-mono/files/jetbrains-mono-latin-400-normal.woff2?url'; + interface Props { title?: string; description?: string; @@ -82,7 +87,7 @@ const siteJsonLd = [ url: SITE.url, logo: new URL('/favicon-96x96.png', SITE.url).toString(), description: SITE.description, - sameAs: [SOCIALS.github, SOCIALS.twitter, SOCIALS.linkedin].filter(Boolean), + sameAs: [SOCIALS.github, SOCIALS.twitter, SOCIALS.linkedin, SOCIALS.discord].filter(Boolean), }, { '@context': 'https://schema.org', @@ -90,6 +95,7 @@ const siteJsonLd = [ name: SITE.name, url: SITE.url, description: SITE.description, + publisher: { '@type': 'Organization', name: SITE.name, url: SITE.url }, potentialAction: { '@type': 'SearchAction', target: { @@ -128,6 +134,8 @@ const siteJsonLd = [ } if (prefs && (prefs.theme === 'light' || prefs.theme === 'dark')) { root.setAttribute('data-theme', prefs.theme); + } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) { + root.setAttribute('data-theme', 'dark'); } const validAccents = ['oxide', 'indigo', 'emerald']; if (prefs && validAccents.indexOf(prefs.accent) !== -1) { @@ -178,10 +186,14 @@ const siteJsonLd = [ + + + + @@ -196,6 +208,8 @@ const siteJsonLd = [ + + diff --git a/src/components/ToolGlyph.tsx b/src/components/ToolGlyph.tsx index bb10ebe..4318051 100644 --- a/src/components/ToolGlyph.tsx +++ b/src/components/ToolGlyph.tsx @@ -28,19 +28,23 @@ export default function ToolGlyph({ id }: { id: string }) { ); - case 'cost-calc': + case 'gpu-mem-calc': return ( - - $ - + + + + + ); case 'model-card': diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index bdf5f2c..879eef1 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -5,6 +5,7 @@ import { APPEARANCE, SITE, SOCIALS } from '@/lib/site'; import Nav from '@/components/Nav.astro'; import Footer from '@/components/Footer.astro'; +import NewsletterStrip from '@/components/NewsletterStrip.astro'; import AnalyticsConsent from '@/components/AnalyticsConsent.astro'; import SearchModal from '@/components/SearchModal.astro'; import '@/styles/global.css'; @@ -253,6 +254,7 @@ const siteJsonLd = [
+
diff --git a/src/lib/data.ts b/src/lib/data.ts index 5bcc7d7..97a96bc 100644 --- a/src/lib/data.ts +++ b/src/lib/data.ts @@ -109,7 +109,7 @@ export const EXTERNAL_TOOLS: ExternalTool[] = [ { name: 'The Tokenizer Playground', source: 'Xenova · Hugging Face', - desc: 'Compare how GPT-4, LLaMA, Mistral, Qwen, Gemma, and others tokenize the same text — side by side, in the browser.', + desc: 'Tokenize the same text with GPT-4, Claude, LLaMA, Mistral, Gemma, and more — switch tokenizers instantly, or load any Hugging Face tokenizer. Runs in the browser.', href: 'https://huggingface.co/spaces/Xenova/the-tokenizer-playground', category: 'Tokenization', }, @@ -130,21 +130,21 @@ export const EXTERNAL_TOOLS: ExternalTool[] = [ { name: 'APXML VRAM Calculator', source: 'APXML', - desc: 'Inference-focused VRAM calculator covering Nvidia GPUs and Apple Silicon. Good for picking hardware for a target model.', + desc: 'Inference and fine-tuning VRAM calculator covering Nvidia GPUs and Apple Silicon. Good for picking hardware for a target model.', href: 'https://apxml.com/tools/vram-calculator', category: 'Memory & VRAM', }, { name: 'LLM Visualization', source: 'Brendan Bycroft', - desc: 'A 3D, animated walk through the entire forward pass of GPT-2 nano, layer by layer. The clearest mental model of how a transformer works.', + desc: 'A 3D, animated walk through the entire forward pass of a nano-GPT model, layer by layer. The clearest mental model of how a transformer works.', href: 'https://bbycroft.net/llm', category: 'Architecture', }, { name: 'Chinchilla Scaling Calculator', source: 'Nathan Godey', - desc: 'Plug in a compute budget, get the compute-optimal model and data size per Hoffmann et al. 2022. Charts the iso-loss surface too.', + desc: 'Enter a model size, get the Chinchilla-optimal training-token count per Hoffmann et al. 2022 — with an interactive params-vs-tokens chart.', href: 'https://nathangodey.github.io/posts/scaling/', category: 'Training & Scaling', }, diff --git a/src/lib/site.ts b/src/lib/site.ts index cf676d6..00fcf6a 100644 --- a/src/lib/site.ts +++ b/src/lib/site.ts @@ -39,9 +39,11 @@ export const SITE = { // set PUBLIC_OG_CARD_OPTIN=true to expose it. Opted-in posts render a per-post OG // card at build (a small build-time cost), so keep it off unless you want it. ogCardOptIn: import.meta.env.PUBLIC_OG_CARD_OPTIN === 'true', - // Newsletter signup link (e.g. a Google Form). Leave empty to hide all - // newsletter UI (footer block + end-of-article box). - newsletterUrl: '', + // Google Form /formResponse URL + email field entry id. + newsletter: { + formUrl: '', + emailEntry: 'entry.999355293', + }, }; export const APPEARANCE = { diff --git a/src/pages/blog/[slug].astro b/src/pages/blog/[slug].astro index c65efe8..8e70a32 100644 --- a/src/pages/blog/[slug].astro +++ b/src/pages/blog/[slug].astro @@ -7,7 +7,6 @@ import { mdxComponents } from '@/components/MDXComponents.tsx'; import ArticleActions from '@/components/ArticleActions.tsx'; import Comments from '@/components/Comments.tsx'; import Avatar from '@/components/Avatar.astro'; -import NewsletterCta from '@/components/NewsletterCta.astro'; import { formatDate, sortPostsByDate, topicName, tagSlug } from '@/lib/data'; import { resolvePostAuthors } from '@/lib/posts'; import { SITE } from '@/lib/site'; @@ -191,10 +190,6 @@ const breadcrumbJsonLd = { date={formatDate(dateISO)} /> -
- -
-
diff --git a/src/pages/index.astro b/src/pages/index.astro index bbff54b..dcad488 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -20,7 +20,6 @@ const recent = allPosts.filter((p) => !featuredIds.has(p.id)).slice(0, 6); const topicCounts = countPostsByTopic(allPosts); const articleCount = allPosts.length; const contributorCount = authors.length; -const topicCount = TOPICS.length; const TOPIC_PREVIEW_LIMIT = 5; const topicsWithPosts = TOPICS.map((t) => ({ @@ -80,7 +79,6 @@ const featuredTools = allTools
-
diff --git a/src/styles/global.css b/src/styles/global.css index 9346092..a7b64a0 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1250,31 +1250,161 @@ a.hashtag:hover { } } -/* Newsletter CTA */ -.newsletter-cta { +/* Newsletter strip + dialog */ +.nl-strip { + display: block; + width: min(100% - 48px, 880px); + margin: var(--section-py) auto 48px; + border: 1px solid var(--line); + border-radius: var(--radius-pill); + background: var(--paper-2); + padding: 0; + cursor: pointer; + font: inherit; + color: inherit; + transition: + border-color 0.15s ease, + background 0.15s ease, + transform 0.18s ease; +} +.nl-strip:hover { + border-color: var(--accent); + background: var(--paper); + transform: translateY(-1px); +} +.nl-strip:hover .nl-strip-cta { + color: var(--accent-2); +} +.nl-strip ~ .footer { + margin-top: 0; +} +.nl-strip-inner { + padding: 16px 28px; display: flex; align-items: center; - justify-content: space-between; - gap: 20px 32px; + gap: 12px 24px; flex-wrap: wrap; + text-align: left; +} +.nl-strip-copy { + font-size: 14px; + color: var(--ink-2); +} +.nl-strip-cta { + margin-left: auto; + font-family: var(--font-mono); + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--accent); +} + +@media (max-width: 640px) { + .nl-strip { + border-radius: var(--radius-md); + } + .nl-strip-inner { + flex-direction: column; + align-items: center; + gap: 6px; + padding: 18px 20px; + text-align: center; + } + .nl-strip-cta { + margin-left: 0; + margin-top: 6px; + } +} + +.nl-modal { + position: fixed; + inset: 0; + z-index: 200; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; +} +.nl-backdrop { + position: absolute; + inset: 0; + border: none; + background: rgba(20, 16, 10, 0.45); + cursor: pointer; +} +.nl-panel { + position: relative; + width: min(440px, 100%); + background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius-md); + padding: 28px; + box-shadow: 0 24px 60px rgba(0, 0, 0, 0.18); +} +.nl-panel h3 { + font-family: var(--font-display); + font-size: 24px; + font-weight: 400; + margin: 0 0 6px; +} +.nl-sub { + margin: 0 0 18px; + font-size: 14px; + color: var(--ink-2); +} +.nl-panel form { + display: flex; + gap: 10px; + flex-wrap: wrap; +} +.nl-panel input[type='email'] { + flex: 1; + min-width: 200px; + padding: 10px 14px; + font: inherit; + font-size: 14px; + color: var(--ink); background: var(--paper-2); - padding: 24px 28px; - margin-top: 64px; + border: 1px solid var(--line-2); + border-radius: var(--radius-sm); } -.newsletter-cta-body .eyebrow { - margin-bottom: 6px; +.nl-panel input[type='email']:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 1px; } -.newsletter-cta-body p { - margin: 0; +.nl-error { + margin: 12px 0 0; + font-size: 13px; + color: var(--accent); +} +.nl-done { + display: flex; + align-items: center; + gap: 10px; font-size: 15px; - color: var(--ink-2); + color: var(--ink); } -.newsletter-cta--compact { - margin: 0 0 40px; - padding: 18px 22px; - background: transparent; +.nl-done-check { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: var(--radius-circle); + background: var(--accent); + color: var(--paper); + font-size: 15px; + animation: nl-pop 0.35s cubic-bezier(0.2, 1.4, 0.4, 1); +} +@keyframes nl-pop { + 0% { + transform: scale(0.3); + opacity: 0; + } + 100% { + transform: scale(1); + opacity: 1; + } } /* Footer */ From 47c794b06f4daff4302cf484407924d2a8bec023 Mon Sep 17 00:00:00 2001 From: Dinesh <13635627+HumbleBee14@users.noreply.github.com> Date: Tue, 14 Jul 2026 08:04:11 -0700 Subject: [PATCH 6/6] Add /why page with manifesto and side art; link from footer, About, homepage --- astro.config.mjs | 3 +- src/components/Footer.astro | 2 +- src/pages/about.astro | 8 +- src/pages/index.astro | 1 + src/pages/why.astro | 222 ++++++++++++++++++++++++++++++++++++ src/styles/global.css | 7 ++ 6 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 src/pages/why.astro diff --git a/astro.config.mjs b/astro.config.mjs index 6a969c0..85adcbb 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -54,7 +54,8 @@ const SKIP_PATTERNS = ['/write', '/search']; function priorityFor(path) { if (path === '/' || path === '') return 1.0; if (path === '/blog' || path === '/topics') return 0.9; - if (path === '/playground' || path === '/community' || path === '/contribute') return 0.8; + if (path === '/playground' || path === '/community' || path === '/contribute' || path === '/why') + return 0.8; if (path.startsWith('/blog/')) return 0.7; if (path.startsWith('/topics/') || path.startsWith('/tags/')) return 0.6; if (path.startsWith('/authors/')) return 0.6; diff --git a/src/components/Footer.astro b/src/components/Footer.astro index 5d5bf36..c32eb68 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -92,7 +92,7 @@ const socialIcons = (['discord', 'github', 'twitter', 'linkedin'] as const).map(
diff --git a/src/pages/about.astro b/src/pages/about.astro index e44f131..0657542 100644 --- a/src/pages/about.astro +++ b/src/pages/about.astro @@ -78,8 +78,12 @@ const links = [

- Everything here is © its respective author, all rights reserved. No paywalls, no popups, no - "10x your ML career" funnels. Just notes from the workbench. + We think this knowledge is about to matter much more than it does today — + here's why this exists. +

+ +

+ Everything here is © its respective author, all rights reserved. Notes from the workbench.

diff --git a/src/pages/why.astro b/src/pages/why.astro new file mode 100644 index 0000000..80c8f94 --- /dev/null +++ b/src/pages/why.astro @@ -0,0 +1,222 @@ +--- +import BaseLayout from '@/layouts/BaseLayout.astro'; +import { SITE } from '@/lib/site'; + +const canonical = `${SITE.url}/why`; + +const pageJsonLd = { + '@context': 'https://schema.org', + '@type': 'WebPage', + name: 'Why this exists', + description: + 'Why learning machine learning systems matters: intelligence is moving to personal, local devices — and the systems layer decides how fast we get there.', + url: canonical, + isPartOf: { '@type': 'WebSite', name: SITE.name, url: SITE.url }, +}; +--- + + +
+
+
Why
+

Why this exists.

+
+ +
+
+

+ Every important technology ends up boring. Electricity, databases, GPS — miracles that + became plumbing. Machine intelligence is on the same path, and we are living through its + plumbing years. This site is about those years, and the people doing the work. +

+ +
+

Most "model progress" is systems progress

+

+ Ask what actually changed between the demo that amazed you and the product you use every + day: tokens got cheaper, first tokens got faster, contexts got longer, models started + fitting on hardware you own. Almost none of that came from smarter weights. It came from + quantization, batching, caches, kernels, schedulers — the unglamorous layer underneath. + The distance between a demo and a product is measured in milliseconds and megabytes, and + systems engineers are the ones who close it. +

+
+ +
+

Computing always moves closer to you

+

+ Mainframe to desktop, desktop to pocket, cloud to edge — every generation of computing + ends up nearer to the person using it, because latency, cost, and privacy all pull the + same way. Intelligence is on the same road. The endpoint is a model that runs on devices + you own, tuned on your own context — your notes, your work, your family's routines — a + private intelligence layer that answers to you and no one else. A model that knows you + that well shouldn't live in someone else's building. The datacenter era of AI is its + mainframe era, and the people who understand inference at the edge are the ones who will + end it. +

+
+ +
+

The fundamentals outlast the headlines

+

+ Architectures churn monthly; the systems layer barely moves. Memory hierarchies, + arithmetic intensity, batching tradeoffs, the cost of moving a byte versus computing on + it — these were true before transformers and will be true after them. Learning ML + systems is learning the invariants: knowledge that compounds for decades while the + leaderboards reshuffle. +

+
+ +
+

The bottleneck is people

+

+ The knowledge that makes all of this work is concentrated in a handful of infrastructure + teams and scattered across conference talks and half-finished blog posts. That scarcity + is the real constraint on how fast the local, personal future arrives. The fix is old + and reliable: write things down, in the open, where anyone can learn them. A field grows + exactly as fast as its commons. +

+
+ +
+

So we write

+

+ Articles, primers, and tools from practitioners — honest, technically grounded, free to + read, open to anyone who has figured something out and is willing to pass it on. If the + future we described sounds right to you, help build the commons that gets us there + sooner. +

+
+ + +
+ + +
+
+
+ + diff --git a/src/styles/global.css b/src/styles/global.css index a7b64a0..6a40740 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1510,6 +1510,13 @@ a.hashtag:hover { font-size: 11px; color: var(--ink-3); } +.footer-why { + color: inherit; + text-decoration: none; +} +.footer-why:hover { + color: var(--accent); +} /* Article view */ .article-shell {