From 7079e32a5fdfb1f7bda0b15034709fb93a7b33fb Mon Sep 17 00:00:00 2001 From: V Date: Sun, 17 May 2026 13:55:41 +0100 Subject: [PATCH] Updated version rollout --- ROUND_3.md | 316 +++++++++++++++++++++++++++ ROUND_4.md | 244 +++++++++++++++++++++ internal/app/handlers_public.go | 12 +- internal/app/handlers_public_test.go | 42 ++++ internal/app/render.go | 8 + web/static/app.js | 37 ++++ web/static/styles.css | 91 ++++++++ web/templates/about.html | 19 +- web/templates/base.html | 117 ++++++++-- web/templates/contact.html | 17 +- web/templates/home.html | 25 ++- web/templates/project.html | 17 +- web/templates/projects.html | 19 +- web/templates/services.html | 19 +- 14 files changed, 928 insertions(+), 55 deletions(-) create mode 100644 ROUND_3.md create mode 100644 ROUND_4.md diff --git a/ROUND_3.md b/ROUND_3.md new file mode 100644 index 0000000..03a51d9 --- /dev/null +++ b/ROUND_3.md @@ -0,0 +1,316 @@ +# Round 3 Plan: Solo Architecture Studio Redesign + +## Context + +The current application is a compact server-rendered Go site with SQLite storage, Tailwind-loaded templates, HTMX navigation, image uploads, a password-protected admin area, project/gallery CRUD, a single editable `site_content` row, and a simple contact request inbox. + +The public interface currently has: + +- Home page with hero, intro statement, and featured projects. +- Projects index. +- Project detail pages with image overlays. +- About page that also contains the only public contact form. +- No dedicated Services page. +- No dedicated Contact page. + +For a small single-person architecture practice, the redesign should not become a full agency website or heavy CMS. The site needs to build trust quickly, show a clear design position, make project work easy to inspect, and collect useful enquiries without making the owner maintain many pages. + +## What From The Recommendation Fits + +- Home page: relevant and already mostly present, but it needs stronger positioning, a clearer call to action, and a better preview of services/process. +- About / Studio page: highly relevant. For a solo practice, this should be one of the strongest trust-building pages because clients are hiring the person, not a large firm. +- Services page: relevant, but should be concise. A long list like architecture, interiors, renovation, consultation, space planning, furniture, project management, and 3D visualization may feel inflated for a one-person studio unless each item is genuinely offered. +- Contact page: relevant. The current form is buried on About, and the form does not pre-qualify leads. + +## What To Avoid + +- Do not create a large multi-page agency structure with too many thin pages. +- Do not add a complex awards/publications system unless the owner has real content to maintain. +- Do not overstate capabilities with every possible design service. +- Do not add map/social integrations until the real contact content exists. +- Do not build a full page builder. The admin should remain simple and opinionated. + +## Proposed Public Site Structure + +### 1. Home + +Purpose: communicate the studio position quickly and send visitors to projects or enquiry. + +Recommended sections: + +- Full-viewport image-led hero, using a real project image when available. +- Positioning line such as "Residential architecture and interiors in London" rather than generic portfolio wording. +- One primary CTA: "Start an enquiry". +- One secondary CTA: "View projects". +- Short studio statement, 2-3 sentences. +- Featured projects, limited to 3-4. +- Compact services preview, 3 service groups maximum. +- Brief process preview, 3 steps maximum. +- Short about preview with portrait and link to Studio. + +Current implementation impact: + +- Keep the existing Home route. +- Rework `home.html` layout and copy fields. +- Continue using featured projects from the existing project model. +- Add admin-editable CTA text/link and positioning fields. + +### 2. Studio + +Purpose: make the solo practitioner credible, specific, and approachable. + +This should replace or rename the current About page to "Studio". The route can remain `/about` for compatibility, but the navigation label should become `Studio`. + +Recommended sections: + +- Portrait or working photo. +- Name, role, and location. +- Short personal narrative. +- Design philosophy. +- Approach/process. +- Credentials, registrations, selected experience, or publications as a simple text block. +- Contact CTA. + +Current implementation impact: + +- Keep the existing about content fields but expand them. +- Add fields for philosophy, approach, credentials, and availability/service area. +- Remove the main contact form from this page or reduce it to a CTA linking to Contact. + +### 3. Services + +Purpose: help visitors self-qualify without making the practice look larger than it is. + +Recommended services for a solo architecture company: + +- Residential architecture. +- Renovation and extensions. +- Interior architecture and spatial planning. +- Early-stage consultation. + +Optional only if accurate: + +- Planning support. +- 3D visualization. +- Furniture and finish selection. +- Project coordination. + +Recommended sections: + +- Service overview. +- 3-4 editable service cards. +- "How projects work" process section. +- Typical timeline ranges. +- Geographic availability. +- Small FAQ. +- CTA to Contact. + +Current implementation impact: + +- Add `GET /services`. +- Add a `services.html` template. +- Add admin support for editable services and FAQs. +- For the first implementation, services can be stored as structured rows rather than hard-coded copy. + +### 4. Contact + +Purpose: create a proper conversion point and collect enough information to assess fit. + +Recommended sections: + +- Contact form. +- Email, phone, location. +- Short expectation statement, for example response time or preferred project types. +- Optional social links. +- Optional map later, not required now. + +Inquiry questionnaire fields: + +- Name. +- Email. +- Phone, optional. +- Project type. +- Location. +- Budget range. +- Timeline. +- Message. + +Current implementation impact: + +- Add `GET /contact`. +- Keep `POST /contact`, but expand the stored fields. +- Update admin contact inbox to show the structured enquiry details. +- Add validation for required fields and length limits. + +## Proposed Navigation + +Use five top-level links: + +- Projects +- Studio +- Services +- Contact + +Home remains the logo link. This keeps navigation focused and avoids a marketing-heavy structure. + +## Backend And Data Plan + +### Site Content + +The existing `site_content` row is good for global editable content, but it needs more fields: + +- `site_name` +- `positioning` +- `hero_cta_label` +- `hero_cta_url` +- `secondary_cta_label` +- `secondary_cta_url` +- `studio_philosophy` +- `studio_approach` +- `studio_credentials` +- `service_area` +- `response_note` + +These can be added through a migration while preserving current data. + +### Services + +Add a `services` table: + +- `id` +- `title` +- `summary` +- `details` +- `position` +- `active` + +This keeps the Services page editable without turning it into a page builder. + +### FAQs + +Add a `faqs` table: + +- `id` +- `question` +- `answer` +- `position` +- `active` + +Use FAQs mainly on Services and optionally Contact. + +### Contact Requests + +Expand `contact_requests`: + +- `phone` +- `project_type` +- `project_location` +- `budget_range` +- `timeline` +- `status` +- `notes` + +`status` can start simple: `new`, `reviewed`, `archived`. `notes` is private admin-only text. + +### Projects + +The current project model is usable. Consider adding only: + +- `summary` for cards and homepage previews. +- `scope` for project detail metadata. +- `status` or `completion_stage` if the studio wants to show built/in progress/concept. +- `position` for manual ordering. + +Manual ordering matters more than created-at ordering for a portfolio. + +## Admin Plan + +Keep the tabbed admin from Round 2 and add two tabs: + +- Main +- Projects +- Studio +- Services +- Contact + +Recommended ownership: + +- Main: home hero, positioning, intro, CTA labels/links, hero image. +- Projects: project CRUD, gallery uploads, featured flag, ordering. +- Studio: portrait, name, role, bio, philosophy, approach, credentials. +- Services: service rows, process copy, FAQ rows. +- Contact: public contact details, enquiry inbox, enquiry status, private notes. + +The admin should stay form-based. Avoid rich text editing in this round unless there is a clear content requirement. + +## Interface Direction + +The current visual direction is image-led, quiet, and minimalist. That fits architecture, but the redesign should make the site feel more intentional and less like a generic template. + +Recommended direction: + +- Use generous image layouts for project pages. +- Keep typography restrained but improve hierarchy. +- Make the homepage less sparse by adding actionable service/process content. +- Use project facts and captions to make work inspectable. +- Keep color neutral, but avoid a flat all-neutral interface by using material-inspired accents such as stone, clay, brass, or muted green in small amounts. +- Do not use decorative cards as the main layout. Use full-width sections, grids, and strong image composition. + +## Implementation Phases + +### Phase 1: Public IA And Contact + +- Add `/contact` page. +- Move the enquiry form there. +- Expand contact request fields. +- Add navigation item. +- Update admin contact inbox. + +This creates immediate business value and fixes the weakest current conversion path. + +### Phase 2: Studio And Home Rework + +- Rename About navigation to Studio. +- Expand Studio content fields. +- Rework homepage sections and CTAs. +- Add home services/process preview using simple editable fields or seeded service rows. + +This improves trust and positioning without a large backend expansion. + +### Phase 3: Services + +- Add `/services`. +- Add services and FAQs tables. +- Add admin Services tab. +- Add process/timeline/geography content. + +This helps visitors self-qualify and reduces repetitive pre-sales explanations. + +### Phase 4: Project Depth + +- Add project summary/scope/status/manual ordering. +- Improve project detail pages with clearer metadata and richer captions. +- Add admin ordering controls. + +This is valuable after the lead path and positioning are stronger. + +## Testing Plan + +- Add route tests for `/`, `/projects`, `/projects/{slug}`, `/about`, `/services`, and `/contact`. +- Add form tests for expanded contact validation and persistence. +- Add migration tests for new columns/tables. +- Add admin tests for new tabs and update actions. +- Manually verify HTMX navigation still works with direct URL fallback. +- Manually verify mobile layouts for Home, Studio, Services, Contact, and Project detail. + +## Open Questions + +- Should the public label be "Studio" while keeping `/about`, or should the route become `/studio` with `/about` redirecting? +- Which services are genuinely offered by the practitioner today? +- Does the studio want to show a phone number publicly, or keep initial contact email/form-only? +- Are there real awards, registrations, or publications to show, or should credentials remain a simple text block? +- Should contact requests trigger email notification, or is the admin inbox enough for now? + +## Recommended Next Step + +Start with Phase 1. A dedicated Contact page with a structured enquiry form is the highest-value backend and interface improvement, and it gives the later Home, Studio, and Services pages a clear conversion destination. diff --git a/ROUND_4.md b/ROUND_4.md new file mode 100644 index 0000000..3201734 --- /dev/null +++ b/ROUND_4.md @@ -0,0 +1,244 @@ +# Round 4 Plan: Public Shell, HTMX Partials, and Smoother Navigation + +## Goal + +Refactor the public interface so `base.html` is the single public shell for header, footer, DaisyUI drawer, overlay root, and the stable main content container. Public pages should render either as a full document on direct visits or as main-content partials during HTMX navigation. + +This keeps the current architecture portfolio style and functionality while making page structure easier to maintain. + +## Current State + +- Public pages call `{{template "site_start" .}}`, then render their own `
`, then call `{{template "site_end" .}}`. +- Public navigation generally uses `hx-boost="true"` with `hx-target="body"` and full `body` replacement. +- The mobile sidebar now uses DaisyUI drawer structure. +- Admin already has a better partial pattern: full page for direct requests, partial panel for HTMX requests, plus out-of-band tab updates. + +## Target Structure + +Use `base.html` as the public shell. + +Recommended template ownership: + +- `base.html` + - `head` + - full public document shell + - header + - desktop nav + - DaisyUI mobile drawer + - footer + - `#main-content` + - `#overlay-root` + - OOB nav fragments +- Page templates + - define only page content partials, for example: + - `home_content` + - `projects_content` + - `project_content` + - `studio_content` + - `services_content` + - `contact_content` + +## Rendering Model + +### Direct Page Load + +Normal browser requests render the full shell: + +```html + + + ... + +
+ ... +
+ page content +
+ ... +
+ + +``` + +### HTMX Navigation + +HTMX requests return: + +- the new `#main-content` element +- out-of-band desktop nav active state +- out-of-band drawer nav active state +- optionally an out-of-band document title update if needed later + +This mirrors the admin partial strategy. + +## HTMX Navigation Changes + +Change public internal navigation from body replacement: + +```html +hx-target="body" +hx-swap="outerHTML transition:true" +``` + +to main-content replacement: + +```html +hx-target="#main-content" +hx-swap="outerHTML transition:true" +hx-push-url="true" +``` + +The header, drawer, footer, and overlay root remain stable across page transitions. + +## Active Navigation + +Because only the main content will swap, active navigation state needs to update separately. + +Use out-of-band fragments: + +- `#site-desktop-nav` +- `#site-drawer-nav` + +The full shell and HTMX partial responses both render nav from the same template definitions, avoiding duplicated active-state logic. + +## Drawer Behavior + +Keep the DaisyUI drawer: + +- `drawer drawer-end` +- `drawer-toggle` +- `drawer-content` +- `drawer-side` +- `drawer-overlay` + +Improvements: + +- Keep the drawer as part of the stable shell, not page content. +- Close drawer after clicking a drawer nav link. +- Close drawer on Escape. +- Keep the matted glass visual: + - `bg-neutral-950/45` + - `backdrop-blur-xl` + - white text + - active item underlined + +## Smoother Transitions + +### Page Content + +Apply transitions to `#main-content`, not the full body. + +Recommended CSS: + +- old content: slight fade out and 4-6px downward motion +- new content: fade in and return to zero offset +- short duration, around 160-220ms +- respect `prefers-reduced-motion` + +The existing View Transition CSS can be adjusted so: + +- `#main-content` has `view-transition-name: main-content` +- root transitions are less dominant or removed for public navigation +- admin panel transitions remain independent + +### Drawer + +DaisyUI handles the structural entry/exit, but add a small custom polish layer if needed: + +- slightly longer transform duration, around 240ms +- eased slide-in/out +- backdrop fade around 180-220ms + +Avoid custom JavaScript animation. Prefer CSS. + +## Backend Changes + +Add a public render helper similar to admin rendering: + +```go +func (s *Server) renderPublic(w http.ResponseWriter, r *http.Request, fullTemplate, partialTemplate string, data pageData) +``` + +Behavior: + +- normal request: render full shell with the correct page content +- HTMX request: render partial content plus OOB nav fragments + +Each public handler should call `renderPublic` instead of `render`. + +## Template Naming Plan + +Suggested full/partial template names: + +- `home.html` full shell, `home_partial.html` +- `projects.html` full shell, `projects_partial.html` +- `project.html` full shell, `project_partial.html` +- `about.html` full shell, `about_partial.html` +- `services.html` full shell, `services_partial.html` +- `contact.html` full shell, `contact_partial.html` + +Alternatively, each page file can define: + +- `page_full` +- `page_partial` +- `page_content` + +Pick one convention and use it everywhere. + +## Preserve Current Functionality + +Must continue working: + +- direct URL visits +- browser back/forward with pushed URLs +- non-JavaScript navigation fallback +- project image overlay HTMX requests +- contact form HTMX submission +- admin tab partial behavior +- health endpoints +- DaisyUI mobile drawer + +## Testing Plan + +Update or add tests for: + +- direct public routes return full HTML document +- HTMX public route requests return partial content, not full document +- HTMX public partial responses include OOB nav fragments +- active nav state updates for each page +- project image overlay still returns only overlay fragment +- contact form submission still returns only `contact_result.html` +- existing admin HTMX tests still pass + +Manual checks: + +- mobile drawer opens/closes on every public page +- drawer closes after nav click +- drawer closes with Escape +- page transitions feel smooth on desktop and mobile +- reduced-motion setting disables meaningful animation +- browser back/forward updates main content and nav state + +## Implementation Order + +1. Refactor `base.html` into the full public shell. +2. Convert one page, preferably Home, to full/partial/content templates. +3. Add `renderPublic`. +4. Convert remaining public pages. +5. Add OOB nav fragments. +6. Update HTMX targets in header, drawer, and internal CTAs. +7. Adjust transition CSS for `#main-content`. +8. Add drawer close-on-link/Escape behavior. +9. Update tests. +10. Run full test suite and manually verify key routes. + +## Risks + +- OOB nav fragments can become noisy if duplicated in every page template. Keep them in `base.html`. +- Contact form and overlay requests should not use `renderPublic`; they should keep returning small fragments. +- Page title will not automatically update if only `#main-content` swaps. This can be handled later with an OOB `` strategy or small JS. +- DaisyUI CDN use is acceptable for now, but a proper Tailwind/DaisyUI build pipeline would be better for production performance and version control. + +## Recommended Scope For Round 4 + +Implement the public shell and main-content HTMX navigation first. Do not refactor admin forms or public content sections in the same round. Keep this round focused on layout architecture, navigation maintainability, and smoother transitions. diff --git a/internal/app/handlers_public.go b/internal/app/handlers_public.go index b8f5da7..b35a9c9 100644 --- a/internal/app/handlers_public.go +++ b/internal/app/handlers_public.go @@ -19,7 +19,7 @@ func (s *Server) home(w http.ResponseWriter, r *http.Request) { s.error(w, err) return } - s.render(w, "home.html", pageData{Title: content.HeroTitle, Active: "home", Content: content, Projects: projects, CurrentPath: r.URL.Path}) + s.renderPublic(w, r, "home.html", "home_partial.html", pageData{Title: content.HeroTitle, Active: "home", Content: content, Projects: projects, CurrentPath: r.URL.Path}) } func (s *Server) projects(w http.ResponseWriter, r *http.Request) { @@ -33,7 +33,7 @@ func (s *Server) projects(w http.ResponseWriter, r *http.Request) { s.error(w, err) return } - s.render(w, "projects.html", pageData{Title: "Projects", Active: "projects", Content: content, Projects: projects, CurrentPath: r.URL.Path}) + s.renderPublic(w, r, "projects.html", "projects_partial.html", pageData{Title: "Projects", Active: "projects", Content: content, Projects: projects, CurrentPath: r.URL.Path}) } func (s *Server) projectDetail(w http.ResponseWriter, r *http.Request) { @@ -51,7 +51,7 @@ func (s *Server) projectDetail(w http.ResponseWriter, r *http.Request) { s.error(w, err) return } - s.render(w, "project.html", pageData{Title: project.Title, Active: "projects", Content: content, Project: project, CurrentPath: r.URL.Path}) + s.renderPublic(w, r, "project.html", "project_partial.html", pageData{Title: project.Title, Active: "projects", Content: content, Project: project, CurrentPath: r.URL.Path}) } func (s *Server) projectImageOverlay(w http.ResponseWriter, r *http.Request) { @@ -78,7 +78,7 @@ func (s *Server) about(w http.ResponseWriter, r *http.Request) { s.error(w, err) return } - s.render(w, "about.html", pageData{Title: "Studio", Active: "about", Content: content, CurrentPath: r.URL.Path}) + s.renderPublic(w, r, "about.html", "about_partial.html", pageData{Title: "Studio", Active: "about", Content: content, CurrentPath: r.URL.Path}) } func (s *Server) services(w http.ResponseWriter, r *http.Request) { @@ -97,7 +97,7 @@ func (s *Server) services(w http.ResponseWriter, r *http.Request) { s.error(w, err) return } - s.render(w, "services.html", pageData{Title: "Services", Active: "services", Content: content, Services: services, FAQs: faqs, CurrentPath: r.URL.Path}) + s.renderPublic(w, r, "services.html", "services_partial.html", pageData{Title: "Services", Active: "services", Content: content, Services: services, FAQs: faqs, CurrentPath: r.URL.Path}) } func (s *Server) contactPage(w http.ResponseWriter, r *http.Request) { @@ -106,7 +106,7 @@ func (s *Server) contactPage(w http.ResponseWriter, r *http.Request) { s.error(w, err) return } - s.render(w, "contact.html", pageData{Title: "Contact", Active: "contact", Content: content, CurrentPath: r.URL.Path}) + s.renderPublic(w, r, "contact.html", "contact_partial.html", pageData{Title: "Contact", Active: "contact", Content: content, CurrentPath: r.URL.Path}) } func (s *Server) contact(w http.ResponseWriter, r *http.Request) { diff --git a/internal/app/handlers_public_test.go b/internal/app/handlers_public_test.go index ea86192..ebdd233 100644 --- a/internal/app/handlers_public_test.go +++ b/internal/app/handlers_public_test.go @@ -24,6 +24,48 @@ func TestPublicRoutes(t *testing.T) { } } +func TestPublicRoutesRenderFullShellOnDirectLoad(t *testing.T) { + srv := newTestServer(t) + req := httptest.NewRequest(http.MethodGet, "/services", nil) + rec := httptest.NewRecorder() + + srv.Routes().ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected ok, got %d", rec.Code) + } + body, _ := io.ReadAll(rec.Result().Body) + text := string(body) + for _, want := range []string{"<!doctype html>", `class="drawer drawer-end"`, `id="main-content"`, `id="site-drawer"`} { + if !strings.Contains(text, want) { + t.Fatalf("direct public route missing %q: %s", want, text) + } + } +} + +func TestPublicHTMXRoutesReturnMainContentPartial(t *testing.T) { + srv := newTestServer(t) + req := httptest.NewRequest(http.MethodGet, "/services", nil) + req.Header.Set("HX-Request", "true") + rec := httptest.NewRecorder() + + srv.Routes().ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected ok, got %d", rec.Code) + } + body, _ := io.ReadAll(rec.Result().Body) + text := string(body) + if strings.Contains(text, "<!doctype html>") || strings.Contains(text, `class="drawer drawer-end"`) { + t.Fatalf("expected partial response, got full document: %s", text) + } + for _, want := range []string{`id="main-content"`, `id="site-header"`, `id="site-drawer-nav"`, `hx-swap-oob="true"`} { + if !strings.Contains(text, want) { + t.Fatalf("HTMX public route missing %q: %s", want, text) + } + } +} + func TestServicesRouteRendersServicesAndFAQs(t *testing.T) { srv := newTestServer(t) req := httptest.NewRequest(http.MethodGet, "/services", nil) diff --git a/internal/app/render.go b/internal/app/render.go index b7685fa..747a6e4 100644 --- a/internal/app/render.go +++ b/internal/app/render.go @@ -25,6 +25,14 @@ func (s *Server) renderAdmin(w http.ResponseWriter, r *http.Request, fullTemplat s.render(w, fullTemplate, data) } +func (s *Server) renderPublic(w http.ResponseWriter, r *http.Request, fullTemplate, partialTemplate string, data pageData) { + if r.Header.Get("HX-Request") == "true" { + s.render(w, partialTemplate, data) + return + } + s.render(w, fullTemplate, data) +} + func (s *Server) error(w http.ResponseWriter, err error) { http.Error(w, err.Error(), http.StatusInternalServerError) } diff --git a/web/static/app.js b/web/static/app.js index 7aeee17..7f394cb 100644 --- a/web/static/app.js +++ b/web/static/app.js @@ -8,6 +8,31 @@ updateHeader(); window.addEventListener("scroll", updateHeader, { passive: true }); + function currentTheme() { + try { + const saved = localStorage.getItem("archi-theme"); + if (saved === "dark" || saved === "light") return saved; + } catch (_) {} + return document.documentElement.getAttribute("data-theme") === "dark" ? "dark" : "light"; + } + + function setTheme(theme) { + const normalized = theme === "dark" ? "dark" : "light"; + document.documentElement.setAttribute("data-theme", normalized); + document.querySelectorAll("[data-theme-toggle]").forEach(function (input) { + input.checked = normalized === "dark"; + }); + try { + localStorage.setItem("archi-theme", normalized); + } catch (_) {} + } + + function syncThemeControls() { + setTheme(currentTheme()); + } + + syncThemeControls(); + function closeOverlay() { const root = document.getElementById("overlay-root"); if (root) root.innerHTML = ""; @@ -25,13 +50,25 @@ }); document.addEventListener("htmx:afterSettle", updateHeader); + document.addEventListener("htmx:afterSettle", syncThemeControls); document.addEventListener("click", function (event) { + if (event.target.closest("[data-drawer-link]")) { + const drawer = document.getElementById("site-drawer"); + if (drawer) drawer.checked = false; + } if (event.target.matches("[data-overlay], [data-overlay-close]")) { closeOverlay(); } }); + document.addEventListener("change", function (event) { + const toggle = event.target.closest("[data-theme-toggle]"); + if (toggle) { + setTheme(toggle.checked ? "dark" : "light"); + } + }); + document.addEventListener("keydown", function (event) { if (event.key === "Escape") { const drawer = document.getElementById("site-drawer"); diff --git a/web/static/styles.css b/web/static/styles.css index 91917b7..ea6ce22 100644 --- a/web/static/styles.css +++ b/web/static/styles.css @@ -6,6 +6,61 @@ body { font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } +html[data-theme="dark"] body, +html[data-theme="dark"] .drawer-content { + background: #0f0f0e; + color: #f5f5f4; +} + +html[data-theme="dark"] .bg-neutral-50 { + background-color: #0f0f0e !important; +} + +html[data-theme="dark"] .bg-white { + background-color: #171716 !important; +} + +html[data-theme="dark"] .bg-neutral-950 { + background-color: #050505 !important; +} + +html[data-theme="dark"] .bg-neutral-200 { + background-color: #2a2926 !important; +} + +html[data-theme="dark"] .text-neutral-950, +html[data-theme="dark"] .text-neutral-800 { + color: #f5f5f4 !important; +} + +html[data-theme="dark"] .text-neutral-700, +html[data-theme="dark"] .text-neutral-600, +html[data-theme="dark"] .text-neutral-500 { + color: #c7c2b8 !important; +} + +html[data-theme="dark"] .text-neutral-400 { + color: #a8a29a !important; +} + +html[data-theme="dark"] .border-neutral-300, +html[data-theme="dark"] .border-neutral-200 { + border-color: rgba(245, 245, 244, 0.16) !important; +} + +html[data-theme="dark"] input, +html[data-theme="dark"] textarea, +html[data-theme="dark"] select { + background-color: #111110; + border-color: rgba(245, 245, 244, 0.22); + color: #f5f5f4; +} + +html[data-theme="dark"] input::placeholder, +html[data-theme="dark"] textarea::placeholder { + color: #78716c; +} + .form-select { appearance: none; border-radius: 0; @@ -30,6 +85,32 @@ body { font-size: 1.25rem; } +html[data-theme="dark"] [data-site-header] { + background: rgba(15, 15, 14, 0.95) !important; + color: #f5f5f4 !important; + box-shadow: 0 1px 0 rgba(245, 245, 244, 0.12); + backdrop-filter: blur(18px); +} + +html[data-theme="dark"] [data-site-header].is-compact { + background: rgba(15, 15, 14, 0.95) !important; + color: #f5f5f4 !important; + box-shadow: 0 1px 0 rgba(245, 245, 244, 0.12); +} + +#main-content { + view-transition-name: main-content; +} + +.drawer-side > aside { + transition-duration: 260ms; + transition-timing-function: cubic-bezier(0.22, 1, 0.36, 1); +} + +.drawer-overlay { + transition-duration: 200ms; +} + @keyframes page-fade-out { from { opacity: 1; @@ -90,6 +171,14 @@ body { animation: 180ms ease both panel-fade-in; } +::view-transition-old(main-content) { + animation: 150ms ease both panel-fade-out; +} + +::view-transition-new(main-content) { + animation: 210ms ease both panel-fade-in; +} + @media (prefers-reduced-motion: reduce) { *, ::before, @@ -99,6 +188,8 @@ body { ::view-transition-old(root), ::view-transition-new(root), + ::view-transition-old(main-content), + ::view-transition-new(main-content), ::view-transition-old(admin-panel), ::view-transition-new(admin-panel) { animation-duration: 1ms; diff --git a/web/templates/about.html b/web/templates/about.html index cb39321..5c0a05c 100644 --- a/web/templates/about.html +++ b/web/templates/about.html @@ -1,5 +1,16 @@ -{{template "site_start" .}} -<main class="mx-auto max-w-7xl px-5 pb-24 pt-32 md:px-8 md:pt-40"> +{{define "about.html"}} +{{template "public_shell_start" .}} +{{template "about_content" .}} +{{template "public_shell_end" .}} +{{end}} + +{{define "about_partial.html"}} +{{template "public_nav_oob" .}} +{{template "about_content" .}} +{{end}} + +{{define "about_content"}} +<main id="main-content" class="mx-auto max-w-7xl px-5 pb-24 pt-32 md:px-8 md:pt-40"> <section class="grid gap-10 md:grid-cols-[0.9fr_1.1fr] md:items-start"> <div class="aspect-[4/5] overflow-hidden bg-neutral-200"> <img src="{{.Content.AboutImage}}" alt="{{.Content.AboutName}}" class="h-full w-full object-cover"> @@ -60,9 +71,9 @@ <h2 class="text-3xl font-semibold">Discuss a project</h2> <div class="max-w-2xl"> <p class="text-lg leading-relaxed text-neutral-600">Share the project type, location, budget range, and timeline so the studio can assess whether the work is a good fit.</p> - <a href="/contact" class="mt-6 inline-flex bg-neutral-950 px-5 py-3 text-sm font-medium uppercase tracking-[0.18em] text-white hover:bg-neutral-700" hx-boost="true" hx-target="body" hx-swap="outerHTML transition:true">Start an enquiry</a> + <a href="/contact" class="mt-6 inline-flex bg-neutral-950 px-5 py-3 text-sm font-medium uppercase tracking-[0.18em] text-white hover:bg-neutral-700" hx-boost="true" hx-target="#main-content" hx-swap="outerHTML transition:true" hx-push-url="true">Start an enquiry</a> </div> </div> </section> </main> -{{template "site_end" .}} +{{end}} diff --git a/web/templates/base.html b/web/templates/base.html index 290aa09..7cb974c 100644 --- a/web/templates/base.html +++ b/web/templates/base.html @@ -5,6 +5,16 @@ <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{{if .Title}}{{.Title}} | {{end}}Archi Folio + @@ -14,7 +24,7 @@ {{end}} -{{define "site_start"}} +{{define "public_shell_start"}} {{template "head" .}}
@@ -23,15 +33,24 @@ {{end}} {{define "site_header"}} -
+ +{{end}} + +{{define "site_header_oob"}} + +{{end}} + +{{define "site_header_inner"}}
- Archi Folio - + Archi Folio +
-
+{{end}} + +{{define "site_desktop_nav"}} + +{{end}} + +{{define "site_desktop_nav_oob"}} + {{end}} {{define "footer"}} @@ -52,7 +88,24 @@ {{end}} -{{define "site_end"}} +{{define "theme_toggle"}} + +{{end}} + +{{define "public_nav_oob"}} +{{template "site_header_oob" .}} +{{template "site_drawer_nav_oob" .}} +{{end}} + +{{define "public_shell_end"}} {{template "footer" .}}
@@ -60,20 +113,18 @@
-{{template "site_end" .}} +{{end}} diff --git a/web/templates/project.html b/web/templates/project.html index 61fa2f4..e62e5eb 100644 --- a/web/templates/project.html +++ b/web/templates/project.html @@ -1,5 +1,16 @@ -{{template "site_start" .}} -
+{{define "project.html"}} +{{template "public_shell_start" .}} +{{template "project_content" .}} +{{template "public_shell_end" .}} +{{end}} + +{{define "project_partial.html"}} +{{template "public_nav_oob" .}} +{{template "project_content" .}} +{{end}} + +{{define "project_content"}} +

