Files
imagesbyolofsson-se/src/layouts/MainLayout.astro
T
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

183 lines
7.4 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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}
&middot; <a href={`mailto:${business.email}`} class="text-gray-300 hover:text-white transition-colors">{business.email}</a>
&middot; <a href={business.social.instagram.url} target="_blank" rel="noopener noreferrer" class="text-gray-300 hover:text-white transition-colors">Instagram</a>
</p>
<p>&copy; {new Date().getFullYear()} {business.brand}. Alla rättigheter förbehållna. &middot; <a href="/privacy" class="text-gray-300 hover:text-white transition-colors">Integritetspolicy</a> &middot; <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>