Før banebesøk

This commit is contained in:
Erol 2026-04-13 13:51:11 +02:00
parent 94afef6f33
commit 25ca19eba1
9 changed files with 715 additions and 54 deletions

View file

@ -0,0 +1,102 @@
import Link from "next/link";
import InfoPageShell from "@/components/InfoPageShell";
import {
createBreadcrumbJsonLd,
createCollectionPageJsonLd,
createPageMetadata,
} from "@/app/seo";
const pageTitle = "Banebesøk";
const pageDescription =
"Personlige artikler fra golfbaner TeeOff har spilt, med bilder, inntrykk og detaljer som er nyttige før ditt eget besøk.";
const articlePillars = [
{
title: "Baneguide med personlighet",
text: "Hver artikkel skal være mer enn faktaark. Målet er å beskrive flyt, inntrykk og hva som faktisk gjør banen minneverdig.",
},
{
title: "Foto og stemning",
text: "Toppslider, utvalgte bilder og tydelige avsnitt gjør at banebesøk kan fungere både som inspirasjon og som planlegging.",
},
{
title: "Norsk golfkontekst",
text: "Banebesøk skal løfte fram særpreg ved norske anlegg i stedet for å bli en generisk reiseblogg.",
},
];
export const metadata = createPageMetadata({
title: pageTitle,
description: pageDescription,
path: "/banebesok",
});
export default function CourseVisitsPage() {
const collectionJsonLd = createCollectionPageJsonLd({
name: pageTitle,
description: pageDescription,
path: "/banebesok",
});
const breadcrumbJsonLd = createBreadcrumbJsonLd([
{ name: "Hjem", path: "/" },
{ name: "Banebesøk", path: "/banebesok" },
]);
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(collectionJsonLd) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }}
/>
<InfoPageShell
eyebrow="Banebesøk"
title="Golfbaner fortalt som opplevelser"
intro="Banebesøk blir TeeOffs redaksjonelle del: artikler om baner vi faktisk har spilt, med bilder, inntrykk og konkrete ting det er verdt å vite før man drar dit selv."
>
<div className="grid gap-6 xl:grid-cols-[1.3fr,0.9fr]">
<section className="surface-card rounded-[2rem] p-6 sm:p-8">
<p className="text-[11px] font-black uppercase tracking-[0.24em] text-[#8BC34A]">
Første versjon
</p>
<h2 className="mt-4 text-3xl font-black text-[#112015]">Seksjonen er klar til å fylles</h2>
<p className="mt-4 max-w-3xl text-base leading-7 text-[#4F5F50]">
Denne siden er satt opp som eget hjem for lange artikler, banebilder og personlige
vurderinger. Neste steg er å koble en faktisk artikkelmodell med egne URL-er per
banebesøk.
</p>
<div className="mt-8 grid gap-4 md:grid-cols-3">
{articlePillars.map((pillar) => (
<article key={pillar.title} className="rounded-[1.5rem] border border-[#112015]/8 bg-[#F7F9F2] p-5">
<h3 className="text-xl font-black text-[#112015]">{pillar.title}</h3>
<p className="mt-3 text-sm leading-6 text-[#5B675C]">{pillar.text}</p>
</article>
))}
</div>
</section>
<aside className="surface-card rounded-[2rem] p-6 sm:p-8">
<p className="text-[11px] font-black uppercase tracking-[0.24em] text-[#FF5722]">
Neste anbefalte steg
</p>
<h2 className="mt-4 text-2xl font-black text-[#112015]">Bygg artikkelflyten før volumet</h2>
<p className="mt-4 text-sm leading-6 text-[#4F5F50]">
Start med et lite, redigerbart oppsett: ingress, slider, hovedtekst, faktaboks og
bildegalleri. Da blir første publisering enkel å ut uten å låse dere til et tungt
CMS-spor med en gang.
</p>
<Link
href="/kontakt"
className="mt-6 inline-flex items-center rounded-full bg-[#112015] px-5 py-3 text-sm font-black uppercase tracking-[0.16em] text-white transition hover:bg-[#25312A]"
>
Planlegg første artikkel
</Link>
</aside>
</div>
</InfoPageShell>
</>
);
}

View file

