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
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f452d6d237
commit
206d31d9c2
+135
-105
@@ -3,9 +3,14 @@
|
||||
import MainLayout from '../layouts/MainLayout.astro';
|
||||
import ThreeDScroll from '../components/ThreeDScroll.astro';
|
||||
import WaveBackground from '../components/WaveBackground.astro';
|
||||
import { business, basedIn, genreList } from '../data/business';
|
||||
---
|
||||
|
||||
<MainLayout title="Home | Images by Olofsson">
|
||||
<MainLayout
|
||||
title="Images by Olofsson – fotograf Linus Olofsson i Sverige"
|
||||
description={`Fotograf Linus Olofsson (Images by Olofsson) i ${basedIn()}: porträtt, natur och djur, stad, makro, norrsken och familjefester. Boka via kundportalen.`}
|
||||
path="/"
|
||||
>
|
||||
|
||||
<div id="wave-wrapper" class="fixed inset-0 z-0 pointer-events-none transition-opacity duration-1000 ease-in-out" style="opacity: 0.4;">
|
||||
<WaveBackground opacity="opacity-100" theme="cyan" />
|
||||
@@ -38,7 +43,8 @@ import WaveBackground from '../components/WaveBackground.astro';
|
||||
|
||||
</div>
|
||||
|
||||
<p id="scroll-arrow" class="text-white/70 mt-6 text-lg font-light tracking-widest uppercase transition-opacity duration-300">↓</p>
|
||||
<p class="text-white/80 mt-4 text-base md:text-lg tracking-widest uppercase">Fotograf {business.person.name} · {basedIn()}</p>
|
||||
<p id="scroll-arrow" aria-hidden="true" class="text-white/70 mt-6 text-lg font-light tracking-widest uppercase transition-opacity duration-300">↓</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -52,14 +58,14 @@ import WaveBackground from '../components/WaveBackground.astro';
|
||||
<div class="flex flex-col md:flex-row gap-12 md:gap-24 items-center text-center">
|
||||
|
||||
<div data-trail-box class="flex flex-col items-center px-6 py-4 rounded-2xl">
|
||||
<h2 class="text-3xl md:text-4xl text-white font-bold mb-6">Looking for inspiration?</h2>
|
||||
<h2 class="text-3xl md:text-4xl text-white font-bold mb-6">Letar du inspiration?</h2>
|
||||
<a href="/portfolio" class="inline-block px-8 py-3 bg-white text-black font-bold rounded-full hover:bg-gray-200 transition transform hover:scale-105">
|
||||
My Portfolio
|
||||
Se min portfolio
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row md:flex-col gap-6 items-center justify-center">
|
||||
<a href="https://www.instagram.com/imagesbyolofsson/" target="_blank" rel="noopener noreferrer" class="circle-btn group" aria-label="Instagram">
|
||||
<a href={business.social.instagram.url} target="_blank" rel="noopener noreferrer" class="circle-btn group" aria-label="Instagram">
|
||||
<svg class="outline-ring" viewBox="0 0 100 100">
|
||||
<circle cx="50" cy="50" r="48" />
|
||||
</svg>
|
||||
@@ -69,7 +75,7 @@ import WaveBackground from '../components/WaveBackground.astro';
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
<a href="https://lightroom.adobe.com/u/linusolofsson1" target="_blank" rel="noopener noreferrer" class="circle-btn group" aria-label="Lightroom">
|
||||
<a href={business.social.lightroom} target="_blank" rel="noopener noreferrer" class="circle-btn group" aria-label="Adobe Lightroom">
|
||||
<svg class="outline-ring" viewBox="0 0 100 100">
|
||||
<circle cx="50" cy="50" r="48" />
|
||||
</svg>
|
||||
@@ -80,15 +86,135 @@ import WaveBackground from '../components/WaveBackground.astro';
|
||||
</div>
|
||||
|
||||
<div data-trail-box class="flex flex-col items-center px-6 py-4 rounded-2xl">
|
||||
<h2 class="text-3xl md:text-4xl text-white font-bold mb-6">Ready to collaborate?</h2>
|
||||
<a href="/contact" class="inline-block px-8 py-3 bg-white text-black font-bold rounded-full hover:bg-gray-200 transition transform hover:scale-105">
|
||||
Contact me
|
||||
<h2 class="text-3xl md:text-4xl text-white font-bold mb-6">Vill du boka en fotografering?</h2>
|
||||
<a href="/tjanster" class="inline-block px-8 py-3 bg-white text-black font-bold rounded-full hover:bg-gray-200 transition transform hover:scale-105">
|
||||
Tjänster och bokning
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fakta om vem, vad, var och bokning: läsbar text för besökare, sökmotorer och AI-assistenter. -->
|
||||
<section id="om-images-by-olofsson" class="relative z-30 bg-neutral-900 border-t border-white/10 py-16 md:py-24">
|
||||
<div class="container mx-auto px-4 max-w-5xl grid gap-12 md:grid-cols-2">
|
||||
<div class="space-y-5 text-gray-300 text-lg leading-relaxed">
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-white">Fotograf {business.person.name}</h2>
|
||||
<p>
|
||||
Jag heter {business.person.name} och fotograferar under namnet {business.brand}.
|
||||
Jag är baserad i {basedIn()} och fotograferar {genreList()}.
|
||||
</p>
|
||||
<p>{business.recognition}</p>
|
||||
<p>
|
||||
Jag fotograferar med en {business.camera} och redigerar i {business.editing}, med fokus på naturliga toner och kontrast.
|
||||
</p>
|
||||
<p class="flex flex-wrap gap-x-6 gap-y-2 text-base">
|
||||
<a href="/about" class="text-white underline underline-offset-4 hover:text-gray-300">Mer om mig</a>
|
||||
<a href="/portfolio" class="text-white underline underline-offset-4 hover:text-gray-300">Portfolio</a>
|
||||
<a href={`mailto:${business.email}`} class="text-white underline underline-offset-4 hover:text-gray-300">{business.email}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-5">
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-white">Så bokar du</h2>
|
||||
<ol class="space-y-4 text-gray-300">
|
||||
{business.bookingSteps.map((step, i) => (
|
||||
<li class="flex gap-4">
|
||||
<span class="shrink-0 w-8 h-8 rounded-full border border-white/30 flex items-center justify-center text-sm font-bold text-white">{i + 1}</span>
|
||||
<span><strong class="text-white">{step.title}.</strong> {step.text}</span>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
<div class="flex flex-wrap gap-4 pt-2">
|
||||
<a href={business.portal.bookingUrl} class="inline-block px-6 py-3 bg-white text-black font-bold rounded-full hover:bg-gray-200 transition">Skicka en förfrågan</a>
|
||||
<a href="/tjanster" class="inline-block px-6 py-3 border border-white text-white font-bold rounded-full hover:bg-white/10 transition">Vanliga frågor</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
// SVG Längd
|
||||
const path = document.getElementById('outline-path') as unknown as SVGPathElement;
|
||||
if (path) {
|
||||
const length = path.getTotalLength();
|
||||
path.closest('svg')?.style.setProperty('--length', length.toString());
|
||||
}
|
||||
|
||||
const heroText = document.getElementById('hero-text');
|
||||
const textWrapper = document.getElementById('text-wrapper');
|
||||
const arrow = document.getElementById('scroll-arrow');
|
||||
const smokyPath = document.querySelector('.smoky-path') as SVGElement;
|
||||
const waveWrapper = document.getElementById('wave-wrapper'); // Hämta bakgrunden
|
||||
|
||||
// LOGIK FÖR GRADVIS FADE BASERAT PÅ SCROLL
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrollY = window.scrollY;
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
if (arrow) {
|
||||
arrow.style.opacity = scrollY > 50 ? '0' : '1';
|
||||
}
|
||||
|
||||
if (heroText && !footerIsVisible) {
|
||||
const fadeProgress = Math.min(scrollY / (windowHeight * 3), 1);
|
||||
heroText.style.opacity = fadeProgress.toString();
|
||||
}
|
||||
|
||||
if (scrollY > 2500) {
|
||||
if (smokyPath) smokyPath.style.animationDuration = '4s';
|
||||
} else {
|
||||
if (smokyPath) smokyPath.style.animationDuration = '6s';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// FOOTER INTERAKTION
|
||||
const footer = document.getElementById('footer-section');
|
||||
const imagesContainer = document.getElementById('images-container');
|
||||
let footerIsVisible = false;
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
footerIsVisible = entry.isIntersecting;
|
||||
|
||||
if (entry.isIntersecting) {
|
||||
// -- FOOTER SYNS --
|
||||
if (imagesContainer) imagesContainer.style.opacity = '0';
|
||||
if (heroText) {
|
||||
heroText.style.opacity = '1';
|
||||
heroText.style.transform = 'translateY(-25vh)';
|
||||
}
|
||||
|
||||
// Tänd Brackets
|
||||
if (textWrapper) textWrapper.classList.add('show-brackets');
|
||||
|
||||
// HÖJ OPACITY TILL 90% (Hårdkodat via style)
|
||||
if (waveWrapper) {
|
||||
waveWrapper.style.opacity = '0.9';
|
||||
}
|
||||
|
||||
} else {
|
||||
// -- FOOTER SYNS INTE --
|
||||
if (imagesContainer) imagesContainer.style.opacity = '1';
|
||||
if (heroText) {
|
||||
heroText.style.transform = 'translateY(0)';
|
||||
}
|
||||
|
||||
// Släck Brackets
|
||||
if (textWrapper) textWrapper.classList.remove('show-brackets');
|
||||
|
||||
// ÅTERSTÄLL OPACITY TILL 40% (Hårdkodat via style)
|
||||
if (waveWrapper) {
|
||||
waveWrapper.style.opacity = '0.4';
|
||||
}
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.1 });
|
||||
|
||||
if (footer) observer.observe(footer);
|
||||
</script>
|
||||
|
||||
</MainLayout>
|
||||
|
||||
<style>
|
||||
@@ -213,99 +339,3 @@ import WaveBackground from '../components/WaveBackground.astro';
|
||||
.outline-ring circle { fill: none; stroke: white; stroke-width: 2; stroke-dasharray: 302; stroke-dashoffset: 302; transition: stroke-dashoffset 0.6s cubic-bezier(0.25, 1, 0.5, 1); opacity: 0.8; }
|
||||
.circle-btn:hover .outline-ring circle { stroke-dashoffset: 0; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// SVG Längd
|
||||
const path = document.getElementById('outline-path') as unknown as SVGPathElement;
|
||||
if (path) {
|
||||
const length = path.getTotalLength();
|
||||
path.closest('svg')?.style.setProperty('--length', length.toString());
|
||||
}
|
||||
|
||||
const heroText = document.getElementById('hero-text');
|
||||
const textWrapper = document.getElementById('text-wrapper');
|
||||
const arrow = document.getElementById('scroll-arrow');
|
||||
const smokyPath = document.querySelector('.smoky-path') as SVGElement;
|
||||
const waveWrapper = document.getElementById('wave-wrapper'); // Hämta bakgrunden
|
||||
|
||||
// LOGIK FÖR GRADVIS FADE BASERAT PÅ SCROLL
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrollY = window.scrollY;
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
if (arrow) {
|
||||
arrow.style.opacity = scrollY > 50 ? '0' : '1';
|
||||
}
|
||||
|
||||
if (heroText && !footerIsVisible) {
|
||||
const fadeProgress = Math.min(scrollY / (windowHeight * 3), 1);
|
||||
heroText.style.opacity = fadeProgress.toString();
|
||||
}
|
||||
|
||||
if (scrollY > 2500) {
|
||||
if (smokyPath) smokyPath.style.animationDuration = '4s';
|
||||
} else {
|
||||
if (smokyPath) smokyPath.style.animationDuration = '6s';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// FOOTER INTERAKTION
|
||||
const footer = document.getElementById('footer-section');
|
||||
const imagesContainer = document.getElementById('images-container');
|
||||
let footerIsVisible = false;
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
footerIsVisible = entry.isIntersecting;
|
||||
|
||||
if (entry.isIntersecting) {
|
||||
// -- FOOTER SYNS --
|
||||
if (imagesContainer) imagesContainer.style.opacity = '0';
|
||||
if (heroText) {
|
||||
heroText.style.opacity = '1';
|
||||
heroText.style.transform = 'translateY(-25vh)';
|
||||
}
|
||||
|
||||
// Tänd Brackets
|
||||
if (textWrapper) textWrapper.classList.add('show-brackets');
|
||||
|
||||
// HÖJ OPACITY TILL 90% (Hårdkodat via style)
|
||||
if (waveWrapper) {
|
||||
waveWrapper.style.opacity = '0.9';
|
||||
}
|
||||
|
||||
} else {
|
||||
// -- FOOTER SYNS INTE --
|
||||
if (imagesContainer) imagesContainer.style.opacity = '1';
|
||||
if (heroText) {
|
||||
heroText.style.transform = 'translateY(0)';
|
||||
}
|
||||
|
||||
// Släck Brackets
|
||||
if (textWrapper) textWrapper.classList.remove('show-brackets');
|
||||
|
||||
// ÅTERSTÄLL OPACITY TILL 40% (Hårdkodat via style)
|
||||
if (waveWrapper) {
|
||||
waveWrapper.style.opacity = '0.4';
|
||||
}
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.1 });
|
||||
|
||||
if (footer) observer.observe(footer);
|
||||
|
||||
// FOOTER-LÄNKAR HOVER: ändra logo-glow när användare hovrar lyxlimo/karleksguiden-länkar
|
||||
const glowLinks = document.querySelectorAll<HTMLElement>('[data-glow-link]');
|
||||
glowLinks.forEach(link => {
|
||||
link.addEventListener('mouseenter', () => {
|
||||
if (!textWrapper) return;
|
||||
const tint = link.getAttribute('data-glow-link');
|
||||
if (tint) textWrapper.setAttribute('data-logo-tint', tint);
|
||||
});
|
||||
link.addEventListener('mouseleave', () => {
|
||||
if (!textWrapper) return;
|
||||
textWrapper.removeAttribute('data-logo-tint');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user