diff --git a/deploy/caddy-site.snippet b/deploy/caddy-site.snippet
index 550c8c4..a4752df 100644
--- a/deploy/caddy-site.snippet
+++ b/deploy/caddy-site.snippet
@@ -10,8 +10,8 @@
# import ibo_www_redirect
# }
# imagesbyolofsson.se {
-# # Befintliga specialregler (t.ex. handle /download/original* -> Immich-proxyn)
-# # ska ligga FÖRE importen, så att de vinner över try_files/file_server.
+# # Specialregler (på Cleura: handle för /about, /contact, /posters -> kundportalen,
+# # se kundportalens deploy/cleura/caddy/Caddyfile) ska ligga FÖRE importen.
# import ibo_site /srv/imagesbyolofsson/dist
# }
#
diff --git a/deploy/cleura/install-portfolio-sync.sh b/deploy/cleura/install-portfolio-sync.sh
new file mode 100644
index 0000000..b83344f
--- /dev/null
+++ b/deploy/cleura/install-portfolio-sync.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+# deploy/cleura/install-portfolio-sync.sh – installs the portfolio sync on the Cleura server (run as root from a
+# checkout of this repo). Needs /etc/portal/portfolio-sync.env (root 600) first; see portfolio-sync.sh for the keys.
+# Undo: systemctl disable --now portfolio-sync.timer && rm /etc/systemd/system/portfolio-sync.{service,timer}
+set -euo pipefail
+HERE=$(cd "$(dirname "$0")" && pwd)
+ENVF=/etc/portal/portfolio-sync.env
+[ -f "$ENVF" ] || { echo "$ENVF is missing - copy IMMICH_URL, PORTFOLIO_TAG_ID, IMMICH_API_KEY and GIT_URL there first"; exit 2; }
+[ "$(stat -c %a "$ENVF")" = "600" ] || chmod 600 "$ENVF"
+install -m 755 -o root -g root "$HERE/portfolio-sync.sh" /opt/portal/site/portfolio-sync.sh
+install -m 644 -o root -g root "$HERE/portfolio-sync.service" /etc/systemd/system/portfolio-sync.service
+install -m 644 -o root -g root "$HERE/portfolio-sync.timer" /etc/systemd/system/portfolio-sync.timer
+systemctl daemon-reload
+systemctl enable --now portfolio-sync.timer
+systemctl list-timers portfolio-sync.timer --no-pager
diff --git a/deploy/cleura/portfolio-sync.service b/deploy/cleura/portfolio-sync.service
new file mode 100644
index 0000000..1580b20
--- /dev/null
+++ b/deploy/cleura/portfolio-sync.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=Immich portfolio tag -> imagesbyolofsson-se repo -> live site (deploy/cleura/portfolio-sync.sh)
+After=network-online.target docker.service
+Wants=network-online.target
+Requires=docker.service
+
+[Service]
+Type=oneshot
+ExecStart=/opt/portal/site/portfolio-sync.sh
+TimeoutStartSec=45min
+Nice=10
diff --git a/deploy/cleura/portfolio-sync.sh b/deploy/cleura/portfolio-sync.sh
new file mode 100644
index 0000000..8c68b5c
--- /dev/null
+++ b/deploy/cleura/portfolio-sync.sh
@@ -0,0 +1,71 @@
+#!/usr/bin/env bash
+# deploy/cleura/portfolio-sync.sh – Immich "portfolio" tag -> this repo -> imagesbyolofsson.se, on the Cleura server.
+#
+# Replaces /opt/immich-sync/run-sync.sh on Nurem (started by n8n over SSH) when imagesbyolofsson.se moved to Cleura
+# (Linus 2026-11-06: everything on the Cleura server). Same steps: fetch main, run scripts/portfolio-sync.mjs,
+# commit + push to Gitea main when photos were added or removed, and then rebuild the live site with
+# /opt/portal/site/build-live.sh (EXIF check included). Nothing is built when nothing changed.
+#
+# Installed by deploy/cleura/install-portfolio-sync.sh as /opt/portal/site/portfolio-sync.sh, run by
+# portfolio-sync.timer every 20 minutes. Secrets: /etc/portal/portfolio-sync.env (root 600) with
+# IMMICH_URL, PORTFOLIO_TAG_ID, IMMICH_API_KEY (read-only Immich key) and GIT_URL (Gitea URL that may push main,
+# with user:token@ in it, as on Nurem).
+#
+# The Gitea token never lands on disk outside that env file: the clone's remote URL is stored without it, git gets
+# it from a credential helper at fetch/push time, and the Node container (npm install runs third-party code) gets
+# an empty tmpfs over .git and only the Immich variables.
+set -euo pipefail
+umask 077
+ENVF=${PORTFOLIO_SYNC_ENV:-/etc/portal/portfolio-sync.env}
+DIR=${PORTFOLIO_SYNC_DIR:-/opt/portal/site/sync/repo}
+NODE=node:22.22.0-alpine@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e34
+
+exec 9>/run/lock/portfolio-sync.lock
+flock -n 9 || { echo "[sync] already running"; exit 0; }
+
+set -a; . "$ENVF"; set +a
+export IMMICH_URL="${IMMICH_URL:-https://foto.imagesbyolofsson.se/api}"
+export PORTFOLIO_TAG_ID="${PORTFOLIO_TAG_ID:-67d16b6c-0dea-42ef-a289-2c9e57fa5c5a}"
+[ -n "${IMMICH_API_KEY:-}" ] || { echo "FATAL: IMMICH_API_KEY missing in $ENVF"; exit 2; }
+[ -n "${GIT_URL:-}" ] || { echo "FATAL: GIT_URL missing in $ENVF"; exit 2; }
+
+# Split GIT_URL into the clean URL (stored in .git/config) and user/token (kept in this process only).
+read -r CLEAN_URL GIT_USER GIT_TOKEN < <(python3 - "$GIT_URL" <<'PY'
+import sys, urllib.parse as u
+p = u.urlsplit(sys.argv[1])
+host = p.hostname + (":%d" % p.port if p.port else "")
+print(u.urlunsplit((p.scheme, host, p.path, "", "")), u.unquote(p.username or "-"), u.unquote(p.password or "-"))
+PY
+)
+unset GIT_URL
+export GIT_USER GIT_TOKEN
+# shellcheck disable=SC2016 # $GIT_USER/$GIT_TOKEN are expanded by the helper's shell, not here.
+G=(git -c credential.helper= -c 'credential.helper=!f() { echo "username=$GIT_USER"; echo "password=$GIT_TOKEN"; }; f')
+
+if [ ! -d "$DIR/.git" ]; then
+ mkdir -p "$(dirname "$DIR")"
+ "${G[@]}" clone -q "$CLEAN_URL" "$DIR"
+fi
+chmod 700 "$(dirname "$DIR")" "$DIR"
+git -C "$DIR" remote set-url origin "$CLEAN_URL"
+echo "[sync] fetch + reset main"
+"${G[@]}" -C "$DIR" fetch -q origin main
+git -C "$DIR" checkout -q -B main origin/main
+git -C "$DIR" reset -q --hard origin/main
+
+echo "[sync] scripts/portfolio-sync.mjs"
+docker run --rm --memory 2g --cpus 2 --network bridge \
+ -e IMMICH_URL -e IMMICH_API_KEY -e PORTFOLIO_TAG_ID \
+ -v "$DIR":/app --mount type=tmpfs,destination=/app/.git -w /app "$NODE" \
+ sh -c 'npm install --no-audit --no-fund --loglevel=error >/dev/null && node scripts/portfolio-sync.mjs'
+
+if git -C "$DIR" status --porcelain -- src/assets/images/portfolio | grep -q .; then
+ git -C "$DIR" add -A src/assets/images/portfolio
+ git -C "$DIR" -c user.name="portfolio-sync (Cleura)" -c user.email="Admin@imagesbyolofsson.se" \
+ commit -q -m "chore(portfolio): sync $(date -u +%Y-%m-%d)"
+ "${G[@]}" -C "$DIR" push -q origin main
+ echo "[sync] pushed $(git -C "$DIR" rev-parse --short HEAD), rebuilding the live site"
+ /opt/portal/site/build-live.sh main
+else
+ echo "[sync] no changes"
+fi
diff --git a/deploy/cleura/portfolio-sync.timer b/deploy/cleura/portfolio-sync.timer
new file mode 100644
index 0000000..dadf8f4
--- /dev/null
+++ b/deploy/cleura/portfolio-sync.timer
@@ -0,0 +1,10 @@
+[Unit]
+Description=Portfolio sync every 20 minutes (Immich has no event when a photo is tagged, so it polls)
+
+[Timer]
+OnCalendar=*:0/20
+RandomizedDelaySec=60
+Persistent=true
+
+[Install]
+WantedBy=timers.target
diff --git a/public/robots.txt b/public/robots.txt
index 20c0216..81b2f88 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -8,8 +8,6 @@
User-agent: *
Allow: /
-# Lösenordsskyddad nedladdning av originalfiler – inget att indexera.
-Disallow: /download/
# AI-crawlers och träningsbotar: uttryckligen tillåtna.
User-agent: GPTBot
@@ -36,6 +34,5 @@ User-agent: YouBot
User-agent: DuckAssistBot
User-agent: MistralAI-User
Allow: /
-Disallow: /download/
Sitemap: https://imagesbyolofsson.se/sitemap-index.xml
diff --git a/src/layouts/MainLayout.astro b/src/layouts/MainLayout.astro
index 0132447..82329ec 100644
--- a/src/layouts/MainLayout.astro
+++ b/src/layouts/MainLayout.astro
@@ -45,6 +45,7 @@ const ogHeight = Number(og.attributes.height) || undefined;
const nav = [
{ href: '/portfolio', label: 'Portfolio' },
+ { href: '/posters', label: 'Posters' },
{ href: '/tjanster', label: 'Tjänster' },
{ href: '/about', label: 'Om mig' },
{ href: '/contact', label: 'Kontakt' },
diff --git a/src/lib/pages.ts b/src/lib/pages.ts
index 77379c8..18c264f 100644
--- a/src/lib/pages.ts
+++ b/src/lib/pages.ts
@@ -3,10 +3,16 @@
// Nya sidor hamnar automatiskt i sitemap.
const pageFiles = import.meta.glob('../pages/*.astro');
+// Sidor på samma domän som kundportalen (samma Cleura-server) visar, så att Om mig-texten,
+// samarbeten och posters redigeras på ett ställe i admin (Linus 2026-11-06). Caddy skickar dem
+// till portalen; enskilda posters listas i portalens egen /posters/sitemap.xml.
+export const PORTAL_PAGES = ['/about', '/contact', '/posters'] as const;
+
export function sitePaths(): string[] {
- return Object.keys(pageFiles)
+ const own = Object.keys(pageFiles)
.map((file) => file.replace(/^\.\.\/pages\//, '').replace(/\.astro$/, ''))
.filter((name) => name !== '404' && !name.startsWith('_') && !name.startsWith('['))
- .map((name) => (name === 'index' ? '/' : `/${name}`))
+ .map((name) => (name === 'index' ? '/' : `/${name}`));
+ return [...new Set([...own, ...PORTAL_PAGES])]
.sort((a, b) => (a === '/' ? -1 : b === '/' ? 1 : a.localeCompare(b)));
}
diff --git a/src/pages/404.astro b/src/pages/404.astro
index 484f2fc..ee65fc2 100644
--- a/src/pages/404.astro
+++ b/src/pages/404.astro
@@ -6,6 +6,7 @@ import WaveBackground from '../components/WaveBackground.astro';
const links = [
{ href: '/', label: 'Startsidan' },
{ href: '/portfolio', label: 'Portfolio' },
+ { href: '/posters', label: 'Posters' },
{ href: '/tjanster', label: 'Tjänster och bokning' },
{ href: '/contact', label: 'Kontakt' },
];
diff --git a/src/pages/about.astro b/src/pages/about.astro
deleted file mode 100644
index ee17719..0000000
--- a/src/pages/about.astro
+++ /dev/null
@@ -1,137 +0,0 @@
----
-// src/pages/about.astro
-import MainLayout from '../layouts/MainLayout.astro';
-import { Image } from 'astro:assets';
-import profileImg from '../assets/images/about/profile.jpg';
-import WaveBackground from '../components/WaveBackground.astro';
-import { business, basedIn } from '../data/business';
----
-
-
- Hej, jag heter {business.person.name} och är fotografen bakom {business.brand}.
-
- Jag är baserad i {basedIn()}. Allt började med en enkel nyfikenhet på hur ljuset formar sättet vi ser världen på. Med tiden blev nyfikenheten en besatthet av att fånga detaljerna som de flesta missar – texturer, strukturer, kontraster och ögonblick som passerar obemärkt förbi.
-
- Jag hittar skönheten i det lilla: en sten, ett träd, en tom gata, en stilla skogsstig eller uttrycket hos en främling som går förbi.
-
- Oavsett om det är de invecklade mönstren i en makrobild, naturens stora tystnad eller stadens energi är målet alltid detsamma – att skapa bilder som förmedlar känslor och berättar en historia utan ord.
- {business.recognition} Linus Olofsson {business.camera} En pålitlig följeslagare som har varit med mig på otaliga äventyr. {business.editing} Här får råfilerna liv. Jag fokuserar på naturliga toner och kontrast.
- Har du frågor om någon av mina bilder, vill du samarbeta eller bara säga hej?
- Hör av dig via sociala medier eller mejla mig direkt.
-
- Vill du boka en fotografering? Skicka en förfrågan i kundportalen så återkommer jag med en offert.
- Läs mer om hur det går till under Tjänster.
- Hör gärna av dig med frågor, förfrågningar eller bara för att prata. Eller skriv till mig på Instagram
- Levande ögonblick,
-
-
- fångade i hög upplösning.
- Vad har jag i kameraväskan?
-
- Kamerahus
- Objektiv
-
- {business.lenses.map((lens) =>
- Redigering
- Kontakta mig
- Jag ser fram emot att höra av dig
-
Ladda ner original
-Originalfilen är i full upplösning och kräver lösenord.
- -Fel lösenord — försök igen.
-