diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..9b2a6a0
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,8 @@
+# Preview-only 410 path artifacts (copy-410-paths.mjs).
+# Deploy uses pnpm run build (no copy step); nginx serves 410 via error_page.
+# These dirs exist only when running build-preview for local serve dist.
+dist/pt
+dist/feed
+dist/2024
+dist/category
+dist/comments
diff --git a/AGENTS.md b/AGENTS.md
index 3ea0d38..34a9ec9 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -10,7 +10,7 @@ This file helps LLM agents work in this repo without introducing anti-patterns.
## Stack and architecture
- **Framework**: SvelteKit with **adapter-static**. All routes are prerendered; there is no client-side router or hydration (`csr: false` in `src/routes/+layout.ts`).
-- **Build**: `pnpm run build` = `vite build` → `node scripts/critters.mjs` → `node scripts/minify-static-js.mjs` → `node scripts/copy-410-paths.mjs`. Output is `dist/` (static files only).
+- **Build**: `pnpm run build` = `vite build` → `node scripts/critters.mjs` → `node scripts/generate-sitemap.mjs` → `node scripts/minify-static-js.mjs`. Output is `dist/` (static files only). Deploy uses this; no 410 path copies. For local preview with 410 URLs working, use `pnpm run build-preview` (adds `copy-410-paths.mjs`). The 410 path dirs are in `.dockerignore` so they are never included in the image.
- **Runtime**: nginx serves `dist/` (mounted as `/usr/share/nginx/html` in the container). No Node at runtime.
- **Theming**: CSS only. Light/dark follows **system preference** via `@media (prefers-color-scheme: dark)` in `src/app.css`. There is no JS theme toggle or `data-theme`; do not add one unless explicitly requested.
- **Fonts**: **Local only.** Inter and Fraunces are served from `static/assets/fonts/` (e.g. `inter-v20-latin-*.woff2`, `fraunces-v38-latin-*.woff2`). Preloads are in `src/routes/+layout.svelte`. Do not add Google Fonts or other external font URLs for the main site or error pages.
@@ -29,7 +29,7 @@ This file helps LLM agents work in this repo without introducing anti-patterns.
| `static/` | Copied as-is into `dist/` by SvelteKit. Favicon, robots.txt, fonts, images, **404.html**, **410.html**, and **assets/error-pages.css** live here. |
| `scripts/critters.mjs` | Post-build: inlines critical CSS into **every** `dist/*.html` (including 404 and 410). Resolves stylesheet URLs relative to `dist/`. |
| `scripts/minify-static-js.mjs` | Post-build: minifies JS in `dist/assets/`. |
-| `scripts/copy-410-paths.mjs` | Post-build: copies `410.html` to each 410 URL path as `index.html` so static preview (e.g. `serve dist`) shows the 410 page at those URLs; nginx still returns 410 via explicit location blocks. |
+| `scripts/copy-410-paths.mjs` | Run by `build-preview` only: copies `410.html` to each 410 URL path as `index.html` so static preview (e.g. `serve dist`) shows the 410 page at those URLs. Production uses `build` (no copy); nginx returns 410 via explicit location blocks and `error_page 410 /410.html`. |
| `nginx.conf` | Serves static files; `try_files $uri $uri/ /index.html` for SPA-style fallback; `error_page 404 /404.html` and `error_page 410 /410.html` for custom error pages. |
## Static error pages (404, 410)
@@ -41,7 +41,7 @@ This file helps LLM agents work in this repo without introducing anti-patterns.
- **Do not** add error-page-only CSS in `src/app.css`; the app bundle is not loaded on error pages.
- **Theme alignment**: When changing light/dark colors or typography in `src/app.css`, update **`static/assets/error-pages.css`** so 404/410 stay visually consistent (same `--ep-*` tokens and, if needed, `@media (prefers-color-scheme: dark)`).
- **Local fonts**: Error pages use the same font paths as the main site (`/assets/fonts/...`) via `@font-face` in `error-pages.css`. Do not use Google Fonts or other external font URLs on error pages.
-- **Preview vs production**: In preview (`serve dist`), the 410 URLs (e.g. `/pt/`, `/feed/`) are served by copying `410.html` to each path as `index.html` (see `scripts/copy-410-paths.mjs`). In production, nginx returns HTTP 410 for those paths and serves the same content via `error_page 410 /410.html`. If you add or remove 410 paths, update both `nginx.conf` and the `PATHS` array in `scripts/copy-410-paths.mjs`.
+- **Preview vs production**: Deploy runs `pnpm run build` (no 410 path copies); the image stays minimal and nginx serves 410 via `error_page 410 /410.html`. For local preview with 410 URLs working, run `pnpm run build-preview` then `serve dist`; the copied `index.html` files under each 410 path are in `.dockerignore` so they are never copied into the image. If you add or remove 410 paths, update `nginx.conf` and the `PATHS` array in `scripts/410-paths.mjs`.
## Anti-patterns to avoid
diff --git a/package.json b/package.json
index e1244fc..98dcaa6 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,8 @@
"description": "mifi Ventures landing site — SvelteKit static build with critical CSS inlining",
"type": "module",
"scripts": {
- "build": "vite build && node scripts/critters.mjs && node scripts/generate-sitemap.mjs && node scripts/minify-static-js.mjs && node scripts/copy-410-paths.mjs",
+ "build": "vite build && node scripts/critters.mjs && node scripts/generate-sitemap.mjs && node scripts/minify-static-js.mjs",
+ "build-preview": "pnpm run build && node scripts/copy-410-paths.mjs",
"dev": "vite dev",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
diff --git a/src/app.css b/src/app.css
index 9837b25..ccf4bca 100644
--- a/src/app.css
+++ b/src/app.css
@@ -333,7 +333,7 @@ a {
max-width: var(--max-width);
margin: 0 auto;
padding: 0 var(--space-md);
-
+
&.container--narrow {
max-width: var(--max-narrow-width);
}
@@ -492,6 +492,152 @@ a {
}
}
+.content-list--ordered {
+ list-style: decimal;
+ padding-left: var(--space-xl);
+}
+
+.content-list--ordered li {
+ padding-left: var(--space-sm);
+}
+
+.content-list--ordered li::before {
+ content: none;
+}
+
+/* ========================================
+ Service Pages: Credibility Strip, Who Grid, FAQ
+ ======================================== */
+
+.service-credibility {
+ background-color: var(--color-bg-subtle);
+}
+
+.service-credibility__list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ max-width: var(--max-width);
+ margin: 0 auto;
+ display: grid;
+ gap: var(--space-md);
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+}
+
+.service-credibility__list li {
+ position: relative;
+ padding-left: var(--space-lg);
+ font-size: var(--font-size-base);
+ line-height: var(--line-height-relaxed);
+ color: var(--color-text-secondary);
+}
+
+.service-credibility__list li::before {
+ content: '→';
+ position: absolute;
+ left: 0;
+ color: var(--color-primary);
+ font-weight: var(--font-weight-semibold);
+}
+
+.who-grid {
+ display: grid;
+ gap: var(--space-xxl);
+ grid-template-columns: 1fr 1fr;
+}
+
+@media (max-width: 768px) {
+ .who-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+.who-block h2,
+.who-block .list-heading {
+ font-size: var(--font-size-xl);
+ margin-bottom: var(--space-md);
+}
+
+/* ========================================
+ Services Landing: Card Grid, Engagements, Ideal
+ ======================================== */
+
+.services-grid-section {
+ background-color: var(--color-bg);
+}
+
+.services-card-list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: grid;
+ gap: var(--space-xl);
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+}
+
+.services-card {
+ padding: var(--space-xl);
+ background-color: var(--color-bg-alt);
+ border: 1px solid var(--color-border);
+ border-radius: var(--border-radius-medium);
+ display: flex;
+ flex-direction: column;
+}
+
+.services-card__title {
+ margin-bottom: var(--space-md);
+ font-size: var(--font-size-large);
+ font-weight: var(--font-weight-semibold);
+}
+
+.services-card__desc {
+ flex: 1;
+ margin-bottom: var(--space-lg);
+ font-size: var(--font-size-base);
+ color: var(--color-text-secondary);
+ line-height: var(--line-height-relaxed);
+}
+
+.services-card__link {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-xs);
+ font-weight: var(--font-weight-semibold);
+}
+
+.services-card__link span {
+ margin-left: var(--space-xs);
+}
+
+.engagements-list {
+ margin: var(--space-lg) 0;
+}
+
+.engagements-list dt {
+ font-weight: var(--font-weight-semibold);
+ color: var(--color-text);
+ margin-top: var(--space-lg);
+ margin-bottom: var(--space-xs);
+}
+
+.engagements-list dt:first-child {
+ margin-top: 0;
+}
+
+.engagements-list dd {
+ margin: 0 0 0 var(--space-md);
+ color: var(--color-text-secondary);
+ line-height: var(--line-height-relaxed);
+}
+
+.services-intro {
+ background-color: var(--color-bg-alt);
+}
+
+.services-ideal {
+ background-color: var(--color-bg-alt);
+}
+
/* ========================================
Nav Item and Footer Links Common Styles
======================================== */
diff --git a/src/lib/components/EngagementsDl.svelte b/src/lib/components/EngagementsDl.svelte
new file mode 100644
index 0000000..fe8a1c6
--- /dev/null
+++ b/src/lib/components/EngagementsDl.svelte
@@ -0,0 +1,37 @@
+
+
+
+
+
{heading}
+ {#if intro}
+
{intro}
+ {/if}
+
+ {#each items as item}
+ - {item.term}
+ - {item.definition}
+ {/each}
+
+ {#if outro}
+
{outro}
+ {/if}
+
+
diff --git a/src/lib/components/FAQ.svelte b/src/lib/components/FAQ.svelte
index a4b29a5..3335f03 100644
--- a/src/lib/components/FAQ.svelte
+++ b/src/lib/components/FAQ.svelte
@@ -29,18 +29,18 @@
.faq-list {
margin: 0;
-
+
& dt {
font-weight: var(--font-weight-semibold);
color: var(--color-text);
margin-top: var(--space-xl);
margin-bottom: var(--space-sm);
-
+
&:first-child {
margin-top: 0;
}
}
-
+
& dd {
margin: 0 0 0 var(--space-md);
color: var(--color-text-secondary);
diff --git a/src/lib/components/Hero.svelte b/src/lib/components/Hero.svelte
index 0f64b76..14b9b45 100644
--- a/src/lib/components/Hero.svelte
+++ b/src/lib/components/Hero.svelte
@@ -100,4 +100,4 @@
width: 100%;
}
}
-
\ No newline at end of file
+
diff --git a/src/lib/components/Navigation.svelte b/src/lib/components/Navigation.svelte
index 5163690..a269f2f 100644
--- a/src/lib/components/Navigation.svelte
+++ b/src/lib/components/Navigation.svelte
@@ -6,7 +6,9 @@
/** Page slug for body class: "page-home" | "page-services" | "page-services-hands-on-saas-architecture-consultant" etc. Set at build time per route; no client JS. */
const bodyClass = $derived(
- path === '/' ? 'page-home' : 'page-' + path.replace(/^\/|\/$/g, '').replace(/\//g, '-')
+ path === '/'
+ ? 'page-home'
+ : 'page-' + path.replace(/^\/|\/$/g, '').replace(/\//g, '-'),
);
interface NavigationItem {
@@ -27,7 +29,10 @@
hidden
/>