@ -0,0 +1,83 @@
import InfoPageShell from "@/components/InfoPageShell";
import {
createBreadcrumbJsonLd,
createCollectionPageJsonLd,
createPageMetadata,
} from "@/app/seo";
const pageTitle = "Klubbnummer";
const pageDescription =
"Klubbnummer blir TeeOffs egen oversikt over NGF- og Golfbox-identifikatorer for norske golfklubber.";
const plannedColumns = ["Klubb", "Klubbnummer", "Sted", "Lenker"];
export const metadata = createPageMetadata({
title: pageTitle,
description: pageDescription,
path: "/klubbnummer",
});
export default function ClubNumbersPage() {
const collectionJsonLd = createCollectionPageJsonLd({
name: pageTitle,
description: pageDescription,
path: "/klubbnummer",
});
const breadcrumbJsonLd = createBreadcrumbJsonLd([
{ name: "Hjem", path: "/" },
{ name: "Klubbnummer", path: "/klubbnummer" },
]);
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(collectionJsonLd) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }}
/>
<InfoPageShell
eyebrow="Klubbnummer"
title="Egen arbeidsflate for klubbnummer"
intro="Denne siden er scaffoldet som hjem for en sorterbar og søkbar oversikt over klubb-ID-er. Den bør bygges som et verktøy, ikke som en vanlig artikkelside."
>
<div className="grid gap-6 xl:grid-cols-[1.2fr,0.8fr]">
<article className="surface-card rounded-[2rem] p-6 sm:p-8">
<p className="text-[11px] font-black uppercase tracking-[0.24em] text-[#8BC34A]">
Planlagt tabell
</p>
<div className="mt-6 grid gap-3">
{plannedColumns.map((column) => (
<div
key={column}
className="flex items-center justify-between rounded-[1.25rem] border border-[#112015]/8 bg-[#F7F9F2] px-4 py-3"
>
<span className="text-sm font-black uppercase tracking-[0.16em] text-[#112015]">
{column}
</span>
<span className="text-xs font-bold text-[#5B675C]">Sortering og filtrering</span>
</div>
))}
</div>
</article>
<article className="surface-card rounded-[2rem] p-6 sm:p-8">
<p className="text-[11px] font-black uppercase tracking-[0.24em] text-[#FF5722]">
Anbefaling
</p>
<h2 className="mt-4 text-2xl font-black text-[#112015]">
Vent med publisering til datakilden er verifisert
</h2>
<p className="mt-4 text-sm leading-6 text-[#4F5F50]">
Dette er en nytteside der feil tall skaper mer frustrasjon enn verdi. Derfor er det
bedre å ha en tydelig struktur klar og koble den mot korrekt datagrunnlag i neste
steg.
</p>
</article>
</div>
</InfoPageShell>
</>
);
}

View file

@ -0,0 +1,82 @@
import InfoPageShell from "@/components/InfoPageShell";
import {
createBreadcrumbJsonLd,
createCollectionPageJsonLd,
createPageMetadata,
} from "@/app/seo";
const pageTitle = "Kontakt TeeOff";
const pageDescription =
"Kontaktpunkter for TeeOff, med lenker til sosiale kanaler og en tydelig plass for fremtidig kontaktinformasjon.";
const contactLinks = [
{
label: "Facebook",
href: "https://www.facebook.com/TeeOff.norge/",
note: "Send melding eller følg oppdateringer.",
},
{
label: "Instagram",
href: "https://www.instagram.com/teeoffno/",
note: "Visuelt innhold og korte oppdateringer.",
},
{
label: "X / Twitter",
href: "https://twitter.com/TeeOffno",
note: "Kortform, delinger og lenker videre.",
},
];
export const metadata = createPageMetadata({
title: pageTitle,
description: pageDescription,
path: "/kontakt",
});
export default function ContactPage() {
const collectionJsonLd = createCollectionPageJsonLd({
name: pageTitle,
description: pageDescription,
path: "/kontakt",
});
const breadcrumbJsonLd = createBreadcrumbJsonLd([
{ name: "Hjem", path: "/" },
{ name: "Kontakt", path: "/kontakt" },
]);
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(collectionJsonLd) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }}
/>
<InfoPageShell
eyebrow="Kontakt"
title="Kontaktpunktene er på plass"
intro="Kontaktsiden er scaffoldet som et tydelig sted for dialog. Foreløpig peker den til TeeOffs sosiale kanaler, og den kan senere utvides med e-post, skjema eller pressehenvendelser."
>
<div className="grid gap-4 lg:grid-cols-3">
{contactLinks.map((link) => (
<a
key={link.href}
href={link.href}
target="_blank"
rel="noreferrer"
className="surface-card rounded-[1.75rem] p-6 transition hover:-translate-y-0.5"
>
<p className="text-[11px] font-black uppercase tracking-[0.24em] text-[#8BC34A]">
Sosial kanal
</p>
<h2 className="mt-3 text-2xl font-black text-[#112015]">{link.label}</h2>
<p className="mt-4 text-sm leading-6 text-[#4F5F50]">{link.note}</p>
</a>
))}
</div>
</InfoPageShell>
</>
);
}

