Commit graph

25 commits

Author SHA1 Message Date
074ea23b1b docs: README image-discipline section reflects the assets/ pipeline
Updates the path (public/images/ → src/assets/projects/) and adds a
sentence describing why the move was made: content-hashed filenames
let the deploy's immutable cache header stay correct across image
swaps without manual cache busts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 20:54:53 +02:00
d18319d593 refactor: serve project images through Vite's asset pipeline
Moves public/images/ → src/assets/projects/<slug>/ and switches
projects.ts to resolve image URLs via import.meta.glob with the
?url query. Vite content-hashes every file (e.g. 08-DzZjAiN9.jpg);
the HomeView hero img also imports through the pipeline.

Why: the deploy serves /assets/* with `cache-control: public,
immutable, max-age=31536000`. That's correct for content-hashed
files but was being applied to /images/* too, so swapping an image
in place left every cached visitor stuck on the old version for a
year. Hashed filenames means every byte-level change produces a
new URL, browsers fetch fresh automatically, and the immutable
cache stays correct.

The img(slug, file) helper throws at build time if a referenced
asset is missing — typos surface immediately instead of producing
a broken <img> at runtime.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 20:54:16 +02:00
b881913f22 retouch: remove brackets under the floating wood ledge in boulder/08
Client request — whiting out the iron brackets supporting the
live-edge ledge lets the wood read as fully cantilevered against
the wall, which is the move the room actually wants.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 20:30:27 +02:00
2bd197f441 copy: drop "oversized windows" line from studio lead
Client feedback — the line didn't flow with the rest of the
paragraph. Result reads tighter:
  "A balance of minimalism and soul. Warm woods, matte black
   accents, layered textures. Refined, but deeply human."

Applied across all three locales.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 20:23:01 +02:00
a26f42fb79 copy: drop redundant "Recent work." H2 on the home selected section
The eyebrow already labels the section "Selected work" — repeating
the same idea as a large H2 underneath ("Recent work.") was
redundant and gave the strip more vertical chrome than it needed.
Section now reads as a single quiet label next to the View-all
link, with the project cards doing the actual work. Removes the
unused home.selected.headline key from all three locales.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 20:20:15 +02:00
c44ed31cdd feat: stacked EARTH WALKER / DESIGN wordmark
Carries forward the original site's wordmark — EARTH WALKER set in
serif semibold over a smaller, lighter, wider-tracked DESIGN —
adapted to use Fraunces from our type system. Centered stack, no
frame, themed via currentColor so it inverts cleanly on the dark
hero overlay and follows the active palette and theme.

Two sizes: 'sm' (header, footer, mobile drawer) scales text-sm →
text-base at md; 'lg' (home hero overlay) scales text-2xl → text-3xl
→ text-4xl across breakpoints. Whitespace-nowrap holds the shape
together regardless of container width.

Used in:
  - SiteHeader logo slot (sm)
  - Mobile Sheet drawer header (sm)
  - SiteFooter brand block (sm)
  - HomeView hero overlay above the headline (lg)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 20:19:47 +02:00
c6ec34f5c3 copy: drop "two homes, one sensibility" tagline
Replace with the flatter "Recent work." / "Projets récents." /
"Proyectos recientes." across home (Selected work section headline)
and portfolio (page H1). The intro paragraph below the portfolio H1
already carries the contrast between the two projects, so the
headline doesn't need to make a claim on top of it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 12:20:32 +02:00
e7c62a2f8a feat: VITE_DEV_TOOLS env-gate for preview tooling on deployed builds
Adds a useDevTools() composable that returns true when either Vite's
own DEV flag is set (local pnpm dev) or VITE_DEV_TOOLS='true' is
passed at build time. AppLayout's PaletteSwitcher now reads through
this composable, so the picker can render on earthwalker.aiolabs.dev
without flipping the build to development mode.

Single gate intentionally — future preview-only surfaces (debug
overlays, unfinished pages, work-in-progress UI) hook into the same
composable. Anything Nicholette should see but the public shouldn't
becomes a v-if="devTools" check. Set the var on the deploy host,
rebuild, surfaces appear; unset, surfaces vanish.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:59:26 +02:00
c65ee029dd fix: escape @ in i18n messages, set useScope:'global' everywhere
Vue-i18n treats a bare '@' as the start of a linked-message reference,
which made every placeholder containing one — you{'@'}example.com,
the Telegram '{'@'}yourname' hint, the bad-Telegram error — crash the
compiler with "Invalid linked format". Escaping each '@' as the
literal {'@'} in en/es/fr fixes the compile and renders as a plain
'@' to the visitor.

Separately, every useI18n() call now passes { useScope: 'global' }.
Without it, components mounted inside <Form> / <Field> contexts
couldn't find a parent i18n scope and vue-i18n logged "Not found
parent scope. use the global scope." on every render. Explicit
global scope silences the warning and matches what the app
actually intends — there are no per-component message bundles.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:39:19 +02:00
00e77ecf05 feat: i18n migration + French + Spanish translations + locale switcher
Every visitor-facing string now flows through namespaced i18n keys
(home.*, portfolio.*, projects.<slug>.*, contact.*, form.*, etc.),
including alt text and form-validation error messages. Project copy
(name / eyebrow / intro / coverAlt) is looked up by slug from the
locale bundle rather than hard-coded in src/data/projects.ts —
project shape stays English-default for fallback values, but the
runtime resolves via i18n.

Adds a complete fr.json and rewrites es.json from the boilerplate
demo content. Three locales total: en (default), fr, es. The
initial locale picks the persisted `ewd:locale` value, falls back
to a navigator.language match, then to en.

LocaleSwitcher (a small EN/FR/ES dropdown in the header next to the
theme toggle) writes to localStorage so the choice survives
navigation and reload. Visible at both desktop and mobile
breakpoints.

The ContactForm's zod schema is rebuilt as a computed so error
messages re-translate live when the locale changes. The PrivacyBlurb
uses <i18n-t> with a #nostr slot so "Nostr network" can be the
emphasized inline span across all three locales.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:33:57 +02:00
f0980d7fb5 feat: four OKLCH palette options + dev-only switcher
Extracts each palette into its own module under src/themes/, scoped
to :root[data-palette='<slug>'] so multiple can coexist and a JS
attribute swap is enough to flip the whole site:

- stone-warm — bone + matte-black, gallery-quiet (current default)
- desert-clay — warm cream with terracotta undertone, evening light
- forest-ash — sage-tinted neutral with mossy accent, nature-leaning
- charcoal-cream — high-contrast monochrome, Asheville moody by default

usePalette() reads ?palette=<slug>, falls back to localStorage, then
to stone-warm. Active palette is written to <html data-palette>.
PaletteSwitcher (the round Palette icon at bottom-right) only renders
when import.meta.env.DEV is true — Nicholette can use it to compare
the four side-by-side, but it ships out of the production bundle.

To make a different palette the production default, edit the bare
:root + .dark blocks at the bottom of themes/index.css. That keeps
the runtime swap working as a preview channel and lets us ship a
single hard-coded chrome once she chooses.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:29:48 +02:00
d9f52c6e7b docs: rewrite README for the Earth Walker site
Replaces the boilerplate's generic stack-and-versioning notes with
project-specific deployment guidance: routes, env vars, the
inquiry-delivery flow (so future maintainers understand the Nostr
plumbing isn't optional), image discipline (the anonymity rule plus
the imagemagick incantation that normalizes new assets), and the
add-a-project recipe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:26:18 +02:00
533e94352f feat: real home page — hero, studio voice, project teasers, CTA
Five-section composition that lets the visitor judge the studio in
under one screen plus one scroll: full-bleed living-room hero with
the brand line in Fraunces overlay, a centered Studio paragraph
that picks the brief's own words ("oversized windows … warm woods …
matte black accents … refined but deeply human"), a two-up Selected
work grid linking to Boulder and Asheville, and a closing CTA.

The hero leans on Boulder 08 (mid-century living room with emerald
tile) — it reads as restorative and inviting, the right note for an
above-the-fold image; the Asheville moodiness comes through on the
project teaser card immediately below.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:25:28 +02:00
def88eacad feat: contact form — method selector, zod validation, Nostr delivery
ContactForm wires vee-validate + zod against the inquiry payload:
optional name, required method (Email/WhatsApp/Signal/Telegram/Nostr),
contact value validated per method (email regex, phone-or-handle,
@handle, npub1 prefix), and a 10-2000 char message. On submit the
form calls submitInquiry() from the Nostr feature and toasts the
result — partial relay acceptance still counts as success and is
surfaced to the visitor.

PrivacyBlurb sits above the form explaining the model in plain
language: encrypted in the browser, delivered through Nostr, no
server in between. Lock icon plus terse copy — the goal is to put a
non-Nostr-native visitor at ease without a wall of jargon.

.env.example documents the two build-time vars (VITE_OWNER_NPUB,
VITE_NOSTR_RELAYS) the form depends on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:24:32 +02:00
dd4c87b548 feat: nostr-tools + ephemeral inquiry submission helper
The contact form's submissions land in the client's Nostr inbox
without ever touching a server we control. submitInquiry generates a
throwaway secp256k1 keypair per submission, wraps the payload as a
NIP-17 / NIP-59 gift-wrapped kind:1059 event (NIP-44 v2 encryption
inside), publishes through SimplePool to the configured relays, and
discards the ephemeral key. The visitor leaves no persistent identity.

VITE_OWNER_NPUB (the recipient) and VITE_NOSTR_RELAYS (optional
override of the default damus/nos.lol/relay.nostr.band set) are
read at build time. env.d.ts grows typed declarations so the rest
of the app catches typos at compile time.

The store is the boilerplate feature README's prescribed shape —
SimplePool + relays ref + Promise.allSettled-wrapped publish —
extended only with env-driven relay parsing. submitInquiry returns
{ok, acceptedBy, attempted} so the form can surface partial-relay
failures honestly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:22:47 +02:00
a79f4a32c7 feat: portfolio scaffolding — index + Boulder + Asheville detail pages
Adds the data layer (src/data/projects.ts) holding curated narrative
order, alt text, and feature tags ('hero'|'wide'|'narrow'|'paired')
for both projects, plus the shared ProjectDetail + ProjectImage
components that read from it. Each ProjectImage opens a Dialog
lightbox on click; lazy-loaded by default.

ProjectDetail's editorial scroll honors the feature tag — full-bleed
hero, narrow centered, side-by-side pairs, etc — so the layout
rhythm is driven by data, not template forks. The two project views
are 4-line shims over the shared component.

PortfolioView is a 2-up 4:5 grid of project covers leading to the
detail pages; the cover ratios are deliberate (portrait crops echo
the editorial spread feel). Router adds the 4 new routes plus a
404→home catch-all and reset-on-navigate scroll behavior. ContactView
ships as a stub; the form lands in a follow-up commit alongside the
Nostr submission helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:20:30 +02:00
52cd156e43 fix: align shadcn-vue components with @lucide/vue + drop redundant baseUrl
The shadcn-vue CLI still emits 'lucide-vue-next' imports even though
the package is deprecated upstream; this boilerplate uses @lucide/vue
(the new home). Replace the imports across every generated component
that pulls icons (dialog, dropdown-menu, select, sheet, sonner).

tsconfig.app.json no longer needs the explicit baseUrl='.' I added
earlier — @vue/tsconfig/tsconfig.dom.json already provides the right
default, and TS6 was warning about it. The root tsconfig.json keeps
baseUrl + ignoreDeprecations because the shadcn-vue CLI reads the
root config and needs it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:20:07 +02:00
5fa3e55392 feat: add Boulder project images; strip metadata from all photos
Twelve Boulder originals pulled from the Wix CDN, resized to a 2400px
long-edge cap with mozjpeg-style 4:2:0 sampling at quality 82 — 120MB
of originals collapses to ~6.5MB, well within reason for a static-site
repo. Filenames are sequence-numbered (01.jpg…12.jpg); narrative order
gets curated in projects.ts.

Re-runs the same -strip pass over the five Asheville Signal images.
The original Signal files carried roughly 2x their stripped weight in
embedded metadata; ImageMagick strips EXIF/GPS/timestamp/maker fields
without touching the visible content. Net: anonymity-first defaults
hold for every image the site serves.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:16:24 +02:00
b1dae9d7e8 feat: relocate Asheville project images into public/
Five client-provided Signal images, renamed to sequence-numbered
generic slugs so the filenames don't leak room counts or other
identifying metadata. Narrative order chosen to lead with the
establishing living-room shot (vertical slat wall + picture window)
and close on the dining-table detail.

The original images-to-use/ scratch folder is removed; these are now
the canonical assets for the Asheville project.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:14:14 +02:00
35e5a01331 feat: app shell — header, footer, layout, theme toggle
SiteHeader is sticky with backdrop blur, wordmark left and tracked
uppercase nav right (Portfolio / Contact). Mobile breakpoint
collapses to a Sheet drawer. ThemeToggle drops a class onto <html>
and persists to localStorage; initial value respects the system
prefers-color-scheme.

SiteFooter is intentionally bare: wordmark, single Inquire link,
copyright. No social, no phone, no address — the brief is
anonymity-first.

AppLayout composes the three plus Sonner's Toaster (used by the
contact form later). App.vue now renders AppLayout instead of a bare
RouterView.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:13:48 +02:00
04ae000a12 feat: install shadcn-vue components for the portfolio + form surface
Adds button, card, input, textarea, label, select, form, sheet,
dialog, sonner, separator, aspect-ratio, dropdown-menu — the set
needed for the layout, project lightbox, and contact form.

Hoists baseUrl + paths into the root tsconfig.json so shadcn-vue's
CLI can resolve aliases (it reads the root config, not the app one).
Sets ignoreDeprecations="6.0" since TypeScript 6 warns about baseUrl
even though the CLI still requires it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:12:38 +02:00
2d75a40d7d feat: stone-warm OKLCH palette + Fraunces/Inter typography
Replace the boilerplate's neutral-gray HSL tokens with a stone-warm
OKLCH palette in both light and dark variants. Drop --radius to 0.25rem
for the clean architectural feel the brief asks for ("clean lines,
matte black accents, layered textures").

Pair Fraunces (display) with Inter (UI/body) via Bunny Fonts, exposed
through Tailwind 4 @theme inline as --font-serif / --font-sans. h1-h3
default to Fraunces; everything else uses Inter. Adds an `eyebrow`
utility for the uppercase tracked labels used across editorial portfolio
layouts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:11:01 +02:00
b751e26eec chore: remove boilerplate demo content, fix components.json
Drop the unrecognized `framework` key from components.json (current
shadcn-vue CLI rejects it). Delete the counter store demo and reduce
HomeView to a placeholder; real content lands in a later commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:10:16 +02:00
0a8ef64527 add shadcn-vue MCP server for Claude Code sessions
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 10:44:48 +02:00
0054f3ab80 initial scaffold: vue 3 + vite 8 + shadcn-vue + tailwind 4
Wires pinia, vue-router, vue-i18n, vee-validate/zod, shadcn-vue
(reka-ui), tailwind 4 via @tailwindcss/vite. Sample HomeView
proves i18n + pinia + tailwind. Optional nostr/lnbits feature
folders ship as documentation only — deps install on opt-in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 10:05:39 +02:00