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>
183 lines
7.4 KiB
Plaintext
183 lines
7.4 KiB
Plaintext
---
|
||
import "../styles/global.css";
|
||
import type { ImageMetadata } from 'astro';
|
||
import { getImage } from 'astro:assets';
|
||
import SEO from '../components/SEO.astro';
|
||
import SchemaOrg from '../components/SchemaOrg.astro';
|
||
import type { FaqItem } from '../data/faq';
|
||
import { business } from '../data/business';
|
||
import defaultOgImage from '../assets/images/3d-scroll/img5.jpg';
|
||
|
||
export interface Props {
|
||
/** Unik svensk titel för sidan. */
|
||
title: string;
|
||
/** Unik svensk meta description, ca 120–160 tecken. */
|
||
description: string;
|
||
/** Kanonisk sökväg, t.ex. "/portfolio". Utelämnas = härleds från URL:en. */
|
||
path?: string;
|
||
image?: ImageMetadata;
|
||
imageAlt?: string;
|
||
pageType?: 'WebPage' | 'AboutPage' | 'ContactPage' | 'CollectionPage';
|
||
/** Namn i brödsmulan (BreadcrumbList) för undersidor. */
|
||
breadcrumb?: string;
|
||
faq?: FaqItem[];
|
||
noindex?: boolean;
|
||
}
|
||
|
||
const {
|
||
title,
|
||
description,
|
||
path,
|
||
image = defaultOgImage,
|
||
imageAlt = 'Svartvit bild av en man som bär ett litet barn bland såpbubblor i motljus',
|
||
pageType,
|
||
breadcrumb,
|
||
faq,
|
||
noindex = false,
|
||
} = Astro.props;
|
||
|
||
// og:image: ny JPG via Astros bildtjänst (EXIF/GPS rensas bort), absolut URL.
|
||
const site = Astro.site ?? new URL(business.url);
|
||
const og = await getImage({ src: image, width: 1200, format: 'jpg', quality: 82 });
|
||
const ogImageUrl = new URL(og.src, site).toString();
|
||
const ogWidth = Number(og.attributes.width) || undefined;
|
||
const ogHeight = Number(og.attributes.height) || undefined;
|
||
|
||
const nav = [
|
||
{ href: '/portfolio', label: 'Portfolio' },
|
||
{ href: '/posters', label: 'Posters' },
|
||
{ href: '/about', label: 'Om mig' },
|
||
{ href: '/contact', label: 'Kontakt' },
|
||
];
|
||
---
|
||
|
||
<!doctype html>
|
||
<html lang="sv" class="scroll-smooth">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<meta name="theme-color" content="#171717" />
|
||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
|
||
<link rel="icon" type="image/png" sizes="512x512" href="/favicon-512.png" />
|
||
<link rel="apple-touch-icon" sizes="180x180" href="/favicon-180.png" />
|
||
<link rel="sitemap" type="application/xml" href="/sitemap-index.xml" />
|
||
<SEO
|
||
title={title}
|
||
description={description}
|
||
path={path}
|
||
imageUrl={ogImageUrl}
|
||
imageWidth={ogWidth}
|
||
imageHeight={ogHeight}
|
||
imageAlt={imageAlt}
|
||
noindex={noindex}
|
||
/>
|
||
{!noindex && (
|
||
<SchemaOrg
|
||
title={title}
|
||
description={description}
|
||
path={path}
|
||
pageType={pageType}
|
||
breadcrumb={breadcrumb}
|
||
faq={faq}
|
||
imageUrl={ogImageUrl}
|
||
/>
|
||
)}
|
||
</head>
|
||
<body class="bg-neutral-900 text-white font-sans antialiased min-h-screen flex flex-col">
|
||
|
||
<nav class="fixed top-0 w-full z-50 border-b border-white/10 py-4 md:py-6 bg-neutral-900/80 backdrop-blur-md transition-all duration-300">
|
||
<div class="container mx-auto px-4 flex flex-wrap gap-y-2 justify-between items-center">
|
||
|
||
<a href="/" class="text-lg md:text-2xl font-bold tracking-wider uppercase hover:text-gray-300 transition relative z-50">
|
||
Images by Olofsson
|
||
</a>
|
||
|
||
<ul class="flex space-x-4 md:space-x-6 text-sm font-medium text-gray-300">
|
||
{nav.map((item) => (
|
||
<li><a href={item.href} class="hover:text-white transition">{item.label}</a></li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
</nav>
|
||
|
||
<main class="flex-grow pt-28 md:pt-24">
|
||
<slot />
|
||
</main>
|
||
|
||
<footer class="border-t border-neutral-800 py-8 text-center text-gray-400 text-sm bg-neutral-900 relative z-40">
|
||
<p class="mb-2">
|
||
{business.brand} – {business.person.jobTitle.toLowerCase()} {business.person.name}, {business.country.name}
|
||
· <a href={`mailto:${business.email}`} class="text-gray-300 hover:text-white transition-colors">{business.email}</a>
|
||
· <a href={business.social.instagram.url} target="_blank" rel="noopener noreferrer" class="text-gray-300 hover:text-white transition-colors">Instagram</a>
|
||
</p>
|
||
<p>© {new Date().getFullYear()} {business.brand}. Alla rättigheter förbehållna. · <a href="/privacy" class="text-gray-300 hover:text-white transition-colors">Integritetspolicy</a> · <button type="button" data-consent-open class="text-gray-300 hover:text-white transition-colors">Cookie-inställningar</button></p>
|
||
</footer>
|
||
|
||
<!-- Cookie consent: Meta Pixel laddas först efter "Godkänn" -->
|
||
<div data-consent-banner hidden class="fixed bottom-4 inset-x-4 md:left-auto md:right-6 md:max-w-md z-[60] bg-neutral-900/95 backdrop-blur-md border border-white/10 rounded-2xl p-5 shadow-2xl text-sm text-gray-300">
|
||
<p class="mb-4">
|
||
Jag använder Meta Pixel för att se hur besökare från Instagram och Facebook använder sajten. Den sätter cookies och skickar data till Meta.
|
||
<a href="/privacy" class="text-white underline hover:text-gray-300">Läs mer</a>
|
||
</p>
|
||
<div class="flex gap-3 justify-end">
|
||
<button type="button" data-consent="denied" class="px-4 py-2 rounded-full border border-white/20 text-gray-300 hover:text-white hover:border-white/40 transition-colors">Avböj</button>
|
||
<button type="button" data-consent="granted" class="px-4 py-2 rounded-full bg-white text-neutral-900 font-medium hover:bg-gray-200 transition-colors">Godkänn</button>
|
||
</div>
|
||
</div>
|
||
|
||
<script is:inline>
|
||
(function () {
|
||
var KEY = 'ibo-consent';
|
||
var banner = document.querySelector('[data-consent-banner]');
|
||
|
||
function read() { try { return localStorage.getItem(KEY); } catch (e) { return null; } }
|
||
function save(v) { try { localStorage.setItem(KEY, v); } catch (e) {} }
|
||
|
||
function loadPixel() {
|
||
if (window.fbq) return;
|
||
/* Meta Pixel Code */
|
||
!function(f,b,e,v,n,t,s)
|
||
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
|
||
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
|
||
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
|
||
n.queue=[];t=b.createElement(e);t.async=!0;
|
||
t.src=v;s=b.getElementsByTagName(e)[0];
|
||
s.parentNode.insertBefore(t,s)}(window, document,'script',
|
||
'https://connect.facebook.net/en_US/fbevents.js');
|
||
fbq('init', '2453598878495749');
|
||
fbq('track', 'PageView');
|
||
/* End Meta Pixel Code */
|
||
}
|
||
|
||
// Återkallat samtycke: ta bort Metas förstapartscookies.
|
||
function clearMetaCookies() {
|
||
['_fbp', '_fbc'].forEach(function (name) {
|
||
document.cookie = name + '=; Max-Age=0; path=/';
|
||
document.cookie = name + '=; Max-Age=0; path=/; domain=.' + location.hostname.replace(/^www\./, '');
|
||
});
|
||
}
|
||
|
||
var choice = read();
|
||
if (choice === 'granted') loadPixel();
|
||
else if (choice !== 'denied') banner.hidden = false;
|
||
|
||
document.querySelectorAll('[data-consent]').forEach(function (btn) {
|
||
btn.addEventListener('click', function () {
|
||
var v = btn.getAttribute('data-consent');
|
||
var had = read() === 'granted';
|
||
save(v);
|
||
banner.hidden = true;
|
||
if (v === 'granted') loadPixel();
|
||
else if (had) { clearMetaCookies(); location.reload(); }
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('[data-consent-open]').forEach(function (btn) {
|
||
btn.addEventListener('click', function () { banner.hidden = false; });
|
||
});
|
||
})();
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|