View file

@ -0,0 +1,76 @@
import InfoPageShell from "@/components/InfoPageShell";
import {
createBreadcrumbJsonLd,
createCollectionPageJsonLd,
createPageMetadata,
} from "@/app/seo";
const pageTitle = "FAQ / Om TeeOff";
const pageDescription =
"Hva TeeOff er, hva slags golfinformasjon siden samler, og hvorfor produktet er bygget som en praktisk arbeidsflate for norske golfspillere.";
const faqItems = [
{
question: "Hva er TeeOff?",
answer:
"TeeOff er en norsk oversiktstjeneste for golfbaner, medlemskap, banestatus, Veien til Golf og annen praktisk klubbinfo.",
},
{
question: "Hva skiller TeeOff fra klubb- og Golfbox-sider?",
answer:
"TeeOff samler informasjon på tvers av klubbene, slik at du kan sammenligne og orientere deg raskere før du klikker deg videre.",
},
{
question: "Skal TeeOff også ha redaksjonelt innhold?",
answer:
"Ja. Banebesøk og utvalgte ressursflater skal gi siden mer personlighet og mer verdi enn en ren katalog.",
},
];
export const metadata = createPageMetadata({
title: pageTitle,
description: pageDescription,
path: "/om",
});
export default function AboutPage() {
const collectionJsonLd = createCollectionPageJsonLd({
name: pageTitle,
description: pageDescription,
path: "/om",
});
const breadcrumbJsonLd = createBreadcrumbJsonLd([
{ name: "Hjem", path: "/" },
{ name: "FAQ / Om", path: "/om" },
]);
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(collectionJsonLd) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }}
/>
<InfoPageShell
eyebrow="FAQ / Om"
title="TeeOff skal være nyttig før det skal være pyntet"
intro="Kjernen i TeeOff er å gjøre golfinformasjon enklere å finne, sammenligne og bruke. Derfor er de viktigste flatene bygd som arbeidsverktøy først, og markedsføring etterpå."
>
<div className="grid gap-4">
{faqItems.map((item) => (
<article key={item.question} className="surface-card rounded-[1.75rem] p-6 sm:p-8">
<p className="text-[11px] font-black uppercase tracking-[0.24em] text-[#8BC34A]">
FAQ
</p>
<h2 className="mt-3 text-2xl font-black text-[#112015]">{item.question}</h2>
<p className="mt-4 max-w-4xl text-sm leading-7 text-[#4F5F50]">{item.answer}</p>
</article>
))}
</div>
</InfoPageShell>
</>
);
}

View file

