diff --git a/apps/web/src/lib/assets/favicon.svg b/apps/web/src/lib/assets/favicon.svg
index cc5dc66..3805101 100644
--- a/apps/web/src/lib/assets/favicon.svg
+++ b/apps/web/src/lib/assets/favicon.svg
@@ -1 +1,10 @@
-
\ No newline at end of file
+
diff --git a/apps/web/src/lib/blob-proxy.ts b/apps/web/src/lib/blob-proxy.ts
index ac4ac0c..02d5690 100644
--- a/apps/web/src/lib/blob-proxy.ts
+++ b/apps/web/src/lib/blob-proxy.ts
@@ -3,7 +3,7 @@
* blob and applies image transformations where the platform provides them.
*/
-const WIDTHS = [256, 1600] as const;
+const WIDTHS = [256, 768, 1600] as const;
export type BlobWidth = (typeof WIDTHS)[number];
export function isBlobWidth(value: number): value is BlobWidth {
diff --git a/apps/web/src/lib/components/theme/SiteLayout.svelte b/apps/web/src/lib/components/theme/SiteLayout.svelte
index 3eab034..108df8f 100644
--- a/apps/web/src/lib/components/theme/SiteLayout.svelte
+++ b/apps/web/src/lib/components/theme/SiteLayout.svelte
@@ -1,6 +1,8 @@
+
{#if site.colors.background}
{@html ``}
+
+ {:else}
+
+
{/if}
@@ -66,6 +73,9 @@
@@ -81,6 +91,7 @@
.site are invisible up here. */
:global(html) {
background: #f7f3ea;
+ color-scheme: light dark;
}
@media (prefers-color-scheme: dark) {
diff --git a/apps/web/src/lib/components/theme/SiteSections.svelte b/apps/web/src/lib/components/theme/SiteSections.svelte
index b5d84f2..f71c4ef 100644
--- a/apps/web/src/lib/components/theme/SiteSections.svelte
+++ b/apps/web/src/lib/components/theme/SiteSections.svelte
@@ -1,7 +1,7 @@
-
+
+ {#if !page.data.site}
+
+ {/if}
{@render children()}
diff --git a/apps/web/src/routes/+page.svelte b/apps/web/src/routes/+page.svelte
index 445cada..e9aff0f 100644
--- a/apps/web/src/routes/+page.svelte
+++ b/apps/web/src/routes/+page.svelte
@@ -1,9 +1,22 @@
@@ -15,8 +28,8 @@
{/if}
- {#if data.site.profile?.bannerUrl ?? data.site.avatarUrl}
-
+ {#if ogImage}
+
{/if}
{#if !data.site.discoverable}
diff --git a/apps/web/src/routes/s/[handle]/+page.svelte b/apps/web/src/routes/s/[handle]/+page.svelte
index 8fc6ca0..3394cd1 100644
--- a/apps/web/src/routes/s/[handle]/+page.svelte
+++ b/apps/web/src/routes/s/[handle]/+page.svelte
@@ -1,9 +1,21 @@
@@ -14,8 +26,8 @@
{/if}
- {#if data.site.profile?.bannerUrl ?? data.site.avatarUrl}
-
+ {#if ogImage}
+
{/if}
{#if !data.site.discoverable}
diff --git a/docs/NEXT.md b/docs/NEXT.md
index 1f91c88..7c421c4 100644
--- a/docs/NEXT.md
+++ b/docs/NEXT.md
@@ -2,7 +2,7 @@
The flight plan. Each item carries enough context to start cold; update this file whenever an item lands (move it to "Done") or a new one is queued. Decisions made while working an item still go through `decisions/` as usual.
-_Last updated: 2026-08-25 (**PR #22 merged** — contact CTA, career clamp, blob proxy (ADR 0015 Accepted), polish sweep. Awaiting Jacob-side steps: deploy, contactEmail in /admin, Image Transformations zone toggle, /about content, lexicon republication)._
+_Last updated: 2026-08-25 (**all operator steps done** — deployed, contactEmail set, Image Transformations live (avatar 966KB→17KB webp), /about written, lexicons republished. Critique 3 ran: 25 → 31 → 29, the dip being newly discovered pre-existing issues; the mechanical fixes for them are in the current PR)._
## Where things stand
@@ -14,12 +14,10 @@ Feasibility is done and the verdict was **build it** (see `FEASIBILITY.md`). All
Done so far: OAuth login (loopback dev client; hosted-client path ready pending a real key + deploy) with D1-backed state/session stores; lexicon convention tests; **read-only adapters** for Bluesky (profile + posts, replies filtered), standard.site (documents/publications, `pub.leaflet.document` fallback only when no standard.site docs exist), and sifa (profile/positions/education/skills, defensively parsed) — fetch-injected modules in `apps/web/src/lib/server/atmosphere/` with unit tests, plus `detectSources` (drives the ADR 0012 default section order) and a source-overview admin page; **site/page authoring** — record builders, PDS writes through the OAuth session, and the `/admin` + `/admin/pages` CRUD routes; **the professional-presence theme** — the render pipeline in `apps/web/src/lib/server/render/` and the public routes at `/s/[handle]`. Remaining, roughly in dependency order:
-- **Theme UX overhaul, remaining tail** (the overhaul landed 2026-08-24 and re-critiqued at **31/40** (from 25) — snapshots in `.impeccable/critique/`; the follow-up round landed 2026-08-25 as PR #22):
- 1. *Content* — replace the "TODO TODO" placeholder on jzweifel.dev/about; the reference instance demos the product. Also: set `contactEmail` in /admin so the new "Write to me" / "Say hello →" affordances render (they answer the re-critique's P0 — "Open to" advertised availability with no way to act).
- 2. *Enable Image Transformations on the mooring.page zone* (dashboard toggle) so the new `/blob` route (ADR 0015, Accepted 2026-08-25) actually resizes — until then it proxies originals (≈1.45MB of hero imagery per first view).
- 3. *Lexicon republication* — `heroSection.style` and the site record's `contactEmail` are new optional fields; republish the on-network schema when convenient (runbook §§3–4).
- 4. *Record-level career curation fields* (decided 2026-08-24, staged): the template now clamps the sifa summary and caps skills, but curation (hide/order/feature for positions, education, skills — e.g. de-emphasizing a high-school entry or dropping generic skills) should become `careerSection` fields edited in admin, once the admin UI grows a section editor.
- 5. *Re-critique after the next deploy*; residual known gaps: no RSS, mono-metadata density (flat 10.9–11.5px cluster — the detector's one finding), postcard hero unexercised by a real record, career-before-writing default order questioned when posts carry the personality.
+- **Theme UX overhaul, remaining tail** (critique trend 25 → 31 → 29 across three dual-assessment rounds — snapshots in `.impeccable/critique/`; the 29 reflects newly *discovered* pre-existing issues, not regression, and the current PR fixes the mechanical ones: per-site favicon (was the stock Svelte logo — the round-3 P0), career records sorted newest-first (was raw PDS record order), `yearSpan` tolerant of sifa's formless date strings, the career "More" disclosure made reversible, visible contact address in the footer, banner `srcset` + 768 blob width, absolute `og:image` through /blob, `color-scheme`/`theme-color` metas):
+ 1. *Record-level career curation fields* (decided 2026-08-24, staged): hide/order/feature for positions, education, skills — e.g. de-emphasizing a high-school entry or dropping generic imported skills ("Software Development", "Windows 7"); becomes `careerSection` fields once the admin grows a section editor. The round-3 review sharpened the framing: "honest by default, curatable by choice."
+ 2. *Design questions queued from round 3*: an airmail **sign-off block** closing the page (postmark/restated contact/handle — the metaphor currently peaks in the first 300px and the page ends unresolved); whether email should be the only action on an Atmosphere-native product (no follow/DM intent anywhere); scheme-aware site color overrides (a single background override pins both schemes); mono-metadata density (the detector's standing flat-type finding).
+ 3. *Residual*: no RSS; postcard hero still unexercised by a real record.
- **Thin deletion-honoring cache** over the direct PDS reads (ADR 0010 §4) — deliberately reordered below hosting on 2026-08-07: rendering reads live per request, which is correct until there is traffic to cache. Revisit once sites are actually served; when it comes up, start from Jetstream v2's snapshot+tail replay (`research/2026-08-14-bluesky-protocol-services.md`) rather than a relay consumer.
- Later, when token-refresh races become real: a Durable-Object `requestLock` for the OAuth client.