From 1e0afb103ca7a53b179437269a57dc77d873e9a9 Mon Sep 17 00:00:00 2001 From: mifi Date: Mon, 9 Mar 2026 18:09:12 -0300 Subject: [PATCH] Now, with services! --- .dockerignore | 8 + AGENTS.md | 6 +- package.json | 3 +- src/app.css | 148 +++++- src/lib/components/EngagementsDl.svelte | 37 ++ src/lib/components/FAQ.svelte | 6 +- src/lib/components/Hero.svelte | 2 +- src/lib/components/Navigation.svelte | 16 +- src/lib/components/ScheduleSection.svelte | 14 +- src/lib/components/ServiceSection.svelte | 82 +++ src/lib/components/ServicesCardGrid.svelte | 38 ++ src/lib/components/TOC.svelte | 10 +- src/lib/components/WhoGrid.svelte | 10 +- src/lib/components/home/Hero.svelte | 18 +- .../components/home/ServicesOverview.svelte | 12 +- src/lib/components/home/WhatWeDo.svelte | 5 +- .../faq.ts | 42 -- .../faq.ts | 47 -- src/lib/data/home/content.ts | 2 +- .../data/mvp-architecture-and-launch/faq.ts | 47 -- src/lib/data/privacy-policy.ts | 368 +++++++------- .../content.ts | 187 +++++++ .../faq.ts | 36 ++ .../content.ts | 230 +++++++++ .../faq.ts | 40 ++ src/lib/data/services/landing/content.ts | 103 ++++ .../mvp-architecture-and-launch/content.ts | 195 ++++++++ .../mvp-architecture-and-launch/faq.ts | 40 ++ .../stage-aligned-infrastructure/content.ts | 185 +++++++ .../stage-aligned-infrastructure/faq.ts | 36 ++ .../data/stage-aligned-infrastructure/faq.ts | 42 -- src/lib/data/terms-of-service.ts | 178 +++---- src/lib/types/service-page.ts | 132 +++++ src/routes/privacy-policy/+page.svelte | 278 ++++++----- src/routes/privacy-policy/+page.ts | 32 +- src/routes/services/+page.svelte | 301 +----------- src/routes/services/+page.ts | 49 +- .../+page.svelte | 187 +------ .../+page.ts | 51 +- .../+page.svelte | 465 +----------------- .../+page.ts | 51 +- .../mvp-architecture-and-launch/+page.svelte | 399 +-------------- .../mvp-architecture-and-launch/+page.ts | 51 +- .../stage-aligned-infrastructure/+page.svelte | 397 +-------------- .../stage-aligned-infrastructure/+page.ts | 51 +- src/routes/terms-of-service/+page.svelte | 218 ++++---- src/routes/terms-of-service/+page.ts | 32 +- tests/visual.spec.ts | 40 ++ .../home-chromium-darwin.png | Bin 0 -> 593558 bytes .../home-chromium-linux.png | Bin 594621 -> 567613 bytes .../privacy-policy-chromium-darwin.png | Bin 0 -> 588886 bytes .../privacy-policy-chromium-linux.png | Bin 557414 -> 561750 bytes .../services-chromium-darwin.png | Bin 0 -> 403664 bytes .../services-chromium-linux.png | Bin 0 -> 389582 bytes ...o-for-early-stage-saas-chromium-darwin.png | Bin 0 -> 829493 bytes ...to-for-early-stage-saas-chromium-linux.png | Bin 0 -> 802698 bytes ...rchitecture-consultant-chromium-darwin.png | Bin 0 -> 964984 bytes ...architecture-consultant-chromium-linux.png | Bin 0 -> 922451 bytes ...rchitecture-and-launch-chromium-darwin.png | Bin 0 -> 735349 bytes ...architecture-and-launch-chromium-linux.png | Bin 0 -> 698520 bytes ...aligned-infrastructure-chromium-darwin.png | Bin 0 -> 927261 bytes ...-aligned-infrastructure-chromium-linux.png | Bin 0 -> 886164 bytes .../terms-of-service-chromium-darwin.png | Bin 0 -> 372514 bytes .../terms-of-service-chromium-linux.png | Bin 355243 -> 359325 bytes 64 files changed, 2365 insertions(+), 2562 deletions(-) create mode 100644 .dockerignore create mode 100644 src/lib/components/EngagementsDl.svelte create mode 100644 src/lib/components/ServiceSection.svelte create mode 100644 src/lib/components/ServicesCardGrid.svelte delete mode 100644 src/lib/data/fractional-cto-for-early-stage-saas/faq.ts delete mode 100644 src/lib/data/hands-on-saas-architecture-consultant/faq.ts delete mode 100644 src/lib/data/mvp-architecture-and-launch/faq.ts create mode 100644 src/lib/data/services/fractional-cto-for-early-stage-saas/content.ts create mode 100644 src/lib/data/services/fractional-cto-for-early-stage-saas/faq.ts create mode 100644 src/lib/data/services/hands-on-saas-architecture-consultant/content.ts create mode 100644 src/lib/data/services/hands-on-saas-architecture-consultant/faq.ts create mode 100644 src/lib/data/services/landing/content.ts create mode 100644 src/lib/data/services/mvp-architecture-and-launch/content.ts create mode 100644 src/lib/data/services/mvp-architecture-and-launch/faq.ts create mode 100644 src/lib/data/services/stage-aligned-infrastructure/content.ts create mode 100644 src/lib/data/services/stage-aligned-infrastructure/faq.ts delete mode 100644 src/lib/data/stage-aligned-infrastructure/faq.ts create mode 100644 src/lib/types/service-page.ts create mode 100644 tests/visual.spec.ts-snapshots/home-chromium-darwin.png create mode 100644 tests/visual.spec.ts-snapshots/privacy-policy-chromium-darwin.png create mode 100644 tests/visual.spec.ts-snapshots/services-chromium-darwin.png create mode 100644 tests/visual.spec.ts-snapshots/services-chromium-linux.png create mode 100644 tests/visual.spec.ts-snapshots/services-fractional-cto-for-early-stage-saas-chromium-darwin.png create mode 100644 tests/visual.spec.ts-snapshots/services-fractional-cto-for-early-stage-saas-chromium-linux.png create mode 100644 tests/visual.spec.ts-snapshots/services-hands-on-saas-architecture-consultant-chromium-darwin.png create mode 100644 tests/visual.spec.ts-snapshots/services-hands-on-saas-architecture-consultant-chromium-linux.png create mode 100644 tests/visual.spec.ts-snapshots/services-mvp-architecture-and-launch-chromium-darwin.png create mode 100644 tests/visual.spec.ts-snapshots/services-mvp-architecture-and-launch-chromium-linux.png create mode 100644 tests/visual.spec.ts-snapshots/services-stage-aligned-infrastructure-chromium-darwin.png create mode 100644 tests/visual.spec.ts-snapshots/services-stage-aligned-infrastructure-chromium-linux.png create mode 100644 tests/visual.spec.ts-snapshots/terms-of-service-chromium-darwin.png 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 />
-