@ -5,7 +5,7 @@ import { createPageMetadata } from "@/app/seo";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
export const metadata = createPageMetadata({ export const metadata = createPageMetadata({
title: "Din guide til norske golfbaner", title: "Komplett oversikt over ALLE norske golfbaner",
description: description:
"Utforsk norske golfanlegg med oppdatert banestatus, kart, priser, medlemskap og Veien til Golf samlet på TeeOff.", "Utforsk norske golfanlegg med oppdatert banestatus, kart, priser, medlemskap og Veien til Golf samlet på TeeOff.",
path: "/", path: "/",

View file

@ -34,6 +34,36 @@ const staticRoutes: MetadataRoute.Sitemap = [
changeFrequency: "daily", changeFrequency: "daily",
priority: 0.8, priority: 0.8,
}, },
{
url: buildAbsoluteUrl("/banebesok"),
lastModified: new Date(),
changeFrequency: "weekly",
priority: 0.72,
},
{
url: buildAbsoluteUrl("/turneringer"),
lastModified: new Date(),
changeFrequency: "daily",
priority: 0.68,
},
{
url: buildAbsoluteUrl("/klubbnummer"),
lastModified: new Date(),
changeFrequency: "weekly",
priority: 0.64,
},
{
url: buildAbsoluteUrl("/om"),
lastModified: new Date(),
changeFrequency: "monthly",
priority: 0.45,
},
{
url: buildAbsoluteUrl("/kontakt"),
lastModified: new Date(),
changeFrequency: "monthly",
priority: 0.42,
},
]; ];
export default async function sitemap(): Promise<MetadataRoute.Sitemap> { export default async function sitemap(): Promise<MetadataRoute.Sitemap> {

View file

@ -0,0 +1,144 @@
import Link from "next/link";
import { API_URL } from "@/config/constants";
import type { FacilityRecord } from "@/app/facilityData";
import InfoPageShell from "@/components/InfoPageShell";
import {
createBreadcrumbJsonLd,
createCollectionPageJsonLd,
createPageMetadata,
} from "@/app/seo";
export const dynamic = "force-dynamic";
const pageTitle = "Turneringer";
const pageDescription =
"Oversikt over norske golfklubber som har publisert turneringslenker via Golfbox, samlet på TeeOff.";
export const metadata = createPageMetadata({
title: pageTitle,
description: pageDescription,
path: "/turneringer",
});
export default async function TournamentsPage() {
let facilities: FacilityRecord[] = [];
try {
const res = await fetch(`${API_URL}/facilities`, {
next: { revalidate: 0 },
cache: "no-store",
});
if (!res.ok) {
throw new Error(`API returnerte status ${res.status}`);
}
const data = await res.json();
facilities = Array.isArray(data) ? data : [];
} catch (error) {
console.error("Kunne ikke hente turneringsdata:", error);
facilities = [];
}
const listings = facilities
.filter(
(facility): facility is FacilityRecord & { golfbox_tournament_url: string } =>
Boolean(facility.slug && facility.golfbox_tournament_url),
)
.sort((a, b) => a.name.localeCompare(b.name, "nb-NO"));
const collectionJsonLd = createCollectionPageJsonLd({
name: pageTitle,
description: pageDescription,
path: "/turneringer",
});
const breadcrumbJsonLd = createBreadcrumbJsonLd([
{ name: "Hjem", path: "/" },
{ name: "Turneringer", path: "/turneringer" },
]);
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(collectionJsonLd) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }}
/>
<InfoPageShell
eyebrow="Turneringer"
title="Turneringslenker fra norske klubber"
intro="Denne siden samler klubber som allerede har en turneringslenke registrert i TeeOff. Det gjør siden nyttig fra første versjon, samtidig som den kan utvides senere med større kalendere og kuraterte turneringsguider."
>
<div className="grid gap-6 xl:grid-cols-[0.85fr,1.15fr]">
<article className="surface-card rounded-[2rem] p-6 sm:p-8">
<p className="text-[11px] font-black uppercase tracking-[0.24em] text-[#8BC34A]">
Klubber med lenke
</p>
<p className="mt-4 text-5xl font-black text-[#112015]">{listings.length}</p>
<p className="mt-4 text-sm leading-6 text-[#4F5F50]">
Hver oppføring peker direkte til klubbens turneringsside i Golfbox og videre til
TeeOffs egen baneside.
</p>
</article>
<article className="surface-card rounded-[2rem] p-6 sm:p-8">
<p className="text-[11px] font-black uppercase tracking-[0.24em] text-[#FF5722]">
Veien videre
</p>
<h2 className="mt-4 text-2xl font-black text-[#112015]">
God nok nytteverdi , enkel å bygge videre senere
</h2>
<p className="mt-4 text-sm leading-6 text-[#4F5F50]">
Neste naturlige steg er filtrering område, åpne eller kommende turneringer og
egne redaksjonelle anbefalinger for turneringer det faktisk er verdt å følge med .
</p>
</article>
</div>
{listings.length > 0 ? (
<div className="mt-8 grid gap-4 md:grid-cols-2 2xl:grid-cols-3">
{listings.map((facility) => (
<article key={facility.slug} className="surface-card rounded-[1.75rem] p-5">
<p className="text-[11px] font-black uppercase tracking-[0.22em] text-[#8BC34A]">
{[facility.county, facility.city].filter(Boolean).join(" • ") || "Norge"}
</p>
<h2 className="mt-3 text-2xl font-black text-[#112015]">{facility.name}</h2>
<p className="mt-3 text-sm leading-6 text-[#5B675C]">
rett til klubbens turneringer i Golfbox, eller åpne TeeOff-siden for resten av
klubbinfoen.
</p>
<div className="mt-5 flex flex-wrap gap-3">
<a
href={facility.golfbox_tournament_url}
target="_blank"
rel="noreferrer"
className="inline-flex items-center rounded-full bg-[#112015] px-4 py-2 text-xs font-black uppercase tracking-[0.16em] text-white transition hover:bg-[#25312A]"
>
Turneringer i Golfbox
</a>
<Link
href={`/golfbaner/${facility.slug}`}
className="inline-flex items-center rounded-full border border-[#112015]/10 px-4 py-2 text-xs font-black uppercase tracking-[0.16em] text-[#112015] transition hover:border-[#FF5722] hover:text-[#FF5722]"
>
Se baneside
</Link>
</div>
</article>
))}
</div>
) : (
<article className="surface-card mt-8 rounded-[2rem] p-6 sm:p-8">
<h2 className="text-2xl font-black text-[#112015]">Ingen turneringslenker registrert ennå</h2>
<p className="mt-4 max-w-3xl text-sm leading-6 text-[#4F5F50]">
Når klubbene får lagt inn turneringslenker i TeeOff-datagrunnlaget, dukker de opp
her automatisk.
</p>
</article>
)}
</InfoPageShell>
</>
);
}

