From 99cb89d1e8a54e4a3c5e1f7881f2cee4e57931fa Mon Sep 17 00:00:00 2001 From: mifi Date: Sun, 15 Feb 2026 14:10:57 -0300 Subject: [PATCH 1/3] =?UTF-8?q?Svelte=20conversion=20=E2=80=94=20quick=20a?= =?UTF-8?q?nd=20dirty?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .devcontainer/devcontainer.json | 83 +- .gitignore | 5 +- .prettierrc | 22 +- .woodpecker/ci.yaml | 11 + AGENTS.md | 53 + Dockerfile | 2 +- README.md | 93 +- eslint.config.js | 50 +- package.json | 83 +- pnpm-lock.yaml | 2694 ++++++++++++++--- postcss.config.js | 9 + scripts/build.js | 78 - scripts/critical-css.js | 40 + src/app.css | 99 + src/app.d.ts | 13 + src/app.html | 10 + src/assets/css/style.css | 147 - src/assets/js/script.js | 120 - src/index.html | 246 -- src/lib/components/GalleryFigure.svelte | 83 + src/lib/components/SiteHeader.svelte | 30 + src/lib/media.ts | 178 ++ src/routes/+layout.svelte | 5 + src/routes/+layout.ts | 1 + src/routes/+page.svelte | 68 + src/routes/+page.ts | 5 + .../assets/android-chrome-192x192.png | Bin .../assets/android-chrome-512x512.png | Bin {src => static}/assets/apple-touch-icon.png | Bin {src => static}/assets/favicon-16x16.png | Bin {src => static}/assets/favicon-32x32.png | Bin {src => static}/assets/favicon.ico | Bin {src => static}/assets/js/ga-init.js | 0 static/assets/js/script.js | 108 + .../media/desktop/backyard_parking.webp | Bin .../media/desktop/backyard_parking@1x.webp | Bin .../assets/media/desktop/bedroom_suite_1.webp | Bin .../media/desktop/bedroom_suite_1@1x.webp | Bin .../assets/media/desktop/bedroom_suite_2.webp | Bin .../media/desktop/bedroom_suite_2@1x.webp | Bin .../assets/media/desktop/bedroom_suite_3.webp | Bin .../media/desktop/bedroom_suite_3@1x.webp | Bin .../assets/media/desktop/coat_closet.webp | Bin .../assets/media/desktop/coat_closet@1x.webp | Bin .../assets/media/desktop/deck_1.webp | Bin .../assets/media/desktop/deck_1@1x.webp | Bin .../assets/media/desktop/deck_2.webp | Bin .../assets/media/desktop/deck_2@1x.webp | Bin .../assets/media/desktop/exterior.webp | Bin .../assets/media/desktop/exterior@1x.webp | Bin .../assets/media/desktop/guest_bath.webp | Bin .../assets/media/desktop/guest_bath@1x.webp | Bin .../assets/media/desktop/kitchen.webp | Bin .../assets/media/desktop/kitchen@1x.webp | Bin .../assets/media/desktop/laundry.webp | Bin .../assets/media/desktop/laundry@1x.webp | Bin .../assets/media/desktop/living_room_1.webp | Bin .../media/desktop/living_room_1@1x.webp | Bin .../assets/media/desktop/living_room_2.webp | Bin .../media/desktop/living_room_2@1x.webp | Bin .../media/desktop/office_fitness_guest_1.webp | Bin .../desktop/office_fitness_guest_1@1x.webp | Bin .../media/desktop/office_fitness_guest_2.webp | Bin .../desktop/office_fitness_guest_2@1x.webp | Bin .../media/desktop/office_fitness_guest_3.webp | Bin .../desktop/office_fitness_guest_3@1x.webp | Bin .../media/desktop/office_fitness_guest_4.webp | Bin .../desktop/office_fitness_guest_4@1x.webp | Bin .../assets/media/desktop/onsuite_1.webp | Bin .../assets/media/desktop/onsuite_1@1x.webp | Bin .../assets/media/desktop/onsuite_2.webp | Bin .../assets/media/desktop/onsuite_2@1x.webp | Bin .../assets/media/desktop/tour_still.webp | Bin .../assets/media/desktop/tour_still@1x.webp | Bin .../assets/media/mobile/backyard_parking.webp | Bin .../media/mobile/backyard_parking@1x.webp | Bin .../media/mobile/backyard_parking_mobile.webp | Bin .../assets/media/mobile/bedroom_suite_1.webp | Bin .../media/mobile/bedroom_suite_1@1x.webp | Bin .../media/mobile/bedroom_suite_1_mobile.webp | Bin .../assets/media/mobile/bedroom_suite_2.webp | Bin .../media/mobile/bedroom_suite_2@1x.webp | Bin .../media/mobile/bedroom_suite_2_mobile.webp | Bin .../assets/media/mobile/bedroom_suite_3.webp | Bin .../media/mobile/bedroom_suite_3@1x.webp | Bin .../media/mobile/bedroom_suite_3_mobile.webp | Bin .../assets/media/mobile/coat_closet.webp | Bin .../assets/media/mobile/coat_closet@1x.webp | Bin .../media/mobile/coat_closet_mobile.webp | Bin .../assets/media/mobile/deck_1.webp | Bin .../assets/media/mobile/deck_1@1x.webp | Bin .../assets/media/mobile/deck_1_mobile.webp | Bin .../assets/media/mobile/deck_2.webp | Bin .../assets/media/mobile/deck_2@1x.webp | Bin .../assets/media/mobile/deck_2_mobile.webp | Bin .../assets/media/mobile/exterior.webp | Bin .../assets/media/mobile/exterior@1x.webp | Bin .../assets/media/mobile/exterior_mobile.webp | Bin .../assets/media/mobile/guest_bath.webp | Bin .../assets/media/mobile/guest_bath@1x.webp | Bin .../media/mobile/guest_bath_mobile.webp | Bin .../assets/media/mobile/kitchen.webp | Bin .../assets/media/mobile/kitchen@1x.webp | Bin .../assets/media/mobile/kitchen_mobile.webp | Bin .../assets/media/mobile/laundry.webp | Bin .../assets/media/mobile/laundry@1x.webp | Bin .../assets/media/mobile/laundry_mobile.webp | Bin .../assets/media/mobile/living_room_1.webp | Bin .../assets/media/mobile/living_room_1@1x.webp | Bin .../media/mobile/living_room_1_mobile.webp | Bin .../assets/media/mobile/living_room_2.webp | Bin .../assets/media/mobile/living_room_2@1x.webp | Bin .../media/mobile/living_room_2_mobile.webp | Bin .../media/mobile/office_fitness_guest_1.webp | Bin .../mobile/office_fitness_guest_1@1x.webp | Bin .../mobile/office_fitness_guest_1_mobile.webp | Bin .../media/mobile/office_fitness_guest_2.webp | Bin .../mobile/office_fitness_guest_2@1x.webp | Bin .../mobile/office_fitness_guest_2_mobile.webp | Bin .../media/mobile/office_fitness_guest_3.webp | Bin .../mobile/office_fitness_guest_3@1x.webp | Bin .../mobile/office_fitness_guest_3_mobile.webp | Bin .../media/mobile/office_fitness_guest_4.webp | Bin .../mobile/office_fitness_guest_4@1x.webp | Bin .../mobile/office_fitness_guest_4_mobile.webp | Bin .../assets/media/mobile/onsuite_1.webp | Bin .../assets/media/mobile/onsuite_1@1x.webp | Bin .../assets/media/mobile/onsuite_1_mobile.webp | Bin .../assets/media/mobile/onsuite_2.webp | Bin .../assets/media/mobile/onsuite_2@1x.webp | Bin .../assets/media/mobile/onsuite_2_mobile.webp | Bin .../assets/media/mobile/tour_still.webp | Bin .../assets/media/mobile/tour_still@1x.webp | Bin .../assets/media/tablet/backyard_parking.webp | Bin .../media/tablet/backyard_parking@1x.webp | Bin .../assets/media/tablet/bedroom_suite_1.webp | Bin .../media/tablet/bedroom_suite_1@1x.webp | Bin .../assets/media/tablet/bedroom_suite_2.webp | Bin .../media/tablet/bedroom_suite_2@1x.webp | Bin .../assets/media/tablet/bedroom_suite_3.webp | Bin .../media/tablet/bedroom_suite_3@1x.webp | Bin .../assets/media/tablet/coat_closet.webp | Bin .../assets/media/tablet/coat_closet@1x.webp | Bin .../assets/media/tablet/deck_1.webp | Bin .../assets/media/tablet/deck_1@1x.webp | Bin .../assets/media/tablet/deck_2.webp | Bin .../assets/media/tablet/deck_2@1x.webp | Bin .../assets/media/tablet/exterior.webp | Bin .../assets/media/tablet/exterior@1x.webp | Bin .../assets/media/tablet/guest_bath.webp | Bin .../assets/media/tablet/guest_bath@1x.webp | Bin .../assets/media/tablet/kitchen.webp | Bin .../assets/media/tablet/kitchen@1x.webp | Bin .../assets/media/tablet/laundry.webp | Bin .../assets/media/tablet/laundry@1x.webp | Bin .../assets/media/tablet/living_room_1.webp | Bin .../assets/media/tablet/living_room_1@1x.webp | Bin .../assets/media/tablet/living_room_2.webp | Bin .../assets/media/tablet/living_room_2@1x.webp | Bin .../media/tablet/office_fitness_guest_1.webp | Bin .../tablet/office_fitness_guest_1@1x.webp | Bin .../media/tablet/office_fitness_guest_2.webp | Bin .../tablet/office_fitness_guest_2@1x.webp | Bin .../media/tablet/office_fitness_guest_3.webp | Bin .../tablet/office_fitness_guest_3@1x.webp | Bin .../media/tablet/office_fitness_guest_4.webp | Bin .../tablet/office_fitness_guest_4@1x.webp | Bin .../assets/media/tablet/onsuite_1.webp | Bin .../assets/media/tablet/onsuite_1@1x.webp | Bin .../assets/media/tablet/onsuite_2.webp | Bin .../assets/media/tablet/onsuite_2@1x.webp | Bin .../assets/media/tablet/tour_still.webp | Bin .../assets/media/tablet/tour_still@1x.webp | Bin .../media/thumbnail/backyard_parking.webp | Bin .../media/thumbnail/bedroom_suite_1.webp | Bin .../media/thumbnail/bedroom_suite_2.webp | Bin .../media/thumbnail/bedroom_suite_3.webp | Bin .../assets/media/thumbnail/coat_closet.webp | Bin .../assets/media/thumbnail/deck_1.webp | Bin .../assets/media/thumbnail/deck_2.webp | Bin .../assets/media/thumbnail/exterior.webp | Bin .../assets/media/thumbnail/guest_bath.webp | Bin .../assets/media/thumbnail/kitchen.webp | Bin .../assets/media/thumbnail/laundry.webp | Bin .../assets/media/thumbnail/living_room_1.webp | Bin .../assets/media/thumbnail/living_room_2.webp | Bin .../thumbnail/office_fitness_guest_1.webp | Bin .../thumbnail/office_fitness_guest_2.webp | Bin .../thumbnail/office_fitness_guest_3.webp | Bin .../thumbnail/office_fitness_guest_4.webp | Bin .../assets/media/thumbnail/onsuite_1.webp | Bin .../assets/media/thumbnail/onsuite_2.webp | Bin .../assets/media/thumbnail/tour.webp | Bin {src => static}/assets/media/videos/tour.mp4 | 0 static/robots.txt | 2 + svelte.config.js | 27 + tsconfig.json | 14 + vite.config.ts | 6 + 198 files changed, 3192 insertions(+), 1193 deletions(-) create mode 100644 AGENTS.md create mode 100644 postcss.config.js delete mode 100644 scripts/build.js create mode 100644 scripts/critical-css.js create mode 100644 src/app.css create mode 100644 src/app.d.ts create mode 100644 src/app.html delete mode 100644 src/assets/css/style.css delete mode 100644 src/assets/js/script.js delete mode 100644 src/index.html create mode 100644 src/lib/components/GalleryFigure.svelte create mode 100644 src/lib/components/SiteHeader.svelte create mode 100644 src/lib/media.ts create mode 100644 src/routes/+layout.svelte create mode 100644 src/routes/+layout.ts create mode 100644 src/routes/+page.svelte create mode 100644 src/routes/+page.ts rename {src => static}/assets/android-chrome-192x192.png (100%) rename {src => static}/assets/android-chrome-512x512.png (100%) rename {src => static}/assets/apple-touch-icon.png (100%) rename {src => static}/assets/favicon-16x16.png (100%) rename {src => static}/assets/favicon-32x32.png (100%) rename {src => static}/assets/favicon.ico (100%) rename {src => static}/assets/js/ga-init.js (100%) create mode 100644 static/assets/js/script.js rename {src => static}/assets/media/desktop/backyard_parking.webp (100%) rename {src => static}/assets/media/desktop/backyard_parking@1x.webp (100%) rename {src => static}/assets/media/desktop/bedroom_suite_1.webp (100%) rename {src => static}/assets/media/desktop/bedroom_suite_1@1x.webp (100%) rename {src => static}/assets/media/desktop/bedroom_suite_2.webp (100%) rename {src => static}/assets/media/desktop/bedroom_suite_2@1x.webp (100%) rename {src => static}/assets/media/desktop/bedroom_suite_3.webp (100%) rename {src => static}/assets/media/desktop/bedroom_suite_3@1x.webp (100%) rename {src => static}/assets/media/desktop/coat_closet.webp (100%) rename {src => static}/assets/media/desktop/coat_closet@1x.webp (100%) rename {src => static}/assets/media/desktop/deck_1.webp (100%) rename {src => static}/assets/media/desktop/deck_1@1x.webp (100%) rename {src => static}/assets/media/desktop/deck_2.webp (100%) rename {src => static}/assets/media/desktop/deck_2@1x.webp (100%) rename {src => static}/assets/media/desktop/exterior.webp (100%) rename {src => static}/assets/media/desktop/exterior@1x.webp (100%) rename {src => static}/assets/media/desktop/guest_bath.webp (100%) rename {src => static}/assets/media/desktop/guest_bath@1x.webp (100%) rename {src => static}/assets/media/desktop/kitchen.webp (100%) rename {src => static}/assets/media/desktop/kitchen@1x.webp (100%) rename {src => static}/assets/media/desktop/laundry.webp (100%) rename {src => static}/assets/media/desktop/laundry@1x.webp (100%) rename {src => static}/assets/media/desktop/living_room_1.webp (100%) rename {src => static}/assets/media/desktop/living_room_1@1x.webp (100%) rename {src => static}/assets/media/desktop/living_room_2.webp (100%) rename {src => static}/assets/media/desktop/living_room_2@1x.webp (100%) rename {src => static}/assets/media/desktop/office_fitness_guest_1.webp (100%) rename {src => static}/assets/media/desktop/office_fitness_guest_1@1x.webp (100%) rename {src => static}/assets/media/desktop/office_fitness_guest_2.webp (100%) rename {src => static}/assets/media/desktop/office_fitness_guest_2@1x.webp (100%) rename {src => static}/assets/media/desktop/office_fitness_guest_3.webp (100%) rename {src => static}/assets/media/desktop/office_fitness_guest_3@1x.webp (100%) rename {src => static}/assets/media/desktop/office_fitness_guest_4.webp (100%) rename {src => static}/assets/media/desktop/office_fitness_guest_4@1x.webp (100%) rename {src => static}/assets/media/desktop/onsuite_1.webp (100%) rename {src => static}/assets/media/desktop/onsuite_1@1x.webp (100%) rename {src => static}/assets/media/desktop/onsuite_2.webp (100%) rename {src => static}/assets/media/desktop/onsuite_2@1x.webp (100%) rename {src => static}/assets/media/desktop/tour_still.webp (100%) rename {src => static}/assets/media/desktop/tour_still@1x.webp (100%) rename {src => static}/assets/media/mobile/backyard_parking.webp (100%) rename {src => static}/assets/media/mobile/backyard_parking@1x.webp (100%) rename {src => static}/assets/media/mobile/backyard_parking_mobile.webp (100%) rename {src => static}/assets/media/mobile/bedroom_suite_1.webp (100%) rename {src => static}/assets/media/mobile/bedroom_suite_1@1x.webp (100%) rename {src => static}/assets/media/mobile/bedroom_suite_1_mobile.webp (100%) rename {src => static}/assets/media/mobile/bedroom_suite_2.webp (100%) rename {src => static}/assets/media/mobile/bedroom_suite_2@1x.webp (100%) rename {src => static}/assets/media/mobile/bedroom_suite_2_mobile.webp (100%) rename {src => static}/assets/media/mobile/bedroom_suite_3.webp (100%) rename {src => static}/assets/media/mobile/bedroom_suite_3@1x.webp (100%) rename {src => static}/assets/media/mobile/bedroom_suite_3_mobile.webp (100%) rename {src => static}/assets/media/mobile/coat_closet.webp (100%) rename {src => static}/assets/media/mobile/coat_closet@1x.webp (100%) rename {src => static}/assets/media/mobile/coat_closet_mobile.webp (100%) rename {src => static}/assets/media/mobile/deck_1.webp (100%) rename {src => static}/assets/media/mobile/deck_1@1x.webp (100%) rename {src => static}/assets/media/mobile/deck_1_mobile.webp (100%) rename {src => static}/assets/media/mobile/deck_2.webp (100%) rename {src => static}/assets/media/mobile/deck_2@1x.webp (100%) rename {src => static}/assets/media/mobile/deck_2_mobile.webp (100%) rename {src => static}/assets/media/mobile/exterior.webp (100%) rename {src => static}/assets/media/mobile/exterior@1x.webp (100%) rename {src => static}/assets/media/mobile/exterior_mobile.webp (100%) rename {src => static}/assets/media/mobile/guest_bath.webp (100%) rename {src => static}/assets/media/mobile/guest_bath@1x.webp (100%) rename {src => static}/assets/media/mobile/guest_bath_mobile.webp (100%) rename {src => static}/assets/media/mobile/kitchen.webp (100%) rename {src => static}/assets/media/mobile/kitchen@1x.webp (100%) rename {src => static}/assets/media/mobile/kitchen_mobile.webp (100%) rename {src => static}/assets/media/mobile/laundry.webp (100%) rename {src => static}/assets/media/mobile/laundry@1x.webp (100%) rename {src => static}/assets/media/mobile/laundry_mobile.webp (100%) rename {src => static}/assets/media/mobile/living_room_1.webp (100%) rename {src => static}/assets/media/mobile/living_room_1@1x.webp (100%) rename {src => static}/assets/media/mobile/living_room_1_mobile.webp (100%) rename {src => static}/assets/media/mobile/living_room_2.webp (100%) rename {src => static}/assets/media/mobile/living_room_2@1x.webp (100%) rename {src => static}/assets/media/mobile/living_room_2_mobile.webp (100%) rename {src => static}/assets/media/mobile/office_fitness_guest_1.webp (100%) rename {src => static}/assets/media/mobile/office_fitness_guest_1@1x.webp (100%) rename {src => static}/assets/media/mobile/office_fitness_guest_1_mobile.webp (100%) rename {src => static}/assets/media/mobile/office_fitness_guest_2.webp (100%) rename {src => static}/assets/media/mobile/office_fitness_guest_2@1x.webp (100%) rename {src => static}/assets/media/mobile/office_fitness_guest_2_mobile.webp (100%) rename {src => static}/assets/media/mobile/office_fitness_guest_3.webp (100%) rename {src => static}/assets/media/mobile/office_fitness_guest_3@1x.webp (100%) rename {src => static}/assets/media/mobile/office_fitness_guest_3_mobile.webp (100%) rename {src => static}/assets/media/mobile/office_fitness_guest_4.webp (100%) rename {src => static}/assets/media/mobile/office_fitness_guest_4@1x.webp (100%) rename {src => static}/assets/media/mobile/office_fitness_guest_4_mobile.webp (100%) rename {src => static}/assets/media/mobile/onsuite_1.webp (100%) rename {src => static}/assets/media/mobile/onsuite_1@1x.webp (100%) rename {src => static}/assets/media/mobile/onsuite_1_mobile.webp (100%) rename {src => static}/assets/media/mobile/onsuite_2.webp (100%) rename {src => static}/assets/media/mobile/onsuite_2@1x.webp (100%) rename {src => static}/assets/media/mobile/onsuite_2_mobile.webp (100%) rename {src => static}/assets/media/mobile/tour_still.webp (100%) rename {src => static}/assets/media/mobile/tour_still@1x.webp (100%) rename {src => static}/assets/media/tablet/backyard_parking.webp (100%) rename {src => static}/assets/media/tablet/backyard_parking@1x.webp (100%) rename {src => static}/assets/media/tablet/bedroom_suite_1.webp (100%) rename {src => static}/assets/media/tablet/bedroom_suite_1@1x.webp (100%) rename {src => static}/assets/media/tablet/bedroom_suite_2.webp (100%) rename {src => static}/assets/media/tablet/bedroom_suite_2@1x.webp (100%) rename {src => static}/assets/media/tablet/bedroom_suite_3.webp (100%) rename {src => static}/assets/media/tablet/bedroom_suite_3@1x.webp (100%) rename {src => static}/assets/media/tablet/coat_closet.webp (100%) rename {src => static}/assets/media/tablet/coat_closet@1x.webp (100%) rename {src => static}/assets/media/tablet/deck_1.webp (100%) rename {src => static}/assets/media/tablet/deck_1@1x.webp (100%) rename {src => static}/assets/media/tablet/deck_2.webp (100%) rename {src => static}/assets/media/tablet/deck_2@1x.webp (100%) rename {src => static}/assets/media/tablet/exterior.webp (100%) rename {src => static}/assets/media/tablet/exterior@1x.webp (100%) rename {src => static}/assets/media/tablet/guest_bath.webp (100%) rename {src => static}/assets/media/tablet/guest_bath@1x.webp (100%) rename {src => static}/assets/media/tablet/kitchen.webp (100%) rename {src => static}/assets/media/tablet/kitchen@1x.webp (100%) rename {src => static}/assets/media/tablet/laundry.webp (100%) rename {src => static}/assets/media/tablet/laundry@1x.webp (100%) rename {src => static}/assets/media/tablet/living_room_1.webp (100%) rename {src => static}/assets/media/tablet/living_room_1@1x.webp (100%) rename {src => static}/assets/media/tablet/living_room_2.webp (100%) rename {src => static}/assets/media/tablet/living_room_2@1x.webp (100%) rename {src => static}/assets/media/tablet/office_fitness_guest_1.webp (100%) rename {src => static}/assets/media/tablet/office_fitness_guest_1@1x.webp (100%) rename {src => static}/assets/media/tablet/office_fitness_guest_2.webp (100%) rename {src => static}/assets/media/tablet/office_fitness_guest_2@1x.webp (100%) rename {src => static}/assets/media/tablet/office_fitness_guest_3.webp (100%) rename {src => static}/assets/media/tablet/office_fitness_guest_3@1x.webp (100%) rename {src => static}/assets/media/tablet/office_fitness_guest_4.webp (100%) rename {src => static}/assets/media/tablet/office_fitness_guest_4@1x.webp (100%) rename {src => static}/assets/media/tablet/onsuite_1.webp (100%) rename {src => static}/assets/media/tablet/onsuite_1@1x.webp (100%) rename {src => static}/assets/media/tablet/onsuite_2.webp (100%) rename {src => static}/assets/media/tablet/onsuite_2@1x.webp (100%) rename {src => static}/assets/media/tablet/tour_still.webp (100%) rename {src => static}/assets/media/tablet/tour_still@1x.webp (100%) rename {src => static}/assets/media/thumbnail/backyard_parking.webp (100%) rename {src => static}/assets/media/thumbnail/bedroom_suite_1.webp (100%) rename {src => static}/assets/media/thumbnail/bedroom_suite_2.webp (100%) rename {src => static}/assets/media/thumbnail/bedroom_suite_3.webp (100%) rename {src => static}/assets/media/thumbnail/coat_closet.webp (100%) rename {src => static}/assets/media/thumbnail/deck_1.webp (100%) rename {src => static}/assets/media/thumbnail/deck_2.webp (100%) rename {src => static}/assets/media/thumbnail/exterior.webp (100%) rename {src => static}/assets/media/thumbnail/guest_bath.webp (100%) rename {src => static}/assets/media/thumbnail/kitchen.webp (100%) rename {src => static}/assets/media/thumbnail/laundry.webp (100%) rename {src => static}/assets/media/thumbnail/living_room_1.webp (100%) rename {src => static}/assets/media/thumbnail/living_room_2.webp (100%) rename {src => static}/assets/media/thumbnail/office_fitness_guest_1.webp (100%) rename {src => static}/assets/media/thumbnail/office_fitness_guest_2.webp (100%) rename {src => static}/assets/media/thumbnail/office_fitness_guest_3.webp (100%) rename {src => static}/assets/media/thumbnail/office_fitness_guest_4.webp (100%) rename {src => static}/assets/media/thumbnail/onsuite_1.webp (100%) rename {src => static}/assets/media/thumbnail/onsuite_2.webp (100%) rename {src => static}/assets/media/thumbnail/tour.webp (100%) rename {src => static}/assets/media/videos/tour.mp4 (100%) create mode 100644 static/robots.txt create mode 100644 svelte.config.js create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 87495b8..39cd0ac 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,40 +1,47 @@ { - "name": "Armandine", - "image": "mcr.microsoft.com/devcontainers/javascript-node:1-22-bookworm", - "features": { - "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {} - }, - "postCreateCommand": "corepack enable && corepack prepare pnpm@10.29.2 --activate && pnpm install", - "forwardPorts": [3000, 80], - "portsAttributes": { - "3000": { - "label": "Static site (pnpm serve)", - "onAutoForward": "notify" - }, - "80": { - "label": "Nginx (when running container)", - "onAutoForward": "silent" - } - }, - "customizations": { - "vscode": { - "extensions": [ - "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode", - "stylelint.vscode-stylelint" - ], - "settings": { - "editor.formatOnSave": true, - "editor.defaultFormatter": "esbenp.prettier-vscode", - "[css]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "[html]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "editor.codeActionsOnSave": { - "source.fixAll.eslint": "explicit", - "source.fixAll.stylelint": "explicit" - } - } - } - }, - "remoteUser": "node" + "name": "Armandine", + "image": "mcr.microsoft.com/devcontainers/javascript-node:1-22-bookworm", + "features": { + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {} + }, + "postCreateCommand": "corepack enable && corepack prepare pnpm@10.29.2 --activate && pnpm install", + "forwardPorts": [5173, 4173, 80], + "portsAttributes": { + "80": { + "label": "Nginx (when running container)", + "onAutoForward": "silent" + }, + "4173": { + "label": "Preview build (pnpm preview)", + "onAutoForward": "silent" + }, + "5173": { + "label": "SvelteKit dev (pnpm dev)", + "onAutoForward": "notify" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "stylelint.vscode-stylelint", + "svelte.svelte-vscode" + ], + "settings": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "[css]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[svelte]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[html]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", + "source.fixAll.stylelint": "explicit" + } + } + } + }, + "remoteUser": "node" } diff --git a/.gitignore b/.gitignore index f626f0d..6786e42 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,10 @@ Thumbs.db *.swo *~ -# Build output (if added later) +# SvelteKit +.svelte-kit + +# Build output dist build .next diff --git a/.prettierrc b/.prettierrc index 81507a8..8887ffa 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,15 +1,11 @@ { - "semi": true, - "singleQuote": true, - "tabWidth": 4, - "trailingComma": "all", - "overrides": [ - { - "files": "*.yml", - "options": { - "tabWidth": 4, - "proseWrap": "preserve" - } - } - ] + "semi": true, + "singleQuote": true, + "tabWidth": 4, + "trailingComma": "all", + "plugins": ["prettier-plugin-svelte"], + "overrides": [ + { "files": "*.yml", "options": { "tabWidth": 4, "proseWrap": "preserve" } }, + { "files": "*.svelte", "options": { "parser": "svelte" } } + ] } diff --git a/.woodpecker/ci.yaml b/.woodpecker/ci.yaml index d13bcea..b29bc54 100644 --- a/.woodpecker/ci.yaml +++ b/.woodpecker/ci.yaml @@ -50,6 +50,16 @@ steps: depends_on: - install + - name: check + image: node:22-alpine + commands: + - corepack enable + - corepack prepare pnpm@10.29.2 --activate + - pnpm install --frozen-lockfile + - pnpm check + depends_on: + - install + - name: Send Prettier Status Notification (failure) image: curlimages/curl environment: @@ -85,5 +95,6 @@ steps: - install - lint - format check + - check when: - status: [success] diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..d31d8c5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,53 @@ +# AGENTS.md – guidance for LLM agents + +This file helps LLM agents work with the Armandine codebase without introducing antipatterns. + +## Stack and goals + +- **Svelte 5** + **SvelteKit** with **TypeScript**. The site is a **single pre-rendered page** (no SSR, no backend). +- **adapter-static**: the app is built to static HTML/JS/CSS in `build/`. The gallery is **rendered at build time** from `src/lib/media.ts`; the HTML already contains the full gallery. Lightbox and theme toggle are handled by a **client script** (`static/assets/js/script.js`) that binds to the pre-rendered DOM. +- **PostCSS**: nesting and CSS level 2; component-scoped ` diff --git a/src/lib/components/SiteHeader.svelte b/src/lib/components/SiteHeader.svelte new file mode 100644 index 0000000..5afe330 --- /dev/null +++ b/src/lib/components/SiteHeader.svelte @@ -0,0 +1,30 @@ + + + diff --git a/src/lib/media.ts b/src/lib/media.ts new file mode 100644 index 0000000..8636f9f --- /dev/null +++ b/src/lib/media.ts @@ -0,0 +1,178 @@ +export interface MediaItem { + type: 'image' | 'video'; + name: string; + caption: string; + alt: string; + height?: number; + width?: number; + loading?: 'lazy' | 'eager'; + fetchpriority?: 'high' | 'low' | 'auto'; +} + +export const mediaItems: MediaItem[] = [ + { + type: 'image', + name: 'living_room_1', + caption: 'An inviting blend of comfort and curated art—relaxation guaranteed.', + alt: 'Sunny living room with stylish seating and vibrant artwork.', + height: 200, + width: 300, + loading: 'eager', + fetchpriority: 'high' + }, + { + type: 'image', + name: 'living_room_2', + caption: 'Relaxation elevated—your stylish living space awaits.', + alt: 'Spacious living area featuring elegant furniture and tasteful decor.', + height: 200, + width: 300, + fetchpriority: 'high' + }, + { + type: 'image', + name: 'kitchen', + caption: 'The culinary stage is set—snacking encouraged, style required.', + alt: 'Modern kitchen showcasing sleek appliances and contemporary design.', + height: 200, + width: 300 + }, + { + type: 'image', + name: 'bedroom_suite_1', + caption: 'A bedroom suite designed to make snoozing irresistible.', + alt: 'Inviting bedroom suite with cozy bedding and warm lighting.', + height: 200, + width: 300 + }, + { + type: 'image', + name: 'bedroom_suite_2', + caption: 'Style meets comfort—sleeping in has never been easier.', + alt: 'Comfortable bedroom suite with elegant decor and soft tones.', + height: 200, + width: 300 + }, + { + type: 'image', + name: 'bedroom_suite_3', + caption: 'Where dreams get stylish—a bedroom that feels like home.', + alt: 'Welcoming bedroom with soothing colors and inviting ambiance.', + height: 200, + width: 300 + }, + { + type: 'image', + name: 'guest_bath', + caption: 'Your personal spa experience—right down the hall.', + alt: 'Sophisticated guest bathroom with modern fixtures and clean lines.', + height: 450, + width: 300 + }, + { + type: 'image', + name: 'onsuite_1', + caption: 'Luxury meets practicality—your private ensuite awaits.', + alt: 'Private ensuite bathroom featuring contemporary design and premium finishes.', + height: 450, + width: 300, + loading: 'eager', + fetchpriority: 'high' + }, + { + type: 'image', + name: 'onsuite_2', + caption: 'Everyday luxury, right at home—your ensuite oasis.', + alt: 'Elegant ensuite with sleek fixtures and stylish decor.', + height: 200, + width: 300 + }, + { + type: 'image', + name: 'laundry', + caption: 'Laundry day reimagined—functional never looked so good.', + alt: 'Modern laundry room with washer, dryer, and organized storage.', + height: 450, + width: 300 + }, + { + type: 'image', + name: 'coat_closet', + caption: 'Organized and chic—your entryway\'s best friend.', + alt: 'Convenient coat closet with tidy storage solutions.', + height: 200, + width: 300 + }, + { + type: 'image', + name: 'deck_1', + caption: 'Outdoor comfort, just steps away—morning coffee optional.', + alt: 'Sunny deck with cozy seating and pleasant outdoor views.', + height: 450, + width: 300 + }, + { + type: 'image', + name: 'deck_2', + caption: 'Your fresh-air escape—ideal for relaxing evenings.', + alt: 'Comfortable deck area perfect for unwinding or entertaining.', + height: 200, + width: 300 + }, + { + type: 'image', + name: 'exterior', + caption: 'Curb appeal perfected—your new favorite place starts here.', + alt: 'Attractive home exterior with inviting architecture.', + height: 200, + width: 300 + }, + { + type: 'image', + name: 'backyard_parking', + caption: 'Convenience meets privacy—your personal backyard parking spot.', + alt: 'Private backyard parking area offering secure convenience.', + height: 200, + width: 300 + }, + { + type: 'image', + name: 'office_fitness_guest_1', + caption: 'Productivity zone meets fitness corner—multitasking done right.', + alt: 'Dual-purpose room featuring office setup and fitness equipment.', + height: 200, + width: 300 + }, + { + type: 'image', + name: 'office_fitness_guest_2', + caption: 'Work, workout, or unwind—the room of endless possibilities.', + alt: 'Versatile office and fitness area with modern amenities.', + height: 200, + width: 300 + }, + { + type: 'image', + name: 'office_fitness_guest_3', + caption: 'Stay focused or get fit—you decide.', + alt: 'Functional space combining a workspace and home fitness area.', + height: 200, + width: 300 + }, + { + type: 'image', + name: 'office_fitness_guest_4', + caption: 'Room for every routine—your workspace meets wellness.', + alt: 'Stylish office area seamlessly integrated with fitness features.', + height: 200, + width: 300 + }, + { + type: 'video', + name: 'tour', + caption: "Take the scenic route—explore your the home's highlights with a virtual walkthrough.", + alt: 'Video tour showcasing the property.', + height: 534, + width: 300 + } +]; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..f87b35e --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,5 @@ + + + diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts new file mode 100644 index 0000000..189f71e --- /dev/null +++ b/src/routes/+layout.ts @@ -0,0 +1 @@ +export const prerender = true; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000..3ac0ada --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,68 @@ + + + + {title} + + + + + + + + + + + + + + + + + + + {@html ``} + + + +
+ +
+ + + diff --git a/src/routes/+page.ts b/src/routes/+page.ts new file mode 100644 index 0000000..e2717c6 --- /dev/null +++ b/src/routes/+page.ts @@ -0,0 +1,5 @@ +import { mediaItems } from '$lib/media.js'; + +export function load() { + return { mediaItems }; +} diff --git a/src/assets/android-chrome-192x192.png b/static/assets/android-chrome-192x192.png similarity index 100% rename from src/assets/android-chrome-192x192.png rename to static/assets/android-chrome-192x192.png diff --git a/src/assets/android-chrome-512x512.png b/static/assets/android-chrome-512x512.png similarity index 100% rename from src/assets/android-chrome-512x512.png rename to static/assets/android-chrome-512x512.png diff --git a/src/assets/apple-touch-icon.png b/static/assets/apple-touch-icon.png similarity index 100% rename from src/assets/apple-touch-icon.png rename to static/assets/apple-touch-icon.png diff --git a/src/assets/favicon-16x16.png b/static/assets/favicon-16x16.png similarity index 100% rename from src/assets/favicon-16x16.png rename to static/assets/favicon-16x16.png diff --git a/src/assets/favicon-32x32.png b/static/assets/favicon-32x32.png similarity index 100% rename from src/assets/favicon-32x32.png rename to static/assets/favicon-32x32.png diff --git a/src/assets/favicon.ico b/static/assets/favicon.ico similarity index 100% rename from src/assets/favicon.ico rename to static/assets/favicon.ico diff --git a/src/assets/js/ga-init.js b/static/assets/js/ga-init.js similarity index 100% rename from src/assets/js/ga-init.js rename to static/assets/js/ga-init.js diff --git a/static/assets/js/script.js b/static/assets/js/script.js new file mode 100644 index 0000000..8924b60 --- /dev/null +++ b/static/assets/js/script.js @@ -0,0 +1,108 @@ +// --- theme toggle --- +const toggle = document.getElementById('theme-toggle'); +const root = document.documentElement; +const saved = window?.localStorage?.getItem('dark-mode'); +const sysDark = window.matchMedia('(prefers-color-scheme: dark)').matches; +if (saved === 'true') { + root.classList.add('dark'); + root.classList.remove('light'); +} else if (saved === 'false') { + root.classList.add('light'); + root.classList.remove('dark'); +} else { + if (sysDark) { + root.classList.add('dark'); + root.classList.remove('light'); + } else { + root.classList.add('light'); + root.classList.remove('dark'); + } +} +toggle?.addEventListener('click', () => { + const isDark = root.classList.contains('dark'); + root.classList.toggle('dark', !isDark); + root.classList.toggle('light', isDark); + window?.localStorage?.setItem('dark-mode', String(!isDark)); +}); + +const { body } = document; + +// --- lightbox base --- +const lb = document.getElementById('lightbox'); +const lbCnt = document.getElementById('lb-content'); +const lbCap = document.getElementById('lb-caption'); +document.getElementById('lb-close')?.addEventListener('click', () => { + lb?.setAttribute('aria-hidden', 'true'); + body.classList.remove('lightbox-open'); + if (lbCnt) lbCnt.innerHTML = ''; +}); + +// Build picture element for lightbox (name, type only) +function createPicture(name, type) { + const pic = document.createElement('picture'); + const breakpoints = [ + { bp: 'desktop', minWidth: 1024 }, + { bp: 'tablet', minWidth: 768 }, + { bp: 'mobile', minWidth: 0 } + ]; + for (const { bp, minWidth } of breakpoints) { + const src = document.createElement('source'); + src.media = `(min-width:${minWidth}px)`; + if (type === 'image') { + src.srcset = + `/assets/media/${bp}/${name}@1x.webp 1x, /assets/media/${bp}/${name}.webp 2x`; + } else { + src.srcset = + `/assets/media/${bp}/${name}_still@1x.webp 1x, /assets/media/${bp}/${name}_still.webp 2x`; + } + pic.appendChild(src); + } + const img = document.createElement('img'); + img.src = `/assets/media/thumbnail/${name}.webp`; + img.alt = ''; + pic.appendChild(img); + return pic; +} + +function openLightbox(item) { + if (!lbCnt || !lbCap || !lb) return; + lbCnt.innerHTML = ''; + if (item.type === 'video') { + const v = document.createElement('video'); + v.src = `/assets/media/videos/${item.name}.mp4`; + v.controls = true; + v.autoplay = true; + lbCnt.appendChild(v); + } else { + lbCnt.appendChild(createPicture(item.name, item.type)); + } + lbCap.textContent = item.caption; + body.classList.add('lightbox-open'); + lb.setAttribute('aria-hidden', 'false'); +} + +// --- bind to pre-rendered gallery --- +document.querySelectorAll('.gallery-item').forEach((fig) => { + const name = fig.getAttribute('data-name'); + const type = fig.getAttribute('data-type'); + const caption = fig.getAttribute('data-caption'); + if (!name || !type || !caption) return; + const item = { name, type, caption }; + fig.addEventListener('click', () => openLightbox(item)); + fig.addEventListener('keydown', (e) => { + if (e.key === 'Enter') openLightbox(item); + }); +}); + +// --- video toggle --- +const videoTgl = document.getElementById('show_video'); +videoTgl?.addEventListener('click', () => { + const videoFig = document.querySelector('.gallery-item.video'); + if (videoFig) { + openLightbox({ + name: videoFig.getAttribute('data-name'), + type: videoFig.getAttribute('data-type'), + caption: videoFig.getAttribute('data-caption') + }); + } +}); diff --git a/src/assets/media/desktop/backyard_parking.webp b/static/assets/media/desktop/backyard_parking.webp similarity index 100% rename from src/assets/media/desktop/backyard_parking.webp rename to static/assets/media/desktop/backyard_parking.webp diff --git a/src/assets/media/desktop/backyard_parking@1x.webp b/static/assets/media/desktop/backyard_parking@1x.webp similarity index 100% rename from src/assets/media/desktop/backyard_parking@1x.webp rename to static/assets/media/desktop/backyard_parking@1x.webp diff --git a/src/assets/media/desktop/bedroom_suite_1.webp b/static/assets/media/desktop/bedroom_suite_1.webp similarity index 100% rename from src/assets/media/desktop/bedroom_suite_1.webp rename to static/assets/media/desktop/bedroom_suite_1.webp diff --git a/src/assets/media/desktop/bedroom_suite_1@1x.webp b/static/assets/media/desktop/bedroom_suite_1@1x.webp similarity index 100% rename from src/assets/media/desktop/bedroom_suite_1@1x.webp rename to static/assets/media/desktop/bedroom_suite_1@1x.webp diff --git a/src/assets/media/desktop/bedroom_suite_2.webp b/static/assets/media/desktop/bedroom_suite_2.webp similarity index 100% rename from src/assets/media/desktop/bedroom_suite_2.webp rename to static/assets/media/desktop/bedroom_suite_2.webp diff --git a/src/assets/media/desktop/bedroom_suite_2@1x.webp b/static/assets/media/desktop/bedroom_suite_2@1x.webp similarity index 100% rename from src/assets/media/desktop/bedroom_suite_2@1x.webp rename to static/assets/media/desktop/bedroom_suite_2@1x.webp diff --git a/src/assets/media/desktop/bedroom_suite_3.webp b/static/assets/media/desktop/bedroom_suite_3.webp similarity index 100% rename from src/assets/media/desktop/bedroom_suite_3.webp rename to static/assets/media/desktop/bedroom_suite_3.webp diff --git a/src/assets/media/desktop/bedroom_suite_3@1x.webp b/static/assets/media/desktop/bedroom_suite_3@1x.webp similarity index 100% rename from src/assets/media/desktop/bedroom_suite_3@1x.webp rename to static/assets/media/desktop/bedroom_suite_3@1x.webp diff --git a/src/assets/media/desktop/coat_closet.webp b/static/assets/media/desktop/coat_closet.webp similarity index 100% rename from src/assets/media/desktop/coat_closet.webp rename to static/assets/media/desktop/coat_closet.webp diff --git a/src/assets/media/desktop/coat_closet@1x.webp b/static/assets/media/desktop/coat_closet@1x.webp similarity index 100% rename from src/assets/media/desktop/coat_closet@1x.webp rename to static/assets/media/desktop/coat_closet@1x.webp diff --git a/src/assets/media/desktop/deck_1.webp b/static/assets/media/desktop/deck_1.webp similarity index 100% rename from src/assets/media/desktop/deck_1.webp rename to static/assets/media/desktop/deck_1.webp diff --git a/src/assets/media/desktop/deck_1@1x.webp b/static/assets/media/desktop/deck_1@1x.webp similarity index 100% rename from src/assets/media/desktop/deck_1@1x.webp rename to static/assets/media/desktop/deck_1@1x.webp diff --git a/src/assets/media/desktop/deck_2.webp b/static/assets/media/desktop/deck_2.webp similarity index 100% rename from src/assets/media/desktop/deck_2.webp rename to static/assets/media/desktop/deck_2.webp diff --git a/src/assets/media/desktop/deck_2@1x.webp b/static/assets/media/desktop/deck_2@1x.webp similarity index 100% rename from src/assets/media/desktop/deck_2@1x.webp rename to static/assets/media/desktop/deck_2@1x.webp diff --git a/src/assets/media/desktop/exterior.webp b/static/assets/media/desktop/exterior.webp similarity index 100% rename from src/assets/media/desktop/exterior.webp rename to static/assets/media/desktop/exterior.webp diff --git a/src/assets/media/desktop/exterior@1x.webp b/static/assets/media/desktop/exterior@1x.webp similarity index 100% rename from src/assets/media/desktop/exterior@1x.webp rename to static/assets/media/desktop/exterior@1x.webp diff --git a/src/assets/media/desktop/guest_bath.webp b/static/assets/media/desktop/guest_bath.webp similarity index 100% rename from src/assets/media/desktop/guest_bath.webp rename to static/assets/media/desktop/guest_bath.webp diff --git a/src/assets/media/desktop/guest_bath@1x.webp b/static/assets/media/desktop/guest_bath@1x.webp similarity index 100% rename from src/assets/media/desktop/guest_bath@1x.webp rename to static/assets/media/desktop/guest_bath@1x.webp diff --git a/src/assets/media/desktop/kitchen.webp b/static/assets/media/desktop/kitchen.webp similarity index 100% rename from src/assets/media/desktop/kitchen.webp rename to static/assets/media/desktop/kitchen.webp diff --git a/src/assets/media/desktop/kitchen@1x.webp b/static/assets/media/desktop/kitchen@1x.webp similarity index 100% rename from src/assets/media/desktop/kitchen@1x.webp rename to static/assets/media/desktop/kitchen@1x.webp diff --git a/src/assets/media/desktop/laundry.webp b/static/assets/media/desktop/laundry.webp similarity index 100% rename from src/assets/media/desktop/laundry.webp rename to static/assets/media/desktop/laundry.webp diff --git a/src/assets/media/desktop/laundry@1x.webp b/static/assets/media/desktop/laundry@1x.webp similarity index 100% rename from src/assets/media/desktop/laundry@1x.webp rename to static/assets/media/desktop/laundry@1x.webp diff --git a/src/assets/media/desktop/living_room_1.webp b/static/assets/media/desktop/living_room_1.webp similarity index 100% rename from src/assets/media/desktop/living_room_1.webp rename to static/assets/media/desktop/living_room_1.webp diff --git a/src/assets/media/desktop/living_room_1@1x.webp b/static/assets/media/desktop/living_room_1@1x.webp similarity index 100% rename from src/assets/media/desktop/living_room_1@1x.webp rename to static/assets/media/desktop/living_room_1@1x.webp diff --git a/src/assets/media/desktop/living_room_2.webp b/static/assets/media/desktop/living_room_2.webp similarity index 100% rename from src/assets/media/desktop/living_room_2.webp rename to static/assets/media/desktop/living_room_2.webp diff --git a/src/assets/media/desktop/living_room_2@1x.webp b/static/assets/media/desktop/living_room_2@1x.webp similarity index 100% rename from src/assets/media/desktop/living_room_2@1x.webp rename to static/assets/media/desktop/living_room_2@1x.webp diff --git a/src/assets/media/desktop/office_fitness_guest_1.webp b/static/assets/media/desktop/office_fitness_guest_1.webp similarity index 100% rename from src/assets/media/desktop/office_fitness_guest_1.webp rename to static/assets/media/desktop/office_fitness_guest_1.webp diff --git a/src/assets/media/desktop/office_fitness_guest_1@1x.webp b/static/assets/media/desktop/office_fitness_guest_1@1x.webp similarity index 100% rename from src/assets/media/desktop/office_fitness_guest_1@1x.webp rename to static/assets/media/desktop/office_fitness_guest_1@1x.webp diff --git a/src/assets/media/desktop/office_fitness_guest_2.webp b/static/assets/media/desktop/office_fitness_guest_2.webp similarity index 100% rename from src/assets/media/desktop/office_fitness_guest_2.webp rename to static/assets/media/desktop/office_fitness_guest_2.webp diff --git a/src/assets/media/desktop/office_fitness_guest_2@1x.webp b/static/assets/media/desktop/office_fitness_guest_2@1x.webp similarity index 100% rename from src/assets/media/desktop/office_fitness_guest_2@1x.webp rename to static/assets/media/desktop/office_fitness_guest_2@1x.webp diff --git a/src/assets/media/desktop/office_fitness_guest_3.webp b/static/assets/media/desktop/office_fitness_guest_3.webp similarity index 100% rename from src/assets/media/desktop/office_fitness_guest_3.webp rename to static/assets/media/desktop/office_fitness_guest_3.webp diff --git a/src/assets/media/desktop/office_fitness_guest_3@1x.webp b/static/assets/media/desktop/office_fitness_guest_3@1x.webp similarity index 100% rename from src/assets/media/desktop/office_fitness_guest_3@1x.webp rename to static/assets/media/desktop/office_fitness_guest_3@1x.webp diff --git a/src/assets/media/desktop/office_fitness_guest_4.webp b/static/assets/media/desktop/office_fitness_guest_4.webp similarity index 100% rename from src/assets/media/desktop/office_fitness_guest_4.webp rename to static/assets/media/desktop/office_fitness_guest_4.webp diff --git a/src/assets/media/desktop/office_fitness_guest_4@1x.webp b/static/assets/media/desktop/office_fitness_guest_4@1x.webp similarity index 100% rename from src/assets/media/desktop/office_fitness_guest_4@1x.webp rename to static/assets/media/desktop/office_fitness_guest_4@1x.webp diff --git a/src/assets/media/desktop/onsuite_1.webp b/static/assets/media/desktop/onsuite_1.webp similarity index 100% rename from src/assets/media/desktop/onsuite_1.webp rename to static/assets/media/desktop/onsuite_1.webp diff --git a/src/assets/media/desktop/onsuite_1@1x.webp b/static/assets/media/desktop/onsuite_1@1x.webp similarity index 100% rename from src/assets/media/desktop/onsuite_1@1x.webp rename to static/assets/media/desktop/onsuite_1@1x.webp diff --git a/src/assets/media/desktop/onsuite_2.webp b/static/assets/media/desktop/onsuite_2.webp similarity index 100% rename from src/assets/media/desktop/onsuite_2.webp rename to static/assets/media/desktop/onsuite_2.webp diff --git a/src/assets/media/desktop/onsuite_2@1x.webp b/static/assets/media/desktop/onsuite_2@1x.webp similarity index 100% rename from src/assets/media/desktop/onsuite_2@1x.webp rename to static/assets/media/desktop/onsuite_2@1x.webp diff --git a/src/assets/media/desktop/tour_still.webp b/static/assets/media/desktop/tour_still.webp similarity index 100% rename from src/assets/media/desktop/tour_still.webp rename to static/assets/media/desktop/tour_still.webp diff --git a/src/assets/media/desktop/tour_still@1x.webp b/static/assets/media/desktop/tour_still@1x.webp similarity index 100% rename from src/assets/media/desktop/tour_still@1x.webp rename to static/assets/media/desktop/tour_still@1x.webp diff --git a/src/assets/media/mobile/backyard_parking.webp b/static/assets/media/mobile/backyard_parking.webp similarity index 100% rename from src/assets/media/mobile/backyard_parking.webp rename to static/assets/media/mobile/backyard_parking.webp diff --git a/src/assets/media/mobile/backyard_parking@1x.webp b/static/assets/media/mobile/backyard_parking@1x.webp similarity index 100% rename from src/assets/media/mobile/backyard_parking@1x.webp rename to static/assets/media/mobile/backyard_parking@1x.webp diff --git a/src/assets/media/mobile/backyard_parking_mobile.webp b/static/assets/media/mobile/backyard_parking_mobile.webp similarity index 100% rename from src/assets/media/mobile/backyard_parking_mobile.webp rename to static/assets/media/mobile/backyard_parking_mobile.webp diff --git a/src/assets/media/mobile/bedroom_suite_1.webp b/static/assets/media/mobile/bedroom_suite_1.webp similarity index 100% rename from src/assets/media/mobile/bedroom_suite_1.webp rename to static/assets/media/mobile/bedroom_suite_1.webp diff --git a/src/assets/media/mobile/bedroom_suite_1@1x.webp b/static/assets/media/mobile/bedroom_suite_1@1x.webp similarity index 100% rename from src/assets/media/mobile/bedroom_suite_1@1x.webp rename to static/assets/media/mobile/bedroom_suite_1@1x.webp diff --git a/src/assets/media/mobile/bedroom_suite_1_mobile.webp b/static/assets/media/mobile/bedroom_suite_1_mobile.webp similarity index 100% rename from src/assets/media/mobile/bedroom_suite_1_mobile.webp rename to static/assets/media/mobile/bedroom_suite_1_mobile.webp diff --git a/src/assets/media/mobile/bedroom_suite_2.webp b/static/assets/media/mobile/bedroom_suite_2.webp similarity index 100% rename from src/assets/media/mobile/bedroom_suite_2.webp rename to static/assets/media/mobile/bedroom_suite_2.webp diff --git a/src/assets/media/mobile/bedroom_suite_2@1x.webp b/static/assets/media/mobile/bedroom_suite_2@1x.webp similarity index 100% rename from src/assets/media/mobile/bedroom_suite_2@1x.webp rename to static/assets/media/mobile/bedroom_suite_2@1x.webp diff --git a/src/assets/media/mobile/bedroom_suite_2_mobile.webp b/static/assets/media/mobile/bedroom_suite_2_mobile.webp similarity index 100% rename from src/assets/media/mobile/bedroom_suite_2_mobile.webp rename to static/assets/media/mobile/bedroom_suite_2_mobile.webp diff --git a/src/assets/media/mobile/bedroom_suite_3.webp b/static/assets/media/mobile/bedroom_suite_3.webp similarity index 100% rename from src/assets/media/mobile/bedroom_suite_3.webp rename to static/assets/media/mobile/bedroom_suite_3.webp diff --git a/src/assets/media/mobile/bedroom_suite_3@1x.webp b/static/assets/media/mobile/bedroom_suite_3@1x.webp similarity index 100% rename from src/assets/media/mobile/bedroom_suite_3@1x.webp rename to static/assets/media/mobile/bedroom_suite_3@1x.webp diff --git a/src/assets/media/mobile/bedroom_suite_3_mobile.webp b/static/assets/media/mobile/bedroom_suite_3_mobile.webp similarity index 100% rename from src/assets/media/mobile/bedroom_suite_3_mobile.webp rename to static/assets/media/mobile/bedroom_suite_3_mobile.webp diff --git a/src/assets/media/mobile/coat_closet.webp b/static/assets/media/mobile/coat_closet.webp similarity index 100% rename from src/assets/media/mobile/coat_closet.webp rename to static/assets/media/mobile/coat_closet.webp diff --git a/src/assets/media/mobile/coat_closet@1x.webp b/static/assets/media/mobile/coat_closet@1x.webp similarity index 100% rename from src/assets/media/mobile/coat_closet@1x.webp rename to static/assets/media/mobile/coat_closet@1x.webp diff --git a/src/assets/media/mobile/coat_closet_mobile.webp b/static/assets/media/mobile/coat_closet_mobile.webp similarity index 100% rename from src/assets/media/mobile/coat_closet_mobile.webp rename to static/assets/media/mobile/coat_closet_mobile.webp diff --git a/src/assets/media/mobile/deck_1.webp b/static/assets/media/mobile/deck_1.webp similarity index 100% rename from src/assets/media/mobile/deck_1.webp rename to static/assets/media/mobile/deck_1.webp diff --git a/src/assets/media/mobile/deck_1@1x.webp b/static/assets/media/mobile/deck_1@1x.webp similarity index 100% rename from src/assets/media/mobile/deck_1@1x.webp rename to static/assets/media/mobile/deck_1@1x.webp diff --git a/src/assets/media/mobile/deck_1_mobile.webp b/static/assets/media/mobile/deck_1_mobile.webp similarity index 100% rename from src/assets/media/mobile/deck_1_mobile.webp rename to static/assets/media/mobile/deck_1_mobile.webp diff --git a/src/assets/media/mobile/deck_2.webp b/static/assets/media/mobile/deck_2.webp similarity index 100% rename from src/assets/media/mobile/deck_2.webp rename to static/assets/media/mobile/deck_2.webp diff --git a/src/assets/media/mobile/deck_2@1x.webp b/static/assets/media/mobile/deck_2@1x.webp similarity index 100% rename from src/assets/media/mobile/deck_2@1x.webp rename to static/assets/media/mobile/deck_2@1x.webp diff --git a/src/assets/media/mobile/deck_2_mobile.webp b/static/assets/media/mobile/deck_2_mobile.webp similarity index 100% rename from src/assets/media/mobile/deck_2_mobile.webp rename to static/assets/media/mobile/deck_2_mobile.webp diff --git a/src/assets/media/mobile/exterior.webp b/static/assets/media/mobile/exterior.webp similarity index 100% rename from src/assets/media/mobile/exterior.webp rename to static/assets/media/mobile/exterior.webp diff --git a/src/assets/media/mobile/exterior@1x.webp b/static/assets/media/mobile/exterior@1x.webp similarity index 100% rename from src/assets/media/mobile/exterior@1x.webp rename to static/assets/media/mobile/exterior@1x.webp diff --git a/src/assets/media/mobile/exterior_mobile.webp b/static/assets/media/mobile/exterior_mobile.webp similarity index 100% rename from src/assets/media/mobile/exterior_mobile.webp rename to static/assets/media/mobile/exterior_mobile.webp diff --git a/src/assets/media/mobile/guest_bath.webp b/static/assets/media/mobile/guest_bath.webp similarity index 100% rename from src/assets/media/mobile/guest_bath.webp rename to static/assets/media/mobile/guest_bath.webp diff --git a/src/assets/media/mobile/guest_bath@1x.webp b/static/assets/media/mobile/guest_bath@1x.webp similarity index 100% rename from src/assets/media/mobile/guest_bath@1x.webp rename to static/assets/media/mobile/guest_bath@1x.webp diff --git a/src/assets/media/mobile/guest_bath_mobile.webp b/static/assets/media/mobile/guest_bath_mobile.webp similarity index 100% rename from src/assets/media/mobile/guest_bath_mobile.webp rename to static/assets/media/mobile/guest_bath_mobile.webp diff --git a/src/assets/media/mobile/kitchen.webp b/static/assets/media/mobile/kitchen.webp similarity index 100% rename from src/assets/media/mobile/kitchen.webp rename to static/assets/media/mobile/kitchen.webp diff --git a/src/assets/media/mobile/kitchen@1x.webp b/static/assets/media/mobile/kitchen@1x.webp similarity index 100% rename from src/assets/media/mobile/kitchen@1x.webp rename to static/assets/media/mobile/kitchen@1x.webp diff --git a/src/assets/media/mobile/kitchen_mobile.webp b/static/assets/media/mobile/kitchen_mobile.webp similarity index 100% rename from src/assets/media/mobile/kitchen_mobile.webp rename to static/assets/media/mobile/kitchen_mobile.webp diff --git a/src/assets/media/mobile/laundry.webp b/static/assets/media/mobile/laundry.webp similarity index 100% rename from src/assets/media/mobile/laundry.webp rename to static/assets/media/mobile/laundry.webp diff --git a/src/assets/media/mobile/laundry@1x.webp b/static/assets/media/mobile/laundry@1x.webp similarity index 100% rename from src/assets/media/mobile/laundry@1x.webp rename to static/assets/media/mobile/laundry@1x.webp diff --git a/src/assets/media/mobile/laundry_mobile.webp b/static/assets/media/mobile/laundry_mobile.webp similarity index 100% rename from src/assets/media/mobile/laundry_mobile.webp rename to static/assets/media/mobile/laundry_mobile.webp diff --git a/src/assets/media/mobile/living_room_1.webp b/static/assets/media/mobile/living_room_1.webp similarity index 100% rename from src/assets/media/mobile/living_room_1.webp rename to static/assets/media/mobile/living_room_1.webp diff --git a/src/assets/media/mobile/living_room_1@1x.webp b/static/assets/media/mobile/living_room_1@1x.webp similarity index 100% rename from src/assets/media/mobile/living_room_1@1x.webp rename to static/assets/media/mobile/living_room_1@1x.webp diff --git a/src/assets/media/mobile/living_room_1_mobile.webp b/static/assets/media/mobile/living_room_1_mobile.webp similarity index 100% rename from src/assets/media/mobile/living_room_1_mobile.webp rename to static/assets/media/mobile/living_room_1_mobile.webp diff --git a/src/assets/media/mobile/living_room_2.webp b/static/assets/media/mobile/living_room_2.webp similarity index 100% rename from src/assets/media/mobile/living_room_2.webp rename to static/assets/media/mobile/living_room_2.webp diff --git a/src/assets/media/mobile/living_room_2@1x.webp b/static/assets/media/mobile/living_room_2@1x.webp similarity index 100% rename from src/assets/media/mobile/living_room_2@1x.webp rename to static/assets/media/mobile/living_room_2@1x.webp diff --git a/src/assets/media/mobile/living_room_2_mobile.webp b/static/assets/media/mobile/living_room_2_mobile.webp similarity index 100% rename from src/assets/media/mobile/living_room_2_mobile.webp rename to static/assets/media/mobile/living_room_2_mobile.webp diff --git a/src/assets/media/mobile/office_fitness_guest_1.webp b/static/assets/media/mobile/office_fitness_guest_1.webp similarity index 100% rename from src/assets/media/mobile/office_fitness_guest_1.webp rename to static/assets/media/mobile/office_fitness_guest_1.webp diff --git a/src/assets/media/mobile/office_fitness_guest_1@1x.webp b/static/assets/media/mobile/office_fitness_guest_1@1x.webp similarity index 100% rename from src/assets/media/mobile/office_fitness_guest_1@1x.webp rename to static/assets/media/mobile/office_fitness_guest_1@1x.webp diff --git a/src/assets/media/mobile/office_fitness_guest_1_mobile.webp b/static/assets/media/mobile/office_fitness_guest_1_mobile.webp similarity index 100% rename from src/assets/media/mobile/office_fitness_guest_1_mobile.webp rename to static/assets/media/mobile/office_fitness_guest_1_mobile.webp diff --git a/src/assets/media/mobile/office_fitness_guest_2.webp b/static/assets/media/mobile/office_fitness_guest_2.webp similarity index 100% rename from src/assets/media/mobile/office_fitness_guest_2.webp rename to static/assets/media/mobile/office_fitness_guest_2.webp diff --git a/src/assets/media/mobile/office_fitness_guest_2@1x.webp b/static/assets/media/mobile/office_fitness_guest_2@1x.webp similarity index 100% rename from src/assets/media/mobile/office_fitness_guest_2@1x.webp rename to static/assets/media/mobile/office_fitness_guest_2@1x.webp diff --git a/src/assets/media/mobile/office_fitness_guest_2_mobile.webp b/static/assets/media/mobile/office_fitness_guest_2_mobile.webp similarity index 100% rename from src/assets/media/mobile/office_fitness_guest_2_mobile.webp rename to static/assets/media/mobile/office_fitness_guest_2_mobile.webp diff --git a/src/assets/media/mobile/office_fitness_guest_3.webp b/static/assets/media/mobile/office_fitness_guest_3.webp similarity index 100% rename from src/assets/media/mobile/office_fitness_guest_3.webp rename to static/assets/media/mobile/office_fitness_guest_3.webp diff --git a/src/assets/media/mobile/office_fitness_guest_3@1x.webp b/static/assets/media/mobile/office_fitness_guest_3@1x.webp similarity index 100% rename from src/assets/media/mobile/office_fitness_guest_3@1x.webp rename to static/assets/media/mobile/office_fitness_guest_3@1x.webp diff --git a/src/assets/media/mobile/office_fitness_guest_3_mobile.webp b/static/assets/media/mobile/office_fitness_guest_3_mobile.webp similarity index 100% rename from src/assets/media/mobile/office_fitness_guest_3_mobile.webp rename to static/assets/media/mobile/office_fitness_guest_3_mobile.webp diff --git a/src/assets/media/mobile/office_fitness_guest_4.webp b/static/assets/media/mobile/office_fitness_guest_4.webp similarity index 100% rename from src/assets/media/mobile/office_fitness_guest_4.webp rename to static/assets/media/mobile/office_fitness_guest_4.webp diff --git a/src/assets/media/mobile/office_fitness_guest_4@1x.webp b/static/assets/media/mobile/office_fitness_guest_4@1x.webp similarity index 100% rename from src/assets/media/mobile/office_fitness_guest_4@1x.webp rename to static/assets/media/mobile/office_fitness_guest_4@1x.webp diff --git a/src/assets/media/mobile/office_fitness_guest_4_mobile.webp b/static/assets/media/mobile/office_fitness_guest_4_mobile.webp similarity index 100% rename from src/assets/media/mobile/office_fitness_guest_4_mobile.webp rename to static/assets/media/mobile/office_fitness_guest_4_mobile.webp diff --git a/src/assets/media/mobile/onsuite_1.webp b/static/assets/media/mobile/onsuite_1.webp similarity index 100% rename from src/assets/media/mobile/onsuite_1.webp rename to static/assets/media/mobile/onsuite_1.webp diff --git a/src/assets/media/mobile/onsuite_1@1x.webp b/static/assets/media/mobile/onsuite_1@1x.webp similarity index 100% rename from src/assets/media/mobile/onsuite_1@1x.webp rename to static/assets/media/mobile/onsuite_1@1x.webp diff --git a/src/assets/media/mobile/onsuite_1_mobile.webp b/static/assets/media/mobile/onsuite_1_mobile.webp similarity index 100% rename from src/assets/media/mobile/onsuite_1_mobile.webp rename to static/assets/media/mobile/onsuite_1_mobile.webp diff --git a/src/assets/media/mobile/onsuite_2.webp b/static/assets/media/mobile/onsuite_2.webp similarity index 100% rename from src/assets/media/mobile/onsuite_2.webp rename to static/assets/media/mobile/onsuite_2.webp diff --git a/src/assets/media/mobile/onsuite_2@1x.webp b/static/assets/media/mobile/onsuite_2@1x.webp similarity index 100% rename from src/assets/media/mobile/onsuite_2@1x.webp rename to static/assets/media/mobile/onsuite_2@1x.webp diff --git a/src/assets/media/mobile/onsuite_2_mobile.webp b/static/assets/media/mobile/onsuite_2_mobile.webp similarity index 100% rename from src/assets/media/mobile/onsuite_2_mobile.webp rename to static/assets/media/mobile/onsuite_2_mobile.webp diff --git a/src/assets/media/mobile/tour_still.webp b/static/assets/media/mobile/tour_still.webp similarity index 100% rename from src/assets/media/mobile/tour_still.webp rename to static/assets/media/mobile/tour_still.webp diff --git a/src/assets/media/mobile/tour_still@1x.webp b/static/assets/media/mobile/tour_still@1x.webp similarity index 100% rename from src/assets/media/mobile/tour_still@1x.webp rename to static/assets/media/mobile/tour_still@1x.webp diff --git a/src/assets/media/tablet/backyard_parking.webp b/static/assets/media/tablet/backyard_parking.webp similarity index 100% rename from src/assets/media/tablet/backyard_parking.webp rename to static/assets/media/tablet/backyard_parking.webp diff --git a/src/assets/media/tablet/backyard_parking@1x.webp b/static/assets/media/tablet/backyard_parking@1x.webp similarity index 100% rename from src/assets/media/tablet/backyard_parking@1x.webp rename to static/assets/media/tablet/backyard_parking@1x.webp diff --git a/src/assets/media/tablet/bedroom_suite_1.webp b/static/assets/media/tablet/bedroom_suite_1.webp similarity index 100% rename from src/assets/media/tablet/bedroom_suite_1.webp rename to static/assets/media/tablet/bedroom_suite_1.webp diff --git a/src/assets/media/tablet/bedroom_suite_1@1x.webp b/static/assets/media/tablet/bedroom_suite_1@1x.webp similarity index 100% rename from src/assets/media/tablet/bedroom_suite_1@1x.webp rename to static/assets/media/tablet/bedroom_suite_1@1x.webp diff --git a/src/assets/media/tablet/bedroom_suite_2.webp b/static/assets/media/tablet/bedroom_suite_2.webp similarity index 100% rename from src/assets/media/tablet/bedroom_suite_2.webp rename to static/assets/media/tablet/bedroom_suite_2.webp diff --git a/src/assets/media/tablet/bedroom_suite_2@1x.webp b/static/assets/media/tablet/bedroom_suite_2@1x.webp similarity index 100% rename from src/assets/media/tablet/bedroom_suite_2@1x.webp rename to static/assets/media/tablet/bedroom_suite_2@1x.webp diff --git a/src/assets/media/tablet/bedroom_suite_3.webp b/static/assets/media/tablet/bedroom_suite_3.webp similarity index 100% rename from src/assets/media/tablet/bedroom_suite_3.webp rename to static/assets/media/tablet/bedroom_suite_3.webp diff --git a/src/assets/media/tablet/bedroom_suite_3@1x.webp b/static/assets/media/tablet/bedroom_suite_3@1x.webp similarity index 100% rename from src/assets/media/tablet/bedroom_suite_3@1x.webp rename to static/assets/media/tablet/bedroom_suite_3@1x.webp diff --git a/src/assets/media/tablet/coat_closet.webp b/static/assets/media/tablet/coat_closet.webp similarity index 100% rename from src/assets/media/tablet/coat_closet.webp rename to static/assets/media/tablet/coat_closet.webp diff --git a/src/assets/media/tablet/coat_closet@1x.webp b/static/assets/media/tablet/coat_closet@1x.webp similarity index 100% rename from src/assets/media/tablet/coat_closet@1x.webp rename to static/assets/media/tablet/coat_closet@1x.webp diff --git a/src/assets/media/tablet/deck_1.webp b/static/assets/media/tablet/deck_1.webp similarity index 100% rename from src/assets/media/tablet/deck_1.webp rename to static/assets/media/tablet/deck_1.webp diff --git a/src/assets/media/tablet/deck_1@1x.webp b/static/assets/media/tablet/deck_1@1x.webp similarity index 100% rename from src/assets/media/tablet/deck_1@1x.webp rename to static/assets/media/tablet/deck_1@1x.webp diff --git a/src/assets/media/tablet/deck_2.webp b/static/assets/media/tablet/deck_2.webp similarity index 100% rename from src/assets/media/tablet/deck_2.webp rename to static/assets/media/tablet/deck_2.webp diff --git a/src/assets/media/tablet/deck_2@1x.webp b/static/assets/media/tablet/deck_2@1x.webp similarity index 100% rename from src/assets/media/tablet/deck_2@1x.webp rename to static/assets/media/tablet/deck_2@1x.webp diff --git a/src/assets/media/tablet/exterior.webp b/static/assets/media/tablet/exterior.webp similarity index 100% rename from src/assets/media/tablet/exterior.webp rename to static/assets/media/tablet/exterior.webp diff --git a/src/assets/media/tablet/exterior@1x.webp b/static/assets/media/tablet/exterior@1x.webp similarity index 100% rename from src/assets/media/tablet/exterior@1x.webp rename to static/assets/media/tablet/exterior@1x.webp diff --git a/src/assets/media/tablet/guest_bath.webp b/static/assets/media/tablet/guest_bath.webp similarity index 100% rename from src/assets/media/tablet/guest_bath.webp rename to static/assets/media/tablet/guest_bath.webp diff --git a/src/assets/media/tablet/guest_bath@1x.webp b/static/assets/media/tablet/guest_bath@1x.webp similarity index 100% rename from src/assets/media/tablet/guest_bath@1x.webp rename to static/assets/media/tablet/guest_bath@1x.webp diff --git a/src/assets/media/tablet/kitchen.webp b/static/assets/media/tablet/kitchen.webp similarity index 100% rename from src/assets/media/tablet/kitchen.webp rename to static/assets/media/tablet/kitchen.webp diff --git a/src/assets/media/tablet/kitchen@1x.webp b/static/assets/media/tablet/kitchen@1x.webp similarity index 100% rename from src/assets/media/tablet/kitchen@1x.webp rename to static/assets/media/tablet/kitchen@1x.webp diff --git a/src/assets/media/tablet/laundry.webp b/static/assets/media/tablet/laundry.webp similarity index 100% rename from src/assets/media/tablet/laundry.webp rename to static/assets/media/tablet/laundry.webp diff --git a/src/assets/media/tablet/laundry@1x.webp b/static/assets/media/tablet/laundry@1x.webp similarity index 100% rename from src/assets/media/tablet/laundry@1x.webp rename to static/assets/media/tablet/laundry@1x.webp diff --git a/src/assets/media/tablet/living_room_1.webp b/static/assets/media/tablet/living_room_1.webp similarity index 100% rename from src/assets/media/tablet/living_room_1.webp rename to static/assets/media/tablet/living_room_1.webp diff --git a/src/assets/media/tablet/living_room_1@1x.webp b/static/assets/media/tablet/living_room_1@1x.webp similarity index 100% rename from src/assets/media/tablet/living_room_1@1x.webp rename to static/assets/media/tablet/living_room_1@1x.webp diff --git a/src/assets/media/tablet/living_room_2.webp b/static/assets/media/tablet/living_room_2.webp similarity index 100% rename from src/assets/media/tablet/living_room_2.webp rename to static/assets/media/tablet/living_room_2.webp diff --git a/src/assets/media/tablet/living_room_2@1x.webp b/static/assets/media/tablet/living_room_2@1x.webp similarity index 100% rename from src/assets/media/tablet/living_room_2@1x.webp rename to static/assets/media/tablet/living_room_2@1x.webp diff --git a/src/assets/media/tablet/office_fitness_guest_1.webp b/static/assets/media/tablet/office_fitness_guest_1.webp similarity index 100% rename from src/assets/media/tablet/office_fitness_guest_1.webp rename to static/assets/media/tablet/office_fitness_guest_1.webp diff --git a/src/assets/media/tablet/office_fitness_guest_1@1x.webp b/static/assets/media/tablet/office_fitness_guest_1@1x.webp similarity index 100% rename from src/assets/media/tablet/office_fitness_guest_1@1x.webp rename to static/assets/media/tablet/office_fitness_guest_1@1x.webp diff --git a/src/assets/media/tablet/office_fitness_guest_2.webp b/static/assets/media/tablet/office_fitness_guest_2.webp similarity index 100% rename from src/assets/media/tablet/office_fitness_guest_2.webp rename to static/assets/media/tablet/office_fitness_guest_2.webp diff --git a/src/assets/media/tablet/office_fitness_guest_2@1x.webp b/static/assets/media/tablet/office_fitness_guest_2@1x.webp similarity index 100% rename from src/assets/media/tablet/office_fitness_guest_2@1x.webp rename to static/assets/media/tablet/office_fitness_guest_2@1x.webp diff --git a/src/assets/media/tablet/office_fitness_guest_3.webp b/static/assets/media/tablet/office_fitness_guest_3.webp similarity index 100% rename from src/assets/media/tablet/office_fitness_guest_3.webp rename to static/assets/media/tablet/office_fitness_guest_3.webp diff --git a/src/assets/media/tablet/office_fitness_guest_3@1x.webp b/static/assets/media/tablet/office_fitness_guest_3@1x.webp similarity index 100% rename from src/assets/media/tablet/office_fitness_guest_3@1x.webp rename to static/assets/media/tablet/office_fitness_guest_3@1x.webp diff --git a/src/assets/media/tablet/office_fitness_guest_4.webp b/static/assets/media/tablet/office_fitness_guest_4.webp similarity index 100% rename from src/assets/media/tablet/office_fitness_guest_4.webp rename to static/assets/media/tablet/office_fitness_guest_4.webp diff --git a/src/assets/media/tablet/office_fitness_guest_4@1x.webp b/static/assets/media/tablet/office_fitness_guest_4@1x.webp similarity index 100% rename from src/assets/media/tablet/office_fitness_guest_4@1x.webp rename to static/assets/media/tablet/office_fitness_guest_4@1x.webp diff --git a/src/assets/media/tablet/onsuite_1.webp b/static/assets/media/tablet/onsuite_1.webp similarity index 100% rename from src/assets/media/tablet/onsuite_1.webp rename to static/assets/media/tablet/onsuite_1.webp diff --git a/src/assets/media/tablet/onsuite_1@1x.webp b/static/assets/media/tablet/onsuite_1@1x.webp similarity index 100% rename from src/assets/media/tablet/onsuite_1@1x.webp rename to static/assets/media/tablet/onsuite_1@1x.webp diff --git a/src/assets/media/tablet/onsuite_2.webp b/static/assets/media/tablet/onsuite_2.webp similarity index 100% rename from src/assets/media/tablet/onsuite_2.webp rename to static/assets/media/tablet/onsuite_2.webp diff --git a/src/assets/media/tablet/onsuite_2@1x.webp b/static/assets/media/tablet/onsuite_2@1x.webp similarity index 100% rename from src/assets/media/tablet/onsuite_2@1x.webp rename to static/assets/media/tablet/onsuite_2@1x.webp diff --git a/src/assets/media/tablet/tour_still.webp b/static/assets/media/tablet/tour_still.webp similarity index 100% rename from src/assets/media/tablet/tour_still.webp rename to static/assets/media/tablet/tour_still.webp diff --git a/src/assets/media/tablet/tour_still@1x.webp b/static/assets/media/tablet/tour_still@1x.webp similarity index 100% rename from src/assets/media/tablet/tour_still@1x.webp rename to static/assets/media/tablet/tour_still@1x.webp diff --git a/src/assets/media/thumbnail/backyard_parking.webp b/static/assets/media/thumbnail/backyard_parking.webp similarity index 100% rename from src/assets/media/thumbnail/backyard_parking.webp rename to static/assets/media/thumbnail/backyard_parking.webp diff --git a/src/assets/media/thumbnail/bedroom_suite_1.webp b/static/assets/media/thumbnail/bedroom_suite_1.webp similarity index 100% rename from src/assets/media/thumbnail/bedroom_suite_1.webp rename to static/assets/media/thumbnail/bedroom_suite_1.webp diff --git a/src/assets/media/thumbnail/bedroom_suite_2.webp b/static/assets/media/thumbnail/bedroom_suite_2.webp similarity index 100% rename from src/assets/media/thumbnail/bedroom_suite_2.webp rename to static/assets/media/thumbnail/bedroom_suite_2.webp diff --git a/src/assets/media/thumbnail/bedroom_suite_3.webp b/static/assets/media/thumbnail/bedroom_suite_3.webp similarity index 100% rename from src/assets/media/thumbnail/bedroom_suite_3.webp rename to static/assets/media/thumbnail/bedroom_suite_3.webp diff --git a/src/assets/media/thumbnail/coat_closet.webp b/static/assets/media/thumbnail/coat_closet.webp similarity index 100% rename from src/assets/media/thumbnail/coat_closet.webp rename to static/assets/media/thumbnail/coat_closet.webp diff --git a/src/assets/media/thumbnail/deck_1.webp b/static/assets/media/thumbnail/deck_1.webp similarity index 100% rename from src/assets/media/thumbnail/deck_1.webp rename to static/assets/media/thumbnail/deck_1.webp diff --git a/src/assets/media/thumbnail/deck_2.webp b/static/assets/media/thumbnail/deck_2.webp similarity index 100% rename from src/assets/media/thumbnail/deck_2.webp rename to static/assets/media/thumbnail/deck_2.webp diff --git a/src/assets/media/thumbnail/exterior.webp b/static/assets/media/thumbnail/exterior.webp similarity index 100% rename from src/assets/media/thumbnail/exterior.webp rename to static/assets/media/thumbnail/exterior.webp diff --git a/src/assets/media/thumbnail/guest_bath.webp b/static/assets/media/thumbnail/guest_bath.webp similarity index 100% rename from src/assets/media/thumbnail/guest_bath.webp rename to static/assets/media/thumbnail/guest_bath.webp diff --git a/src/assets/media/thumbnail/kitchen.webp b/static/assets/media/thumbnail/kitchen.webp similarity index 100% rename from src/assets/media/thumbnail/kitchen.webp rename to static/assets/media/thumbnail/kitchen.webp diff --git a/src/assets/media/thumbnail/laundry.webp b/static/assets/media/thumbnail/laundry.webp similarity index 100% rename from src/assets/media/thumbnail/laundry.webp rename to static/assets/media/thumbnail/laundry.webp diff --git a/src/assets/media/thumbnail/living_room_1.webp b/static/assets/media/thumbnail/living_room_1.webp similarity index 100% rename from src/assets/media/thumbnail/living_room_1.webp rename to static/assets/media/thumbnail/living_room_1.webp diff --git a/src/assets/media/thumbnail/living_room_2.webp b/static/assets/media/thumbnail/living_room_2.webp similarity index 100% rename from src/assets/media/thumbnail/living_room_2.webp rename to static/assets/media/thumbnail/living_room_2.webp diff --git a/src/assets/media/thumbnail/office_fitness_guest_1.webp b/static/assets/media/thumbnail/office_fitness_guest_1.webp similarity index 100% rename from src/assets/media/thumbnail/office_fitness_guest_1.webp rename to static/assets/media/thumbnail/office_fitness_guest_1.webp diff --git a/src/assets/media/thumbnail/office_fitness_guest_2.webp b/static/assets/media/thumbnail/office_fitness_guest_2.webp similarity index 100% rename from src/assets/media/thumbnail/office_fitness_guest_2.webp rename to static/assets/media/thumbnail/office_fitness_guest_2.webp diff --git a/src/assets/media/thumbnail/office_fitness_guest_3.webp b/static/assets/media/thumbnail/office_fitness_guest_3.webp similarity index 100% rename from src/assets/media/thumbnail/office_fitness_guest_3.webp rename to static/assets/media/thumbnail/office_fitness_guest_3.webp diff --git a/src/assets/media/thumbnail/office_fitness_guest_4.webp b/static/assets/media/thumbnail/office_fitness_guest_4.webp similarity index 100% rename from src/assets/media/thumbnail/office_fitness_guest_4.webp rename to static/assets/media/thumbnail/office_fitness_guest_4.webp diff --git a/src/assets/media/thumbnail/onsuite_1.webp b/static/assets/media/thumbnail/onsuite_1.webp similarity index 100% rename from src/assets/media/thumbnail/onsuite_1.webp rename to static/assets/media/thumbnail/onsuite_1.webp diff --git a/src/assets/media/thumbnail/onsuite_2.webp b/static/assets/media/thumbnail/onsuite_2.webp similarity index 100% rename from src/assets/media/thumbnail/onsuite_2.webp rename to static/assets/media/thumbnail/onsuite_2.webp diff --git a/src/assets/media/thumbnail/tour.webp b/static/assets/media/thumbnail/tour.webp similarity index 100% rename from src/assets/media/thumbnail/tour.webp rename to static/assets/media/thumbnail/tour.webp diff --git a/src/assets/media/videos/tour.mp4 b/static/assets/media/videos/tour.mp4 similarity index 100% rename from src/assets/media/videos/tour.mp4 rename to static/assets/media/videos/tour.mp4 diff --git a/static/robots.txt b/static/robots.txt new file mode 100644 index 0000000..c2a49f4 --- /dev/null +++ b/static/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Allow: / diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 0000000..f2fff2b --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,27 @@ +import adapter from '@sveltejs/adapter-static'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + preprocess: vitePreprocess(), + kit: { + prerender: { + handleHttpError: ({ status }) => { + if (status === 404) return; + throw new Error('Prerender failed'); + } + }, + adapter: adapter({ + pages: 'build', + assets: 'build', + fallback: undefined, + precompress: false, + strict: true + }), + alias: { + $lib: 'src/lib' + } + } +}; + +export default config; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a8f10c8 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..bbf8c7d --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,6 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [sveltekit()] +}); -- 2.49.1 From 4f863e568643a1cefa2517842a92212e5f1ab59d Mon Sep 17 00:00:00 2001 From: mifi Date: Sun, 15 Feb 2026 23:01:16 -0300 Subject: [PATCH 2/3] Finished the Sveltification --- package.json | 3 +- pnpm-lock.yaml | 15 +- scripts/externalize-bootstrap.js | 79 ++++ src/app.css | 118 +++--- src/app.d.ts | 14 +- src/lib/components/GalleryFigure.svelte | 161 ++++---- src/lib/components/Lightbox.svelte | 140 +++++++ src/lib/components/SiteHeader.svelte | 65 ++-- src/lib/media.ts | 350 +++++++++--------- src/lib/stores/theme.svelte.ts | 10 + src/routes/+layout.svelte | 6 +- src/routes/+layout.ts | 1 + src/routes/+page.svelte | 124 ++++--- src/routes/+page.ts | 2 +- .../appspecific/com.chrome.devtools.json | 1 + static/assets/js/script.js | 107 +----- static/assets/media/videos/tour-captions.vtt | 4 + 17 files changed, 683 insertions(+), 517 deletions(-) create mode 100644 scripts/externalize-bootstrap.js create mode 100644 src/lib/components/Lightbox.svelte create mode 100644 src/lib/stores/theme.svelte.ts create mode 100644 static/.well-known/appspecific/com.chrome.devtools.json create mode 100644 static/assets/media/videos/tour-captions.vtt diff --git a/package.json b/package.json index 416ff28..b20fca3 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "description": "Armandine gallery – pre-rendered Svelte site", "scripts": { "dev": "vite dev", - "build": "vite build && node scripts/critical-css.js", + "build": "vite build && node scripts/critical-css.js && node scripts/externalize-bootstrap.js", "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", @@ -42,6 +42,7 @@ "stylelint": "^17.3.0", "stylelint-config-standard": "^40.0.0", "svelte": "^5.0.0", + "terser": "^5.0.0", "svelte-check": "^4.0.0", "typescript": "^5.0.0", "vite": "^7.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c5262d6..d85fd10 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -59,6 +59,9 @@ importers: svelte-check: specifier: ^4.0.0 version: 4.4.0(picomatch@4.0.3)(svelte@5.51.2)(typescript@5.9.3) + terser: + specifier: ^5.0.0 + version: 5.46.0 typescript: specifier: ^5.0.0 version: 5.9.3 @@ -2571,7 +2574,6 @@ snapshots: dependencies: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - optional: true '@jridgewell/sourcemap-codec@1.5.5': {} @@ -2921,8 +2923,7 @@ snapshots: node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) - buffer-from@1.1.2: - optional: true + buffer-from@1.1.2: {} cacheable@2.3.2: dependencies: @@ -2956,8 +2957,7 @@ snapshots: colord@2.9.3: {} - commander@2.20.3: - optional: true + commander@2.20.3: {} consola@2.15.3: {} @@ -3820,10 +3820,8 @@ snapshots: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 - optional: true - source-map@0.6.1: - optional: true + source-map@0.6.1: {} string-width@4.2.3: dependencies: @@ -3952,7 +3950,6 @@ snapshots: acorn: 8.15.0 commander: 2.20.3 source-map-support: 0.5.21 - optional: true tinyglobby@0.2.15: dependencies: diff --git a/scripts/externalize-bootstrap.js b/scripts/externalize-bootstrap.js new file mode 100644 index 0000000..09cdbe5 --- /dev/null +++ b/scripts/externalize-bootstrap.js @@ -0,0 +1,79 @@ +/** + * Move SvelteKit's inline bootstrap script to an external file for CSP (no unsafe-inline). + * Run after vite build; reads/writes build/. + * Finds containing __sveltekit_, minifies it, writes to _app/immutable/entry/bootstrap.js, + * and replaces the inline script with that contains __sveltekit_ +function findInlineBootstrap(html) { + const scriptOpen = html.indexOf('', scriptOpen); + if (scriptClose === -1) return null; + const content = html.slice(scriptOpen + ''.length }; +} + +const SCRIPT_TAG = ``; + +async function main() { + const htmlFiles = getFiles(buildDir, '.html'); + let bootstrapWritten = false; + let count = 0; + for (const htmlFile of htmlFiles) { + let html = readFileSync(htmlFile, 'utf8'); + const found = findInlineBootstrap(html); + if (!found) continue; + + if (!bootstrapWritten) { + // Imports relative to script location when in _app/immutable/entry/ + let scriptContent = found.content.replace( + /import\("\.\/_app\/immutable\/entry\/([^"]+)"\)/g, + 'import("./$1")' + ); + const result = await minify(scriptContent, { + format: { comments: false }, + compress: { passes: 1 } + }); + if (result.code) scriptContent = result.code; + mkdirSync(entryDir, { recursive: true }); + writeFileSync(bootstrapPath, scriptContent, 'utf8'); + bootstrapWritten = true; + } + + html = html.slice(0, found.start) + SCRIPT_TAG + html.slice(found.end); + writeFileSync(htmlFile, html, 'utf8'); + count++; + } + if (count > 0) { + console.log('Bootstrap script externalized (minified):', scriptSrc, `(${count} HTML file(s))`); + } else if (htmlFiles.length > 0) { + console.log('No SvelteKit inline script found in HTML (bootstrap already external?)'); + } +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/src/app.css b/src/app.css index 363e732..494a5e3 100644 --- a/src/app.css +++ b/src/app.css @@ -1,99 +1,67 @@ :root { - --bg: #fff; - --fg: #222; - --accent: #007acc; + --bg: #fff; + --fg: #222; + --accent: #007acc; + --lightbox-backdrop: rgb(255 255 255 / 90%); + --lightbox-shadow: 0 0 10px 0 rgb(0 0 0 / 10%); + --surface-elevated: #f3f3f3; } @media (prefers-color-scheme: dark) { - :root { - --bg: #111; - --fg: #eee; - --accent: #46c; - } + :root { + --bg: #111; + --fg: #eee; + --accent: #46c; + --lightbox-backdrop: rgb(0 0 0 / 90%); + --surface-elevated: #222; + } } /* Explicit theme toggle overrides (win over media query when set) */ -html.dark { - --bg: #111; - --fg: #eee; - --accent: #46c; -} +html { + &[data-theme='dark'] { + --bg: #111; + --fg: #eee; + --accent: #46c; + --lightbox-backdrop: rgb(0 0 0 / 90%); + --surface-elevated: #222; + } -html.light { - --bg: #fff; - --fg: #222; - --accent: #007acc; + &[data-theme='light'] { + --bg: #fff; + --fg: #222; + --accent: #007acc; + --lightbox-backdrop: rgb(255 255 255 / 90%); + --lightbox-shadow: 0 0 10px 0 rgb(0 0 0 / 10%); + --surface-elevated: #f3f3f3; + } } body { - margin: 0; - font-family: sans-serif; - background-color: var(--bg); - color: var(--fg); + margin: 0; + font-family: sans-serif; + background-color: var(--bg); + color: var(--fg); } .lightbox-open { - overflow: hidden; + overflow: hidden; } .gallery-grid { - column-count: 1; - column-gap: 1rem; - padding: 1rem; + column-count: 1; + column-gap: 1rem; + padding: 1rem; } @media (width >= 768px) { - .gallery-grid { - column-count: 2; - } + .gallery-grid { + column-count: 2; + } } @media (width >= 1024px) { - .gallery-grid { - column-count: 3; - } -} - -/* Lightbox: not a Svelte component, styled globally for script.js target */ -#lightbox { - position: fixed; - inset: 0; - background: rgb(0 0 0 / 90%); - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - visibility: hidden; - opacity: 0; - transition: opacity 0.3s; -} - -#lightbox[aria-hidden='false'] { - visibility: visible; - opacity: 1; -} - -#lb-content img, -#lb-content video { - max-width: 90vw; - max-height: 80vh; - border-radius: 8px; -} - -#lb-caption { - color: #fff; - margin-top: 0.5rem; - text-align: center; - max-width: 90vw; -} - -#lb-close { - position: absolute; - top: 1rem; - right: 1rem; - background: none; - border: none; - font-size: 2rem; - color: #fff; - cursor: pointer; + .gallery-grid { + column-count: 3; + } } diff --git a/src/app.d.ts b/src/app.d.ts index da08e6d..d76242a 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,13 +1,13 @@ // See https://svelte.dev/docs/kit/types#app.d.ts // for information about these interfaces declare global { - namespace App { - // interface Error {} - // interface Locals {} - // interface PageData {} - // interface PageState {} - // interface Platform {} - } + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } } export {}; diff --git a/src/lib/components/GalleryFigure.svelte b/src/lib/components/GalleryFigure.svelte index 5383b29..34cd1db 100644 --- a/src/lib/components/GalleryFigure.svelte +++ b/src/lib/components/GalleryFigure.svelte @@ -1,83 +1,100 @@ - - +
+ + {#each [{ bp: 'desktop', minWidth: 1024 }, { bp: 'tablet', minWidth: 768 }, { bp: 'mobile', minWidth: 0 }] as breakpoint} + + {/each} + {item.alt.replace(/['']/g, 2 + ? (item.loading ?? 'lazy') + : undefined} + fetchpriority={item.fetchpriority ?? undefined} + /> + +
{item.caption}
+
+ diff --git a/src/lib/components/Lightbox.svelte b/src/lib/components/Lightbox.svelte new file mode 100644 index 0000000..f132351 --- /dev/null +++ b/src/lib/components/Lightbox.svelte @@ -0,0 +1,140 @@ + + + + + diff --git a/src/lib/components/SiteHeader.svelte b/src/lib/components/SiteHeader.svelte index 5afe330..3ef2a70 100644 --- a/src/lib/components/SiteHeader.svelte +++ b/src/lib/components/SiteHeader.svelte @@ -1,30 +1,47 @@ + + diff --git a/src/lib/media.ts b/src/lib/media.ts index 8636f9f..7d41b3d 100644 --- a/src/lib/media.ts +++ b/src/lib/media.ts @@ -1,178 +1,182 @@ export interface MediaItem { - type: 'image' | 'video'; - name: string; - caption: string; - alt: string; - height?: number; - width?: number; - loading?: 'lazy' | 'eager'; - fetchpriority?: 'high' | 'low' | 'auto'; + type: 'image' | 'video'; + name: string; + caption: string; + alt: string; + height?: number; + width?: number; + loading?: 'lazy' | 'eager'; + fetchpriority?: 'high' | 'low' | 'auto'; } export const mediaItems: MediaItem[] = [ - { - type: 'image', - name: 'living_room_1', - caption: 'An inviting blend of comfort and curated art—relaxation guaranteed.', - alt: 'Sunny living room with stylish seating and vibrant artwork.', - height: 200, - width: 300, - loading: 'eager', - fetchpriority: 'high' - }, - { - type: 'image', - name: 'living_room_2', - caption: 'Relaxation elevated—your stylish living space awaits.', - alt: 'Spacious living area featuring elegant furniture and tasteful decor.', - height: 200, - width: 300, - fetchpriority: 'high' - }, - { - type: 'image', - name: 'kitchen', - caption: 'The culinary stage is set—snacking encouraged, style required.', - alt: 'Modern kitchen showcasing sleek appliances and contemporary design.', - height: 200, - width: 300 - }, - { - type: 'image', - name: 'bedroom_suite_1', - caption: 'A bedroom suite designed to make snoozing irresistible.', - alt: 'Inviting bedroom suite with cozy bedding and warm lighting.', - height: 200, - width: 300 - }, - { - type: 'image', - name: 'bedroom_suite_2', - caption: 'Style meets comfort—sleeping in has never been easier.', - alt: 'Comfortable bedroom suite with elegant decor and soft tones.', - height: 200, - width: 300 - }, - { - type: 'image', - name: 'bedroom_suite_3', - caption: 'Where dreams get stylish—a bedroom that feels like home.', - alt: 'Welcoming bedroom with soothing colors and inviting ambiance.', - height: 200, - width: 300 - }, - { - type: 'image', - name: 'guest_bath', - caption: 'Your personal spa experience—right down the hall.', - alt: 'Sophisticated guest bathroom with modern fixtures and clean lines.', - height: 450, - width: 300 - }, - { - type: 'image', - name: 'onsuite_1', - caption: 'Luxury meets practicality—your private ensuite awaits.', - alt: 'Private ensuite bathroom featuring contemporary design and premium finishes.', - height: 450, - width: 300, - loading: 'eager', - fetchpriority: 'high' - }, - { - type: 'image', - name: 'onsuite_2', - caption: 'Everyday luxury, right at home—your ensuite oasis.', - alt: 'Elegant ensuite with sleek fixtures and stylish decor.', - height: 200, - width: 300 - }, - { - type: 'image', - name: 'laundry', - caption: 'Laundry day reimagined—functional never looked so good.', - alt: 'Modern laundry room with washer, dryer, and organized storage.', - height: 450, - width: 300 - }, - { - type: 'image', - name: 'coat_closet', - caption: 'Organized and chic—your entryway\'s best friend.', - alt: 'Convenient coat closet with tidy storage solutions.', - height: 200, - width: 300 - }, - { - type: 'image', - name: 'deck_1', - caption: 'Outdoor comfort, just steps away—morning coffee optional.', - alt: 'Sunny deck with cozy seating and pleasant outdoor views.', - height: 450, - width: 300 - }, - { - type: 'image', - name: 'deck_2', - caption: 'Your fresh-air escape—ideal for relaxing evenings.', - alt: 'Comfortable deck area perfect for unwinding or entertaining.', - height: 200, - width: 300 - }, - { - type: 'image', - name: 'exterior', - caption: 'Curb appeal perfected—your new favorite place starts here.', - alt: 'Attractive home exterior with inviting architecture.', - height: 200, - width: 300 - }, - { - type: 'image', - name: 'backyard_parking', - caption: 'Convenience meets privacy—your personal backyard parking spot.', - alt: 'Private backyard parking area offering secure convenience.', - height: 200, - width: 300 - }, - { - type: 'image', - name: 'office_fitness_guest_1', - caption: 'Productivity zone meets fitness corner—multitasking done right.', - alt: 'Dual-purpose room featuring office setup and fitness equipment.', - height: 200, - width: 300 - }, - { - type: 'image', - name: 'office_fitness_guest_2', - caption: 'Work, workout, or unwind—the room of endless possibilities.', - alt: 'Versatile office and fitness area with modern amenities.', - height: 200, - width: 300 - }, - { - type: 'image', - name: 'office_fitness_guest_3', - caption: 'Stay focused or get fit—you decide.', - alt: 'Functional space combining a workspace and home fitness area.', - height: 200, - width: 300 - }, - { - type: 'image', - name: 'office_fitness_guest_4', - caption: 'Room for every routine—your workspace meets wellness.', - alt: 'Stylish office area seamlessly integrated with fitness features.', - height: 200, - width: 300 - }, - { - type: 'video', - name: 'tour', - caption: "Take the scenic route—explore your the home's highlights with a virtual walkthrough.", - alt: 'Video tour showcasing the property.', - height: 534, - width: 300 - } + { + type: 'image', + name: 'living_room_1', + caption: + 'An inviting blend of comfort and curated art—relaxation guaranteed.', + alt: 'Sunny living room with stylish seating and vibrant artwork.', + height: 200, + width: 300, + fetchpriority: 'high', + }, + { + type: 'image', + name: 'living_room_2', + caption: 'Relaxation elevated—your stylish living space awaits.', + alt: 'Spacious living area featuring elegant furniture and tasteful decor.', + height: 200, + width: 300, + fetchpriority: 'high', + }, + { + type: 'image', + name: 'kitchen', + caption: + 'The culinary stage is set—snacking encouraged, style required.', + alt: 'Modern kitchen showcasing sleek appliances and contemporary design.', + height: 200, + width: 300, + }, + { + type: 'image', + name: 'bedroom_suite_1', + caption: 'A bedroom suite designed to make snoozing irresistible.', + alt: 'Inviting bedroom suite with cozy bedding and warm lighting.', + height: 200, + width: 300, + }, + { + type: 'image', + name: 'bedroom_suite_2', + caption: 'Style meets comfort—sleeping in has never been easier.', + alt: 'Comfortable bedroom suite with elegant decor and soft tones.', + height: 200, + width: 300, + }, + { + type: 'image', + name: 'bedroom_suite_3', + caption: 'Where dreams get stylish—a bedroom that feels like home.', + alt: 'Welcoming bedroom with soothing colors and inviting ambiance.', + height: 200, + width: 300, + }, + { + type: 'image', + name: 'guest_bath', + caption: 'Your personal spa experience—right down the hall.', + alt: 'Sophisticated guest bathroom with modern fixtures and clean lines.', + height: 450, + width: 300, + }, + { + type: 'image', + name: 'onsuite_1', + caption: 'Luxury meets practicality—your private ensuite awaits.', + alt: 'Private ensuite bathroom featuring contemporary design and premium finishes.', + height: 450, + width: 300, + loading: 'eager', + fetchpriority: 'high', + }, + { + type: 'image', + name: 'onsuite_2', + caption: 'Everyday luxury, right at home—your ensuite oasis.', + alt: 'Elegant ensuite with sleek fixtures and stylish decor.', + height: 200, + width: 300, + }, + { + type: 'image', + name: 'laundry', + caption: 'Laundry day reimagined—functional never looked so good.', + alt: 'Modern laundry room with washer, dryer, and organized storage.', + height: 450, + width: 300, + }, + { + type: 'image', + name: 'coat_closet', + caption: "Organized and chic—your entryway's best friend.", + alt: 'Convenient coat closet with tidy storage solutions.', + height: 200, + width: 300, + }, + { + type: 'image', + name: 'deck_1', + caption: 'Outdoor comfort, just steps away—morning coffee optional.', + alt: 'Sunny deck with cozy seating and pleasant outdoor views.', + height: 450, + width: 300, + }, + { + type: 'image', + name: 'deck_2', + caption: 'Your fresh-air escape—ideal for relaxing evenings.', + alt: 'Comfortable deck area perfect for unwinding or entertaining.', + height: 200, + width: 300, + }, + { + type: 'image', + name: 'exterior', + caption: 'Curb appeal perfected—your new favorite place starts here.', + alt: 'Attractive home exterior with inviting architecture.', + height: 200, + width: 300, + }, + { + type: 'image', + name: 'backyard_parking', + caption: + 'Convenience meets privacy—your personal backyard parking spot.', + alt: 'Private backyard parking area offering secure convenience.', + height: 200, + width: 300, + }, + { + type: 'image', + name: 'office_fitness_guest_1', + caption: + 'Productivity zone meets fitness corner—multitasking done right.', + alt: 'Dual-purpose room featuring office setup and fitness equipment.', + height: 200, + width: 300, + }, + { + type: 'image', + name: 'office_fitness_guest_2', + caption: 'Work, workout, or unwind—the room of endless possibilities.', + alt: 'Versatile office and fitness area with modern amenities.', + height: 200, + width: 300, + }, + { + type: 'image', + name: 'office_fitness_guest_3', + caption: 'Stay focused or get fit—you decide.', + alt: 'Functional space combining a workspace and home fitness area.', + height: 200, + width: 300, + }, + { + type: 'image', + name: 'office_fitness_guest_4', + caption: 'Room for every routine—your workspace meets wellness.', + alt: 'Stylish office area seamlessly integrated with fitness features.', + height: 200, + width: 300, + }, + { + type: 'video', + name: 'tour', + caption: + "Take the scenic route—explore your the home's highlights with a virtual walkthrough.", + alt: 'Video tour showcasing the property.', + height: 534, + width: 300, + }, ]; diff --git a/src/lib/stores/theme.svelte.ts b/src/lib/stores/theme.svelte.ts new file mode 100644 index 0000000..3de7dfc --- /dev/null +++ b/src/lib/stores/theme.svelte.ts @@ -0,0 +1,10 @@ +let mode = $state<'light' | 'dark'>('light'); + +export const theme = { + get: () => mode, + set: (value: 'light' | 'dark') => { + mode = value; + document.documentElement.setAttribute('data-theme', value); + localStorage.setItem('theme', value); + }, +}; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index f87b35e..8921562 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,5 +1,7 @@ - +{@render children()} diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index 189f71e..0df44ad 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -1 +1,2 @@ export const prerender = true; +export const ssr = true; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 3ac0ada..2a7967e 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,68 +1,88 @@ - {title} - - - - - - - + {title} + + + + + + + - - - + + + - - + + - - + + - {@html ``} + {@html ``}
- +
- - - + diff --git a/src/routes/+page.ts b/src/routes/+page.ts index e2717c6..074d678 100644 --- a/src/routes/+page.ts +++ b/src/routes/+page.ts @@ -1,5 +1,5 @@ import { mediaItems } from '$lib/media.js'; export function load() { - return { mediaItems }; + return { mediaItems }; } diff --git a/static/.well-known/appspecific/com.chrome.devtools.json b/static/.well-known/appspecific/com.chrome.devtools.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/static/.well-known/appspecific/com.chrome.devtools.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/static/assets/js/script.js b/static/assets/js/script.js index 8924b60..fac19d1 100644 --- a/static/assets/js/script.js +++ b/static/assets/js/script.js @@ -1,108 +1,13 @@ -// --- theme toggle --- -const toggle = document.getElementById('theme-toggle'); const root = document.documentElement; -const saved = window?.localStorage?.getItem('dark-mode'); +const saved = window?.localStorage?.getItem('theme'); const sysDark = window.matchMedia('(prefers-color-scheme: dark)').matches; -if (saved === 'true') { - root.classList.add('dark'); - root.classList.remove('light'); -} else if (saved === 'false') { - root.classList.add('light'); - root.classList.remove('dark'); + +if (saved) { + root.setAttribute('data-theme', saved); } else { if (sysDark) { - root.classList.add('dark'); - root.classList.remove('light'); + root.setAttribute('data-theme', 'dark'); } else { - root.classList.add('light'); - root.classList.remove('dark'); + root.setAttribute('data-theme', 'light'); } } -toggle?.addEventListener('click', () => { - const isDark = root.classList.contains('dark'); - root.classList.toggle('dark', !isDark); - root.classList.toggle('light', isDark); - window?.localStorage?.setItem('dark-mode', String(!isDark)); -}); - -const { body } = document; - -// --- lightbox base --- -const lb = document.getElementById('lightbox'); -const lbCnt = document.getElementById('lb-content'); -const lbCap = document.getElementById('lb-caption'); -document.getElementById('lb-close')?.addEventListener('click', () => { - lb?.setAttribute('aria-hidden', 'true'); - body.classList.remove('lightbox-open'); - if (lbCnt) lbCnt.innerHTML = ''; -}); - -// Build picture element for lightbox (name, type only) -function createPicture(name, type) { - const pic = document.createElement('picture'); - const breakpoints = [ - { bp: 'desktop', minWidth: 1024 }, - { bp: 'tablet', minWidth: 768 }, - { bp: 'mobile', minWidth: 0 } - ]; - for (const { bp, minWidth } of breakpoints) { - const src = document.createElement('source'); - src.media = `(min-width:${minWidth}px)`; - if (type === 'image') { - src.srcset = - `/assets/media/${bp}/${name}@1x.webp 1x, /assets/media/${bp}/${name}.webp 2x`; - } else { - src.srcset = - `/assets/media/${bp}/${name}_still@1x.webp 1x, /assets/media/${bp}/${name}_still.webp 2x`; - } - pic.appendChild(src); - } - const img = document.createElement('img'); - img.src = `/assets/media/thumbnail/${name}.webp`; - img.alt = ''; - pic.appendChild(img); - return pic; -} - -function openLightbox(item) { - if (!lbCnt || !lbCap || !lb) return; - lbCnt.innerHTML = ''; - if (item.type === 'video') { - const v = document.createElement('video'); - v.src = `/assets/media/videos/${item.name}.mp4`; - v.controls = true; - v.autoplay = true; - lbCnt.appendChild(v); - } else { - lbCnt.appendChild(createPicture(item.name, item.type)); - } - lbCap.textContent = item.caption; - body.classList.add('lightbox-open'); - lb.setAttribute('aria-hidden', 'false'); -} - -// --- bind to pre-rendered gallery --- -document.querySelectorAll('.gallery-item').forEach((fig) => { - const name = fig.getAttribute('data-name'); - const type = fig.getAttribute('data-type'); - const caption = fig.getAttribute('data-caption'); - if (!name || !type || !caption) return; - const item = { name, type, caption }; - fig.addEventListener('click', () => openLightbox(item)); - fig.addEventListener('keydown', (e) => { - if (e.key === 'Enter') openLightbox(item); - }); -}); - -// --- video toggle --- -const videoTgl = document.getElementById('show_video'); -videoTgl?.addEventListener('click', () => { - const videoFig = document.querySelector('.gallery-item.video'); - if (videoFig) { - openLightbox({ - name: videoFig.getAttribute('data-name'), - type: videoFig.getAttribute('data-type'), - caption: videoFig.getAttribute('data-caption') - }); - } -}); diff --git a/static/assets/media/videos/tour-captions.vtt b/static/assets/media/videos/tour-captions.vtt new file mode 100644 index 0000000..0e770e0 --- /dev/null +++ b/static/assets/media/videos/tour-captions.vtt @@ -0,0 +1,4 @@ +WEBVTT + +00:00:00.000 --> 00:00:02.000 +No audio. Silent video tour. -- 2.49.1 From 9f74726236317bdf203deafd5b92e5d683780b7c Mon Sep 17 00:00:00 2001 From: mifi Date: Sun, 15 Feb 2026 23:05:31 -0300 Subject: [PATCH 3/3] Add test build to CI pipeline --- .woodpecker/ci.yaml | 125 ++++++++++++++++++++++++++++++-------------- 1 file changed, 86 insertions(+), 39 deletions(-) diff --git a/.woodpecker/ci.yaml b/.woodpecker/ci.yaml index b29bc54..702f8d8 100644 --- a/.woodpecker/ci.yaml +++ b/.woodpecker/ci.yaml @@ -12,34 +12,6 @@ steps: - corepack prepare pnpm@10.29.2 --activate - pnpm install --frozen-lockfile - - name: lint - image: node:22-alpine - commands: - - corepack enable - - corepack prepare pnpm@10.29.2 --activate - - pnpm install --frozen-lockfile - - pnpm lint - depends_on: - - install - - - name: Send Lint Status Notification (failure) - image: curlimages/curl - environment: - MATTERMOST_BOT_ACCESS_TOKEN: - from_secret: mattermost_bot_access_token - MATTERMOST_CHANNEL_ID: - from_secret: mattermost_tests_channel_id - MATTERMOST_POST_API_URL: - from_secret: mattermost_post_api_url - commands: - - | - BODY=$(printf '{"channel_id":"%s","message":"[%s - Build #%s] Lint failure 💩"}' "$MATTERMOST_CHANNEL_ID" "$CI_REPO" "$CI_PIPELINE_NUMBER") - curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" -H "Authorization: Bearer $MATTERMOST_BOT_ACCESS_TOKEN" $MATTERMOST_POST_API_URL - depends_on: - - lint - when: - - status: [failure] - - name: format check image: node:22-alpine commands: @@ -50,16 +22,6 @@ steps: depends_on: - install - - name: check - image: node:22-alpine - commands: - - corepack enable - - corepack prepare pnpm@10.29.2 --activate - - pnpm install --frozen-lockfile - - pnpm check - depends_on: - - install - - name: Send Prettier Status Notification (failure) image: curlimages/curl environment: @@ -78,6 +40,90 @@ steps: when: - status: [failure] + - name: lint + image: node:22-alpine + commands: + - corepack enable + - corepack prepare pnpm@10.29.2 --activate + - pnpm install --frozen-lockfile + - pnpm lint + depends_on: + - format check + + - name: Send Lint Status Notification (failure) + image: curlimages/curl + environment: + MATTERMOST_BOT_ACCESS_TOKEN: + from_secret: mattermost_bot_access_token + MATTERMOST_CHANNEL_ID: + from_secret: mattermost_tests_channel_id + MATTERMOST_POST_API_URL: + from_secret: mattermost_post_api_url + commands: + - | + BODY=$(printf '{"channel_id":"%s","message":"[%s - Build #%s] Lint failure 💩"}' "$MATTERMOST_CHANNEL_ID" "$CI_REPO" "$CI_PIPELINE_NUMBER") + curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" -H "Authorization: Bearer $MATTERMOST_BOT_ACCESS_TOKEN" $MATTERMOST_POST_API_URL + depends_on: + - lint + when: + - status: [failure] + + - name: check + image: node:22-alpine + commands: + - corepack enable + - corepack prepare pnpm@10.29.2 --activate + - pnpm install --frozen-lockfile + - pnpm check + depends_on: + - lint + + - name: Send Svelte Check Status Notification (failure) + image: curlimages/curl + environment: + MATTERMOST_BOT_ACCESS_TOKEN: + from_secret: mattermost_bot_access_token + MATTERMOST_CHANNEL_ID: + from_secret: mattermost_tests_channel_id + MATTERMOST_POST_API_URL: + from_secret: mattermost_post_api_url + commands: + - | + BODY=$(printf '{"channel_id":"%s","message":"[%s - Build #%s] Svelte check failure 💩"}' "$MATTERMOST_CHANNEL_ID" "$CI_REPO" "$CI_PIPELINE_NUMBER") + curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" -H "Authorization: Bearer $MATTERMOST_BOT_ACCESS_TOKEN" $MATTERMOST_POST_API_URL + depends_on: + - check + when: + - status: [failure] + + - name: build + image: node:22-alpine + commands: + - corepack enable + - corepack prepare pnpm@10.29.2 --activate + - pnpm install --frozen-lockfile + - pnpm build + depends_on: + - check + + - name: Send Build Status Notification (failure) + image: curlimages/curl + environment: + MATTERMOST_BOT_ACCESS_TOKEN: + from_secret: mattermost_bot_access_token + MATTERMOST_CHANNEL_ID: + from_secret: mattermost_tests_channel_id + MATTERMOST_POST_API_URL: + from_secret: mattermost_post_api_url + commands: + - | + BODY=$(printf '{"channel_id":"%s","message":"[%s - Build #%s] Build failure 💩"}' "$MATTERMOST_CHANNEL_ID" "$CI_REPO" "$CI_PIPELINE_NUMBER") + curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" -H "Authorization: Bearer $MATTERMOST_BOT_ACCESS_TOKEN" $MATTERMOST_POST_API_URL + depends_on: + - build + when: + - status: [failure] + - name: Send CI Pipeline Status Notification (success) image: curlimages/curl environment: @@ -93,8 +139,9 @@ steps: curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" -H "Authorization: Bearer $MATTERMOST_BOT_ACCESS_TOKEN" $MATTERMOST_POST_API_URL depends_on: - install - - lint - format check + - lint - check + - build when: - status: [success] -- 2.49.1