Commit Graph
81 Commits
Author SHA1 Message Date
Linus OlofssonandClaude Opus 5.5 63baa35b92 Start page, portfolio, privacy and 404: Linus's list of 2026-11-06, and Swedish/English on every page
- Start page: the text block ("Fotograf Linus Olofsson ..." and "Posters och samarbeten ...") is gone; the buttons
  Posters, Samarbeten, Mer om mig, Portfolio and Kontakt sit at the bottom as long, straight buttons (no round pills, no
  text links). The second scroll box is Linus's own from May again (Redo att samarbeta? -> Kontakta mig) instead of
  "Vill du ha en bild på väggen?".
- Portfolio: "Vill du ha en bild på väggen? Se posters." and every "Köp som poster" button are gone. Photos are
  protected like on Posters: no link to the large file (nothing to open in a new tab), a transparent layer over every
  photo, no context menu, no drag, no long-press save.
- SV/EN: SV/EN switch in the menu; every visible text in both languages (menu, footer, cookie box, start page,
  portfolio with dates and months, privacy page, 404). The choice uses the portal's key (ibo_lang), so all pages on
  imagesbyolofsson.se follow the same choice; it is set before the page is drawn.
- Privacy: the statistics count openings only (there are no downloads any more).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 06:22:52 +01:00
imagesbyolofsson 63a4b3a848 Merge pull request 'Meny: Portfolio, Posters, Om mig, Kontakt – bort med Tjänster och all bokningstext' (#4) from inga-tjanster-2026-11-06 into main
Reviewed-on: #4
2026-11-06 04:42:11 +00:00
Linus OlofssonandClaude Opus 5.5 c760e11c4c No services: /tjanster, booking steps, offers, galleries and downloads removed everywhere
Linus 2026-11-06: he offers no services; the site has posters and collaborations only. /tjanster went live with the
move to Cleura (it came with the SEO pull request #1) and was redirected to the start page in Caddy the same morning.

- tjanster.astro deleted; Tjänster out of the menu and the 404 page.
- Start page: "Vill du boka en fotografering?" -> "Vill du ha en bild på väggen?" (Se posters); "Så bokar du" ->
  "Posters och samarbeten"; description without "Boka via kundportalen".
- Portfolio intro points to the posters; privacy page: orders of posters and collaborations instead of bookings.
- JSON-LD: Organization instead of ProfessionalService, no ReserveAction, offer catalog, price range or service area.
- llms.txt: no prices, booking, booking steps, services page or booking FAQ; faq.ts keeps only the type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 05:31:20 +01:00
imagesbyolofsson d841cf98b5 chore(portfolio): sync 2026-11-06 2026-11-06 04:23:58 +00:00
imagesbyolofsson 5a5b2b42d2 Merge pull request 'Portfoliosynk: 13 bilder som fastnat sedan 29/9 kommer med; en trasig bild stoppar inte de andra' (#3) from portfoliosynk-fix-2026-11-06 into main
Reviewed-on: #3
2026-11-06 04:22:38 +00:00
Linus OlofssonandClaude Opus 5.5 24a59c6e93 Portfolio sync: a photo that fails no longer stops the others; sharp accepts pixel-shift and 15000 px squares
Seen on the first run on Cleura 2026-11-06: 13 photos tagged since 2026-09-29 were never synced. portfolio-sync.mjs
sets exit code 1 when one photo fails (DSC00342_PSMS16-square.jpg: "Input image exceeds pixel limit", sharp's default
268 MP) and set -e then stopped before commit/push - on Nurem as well. Now the photos that worked are committed and
built, the run still ends with exit 1 so systemd shows it, and sharp's limit is 1 000 MP (JPEGs shrink while decoding;
the container gets 3 GB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 05:18:14 +01:00
imagesbyolofsson 8c9afb65c3 Merge pull request 'En domän: portfolion utan nedladdning, Om mig/Kontakt/Posters från portalen, portfoliosynk på Cleura' (#2) from ett-domannamn-2026-11-06 into main
Reviewed-on: #2
2026-11-06 04:07:25 +00:00
Linus OlofssonandClaude Opus 5.5 dc2bda8c07 Portfolio sync: Gitea token stays out of .git/config and out of the Node container
Security review: GIT_URL (user:token@) was stored as the clone's remote URL, and the repo, .git included, was mounted
into the container that runs npm install. Now: umask 077 + 700 directories, the remote is the clean URL, git gets the
token from a credential helper at fetch/push time, and the container sees an empty tmpfs over .git.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 04:45:09 +01:00
Linus OlofssonandClaude Opus 5.5 6bb56e6035 Portfolio sync runs on the Cleura server (systemd timer every 20 min) instead of n8n -> Nurem
deploy/cleura/portfolio-sync.sh: same steps as /opt/immich-sync/run-sync.sh on Nurem (fetch main, portfolio-sync.mjs,
commit + push when photos changed) and then build-live.sh main on Cleura; flock against overlapping runs, the sync
runs in the pinned Node container. install-portfolio-sync.sh puts it in place; secrets stay in
/etc/portal/portfolio-sync.env (root 600). The snippet comment no longer mentions the removed /download/original.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 04:43:33 +01:00
Linus OlofssonandClaude Opus 5.5 1b24607e02 Portfolio without downloads; Om mig, Kontakt and Posters come from the portal on the same domain
Linus 2026-11-06: everything public lives on imagesbyolofsson.se (Cleura), the customer
subdomain is only for ordering and the logged-in portal; photos are sold as posters
instead of being downloadable.

- Portfolio: download menu (S/M/L), the original password dialog and /download/original
  are gone; the lightbox has "Köp som poster" -> /posters instead.
- about.astro and contact.astro removed: Caddy on Cleura sends /about, /contact and
  /posters to the portal, so the About text and collaborations are edited once in admin.
  They stay in sitemap-0.xml (PORTAL_PAGES); the sitemap index lists /posters/sitemap.xml.
- Menu and 404 page link Posters; privacy, robots.txt and llms.txt no longer mention
  password downloads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 04:08:04 +01:00
imagesbyolofsson 85bd49b0c3 Merge pull request 'SEO & AEO: everything from the PDF review (robots, sitemap, 404, meta/OG, JSON-LD, Swedish text, /tjanster, EXIF clean, alt texts, Admin@, Örebro)' (#1) from seo-aeo-2026-10-27 into main
Reviewed-on: #1
2026-10-27 23:45:31 +00:00
Linus OlofssonandClaude Opus 5.5 7207e351d4 Portfolio alt texts: what each photo shows (147/147, Swedish, no names/places/camera), written with Cleura gemma-4 vision and checked by eye
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WvE3C43Uwtv2vtnoFBLDw7
2026-10-27 23:27:17 +01:00
Linus OlofssonandClaude Opus 5.5 03a70efcb6 Verified facts: Örebro / Örebro län (his public Flickr + LinkedIn) and the Facebook page in sameAs and llms.txt
Phone, prices, packages, delivery time and years stay null until Linus confirms them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WvE3C43Uwtv2vtnoFBLDw7
2026-10-27 23:14:55 +01:00
Linus OlofssonandClaude Opus 5.5 15560445ce Contact e-mail: Admin@imagesbyolofsson.se everywhere (Linus 2026-10-27), as on the old contact page
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WvE3C43Uwtv2vtnoFBLDw7
2026-10-27 23:08:40 +01:00
Linus OlofssonandClaude Opus 5.5 1e921dfe37 fix(images): stop publishing EXIF/GPS originals, responsive images, crawlable gallery
- Homepage images: <Picture> from src/assets (AVIF/WebP/JPG srcset, width/
  height, first image eager + fetchpriority high, rest lazy), Swedish alt
  texts. (The duplicate EXIF-laden copies in public/3d-scroll were deleted
  in 30e385e; README links now point at src/assets.)
- Portfolio: originals are no longer referenced (no .src access), so Astro
  does not copy them to dist/. Small/Medium/Large downloads and the lightbox
  srcset are EXIF-free JPGs from getImage. Old -s/-m files are no longer
  imported.
- Alt texts: Swedish fallback from date (EXIF or filename), never camera
  model or "undefined"; portfolio-meta.ts overrides per image.
- Gallery items are <a href> to the large image (lightbox on click);
  download options are buttons; no empty H3, no H4 for the password box.
- Stats beacon unchanged; item keys computed at build time (same format).
- Script moved inside the layout so it is no longer rendered after </html>.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WvE3C43Uwtv2vtnoFBLDw7
2026-10-27 19:13:24 +01:00
Linus OlofssonandClaude Opus 5.5 4f1b468f75 feat(server): redirects, real 404, caching and security headers (nginx + Caddy)
- www -> apex 301, old URLs (/my-people, /animales-and-nature,
  /celebrations-with-family, /northen-lights) -> /portfolio 301,
  trailing slash and .html -> clean URL 301.
- try_files $uri $uri.html, otherwise 404.html with status 404.
- /_astro/*: public, max-age=31536000, immutable (path-based map, no
  duplicate Cache-Control headers); HTML must-revalidate.
- HSTS, Referrer-Policy, Permissions-Policy, X-Frame-Options, CSP that
  allows the Meta Pixel and the stats beacon to customer.imagesbyolofsson.se.
- deploy/caddy-site.snippet: the same rules as importable Caddy snippets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WvE3C43Uwtv2vtnoFBLDw7
2026-10-27 19:13:07 +01:00
Linus OlofssonandClaude Opus 5.5 206d31d9c2 feat(content): Swedish copy, new /tjanster page with booking flow and FAQ
- New /tjanster: genres from existing copy, where/price (contact for offer
  while unknown), booking flow via customer portal, delivery in a private
  gallery, originals after payment, FAQ (also emitted as FAQPage).
- Homepage: tagline + text section on who/what/where/how to book.
- About: Swedish, photographer framing instead of "by day/by night",
  Adobe line. Contact: Swedish, plain mailto info@, booking CTA, fixed
  unclosed div. Privacy: Swedish (Google Limited Use line kept in English).
- Unique Swedish title and description per page; scripts moved inside the
  layout (no <script> after </html>).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WvE3C43Uwtv2vtnoFBLDw7
2026-10-27 19:13:07 +01:00
Linus OlofssonandClaude Opus 5.5 f452d6d237 feat(seo): site config, sitemap, robots.txt, llms.txt and a real 404 page
- astro.config: site, trailingSlash "never", build.format "file".
- /sitemap-index.xml + /sitemap-0.xml as prerendered endpoints (no new
  dependency); pages are derived from src/pages/*.astro, 404 excluded.
- robots.txt: all bots allowed, AI crawlers/training bots explicitly
  allowed (Linus's decision), /download/ disallowed, sitemap line.
- /llms.txt generated from business.ts, incl. usage-with-attribution note.
- 404.astro in Swedish with links back, noindex.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WvE3C43Uwtv2vtnoFBLDw7
2026-10-27 19:13:07 +01:00
Linus OlofssonandClaude Opus 5.5 30e385ec7f feat(seo): facts in src/data/business.ts, SEO + JSON-LD components in layout
- business.ts: single source of truth; unknown facts (city, area, prices,
  packages, phone, delivery time, experience, testimonials) are null and are
  skipped everywhere. faq.ts builds answers only from known facts.
- portfolio-meta.ts: data hook for real per-image alt texts.
- SEO.astro: title, description, canonical, Open Graph, Twitter, og:image
  (EXIF-free JPG via getImage, absolute URL).
- SchemaOrg.astro: @graph with WebSite, Person, ProfessionalService
  (sameAs, ReserveAction to the customer portal, offer catalog without
  prices), WebPage, optional FAQPage and BreadcrumbList.
- MainLayout: lang=sv, Swedish nav (adds Tjänster), consent banner and
  footer in Swedish, lyxlimo/karleksguiden line removed, info@ address.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WvE3C43Uwtv2vtnoFBLDw7
2026-10-27 19:13:07 +01:00
Claude (for Linus) ce5676f552 Anonymous portfolio statistics (opens/downloads per image, cookieless) + privacy text
Counts go to customer.imagesbyolofsson.se/api/stats/hit via sendBeacon. No cookie, IP or id stored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 11:41:59 +01:00
Linus OlofssonandClaude Opus 5.5 31d4e540ce feat(tracking): Meta Pixel behind cookie consent
- Meta Pixel (2453598878495749) loads only after the visitor clicks Accept;
  choice stored in localStorage (ibo-consent).
- Decline after a previous Accept clears _fbp/_fbc and reloads.
- "Cookie settings" link in the footer reopens the banner.
- Privacy page: removed "no tracking cookies", added Meta Pixel section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 18:11:36 +01:00
imagesbyolofssonandClaude Opus 5.5 3f02b323a3 feat(privacy): privacy policy page + footer link
Needed to publish the townhouse-backup Google app (Drive/Photos backup).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 23:46:24 +00:00
imagesbyolofsson e3c511b5d5 chore(portfolio): sync 2026-09-25 2026-09-25 09:20:03 +00:00
imagesbyolofsson ae51f41fdf chore(portfolio): sync 2026-09-20 2026-09-20 19:20:22 +00:00
portfolio-sync d875ae70d5 style(portfolio): mork hover, pilstatus och klickrorelse
- storleksraderna far en mork hinna vid hover i stallet for ljus
- pilen i knappen gar gra medan menyn ar oppen, vit igen nar den stangs
  (bade vid val av storlek och nar man klickar bort menyn)
- knappen trycks ner lite vid klick (active:scale-95)
2026-09-19 16:33:59 +00:00
portfolio-sync 841d831486 style(portfolio): dampad knapp intill EXIF-raden
- neutral-700 i stallet for helvit sa den inte skriker mot bilden
- tunn svart kant + ring/skugga for en sleak kontur
- knappen grupperad med EXIF i en hogergrupp; footern ar justify-between,
  darfor hamnade den tidigare mitt i raden
- menyn forankras till hoger pa desktop sa den inte gar utanfor kortet
2026-09-19 16:27:15 +00:00
portfolio-sync ce93f90250 style(portfolio): nedladdnings-UI i linje med sajtens formsprak
Knappen var kantig medan allt runt omkring ar rundat. Nu:
- rounded-full + hover:scale-105 som sajtens ovriga CTA-knappar
- nedladdningsikon, uppercase tracking-widest
- menyn rundad med backdrop-blur och mjuk in/ut-overgang
- losenordsrutan rundad med samma kort-kansla som lightboxen
- Escape stanger menyn innan lightboxen
2026-09-19 16:21:53 +00:00
portfolio-sync 59383f703f feat(portfolio): nedladdningsmeny med Small/Medium/Large/Original
DOWNLOAD-knapp i lightboxen oppnar en storleksmeny. S/M/L pekar pa filer i
repot; Original gar till /download/original och oppnar en losenordsruta.
Losenordet sparas i sessionStorage (bara fliken), och HEAD-anrop anvands sa
fel losenord ger 401 utan att dra igang en 70 MB-nedladdning.

🔴 Globen filtrerar nu bort -s/-m ur galleriet - utan det visades varje bild
tre ganger. Variantkartan (assetId -> hashade URL:er) genereras i frontmattern.
2026-09-19 16:11:13 +00:00
portfolio-sync 0d3da3d102 feat(portfolio): small/medium/large nedladdningsstorlekar
Genererar -s (800px) och -m (1600px) vid sidan av befintlig 3000px-fil.
En nedladdning fran Immich -> tre storlekar. Befintliga 82 bilder backfillade.

Original (27-70 MB/st, ~4 GB totalt) ligger INTE i repot utan hamtas live
via original-proxyn pa /download/original, skyddad av losenord + kontroll
att assetet faktiskt bar portfolio-taggen.
2026-09-19 16:03:56 +00:00
imagesbyolofsson c5d4d16596 chore(portfolio): sync 2026-09-19 2026-09-19 00:40:02 +00:00
imagesbyolofsson 1b2f26e7eb chore(portfolio): sync 2026-09-18 2026-09-18 23:45:34 +00:00
imagesbyolofsson e78ac3a4c6 Portfolio: multi-select filter (flera ar + manader samtidigt) 2026-09-15 00:49:41 +00:00
imagesbyolofsson d2de8d6c7e Content: new slogan, Sony A7RV gear, email Admin@imagesbyolofsson.se 2026-09-15 00:30:11 +00:00
Linus Olofssonandportfolio-sync[bot] ab6da0d049 chore(portfolio): sync 33 added, 0 removed from Immich (#36)
Co-authored-by: portfolio-sync[bot] <actions@github.com>
2026-09-06 04:52:10 +02:00
Linus Olofssonandportfolio-sync[bot] 35b45c5bcb chore(portfolio): sync 2 added, 0 removed from Immich (#35)
Co-authored-by: portfolio-sync[bot] <actions@github.com>
2026-07-07 08:48:21 +02:00
Linus Olofssonandportfolio-sync[bot] 0f438c7557 chore(portfolio): sync 41 added, 1 removed from Immich (#34)
Co-authored-by: portfolio-sync[bot] <actions@github.com>
2026-06-22 03:13:27 +02:00
Linus Olofsson b5fbcdfb27 feat(portfolio-sync): skip RAW files elegantly (tag JPEG version instead) (#33)
RAW formats (.CR2/.ARW/etc) can't be converted by sharp/libvips. Skip them
with a clear log instead of counting as failures. Tag the JPEG export instead.
2026-06-22 03:09:35 +02:00
Linus Olofssonandportfolio-sync[bot] 29f3f1a448 chore(portfolio): sync 1 added, 0 removed from Immich (#32)
Co-authored-by: portfolio-sync[bot] <actions@github.com>
2026-06-22 02:06:05 +02:00
Linus Olofsson d45f2e1cd3 fix(ci): Coolify auto-deploy via push + remove broken codeql.yml (#31)
- portfolio-sync: GitHub-runner kan ej nå Tailscale-Coolify, förlitar
  på Coolifys auto-deploy-on-push istället
- ta bort codeql.yml (tom matrix, ogiltig, failade på varje push)
2026-06-22 01:55:54 +02:00
Linus Olofssonandportfolio-sync[bot] a857c5bf2e chore(portfolio): sync 1 added, 0 removed from Immich (#30)
Co-authored-by: portfolio-sync[bot] <actions@github.com>
2026-06-22 01:49:59 +02:00
Linus Olofsson c0a989e088 fix(portfolio-sync): PR + auto-admin-merge (branch protection kräver signering) (#29) 2026-06-22 01:48:43 +02:00
Linus OlofssonandClaude Opus 4.8 dde6155304 feat: automated Immich portfolio sync (GitHub Action) (#28)
GitHub Action that mirrors Immich 'portfolio'-tagged images to the
portfolio gallery. Runs daily + on-demand (workflow_dispatch).
Two-way mirror: tagged added, untagged removed. EXIF preserved.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 01:39:55 +02:00
Linus OlofssonandClaude Opus 4.8 c128093f46 fix: replace favicon with proper branded logo (gradient glow) (#27)
The previous favicon was the dark logo on transparent which was nearly
invisible. This uses the proper logo with purple-cyan gradient glow that
shows clearly against both light and dark browser tabs.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 00:39:54 +02:00
Linus OlofssonandClaude Opus 4.8 bafad2c69e feat: add favicon (Images by Olofsson logo) (#26)
Render logo to square PNG favicons (32/180/512px) and link them in
MainLayout.astro head. Previously the active layout had no favicon.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 00:13:43 +02:00
Linus Olofsson fa3a953a96 feat(footer): hover-glow links to lyxlimo + karleksguiden (#25)
MainLayout footer now includes attribution: 'Crafting digital
experiences for lyxlimo.se & karleksguiden.se' visible on all pages.

On index page only: hovering lyxlimo.se tints hero-logo with
green (#4fd1c5 teal), hovering karleksguiden.se tints with
magenta (#c026d3). Mouseleave restores default white smoky-path
animation. Mechanism reuses existing data-logo-tint attribute on
#text-wrapper.

Removed logo-pulse functionality entirely (threadLogoIn/Out paths,
runLogoPulse function, LOGO_PROBABILITY, updateLogoThreadPaths,
resize/scroll listeners for logo threads). Hover-based UX replaces
the random pulse approach per Linus design preference.
2026-05-21 06:01:25 +02:00
Linus Olofsson fb80bd0f2b feat(logo-threads): bracket-anchored paths with x^2 / -x^2 curves (#24)
Threads now lock to logo bracket pin endpoints (where vertical legs
of ::before / ::after bracket-corners end):
- Rosa tråd: från (0, viewport.height) → (logoLeft, logoTop + 40% height)
  Quadratic Bezier with cp=(endX, startY) = x^2 curve, går rakt först
  och accelererar upp i slutet
- Cyan tråd: från (logoRight, logoBottom - 40% height) → (viewport.width, 0)
  Quadratic Bezier with cp=(startX, endY) = -x^2 curve, accelererar upp
  i början och decelererar mot slutet

Permanent paths som uppdateras vid resize + scroll + 500ms/1500ms
post-load. runLogoPulse triggar update före varje pulse.
2026-05-21 05:34:27 +02:00
Linus Olofsson 336ac26e64 fix(pulse): use SVG-coords directly (no double-transform) (#23)
Pulse-blob is inside the SAME SVG as the path, so applyTransform
should use raw SVG coords (pt.x, pt.y) — not getScreenCTM-converted
ones. Double-transform was pushing pulse to SVG-X 9794+ which renders
off-screen-right.

Also phase2 now uses entryPath-end + exitPath-start positions in
SVG-coord-space so the inside-logo glide is continuous with phase1
and phase3.
2026-05-21 05:10:32 +02:00
Linus Olofsson 2591b46616 fix(logo-threads): Bezier paths pass exactly through logo-center (#22)
Cubic Bezier (C/S) locks Y=287 across X=900..1900 (full logo span).
2026-05-21 05:03:03 +02:00
Linus Olofsson 6e88cf4cd4 fix(logo-threads): Y-align to cross logo (#21)
* fix(pulse): split halves now meet simultaneously + no backtracking

Two bugs fixed after Linus feedback:

1. Path-form: top/bottom split halves used Bezier curves that could
   loop back on themselves when entry.x lay outside box X-range.
   Now use clamped startX/endX (max(xL+r, min(xR-r, entry.x))) so
   pathen alltid rör sig framåt (entry.x → exit.x), aldrig vänder.

2. Synchronized meeting: previously each blob moved at fixed velocity
   along its own path → shorter path arrived first and waited.
   Now both blobs share a phase2T (0→1) progress fraction, so they
   meet at exit-point simultaneously. The path with different length
   simply moves at a slightly different speed (averaged). Phase2
   duration based on avg(topLen,bottomLen)/velocity so total animation
   time stays consistent.

* feat: logo-pulse with pink->green color transition

Adds a 4th pulse target: the IMAGES BY OLOFSSON hero-text logo.
20% chance per pulse-slot when logo is visible (footer scrolled in).

Flow:
1. New purple wave-thread on bottom-left (mirror of cyan wave1 on right)
2. Pulse glides in along purple thread to logo-left edge
3. Pulse enters logo + tints smoky-path stroke pink (#e879f9) + adds
   pink text-shadow halo on h1
4. Mid-passage: tint transitions to green (#4fd1c5) — matches default
   smoke + cyan glow
5. Pulse exits logo via wave1 (existing cyan thread on right)
6. Logo tint reverts to default (white smokeShift animation)

CSS-side: data-logo-tint attribute on #text-wrapper drives stroke +
text-shadow overrides with 0.6-0.8s transitions for smooth fade.

* fix(logo-pulse): dynamic paths anchored to logo + 50% probability

Previous version tried to use existing wave1+threadPurple paths for
logo-pulse glide-in/out, but those paths are in SVG-fixed coordinates
that don't align with logo's viewport position when hero is scrolled
into view. wave1 has translateX(2283) which pushes it off-screen for
ultrawide viewports.

Fix: build dynamic Bezier paths anchored to the logo's actual
getBoundingClientRect() position at pulse-time:
- Entry path: from (offLeft, logoY+dipOffset) curves up into logo-left
- Exit path: from logo-right curves up to (offRight, logoY-riseOffset)

Paths are appended to box-trail-group, used for one pulse, then
removed in cleanup. No reliance on fixed wave-thread coordinates.

Also raises LOGO_PROBABILITY 20%->50% so the effect is visible more
often when hero is in view.

* fix(logo-pulse): visible entry/exit threads + restore pulse color after

Two bugs fixed:

1. Entry+exit paths were stroke=transparent — pulse appeared to spawn
   out of nothing. Now stroke=url(#gradPurple) on entry (subtle pink
   thread visible from off-screen left) and stroke=url(#gradMain) on
   exit (cyan thread visible going off-screen right). Both at
   stroke-width 0.8 + opacity 0.55 to match other wave-thread style.

2. After logo-pulse completed, pulse-gradient stops were left in pink
   state — next regular wave-pulse rendered as stale pink instead of
   cyan/theme color. Now updatePulseColors(false) is called in cleanup
   to restore default cyan-tema.

* fix(logo-pulse): entry/exit threads now properly visible

Previous version used gradient strokes (gradPurple/gradMain) which
have stop-opacity 0 at ends + 0.45 mid, multiplied by path-opacity
0.55 = ~0.25 effective opacity. Practically invisible.

Now solid stroke colors (#e879f9 pink, #4fd1c5 cyan), stroke-width 2
(was 0.8), opacity 0.85 (was 0.55), plus pulseGlowMid filter for soft
halo. Should be clearly visible from off-screen edges to logo.

* feat(logo-pulse): permanent visible threads (no dynamic creation)

Previously dynamic paths were built/destroyed per pulse, were too
thick (stroke-width 2 + glow), and persisted incorrectly causing
visual artifacts.

Now:
- threadLogoIn: permanent purple wave-thread, same style as other
  wave-threads (gradPurple stroke, stroke-width 0.7, opacity 0.55)
- threadLogoOut: permanent cyan wave-thread (gradMain stroke 0.7,
  opacity 0.50)
- runLogoPulse uses these permanent paths via getPointAtLength
- getScreenCTM converts SVG-coords to screen-coords so pulse follows
  thread even with scroll/viewport variance

* fix(logo-threads): align Y-coords to actually cross logo

Logo Y-center is approx 287 in screen-coords. Previous thread Y-coords
(400-600) only grazed logo-bottom or sat entirely below it. Adjusted:

- threadLogoIn: M-1000 500 Q 200 350 850 287 T 2600 200
  Kommer från nedre-vänster (Y=500), kurvar UPP till logo-center
  (Y=287), fortsätter upp till övre-höger (Y=200)
- threadLogoOut: M-1000 350 Q 400 200 850 287 T 2600 500
  Kommer från övre-vänster (Y=350), genom logo-center (Y=287), ner
  till nedre-höger (Y=500) — vertikalt motsatt entry-tråd
2026-05-21 04:51:34 +02:00
Linus Olofsson 650221b74b feat(logo-pulse): permanent visible threads (#20)
* fix(pulse): split halves now meet simultaneously + no backtracking

Two bugs fixed after Linus feedback:

1. Path-form: top/bottom split halves used Bezier curves that could
   loop back on themselves when entry.x lay outside box X-range.
   Now use clamped startX/endX (max(xL+r, min(xR-r, entry.x))) so
   pathen alltid rör sig framåt (entry.x → exit.x), aldrig vänder.

2. Synchronized meeting: previously each blob moved at fixed velocity
   along its own path → shorter path arrived first and waited.
   Now both blobs share a phase2T (0→1) progress fraction, so they
   meet at exit-point simultaneously. The path with different length
   simply moves at a slightly different speed (averaged). Phase2
   duration based on avg(topLen,bottomLen)/velocity so total animation
   time stays consistent.

* feat: logo-pulse with pink->green color transition

Adds a 4th pulse target: the IMAGES BY OLOFSSON hero-text logo.
20% chance per pulse-slot when logo is visible (footer scrolled in).

Flow:
1. New purple wave-thread on bottom-left (mirror of cyan wave1 on right)
2. Pulse glides in along purple thread to logo-left edge
3. Pulse enters logo + tints smoky-path stroke pink (#e879f9) + adds
   pink text-shadow halo on h1
4. Mid-passage: tint transitions to green (#4fd1c5) — matches default
   smoke + cyan glow
5. Pulse exits logo via wave1 (existing cyan thread on right)
6. Logo tint reverts to default (white smokeShift animation)

CSS-side: data-logo-tint attribute on #text-wrapper drives stroke +
text-shadow overrides with 0.6-0.8s transitions for smooth fade.

* fix(logo-pulse): dynamic paths anchored to logo + 50% probability

Previous version tried to use existing wave1+threadPurple paths for
logo-pulse glide-in/out, but those paths are in SVG-fixed coordinates
that don't align with logo's viewport position when hero is scrolled
into view. wave1 has translateX(2283) which pushes it off-screen for
ultrawide viewports.

Fix: build dynamic Bezier paths anchored to the logo's actual
getBoundingClientRect() position at pulse-time:
- Entry path: from (offLeft, logoY+dipOffset) curves up into logo-left
- Exit path: from logo-right curves up to (offRight, logoY-riseOffset)

Paths are appended to box-trail-group, used for one pulse, then
removed in cleanup. No reliance on fixed wave-thread coordinates.

Also raises LOGO_PROBABILITY 20%->50% so the effect is visible more
often when hero is in view.

* fix(logo-pulse): visible entry/exit threads + restore pulse color after

Two bugs fixed:

1. Entry+exit paths were stroke=transparent — pulse appeared to spawn
   out of nothing. Now stroke=url(#gradPurple) on entry (subtle pink
   thread visible from off-screen left) and stroke=url(#gradMain) on
   exit (cyan thread visible going off-screen right). Both at
   stroke-width 0.8 + opacity 0.55 to match other wave-thread style.

2. After logo-pulse completed, pulse-gradient stops were left in pink
   state — next regular wave-pulse rendered as stale pink instead of
   cyan/theme color. Now updatePulseColors(false) is called in cleanup
   to restore default cyan-tema.

* fix(logo-pulse): entry/exit threads now properly visible

Previous version used gradient strokes (gradPurple/gradMain) which
have stop-opacity 0 at ends + 0.45 mid, multiplied by path-opacity
0.55 = ~0.25 effective opacity. Practically invisible.

Now solid stroke colors (#e879f9 pink, #4fd1c5 cyan), stroke-width 2
(was 0.8), opacity 0.85 (was 0.55), plus pulseGlowMid filter for soft
halo. Should be clearly visible from off-screen edges to logo.

* feat(logo-pulse): permanent visible threads (no dynamic creation)

Previously dynamic paths were built/destroyed per pulse, were too
thick (stroke-width 2 + glow), and persisted incorrectly causing
visual artifacts.

Now:
- threadLogoIn: permanent purple wave-thread, same style as other
  wave-threads (gradPurple stroke, stroke-width 0.7, opacity 0.55)
- threadLogoOut: permanent cyan wave-thread (gradMain stroke 0.7,
  opacity 0.50)
- runLogoPulse uses these permanent paths via getPointAtLength
- getScreenCTM converts SVG-coords to screen-coords so pulse follows
  thread even with scroll/viewport variance
2026-05-21 04:47:30 +02:00