View file

@ -1,7 +1,14 @@
"use client"; "use client";
import Image from "next/image"; import Image from "next/image";
import { useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { useState } from "react";
type NavItem = {
href: string;
label: string;
external?: boolean;
};
const placeGroups = [ const placeGroups = [
{ {
@ -59,21 +66,35 @@ const placeGroups = [
{ href: "/sted/oslo-og-akershus", label: "Oslo og Akershus" }, { href: "/sted/oslo-og-akershus", label: "Oslo og Akershus" },
{ href: "/sted/akershus", label: "Akershus" }, { href: "/sted/akershus", label: "Akershus" },
{ href: "/sted/oslo", label: "Oslo" }, { href: "/sted/oslo", label: "Oslo" },
{ href: "/sted/innlandet", label: "Innlandet" },
{ href: "/sted/viken", label: "Viken" },
], ],
}, },
]; ];
const primaryNavItems: NavItem[] = [
{ href: "/medlemskap", label: "Medlemskap" },
{ href: "/vtg", label: "VTG" },
{ href: "/banebesok", label: "Banebesøk" },
];
const resourceNavItems: NavItem[] = [
{ href: "/turneringer", label: "Turneringer" },
{ href: "/klubbnummer", label: "Klubbnummer" },
{ href: "https://golfquiz.no", label: "Golfquiz.no", external: true },
{ href: "https://golfbox.golf", label: "Golfbox", external: true },
{ href: "/om", label: "FAQ / Om" },
{ href: "/kontakt", label: "Kontakt" },
];
export default function Header() { export default function Header() {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [isPlacesOpen, setIsPlacesOpen] = useState(false); const [isPlacesOpen, setIsPlacesOpen] = useState(false);
const navItems = [ const [isResourcesOpen, setIsResourcesOpen] = useState(false);
{ href: "/", label: "Hjem" },
{ href: "/golfbaner", label: "Golfbaner" }, const closeAllMenus = () => {
{ href: "/medlemskap", label: "Medlemskap" }, setIsOpen(false);
{ href: "/vtg", label: "VTG" }, setIsPlacesOpen(false);
]; setIsResourcesOpen(false);
};
return ( return (
<header className="sticky top-0 z-[2000] border-b border-white/10 bg-[#25312A]/95 text-white shadow-sm backdrop-blur-md"> <header className="sticky top-0 z-[2000] border-b border-white/10 bg-[#25312A]/95 text-white shadow-sm backdrop-blur-md">
@ -90,11 +111,6 @@ export default function Header() {
</Link> </Link>
<nav className="hidden items-center gap-8 text-[12px] font-extrabold uppercase tracking-[0.14em] text-white/90 md:flex"> <nav className="hidden items-center gap-8 text-[12px] font-extrabold uppercase tracking-[0.14em] text-white/90 md:flex">
{navItems.map((item) => (
<Link key={item.href} href={item.href} className="transition hover:text-[#8BC34A]">
{item.label}
</Link>
))}
<div <div
className="group relative" className="group relative"
onMouseEnter={() => setIsPlacesOpen(true)} onMouseEnter={() => setIsPlacesOpen(true)}
@ -103,7 +119,11 @@ export default function Header() {
<button <button
type="button" type="button"
className="flex items-center gap-2 text-[12px] font-extrabold uppercase tracking-[0.14em] transition hover:text-[#8BC34A]" className="flex items-center gap-2 text-[12px] font-extrabold uppercase tracking-[0.14em] transition hover:text-[#8BC34A]"
onClick={() => setIsPlacesOpen((current) => !current)} onClick={() => {
setIsPlacesOpen((current) => !current);
setIsResourcesOpen(false);
}}
aria-expanded={isPlacesOpen}
> >
<span>Steder</span> <span>Steder</span>
<span className={`text-[10px] transition ${isPlacesOpen ? "rotate-180" : ""}`}></span> <span className={`text-[10px] transition ${isPlacesOpen ? "rotate-180" : ""}`}></span>
@ -137,6 +157,67 @@ export default function Header() {
</div> </div>
)} )}
</div> </div>
{primaryNavItems.map((item) => (
<Link key={item.href} href={item.href} className="transition hover:text-[#8BC34A]">
{item.label}
</Link>
))}
<div
className="group relative"
onMouseEnter={() => setIsResourcesOpen(true)}
onMouseLeave={() => setIsResourcesOpen(false)}
>
<button
type="button"
className="flex items-center gap-2 text-[12px] font-extrabold uppercase tracking-[0.14em] transition hover:text-[#8BC34A]"
onClick={() => {
setIsResourcesOpen((current) => !current);
setIsPlacesOpen(false);
}}
aria-expanded={isResourcesOpen}
>
<span>Ressurser</span>
<span className={`text-[10px] transition ${isResourcesOpen ? "rotate-180" : ""}`}></span>
</button>
{isResourcesOpen && (
<div className="absolute right-0 top-full z-[2100] w-[20rem] pt-4">
<div className="rounded-[1.75rem] border border-white/10 bg-[#25312A] p-5 shadow-2xl">
<p className="mb-3 text-[10px] font-extrabold uppercase tracking-[0.2em] text-[#8BC34A]">
Ressurser
</p>
<div className="space-y-2">
{resourceNavItems.map((item) =>
item.external ? (
<a
key={item.href}
href={item.href}
target="_blank"
rel="noreferrer"
className="flex items-center justify-between text-[13px] font-bold normal-case tracking-normal text-white/88 transition hover:text-[#FF5722]"
onClick={() => setIsResourcesOpen(false)}
>
<span>{item.label}</span>
<span aria-hidden="true"></span>
</a>
) : (
<Link
key={item.href}
href={item.href}
className="block text-[13px] font-bold normal-case tracking-normal text-white/88 transition hover:text-[#FF5722]"
onClick={() => setIsResourcesOpen(false)}
>
{item.label}
</Link>
),
)}
</div>
</div>
</div>
)}
</div>
</nav> </nav>
<button onClick={() => setIsOpen(!isOpen)} className="p-2 text-white md:hidden" aria-label="Meny"> <button onClick={() => setIsOpen(!isOpen)} className="p-2 text-white md:hidden" aria-label="Meny">
@ -149,46 +230,79 @@ export default function Header() {
{isOpen && ( {isOpen && (
<div className="absolute left-0 top-20 max-h-[calc(100vh-5rem)] w-full overflow-y-auto border-b border-white/10 bg-[#25312A] px-6 py-6 shadow-2xl md:hidden"> <div className="absolute left-0 top-20 max-h-[calc(100vh-5rem)] w-full overflow-y-auto border-b border-white/10 bg-[#25312A] px-6 py-6 shadow-2xl md:hidden">
<div className="flex flex-col gap-5 pb-6"> <div className="flex flex-col gap-5 pb-6">
{navItems.map((item) => ( <div className="border-b border-white/10 pb-5">
<Link <Link
key={item.href} onClick={closeAllMenus}
onClick={() => setIsOpen(false)} href="/sted/norge"
href={item.href} className="block text-lg font-extrabold uppercase tracking-[0.08em] text-white"
className="text-lg font-extrabold uppercase tracking-[0.08em] text-white" >
> Steder
{item.label} </Link>
</Link> <div className="mt-4 grid gap-4">
))} {placeGroups.map((group) => (
<div className="border-t border-white/10 pt-5"> <div key={group.label}>
<Link <p className="mb-2 text-[10px] font-extrabold uppercase tracking-[0.2em] text-[#8BC34A]">
onClick={() => setIsOpen(false)} {group.label}
href="/sted/norge" </p>
className="block text-lg font-extrabold uppercase tracking-[0.08em] text-white" <div className="space-y-2">
> {group.items.map((item) => (
Steder <Link
</Link> key={item.href}
<div className="mt-4 grid gap-4"> onClick={closeAllMenus}
{placeGroups.map((group) => ( href={item.href}
<div key={group.label}> className="block text-sm font-bold text-white/88"
<p className="mb-2 text-[10px] font-extrabold uppercase tracking-[0.2em] text-[#8BC34A]"> >
{group.label} {item.label}
</p> </Link>
<div className="space-y-2"> ))}
{group.items.map((item) => ( </div>
<Link
key={item.href}
onClick={() => setIsOpen(false)}
href={item.href}
className="block text-sm font-bold text-white/88"
>
{item.label}
</Link>
))}
</div> </div>
</div> ))}
))} </div>
</div>
{primaryNavItems.map((item) => (
<Link
key={item.href}
onClick={closeAllMenus}
href={item.href}
className="text-lg font-extrabold uppercase tracking-[0.08em] text-white"
>
{item.label}
</Link>
))}
<div className="border-t border-white/10 pt-5">
<p className="text-[10px] font-extrabold uppercase tracking-[0.2em] text-[#8BC34A]">
Ressurser
</p>
<div className="mt-4 flex flex-col gap-4">
{resourceNavItems.map((item) =>
item.external ? (
<a
key={item.href}
href={item.href}
target="_blank"
rel="noreferrer"
className="flex items-center justify-between text-sm font-bold text-white/88"
onClick={closeAllMenus}
>
<span>{item.label}</span>
<span aria-hidden="true"></span>
</a>
) : (
<Link
key={item.href}
onClick={closeAllMenus}
href={item.href}
className="text-sm font-bold text-white/88"
>
{item.label}
</Link>
),
)}
</div>
</div> </div>
</div>
</div> </div>
</div> </div>
)} )}

View file

@ -0,0 +1,30 @@
import type { ReactNode } from "react";
type InfoPageShellProps = {
eyebrow: string;
title: string;
intro: string;
children: ReactNode;
};
export default function InfoPageShell({ eyebrow, title, intro, children }: InfoPageShellProps) {
return (
<main className="site-shell min-h-screen">
<section className="border-b border-[#112015]/8 bg-[linear-gradient(135deg,rgba(139,195,74,0.16),rgba(255,255,255,0.92))]">
<div className="mx-auto max-w-[1400px] px-4 py-14 sm:px-6 lg:px-8 lg:py-20">
<div className="max-w-4xl">
<p className="mb-4 text-[11px] font-black uppercase tracking-[0.28em] text-[#8BC34A]">
{eyebrow}
</p>
<h1 className="max-w-3xl text-5xl font-black text-[#112015] sm:text-6xl">{title}</h1>
<p className="mt-6 max-w-3xl text-base leading-7 text-[#4F5F50] sm:text-lg">{intro}</p>
</div>
</div>
</section>
<section className="mx-auto max-w-[1400px] px-4 py-8 sm:px-6 lg:px-8 lg:py-10">
{children}
</section>
</main>
);
}