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 metadata = createPageMetadata({
title: "Din guide til norske golfbaner",
title: "Komplett oversikt over ALLE norske golfbaner",
description:
"Utforsk norske golfanlegg med oppdatert banestatus, kart, priser, medlemskap og Veien til Golf samlet på TeeOff.",
path: "/",

View file

@ -34,6 +34,36 @@ const staticRoutes: MetadataRoute.Sitemap = [
changeFrequency: "daily",
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> {

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";
import Image from "next/image";
import { useState } from "react";
import Link from "next/link";
import { useState } from "react";
type NavItem = {
href: string;
label: string;
external?: boolean;
};
const placeGroups = [
{
@ -59,21 +66,35 @@ const placeGroups = [
{ href: "/sted/oslo-og-akershus", label: "Oslo og Akershus" },
{ href: "/sted/akershus", label: "Akershus" },
{ 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() {
const [isOpen, setIsOpen] = useState(false);
const [isPlacesOpen, setIsPlacesOpen] = useState(false);
const navItems = [
{ href: "/", label: "Hjem" },
{ href: "/golfbaner", label: "Golfbaner" },
{ href: "/medlemskap", label: "Medlemskap" },
{ href: "/vtg", label: "VTG" },
];
const [isResourcesOpen, setIsResourcesOpen] = useState(false);
const closeAllMenus = () => {
setIsOpen(false);
setIsPlacesOpen(false);
setIsResourcesOpen(false);
};
return (
<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>
<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
className="group relative"
onMouseEnter={() => setIsPlacesOpen(true)}
@ -103,7 +119,11 @@ export default function Header() {
<button
type="button"
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 className={`text-[10px] transition ${isPlacesOpen ? "rotate-180" : ""}`}></span>
@ -137,6 +157,67 @@ export default function Header() {
</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>
<button onClick={() => setIsOpen(!isOpen)} className="p-2 text-white md:hidden" aria-label="Meny">
@ -149,46 +230,79 @@ export default function Header() {
{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="flex flex-col gap-5 pb-6">
{navItems.map((item) => (
<Link
key={item.href}
onClick={() => setIsOpen(false)}
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">
<Link
onClick={() => setIsOpen(false)}
href="/sted/norge"
className="block text-lg font-extrabold uppercase tracking-[0.08em] text-white"
>
Steder
</Link>
<div className="mt-4 grid gap-4">
{placeGroups.map((group) => (
<div key={group.label}>
<p className="mb-2 text-[10px] font-extrabold uppercase tracking-[0.2em] text-[#8BC34A]">
{group.label}
</p>
<div className="space-y-2">
{group.items.map((item) => (
<Link
key={item.href}
onClick={() => setIsOpen(false)}
href={item.href}
className="block text-sm font-bold text-white/88"
>
{item.label}
</Link>
))}
<div className="border-b border-white/10 pb-5">
<Link
onClick={closeAllMenus}
href="/sted/norge"
className="block text-lg font-extrabold uppercase tracking-[0.08em] text-white"
>
Steder
</Link>
<div className="mt-4 grid gap-4">
{placeGroups.map((group) => (
<div key={group.label}>
<p className="mb-2 text-[10px] font-extrabold uppercase tracking-[0.2em] text-[#8BC34A]">
{group.label}
</p>
<div className="space-y-2">
{group.items.map((item) => (
<Link
key={item.href}
onClick={closeAllMenus}
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>
)}

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>
);
}