{{.Project.Category}} ยท {{.Project.Status}}

@@ -34,4 +45,4 @@
-{{template "site_end" .}} +{{end}} diff --git a/web/templates/projects.html b/web/templates/projects.html index 359a5ed..376be9e 100644 --- a/web/templates/projects.html +++ b/web/templates/projects.html @@ -1,5 +1,16 @@ -{{template "site_start" .}} -
+{{define "projects.html"}} +{{template "public_shell_start" .}} +{{template "projects_content" .}} +{{template "public_shell_end" .}} +{{end}} + +{{define "projects_partial.html"}} +{{template "public_nav_oob" .}} +{{template "projects_content" .}} +{{end}} + +{{define "projects_content"}} +

Selected work

@@ -7,7 +18,7 @@

A visual index of architectural and interior design work.

-
+
{{range .Projects}}
@@ -25,4 +36,4 @@ {{end}}
-{{template "site_end" .}} +{{end}} diff --git a/web/templates/services.html b/web/templates/services.html index fc5a5cd..3e2e453 100644 --- a/web/templates/services.html +++ b/web/templates/services.html @@ -1,5 +1,16 @@ -{{template "site_start" .}} -
+{{define "services.html"}} +{{template "public_shell_start" .}} +{{template "services_content" .}} +{{template "public_shell_end" .}} +{{end}} + +{{define "services_partial.html"}} +{{template "public_nav_oob" .}} +{{template "services_content" .}} +{{end}} + +{{define "services_content"}} +
-{{template "site_end" .}} +{{end}}