Add full trilingual EN/FR/NB support across all pages and data

- Convert all data files (site, profile, norway, jazz, projects, cv) to Record<LocaleCode, string> fields
- Update all page templates to use LocaleCopy component for locale-aware rendering
- Fix CSS specificity conflict: .locale-copy .locale-copy__text (spec 20) now beats container span rules (spec 11)
- Update LocaleSwitcher, BaseLayout, and SectionCard for locale prop types
- Design system overhaul: Special Elite font, burgundy #8f2218, DG seal favicon

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-17 23:23:14 +02:00
parent ffb368670c
commit 2a06888fd6
21 changed files with 1377 additions and 848 deletions
+27 -9
View File
@@ -1,9 +1,27 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" fill="none">
<defs>
<path id="rim-top" d="M 100 100 m -82 0 a 82 82 0 0 1 164 0"></path>
<path id="rim-bot" d="M 100 100 m 82 0 a 82 82 0 0 1 -164 0"></path>
</defs>
<circle cx="100" cy="100" r="92" stroke="#8f2218" stroke-width="1.4"></circle>
<circle cx="100" cy="100" r="84" stroke="#8f2218" stroke-width="0.6"></circle>
<g fill="#8f2218" font-family="Special Elite, Courier, monospace" font-size="11" letter-spacing="3">
<text><textPath href="#rim-top" startOffset="50%" text-anchor="middle">DAVE · GILLIGAN</textPath></text>
<text><textPath href="#rim-bot" startOffset="50%" text-anchor="middle">· KONGSBERG · EDITION ·</textPath></text>
</g>
<path d="M 100 70&#xA; C 120 70 130 88 120 102&#xA; C 112 113 96 113 91 102&#xA; C 87 92 96 84 105 86&#xA; C 113 89 113 99 106 102&#xA; C 100 105 95 100 97 95" stroke="#8f2218" stroke-width="1.6" stroke-linecap="round"></path>
<g font-family="Instrument Serif, serif" fill="#0e0c08">
<text x="100" y="142" font-size="32" text-anchor="middle" letter-spacing="-1">DG</text>
</g>
<g fill="#8f2218">
<circle cx="100" cy="20" r="1.8"></circle>
<circle cx="180" cy="100" r="1.8"></circle>
<circle cx="100" cy="180" r="1.8"></circle>
<circle cx="20" cy="100" r="1.8"></circle>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 749 B

After

Width:  |  Height:  |  Size: 1.4 KiB

+11 -9
View File
@@ -1,12 +1,14 @@
---
import type { LocaleCode } from "../data/locales";
import LocaleCopy from "./LocaleCopy.astro";
import SectionMark from "./SectionMark.astro";
interface Props {
slug: string;
label: string;
title: string;
summary: string;
tone: string;
summary: Record<LocaleCode, string>;
tone: Record<LocaleCode, string>;
href?: string;
}
@@ -59,7 +61,7 @@ const sectionArt: Record<
caption: "French, English, Norwegian, and trouble",
},
"ai-lab": {
src: "/images/ai-lab/corpus-grid.svg",
src: "/assets/ai-lab/corpus-grid.svg",
alt: "Illustrated knowledge corpus grid.",
eyebrow: "Machine room",
caption: "Private memory with cited answers",
@@ -71,7 +73,7 @@ const sectionArt: Record<
caption: "Silver city, civic weather, due process",
},
projects: {
src: "/images/ai-lab/api-flow.svg",
src: "/assets/ai-lab/api-flow.svg",
alt: "Diagram of a connected API workflow.",
eyebrow: "Bench test",
caption: "Products, repairs, and live deployments",
@@ -87,13 +89,13 @@ const sectionArt: Record<
const art = sectionArt[slug];
---
<article class="section-card">
<a href={href} class="section-card">
<div class="section-card__header">
<div class="section-card__stamp">
<SectionMark slug={slug} className="section-mark--card" />
<span class="section-card__label">{label}</span>
</div>
<span class="section-card__tone">{tone}</span>
<span class="section-card__tone"><LocaleCopy copy={tone} /></span>
</div>
{art && (
<figure class="section-card__art">
@@ -105,6 +107,6 @@ const art = sectionArt[slug];
</figure>
)}
<h3>{title}</h3>
<p>{summary}</p>
<a href={href} class="section-card__link">Open section</a>
</article>
<p><LocaleCopy copy={summary} /></p>
<span class="section-card__link">Open section</span>
</a>
+52 -73
View File
@@ -1,10 +1,12 @@
import type { LocaleCode } from "./locales";
export type CvMandate = {
years: string;
role: string;
org: string;
location: string;
summary: string;
detail: string;
summary: Record<LocaleCode, string>;
detail: Record<LocaleCode, string>;
sourceLabel: string;
sourceUrl: string;
};
@@ -14,22 +16,38 @@ export type CvRole = {
role: string;
org: string;
location: string;
summary: string;
bullets: string[];
summary: Record<LocaleCode, string>;
bullets: Record<LocaleCode, string[]>;
};
export type CvSkillTrack = {
label: string;
label: Record<LocaleCode, string>;
items: string[];
};
export const cvHero = {
eyebrow: "Section 11 / curriculum vitae",
title: "The formal record, but still edited like a cover story.",
lede:
"Finance, reinsurance, systems engineering, private AI, multilingual business study, and children's-rights advocacy belong on one page only if the through-line is legible: build the machinery, understand the money, keep the language human.",
note:
"This desk combines the standing employment history from the current public profile with the live venture descriptions now used across the new site.",
eyebrow: { en: "Section 11 / curriculum vitae", fr: "Section 11 / curriculum vitae", nb: "Seksjon 11 / curriculum vitae" },
title: { en: "The formal record, but still edited like a cover story.", fr: "Le dossier formel, mais toujours édité comme une histoire de couverture.", nb: "Den formelle oversikten, men fortsatt redigert som en forsidehistorie." },
lede: { en: "Finance, reinsurance, systems engineering, private AI, multilingual business study, and children's-rights advocacy belong on one page only if the through-line is legible: build the machinery, understand the money, keep the language human.", fr: "Finance, réassurance, ingénierie des systèmes, IA privée, études commerciales multilingues et défense des droits des enfants tiennent sur une seule page seulement si le fil conducteur est lisible : construire la machine, comprendre l'argent, garder le langage humain.", nb: "Finans, reassuranse, systemteknikk, privat AI, flerspråklige forretningsstudier og barns rettighetsarbeid hører hjemme på én side bare hvis den røde tråden er lesbar: bygg maskineriet, forstå pengene, hold språket menneskelig." },
note: { en: "This desk combines the standing employment history from the current public profile with the live venture descriptions now used across the new site.", fr: "Ce bureau combine l'historique d'emploi actuel issu du profil public avec les descriptions des projets en cours désormais utilisées sur le nouveau site.", nb: "Denne desken kombinerer den nåværende ansettelseshistorikken fra den offentlige profilen med de levende prosjektbeskrivelsene som nå brukes på det nye nettstedet." },
};
export const cvHighlights: Record<LocaleCode, string[]> = {
en: [
"Dual Norwegian-American profile shaped across the United States and Europe.",
"Business education in Philadelphia, Brussels, Columbia, Paris, and Krakow.",
"Current work sits at the overlap of AI systems, civic advocacy, and cultural publishing.",
],
fr: [
"Double profil norvégien-américain façonné entre les États-Unis et l'Europe.",
"Formation en commerce à Philadelphie, Bruxelles, Columbia, Paris et Cracovie.",
"Travail actuel à l'intersection des systèmes d'IA, du plaidoyer civique et de l'édition culturelle."
],
nb: [
"Dobbelt norsk-amerikansk profil formet mellom USA og Europa.",
"Forretningsutdanning i Philadelphia, Brussel, Columbia, Paris og Krakow.",
"Nåværende arbeid ligger i skjæringspunktet mellom AI-systemer, samfunnsadvokatvirksomhet og kulturell publisering."
],
};
export const cvMandates: CvMandate[] = [
@@ -38,10 +56,8 @@ export const cvMandates: CvMandate[] = [
role: "Owner",
org: "Blue Note Logic Inc",
location: "Philadelphia to Paris, with EU infrastructure",
summary:
"Private AI platforms, document intelligence, and sovereign infrastructure designed to turn working knowledge into owned systems rather than rented dependency.",
detail:
"Current work centers on CorpusAI, document-grounded retrieval, corporate memory extraction, and EU-hosted deployments that keep data, models, and outcomes under client control.",
summary: { en: "Private AI platforms, document intelligence, and sovereign infrastructure designed to turn working knowledge into owned systems rather than rented dependency.", fr: "Plateformes d'IA privées, intelligence documentaire et infrastructures souveraines conçues pour transformer les connaissances opérationnelles en systèmes possédés plutôt qu'en dépendances louées.", nb: "Private AI-plattformer, dokumentintelligens og suveren infrastruktur designet for å gjøre arbeidskunnskap om til eierskapssystemer i stedet for leieavhengighet." },
detail: { en: "Current work centers on CorpusAI, document-grounded retrieval, corporate memory extraction, and EU-hosted deployments that keep data, models, and outcomes under client control.", fr: "Le travail actuel se concentre sur CorpusAI, la récupération basée sur les documents, l'extraction de mémoire d'entreprise et les déploiements hébergés dans l'UE qui maintiennent les données, les modèles et les résultats sous le contrôle des clients.", nb: "Nåværende arbeid fokuserer på CorpusAI, dokumentbasert gjenfinning, bedriftsminneuttrekk og EU-vertsbaserte distribusjoner som holder data, modeller og resultater under klientens kontroll." },
sourceLabel: "Blue Note Logic official sites",
sourceUrl: "https://ai.bluenotelogic.com/",
},
@@ -50,10 +66,8 @@ export const cvMandates: CvMandate[] = [
role: "CEO & Founder",
org: "Do Better Norge",
location: "Kongsberg, Norway",
summary:
"A civic and advocacy organization focused on parental rights, children's rights, due process, and the treatment of immigrant and international families in Norway.",
detail:
"The public work includes rights explainers, source-based legal materials, media-facing advocacy, and a broader reform effort aimed at family unity and procedural fairness.",
summary: { en: "A civic and advocacy organization focused on parental rights, children's rights, due process, and the treatment of immigrant and international families in Norway.", fr: "Une organisation civique et de plaidoyer axée sur les droits parentaux, les droits des enfants, le respect des procédures légales et le traitement des familles immigrées et internationales en Norvège.", nb: "En samfunns- og interesseorganisasjon med fokus på foreldrerettigheter, barns rettigheter, rettssikkerhet og behandlingen av innvandrer- og internasjonale familier i Norge." },
detail: { en: "The public work includes rights explainers, source-based legal materials, media-facing advocacy, and a broader reform effort aimed at family unity and procedural fairness.", fr: "Le travail public inclut des explications sur les droits, des documents juridiques basés sur des sources, un plaidoyer médiatique et un effort de réforme plus large visant à l'unité familiale et à l'équité procédurale.", nb: "Det offentlige arbeidet inkluderer rettighetsforklaringer, kildebaserte juridiske materialer, medieorientert påvirkning og en bredere reforminnsats rettet mot familiegjenforening og prosessrettferdighet." },
sourceLabel: "Do Better Norge",
sourceUrl: "https://dobetternorge.no/",
},
@@ -62,10 +76,8 @@ export const cvMandates: CvMandate[] = [
role: "Owner",
org: "Gilligan TECH ENK",
location: "Kongsberg, Norway",
summary:
"AI systems engineering for Nordic SMBs: architectural audits, system builds, and fractional CTO support anchored in practical ROI rather than consultant theatre.",
detail:
"The current Gilligan Tech positioning is explicit about sovereign European AI, dedicated infrastructure, and the mix of strategy, SQL, RAG, transcription, and deployment work needed to make AI useful in the real world.",
summary: { en: "AI systems engineering for Nordic SMBs: architectural audits, system builds, and fractional CTO support anchored in practical ROI rather than consultant theatre.", fr: "Ingénierie de systèmes d'IA pour les PME nordiques : audits architecturaux, constructions de systèmes et support CTO fractionné ancré dans un ROI pratique plutôt que dans du théâtre de consultant.", nb: "AI-systemteknikk for nordiske SMB-er: arkitekturgjennomganger, systembygging og delt CTO-støtte forankret i praktisk avkastning på investeringer fremfor konsulentteater." },
detail: { en: "The current Gilligan Tech positioning is explicit about sovereign European AI, dedicated infrastructure, and the mix of strategy, SQL, RAG, transcription, and deployment work needed to make AI useful in the real world.", fr: "Le positionnement actuel de Gilligan Tech est explicite sur l'IA européenne souveraine, les infrastructures dédiées et le mélange de stratégie, SQL, RAG, transcription et travail de déploiement nécessaire pour rendre l'IA utile dans le monde réel.", nb: "Den nåværende posisjoneringen til Gilligan Tech er eksplisitt om suveren europeisk AI, dedikert infrastruktur og blandingen av strategi, SQL, RAG, transkripsjon og distribusjonsarbeid som trengs for å gjøre AI nyttig i den virkelige verden." },
sourceLabel: "Gilligan Tech",
sourceUrl: "https://gilligan.tech/",
},
@@ -77,110 +89,77 @@ export const cvTimeline: CvRole[] = [
role: "Technical Consultant - Finance",
org: "Kvaerner",
location: "Oslo, Norway",
summary:
"Worked with corporate finance, HR, purchasing, cost estimating, MS SQL Server, Promineo, and QlikView to make large industrial bid and cost systems more useful to decision-makers.",
bullets: [
"Reported to the Senior Vice President of Corporate Finance.",
"Helped build the Cost Estimating Project (CEM), a major change in how tenders were issued for offshore platforms and jackets.",
"Linked finance logic, operational data, and what-if analysis across multiple departments.",
],
summary: { en: "Worked with corporate finance, HR, purchasing, cost estimating, MS SQL Server, Promineo, and QlikView to make large industrial bid and cost systems more useful to decision-makers.", fr: "Travaillé avec la finance d'entreprise, les RH, les achats, l'estimation des coûts, MS SQL Server, Promineo et QlikView pour rendre les systèmes d'offres et de coûts industriels plus utiles aux décideurs.", nb: "Jobbet med bedriftsfinans, HR, innkjøp, kostnadsestimering, MS SQL Server, Promineo og QlikView for å gjøre store industrielle anbuds- og kostnadssystemer mer nyttige for beslutningstakere." },
bullets: { en: ["Reported to the Senior Vice President of Corporate Finance.","Helped build the Cost Estimating Project (CEM), a major change in how tenders were issued for offshore platforms and jackets.","Linked finance logic, operational data, and what-if analysis across multiple departments."], fr: ["Rapporté au Vice-Président Senior des Finances d'Entreprise.","A aidé à construire le projet d'estimation des coûts (CEM), un changement majeur dans la manière dont les appels d'offres étaient émis pour les plateformes offshore et les jackets.","Lié la logique financière, les données opérationnelles et l'analyse de scénarios entre plusieurs départements."], nb: ["Rapporterte til Senior Vice President for Corporate Finance.","Bidro til å bygge Cost Estimating Project (CEM), en stor endring i hvordan anbud ble utstedt for offshore-plattformer og -understell.","Koblet finanslogikk, operasjonelle data og hypotetiske analyser på tvers av flere avdelinger."] },
},
{
years: "2008 to 2011",
role: "Owner",
org: "Chloe & Colin",
location: "United States",
summary:
"Operated a general IT consultancy serving small businesses and organizations across practical technology needs before the current AI era.",
bullets: [
"Worked as a hands-on generalist across infrastructure, applications, websites, databases, and operational problem-solving.",
"Helped clients improve reliability, efficiency, and day-to-day use of their systems without unnecessary complexity.",
"A broad-spectrum owner-operator chapter that fills the gap between larger corporate roles and later venture work.",
],
summary: { en: "Operated a general IT consultancy serving small businesses and organizations across practical technology needs before the current AI era.", fr: "Exploité une société de conseil en informatique générale au service des petites entreprises et organisations pour leurs besoins technologiques pratiques avant l'ère actuelle de l'IA.", nb: "Drevet et generelt IT-konsulentfirma som betjente små bedrifter og organisasjoner med praktiske teknologibehov før den nåværende AI-æraen." },
bullets: { en: ["Worked as a hands-on generalist across infrastructure, applications, websites, databases, and operational problem-solving.","Helped clients improve reliability, efficiency, and day-to-day use of their systems without unnecessary complexity.","A broad-spectrum owner-operator chapter that fills the gap between larger corporate roles and later venture work."], fr: ["Travaillé comme généraliste pratique sur les infrastructures, applications, sites web, bases de données et résolution de problèmes opérationnels.","A aidé les clients à améliorer la fiabilité, l'efficacité et l'utilisation quotidienne de leurs systèmes sans complexité inutile.","Un chapitre de propriétaire-opérateur à large spectre comblant l'écart entre les rôles d'entreprise plus importants et le travail de capital-risque ultérieur."], nb: ["Jobbet som en praktisk generalist innen infrastruktur, applikasjoner, nettsteder, databaser og operasjonell problemløsning.","Hjalp klienter med å forbedre pålitelighet, effektivitet og daglig bruk av systemene deres uten unødvendig kompleksitet.","En bredspektret eier-operatør-periode som fylte gapet mellom større bedriftsroller og senere venturearbeid."] },
},
{
years: "2006 to 2007",
role: "Assistant Vice President",
org: "Ariel Re",
location: "Hamilton, Bermuda",
summary:
"Built the IT support structure for AIR and RMS catastrophe risk modeling and ran stochastic analysis on large insured property portfolios.",
bullets: [
"Worked at the junction of underwriters, analysts, and internal software teams.",
"Supported catastrophe modeling across hurricanes, earthquakes, storms, and other modeled events.",
],
summary: { en: "Built the IT support structure for AIR and RMS catastrophe risk modeling and ran stochastic analysis on large insured property portfolios.", fr: "Construit la structure de support informatique pour la modélisation des risques catastrophiques AIR et RMS et réalisé des analyses stochastiques sur de grands portefeuilles de propriétés assurées.", nb: "Bygget IT-støttestrukturen for AIR- og RMS-katastroferisikomodellering og utførte stokastiske analyser på store forsikrede eiendomsporteføljer." },
bullets: { en: ["Worked at the junction of underwriters, analysts, and internal software teams.","Supported catastrophe modeling across hurricanes, earthquakes, storms, and other modeled events."], fr: ["Travaillé à la jonction des souscripteurs, analystes et équipes logicielles internes.","Soutenu la modélisation des catastrophes pour les ouragans, tremblements de terre, tempêtes et autres événements modélisés."], nb: ["Jobbet i skjæringspunktet mellom forsikringsgivere, analytikere og interne programvareteam.","Støttet katastrofemodellering for orkaner, jordskjelv, stormer og andre modellerte hendelser."] },
},
{
years: "2001 to 2006",
role: "Assistant Secretary",
org: "Folksamerica / White Mountains Re",
location: "New York City, New York",
summary:
"Analyzed property treaty reinsurance risk while helping design the infrastructure around large-scale actuarial and underwriting work.",
bullets: [
"Worked closely with senior management, underwriters, and actuarial staff on pricing and risk analysis.",
"Helped shape a multi-tiered risk management environment with roughly 30 data-analysis engines and 4 MS SQL database servers.",
"Supported both Windows client and IIS-based global web interfaces.",
],
summary: { en: "Analyzed property treaty reinsurance risk while helping design the infrastructure around large-scale actuarial and underwriting work.", fr: "Analysé les risques de réassurance des traités immobiliers tout en aidant à concevoir l'infrastructure autour des travaux actuariels et de souscription à grande échelle.", nb: "Analyserte risikoen for eiendomsgjenforsikring mens jeg bidro til å designe infrastrukturen rundt storskala aktuarmessig og underwriting-arbeid." },
bullets: { en: ["Worked closely with senior management, underwriters, and actuarial staff on pricing and risk analysis.","Helped shape a multi-tiered risk management environment with roughly 30 data-analysis engines and 4 MS SQL database servers.","Supported both Windows client and IIS-based global web interfaces."], fr: ["Travaillé en étroite collaboration avec la direction, les souscripteurs et le personnel actuariel sur la tarification et l'analyse des risques.","A aidé à façonner un environnement de gestion des risques à plusieurs niveaux avec environ 30 moteurs d'analyse de données et 4 serveurs de bases de données MS SQL.","Soutenu à la fois les interfaces globales Windows client et IIS basées sur le web."], nb: ["Jobbet tett med toppledelsen, forsikringsgivere og aktuarmessig personale om prising og risikoberegning.","Bidro til å forme et flernivå risikostyringsmiljø med omtrent 30 dataanalyse-motorer og 4 MS SQL-databaseservere.","Støttet både Windows-klient og IIS-baserte globale webgrensesnitt."] },
},
{
years: "1998 to 1999",
role: "Intern for CFO",
org: "Zurich France / Zurich Financial Services",
location: "Paris, France",
summary:
"Graduate internship in the CFO's orbit during the French Franc to Euro transition and the Year 2000 remediation cycle.",
bullets: [
"Worked on specialist project teams related to Euro conversion and Y2K readiness.",
"Built the \"Euro 1999\" client and partner sub-site in Lotus Notes.",
],
summary: { en: "Graduate internship in the CFO's orbit during the French Franc to Euro transition and the Year 2000 remediation cycle.", fr: "Stage de fin d'études dans l'orbite du CFO pendant la transition du Franc français à l'Euro et le cycle de remédiation de l'An 2000.", nb: "Graduate internship i CFOs sfære under overgangen fra franske franc til euro og år 2000-oppgraderingssyklusen." },
bullets: { en: ["Worked on specialist project teams related to Euro conversion and Y2K readiness.","Built the \"Euro 1999\" client and partner sub-site in Lotus Notes."], fr: ["Travaillé sur des équipes de projets spécialisés liés à la conversion à l'Euro et à la préparation au passage à l'An 2000.","Construit le sous-site client et partenaire \"Euro 1999\" dans Lotus Notes."], nb: ["Jobbet i spesialistprosjektteam relatert til euro-konvertering og Y2K-beredskap.","Bygget \"Euro 1999\" klient- og partnerunderside i Lotus Notes."] },
},
];
export const cvSkillTracks: CvSkillTrack[] = [
{
label: "Operating languages",
label: { en: "Operating languages", fr: "Langues de travail", nb: "Arbeidsspråk" },
items: ["English", "French", "Norwegian", "SQL", "the language of finance"],
},
{
label: "Technical stack",
label: { en: "Technical stack", fr: "Stack technique", nb: "Teknisk stack" },
items: ["MS SQL / MariaDB / MySQL", "PHP", "Python", "JavaScript / React / Astro", "RAG systems"],
},
{
label: "Working domains",
label: { en: "Working domains", fr: "Domaines de travail", nb: "Arbeidsdomener" },
items: ["Global finance", "Reinsurance", "Private AI", "Document intelligence", "Family-rights advocacy"],
},
];
export const cvHighlights = [
"Dual Norwegian-American profile shaped across the United States and Europe.",
"Business education in Philadelphia, Brussels, Columbia, Paris, and Krakow.",
"Current work sits at the overlap of AI systems, civic advocacy, and cultural publishing.",
];
export const cvSourceNotes = [
{
label: "Current public profile API",
url: "http://davegilligan.local/api/content.php?lang=en",
note:
"Used for the standing public experience history, headline language, education chronology, and contact references from the current local site.",
note: "Used for the standing public experience history, headline language, education chronology, and contact references from the current local site.",
},
{
label: "Gilligan Tech",
url: "https://gilligan.tech/",
note:
"Used for the current Gilligan TECH ENK positioning, Kongsberg-based identity, and the AI Dispatch / Systems Forge / Systems Command model.",
note: "Used for the current Gilligan TECH ENK positioning, Kongsberg-based identity, and the AI Dispatch / Systems Forge / Systems Command model.",
},
{
label: "Blue Note Logic / CorpusAI",
url: "https://ai.bluenotelogic.com/",
note:
"Used for the current Blue Note Logic mandate around private AI, document intelligence, and source-cited retrieval.",
note: "Used for the current Blue Note Logic mandate around private AI, document intelligence, and source-cited retrieval.",
},
{
label: "Do Better Norge",
url: "https://dobetternorge.no/",
note:
"Used for the public advocacy scope around parental rights, children, family unity, and due process in Norway.",
note: "Used for the public advocacy scope around parental rights, children, family unity, and due process in Norway.",
},
];
+55 -100
View File
@@ -1,3 +1,5 @@
import type { LocaleCode } from "./locales";
export type JazzImage = {
src: string;
alt: string;
@@ -11,23 +13,20 @@ export type JazzImage = {
export type JazzVenue = {
name: string;
href: string;
summary: string;
summary: Record<LocaleCode, string>;
};
export const jazzHero = {
eyebrow: "Section 06 / jazz and music",
title: "Pataphysics by the silver river, with one ear still turned toward the Caveau.",
lede:
"The jazz desk now has a proper field note: half Latin Quarter memory, half Kongsberg route map, with a little smoke from rue Saint-Jacques still caught in the coat lining.",
note:
"Built around official Kongsberg Jazz Festival sources for 2026, with venue links and openly licensed imagery where possible.",
eyebrow: { en: "Section 06 / jazz and music", fr: undefined, nb: undefined },
title: { en: "Pataphysics by the silver river, with one ear still turned toward the Caveau.", fr: "Pataphysique au bord de la rivière argentée, avec une oreille encore tournée vers le Caveau.", nb: "Pataphysikk ved den sølvfargede elven, med ett øre fortsatt vendt mot Caveau." },
lede: { en: "The jazz desk now has a proper field note: half Latin Quarter memory, half Kongsberg route map, with a little smoke from rue Saint-Jacques still caught in the coat lining.", fr: "Le bureau jazz a enfin une note de terrain digne de ce nom : moitié souvenir du Quartier Latin, moitié carte des itinéraires de Kongsberg, avec un peu de fumée de la rue Saint-Jacques encore prise dans la doublure du manteau.", nb: "Jazzkontoret har nå en ordentlig feltnotat: halvveis et minne fra Latinerkvarteret, halvveis et kart over Kongsbergs ruter, med litt røyk fra rue Saint-Jacques fortsatt fanget i frakkens fôr." },
note: { en: "Built around official Kongsberg Jazz Festival sources for 2026, with venue links and openly licensed imagery where possible.", fr: "Construit autour des sources officielles du Kongsberg Jazz Festival pour 2026, avec des liens vers les lieux et des images sous licence libre lorsque cela est possible.", nb: "Bygget rundt offisielle kilder fra Kongsberg Jazzfestival for 2026, med lenker til arenaer og åpent lisensierte bilder der det er mulig." },
};
export const jazzArticle = {
title: "A Pataphysical Field Guide to Kongsberg Jazz 2026",
publishedAt: "2026-04-06 08:15:00",
excerpt:
"A personal route from the Caveau de la Huchette to Kongsberg Jazzfestival 2026, with notes on Samara Joy, Snarky Puppy, Mezzoforte, Kurt Rosenwinkel, Jazzbox free shows, and the venues that keep the town musically honest.",
excerpt: { en: "A personal route from the Caveau de la Huchette to Kongsberg Jazzfestival 2026, with notes on Samara Joy, Snarky Puppy, Mezzoforte, Kurt Rosenwinkel, Jazzbox free shows, and the venues that keep the town musically honest.", fr: "Un itinéraire personnel du Caveau de la Huchette au Kongsberg Jazzfestival 2026, avec des notes sur Samara Joy, Snarky Puppy, Mezzoforte, Kurt Rosenwinkel, les concerts gratuits Jazzbox, et les lieux qui maintiennent l'intégrité musicale de la ville.", nb: "En personlig rute fra Caveau de la Huchette til Kongsberg Jazzfestival 2026, med notater om Samara Joy, Snarky Puppy, Mezzoforte, Kurt Rosenwinkel, gratis Jazzbox-konserter og arenaene som holder byen musikalsk ærlig." },
};
export const jazzImages: JazzImage[] = [
@@ -36,11 +35,9 @@ export const jazzImages: JazzImage[] = [
alt: "Concert photograph from Christians Kjeller during Kongsberg Jazzfestival 2018.",
credit: "Tore Saetre",
license: "CC BY-SA 4.0",
sourceUrl:
"https://commons.wikimedia.org/wiki/File:Hans_Cato_Kristiansen_Kongsberg_Jazzfestival_2018_(223526).jpg",
sourceUrl: "https://commons.wikimedia.org/wiki/File:Hans_Cato_Kristiansen_Kongsberg_Jazzfestival_2018_(223526).jpg",
sourceLabel: "Wikimedia Commons",
note:
"Used here as an atmospheric Christians Kjeller / Kongsberg Jazz image rather than a 2026 festival still.",
note: "Used here as an atmospheric Christians Kjeller / Kongsberg Jazz image rather than a 2026 festival still.",
},
{
src: "https://commons.wikimedia.org/wiki/Special:Redirect/file/Le_Caveau_de_la_Huchette.jpg",
@@ -53,100 +50,25 @@ export const jazzImages: JazzImage[] = [
];
export const jazzPicks = [
{
day: "Wednesday 1 July 2026",
artist: "Samara Joy",
detail: "Opening concert at Kongsberg Musikkteater, followed later that night by Ghosted at Energimolla.",
href: "https://kongsbergjazz.no/program/",
},
{
day: "Thursday 2 July 2026",
artist: "Bobo Stenson Trio / Snarky Puppy / Mezzoforte",
detail: "A day that moves from piano intelligence to large-room propulsion and then into late-night club lift at Christians Kjeller.",
href: "https://kongsbergjazz.no/program/",
},
{
day: "Friday 3 July 2026",
artist: "Wesseltoft-Andersen-Nilssen Trio / Wibutee / Matoma",
detail: "The sort of Friday where one can begin in church-space seriousness and end outdoors at Gamle Norge with the town in full chorus.",
href: "https://kongsbergjazz.no/program/",
},
{
day: "Saturday 4 July 2026",
artist: "Silya & Kongsberg Storband / Kurt Rosenwinkel / Nils Petter Molvaer / Silje Nergaard",
detail: "The last day plays like a designed crescendo: big-band daylight, guitar dusk, electric weather, then midnight vocals.",
href: "https://kongsbergjazz.no/program/",
},
{ day: "Wednesday 1 July 2026", artist: "Samara Joy", detail: { en: "Opening concert at Kongsberg Musikkteater, followed later that night by Ghosted at Energimolla.", fr: "Concert d'ouverture au Kongsberg Musikkteater, suivi plus tard dans la soirée par Ghosted à l'Energimolla.", nb: "Åpningskonsert på Kongsberg Musikkteater, etterfulgt senere samme kveld av Ghosted på Energimolla." }, href: "https://kongsbergjazz.no/program/" },
{ day: "Thursday 2 July 2026", artist: "Bobo Stenson Trio / Snarky Puppy / Mezzoforte", detail: { en: "A day that moves from piano intelligence to large-room propulsion and then into late-night club lift at Christians Kjeller.", fr: "Une journée qui passe de l'intelligence du piano à la propulsion des grandes salles, puis à l'élévation nocturne du club Christians Kjeller.", nb: "En dag som beveger seg fra piano-intelligens til storroms-propulsjon og deretter til nattklubb-løft på Christians Kjeller." }, href: "https://kongsbergjazz.no/program/" },
{ day: "Friday 3 July 2026", artist: "Wesseltoft-Andersen-Nilssen Trio / Wibutee / Matoma", detail: { en: "The sort of Friday where one can begin in church-space seriousness and end outdoors at Gamle Norge with the town in full chorus.", fr: "Le genre de vendredi où l'on peut commencer dans la gravité d'un espace sacré et finir en plein air à Gamle Norge avec la ville en chœur.", nb: "En slags fredag hvor man kan starte med alvor i kirkerommet og avslutte utendørs på Gamle Norge med byen i full kor." }, href: "https://kongsbergjazz.no/program/" },
{ day: "Saturday 4 July 2026", artist: "Silya & Kongsberg Storband / Kurt Rosenwinkel / Nils Petter Molvaer / Silje Nergaard", detail: { en: "The last day plays like a designed crescendo: big-band daylight, guitar dusk, electric weather, then midnight vocals.", fr: "Le dernier jour joue comme un crescendo conçu : lumière du jour en big band, crépuscule de guitare, météo électrique, puis voix de minuit.", nb: "Den siste dagen spiller som en designet crescendo: storband i dagslys, gitar i skumringen, elektrisk vær, og deretter midnattssang." }, href: "https://kongsbergjazz.no/program/" },
];
export const jazzFreeNotes = [
{
title: "Jazzbox / Jazzboksen",
summary:
"Free concerts every day during the festival, with students, semi-professionals and amateurs onstage, no reservation required, and a proper social center in the middle of town.",
href: "https://kongsbergjazz.no/en/faq/jazzboksen/",
},
{
title: "Jazzmine",
summary:
"A free Friday session in the Jazzbox area that points toward the new Magasinet / Fellesbrukskrysset idea as a future festival midpoint.",
href: "https://kongsbergjazz.no/events/jazzmine/",
},
{ title: { en: "Jazzbox / Jazzboksen", fr: "Jazzbox / Jazzboksen", nb: "Jazzbox / Jazzboksen" }, summary: { en: "Free concerts every day during the festival, with students, semi-professionals and amateurs onstage, no reservation required, and a proper social center in the middle of town.", fr: "Concerts gratuits tous les jours pendant le festival, avec des étudiants, semi-professionnels et amateurs sur scène, sans réservation requise, et un véritable centre social au cœur de la ville.", nb: "Gratis konserter hver dag under festivalen, med studenter, semi-profesjonelle og amatører på scenen, ingen reservasjon nødvendig, og et ordentlig sosialt sentrum midt i byen." }, href: "https://kongsbergjazz.no/en/faq/jazzboksen/" },
{ title: { en: "Jazzmine", fr: "Jazzmine", nb: "Jazzmine" }, summary: { en: "A free Friday session in the Jazzbox area that points toward the new Magasinet / Fellesbrukskrysset idea as a future festival midpoint.", fr: "Une session gratuite le vendredi dans la zone Jazzbox qui pointe vers la nouvelle idée Magasinet / Fellesbrukskrysset comme futur centre du festival.", nb: "En gratis fredagssession i Jazzbox-området som peker mot den nye Magasinet / Fellesbrukskrysset-ideen som et fremtidig festivalmidtpunkt." }, href: "https://kongsbergjazz.no/events/jazzmine/" },
];
export const jazzVenues: JazzVenue[] = [
{
name: "Privat Bar",
href: "https://www.privatbar.no/",
summary:
"Privat Bar describes itself as scene, sport, cocktail bar, gastropub and courtyard venue, a place built for after-hours momentum.",
},
{
name: "Gamle Norge / Folkefestscenen",
href: "https://kongsbergjazz.no/spillesteder/folkefestscenen-gamle-norge/",
summary:
"The festival calls it an outdoor stage between Gamle Norge and Trattoria Madre, designed for familiar names, sing-along lift and party atmosphere.",
},
{
name: "Opsahlgarden / Christians Kjeller",
href: "https://kongsbergjazz.no/hei-christian-kjeller/",
summary:
"Christian's Kjeller sits inside Opsahlgarden on Vestsida: restaurant upstairs, gastro/music pub in the basement, brewery in the mix, and a long memory for late sets.",
},
{ name: "Privat Bar", href: "https://www.privatbar.no/", summary: { en: "Privat Bar describes itself as scene, sport, cocktail bar, gastropub and courtyard venue, a place built for after-hours momentum.", fr: "Privat Bar se décrit comme une scène, un bar sportif, un bar à cocktails, un gastropub et un lieu en cour intérieure, un endroit conçu pour le momentum après les heures.", nb: "Privat Bar beskriver seg selv som scene, sportsbar, cocktailbar, gastropub og gårdsplass-venue, et sted bygget for etter-timers momentum." } },
{ name: "Gamle Norge / Folkefestscenen", href: "https://kongsbergjazz.no/spillesteder/folkefestscenen-gamle-norge/", summary: { en: "The festival calls it an outdoor stage between Gamle Norge and Trattoria Madre, designed for familiar names, sing-along lift and party atmosphere.", fr: "Le festival le décrit comme une scène extérieure entre Gamle Norge et Trattoria Madre, conçue pour des noms familiers, une ambiance chantante et une atmosphère festive.", nb: "Festivalen kaller det en utendørsscene mellom Gamle Norge og Trattoria Madre, designet for kjente navn, allsang-løft og feststemning." } },
{ name: "Opsahlgarden / Christians Kjeller", href: "https://kongsbergjazz.no/hei-christian-kjeller/", summary: { en: "Christian's Kjeller sits inside Opsahlgarden on Vestsida: restaurant upstairs, gastro/music pub in the basement, brewery in the mix, and a long memory for late sets.", fr: "Christian's Kjeller se trouve à l'intérieur d'Opsahlgarden sur Vestsida : restaurant à l'étage, pub gastro/musical au sous-sol, brasserie dans le mélange, et une longue mémoire pour les sets tardifs.", nb: "Christian's Kjeller ligger inne i Opsahlgarden på Vestsida: restaurant oppe, gastro/musikkpub i kjelleren, bryggeri i miksen, og en lang hukommelse for sene sett." } },
];
export const jazzSources = [
{
label: "Kongsberg Jazzfestival programme 2026",
href: "https://kongsbergjazz.no/program/",
},
{
label: "Kongsberg Jazzfestival / Folkefestscenen Gamle Norge",
href: "https://kongsbergjazz.no/spillesteder/folkefestscenen-gamle-norge/",
},
{
label: "Kongsberg Jazzfestival / Hei Privat Bar",
href: "https://kongsbergjazz.no/hei-privat-bar/",
},
{
label: "Kongsberg Jazzfestival / Hei Christian Kjeller",
href: "https://kongsbergjazz.no/hei-christian-kjeller/",
},
{
label: "Kongsberg Jazzfestival / Jazzbox",
href: "https://kongsbergjazz.no/en/faq/jazzboksen/",
},
{
label: "Kongsberg Jazzfestival / Jazzmine",
href: "https://kongsbergjazz.no/events/jazzmine/",
},
{
label: "Caveau de la Huchette",
href: "https://www.caveaudelahuchette.fr/",
},
];
export const jazzBody = [
export const jazzBody: Record<LocaleCode, string[]> = {
en: [
"There are people who approach a jazz festival with a spreadsheet, and there are people who approach it the way one enters a cellar in Paris after midnight: not to control the night, but to meet it properly. I belong to the second camp, even when the first camp pays the invoices. So yes, Kongsberg Jazzfestival 2026 deserves practical planning. But it also deserves a little pataphysics: the science of exceptions, the doctrine of beloved detours, the right to follow one brass phrase into another street and call that method.",
"My own measuring stick is still French and subterranean. For a time I lived at 23 rue Saint-Jacques, with the Caveau de la Huchette close enough to count as neighborhood weather. The Caveau at 5 rue de la Huchette still presents itself as the temple of swing, open all year, every night, a place where Paris and jazz agree to keep dancing without asking permission. Once you have learned to think of a city through such a room, every later festival is judged by whether it contains at least one staircase into a better mood.",
"Kongsberg, fortunately, understands this. The official 2026 programme already reads like a civilised argument between polish and danger. On Wednesday 1 July, Samara Joy opens at Kongsberg Musikkteater, and Ghosted follows at Energimolla. Thursday 2 July offers Bobo Stenson Trio, ganavya, Snarky Puppy and late-night Mezzoforte at Christians Kjeller. Friday 3 July runs from Wesseltoft-Andersen-Nilssen Trio and Wibutee to the larger public-weather of Rotlaus and Matoma at Gamle Norge. Saturday 4 July closes with Silya & Kongsberg Storband, Kurt Rosenwinkel, Nils Petter Molvaer and a midnight Silje Nergaard set. That is not a thin programme. That is a town temporarily edited by rhythm.",
@@ -155,5 +77,38 @@ export const jazzBody = [
"Then there is Christians Kjeller, which matters to me more than the poster typography. The festival's portrait of the venue places it inside Opsahlgarden on Vestsida, one of the town's older preserved houses, restored and reopened as restaurant in 2001. The house carries restaurant, brewery and basement music-pub energy all at once. That combination is morally sound. If Gamle Norge is the public square in summer clothes, Christians Kjeller is the lower room where style loosens its tie and means it. Mezzoforte on Thursday night, Poesioasen with Ine Hoem and Edvard Hoem on Saturday afternoon, and Pumpegris later that same night: this is exactly the kind of sequencing one wants from a compact jazz city.",
"And then there is the democratic correction: the free programme. Jazzbox / Jazzboksen is still one of the best arguments for the whole festival, because it gives students, semi-professionals and amateurs a real audience rather than an educational corner. The official festival note is clear: free concerts every day, no reservation, just turn up, with room to lounge, drift, listen, dance and discover tomorrow's names before they become brochure adjectives. Jazzmine, listed as a free Friday happening, extends that logic into the new Magasinet / Fellesbrukskrysset center. Every good festival needs paid monuments; every great one also needs zones where curiosity remains inexpensive.",
"So the proper route, at least in my private cosmology, is not to choose between the polished and the unruly. It is to move among them. Begin with shape and listening. Allow Samara Joy or Bobo Stenson to set the grammar. Slip into Privat Bar for the social murmur. Let Gamle Norge provide the public proof that joy also scales. End in Christians Kjeller when the evening starts to remember its own pulse. And if the day needs a reset, take the free path through Jazzbox and Jazzmine, where the future is usually less expensive and often more alive.",
"Boris Vian would probably recommend a trumpet, a bad idea and a better jacket. Vernon Sullivan would tell you to stay out too late and call it field research. I will settle for a calmer prescription: Kongsberg Jazzfestival, 1 to 4 July 2026, looks like the kind of week that lets a town become multiple versions of itself at once. That is close enough to pataphysics for me, and certainly close enough to jazz.",
"Boris Vian would probably recommend a trumpet, a bad idea and a better jacket. Vernon Sullivan would tell you to stay out too late and call it field research. I will settle for a calmer prescription: Kongsberg Jazzfestival, 1 to 4 July 2026, looks like the kind of week that lets a town become multiple versions of itself at once. That is close enough to pataphysics for me, and certainly close enough to jazz."
],
fr: [
"Il y a ceux qui abordent un festival de jazz avec un tableur, et ceux qui y entrent comme on pénètre dans une cave à Paris après minuit : non pas pour contrôler la nuit, mais pour la rencontrer comme il se doit. Je fais partie du deuxième camp, même lorsque le premier camp paie les factures. Alors oui, le Kongsberg Jazzfestival 2026 mérite une planification pratique. Mais il mérite aussi un peu de pataphysique : la science des exceptions, la doctrine des détours bien-aimés, le droit de suivre une phrase de cuivre dans une autre rue et d'appeler cela une méthode.",
"Mon propre étalon reste français et souterrain. Pendant un temps, j'ai vécu au 23 rue Saint-Jacques, avec le Caveau de la Huchette assez proche pour compter comme météo de quartier. Le Caveau au 5 rue de la Huchette se présente toujours comme le temple du swing, ouvert toute l'année, chaque soir, un endroit où Paris et le jazz s'accordent pour continuer à danser sans demander la permission. Une fois que vous avez appris à penser une ville à travers une telle salle, chaque festival ultérieur est jugé par sa capacité à contenir au moins un escalier vers une meilleure humeur.",
"Kongsberg, heureusement, comprend cela. Le programme officiel 2026 ressemble déjà à un argument civilisé entre le poli et le danger. Le mercredi 1er juillet, Samara Joy ouvre au Kongsberg Musikkteater, et Ghosted suit à l'Energimolla. Le jeudi 2 juillet propose Bobo Stenson Trio, ganavya, Snarky Puppy et Mezzoforte en fin de soirée au Christians Kjeller. Le vendredi 3 juillet passe du Wesseltoft-Andersen-Nilssen Trio et Wibutee à la météo publique plus large de Rotlaus et Matoma à Gamle Norge. Le samedi 4 juillet se termine avec Silya & Kongsberg Storband, Kurt Rosenwinkel, Nils Petter Molvaer et un set de minuit de Silje Nergaard. Ce n'est pas un programme maigre. C'est une ville temporairement éditée par le rythme.",
"Si l'on préfère les petites salles, la géographie devient encore plus intéressante. Le JAZZpass du festival nomme explicitement Privat Bar parmi les lieux intérieurs intimes, tout en notant que Gamle Norge et Christians Kjeller échappent à cette logique de pass, ce qui est exactement juste : certains endroits appartiennent au circuit ordonné, d'autres à l'appétit. Privat Bar, dans sa propre voix, est un bar à cocktails, une scène, un bar sportif, une discothèque et une cour appelée Oasen ; le portrait du lieu par le festival le décrit comme un endroit qui prospère grâce à la qualité, au service, à l'atmosphère et à la folkefest. En d'autres termes : pas seulement un bar, mais un accélérateur social.",
"Gamle Norge est la réponse extérieure. Le festival décrit Folkefestscenen / Gamle Norge comme la scène des noms familiers, de la force chantante et de l'excès festif, placée entre Gamle Norge et Trattoria Madre. Cela vous dit presque tout ce que vous devez savoir. On n'y va pas pour prouver son sérieux. On y va parce que chaque festival sérieux a besoin d'une zone où la rue devient chœur. Espen Lind, Rotlaus, Matoma, No. 4 et Stavangerkameratene donnent à cette scène son visage 2026, et aucune de ces programmations ne prétend être austère. Bien. Les festivals ont besoin de largeur s'ils veulent ressembler à des villes plutôt qu'à des cercles fermés.",
"Puis il y a Christians Kjeller, qui compte pour moi plus que la typographie des affiches. Le portrait du lieu par le festival le place à l'intérieur d'Opsahlgarden sur Vestsida, l'une des maisons anciennes préservées de la ville, restaurée et rouverte comme restaurant en 2001. La maison porte à la fois restaurant, brasserie et énergie de pub musical en sous-sol. Cette combinaison est moralement saine. Si Gamle Norge est la place publique en habits d'été, Christians Kjeller est la salle basse où le style desserre sa cravate et le fait avec sincérité. Mezzoforte jeudi soir, Poesioasen avec Ine Hoem et Edvard Hoem samedi après-midi, et Pumpegris plus tard dans la même nuit : c'est exactement le genre de séquençage que l'on attend d'une ville jazz compacte.",
"Et puis il y a la correction démocratique : le programme gratuit. Jazzbox / Jazzboksen reste l'un des meilleurs arguments pour tout le festival, car il offre aux étudiants, semi-professionnels et amateurs un véritable public plutôt qu'un coin éducatif. La note officielle du festival est claire : concerts gratuits tous les jours, sans réservation, il suffit de venir, avec de l'espace pour se détendre, dériver, écouter, danser et découvrir les noms de demain avant qu'ils ne deviennent des adjectifs de brochure. Jazzmine, listé comme un événement gratuit le vendredi, étend cette logique au nouveau centre Magasinet / Fellesbrukskrysset. Tout bon festival a besoin de monuments payants ; tout grand festival a également besoin de zones où la curiosité reste peu coûteuse.",
"Alors, le bon itinéraire, du moins dans ma cosmologie privée, n'est pas de choisir entre le poli et l'indiscipliné. C'est de naviguer entre eux. Commencez par la forme et l'écoute. Laissez Samara Joy ou Bobo Stenson poser la grammaire. Glissez-vous dans Privat Bar pour le murmure social. Laissez Gamle Norge fournir la preuve publique que la joie peut aussi prendre de l'ampleur. Terminez à Christians Kjeller lorsque la soirée commence à se souvenir de son propre pouls. Et si la journée a besoin d'un redémarrage, prenez le chemin gratuit à travers Jazzbox et Jazzmine, où l'avenir est généralement moins cher et souvent plus vivant.",
"Boris Vian recommanderait probablement une trompette, une mauvaise idée et une meilleure veste. Vernon Sullivan vous dirait de rester dehors trop tard et d'appeler cela de la recherche sur le terrain. Je me contenterai d'une prescription plus calme : le Kongsberg Jazzfestival, du 1er au 4 juillet 2026, ressemble au genre de semaine qui permet à une ville de devenir plusieurs versions d'elle-même à la fois. C'est assez proche de la pataphysique pour moi, et certainement assez proche du jazz."
],
nb: [
"Det finnes folk som nærmer seg en jazzfestival med et regneark, og det finnes folk som går inn i den slik man entrer en kjeller i Paris etter midnatt: ikke for å kontrollere natten, men for å møte den ordentlig. Jeg tilhører den andre leiren, selv når den første leiren betaler fakturaene. Så ja, Kongsberg Jazzfestival 2026 fortjener praktisk planlegging. Men den fortjener også litt pataphysikk: unntakenes vitenskap, doktrinen om elskede omveier, retten til å følge en messingfrase inn i en annen gate og kalle det metode.",
"Min egen målestokk er fortsatt fransk og underjordisk. En periode bodde jeg på 23 rue Saint-Jacques, med Caveau de la Huchette nær nok til å telle som nabolagsvær. Caveau på 5 rue de la Huchette presenterer seg fortsatt som swingens tempel, åpent hele året, hver kveld, et sted hvor Paris og jazz er enige om å fortsette å danse uten å be om tillatelse. Når du først har lært å tenke på en by gjennom et slikt rom, blir enhver senere festival vurdert etter om den inneholder minst én trapp ned til en bedre stemning.",
"Kongsberg, heldigvis, forstår dette. Det offisielle 2026-programmet leser allerede som et sivilisert argument mellom polering og fare. Onsdag 1. juli åpner Samara Joy på Kongsberg Musikkteater, og Ghosted følger på Energimolla. Torsdag 2. juli tilbyr Bobo Stenson Trio, ganavya, Snarky Puppy og Mezzoforte sent på kvelden på Christians Kjeller. Fredag 3. juli går fra Wesseltoft-Andersen-Nilssen Trio og Wibutee til den større offentlige stemningen med Rotlaus og Matoma på Gamle Norge. Lørdag 4. juli avsluttes med Silya & Kongsberg Storband, Kurt Rosenwinkel, Nils Petter Molvaer og et midnattsshow med Silje Nergaard. Dette er ikke et tynt program. Dette er en by midlertidig redigert av rytme.",
"Hvis man foretrekker de mindre rommene, blir geografien enda mer interessant. Festivalens JAZZpass nevner eksplisitt Privat Bar blant de intime innendørsarenaene, samtidig som det påpeker at Gamle Norge og Christians Kjeller ligger utenfor denne pass-logikken, noe som er helt riktig: noen steder tilhører den ryddige kretsen, andre tilhører appetitten. Privat Bar, i sin egen stemme, er cocktailbar, scene, sportsbar, nattklubb og gårdsplass kalt Oasen; festivalens eget portrett av arenaen beskriver det som et sted som trives på kvalitet, service, atmosfære og folkefest. Med andre ord: ikke bare en bar, men en sosial akselerator.",
"Gamle Norge er det utendørs svaret. Festivalen beskriver Folkefestscenen / Gamle Norge som scenen for kjente navn, allsangkraft og festlig overflod, plassert mellom Gamle Norge og Trattoria Madre. Det forteller deg nesten alt du trenger å vite. Man går ikke dit for å bevise alvor. Man går dit fordi enhver seriøs festival trenger én sone hvor gaten blir til kor. Espen Lind, Rotlaus, Matoma, No. 4 og Stavangerkameratene gir den scenen sitt 2026-ansikt, og ingen av disse bookingene later som de er strenge. Bra. Festivaler trenger bredde hvis de skal føles som byer snarere enn klikker.",
"Og så er det Christians Kjeller, som betyr mer for meg enn plakatens typografi. Festivalens portrett av arenaen plasserer den inne i Opsahlgarden på Vestsida, et av byens eldre bevarte hus, restaurert og gjenåpnet som restaurant i 2001. Huset bærer både restaurant, bryggeri og kjeller-musikkpub-energi på én gang. Den kombinasjonen er moralsk sunn. Hvis Gamle Norge er det offentlige torget i sommerklær, er Christians Kjeller det lavere rommet hvor stilen løsner slipset og mener det. Mezzoforte torsdag kveld, Poesioasen med Ine Hoem og Edvard Hoem lørdag ettermiddag, og Pumpegris senere samme kveld: dette er akkurat den typen sekvensering man ønsker fra en kompakt jazzby.",
"Og så er det den demokratiske korreksjonen: gratisprogrammet. Jazzbox / Jazzboksen er fortsatt et av de beste argumentene for hele festivalen, fordi det gir studenter, semi-profesjonelle og amatører et ekte publikum snarere enn et utdanningshjørne. Festivalens offisielle notat er klart: gratis konserter hver dag, ingen reservasjon, bare møt opp, med rom for å slappe av, drive, lytte, danse og oppdage morgendagens navn før de blir brosjyreadjektiver. Jazzmine, oppført som en gratis fredagshendelse, utvider den logikken til det nye Magasinet / Fellesbrukskrysset-senteret. Enhver god festival trenger betalte monumenter; enhver stor festival trenger også soner hvor nysgjerrighet forblir rimelig.",
"Så den riktige ruten, i det minste i min private kosmologi, er ikke å velge mellom det polerte og det uregjerlige. Det er å bevege seg mellom dem. Begynn med form og lytting. La Samara Joy eller Bobo Stenson sette grammatikken. Snik deg inn på Privat Bar for den sosiale mumlingen. La Gamle Norge gi det offentlige beviset på at glede også kan skaleres. Avslutt på Christians Kjeller når kvelden begynner å huske sin egen puls. Og hvis dagen trenger en omstart, ta den gratis stien gjennom Jazzbox og Jazzmine, hvor fremtiden vanligvis er billigere og ofte mer levende.",
"Boris Vian ville sannsynligvis anbefale en trompet, en dårlig idé og en bedre jakke. Vernon Sullivan ville fortelle deg å være ute for sent og kalle det feltforskning. Jeg nøyer meg med en roligere resept: Kongsberg Jazzfestival, 1. til 4. juli 2026, ser ut som den typen uke som lar en by bli flere versjoner av seg selv samtidig. Det er nært nok pataphysikk for meg, og absolutt nært nok jazz."
],
};
export const jazzSources = [
{ label: "Kongsberg Jazzfestival programme 2026", href: "https://kongsbergjazz.no/program/" },
{ label: "Kongsberg Jazzfestival / Folkefestscenen Gamle Norge", href: "https://kongsbergjazz.no/spillesteder/folkefestscenen-gamle-norge/" },
{ label: "Kongsberg Jazzfestival / Hei Privat Bar", href: "https://kongsbergjazz.no/hei-privat-bar/" },
{ label: "Kongsberg Jazzfestival / Hei Christian Kjeller", href: "https://kongsbergjazz.no/hei-christian-kjeller/" },
{ label: "Kongsberg Jazzfestival / Jazzbox", href: "https://kongsbergjazz.no/en/faq/jazzboksen/" },
{ label: "Kongsberg Jazzfestival / Jazzmine", href: "https://kongsbergjazz.no/events/jazzmine/" },
{ label: "Caveau de la Huchette", href: "https://www.caveaudelahuchette.fr/" },
];
+41 -109
View File
@@ -1,3 +1,5 @@
import type { LocaleCode } from "./locales";
export type NorwayImage = {
src: string;
alt: string;
@@ -12,31 +14,28 @@ export type NorwayCase = {
date: string;
href: string;
sourceLabel: string;
summary: string;
significance: string;
summary: Record<LocaleCode, string>;
significance: Record<LocaleCode, string>;
};
export type NorwayOrganization = {
name: string;
href: string;
strap: string;
summary: string;
strap: Record<LocaleCode, string>;
summary: Record<LocaleCode, string>;
};
export const norwayFeature = {
eyebrow: "Section 09 / Norway",
title: "Family life, fathers, immigration, and the long Norwegian argument over Article 8.",
lede:
"This months Norway feature sits where advocacy, rights language, and lived family conflict meet: Do Better Norges campaign line, fathers rights groups, immigrant-family anxiety, and the official Strasbourg record against Norway.",
note:
"The argument here is deliberately two-track: advocacy claims are identified as advocacy, while the legal spine is anchored to official European Court of Human Rights material.",
eyebrow: { en: "Section 09 / Norway", fr: "Section 09 / Norvège", nb: "Seksjon 09 / Norge" },
title: { en: "Family life, fathers, immigration, and the long Norwegian argument over Article 8.", fr: "Vie familiale, pères, immigration et le long débat norvégien sur l'Article 8.", nb: "Familieliv, fedre, immigrasjon og den lange norske debatten om artikkel 8." },
lede: { en: "This month's Norway feature sits where advocacy, rights language, and lived family conflict meet: Do Better Norge's campaign line, fathers' rights groups, immigrant-family anxiety, and the official Strasbourg record against Norway.", fr: "Le dossier de ce mois sur la Norvège se situe à l'intersection du plaidoyer, du langage des droits et des conflits familiaux vécus : la campagne de Do Better Norge, les groupes de défense des droits des pères, l'anxiété des familles immigrées et le dossier officiel de Strasbourg contre la Norvège.", nb: "Denne månedens Norge-artikkel befinner seg der aktivisme, rettighetsspråk og levde familiekonflikter møtes: Do Better Norges kampanjelinje, fedres rettighetsgrupper, immigrantfamilienes uro og den offisielle Strasbourg-protokollen mot Norge." },
note: { en: "The argument here is deliberately two-track: advocacy claims are identified as advocacy, while the legal spine is anchored to official European Court of Human Rights material.", fr: "L'argument ici est délibérément à deux niveaux : les revendications de plaidoyer sont identifiées comme telles, tandis que la colonne vertébrale juridique est ancrée dans le matériel officiel de la Cour européenne des droits de l'homme.", nb: "Argumentet her er bevisst todelt: aktivistiske påstander identifiseres som aktivisme, mens den juridiske ryggraden er forankret i offisielt materiale fra Den europeiske menneskerettighetsdomstolen." },
};
export const norwayArticle = {
title: "April 2026: Norway's Family-Life Problem Still Has Faces, Fathers, and a Court Record",
publishedAt: "2026-04-06 10:20:00",
excerpt:
"A Norway dispatch about Do Better Norge, fathers' rights, immigrant families, and the official Article 8 case law showing why the argument over family life has not gone away.",
excerpt: { en: "A Norway dispatch about Do Better Norge, fathers' rights, immigrant families, and the official Article 8 case law showing why the argument over family life has not gone away.", fr: "Un reportage norvégien sur Do Better Norge, les droits des pères, les familles immigrées et la jurisprudence officielle de l'Article 8 montrant pourquoi le débat sur la vie familiale persiste.", nb: "En Norge-rapport om Do Better Norge, fedres rettigheter, immigrantfamilier og den offisielle artikkel 8-rettspraksisen som viser hvorfor debatten om familieliv ikke har forsvunnet." },
};
export const norwayImages: NorwayImage[] = [
@@ -46,8 +45,7 @@ export const norwayImages: NorwayImage[] = [
credit: "Do Better Norge",
sourceUrl: "https://dobetternorge.no/images/humanRightsCollision.jpeg",
sourceLabel: "Do Better Norge",
note:
"Used to frame the article's central tension between legal standards, contact rights, and what advocacy groups say happens in practice.",
note: "Used to frame the article's central tension between legal standards, contact rights, and what advocacy groups say happens in practice.",
},
{
src: "https://dobetternorge.no/images/echrIntervention.jpeg",
@@ -55,8 +53,7 @@ export const norwayImages: NorwayImage[] = [
credit: "Do Better Norge",
sourceUrl: "https://dobetternorge.no/images/echrIntervention.jpeg",
sourceLabel: "Do Better Norge",
note:
"Used as a visual bridge between Strasbourg case law and Norway's domestic reunification debate.",
note: "Used as a visual bridge between Strasbourg case law and Norway's domestic reunification debate.",
},
{
src: "https://dobetternorge.no/uploads/infographics/norwayLaw-vs-ECHR.png",
@@ -64,8 +61,7 @@ export const norwayImages: NorwayImage[] = [
credit: "Do Better Norge",
sourceUrl: "https://dobetternorge.no/uploads/infographics/norwayLaw-vs-ECHR.png",
sourceLabel: "Do Better Norge",
note:
"Useful for the article's law-versus-practice section because it visualizes the reunification versus stability conflict directly.",
note: "Useful for the article's law-versus-practice section because it visualizes the reunification versus stability conflict directly.",
},
{
src: "https://dobetternorge.no/images/theBlackBox.jpeg",
@@ -73,8 +69,7 @@ export const norwayImages: NorwayImage[] = [
credit: "Do Better Norge",
sourceUrl: "https://dobetternorge.no/images/theBlackBox.jpeg",
sourceLabel: "Do Better Norge",
note:
"Selected for the immigrant-family section because it explicitly depicts cultural blindness and interpretive bias as advocacy concerns.",
note: "Selected for the immigrant-family section because it explicitly depicts cultural blindness and interpretive bias as advocacy concerns.",
},
];
@@ -84,50 +79,40 @@ export const norwayCases: NorwayCase[] = [
date: "10 September 2019",
href: "https://www.echr.coe.int/w/strand-lobben-and-others-v.-norway-no.-37283/13-",
sourceLabel: "ECHR case page",
summary:
"Grand Chamber case concerning forced adoption and the severing of legal ties between mother and child.",
significance:
"The core signal was that reunification must remain the ultimate aim where possible and that permanently cutting family ties demands exceptional justification.",
summary: { en: "Grand Chamber case concerning forced adoption and the severing of legal ties between mother and child.", fr: "Affaire de la Grande Chambre concernant l'adoption forcée et la rupture des liens juridiques entre une mère et son enfant.", nb: "Storkammer-sak om tvangsadopsjon og brudd på juridiske bånd mellom mor og barn." },
significance: { en: "The core signal was that reunification must remain the ultimate aim where possible and that permanently cutting family ties demands exceptional justification.", fr: "Le signal clé était que la réunification doit rester l'objectif ultime lorsque cela est possible, et que couper définitivement les liens familiaux exige une justification exceptionnelle.", nb: "Kjernesignalet var at gjenforening må forbli det ultimate målet der det er mulig, og at permanent brudd på familiebånd krever en ekstraordinær begrunnelse." },
},
{
title: "K.O. and V.M. v. Norway",
date: "19 November 2019",
href: "https://hudoc.echr.coe.int/app/conversion/pdf?filename=Judgment+K.O.+and+V.M.+v.+Norway+-+placement+in+foster+care+and+contact+rights.pdf&id=003-6566150-8690597&library=ECHR",
sourceLabel: "ECHR press release",
summary:
"The Court found no Article 8 violation in the placement itself, but did find a violation in the parents' contact regime.",
significance:
"That split matters: the recurring Norway problem is often not only removal, but low-contact decisions that let separation harden into estrangement.",
summary: { en: "The Court found no Article 8 violation in the placement itself, but did find a violation in the parents' contact regime.", fr: "La Cour n'a pas trouvé de violation de l'Article 8 dans le placement lui-même, mais a constaté une violation dans le régime de contact des parents.", nb: "Domstolen fant ingen brudd på artikkel 8 i selve plasseringen, men fant brudd i foreldrenes kontaktregime." },
significance: { en: "That split matters: the recurring Norway problem is often not only removal, but low-contact decisions that let separation harden into estrangement.", fr: "Cette distinction est importante : le problème récurrent en Norvège concerne souvent non seulement le retrait, mais aussi les décisions de faible contact qui laissent la séparation se transformer en éloignement.", nb: "Den splittelsen er viktig: det tilbakevendende Norge-problemet handler ofte ikke bare om fjerning, men om lavkontaktavgjørelser som lar separasjon bli til fremmedgjøring." },
},
{
title: "Abdi Ibrahim v. Norway",
date: "10 December 2021",
href: "https://www.echr.coe.int/w/abdi-ibrahim-v.-norway-no.-15379/16-",
sourceLabel: "ECHR case page",
summary:
"Grand Chamber case about adoption by a foster family against the wishes of a Somali Muslim mother.",
significance:
"The Court held that Norway had not given sufficient weight to the family's cultural and religious background, making this one of the clearest immigrant-family Article 8 judgments.",
summary: { en: "Grand Chamber case about adoption by a foster family against the wishes of a Somali Muslim mother.", fr: "Affaire de la Grande Chambre concernant l'adoption par une famille d'accueil contre les souhaits d'une mère somalienne musulmane.", nb: "Storkammer-sak om adopsjon av en fosterfamilie mot ønskene til en somalisk muslimsk mor." },
significance: { en: "The Court held that Norway had not given sufficient weight to the family's cultural and religious background, making this one of the clearest immigrant-family Article 8 judgments.", fr: "La Cour a estimé que la Norvège n'avait pas accordé suffisamment de poids au contexte culturel et religieux de la famille, ce qui en fait l'un des jugements les plus clairs concernant les familles immigrées dans le cadre de l'Article 8.", nb: "Domstolen mente at Norge ikke hadde gitt tilstrekkelig vekt til familiens kulturelle og religiøse bakgrunn, noe som gjør dette til en av de tydeligste immigrantfamilie-dommene under artikkel 8." },
},
{
title: "A.L. and Others v. Norway",
date: "20 January 2022",
href: "https://hudoc.echr.coe.int/app/conversion/pdf/?filename=Two+judgments+concerning+care+orders+for+children+in+Norway.pdf&id=003-7235635-9843609&library=ECHR",
sourceLabel: "ECHR press release",
summary:
"Case involving Norwegian and Slovak family members and the handling of a care order plus restricted parental contact.",
significance:
"The Court found that the child had effectively been set on a foster-care trajectory without proper consideration of alternatives or genuine reconciliation work.",
summary: { en: "Case involving Norwegian and Slovak family members and the handling of a care order plus restricted parental contact.", fr: "Affaire impliquant des membres de familles norvégiennes et slovaques et la gestion d'une ordonnance de prise en charge avec un contact parental restreint.", nb: "Sak som involverer norske og slovakiske familiemedlemmer og håndtering av en omsorgsordre med begrenset foreldrekontakt." },
significance: { en: "The Court found that the child had effectively been set on a foster-care trajectory without proper consideration of alternatives or genuine reconciliation work.", fr: "La Cour a constaté que l'enfant avait été effectivement placé sur une trajectoire de placement en famille d'accueil sans considération adéquate des alternatives ou de véritables efforts de réconciliation.", nb: "Domstolen fant at barnet i praksis var satt på en fosterhjemsbane uten tilstrekkelig vurdering av alternativer eller genuint forsoningsarbeid." },
},
{
title: "Country profile: Norway parental-rights line",
date: "Updated July 2024",
href: "https://www.echr.coe.int/documents/d/echr/CP_Norway_ENG",
sourceLabel: "ECHR country profile",
summary:
"The Court's official Norway profile groups together child-welfare, parental-rights, and immigration-family-life cases.",
significance:
"It records that on 14 September 2023 the Court dealt with 21 Norway public-care applications, declaring 12 inadmissible and finding Article 8 violations in nine others.",
summary: { en: "The Court's official Norway profile groups together child-welfare, parental-rights, and immigration-family-life cases.", fr: "Le profil officiel de la Norvège par la Cour regroupe les affaires concernant le bien-être des enfants, les droits parentaux et la vie familiale des immigrés.", nb: "Domstolens offisielle Norge-profil grupperer saker om barnevern, foreldrerettigheter og immigrantfamilieliv." },
significance: { en: "It records that on 14 September 2023 the Court dealt with 21 Norway public-care applications, declaring 12 inadmissible and finding Article 8 violations in nine others.", fr: "Il enregistre que, le 14 septembre 2023, la Cour a traité 21 demandes concernant la prise en charge publique en Norvège, déclarant 12 irrecevables et constatant des violations de l'Article 8 dans neuf autres.", nb: "Den registrerer at den 14. september 2023 behandlet domstolen 21 norske offentlige omsorgssøknader, erklærte 12 som inadmissible og fant brudd på artikkel 8 i ni andre." },
},
];
@@ -135,85 +120,32 @@ export const norwayOrganizations: NorwayOrganization[] = [
{
name: "Do Better Norge",
href: "https://dobetternorge.no/",
strap: "rights education, reform pressure, and family-life advocacy",
summary:
"The site presents itself as a practical route for families navigating custody, contact, child-welfare process, and Article 8 questions, with guides, infographics, videos, and reform arguments.",
strap: { en: "rights education, reform pressure, and family-life advocacy", fr: "éducation aux droits, pression pour la réforme et plaidoyer pour la vie familiale", nb: "rettighetsopplæring, reformpress og familielivsaktivisme" },
summary: { en: "The site presents itself as a practical route for families navigating custody, contact, child-welfare process, and Article 8 questions, with guides, infographics, videos, and reform arguments.", fr: "Le site se présente comme une voie pratique pour les familles naviguant dans les questions de garde, de contact, de processus de protection de l'enfance et de l'Article 8, avec des guides, des infographies, des vidéos et des arguments pour la réforme.", nb: "Nettstedet presenterer seg som en praktisk vei for familier som navigerer i spørsmål om omsorg, kontakt, barnevernsprosesser og artikkel 8, med guider, infografikk, videoer og reformargumenter." },
},
{
name: "MannsForum",
href: "https://mannsforum.no/kontaktinformasjon-mannsforum/",
strap: "boys, fathers, and men's equality voice",
summary:
"MannsForum describes itself as Norway's largest membership-based equality organization focused on boys, fathers, and men, which makes it a natural bridge into the fathers' rights side of the debate.",
strap: { en: "boys, fathers, and men's equality voice", fr: "voix pour l'égalité des garçons, des pères et des hommes", nb: "gutters, fedres og menns likestillingsstemme" },
summary: { en: "MannsForum describes itself as Norway's largest membership-based equality organization focused on boys, fathers, and men, which makes it a natural bridge into the fathers' rights side of the debate.", fr: "MannsForum se décrit comme la plus grande organisation d'égalité basée sur l'adhésion en Norvège, axée sur les garçons, les pères et les hommes, ce qui en fait un pont naturel vers le côté des droits des pères du débat.", nb: "MannsForum beskriver seg selv som Norges største medlemsbaserte likestillingsorganisasjon fokusert på gutter, fedre og menn, noe som gjør det til en naturlig bro inn i fedres rettighetssiden av debatten." },
},
{
name: "Foreningen 2 Foreldre",
href: "https://www.f2f.no/",
strap: "children with two homes, shared-parenting culture",
summary:
"F2F's own framing is concise and revealing: safe family conditions for children with two homes, which is almost a thesis statement for the contact-and-continuity side of the article.",
strap: { en: "children with two homes, shared-parenting culture", fr: "enfants avec deux foyers, culture de co-parentalité", nb: "barn med to hjem, delt foreldrekultur" },
summary: { en: "F2F's own framing is concise and revealing: safe family conditions for children with two homes, which is almost a thesis statement for the contact-and-continuity side of the article.", fr: "La propre présentation de F2F est concise et révélatrice : des conditions familiales sûres pour les enfants avec deux foyers, ce qui est presque une déclaration de thèse pour le côté contact et continuité de l'article.", nb: "F2Fs egen formulering er kortfattet og avslørende: trygge familieforhold for barn med to hjem, som nesten er en teseerklæring for kontakt- og kontinuitetssiden av artikkelen." },
},
];
export const norwaySources = [
{
label: "ECHR country profile for Norway",
href: "https://www.echr.coe.int/documents/d/echr/CP_Norway_ENG",
note:
"Used for the official summary of Norway parental-rights and immigration-family-life judgments, including the 2023 line on 21 public-care applications.",
},
{
label: "ECHR case page: Strand Lobben and Others v. Norway",
href: "https://www.echr.coe.int/w/strand-lobben-and-others-v.-norway-no.-37283/13-",
note:
"Used for the Grand Chamber adoption and reunification reference point that reshaped the Norway debate.",
},
{
label: "ECHR case page: Abdi Ibrahim v. Norway",
href: "https://www.echr.coe.int/w/abdi-ibrahim-v.-norway-no.-15379/16-",
note:
"Used for the cultural and religious identity dimension in foster-care and adoption decisions.",
},
{
label: "ECHR press release: K.O. and V.M. v. Norway",
href: "https://hudoc.echr.coe.int/app/conversion/pdf?filename=Judgment+K.O.+and+V.M.+v.+Norway+-+placement+in+foster+care+and+contact+rights.pdf&id=003-6566150-8690597&library=ECHR",
note:
"Used for the contact-rights holding and the distinction between the placement decision and the visitation regime.",
},
{
label: "ECHR press release: A.L. and Others v. Norway / E.M. and Others v. Norway",
href: "https://hudoc.echr.coe.int/app/conversion/pdf/?filename=Two+judgments+concerning+care+orders+for+children+in+Norway.pdf&id=003-7235635-9843609&library=ECHR",
note:
"Used for the 2022 family-reconciliation and long-term foster-care analysis.",
},
{
label: "ECHR press release: 21 applications against Norway concerning children taken into public care",
href: "https://hudoc.echr.coe.int/app/conversion/pdf/?filename=21+applications+against+Norway+concerning+children+taken+into+public+care.pdf&id=003-7744629-10718629&library=ECHR",
note:
"Used for the official 2023 aggregation of admissibility outcomes and Article 8 violations against Norway.",
},
{
label: "ECHR factsheet: Parental rights",
href: "https://www.echr.coe.int/Documents/FS_Parental_ENG.pdf",
note:
"Used as the official thematic overview linking Norway decisions into the wider Article 8 parental-rights doctrine.",
},
{
label: "Do Better Norge homepage",
href: "https://dobetternorge.no/",
note:
"Used for the site's self-description, emphasis on Article 8, custody, child welfare, and reform work.",
},
{
label: "MannsForum contact/about page",
href: "https://mannsforum.no/kontaktinformasjon-mannsforum/",
note:
"Used for MannsForum's own description of itself as an organization focused on boys, fathers, and men.",
},
{
label: "Foreningen 2 Foreldre homepage",
href: "https://www.f2f.no/",
note:
"Used for F2F's own framing of children with two homes and shared-family stability.",
},
{ label: "ECHR country profile for Norway", href: "https://www.echr.coe.int/documents/d/echr/CP_Norway_ENG", note: "Used for the official summary of Norway parental-rights and immigration-family-life judgments, including the 2023 line on 21 public-care applications." },
{ label: "ECHR case page: Strand Lobben and Others v. Norway", href: "https://www.echr.coe.int/w/strand-lobben-and-others-v.-norway-no.-37283/13-", note: "Used for the Grand Chamber adoption and reunification reference point that reshaped the Norway debate." },
{ label: "ECHR case page: Abdi Ibrahim v. Norway", href: "https://www.echr.coe.int/w/abdi-ibrahim-v.-norway-no.-15379/16-", note: "Used for the cultural and religious identity dimension in foster-care and adoption decisions." },
{ label: "ECHR press release: K.O. and V.M. v. Norway", href: "https://hudoc.echr.coe.int/app/conversion/pdf?filename=Judgment+K.O.+and+V.M.+v.+Norway+-+placement+in+foster+care+and+contact+rights.pdf&id=003-6566150-8690597&library=ECHR", note: "Used for the contact-rights holding and the distinction between the placement decision and the visitation regime." },
{ label: "ECHR press release: A.L. and Others v. Norway / E.M. and Others v. Norway", href: "https://hudoc.echr.coe.int/app/conversion/pdf/?filename=Two+judgments+concerning+care+orders+for+children+in+Norway.pdf&id=003-7235635-9843609&library=ECHR", note: "Used for the 2022 family-reconciliation and long-term foster-care analysis." },
{ label: "ECHR press release: 21 applications against Norway concerning children taken into public care", href: "https://hudoc.echr.coe.int/app/conversion/pdf/?filename=21+applications+against+Norway+concerning+children+taken+into+public+care.pdf&id=003-7744629-10718629&library=ECHR", note: "Used for the official 2023 aggregation of admissibility outcomes and Article 8 violations against Norway." },
{ label: "ECHR factsheet: Parental rights", href: "https://www.echr.coe.int/Documents/FS_Parental_ENG.pdf", note: "Used as the official thematic overview linking Norway decisions into the wider Article 8 parental-rights doctrine." },
{ label: "Do Better Norge homepage", href: "https://dobetternorge.no/", note: "Used for the site's self-description, emphasis on Article 8, custody, child welfare, and reform work." },
{ label: "MannsForum contact/about page", href: "https://mannsforum.no/kontaktinformasjon-mannsforum/", note: "Used for MannsForum's own description of itself as an organization focused on boys, fathers, and men." },
{ label: "Foreningen 2 Foreldre homepage", href: "https://www.f2f.no/", note: "Used for F2F's own framing of children with two homes and shared-family stability." },
];
+61 -115
View File
@@ -1,6 +1,8 @@
import type { LocaleCode } from "./locales";
export type RouteStop = {
place: string;
note: string;
note: Record<LocaleCode, string>;
};
export type SourceCredit = {
@@ -16,16 +18,16 @@ export type Venture = {
name: string;
label: string;
location: string;
summary: string;
detail: string;
highlights: string[];
summary: Record<LocaleCode, string>;
detail: Record<LocaleCode, string>;
highlights: Record<LocaleCode, string[]>;
source: SourceCredit;
};
export type VentureSignal = {
name: string;
strap: string;
summary: string;
strap: Record<LocaleCode, string>;
summary: Record<LocaleCode, string>;
href: string;
imageSrc: string;
imageAlt: string;
@@ -36,12 +38,12 @@ export type VentureSignal = {
export type CollageImage = {
src: string;
alt: string;
caption: string;
caption: Record<LocaleCode, string>;
};
export type AILabCapability = {
title: string;
summary: string;
title: Record<LocaleCode, string>;
summary: Record<LocaleCode, string>;
};
export type AILabProgramme = {
@@ -51,20 +53,20 @@ export type AILabProgramme = {
};
export const routeStops: RouteStop[] = [
{ place: "Ringwood, New Jersey", note: "born" },
{ place: "Villanova, Pennsylvania", note: "undergraduate chapter" },
{ place: "Brussels, Belgium", note: "trade and multilingual weather" },
{ place: "New Jersey", note: "return passage" },
{ place: "Columbia, South Carolina", note: "business school orbit" },
{ place: "Paris, France", note: "international MBA" },
{ place: "Washington, DC", note: "capital interval" },
{ place: "Manhattan, New York", note: "city tempo" },
{ place: "Hamilton, Bermuda", note: "Atlantic detour" },
{ place: "Washington, DC", note: "federal coda" },
{ place: "Brooklyn, New York", note: "borough voltage" },
{ place: "Krakow, Poland", note: "EU studies" },
{ place: "Oslo, Norway", note: "Nordic transition" },
{ place: "Kongsberg, Norway", note: "current desk" },
{ place: "Ringwood, New Jersey", note: { en: "born", fr: "naissance", nb: "født" } },
{ place: "Villanova, Pennsylvania", note: { en: "undergraduate chapter", fr: "chapitre universitaire", nb: "grunnleggende kapittel" } },
{ place: "Brussels, Belgium", note: { en: "trade and multilingual weather", fr: "commerce et météo multilingue", nb: "handel og flerspråklig vær" } },
{ place: "New Jersey", note: { en: "return passage", fr: "retour", nb: "returpassasje" } },
{ place: "Columbia, South Carolina", note: { en: "business school orbit", fr: "orbite de l'école de commerce", nb: "handelshøyskolebane" } },
{ place: "Paris, France", note: { en: "international MBA", fr: "MBA international", nb: "internasjonal MBA" } },
{ place: "Washington, DC", note: { en: "capital interval", fr: "intervalle capital", nb: "hovedstadsintervall" } },
{ place: "Manhattan, New York", note: { en: "city tempo", fr: "tempo urbain", nb: "bytempo" } },
{ place: "Hamilton, Bermuda", note: { en: "Atlantic detour", fr: "détour atlantique", nb: "atlantisk omvei" } },
{ place: "Washington, DC", note: { en: "federal coda", fr: "coda fédérale", nb: "føderal koda" } },
{ place: "Brooklyn, New York", note: { en: "borough voltage", fr: "voltage de quartier", nb: "bydelsspenning" } },
{ place: "Krakow, Poland", note: { en: "EU studies", fr: "études européennes", nb: "EU-studier" } },
{ place: "Oslo, Norway", note: { en: "Nordic transition", fr: "transition nordique", nb: "nordisk overgang" } },
{ place: "Kongsberg, Norway", note: { en: "current desk", fr: "bureau actuel", nb: "nåværende skrivebord" } },
];
export const ventureDesk: Venture[] = [
@@ -75,20 +77,13 @@ export const ventureDesk: Venture[] = [
name: "Gilligan TECH ENK",
label: "Norwegian ENK",
location: "Kongsberg, Norway",
summary:
"Local AI systems consulting for Nordic businesses, with strategy first, delivery second, and infrastructure only when it materially improves the result.",
detail:
"The Gilligan Tech line is practical and operator-minded: architectural audits, system builds, and fractional CTO guidance for Nordic SMBs that want useful AI without rented buzzwords.",
highlights: [
"AI Dispatch: fixed-fee architectural audit and strike map",
"Systems Forge: custom RAG, AI portals, and transcription services",
"Systems Command: fractional CTO support and board-ready AI strategy",
],
summary: { en: "Local AI systems consulting for Nordic businesses, with strategy first, delivery second, and infrastructure only when it materially improves the result.", fr: "Consultation en systèmes d'IA locaux pour les entreprises nordiques, avec la stratégie d'abord, la mise en œuvre ensuite, et l'infrastructure uniquement si elle améliore réellement le résultat.", nb: "Konsulenttjenester for lokale AI-systemer for nordiske bedrifter, med strategi først, levering deretter, og infrastruktur kun når det gir en materiell forbedring." },
detail: { en: "The Gilligan Tech line is practical and operator-minded: architectural audits, system builds, and fractional CTO guidance for Nordic SMBs that want useful AI without rented buzzwords.", fr: "La ligne Gilligan TECH est pratique et orientée opérateurs : audits architecturaux, constructions de systèmes et conseils CTO fractionnés pour les PME nordiques qui souhaitent une IA utile sans dépendre des mots à la mode.", nb: "Gilligan TECH-linjen er praktisk og operatørfokusert: arkitektoniske revisjoner, systembygging og delt CTO-veiledning for nordiske SMB-er som ønsker nyttig AI uten leide buzzwords." },
highlights: { en: ["AI Dispatch: fixed-fee architectural audit and strike map","Systems Forge: custom RAG, AI portals, and transcription services","Systems Command: fractional CTO support and board-ready AI strategy"], fr: ["AI Dispatch : audit architectural à tarif fixe et carte d'intervention","Systems Forge : RAG personnalisé, portails IA et services de transcription","Systems Command : support CTO fractionné et stratégie IA prête pour le conseil d'administration"], nb: ["AI Dispatch: fastpris arkitektonisk revisjon og handlingskart","Systems Forge: tilpasset RAG, AI-portaler og transkripsjonstjenester","Systems Command: delt CTO-støtte og styreklar AI-strategi"] },
source: {
label: "Gilligan Tech",
url: "https://gilligan.tech/",
note:
"Paraphrased from the official Gilligan Tech site, including the AI Dispatch, Systems Forge, and Systems Command descriptions.",
note: "Paraphrased from the official Gilligan Tech site, including the AI Dispatch, Systems Forge, and Systems Command descriptions.",
},
},
{
@@ -98,20 +93,13 @@ export const ventureDesk: Venture[] = [
name: "Blue Note Logic Inc",
label: "AI / IT Lab",
location: "Philadelphia to Paris, with EU infrastructure",
summary:
"Private AI platforms, document intelligence, and production infrastructure designed to turn working knowledge into owned systems rather than vendor dependency.",
detail:
"Blue Note Logic is the lab, forge, and infrastructure house behind the AI side of the publication: consultancy, sovereign model work, corpus design, and productized document intelligence with cited answers and European hosting.",
highlights: [
"CorpusAI and CaveauAI for private, source-cited retrieval",
"Knowledge corpus design, document intelligence, and deployment strategy",
"Sovereign fine-tuning paths built around owned data and owned outcomes",
],
summary: { en: "Private AI platforms, document intelligence, and production infrastructure designed to turn working knowledge into owned systems rather than vendor dependency.", fr: "Plateformes d'IA privées, intelligence documentaire et infrastructure de production conçues pour transformer le savoir-faire en systèmes propriétaires plutôt qu'en dépendance aux fournisseurs.", nb: "Private AI-plattformer, dokumentintelligens og produksjonsinfrastruktur designet for å gjøre arbeidskunnskap til egne systemer i stedet for leverandøravhengighet." },
detail: { en: "Blue Note Logic is the lab, forge, and infrastructure house behind the AI side of the publication: consultancy, sovereign model work, corpus design, and productized document intelligence with cited answers and European hosting.", fr: "Blue Note Logic est le laboratoire, la forge et la maison d'infrastructure derrière le volet IA de la publication : conseil, travail sur des modèles souverains, conception de corpus et intelligence documentaire produit avec des réponses citées et un hébergement européen.", nb: "Blue Note Logic er laboratoriet, smia og infrastrukturhuset bak AI-delen av publikasjonen: konsulenttjenester, arbeid med suverene modeller, korpusdesign og produktifisert dokumentintelligens med siterte svar og europeisk hosting." },
highlights: { en: ["CorpusAI and CaveauAI for private, source-cited retrieval","Knowledge corpus design, document intelligence, and deployment strategy","Sovereign fine-tuning paths built around owned data and owned outcomes"], fr: ["CorpusAI et CaveauAI pour une récupération privée et sourcée","Conception de corpus de connaissances, intelligence documentaire et stratégie de déploiement","Chemins de fine-tuning souverains basés sur des données et des résultats propriétaires"], nb: ["CorpusAI og CaveauAI for privat, kildebasert gjenfinning","Korpusdesign, dokumentintelligens og distribusjonsstrategi","Suverene finjusteringsveier bygget rundt egne data og egne resultater"] },
source: {
label: "Blue Note Logic official sites",
url: "https://ai.bluenotelogic.com/",
note:
"Paraphrased from Blue Note Logic and CorpusAI official pages covering private AI, document intelligence, and owned infrastructure.",
note: "Paraphrased from Blue Note Logic and CorpusAI official pages covering private AI, document intelligence, and owned infrastructure.",
},
},
];
@@ -119,9 +107,8 @@ export const ventureDesk: Venture[] = [
export const ventureSignals: VentureSignal[] = [
{
name: "Blue Note Logic",
strap: "private AI, document intelligence, and source-cited memory",
summary:
"The machine room behind the paper: owned infrastructure, private corpora, multilingual controls, and AI that keeps its receipts.",
strap: { en: "private AI, document intelligence, and source-cited memory", fr: "IA privée, intelligence documentaire et mémoire sourcée", nb: "privat AI, dokumentintelligens og kildebasert hukommelse" },
summary: { en: "The machine room behind the paper: owned infrastructure, private corpora, multilingual controls, and AI that keeps its receipts.", fr: "La salle des machines derrière le papier : infrastructure propriétaire, corpus privés, contrôles multilingues et IA qui garde ses preuves.", nb: "Maskinrommet bak papiret: egen infrastruktur, private korpus, flerspråklige kontroller og AI som holder kvitteringene sine." },
href: "https://ai.bluenotelogic.com/",
imageSrc: "/images/ai-lab/hero-lab.svg",
imageAlt: "Illustrated AI lab diagram for Blue Note Logic.",
@@ -130,21 +117,18 @@ export const ventureSignals: VentureSignal[] = [
},
{
name: "Trivia & Tunes",
strap: "live-hosted games, music rounds, and true AI in the loop",
summary:
"A culture product with venue instincts: playlists, game craft, AI grading, and voice features edging toward the microphone.",
strap: { en: "live-hosted games, music rounds, and true AI in the loop", fr: "jeux animés en direct, tours musicaux et vraie IA en boucle", nb: "live-vertede spill, musikkrunder og ekte AI i loopen" },
summary: { en: "A culture product with venue instincts: playlists, game craft, AI grading, and voice features edging toward the microphone.", fr: "Un produit culturel avec des instincts de lieu : playlists, conception de jeux, évaluation par IA et fonctionnalités vocales qui s'approchent du micro.", nb: "Et kulturprodukt med stedssans: spillelister, spilldesign, AI-evaluering og stemmefunksjoner som nærmer seg mikrofonen." },
href: "https://triviaandtunes.no/",
imageSrc:
"https://commons.wikimedia.org/wiki/Special:Redirect/file/Level_42_Kongsberg_Jazzfestival_2017_%28214257%29.jpg",
imageSrc: "https://commons.wikimedia.org/wiki/Special:Redirect/file/Level_42_Kongsberg_Jazzfestival_2017_%28214257%29.jpg",
imageAlt: "Crowd-facing stage image from Kongsberg Jazzfestival.",
imageNote: "Live rooms / music energy / quiz-night voltage",
external: true,
},
{
name: "Do Better Norge",
strap: "children's rights, due process, and family life protections",
summary:
"A civic and advocacy desk grounded in primary sources, practical guidance, and a refusal to treat children as procedural debris.",
strap: { en: "children's rights, due process, and family life protections", fr: "droits des enfants, procédure équitable et protection de la vie familiale", nb: "barns rettigheter, rettferdig prosess og beskyttelse av familieliv" },
summary: { en: "A civic and advocacy desk grounded in primary sources, practical guidance, and a refusal to treat children as procedural debris.", fr: "Un bureau civique et de plaidoyer ancré dans les sources primaires, des conseils pratiques et un refus de traiter les enfants comme des débris procéduraux.", nb: "En samfunns- og advokatdesk forankret i primærkilder, praktisk veiledning og en avvisning av å behandle barn som prosedyremessig avfall." },
href: "https://dobetternorge.no/",
imageSrc: "https://commons.wikimedia.org/wiki/Special:Redirect/file/Kongsberg_IMG_0357.JPG",
imageAlt: "Riverfront view in Kongsberg, Norway.",
@@ -153,12 +137,10 @@ export const ventureSignals: VentureSignal[] = [
},
{
name: "School dossier",
strap: "Brussels, Krakow, Paris, Villanova, and the long route north",
summary:
"The education issue turns institutions into chapters, cities into footnotes, and degrees into a proper migration story.",
strap: { en: "Brussels, Krakow, Paris, Villanova, and the long route north", fr: "Bruxelles, Cracovie, Paris, Villanova et la longue route vers le nord", nb: "Brussel, Krakow, Paris, Villanova og den lange veien nordover" },
summary: { en: "The education issue turns institutions into chapters, cities into footnotes, and degrees into a proper migration story.", fr: "Le numéro éducation transforme les institutions en chapitres, les villes en notes de bas de page et les diplômes en une véritable histoire de migration.", nb: "Utdanningsutgaven gjør institusjoner til kapitler, byer til fotnoter og grader til en ordentlig migrasjonshistorie." },
href: "/education",
imageSrc:
"https://commons.wikimedia.org/wiki/Special:Redirect/file/Grand-Place,_Brussels_%2839528722772%29.jpg",
imageSrc: "https://commons.wikimedia.org/wiki/Special:Redirect/file/Grand-Place,_Brussels_%2839528722772%29.jpg",
imageAlt: "Grand-Place in Brussels.",
imageNote: "Brussels / multilingual weather / first European chapter",
external: false,
@@ -169,43 +151,39 @@ export const collageImages: CollageImage[] = [
{
src: "https://commons.wikimedia.org/wiki/Special:Redirect/file/Villanova_University_A_panoramic_shot.jpg",
alt: "Panoramic view of Villanova University.",
caption: "Villanova / the first brass section",
caption: { en: "Villanova / the first brass section", fr: "Villanova / la première section de cuivres", nb: "Villanova / den første messingseksjonen" },
},
{
src: "https://commons.wikimedia.org/wiki/Special:Redirect/file/Jagiellonian_University_Collegium_Novum%2C_1882_designed_by_Feliks_Ksi%C4%99%C5%BCarski%2C_24_Go%C5%82%C4%99bia_street%2C_Old_Town%2C_Krak%C3%B3w%2C_Poland_%284%29.jpg",
alt: "Collegium Novum at Jagiellonian University in Krakow.",
caption: "Krakow / bureaucracy and stone",
caption: { en: "Krakow / bureaucracy and stone", fr: "Cracovie / bureaucratie et pierre", nb: "Krakow / byråkrati og stein" },
},
{
src: "https://commons.wikimedia.org/wiki/Special:Redirect/file/Kongsberg_IMG_0357.JPG",
alt: "Kongsberg riverfront in Norway.",
caption: "Kongsberg / present tense",
caption: { en: "Kongsberg / present tense", fr: "Kongsberg / présent immédiat", nb: "Kongsberg / nåtid" },
},
];
export const aiLabCapabilities: AILabCapability[] = [
{
title: "Private AI on your own corpus",
summary:
"Document intelligence built around a private corpus, with source-cited answers and search modes that stay anchored to the evidence.",
title: { en: "Private AI on your own corpus", fr: "IA privée sur votre propre corpus", nb: "Privat AI på ditt eget korpus" },
summary: { en: "Document intelligence built around a private corpus, with source-cited answers and search modes that stay anchored to the evidence.", fr: "Intelligence documentaire construite autour d'un corpus privé, avec des réponses sourcées et des modes de recherche ancrés dans les preuves.", nb: "Dokumentintelligens bygget rundt et privat korpus, med kildebaserte svar og søkemoduser som forblir forankret i bevisene." },
},
{
title: "European infrastructure by design",
summary:
"Dedicated European hosting, isolated telemetry, and a security posture built to reduce cloud leakage and shared-tenancy risk.",
title: { en: "European infrastructure by design", fr: "Infrastructure européenne par conception", nb: "Europeisk infrastruktur som standard" },
summary: { en: "Dedicated European hosting, isolated telemetry, and a security posture built to reduce cloud leakage and shared-tenancy risk.", fr: "Hébergement européen dédié, télémétrie isolée et posture de sécurité conçue pour réduire les fuites cloud et les risques de cohabitation.", nb: "Dedikert europeisk hosting, isolert telemetri og en sikkerhetsholdning bygget for å redusere sky-lekkasje og risiko for delt leietak." },
},
{
title: "From retrieval to owned model",
summary:
"A path from daily retrieval work toward domain-tuned models shaped by verified expert interaction instead of disposable prompt theatre.",
title: { en: "From retrieval to owned model", fr: "De la récupération au modèle propriétaire", nb: "Fra gjenfinning til egen modell" },
summary: { en: "A path from daily retrieval work toward domain-tuned models shaped by verified expert interaction instead of disposable prompt theatre.", fr: "Un chemin allant du travail quotidien de récupération vers des modèles adaptés au domaine, façonnés par une interaction experte vérifiée plutôt que par un théâtre de prompts jetables.", nb: "En vei fra daglig gjenfinningsarbeid til domene-tilpassede modeller formet av verifisert ekspertinteraksjon i stedet for engangs prompt-teater." },
},
];
export const aiLabProgrammes: AILabProgramme[] = [
{
title: "CorpusAI / CaveauAI",
summary:
"The product layer for private retrieval, cited answers, and document-grounded search from day one.",
summary: "The product layer for private retrieval, cited answers, and document-grounded search from day one.",
bullets: [
"Vector, keyword, and hybrid search modes",
"Cited answers linked back to source paragraphs",
@@ -214,8 +192,7 @@ export const aiLabProgrammes: AILabProgramme[] = [
},
{
title: "Corporate Memory Extraction",
summary:
"A flagship service that turns daily document workflows into a proprietary training asset and an owned model trajectory.",
summary: "A flagship service that turns daily document workflows into a proprietary training asset and an owned model trajectory.",
bullets: [
"EU-hosted RAG as the operational starting point",
"Verified interaction telemetry captured in isolated MariaDB",
@@ -224,8 +201,7 @@ export const aiLabProgrammes: AILabProgramme[] = [
},
{
title: "Related services",
summary:
"The working bench around the core platform: deployment, corpus architecture, and privacy-aware data engineering.",
summary: "The working bench around the core platform: deployment, corpus architecture, and privacy-aware data engineering.",
bullets: [
"Document intelligence consulting",
"Knowledge corpus development",
@@ -235,40 +211,10 @@ export const aiLabProgrammes: AILabProgramme[] = [
];
export const aiLabSources: SourceCredit[] = [
{
label: "Gilligan Tech",
url: "https://gilligan.tech/",
note:
"Used for the Gilligan Tech positioning, engagement models, and the relationship to the sister platform.",
},
{
label: "Blue Note Logic / CorpusAI",
url: "https://ai.bluenotelogic.com/",
note:
"Used for the private AI, document intelligence, and source-cited corpus positioning.",
},
{
label: "Blue Note Logic service page",
url: "https://bluenotelogic.com/service.php?slug=corporate-memory-extraction",
note:
"Used for the sovereign model, EU-hosted RAG, isolated telemetry, and related-service descriptions.",
},
{
label: "Trivia & Tunes",
url: "https://triviaandtunes.no/",
note:
"Used for the live-hosted trivia and music angle on the homepage culture desk.",
},
{
label: "Do Better Norge",
url: "https://dobetternorge.no/",
note:
"Used for the advocacy and children's-rights positioning on the homepage civic desk.",
},
{
label: "Wikimedia Commons",
url: "https://commons.wikimedia.org/",
note:
"Used for the public-domain and openly licensed city, campus, and festival images woven through the homepage and section cards.",
},
{ label: "Gilligan Tech", url: "https://gilligan.tech/", note: "Used for the Gilligan Tech positioning, engagement models, and the relationship to the sister platform." },
{ label: "Blue Note Logic / CorpusAI", url: "https://ai.bluenotelogic.com/", note: "Used for the private AI, document intelligence, and source-cited corpus positioning." },
{ label: "Blue Note Logic service page", url: "https://bluenotelogic.com/service.php?slug=corporate-memory-extraction", note: "Used for the sovereign model, EU-hosted RAG, isolated telemetry, and related-service descriptions." },
{ label: "Trivia & Tunes", url: "https://triviaandtunes.no/", note: "Used for the live-hosted trivia and music angle on the homepage culture desk." },
{ label: "Do Better Norge", url: "https://dobetternorge.no/", note: "Used for the advocacy and children's-rights positioning on the homepage civic desk." },
{ label: "Wikimedia Commons", url: "https://commons.wikimedia.org/", note: "Used for the public-domain and openly licensed city, campus, and festival images woven through the homepage and section cards." },
];
+72 -93
View File
@@ -1,3 +1,5 @@
import type { LocaleCode } from "./locales";
export type ProjectImage = {
src: string;
alt: string;
@@ -14,19 +16,16 @@ export type ProjectSource = {
};
export const projectsFeature = {
eyebrow: "Section 10 / projects desk",
title: "The April project is a music-trivia machine with a rhinoceros in the judging booth.",
lede:
"Trivia & Tunes is the first full vibe-coded product in the portfolio: a live trivia platform built for homes, venues, tournaments, player identities, and now a genuinely theatrical AI layer.",
note:
"This desk distinguishes between what Trivia & Tunes says publicly today and what the working codebase already proves is built or actively being staged for release.",
eyebrow: { en: "Section 10 / projects desk", fr: undefined, nb: undefined },
title: { en: "The April project is a music-trivia machine with a rhinoceros in the judging booth.", fr: "Le projet d'avril est une machine de quiz musical avec un rhinocéros dans le rôle de juge.", nb: "April-prosjektet er en musikkquiz-maskin med en neshorn i dommerboden." },
lede: { en: "Trivia & Tunes is the first full vibe-coded product in the portfolio: a live trivia platform built for homes, venues, tournaments, player identities, and now a genuinely theatrical AI layer.", fr: "Trivia & Tunes est le premier produit entièrement codé par ambiance du portfolio : une plateforme de quiz en direct conçue pour les maisons, les lieux, les tournois, les identités des joueurs, et désormais une couche IA véritablement théâtrale.", nb: "Trivia & Tunes er det første fullstendig stemningskodede produktet i porteføljen: en live quizplattform bygget for hjem, arenaer, turneringer, spilleridentiteter, og nå et genuint teatralsk AI-lag." },
note: { en: "This desk distinguishes between what Trivia & Tunes says publicly today and what the working codebase already proves is built or actively being staged for release.", fr: "Ce bureau distingue ce que Trivia & Tunes annonce publiquement aujourd'hui de ce que la base de code prouve déjà être construit ou activement en préparation pour la sortie.", nb: "Denne skrivepulten skiller mellom hva Trivia & Tunes sier offentlig i dag og hva kodebasen allerede viser er bygget eller aktivt under oppseiling for lansering." },
};
export const projectsArticle = {
slug: "trivia-and-tunes-april-2026",
title: "Trivia & Tunes and the Arrival of True AI at the Pub Quiz",
excerpt:
"A spring 2026 project dispatch on music trivia, Blue Note Rhino, multiplayer formats, and the small matter of giving a quiz night an actual machine personality.",
excerpt: { en: "A spring 2026 project dispatch on music trivia, Blue Note Rhino, multiplayer formats, and the small matter of giving a quiz night an actual machine personality.", fr: "Un rapport de projet du printemps 2026 sur le quiz musical, Blue Note Rhino, les formats multijoueurs, et la petite affaire de donner une personnalité machine à une soirée quiz.", nb: "En vår 2026 prosjektoppdatering om musikkquiz, Blue Note Rhino, flerspillerformater, og den lille saken med å gi en quizkveld en faktisk maskinpersonlighet." },
publishedAt: "2026-04-06 10:15:00",
};
@@ -37,8 +36,7 @@ export const projectsImages: ProjectImage[] = [
credit: "Original Trivia & Tunes venue image",
sourceLabel: "Trivia & Tunes live uploads",
sourceUrl: "https://triviaandtunes.no/uploads/venues/venue_1768237941_ec432572.jpg",
note:
"Used here as project mood art: the right combination of room heat, street cold, and low-lit invitation.",
note: "Used here as project mood art: the right combination of room heat, street cold, and low-lit invitation.",
},
{
src: "https://triviaandtunes.no/uploads/teams/seed_team_microphone.png",
@@ -46,8 +44,7 @@ export const projectsImages: ProjectImage[] = [
credit: "Original Trivia & Tunes team artwork",
sourceLabel: "Trivia & Tunes live uploads",
sourceUrl: "https://triviaandtunes.no/uploads/teams/seed_team_microphone.png",
note:
"A good emblem for the product itself: performance, scoring, personality, and room presence.",
note: "A good emblem for the product itself: performance, scoring, personality, and room presence.",
},
{
src: "https://triviaandtunes.no/logoNew.png",
@@ -55,73 +52,57 @@ export const projectsImages: ProjectImage[] = [
credit: "Original Trivia & Tunes logo",
sourceLabel: "Trivia & Tunes live site",
sourceUrl: "https://triviaandtunes.no/logoNew.png",
note:
"The live public brand mark used by the current production site.",
note: "The live public brand mark used by the current production site.",
},
];
export const projectsSignals = [
{
title: "Public truth, right now",
summary:
"The live site currently presents Trivia & Tunes as a three-format product: solo host, live display, and pro live, with strong music integrations and venue-facing play.",
},
{
title: "The stronger local build",
summary:
"The working codebase already goes further: AI Home, AI Live, AI Solo, Blue Note Rhino, commentary, wrapups, model selection, and call logging are all visible in the repo on April 6, 2026.",
},
{
title: "Voice is the next turn of the screw",
summary:
"A dedicated TTS service is already staged for testing, with English, Norwegian, French, and additional voices mapped and ready for use.",
},
{ title: { en: "Public truth, right now", fr: "La vérité publique, à ce jour", nb: "Offentlig sannhet, akkurat nå" }, summary: { en: "The live site currently presents Trivia & Tunes as a three-format product: solo host, live display, and pro live, with strong music integrations and venue-facing play.", fr: "Le site en direct présente actuellement Trivia & Tunes comme un produit à trois formats : hôte solo, affichage en direct et pro en direct, avec de fortes intégrations musicales et un jeu orienté vers les lieux.", nb: "Den aktive nettsiden presenterer Trivia & Tunes som et produkt med tre formater: solovert, live display og pro live, med sterke musikkintegrasjoner og spill rettet mot arenaer." } },
{ title: { en: "The stronger local build", fr: "La version locale renforcée", nb: "Den sterkere lokale bygningen" }, summary: { en: "The working codebase already goes further: AI Home, AI Live, AI Solo, Blue Note Rhino, commentary, wrapups, model selection, and call logging are all visible in the repo on April 6, 2026.", fr: "La base de code en cours va déjà plus loin : AI Home, AI Live, AI Solo, Blue Note Rhino, commentaires, résumés, sélection de modèles et journalisation des appels sont tous visibles dans le dépôt au 6 avril 2026.", nb: "Kodebasen går allerede lenger: AI Home, AI Live, AI Solo, Blue Note Rhino, kommentarer, oppsummeringer, modellvalg og samtalelogger er alle synlige i repoet per 6. april 2026." } },
{ title: { en: "Voice is the next turn of the screw", fr: "La voix est le prochain tour de vis", nb: "Stemmen er neste skritt" }, summary: { en: "A dedicated TTS service is already staged for testing, with English, Norwegian, French, and additional voices mapped and ready for use.", fr: "Un service TTS dédié est déjà prêt pour les tests, avec des voix en anglais, norvégien, français et d'autres langues mappées et prêtes à l'emploi.", nb: "En dedikert TTS-tjeneste er allerede klar for testing, med engelsk, norsk, fransk og flere stemmer kartlagt og klare til bruk." } },
];
export const projectsCapabilities = [
{
title: "One game, multiple room geometries",
body:
"The product is not just a quiz app. It is a room-design system. One-screen home play, host-plus-display nights, and phone-driven player lanes all exist as distinct social formats rather than accidental layouts.",
},
{
title: "Blue Note Rhino as judge and emcee",
body:
"The AI layer is not framed as vague assistance. It grades free-text answers, applies strictness levels, generates wrapups, reacts to how a round went, and speaks in a built persona instead of silent utility.",
},
{
title: "Music is treated as infrastructure",
body:
"Spotify, Apple Music, and YouTube are not ornamental add-ons. They sit inside the trivia flow itself, which is why the whole thing feels closer to a hosted night than a generic browser game.",
},
{
title: "A real management surface",
body:
"Questions, players, teams, events, tournaments, venue pages, and media links are all treated as editable operating material, not hard-coded brochure text.",
},
{
title: "The first real vibe-coded product",
body:
"The internal build notes tell the truth plainly: start with room feel, iterate fast, keep it useful, document everything, and let the software earn its style through use.",
},
{
title: "The April turn",
body:
"This month matters because the product crosses a line from 'music trivia with good UX' into 'music trivia with an actual AI performance layer,' and that changes the identity of the whole thing.",
},
{ title: { en: "One game, multiple room geometries", fr: "Un jeu, plusieurs géométries de salle", nb: "Ett spill, flere romgeometrier" }, body: { en: "The product is not just a quiz app. It is a room-design system. One-screen home play, host-plus-display nights, and phone-driven player lanes all exist as distinct social formats rather than accidental layouts.", fr: "Le produit n'est pas juste une application de quiz. C'est un système de conception de salle. Le jeu à un écran pour la maison, les soirées avec hôte et affichage, et les couloirs de joueurs pilotés par téléphone existent tous comme des formats sociaux distincts plutôt que des dispositions accidentelles.", nb: "Produktet er ikke bare en quizapp. Det er et romdesignsystem. Énskjermspill for hjemmet, vert-pluss-display-kvelder og telefonstyrte spillerbaner eksisterer alle som distinkte sosiale formater snarere enn tilfeldige oppsett." } },
{ title: { en: "Blue Note Rhino as judge and emcee", fr: "Blue Note Rhino comme juge et maître de cérémonie", nb: "Blue Note Rhino som dommer og konferansier" }, body: { en: "The AI layer is not framed as vague assistance. It grades free-text answers, applies strictness levels, generates wrapups, reacts to how a round went, and speaks in a built persona instead of silent utility.", fr: "La couche IA n'est pas présentée comme une assistance vague. Elle évalue les réponses en texte libre, applique des niveaux de rigueur, génère des résumés, réagit à la manière dont une manche s'est déroulée, et parle dans une persona construite plutôt que d'être une utilité silencieuse.", nb: "AI-laget er ikke rammet inn som vag assistanse. Det vurderer fritekstsvar, anvender strenghetsnivåer, genererer oppsummeringer, reagerer på hvordan en runde gikk, og snakker i en bygget persona i stedet for å være en stille nyttefunksjon." } },
{ title: { en: "Music is treated as infrastructure", fr: "La musique est traitée comme une infrastructure", nb: "Musikk behandles som infrastruktur" }, body: { en: "Spotify, Apple Music, and YouTube are not ornamental add-ons. They sit inside the trivia flow itself, which is why the whole thing feels closer to a hosted night than a generic browser game.", fr: "Spotify, Apple Music et YouTube ne sont pas des ajouts ornementaux. Ils s'insèrent dans le flux de quiz lui-même, ce qui explique pourquoi l'ensemble ressemble plus à une soirée animée qu'à un jeu de navigateur générique.", nb: "Spotify, Apple Music og YouTube er ikke dekorative tillegg. De sitter inne i quizflyten selv, noe som gjør at hele opplevelsen føles nærmere en vertsstyrt kveld enn et generisk nettleserspill." } },
{ title: { en: "A real management surface", fr: "Une vraie surface de gestion", nb: "En ekte administrasjonsoverflate" }, body: { en: "Questions, players, teams, events, tournaments, venue pages, and media links are all treated as editable operating material, not hard-coded brochure text.", fr: "Les questions, les joueurs, les équipes, les événements, les tournois, les pages de lieux et les liens médias sont tous traités comme du matériel opérationnel éditable, et non comme du texte de brochure codé en dur.", nb: "Spørsmål, spillere, lag, arrangementer, turneringer, arena-sider og medielenker behandles som redigerbart operativt materiale, ikke hardkodet brosjyretekst." } },
{ title: { en: "The first real vibe-coded product", fr: "Le premier vrai produit codé par ambiance", nb: "Det første ekte stemningskodede produktet" }, body: { en: "The internal build notes tell the truth plainly: start with room feel, iterate fast, keep it useful, document everything, and let the software earn its style through use.", fr: "Les notes internes de construction disent la vérité simplement : commencer par l'ambiance de la salle, itérer rapidement, garder cela utile, tout documenter, et laisser le logiciel gagner son style par l'usage.", nb: "De interne byggnotatene sier sannheten enkelt: start med romfølelsen, iterer raskt, hold det nyttig, dokumenter alt, og la programvaren tjene sin stil gjennom bruk." } },
{ title: { en: "The April turn", fr: "Le tournant d'avril", nb: "April-svingen" }, body: { en: "This month matters because the product crosses a line from 'music trivia with good UX' into 'music trivia with an actual AI performance layer,' and that changes the identity of the whole thing.", fr: "Ce mois compte parce que le produit franchit une ligne entre 'quiz musical avec une bonne UX' et 'quiz musical avec une véritable couche de performance IA', et cela change l'identité de l'ensemble.", nb: "Denne måneden er viktig fordi produktet krysser en linje fra 'musikkquiz med god UX' til 'musikkquiz med et faktisk AI-ytelseslag', og det endrer identiteten til hele opplevelsen." } },
];
export const projectsDeskBody = [
export const projectsDeskBody: Record<LocaleCode, string[]> = {
en: [
"Trivia nights usually suffer from one of two problems. Either they are dead administrative exercises disguised as fun, or they are charming little messes that collapse as soon as a room gets big, loud, or demanding. Trivia & Tunes is interesting because it refuses that choice. It wants proper room energy and proper system design at the same time.",
"The live public site still presents the older triptych: solo host, live display, and pro live. Even in that public form, the platform already reads as more than a hobby project. It speaks in formats, roles, display logic, music sources, venues, tournaments, and player identity. That is the vocabulary of an ecosystem, not a one-off game.",
"The working April build goes further. In the local codebase reviewed on April 6, 2026, Trivia & Tunes has already developed an AI vocabulary of its own: AI Home, AI Live, AI Solo, model selection, per-question commentary, round wrapups, game wrapups, and a named judging persona called Blue Note Rhino. That is not decorative AI sprayed over an old surface. It is a real change in product character.",
"What makes the Rhino interesting is not merely that it grades. Plenty of systems can call a model and pretend they have solved judgment. Here the ambition is theatrical. The Rhino has strictness levels, a configurable personality, free-text semantic grading, and the beginnings of a running relationship with the room. In other words, the machine is not only a checker. It is becoming part of the night.",
"That matters because music trivia is social timing more than raw database retrieval. A good night has pacing, tension, recovery, banter, and a little danger. The AI layer works best when it helps the host keep those qualities alive rather than replacing them. The most promising parts of the local build do exactly that: commentary between questions, wrapups between rounds, and mode-specific flows that respect the room rather than flatten it.",
"The codebase also reveals another practical truth: this is not a toy AI experiment sitting in isolation. There are management surfaces, player records, team structures, events, tournaments, login gates, model logs, and admin settings. Even voice is already staged, with a TTS service ready for testing across multiple languages. If the GPU and IIS side are not always awake yet, the architecture is nevertheless pointing in the right direction.",
"That is why this April project deserves the Projects desk rather than a casual mention elsewhere. Trivia & Tunes is the first full, opinionated, public-facing vibe-coded product in the portfolio: music-heavy, UX-led, multilingual, operationally serious, and now decisively AI-backed. It has enough room heat to feel alive, and enough systems underneath to survive contact with real users.",
];
"That is why this April project deserves the Projects desk rather than a casual mention elsewhere. Trivia & Tunes is the first full, opinionated, public-facing vibe-coded product in the portfolio: music-heavy, UX-led, multilingual, operationally serious, and now decisively AI-backed. It has enough room heat to feel alive, and enough systems underneath to survive contact with real users."
],
fr: [
"Les soirées quiz souffrent généralement de l'un des deux problèmes. Soit elles sont des exercices administratifs morts déguisés en amusement, soit elles sont de charmants petits désordres qui s'effondrent dès qu'une salle devient grande, bruyante ou exigeante. Trivia & Tunes est intéressant parce qu'il refuse ce choix. Il veut à la fois une vraie énergie de salle et un vrai design de système.",
"Le site public en direct présente encore l'ancien triptyque : hôte solo, affichage en direct et pro en direct. Même sous cette forme publique, la plateforme se lit déjà comme plus qu'un projet de hobby. Elle parle en formats, rôles, logique d'affichage, sources musicales, lieux, tournois et identité des joueurs. C'est le vocabulaire d'un écosystème, pas d'un jeu unique.",
"La version d'avril va plus loin. Dans la base de code locale examinée le 6 avril 2026, Trivia & Tunes a déjà développé un vocabulaire IA propre : AI Home, AI Live, AI Solo, sélection de modèles, commentaires par question, résumés de manche, résumés de jeu, et une persona de juge nommée Blue Note Rhino. Ce n'est pas une IA décorative pulvérisée sur une vieille surface. C'est un vrai changement de caractère du produit.",
"Ce qui rend le Rhino intéressant, ce n'est pas seulement qu'il évalue. Beaucoup de systèmes peuvent appeler un modèle et prétendre avoir résolu le jugement. Ici, l'ambition est théâtrale. Le Rhino a des niveaux de rigueur, une personnalité configurable, une évaluation sémantique des réponses en texte libre, et les débuts d'une relation continue avec la salle. En d'autres termes, la machine n'est pas seulement un vérificateur. Elle devient partie intégrante de la soirée.",
"Cela compte parce que le quiz musical est plus une question de timing social que de récupération brute de base de données. Une bonne soirée a du rythme, de la tension, de la récupération, des plaisanteries et un peu de danger. La couche IA fonctionne mieux lorsqu'elle aide l'hôte à maintenir ces qualités vivantes plutôt que de les remplacer. Les parties les plus prometteuses de la version locale font exactement cela : commentaires entre les questions, résumés entre les manches, et flux spécifiques au mode qui respectent la salle plutôt que de l'aplatir.",
"La base de code révèle également une autre vérité pratique : ce n'est pas une expérience IA jouet isolée. Il y a des surfaces de gestion, des enregistrements de joueurs, des structures d'équipe, des événements, des tournois, des portes de connexion, des journaux de modèles et des paramètres d'administration. Même la voix est déjà prête, avec un service TTS prêt pour les tests dans plusieurs langues. Si le côté GPU et IIS n'est pas toujours éveillé, l'architecture pointe néanmoins dans la bonne direction.",
"C'est pourquoi ce projet d'avril mérite le bureau des projets plutôt qu'une mention occasionnelle ailleurs. Trivia & Tunes est le premier produit entièrement codé par ambiance, public et opiniâtre du portfolio : axé sur la musique, dirigé par l'UX, multilingue, opérationnellement sérieux, et maintenant résolument soutenu par l'IA. Il a assez de chaleur de salle pour se sentir vivant, et assez de systèmes en dessous pour survivre au contact avec de vrais utilisateurs."
],
nb: [
"Quizkvelder lider vanligvis av ett av to problemer. Enten er de døde administrative øvelser forkledd som moro, eller så er de sjarmerende små rot som kollapser så snart et rom blir stort, høyt eller krevende. Trivia & Tunes er interessant fordi det nekter å velge. Det vil ha ekte romenergi og ekte systemdesign samtidig.",
"Den aktive offentlige nettsiden presenterer fortsatt den eldre triptyken: solovert, live display og pro live. Selv i denne offentlige formen leser plattformen allerede som mer enn et hobbyprosjekt. Den snakker i formater, roller, displaylogikk, musikkskilder, arenaer, turneringer og spilleridentitet. Det er vokabularet til et økosystem, ikke et engangsspill.",
"Den lokale aprilbygningen går lenger. I kodebasen som ble gjennomgått 6. april 2026, har Trivia & Tunes allerede utviklet et eget AI-vokabular: AI Home, AI Live, AI Solo, modellvalg, per-spørsmål-kommentarer, rundeoppsummeringer, spilloppsummeringer, og en navngitt dommerpersona kalt Blue Note Rhino. Dette er ikke dekorativ AI sprayet over en gammel overflate. Det er en reell endring i produktkarakter.",
"Det som gjør Rhino interessant er ikke bare at det vurderer. Mange systemer kan kalle en modell og late som de har løst dommerproblemet. Her er ambisjonen teatralsk. Rhino har strenghetsnivåer, en konfigurerbar personlighet, semantisk vurdering av fritekstsvar, og begynnelsen på et løpende forhold til rommet. Med andre ord, maskinen er ikke bare en sjekker. Den blir en del av kvelden.",
"Det betyr noe fordi musikkquiz handler mer om sosial timing enn rå databasehenting. En god kveld har tempo, spenning, gjenoppretting, småprat og litt fare. AI-laget fungerer best når det hjelper verten med å holde disse kvalitetene levende i stedet for å erstatte dem. De mest lovende delene av den lokale bygningen gjør akkurat det: kommentarer mellom spørsmål, oppsummeringer mellom runder, og modusspesifikke flyter som respekterer rommet i stedet for å flate det ut.",
"Kodebasen avslører også en annen praktisk sannhet: dette er ikke et leketøy-AI-eksperiment som sitter isolert. Det finnes administrasjonsoverflater, spillerregistre, lagstrukturer, arrangementer, turneringer, innloggingsporter, modelllogger og admininnstillinger. Selv stemme er allerede klar, med en TTS-tjeneste klar for testing på flere språk. Hvis GPU- og IIS-siden ikke alltid er våken ennå, peker arkitekturen likevel i riktig retning.",
"Det er derfor dette aprilprosjektet fortjener Prosjekt-skrivebordet i stedet for en tilfeldig omtale andre steder. Trivia & Tunes er det første fullstendig stemningskodede, offentlig orienterte produktet i porteføljen: musikkfokusert, UX-ledet, flerspråklig, operativt seriøst, og nå definitivt AI-støttet. Det har nok romvarme til å føles levende, og nok systemer under til å overleve kontakt med ekte brukere."
],
};
export const projectsArticleBody = [
export const projectsArticleBody: Record<LocaleCode, string[]> = {
en: [
"The phrase 'true AI' is abused so often that it usually arrives smelling of stale venture decks. But once in a while a project earns the phrase by changing what the product is allowed to feel like. Trivia & Tunes has reached that threshold this spring.",
"On the public site today, Trivia & Tunes already presents itself as a layered trivia platform rather than a simple quiz toy. The live games page offers three recognizable public formats: a solo host edition for home use, a live game with a big-screen display, and a pro live version with mobile answering for players. That is the respectable front room, and it is already more ambitious than most trivia products ever become.",
"Behind that public face, the working local build tells a hotter story. The repo reviewed on April 6, 2026 shows a second life already under construction: AI Home, AI Live, AI Solo, model management, call logging, wrapup prompts, per-question commentary, and a fully named judging persona, Blue Note Rhino. In other words, this is no longer only a trivia platform with good screens. It is becoming a performance system with machine timing.",
@@ -129,36 +110,34 @@ export const projectsArticleBody = [
"The clever part is that the underlying architecture still respects the old truths of quiz nights. Music remains central, with Spotify, Apple Music, and YouTube embedded into the experience. Room geometry matters. One-screen living-room play is not treated the same as host-plus-display nights or the more competitive phone-plus-display format. This is where the vibe coding note becomes more than a slogan. The code follows the social feel of the room.",
"Even the build notes are unusually honest. They talk about starting with the desired feeling, iterating quickly, keeping the code organized without fetishizing perfection, and making the product work beautifully for non-technical people. Normally this kind of internal document is too earnest to quote. Here it belongs to the story, because the resulting product really does show the fingerprints of that method.",
"Then there is voice. The TTS service sitting in the local project is not yet a triumphant public launch, and I will not pretend otherwise. But it is real enough to matter: mapped voices for English, Norwegian, French, German, Spanish, Italian, Portuguese, Polish, Dutch, Swedish, Danish, and Finnish, all framed as part of the next testing layer. That means the Rhino is not only a text persona waiting in the shadows. It is edging toward an audible one.",
"So April's verdict is simple. Trivia & Tunes is the first full vibe-coded product in the broader Dave Gilligan orbit to step across the line from strong interface into living system. It already knows how to run a room. Now it is learning how to talk back.",
];
"So April's verdict is simple. Trivia & Tunes is the first full vibe-coded product in the broader Dave Gilligan orbit to step across the line from strong interface into living system. It already knows how to run a room. Now it is learning how to talk back."
],
fr: [
"L'expression 'véritable IA' est tellement abusée qu'elle arrive généralement avec une odeur de présentations d'investissement périmées. Mais de temps en temps, un projet mérite cette expression en changeant ce que le produit est autorisé à ressentir. Trivia & Tunes a atteint ce seuil ce printemps.",
"Sur le site public aujourd'hui, Trivia & Tunes se présente déjà comme une plateforme de quiz stratifiée plutôt qu'un simple jouet de quiz. La page des jeux en direct propose trois formats publics reconnaissables : une édition hôte solo pour un usage domestique, un jeu en direct avec un grand écran d'affichage, et une version pro en direct avec réponses mobiles pour les joueurs. C'est le salon respectable, et c'est déjà plus ambitieux que la plupart des produits de quiz ne le deviennent jamais.",
"Derrière cette façade publique, la version locale raconte une histoire plus chaude. Le dépôt examiné le 6 avril 2026 montre une seconde vie déjà en construction : AI Home, AI Live, AI Solo, gestion des modèles, journalisation des appels, invites de résumés, commentaires par question, et une persona de juge entièrement nommée, Blue Note Rhino. En d'autres termes, ce n'est plus seulement une plateforme de quiz avec de bons écrans. Cela devient un système de performance avec un timing machine.",
"Blue Note Rhino est le bon type de dépassement. Le guide IA présente le Rhino non pas comme une utilité cachée mais comme un véritable personnage de salle : un évaluateur, un commentateur, et un maître de cérémonie taquin qui comprend les réponses en texte libre de manière sémantique au lieu de forcer les joueurs dans des cases à choix multiples. L'hôte peut ajuster la rigueur. La machine peut générer des résumés de manche et de jeu. Elle peut réagir à la performance des joueurs. C'est une identité de produit, pas seulement une consommation d'API.",
"La partie intelligente est que l'architecture sous-jacente respecte encore les anciennes vérités des soirées quiz. La musique reste centrale, avec Spotify, Apple Music et YouTube intégrés dans l'expérience. La géométrie de la salle compte. Le jeu à un écran dans le salon n'est pas traité de la même manière que les soirées avec hôte et affichage ou le format plus compétitif téléphone-plus-affichage. C'est là que la note de codage par ambiance devient plus qu'un slogan. Le code suit le ressenti social de la salle.",
"Même les notes de construction sont exceptionnellement honnêtes. Elles parlent de commencer par le ressenti désiré, d'itérer rapidement, de garder le code organisé sans fétichiser la perfection, et de faire fonctionner le produit magnifiquement pour les personnes non techniques. Normalement, ce genre de document interne est trop sérieux pour être cité. Ici, il appartient à l'histoire, parce que le produit résultant montre vraiment les empreintes de cette méthode.",
"Et puis il y a la voix. Le service TTS dans le projet local n'est pas encore un lancement public triomphal, et je ne prétendrai pas le contraire. Mais il est suffisamment réel pour compter : des voix mappées pour l'anglais, le norvégien, le français, l'allemand, l'espagnol, l'italien, le portugais, le polonais, le néerlandais, le suédois, le danois et le finnois, toutes encadrées comme partie de la prochaine couche de test. Cela signifie que le Rhino n'est pas seulement une persona textuelle attendant dans l'ombre. Il se rapproche d'une persona audible.",
"Donc le verdict d'avril est simple. Trivia & Tunes est le premier produit entièrement codé par ambiance dans l'orbite élargie de Dave Gilligan à franchir la ligne entre une interface forte et un système vivant. Il sait déjà comment gérer une salle. Maintenant, il apprend à répondre."
],
nb: [
"Uttrykket 'ekte AI' blir misbrukt så ofte at det vanligvis ankommer med en lukt av utdaterte investorpresentasjoner. Men av og til fortjener et prosjekt uttrykket ved å endre hva produktet får lov til å føles som. Trivia & Tunes har nådd den terskelen denne våren.",
"På den offentlige nettsiden i dag presenterer Trivia & Tunes seg allerede som en lagdelt quizplattform snarere enn et enkelt quiz-leketøy. Siden for live spill tilbyr tre gjenkjennelige offentlige formater: en solovert-utgave for hjemmebruk, et live spill med storskjermdisplay, og en pro live-versjon med mobilbesvarelse for spillere. Det er den respektable stuen, og det er allerede mer ambisiøst enn de fleste quizprodukter noen gang blir.",
"Bak denne offentlige fasaden forteller den lokale bygningen en varmere historie. Repoet som ble gjennomgått 6. april 2026 viser et annet liv allerede under konstruksjon: AI Home, AI Live, AI Solo, modelladministrasjon, samtalelogging, oppsummeringsprompt, per-spørsmål-kommentarer, og en fullt navngitt dommerpersona, Blue Note Rhino. Med andre ord, dette er ikke lenger bare en quizplattform med gode skjermer. Det blir et ytelsessystem med maskintiming.",
"Blue Note Rhino er den rette typen overambisjon. AI-guiden rammer inn Rhino ikke som en skjult nyttefunksjon, men som en faktisk romkarakter: en vurderer, kommentator og ertende konferansier som forstår fritekstsvar semantisk i stedet for å tvinge spillere inn i flervalgskasser. Vert kan justere strenghet. Maskinen kan generere runde- og spilloppsummeringer. Den kan reagere på hvordan spillerne presterte. Det er produktidentitet, ikke bare API-forbruk.",
"Det smarte er at den underliggende arkitekturen fortsatt respekterer de gamle sannhetene om quizkvelder. Musikk forblir sentralt, med Spotify, Apple Music og YouTube integrert i opplevelsen. Romgeometri betyr noe. Énskjermspill i stuen behandles ikke på samme måte som vert-pluss-display-kvelder eller det mer konkurransedyktige telefon-pluss-display-formatet. Det er her stemningskoding blir mer enn et slagord. Koden følger den sosiale følelsen av rommet.",
"Selv byggnotatene er uvanlig ærlige. De snakker om å starte med ønsket følelse, iterere raskt, holde koden organisert uten å fetisjere perfeksjon, og få produktet til å fungere vakkert for ikke-tekniske mennesker. Normalt er denne typen interne dokumenter for alvorlige til å sitere. Her hører de til historien, fordi det resulterende produktet virkelig viser fingeravtrykkene av den metoden.",
"Og så er det stemmen. TTS-tjenesten som ligger i det lokale prosjektet er ennå ikke en triumferende offentlig lansering, og jeg vil ikke late som noe annet. Men den er ekte nok til å telle: kartlagte stemmer for engelsk, norsk, fransk, tysk, spansk, italiensk, portugisisk, polsk, nederlandsk, svensk, dansk og finsk, alle rammet inn som en del av neste testlag. Det betyr at Rhino ikke bare er en tekstpersona som venter i skyggene. Den nærmer seg en hørbar en.",
"Så april-dommen er enkel. Trivia & Tunes er det første fullstendig stemningskodede produktet i den bredere Dave Gilligan-sfæren som krysser linjen fra sterk grensesnitt til levende system. Den vet allerede hvordan man styrer et rom. Nå lærer den å svare."
],
};
export const projectsSources: ProjectSource[] = [
{
label: "Trivia & Tunes live homepage",
href: "https://triviaandtunes.no/",
note:
"Used for the current public brand, tagline, and confirmation that the live production shell is active.",
},
{
label: "Trivia & Tunes games page",
href: "https://triviaandtunes.no/games/",
note:
"Used for the live public format breakdown: solo host, live game with big-screen display, and pro live with mobile answering.",
},
{
label: "Trivia & Tunes live image assets",
href: "https://triviaandtunes.no/uploads/venues/venue_1768237941_ec432572.jpg",
note:
"Used for original project imagery pulled from the live Trivia & Tunes installation.",
},
{
label: "Local Trivia & Tunes working repository",
note:
"Reviewed locally on April 6, 2026 for AI Home, AI Live, AI Solo, Blue Note Rhino, wrapups, per-question commentary, model management, call logging, and voice-service staging.",
},
{
label: "How We Built Trivia & Tunes",
note:
"Used for the internal product-development narrative around vibe coding, iterative delivery, UX-first decisions, and the explanation of the system for non-technical readers.",
},
{ label: "Trivia & Tunes live homepage", href: "https://triviaandtunes.no/", note: "Used for the current public brand, tagline, and confirmation that the live production shell is active." },
{ label: "Trivia & Tunes games page", href: "https://triviaandtunes.no/games/", note: "Used for the live public format breakdown: solo host, live game with big-screen display, and pro live with mobile answering." },
{ label: "Trivia & Tunes live image assets", href: "https://triviaandtunes.no/uploads/venues/venue_1768237941_ec432572.jpg", note: "Used for original project imagery pulled from the live Trivia & Tunes installation." },
{ label: "Local Trivia & Tunes working repository", note: "Reviewed locally on April 6, 2026 for AI Home, AI Live, AI Solo, Blue Note Rhino, wrapups, per-question commentary, model management, call logging, and voice-service staging." },
{ label: "How We Built Trivia & Tunes", note: "Used for the internal product-development narrative around vibe coding, iterative delivery, UX-first decisions, and the explanation of the system for non-technical readers." },
];
+152 -141
View File
@@ -1,13 +1,15 @@
import type { LocaleCode } from "./locales";
export type LaunchSection = {
slug: string;
label: string;
title: string;
summary: string;
tone: string;
strap: string;
coverline: string;
motif: string;
samples: string[];
summary: Record<LocaleCode, string>;
tone: Record<LocaleCode, string>;
strap: Record<LocaleCode, string>;
coverline: Record<LocaleCode, string>;
motif: Record<LocaleCode, string>;
samples: Record<LocaleCode, string[]>;
};
export type SchoolDossier = {
@@ -17,143 +19,115 @@ export type SchoolDossier = {
program: string;
city: string;
logo: string;
teaser: string;
teaser: Record<LocaleCode, string>;
};
export const hero = {
kicker: "Pataphysical bulletin / Kongsberg edition",
title: "A high-tech newsmagazine disguised as one man's improbable paper trail.",
lede:
"Private AI, jazz basements, multilingual weather, family rights, and systems work from Ringwood to Kongsberg, edited with equal parts brass, evidence, and deliberate mischief.",
sublede:
"Inside this issue: Blue Note Logic in the machine room, Gilligan Tech in the field, Do Better Norge in the civic file, Trivia & Tunes in the live-wire culture pages, and school dossiers written like contraband literature.",
kicker: { en: "Pataphysical bulletin / Kongsberg edition", fr: "Bulletin pataphysique / Édition Kongsberg", nb: "Pataphysisk bulletin / Kongsberg-utgave" },
title: { en: "A high-tech newsmagazine disguised as one man's improbable paper trail.", fr: "Un magazine d'actualités high-tech déguisé en le parcours improbable d'un homme.", nb: "Et høyteknologisk nyhetsmagasin forkledd som én manns usannsynlige papirspor." },
lede: { en: "Private AI, jazz basements, multilingual weather, family rights, and systems work from Ringwood to Kongsberg, edited with equal parts brass, evidence, and deliberate mischief.", fr: "IA privée, caves de jazz, météo multilingue, droits familiaux et travail systémique de Ringwood à Kongsberg, édité avec autant de cuivres, de preuves et de malice délibérée.", nb: "Privat AI, jazzkjellere, flerspråklig vær, familierettigheter og systemarbeid fra Ringwood til Kongsberg, redigert med like deler messing, bevis og bevisst rampestreker." },
sublede: { en: "Inside this issue: Blue Note Logic in the machine room, Gilligan Tech in the field, Do Better Norge in the civic file, Trivia & Tunes in the live-wire culture pages, and school dossiers written like contraband literature.", fr: "Dans ce numéro : Blue Note Logic dans la salle des machines, Gilligan TECH sur le terrain, Do Better Norge dans le dossier civique, Trivia & Tunes dans les pages culturelles sous tension, et des dossiers scolaires écrits comme de la littérature de contrebande.", nb: "I denne utgaven: Blue Note Logic i maskinrommet, Gilligan TECH i felten, Do Better Norge i det sivile arkivet, Trivia & Tunes i de kulturspente sidene, og skoledokumenter skrevet som forbudt litteratur." },
};
export const launchSections: LaunchSection[] = [
{
slug: "business",
label: "01",
title: "Business",
summary: "Consulting notes, AI architecture, and operator essays for people who prefer working systems to rented theater.",
tone: "Sharp, practical, anti-buzzword.",
strap: "Operator studies for adults who are tired of consultant vapor.",
coverline: "The anti-buzzword ledger.",
motif: "Pricing, systems, execution, and elegant refusal.",
samples: ["The one-laptop doctrine", "SME systems with teeth", "Boardroom notes without cologne"],
slug: "business", label: "01", title: "Business",
summary: { en: "Consulting notes, AI architecture, and operator essays for people who prefer working systems to rented theater.", fr: "Notes de conseil, architectures IA et essais d'opérateurs pour ceux qui préfèrent les systèmes fonctionnels au théâtre en location.", nb: "Konsulentnotater, AI-arkitektur og operatør-essays for folk som foretrekker fungerende systemer fremfor leid teater." },
tone: { en: "Sharp, practical, anti-buzzword.", fr: "Tranchant, pratique, anti-buzzword.", nb: "Skarpt, praktisk, anti-buzzword." },
strap: { en: "Operator studies for adults who are tired of consultant vapor.", fr: "Études d'opérateurs pour adultes lassés des consultants vaporeux.", nb: "Operatørstudier for voksne som er lei av konsulentprat." },
coverline: { en: "The anti-buzzword ledger.", fr: "Le registre anti-buzzword.", nb: "Anti-buzzword-regnskapet." },
motif: { en: "Pricing, systems, execution, and elegant refusal.", fr: "Tarification, systèmes, exécution et refus élégant.", nb: "Prising, systemer, utførelse og elegant avslag." },
samples: { en: ["The one-laptop doctrine","SME systems with teeth","Boardroom notes without cologne"], fr: ["La doctrine du seul ordinateur portable","Systèmes PME avec du mordant","Notes de salle de conseil sans eau de Cologne"], nb: ["Én-laptop-doktrinen","SME-systemer med tenner","Styreromsnotater uten parfyme"] },
},
{
slug: "education",
label: "02",
title: "Education",
summary: "School dossiers, study notes, intellectual migrations, and academic detours worth keeping.",
tone: "Curious, rigorous, lightly mischievous.",
strap: "Degrees as cities, schools as chapters, study as migration.",
coverline: "The dossier issue.",
motif: "Memory, theory, institutions, and weather.",
samples: ["Villanova as overture", "Krakow and the bureaucracy sublime", "Kongsberg after experience"],
slug: "education", label: "02", title: "Education",
summary: { en: "School dossiers, study notes, intellectual migrations, and academic detours worth keeping.", fr: "Dossiers scolaires, notes d'étude, migrations intellectuelles et détours académiques qui valent la peine d'être conservés.", nb: "Skolemapper, studienotater, intellektuelle migrasjoner og akademiske omveier verdt å beholde." },
tone: { en: "Curious, rigorous, lightly mischievous.", fr: "Curieux, rigoureux, légèrement espiègle.", nb: "Nysgjerrig, grundig, lett rampete." },
strap: { en: "Degrees as cities, schools as chapters, study as migration.", fr: "Les diplômes comme des villes, les écoles comme des chapitres, l'étude comme une migration.", nb: "Grader som byer, skoler som kapitler, studier som migrasjon." },
coverline: { en: "The dossier issue.", fr: "Le numéro dossier.", nb: "Dossier-utgaven." },
motif: { en: "Memory, theory, institutions, and weather.", fr: "Mémoire, théorie, institutions et météo.", nb: "Minne, teori, institusjoner og vær." },
samples: { en: ["Villanova as overture","Krakow and the bureaucracy sublime","Kongsberg after experience"], fr: ["Villanova comme ouverture","Cracovie et la bureaucratie sublime","Kongsberg après l'expérience"], nb: ["Villanova som ouverture","Krakow og den sublime byråkratien","Kongsberg etter erfaring"] },
},
{
slug: "family",
label: "03",
title: "Family",
summary: "A warmer archive for memory, milestones, and the private weather that keeps the machinery worth running.",
tone: "Private-minded, generous, alive.",
strap: "The soft archive, still edited like it matters.",
coverline: "Domestic front pages.",
motif: "Photographs, notes, kinship, and time.",
samples: ["Albums with captions worth reading", "Milestones without sentimentality", "A household in motion"],
slug: "family", label: "03", title: "Family",
summary: { en: "A warmer archive for memory, milestones, and the private weather that keeps the machinery worth running.", fr: "Une archive plus chaleureuse pour la mémoire, les jalons et la météo privée qui donne un sens à la machine.", nb: "Et varmere arkiv for minner, milepæler og det private været som gjør maskineriet verdt å holde i gang." },
tone: { en: "Private-minded, generous, alive.", fr: "Intimiste, généreux, vivant.", nb: "Privat, generøs, levende." },
strap: { en: "The soft archive, still edited like it matters.", fr: "L'archive douce, toujours éditée comme si elle comptait.", nb: "Det myke arkivet, fortsatt redigert som om det betyr noe." },
coverline: { en: "Domestic front pages.", fr: "Les premières pages domestiques.", nb: "Hjemlige forsider." },
motif: { en: "Photographs, notes, kinship, and time.", fr: "Photographies, notes, liens familiaux et temps.", nb: "Fotografier, notater, slektskap og tid." },
samples: { en: ["Albums with captions worth reading","Milestones without sentimentality","A household in motion"], fr: ["Albums avec légendes qui valent la peine d'être lues","Jalons sans sentimentalisme","Un foyer en mouvement"], nb: ["Album med bildetekster verdt å lese","Milepæler uten sentimentalitet","Et hushold i bevegelse"] },
},
{
slug: "fun-postings",
label: "04",
title: "Fun Postings",
summary: "Odd notices, cultural flyers, side projects, and the sort of elegant nonsense that deserves proper typesetting.",
tone: "Playful, deadpan, collectible.",
strap: "The classified page gets strange and starts wearing cologne.",
coverline: "Useful nonsense, neatly set.",
motif: "Flyers, jokes, events, absurd notices.",
samples: ["Cultural dispatches", "Ridiculous but sincere", "Posters for improbable evenings"],
slug: "fun-postings", label: "04", title: "Fun Postings",
summary: { en: "Odd notices, cultural flyers, side projects, and the sort of elegant nonsense that deserves proper typesetting.", fr: "Avis insolites, flyers culturels, projets parallèles et ce genre de non-sens élégant qui mérite une typographie soignée.", nb: "Merkelige kunngjøringer, kulturelle flyers, sideprosjekter og den typen elegant nonsens som fortjener ordentlig typografi." },
tone: { en: "Playful, deadpan, collectible.", fr: "Ludique, pince-sans-rire, collectionnable.", nb: "Leken, deadpan, samlerverdig." },
strap: { en: "The classified page gets strange and starts wearing cologne.", fr: "La page des petites annonces devient étrange et commence à porter de l'eau de Cologne.", nb: "Rubrikkannonsen blir rar og begynner å bruke parfyme." },
coverline: { en: "Useful nonsense, neatly set.", fr: "Non-sens utile, soigneusement présenté.", nb: "Nyttig nonsens, pent satt." },
motif: { en: "Flyers, jokes, events, absurd notices.", fr: "Flyers, blagues, événements, avis absurdes.", nb: "Flyers, vitser, arrangementer, absurde kunngjøringer." },
samples: { en: ["Cultural dispatches","Ridiculous but sincere","Posters for improbable evenings"], fr: ["Dépêches culturelles","Ridicule mais sincère","Affiches pour des soirées improbables"], nb: ["Kulturelle utsendelser","Latterlig men oppriktig","Plakater for usannsynlige kvelder"] },
},
{
slug: "writing",
label: "05",
title: "Writing",
summary: "This month's writing desk runs on Boris Vian: novels with trapdoors, Vernon Sullivan weather, Saint-Germain smoke, and bibliography arranged like a contraband route.",
tone: "Literary, international, smoky.",
strap: "A low-lit file on Boris Vian, jazz syntax, and the exact science of glorious exception.",
coverline: "Boris Vian in the side door.",
motif: "Novels, jazz, pataphysics, counterfeit signatures, and Paris after midnight.",
samples: [
"The engineer of exceptions",
"Five doors into Boris Vian",
"Why Saint-Germain still leaks into the prose",
],
slug: "writing", label: "05", title: "Writing",
summary: { en: "This month's writing desk runs on Boris Vian: novels with trapdoors, Vernon Sullivan weather, Saint-Germain smoke, and bibliography arranged like a contraband route.", fr: "Le bureau d'écriture de ce mois-ci fonctionne sur Boris Vian : romans avec trappes, météo Vernon Sullivan, fumée de Saint-Germain et bibliographie arrangée comme une route de contrebande.", nb: "Denne månedens skrivebord drives av Boris Vian: romaner med fallgruver, Vernon Sullivan-vær, Saint-Germain-røyk og bibliografi arrangert som en smuglerute." },
tone: { en: "Literary, international, smoky.", fr: "Littéraire, international, enfumé.", nb: "Litterær, internasjonal, røykfylt." },
strap: { en: "A low-lit file on Boris Vian, jazz syntax, and the exact science of glorious exception.", fr: "Un dossier en lumière tamisée sur Boris Vian, la syntaxe jazz et la science exacte de l'exception glorieuse.", nb: "En lavmælt fil om Boris Vian, jazzsyntaks og den eksakte vitenskapen om strålende unntak." },
coverline: { en: "Boris Vian in the side door.", fr: "Boris Vian par la porte dérobée.", nb: "Boris Vian via sidedøren." },
motif: { en: "Novels, jazz, pataphysics, counterfeit signatures, and Paris after midnight.", fr: "Romans, jazz, pataphysique, signatures contrefaites et Paris après minuit.", nb: "Romaner, jazz, patafysikk, falske signaturer og Paris etter midnatt." },
samples: { en: ["The engineer of exceptions","Five doors into Boris Vian","Why Saint-Germain still leaks into the prose"], fr: ["L'ingénieur des exceptions","Cinq portes vers Boris Vian","Pourquoi Saint-Germain s'infiltre encore dans la prose"], nb: ["Unntakenes ingeniør","Fem dører inn til Boris Vian","Hvorfor Saint-Germain fortsatt siver inn i prosaen"] },
},
{
slug: "jazz-music",
label: "06",
title: "Jazz and Music",
summary: "Listening notes, Kongsberg nights, Caveau memories, and the low-lit logic of serious groove.",
tone: "Velvet, brassy, precise.",
strap: "For records, rooms, and players who understand that rhythm is governance.",
coverline: "Blue notes and side doors.",
motif: "Listening, memory, improvisation, timing.",
samples: ["Records after midnight", "Why groove beats branding", "A private history of swing"],
slug: "jazz-music", label: "06", title: "Jazz and Music",
summary: { en: "Listening notes, Kongsberg nights, Caveau memories, and the low-lit logic of serious groove.", fr: "Notes d'écoute, nuits à Kongsberg, souvenirs de Caveau et la logique tamisée du groove sérieux.", nb: "Lyttenotater, Kongsberg-netter, Caveau-minner og den lavmælte logikken til seriøs groove." },
tone: { en: "Velvet, brassy, precise.", fr: "Velours, cuivré, précis.", nb: "Fløyel, messing, presist." },
strap: { en: "For records, rooms, and players who understand that rhythm is governance.", fr: "Pour les disques, les salles et les musiciens qui comprennent que le rythme est une gouvernance.", nb: "For plater, rom og musikere som forstår at rytme er styring." },
coverline: { en: "Blue notes and side doors.", fr: "Blue notes et portes dérobées.", nb: "Blue notes og sidedører." },
motif: { en: "Listening, memory, improvisation, timing.", fr: "Écoute, mémoire, improvisation, timing.", nb: "Lytting, minne, improvisasjon, timing." },
samples: { en: ["Records after midnight","Why groove beats branding","A private history of swing"], fr: ["Disques après minuit","Pourquoi le groove l'emporte sur le branding","Une histoire privée du swing"], nb: ["Plater etter midnatt","Hvorfor groove slår merkevarebygging","En privat historie om swing"] },
},
{
slug: "languages",
label: "07",
title: "Languages",
summary: "English, French, and Norwegian switching places without losing the joke, the seduction, or the filing detail.",
tone: "Polyglot, sly, welcoming.",
strap: "A section for mistranslation, seduction, and grammatical diplomacy.",
coverline: "The multilingual cabinet.",
motif: "French, Norwegian, English, and elegant trouble.",
samples: ["Words that travel badly", "Translation as personality", "Syntax with a passport"],
slug: "languages", label: "07", title: "Languages",
summary: { en: "English, French, and Norwegian switching places without losing the joke, the seduction, or the filing detail.", fr: "Anglais, français et norvégien échangent leurs places sans perdre la blague, la séduction ou le détail archivistique.", nb: "Engelsk, fransk og norsk bytter plass uten å miste vitsen, forførelsen eller arkivdetaljen." },
tone: { en: "Polyglot, sly, welcoming.", fr: "Polyglotte, subtil, accueillant.", nb: "Polyglott, lun, imøtekommende." },
strap: { en: "A section for mistranslation, seduction, and grammatical diplomacy.", fr: "Une section pour les mauvaises traductions, la séduction et la diplomatie grammaticale.", nb: "En seksjon for feiltolkning, forførelse og grammatisk diplomati." },
coverline: { en: "The multilingual cabinet.", fr: "Le cabinet multilingue.", nb: "Det flerspråklige kabinettet." },
motif: { en: "French, Norwegian, English, and elegant trouble.", fr: "Français, norvégien, anglais et troubles élégants.", nb: "Fransk, norsk, engelsk og elegant trøbbel." },
samples: { en: ["Words that travel badly","Translation as personality","Syntax with a passport"], fr: ["Mots qui voyagent mal","La traduction comme personnalité","Syntaxe avec passeport"], nb: ["Ord som reiser dårlig","Oversettelse som personlighet","Syntaks med pass"] },
},
{
slug: "ai-lab",
label: "08",
title: "AI Lab",
summary: "Private corpora, cited answers, multilingual agents, and practical machine intelligence with actual memory.",
tone: "Forward-looking, grounded, open source friendly.",
strap: "Machine intelligence without the conference lanyard.",
coverline: "The atelier for useful futures.",
motif: "Prompts, tooling, agentic systems, humane interfaces.",
samples: ["AI that can write in-house style", "Editorial controls, not gimmicks", "Open tools with manners"],
slug: "ai-lab", label: "08", title: "AI Lab",
summary: { en: "Private corpora, cited answers, multilingual agents, and practical machine intelligence with actual memory.", fr: "Corpus privés, réponses citées, agents multilingues et intelligence artificielle pratique avec une mémoire réelle.", nb: "Private korpora, siterte svar, flerspråklige agenter og praktisk maskinintelligens med faktisk hukommelse." },
tone: { en: "Forward-looking, grounded, open source friendly.", fr: "Visionnaire, ancré, favorable à l'open source.", nb: "Framtidsrettet, jordnær, vennlig mot åpen kildekode." },
strap: { en: "Machine intelligence without the conference lanyard.", fr: "Intelligence artificielle sans le badge de conférence.", nb: "Maskinintelligens uten konferansebånd." },
coverline: { en: "The atelier for useful futures.", fr: "L'atelier des futurs utiles.", nb: "Atelieret for nyttige fremtider." },
motif: { en: "Prompts, tooling, agentic systems, humane interfaces.", fr: "Prompts, outils, systèmes agentiques, interfaces humaines.", nb: "Prompter, verktøy, agentbaserte systemer, humane grensesnitt." },
samples: { en: ["AI that can write in-house style","Editorial controls, not gimmicks","Open tools with manners"], fr: ["IA capable d'écrire dans un style maison","Contrôles éditoriaux, pas des gadgets","Outils ouverts avec des manières"], nb: ["AI som kan skrive i husstil","Redaksjonelle kontroller, ikke gimmicker","Åpne verktøy med manerer"] },
},
{
slug: "norway",
label: "09",
title: "Norway",
summary: "Kongsberg dispatches, civic reporting, immigrant-family realities, and Norwegian life observed without brochure language.",
tone: "Observant, civic, place-aware.",
strap: "A local paper for one town and several realities.",
coverline: "Kongsberg, correctly observed.",
motif: "Place, policy, weather, Nordic texture.",
samples: ["Silver city dispatches", "The civic weather report", "Norway beyond brochure language"],
slug: "norway", label: "09", title: "Norway",
summary: { en: "Kongsberg dispatches, civic reporting, immigrant-family realities, and Norwegian life observed without brochure language.", fr: "Dépêches de Kongsberg, reportages civiques, réalités des familles immigrées et vie norvégienne observée sans langage de brochure.", nb: "Kongsberg-utsendelser, samfunnsrapportering, innvandrerfamilie-realiteter og norsk liv observert uten brosjyrespråk." },
tone: { en: "Observant, civic, place-aware.", fr: "Observateur, civique, conscient du lieu.", nb: "Observant, samfunnsengasjert, stedbevisst." },
strap: { en: "A local paper for one town and several realities.", fr: "Un journal local pour une ville et plusieurs réalités.", nb: "En lokalavis for én by og flere realiteter." },
coverline: { en: "Kongsberg, correctly observed.", fr: "Kongsberg, correctement observé.", nb: "Kongsberg, korrekt observert." },
motif: { en: "Place, policy, weather, Nordic texture.", fr: "Lieu, politique, météo, texture nordique.", nb: "Sted, politikk, vær, nordisk tekstur." },
samples: { en: ["Silver city dispatches","The civic weather report","Norway beyond brochure language"], fr: ["Dépêches de la ville d'argent","Le bulletin météo civique","La Norvège au-delà du langage des brochures"], nb: ["Sølvby-utsendelser","Den samfunnsmessige værmeldingen","Norge uten brosjyrespråk"] },
},
{
slug: "projects",
label: "10",
title: "Projects",
summary: "Things launched, repaired, modernized, or made slightly dangerous across code, content, venues, and data.",
tone: "Builder energy, clean receipts.",
strap: "The workshop floor, but art directed.",
coverline: "Built, fixed, and shipped.",
motif: "Case files, before/after, code and consequence.",
samples: ["From schema to interface", "Deployments with fingerprints", "Systems that survived contact"],
slug: "projects", label: "10", title: "Projects",
summary: { en: "Things launched, repaired, modernized, or made slightly dangerous across code, content, venues, and data.", fr: "Choses lancées, réparées, modernisées ou rendues légèrement dangereuses à travers le code, le contenu, les lieux et les données.", nb: "Ting lansert, reparert, modernisert eller gjort litt farlige på tvers av kode, innhold, arenaer og data." },
tone: { en: "Builder energy, clean receipts.", fr: "Énergie de constructeur, reçus propres.", nb: "Byggerenergi, rene kvitteringer." },
strap: { en: "The workshop floor, but art directed.", fr: "Le sol de l'atelier, mais dirigé artistiquement.", nb: "Verkstedgulvet, men kunstnerisk regissert." },
coverline: { en: "Built, fixed, and shipped.", fr: "Construit, réparé et expédié.", nb: "Bygget, fikset og sendt." },
motif: { en: "Case files, before/after, code and consequence.", fr: "Dossiers, avant/après, code et conséquences.", nb: "Saksmapper, før/etter, kode og konsekvens." },
samples: { en: ["From schema to interface","Deployments with fingerprints","Systems that survived contact"], fr: ["Du schéma à l'interface","Déploiements avec empreintes digitales","Systèmes qui ont survécu au contact"], nb: ["Fra skjema til grensesnitt","Utrullinger med fingeravtrykk","Systemer som overlevde kontakt"] },
},
{
slug: "cv",
label: "11",
title: "CV",
summary: "The formal record, still elegant, still readable, and never trapped in a dusty PDF.",
tone: "Professional, legible, confident.",
strap: "The biography in pressed clothes.",
coverline: "A resume that behaves like publishing.",
motif: "Timeline, experience, proof, education.",
samples: ["Careers across countries", "A record without dead language", "Credentials with pulse"],
slug: "cv", label: "11", title: "CV",
summary: { en: "The formal record, still elegant, still readable, and never trapped in a dusty PDF.", fr: "Le dossier formel, toujours élégant, toujours lisible et jamais enfermé dans un PDF poussiéreux.", nb: "Den formelle opptegnelsen, fortsatt elegant, fortsatt lesbar og aldri fanget i en støvete PDF." },
tone: { en: "Professional, legible, confident.", fr: "Professionnel, lisible, confiant.", nb: "Profesjonell, lesbar, selvsikker." },
strap: { en: "The biography in pressed clothes.", fr: "La biographie en vêtements pressés.", nb: "Biografien i pressede klær." },
coverline: { en: "A resume that behaves like publishing.", fr: "Un CV qui se comporte comme une publication.", nb: "En CV som oppfører seg som publisering." },
motif: { en: "Timeline, experience, proof, education.", fr: "Chronologie, expérience, preuves, éducation.", nb: "Tidslinje, erfaring, bevis, utdanning." },
samples: { en: ["Careers across countries","A record without dead language","Credentials with pulse"], fr: ["Carrières à travers les pays","Un dossier sans langage mort","Certifications avec du souffle"], nb: ["Karrierer på tvers av land","En opptegnelse uten dødt språk","Kvalifikasjoner med puls"] },
},
];
@@ -165,8 +139,7 @@ export const schoolDossiers: SchoolDossier[] = [
program: "MSc, Innovation and Technology Management",
city: "Kongsberg, Norway",
logo: "/images/schools/usn.svg",
teaser:
"A systems laboratory where engineering discipline and lived experience meet over coffee, models, and unfinished questions.",
teaser: { en: "A systems laboratory where engineering discipline and lived experience meet over coffee, models, and unfinished questions.", fr: "Un laboratoire de systèmes où la discipline d'ingénierie et l'expérience vécue se rencontrent autour d'un café, de modèles et de questions inachevées.", nb: "Et systemlaboratorium der ingeniørdisiplin og levd erfaring møtes over kaffe, modeller og uferdige spørsmål." },
},
{
slug: "jagiellonian",
@@ -175,8 +148,7 @@ export const schoolDossiers: SchoolDossier[] = [
program: "European Union Studies",
city: "Krakow, Poland",
logo: "/images/schools/jagiellonian.svg",
teaser:
"History, bureaucracy, Europe, and the old civilizational habit of asking larger questions than the room can comfortably hold.",
teaser: { en: "History, bureaucracy, Europe, and the old civilizational habit of asking larger questions than the room can comfortably hold.", fr: "Histoire, bureaucratie, Europe et l'ancienne habitude civilisationnelle de poser des questions plus grandes que la salle ne peut confortablement contenir.", nb: "Historie, byråkrati, Europa og den gamle sivilisasjonsvanen med å stille større spørsmål enn rommet komfortabelt kan romme." },
},
{
slug: "escp-moore",
@@ -185,8 +157,7 @@ export const schoolDossiers: SchoolDossier[] = [
program: "International MBA and Master of International Business",
city: "Paris and Columbia, South Carolina",
logo: "/images/schools/escp-moore.svg",
teaser:
"A dual orbit of French polish and American execution, where commerce learned to travel with style and consequence.",
teaser: { en: "A dual orbit of French polish and American execution, where commerce learned to travel with style and consequence.", fr: "Une double orbite de raffinement français et d'exécution américaine, où le commerce a appris à voyager avec style et conséquence.", nb: "En dobbel bane av fransk eleganse og amerikansk utførelse, der handel lærte å reise med stil og konsekvens." },
},
{
slug: "european-university",
@@ -195,8 +166,7 @@ export const schoolDossiers: SchoolDossier[] = [
program: "MBA and Master of International Trade",
city: "Brussels, Belgium",
logo: "/images/schools/european-university.svg",
teaser:
"Trade, movement, and continental ambition, written in the language of deals, trains, and multilingual evenings.",
teaser: { en: "Trade, movement, and continental ambition, written in the language of deals, trains, and multilingual evenings.", fr: "Commerce, mouvement et ambition continentale, écrits dans le langage des affaires, des trains et des soirées multilingues.", nb: "Handel, bevegelse og kontinental ambisjon, skrevet i språket til avtaler, tog og flerspråklige kvelder." },
},
{
slug: "villanova",
@@ -205,28 +175,69 @@ export const schoolDossiers: SchoolDossier[] = [
program: "BSc, Business Administration",
city: "Philadelphia, Pennsylvania",
logo: "/images/schools/villanova.svg",
teaser:
"The Main Line chapter, where finance, communication, and character first learned to share the same bandstand.",
teaser: { en: "The Main Line chapter, where finance, communication, and character first learned to share the same bandstand.", fr: "Le chapitre de Main Line, où la finance, la communication et le caractère ont appris à partager la même scène.", nb: "Main Line-kapittelet, der finans, kommunikasjon og karakter først lærte å dele samme scene." },
},
];
export const fieldNotes = [
"Blue Note Logic keeps the machine room full of private AI, cited answers, and document intelligence that behaves like evidence instead of theater.",
"Trivia & Tunes is now a living product story: venue-grade quiz nights, real AI in the game loop, and voice layers warming up for live testing.",
"Do Better Norge keeps the civic file open on family life, immigrant fathers, due process, and the legal weather in contemporary Norway.",
];
export const fieldNotes: Record<LocaleCode, string[]> = {
en: [
"Blue Note Logic keeps the machine room full of private AI, cited answers, and document intelligence that behaves like evidence instead of theater.",
"Trivia & Tunes is now a living product story: venue-grade quiz nights, real AI in the game loop, and voice layers warming up for live testing.",
"Do Better Norge keeps the civic file open on family life, immigrant fathers, due process, and the legal weather in contemporary Norway.",
],
fr: [
"Blue Note Logic garde la salle des machines remplie d'IA privée, de réponses citées et d'intelligence documentaire qui se comporte comme une preuve plutôt qu'un théâtre.",
"Trivia & Tunes est désormais une histoire de produit vivant : soirées quiz dignes de lieux, véritable IA dans la boucle de jeu, et couches vocales prêtes pour des tests en direct.",
"Do Better Norge garde le dossier civique ouvert sur la vie familiale, les pères immigrés, le droit à un procès équitable et la météo juridique dans la Norvège contemporaine."
],
nb: [
"Blue Note Logic holder maskinrommet fullt av privat AI, siterte svar og dokumentintelligens som oppfører seg som bevis i stedet for teater.",
"Trivia & Tunes er nå en levende produktfortelling: quizkvelder på nivå med arenaer, ekte AI i spillsløyfen, og stemmelag som varmer opp for live-testing.",
"Do Better Norge holder det sivile arkivet åpent om familieliv, innvandrerfedre, rettferdig prosess og det juridiske været i dagens Norge."
],
};
export const editorialPromises = [
"Keep the paper light, breathable, and a little dangerous around the edges.",
"Make AI visible as a craft tool, a newsroom instrument, and never a plastic gimmick.",
"Treat the CV, the jazz notebook, the civic archive, and the family pages with equal design seriousness.",
];
export const editorialPromises: Record<LocaleCode, string[]> = {
en: [
"Keep the paper light, breathable, and a little dangerous around the edges.",
"Make AI visible as a craft tool, a newsroom instrument, and never a plastic gimmick.",
"Treat the CV, the jazz notebook, the civic archive, and the family pages with equal design seriousness.",
],
fr: [
"Garder le papier léger, respirable et un peu dangereux sur les bords.",
"Rendre l'IA visible comme un outil artisanal, un instrument de salle de rédaction, et jamais un gadget en plastique.",
"Traiter le CV, le carnet de jazz, l'archive civique et les pages familiales avec le même sérieux de conception."
],
nb: [
"Hold papiret lett, pustende og litt farlig i kantene.",
"Gjøre AI synlig som et håndverksverktøy, et nyhetsromsinstrument, og aldri en plastgimmick.",
"Behandle CV-en, jazznotatboken, det sivile arkivet og familiesidene med samme designalvor."
],
};
export const coverLines = [
"A pataphysical field paper with jazz smoke in the margins and SQL under the floorboards.",
"Five school dossiers, each signed with a clearly counterfeit blessing and a straight face.",
"AI in the machinery, not sprayed on top like fresh conference cologne.",
];
export const coverLines: Record<LocaleCode, string[]> = {
en: [
"A pataphysical field paper with jazz smoke in the margins and SQL under the floorboards.",
"Five school dossiers, each signed with a clearly counterfeit blessing and a straight face.",
"AI in the machinery, not sprayed on top like fresh conference cologne.",
],
fr: [
"Un papier de terrain pataphysique avec de la fumée de jazz dans les marges et du SQL sous les planchers.",
"Cinq dossiers scolaires, chacun signé avec une bénédiction clairement contrefaite et un visage impassible.",
"IA dans la machinerie, pas vaporisée sur le dessus comme une eau de cologne fraîche de conférence."
],
nb: [
"Et pataphysisk feltpapir med jazzrøyk i margen og SQL under gulvbordene.",
"Fem skoledokumenter, hver signert med en åpenbart falsk velsignelse og et rett ansikt.",
"AI i maskineriet, ikke sprayet på toppen som fersk konferanseparfyme."
],
};
export const sectionCardCta: Record<LocaleCode, string> = {
en: "Open section",
fr: "Ouvrir la section",
nb: "Åpne seksjonen",
};
export function getSectionHref(section: LaunchSection) {
return section.slug === "education" ? "/education" : `/${section.slug}`;
+16
View File
@@ -81,6 +81,22 @@ const chromeCopy = getChromeCopy({ activeSlug, issueDate, articleKey });
</script>
</head>
<body>
<div class="locale-bar" role="navigation" aria-label="Language selection">
<div class="locale-bar__inner">
<button type="button" class="locale-bar__btn" data-lang-button="en" aria-pressed="true">
🇬🇧 <span>EN</span>
</button>
<span class="locale-bar__sep" aria-hidden="true">·</span>
<button type="button" class="locale-bar__btn" data-lang-button="fr" aria-pressed="false">
🇫🇷 <span>FR</span>
</button>
<span class="locale-bar__sep" aria-hidden="true">·</span>
<button type="button" class="locale-bar__btn" data-lang-button="nb" aria-pressed="false">
🇳🇴 <span>NO</span>
</button>
</div>
</div>
<header class="site-ribbon">
<div class="container site-ribbon__row">
<LocaleCopy copy={chromeCopy.ribbonIssue} />
+12 -11
View File
@@ -1,6 +1,7 @@
---
import { getSectionHref, launchSections } from "../data/site";
import BaseLayout from "../layouts/BaseLayout.astro";
import LocaleCopy from "../components/LocaleCopy.astro";
export function getStaticPaths() {
return launchSections
@@ -25,24 +26,24 @@ const relatedSections = launchSections.filter(
<BaseLayout
title={`${section.title} | Dave Gilligan`}
description={section.summary}
description={section.summary.en}
>
<main class="section-page">
<section class="container section-page__hero">
<div class="section-page__intro">
<span class="eyebrow">Section {section.label}</span>
<h1>{section.title}</h1>
<p class="section-page__strap">{section.strap}</p>
<p class="section-page__lede">{section.summary}</p>
<p class="section-page__strap"><LocaleCopy copy={section.strap} /></p>
<p class="section-page__lede"><LocaleCopy copy={section.summary} /></p>
</div>
<aside class="panel section-page__cover">
<div class="capsule__kicker">
<span>Cover line</span>
<span>{section.tone}</span>
<span><LocaleCopy copy={section.tone} /></span>
</div>
<h2>{section.coverline}</h2>
<p>{section.motif}</p>
<h2><LocaleCopy copy={section.coverline} /></h2>
<p><LocaleCopy copy={section.motif} /></p>
</aside>
</section>
@@ -53,11 +54,11 @@ const relatedSections = launchSections.filter(
<span>Drafting notes</span>
</div>
<ul class="section-page__samples">
{section.samples.map((sample, index) => (
{section.samples.en.map((_, i) => (
<li>
<span>{String(index + 1).padStart(2, "0")}</span>
<strong>{sample}</strong>
<p>{section.motif}</p>
<span>{String(i + 1).padStart(2, "0")}</span>
<strong><LocaleCopy copy={{ en: section.samples.en[i], fr: section.samples.fr[i], nb: section.samples.nb[i] }} /></strong>
<p><LocaleCopy copy={section.motif} /></p>
</li>
))}
</ul>
@@ -93,7 +94,7 @@ const relatedSections = launchSections.filter(
<a class="section-related-card" href={getSectionHref(entry)}>
<span>{entry.label}</span>
<strong>{entry.title}</strong>
<p>{entry.strap}</p>
<p><LocaleCopy copy={entry.strap} /></p>
</a>
))}
</div>
+5 -4
View File
@@ -1,18 +1,19 @@
---
import { jazzArticle, jazzBody, jazzImages, jazzSources } from "../../data/jazz";
import BaseLayout from "../../layouts/BaseLayout.astro";
import LocaleCopy from "../../components/LocaleCopy.astro";
---
<BaseLayout
title={`${jazzArticle.title} | Dave Gilligan`}
description={jazzArticle.excerpt}
description={jazzArticle.excerpt.en}
>
<main class="jazz-page">
<section class="container jazz-hero">
<div class="jazz-hero__copy">
<span class="eyebrow">Article / Jazz and Music</span>
<h1>{jazzArticle.title}</h1>
<p class="jazz-hero__lede">{jazzArticle.excerpt}</p>
<p class="jazz-hero__lede"><LocaleCopy copy={jazzArticle.excerpt} /></p>
</div>
<aside class="panel jazz-hero__note">
@@ -47,8 +48,8 @@ import BaseLayout from "../../layouts/BaseLayout.astro";
<span>Field report</span>
<span>Kongsberg x Paris</span>
</div>
{jazzBody.map((paragraph) => (
<p>{paragraph}</p>
{jazzBody.en.map((_, i) => (
<p><LocaleCopy copy={{ en: jazzBody.en[i], fr: jazzBody.fr[i], nb: jazzBody.nb[i] }} /></p>
))}
</article>
+6 -5
View File
@@ -7,18 +7,19 @@ import {
norwaySources,
} from "../../data/norway";
import BaseLayout from "../../layouts/BaseLayout.astro";
import LocaleCopy from "../../components/LocaleCopy.astro";
---
<BaseLayout
title={`${norwayArticle.title} | Dave Gilligan`}
description={norwayArticle.excerpt}
description={norwayArticle.excerpt.en}
>
<main class="norway-page">
<section class="container norway-hero norway-hero--article">
<div class="norway-hero__copy">
<span class="eyebrow">Article / Norway desk</span>
<h1>{norwayArticle.title}</h1>
<p class="norway-hero__lede">{norwayArticle.excerpt}</p>
<p class="norway-hero__lede"><LocaleCopy copy={norwayArticle.excerpt} /></p>
</div>
<aside class="panel norway-hero__note">
@@ -197,7 +198,7 @@ import BaseLayout from "../../layouts/BaseLayout.astro";
<article class="panel norway-casebook__item">
<span>{item.date}</span>
<h3>{item.title}</h3>
<p>{item.significance}</p>
<p><LocaleCopy copy={item.significance} /></p>
<a href={item.href} target="_blank" rel="noreferrer">{item.sourceLabel}</a>
</article>
))}
@@ -216,9 +217,9 @@ import BaseLayout from "../../layouts/BaseLayout.astro";
<div class="norway-network__grid">
{norwayOrganizations.map((organization) => (
<a class="norway-network__card" href={organization.href} target="_blank" rel="noreferrer">
<span>{organization.strap}</span>
<span><LocaleCopy copy={organization.strap} /></span>
<strong>{organization.name}</strong>
<p>{organization.summary}</p>
<p><LocaleCopy copy={organization.summary} /></p>
</a>
))}
</div>
@@ -6,18 +6,19 @@ import {
projectsSources,
} from "../../data/projects";
import BaseLayout from "../../layouts/BaseLayout.astro";
import LocaleCopy from "../../components/LocaleCopy.astro";
---
<BaseLayout
title={`${projectsArticle.title} | Dave Gilligan`}
description={projectsArticle.excerpt}
description={projectsArticle.excerpt.en}
>
<main class="projects-page">
<section class="container projects-hero">
<div class="projects-hero__copy">
<span class="eyebrow">Article / Projects desk</span>
<h1>{projectsArticle.title}</h1>
<p class="projects-hero__lede">{projectsArticle.excerpt}</p>
<p class="projects-hero__lede"><LocaleCopy copy={projectsArticle.excerpt} /></p>
</div>
<aside class="panel projects-hero__note">
@@ -51,8 +52,8 @@ import BaseLayout from "../../layouts/BaseLayout.astro";
<span>April dispatch</span>
<span>Music trivia x Blue Note Rhino</span>
</div>
{projectsArticleBody.map((paragraph) => (
<p>{paragraph}</p>
{projectsArticleBody.en.map((_, i) => (
<p><LocaleCopy copy={{ en: projectsArticleBody.en[i], fr: projectsArticleBody.fr[i], nb: projectsArticleBody.nb[i] }} /></p>
))}
</article>
+12 -2
View File
@@ -1,5 +1,4 @@
---
import BusinessIssue from "../components/BusinessIssue.jsx";
import BaseLayout from "../layouts/BaseLayout.astro";
---
@@ -8,6 +7,17 @@ import BaseLayout from "../layouts/BaseLayout.astro";
description="A live business desk fed from the CMS: agentic AI, labour, Queneau, Ionesco, and the anti-buzzword management of imaginary solutions."
>
<main class="business-page">
<BusinessIssue client:load />
<section class="container jazz-layout" style="margin-bottom: 0; padding-bottom: 0;">
<article class="panel jazz-article" style="margin-bottom: var(--space-md, 1.5rem);">
<div class="capsule__kicker">
<span>Latest dispatch</span>
<span>May 2026</span>
</div>
<h2 style="margin-top: 0.5rem;">The Science of Imaginary Revenues</h2>
<p style="font-style: italic; margin-bottom: 0.75rem;">A Field Study of the AI Valuation Ecosystem</p>
<p>From inside the machine that builds the machines: a correspondent's notes on circular capital, NVIDIA's golden shovel, and the dot-com rhyme that the AI industry insists it is not repeating.</p>
<a class="button button--dark" href="/articles/ai-bubble-2026/">Read the field report</a>
</article>
</section>
</main>
</BaseLayout>
+13 -12
View File
@@ -9,6 +9,7 @@ import {
cvTimeline,
} from "../data/cv";
import BaseLayout from "../layouts/BaseLayout.astro";
import LocaleCopy from "../components/LocaleCopy.astro";
const currentMandates = cvMandates.slice(0, 3);
const educationSlice = schoolDossiers.slice(0, 5);
@@ -21,9 +22,9 @@ const educationSlice = schoolDossiers.slice(0, 5);
<main class="cv-page">
<section class="container cv-hero">
<div class="cv-hero__copy">
<span class="eyebrow">{cvHero.eyebrow}</span>
<h1>{cvHero.title}</h1>
<p class="cv-hero__lede">{cvHero.lede}</p>
<span class="eyebrow"><LocaleCopy copy={cvHero.eyebrow} /></span>
<h1><LocaleCopy copy={cvHero.title} /></h1>
<p class="cv-hero__lede"><LocaleCopy copy={cvHero.lede} /></p>
<div class="cv-hero__actions">
<a class="button button--dark" href="#career-record">Read the chronology</a>
@@ -38,13 +39,13 @@ const educationSlice = schoolDossiers.slice(0, 5);
</div>
<p class="cv-poster__eyebrow">Bridgework</p>
<h2>Finance, systems, language, and advocacy on one line.</h2>
<p>{cvHero.note}</p>
<p><LocaleCopy copy={cvHero.note} /></p>
<div class="cv-poster__grid">
{cvHighlights.map((item) => (
{cvHighlights.en.map((_, i) => (
<article>
<span>Signal</span>
<p>{item}</p>
<p><LocaleCopy copy={{ en: cvHighlights.en[i], fr: cvHighlights.fr[i], nb: cvHighlights.nb[i] }} /></p>
</article>
))}
</div>
@@ -68,8 +69,8 @@ const educationSlice = schoolDossiers.slice(0, 5);
<span>{mandate.location}</span>
</div>
<h2>{mandate.org}</h2>
<p class="cv-mandate__summary">{mandate.summary}</p>
<p>{mandate.detail}</p>
<p class="cv-mandate__summary"><LocaleCopy copy={mandate.summary} /></p>
<p><LocaleCopy copy={mandate.detail} /></p>
<a href={mandate.sourceUrl} target="_blank" rel="noreferrer">
Source: {mandate.sourceLabel}
</a>
@@ -100,11 +101,11 @@ const educationSlice = schoolDossiers.slice(0, 5);
<strong>{role.role}</strong>
</div>
<p class="cv-role__summary">{role.summary}</p>
<p class="cv-role__summary"><LocaleCopy copy={role.summary} /></p>
<ul>
{role.bullets.map((bullet) => (
<li>{bullet}</li>
{role.bullets.en.map((_, i) => (
<li><LocaleCopy copy={{ en: role.bullets.en[i], fr: role.bullets.fr[i], nb: role.bullets.nb[i] }} /></li>
))}
</ul>
</div>
@@ -120,7 +121,7 @@ const educationSlice = schoolDossiers.slice(0, 5);
</div>
{cvSkillTracks.map((track) => (
<section>
<h3>{track.label}</h3>
<h3><LocaleCopy copy={track.label} /></h3>
<ul>
{track.items.map((item) => (
<li>{item}</li>
+30 -29
View File
@@ -1,6 +1,7 @@
---
import SectionCard from "../components/SectionCard.astro";
import EditionConsole from "../components/EditionConsole.jsx";
import LocaleCopy from "../components/LocaleCopy.astro";
import {
aiLabCapabilities,
aiLabSources,
@@ -35,11 +36,11 @@ const projects = launchSections.find((section) => section.slug === "projects");
<section class="cover-hero">
<div class="container cover-hero__inner">
<article class="cover-hero__copy">
<span class="eyebrow eyebrow--cover">{hero.kicker}</span>
<span class="eyebrow eyebrow--cover"><LocaleCopy copy={hero.kicker} /></span>
<p class="cover-hero__brand">davegilligan.com</p>
<h1>{hero.title}</h1>
<p class="cover-hero__lede">{hero.lede}</p>
<p class="cover-hero__sublede">{hero.sublede}</p>
<h1><LocaleCopy copy={hero.title} /></h1>
<p class="cover-hero__lede"><LocaleCopy copy={hero.lede} /></p>
<p class="cover-hero__sublede"><LocaleCopy copy={hero.sublede} /></p>
<div class="cover-hero__actions">
<a class="button button--dark" href="#venture-desk">Enter the front page</a>
@@ -49,13 +50,13 @@ const projects = launchSections.find((section) => section.slug === "projects");
<div class="cover-hero__billboard">
<div>
<span>Writing desk</span>
<strong>{writing?.coverline}</strong>
<p>{writing?.strap}</p>
{writing && <strong><LocaleCopy copy={writing.coverline} /></strong>}
{writing && <p><LocaleCopy copy={writing.strap} /></p>}
</div>
<div>
<span>Jazz desk</span>
<strong>{jazzMusic?.coverline}</strong>
<p>{jazzMusic?.strap}</p>
{jazzMusic && <strong><LocaleCopy copy={jazzMusic.coverline} /></strong>}
{jazzMusic && <p><LocaleCopy copy={jazzMusic.strap} /></p>}
</div>
</div>
</article>
@@ -65,7 +66,7 @@ const projects = launchSections.find((section) => section.slug === "projects");
{collageImages.map((image, index) => (
<figure class={`cover-collage__frame cover-collage__frame--${index + 1}`}>
<img src={image.src} alt={image.alt} loading="lazy" />
<figcaption>{image.caption}</figcaption>
<figcaption><LocaleCopy copy={image.caption} /></figcaption>
</figure>
))}
</div>
@@ -81,14 +82,14 @@ const projects = launchSections.find((section) => section.slug === "projects");
{routeStops.map((stop) => (
<span>
<strong>{stop.place}</strong>
<em>{stop.note}</em>
<em><LocaleCopy copy={stop.note} /></em>
</span>
))}
</section>
<section class="container frontline">
{fieldNotes.map((note) => (
<span>{note}</span>
{fieldNotes.en.map((_, i) => (
<span><LocaleCopy copy={{ en: fieldNotes.en[i], fr: fieldNotes.fr[i], nb: fieldNotes.nb[i] }} /></span>
))}
</section>
@@ -112,11 +113,11 @@ const projects = launchSections.find((section) => section.slug === "projects");
</div>
<p class="venture-story__label">{venture.label}</p>
<h2>{venture.name}</h2>
<p class="venture-story__summary">{venture.summary}</p>
<p class="venture-story__detail">{venture.detail}</p>
<p class="venture-story__summary"><LocaleCopy copy={venture.summary} /></p>
<p class="venture-story__detail"><LocaleCopy copy={venture.detail} /></p>
<ul>
{venture.highlights.map((item) => (
<li>{item}</li>
{venture.highlights.en.map((_, i) => (
<li><LocaleCopy copy={{ en: venture.highlights.en[i], fr: venture.highlights.fr[i], nb: venture.highlights.nb[i] }} /></li>
))}
</ul>
<p class="venture-story__source">
@@ -136,9 +137,9 @@ const projects = launchSections.find((section) => section.slug === "projects");
rel={signal.external ? "noreferrer" : undefined}
>
<img src={signal.imageSrc} alt={signal.imageAlt} loading="lazy" />
<span>{signal.strap}</span>
<span><LocaleCopy copy={signal.strap} /></span>
<strong>{signal.name}</strong>
<p>{signal.summary}</p>
<p><LocaleCopy copy={signal.summary} /></p>
<small>{signal.imageNote}</small>
</a>
))}
@@ -167,8 +168,8 @@ const projects = launchSections.find((section) => section.slug === "projects");
<div class="ai-observatory__list">
{aiLabCapabilities.map((capability) => (
<article>
<span>{capability.title}</span>
<p>{capability.summary}</p>
<span><LocaleCopy copy={capability.title} /></span>
<p><LocaleCopy copy={capability.summary} /></p>
</article>
))}
</div>
@@ -179,7 +180,7 @@ const projects = launchSections.find((section) => section.slug === "projects");
{[languages, norway, projects].filter(Boolean).map((section) => (
<a href={getSectionHref(section!)}>
<span>{section!.title}</span>
<strong>{section!.strap}</strong>
<strong><LocaleCopy copy={section!.strap} /></strong>
</a>
))}
</div>
@@ -215,11 +216,11 @@ const projects = launchSections.find((section) => section.slug === "projects");
<span>Education dossier</span>
<span>{education?.label}</span>
</div>
<h2>{education?.coverline}</h2>
<p>{education?.summary}</p>
{education && <h2><LocaleCopy copy={education.coverline} /></h2>}
{education && <p><LocaleCopy copy={education.summary} /></p>}
<ul>
{education?.samples.map((sample) => (
<li>{sample}</li>
{education && education.samples.en.map((_, i) => (
<li><LocaleCopy copy={{ en: education.samples.en[i], fr: education.samples.fr[i], nb: education.samples.nb[i] }} /></li>
))}
</ul>
<a class="button button--dark" href="/education">Read the school issue</a>
@@ -236,7 +237,7 @@ const projects = launchSections.find((section) => section.slug === "projects");
</div>
<h3>{school.institution}</h3>
<p><strong>{school.program}</strong></p>
<p>{school.teaser}</p>
<p><LocaleCopy copy={school.teaser} /></p>
</div>
</article>
))}
@@ -253,10 +254,10 @@ const projects = launchSections.find((section) => section.slug === "projects");
</div>
<div class="manifesto-grid">
{editorialPromises.map((item, index) => (
{editorialPromises.en.map((_, i) => (
<article class="manifesto-card">
<span>Promise {index + 1}</span>
<p>{item}</p>
<span>Promise {i + 1}</span>
<p><LocaleCopy copy={{ en: editorialPromises.en[i], fr: editorialPromises.fr[i], nb: editorialPromises.nb[i] }} /></p>
</article>
))}
</div>
+12 -11
View File
@@ -1,18 +1,19 @@
---
import { jazzArticle, jazzBody, jazzFreeNotes, jazzHero, jazzImages, jazzPicks, jazzSources, jazzVenues } from "../data/jazz";
import BaseLayout from "../layouts/BaseLayout.astro";
import LocaleCopy from "../components/LocaleCopy.astro";
---
<BaseLayout
title="Jazz and Music | Dave Gilligan"
description={jazzArticle.excerpt}
description={jazzArticle.excerpt.en}
>
<main class="jazz-page">
<section class="container jazz-hero">
<div class="jazz-hero__copy">
<span class="eyebrow">{jazzHero.eyebrow}</span>
<h1>{jazzHero.title}</h1>
<p class="jazz-hero__lede">{jazzHero.lede}</p>
<span class="eyebrow"><LocaleCopy copy={jazzHero.eyebrow} /></span>
<h1><LocaleCopy copy={jazzHero.title} /></h1>
<p class="jazz-hero__lede"><LocaleCopy copy={jazzHero.lede} /></p>
</div>
<aside class="panel jazz-hero__note">
@@ -21,7 +22,7 @@ import BaseLayout from "../layouts/BaseLayout.astro";
<span>{jazzArticle.publishedAt.slice(0, 10)}</span>
</div>
<h2>{jazzArticle.title}</h2>
<p>{jazzHero.note}</p>
<p><LocaleCopy copy={jazzHero.note} /></p>
<div class="jazz-hero__actions">
<a class="button button--dark" href="/articles/kongsberg-jazz-2026">Read the article</a>
</div>
@@ -47,8 +48,8 @@ import BaseLayout from "../layouts/BaseLayout.astro";
<span>Editorial</span>
<span>Kongsberg x Paris</span>
</div>
{jazzBody.map((paragraph) => (
<p>{paragraph}</p>
{jazzBody.en.map((_, i) => (
<p><LocaleCopy copy={{ en: jazzBody.en[i], fr: jazzBody.fr[i], nb: jazzBody.nb[i] }} /></p>
))}
</article>
@@ -63,7 +64,7 @@ import BaseLayout from "../layouts/BaseLayout.astro";
<li>
<span>{pick.day}</span>
<strong>{pick.artist}</strong>
<p>{pick.detail}</p>
<p><LocaleCopy copy={pick.detail} /></p>
<a href={pick.href} target="_blank" rel="noreferrer">Festival programme</a>
</li>
))}
@@ -78,8 +79,8 @@ import BaseLayout from "../layouts/BaseLayout.astro";
<ul class="jazz-list">
{jazzFreeNotes.map((note) => (
<li>
<strong>{note.title}</strong>
<p>{note.summary}</p>
<strong><LocaleCopy copy={note.title} /></strong>
<p><LocaleCopy copy={note.summary} /></p>
<a href={note.href} target="_blank" rel="noreferrer">Open details</a>
</li>
))}
@@ -101,7 +102,7 @@ import BaseLayout from "../layouts/BaseLayout.astro";
{jazzVenues.map((venue) => (
<article class="jazz-venue-card">
<h3>{venue.name}</h3>
<p>{venue.summary}</p>
<p><LocaleCopy copy={venue.summary} /></p>
<a href={venue.href} target="_blank" rel="noreferrer">Open venue</a>
</article>
))}
+10 -9
View File
@@ -8,6 +8,7 @@ import {
norwaySources,
} from "../data/norway";
import BaseLayout from "../layouts/BaseLayout.astro";
import LocaleCopy from "../components/LocaleCopy.astro";
---
<BaseLayout
@@ -17,9 +18,9 @@ import BaseLayout from "../layouts/BaseLayout.astro";
<main class="norway-page">
<section class="container norway-hero">
<div class="norway-hero__copy">
<span class="eyebrow">{norwayFeature.eyebrow}</span>
<h1>{norwayFeature.title}</h1>
<p class="norway-hero__lede">{norwayFeature.lede}</p>
<span class="eyebrow"><LocaleCopy copy={norwayFeature.eyebrow} /></span>
<h1><LocaleCopy copy={norwayFeature.title} /></h1>
<p class="norway-hero__lede"><LocaleCopy copy={norwayFeature.lede} /></p>
<div class="norway-hero__actions">
<a class="button button--dark" href="/articles/norway-april-2026">Read the April article</a>
@@ -33,8 +34,8 @@ import BaseLayout from "../layouts/BaseLayout.astro";
<span>{norwayArticle.publishedAt.slice(0, 10)}</span>
</div>
<h2>{norwayArticle.title}</h2>
<p>{norwayArticle.excerpt}</p>
<p>{norwayFeature.note}</p>
<p><LocaleCopy copy={norwayArticle.excerpt} /></p>
<p><LocaleCopy copy={norwayFeature.note} /></p>
</aside>
</section>
@@ -91,8 +92,8 @@ import BaseLayout from "../layouts/BaseLayout.astro";
<article class="panel norway-casebook__item">
<span>{item.date}</span>
<h3>{item.title}</h3>
<p>{item.summary}</p>
<p>{item.significance}</p>
<p><LocaleCopy copy={item.summary} /></p>
<p><LocaleCopy copy={item.significance} /></p>
<a href={item.href} target="_blank" rel="noreferrer">{item.sourceLabel}</a>
</article>
))}
@@ -111,9 +112,9 @@ import BaseLayout from "../layouts/BaseLayout.astro";
<div class="norway-network__grid">
{norwayOrganizations.map((organization) => (
<a class="norway-network__card" href={organization.href} target="_blank" rel="noreferrer">
<span>{organization.strap}</span>
<span><LocaleCopy copy={organization.strap} /></span>
<strong>{organization.name}</strong>
<p>{organization.summary}</p>
<p><LocaleCopy copy={organization.summary} /></p>
</a>
))}
</div>
+13 -12
View File
@@ -9,6 +9,7 @@ import {
projectsSources,
} from "../data/projects";
import BaseLayout from "../layouts/BaseLayout.astro";
import LocaleCopy from "../components/LocaleCopy.astro";
---
<BaseLayout
@@ -18,9 +19,9 @@ import BaseLayout from "../layouts/BaseLayout.astro";
<main class="projects-page">
<section class="container projects-hero">
<div class="projects-hero__copy">
<span class="eyebrow">{projectsFeature.eyebrow}</span>
<h1>{projectsFeature.title}</h1>
<p class="projects-hero__lede">{projectsFeature.lede}</p>
<span class="eyebrow"><LocaleCopy copy={projectsFeature.eyebrow} /></span>
<h1><LocaleCopy copy={projectsFeature.title} /></h1>
<p class="projects-hero__lede"><LocaleCopy copy={projectsFeature.lede} /></p>
<div class="projects-hero__actions">
<a class="button button--dark" href={`/articles/${projectsArticle.slug}`}>Read the April article</a>
@@ -36,8 +37,8 @@ import BaseLayout from "../layouts/BaseLayout.astro";
<span>{projectsArticle.publishedAt.slice(0, 10)}</span>
</div>
<h2>{projectsArticle.title}</h2>
<p>{projectsArticle.excerpt}</p>
<p>{projectsFeature.note}</p>
<p><LocaleCopy copy={projectsArticle.excerpt} /></p>
<p><LocaleCopy copy={projectsFeature.note} /></p>
</aside>
</section>
@@ -60,16 +61,16 @@ import BaseLayout from "../layouts/BaseLayout.astro";
<span>Project notebook</span>
<span>Trivia x AI x room design</span>
</div>
{projectsDeskBody.map((paragraph) => (
<p>{paragraph}</p>
{projectsDeskBody.en.map((_, i) => (
<p><LocaleCopy copy={{ en: projectsDeskBody.en[i], fr: projectsDeskBody.fr[i], nb: projectsDeskBody.nb[i] }} /></p>
))}
</article>
<aside class="projects-aside">
{projectsSignals.map((signal) => (
<article class="panel projects-artifact">
<span>{signal.title}</span>
<p>{signal.summary}</p>
<span><LocaleCopy copy={signal.title} /></span>
<p><LocaleCopy copy={signal.summary} /></p>
</article>
))}
</aside>
@@ -86,8 +87,8 @@ import BaseLayout from "../layouts/BaseLayout.astro";
<div class="projects-capabilities__grid">
{projectsCapabilities.map((capability) => (
<article class="projects-card">
<h3>{capability.title}</h3>
<p>{capability.body}</p>
<h3><LocaleCopy copy={capability.title} /></h3>
<p><LocaleCopy copy={capability.body} /></p>
</article>
))}
</div>
@@ -100,7 +101,7 @@ import BaseLayout from "../layouts/BaseLayout.astro";
<span>Direct article</span>
</div>
<h2>{projectsArticle.title}</h2>
<p>{projectsArticle.excerpt}</p>
<p><LocaleCopy copy={projectsArticle.excerpt} /></p>
<a class="button button--dark" href={`/articles/${projectsArticle.slug}`}>Open the article</a>
</article>
</section>
+12
View File
@@ -8,6 +8,18 @@ import BaseLayout from "../layouts/BaseLayout.astro";
description="A live writing desk fed from the PHP archive: Boris Vian, Vernon Sullivan weather, jazz syntax, and pataphysical essays."
>
<main class="writing-page">
<section class="container writing-feature" style="margin-bottom: 0; padding-bottom: 0;">
<article class="panel jazz-article" style="margin-bottom: var(--space-md, 1.5rem);">
<div class="capsule__kicker">
<span>Latest piece</span>
<span>May 2026</span>
</div>
<h2 style="margin-top: 0.5rem;">L'Homme Impossible</h2>
<p style="font-style: italic; margin-bottom: 0.75rem;">A Guide to Boris Vian and the Collège de 'Pataphysique</p>
<p>He was a trained engineer who played trumpet in a cellar with Miles Davis, wrote ten novels before his fortieth birthday, and became a Transcendent Satrap of an institution dedicated to deliberate uselessness.</p>
<a class="button button--dark" href="/articles/boris-vian-2026/">Read the introduction</a>
</article>
</section>
<WritingIssue client:load />
</main>
</BaseLayout>
+760 -100
View File
@@ -1,22 +1,130 @@
:root {
--paper: #f6f0e1;
--paper-deep: #ead9b8;
--ink: #181511;
--ink-soft: #5e564b;
--ink-faint: #867a6b;
--line: rgba(24, 21, 17, 0.16);
--line-strong: rgba(24, 21, 17, 0.32);
--brass: #936c20;
--burgundy: #73312a;
--teal: #1f5d5f;
--card: rgba(255, 252, 245, 0.82);
--card-strong: rgba(255, 250, 241, 0.94);
--shadow: 0 18px 55px rgba(50, 36, 20, 0.08);
--radius-lg: 28px;
--radius-sm: 999px;
--font-display: "Instrument Serif", "Times New Roman", serif;
--font-body: "Newsreader", Georgia, serif;
--font-mono: "IBM Plex Mono", "Courier New", monospace;
/* ---------- Surfaces (paper) ---------- */
--paper-white: #fbf8f0;
--paper: #f6f0e1;
--paper-warm: #f2ead8;
--paper-deep: #ead9b8;
--paper-shadow: #d9c79e;
/* ---------- Ink (foreground) ---------- */
--ink: #120f0a;
--ink-strong: #1a160f;
--ink-soft: #5e564b;
--ink-faint: #867a6b;
--ink-whisper: #b1a795;
/* ---------- Lines & rules ---------- */
--line: rgba(18, 15, 10, 0.16);
--line-strong: rgba(18, 15, 10, 0.32);
--line-heavy: rgba(18, 15, 10, 0.55);
--rule-double: 2px;
/* ---------- Brand inks ---------- */
--burgundy: #8f2218;
--burgundy-deep: #5a1610;
--burgundy-legacy: #73312a;
--brass: #a07614;
--brass-deep: #6a4a08;
--mustard: #c9941a;
--mustard-pale: #e7c777;
--ink-blue: #1b3a5f;
--ink-blue-deep: #0f2440;
--teal: #1f5d5f;
--teal-deep: #143e40;
/* ---------- Section accents ---------- */
--sec-business: #1f5d5f;
--sec-education: #a07614;
--sec-family: #8b4a41;
--sec-fun: #8a5a17;
--sec-writing: #8f2218;
--sec-jazz: #7b3f74;
--sec-languages: #2d5f92;
--sec-ai-lab: #1b3a5f;
--sec-norway: #46613e;
--sec-projects: #6b4e85;
--sec-cv: #5c564c;
/* ---------- Cards & surfaces ---------- */
--card: rgba(255, 252, 245, 0.82);
--card-strong: rgba(255, 250, 241, 0.94);
--card-deep: rgba(247, 239, 222, 0.88);
--card-press: #fffaef;
/* ---------- Radius ---------- */
--radius-xs: 0.5rem;
--radius-sm: 999px;
--radius-md: 1rem;
--radius-lg: 1.4rem;
--radius-xl: 1.8rem;
--radius-2xl: 2rem;
/* ---------- Shadows (warm, never grey) ---------- */
--shadow-paper: 0 18px 55px rgba(50, 36, 20, 0.08);
--shadow-card: 0 10px 35px rgba(40, 28, 12, 0.05);
--shadow-card-hover: 0 18px 35px rgba(40, 28, 12, 0.08);
--shadow-photo: 0 32px 80px rgba(31, 20, 9, 0.16);
--shadow-stamp: inset 0 0 0 1px rgba(143, 34, 24, 0.35),
0 1px 0 rgba(143, 34, 24, 0.18);
/* ---------- Spacing ---------- */
--sp-0: 0;
--sp-1: 0.25rem;
--sp-2: 0.5rem;
--sp-3: 0.75rem;
--sp-4: 1rem;
--sp-5: 1.25rem;
--sp-6: 1.5rem;
--sp-7: 2rem;
--sp-8: 2.5rem;
--sp-9: 3rem;
--sp-10: 4rem;
--gutter: 1rem;
--gutter-tight: 0.75rem;
--column-max: 1180px;
/* ---------- Type scale ---------- */
--t-display-xl: clamp(4.15rem, 11vw, 8.6rem);
--t-display-l: clamp(3.2rem, 8vw, 6.6rem);
--t-display-m: clamp(2.5rem, 6vw, 4.8rem);
--t-display-s: clamp(2rem, 4vw, 3rem);
--t-display-xs: clamp(1.55rem, 3vw, 2rem);
--t-body-xl: 1.3rem;
--t-body-l: 1.14rem;
--t-body: 1rem;
--t-body-s: 0.92rem;
--t-caption: 0.78rem;
--t-micro: 0.68rem;
/* ---------- Tracking ---------- */
--track-display: -0.04em;
--track-display-tight: -0.06em;
--track-body: 0;
--track-mono: 0.14em;
--track-mono-wide: 0.22em;
/* ---------- Line heights ---------- */
--lh-display: 0.9;
--lh-display-tight: 0.86;
--lh-headline: 1.0;
--lh-body: 1.65;
--lh-body-tight: 1.45;
/* ---------- Motion ---------- */
--ease-out: cubic-bezier(0.2, 0.7, 0.2, 1);
--ease-in-out: cubic-bezier(0.5, 0, 0.3, 1);
--dur-fast: 120ms;
--dur-base: 180ms;
--dur-slow: 280ms;
/* ---------- Fonts ---------- */
--font-display: "Instrument Serif", "Times New Roman", serif;
--font-body: "Newsreader", Georgia, "Iowan Old Style", serif;
--font-mono: "Special Elite", "Courier Prime", "IBM Plex Mono", "Courier New", monospace;
--font-mono-tech: "IBM Plex Mono", "Courier New", monospace;
color-scheme: light;
}
@@ -34,10 +142,10 @@ body {
color: var(--ink);
font-family: var(--font-body);
background:
radial-gradient(circle at top left, rgba(197, 165, 105, 0.2), transparent 28%),
radial-gradient(circle at 85% 20%, rgba(31, 93, 95, 0.18), transparent 26%),
radial-gradient(circle at 50% 100%, rgba(115, 49, 42, 0.14), transparent 32%),
linear-gradient(180deg, #fbf8f0 0%, #f6f0e1 44%, #f2ead8 100%);
radial-gradient(circle at top left, rgba(160, 118, 20, 0.18), transparent 28%),
radial-gradient(circle at 85% 20%, rgba(31, 93, 95, 0.16), transparent 26%),
radial-gradient(circle at 50% 100%, rgba(143, 34, 24, 0.12), transparent 32%),
linear-gradient(180deg, var(--paper-white) 0%, var(--paper) 44%, var(--paper-warm) 100%);
}
body::before {
@@ -81,7 +189,7 @@ img {
height: 34rem;
top: -10rem;
left: -9rem;
background: radial-gradient(circle, rgba(147, 108, 32, 0.22), transparent 65%);
background: radial-gradient(circle, rgba(160, 118, 20, 0.22), transparent 65%);
animation: drift 16s ease-in-out infinite;
}
@@ -194,6 +302,7 @@ img {
font-size: 0.74rem;
letter-spacing: 0.2em;
color: var(--burgundy);
font-style: italic;
}
.site-nav {
@@ -277,60 +386,17 @@ img {
height: 1.05rem;
}
.section-mark--business {
color: #1f5d5f;
background: rgba(31, 93, 95, 0.12);
}
.section-mark--education {
color: #936c20;
background: rgba(147, 108, 32, 0.12);
}
.section-mark--family {
color: #8b4a41;
background: rgba(139, 74, 65, 0.12);
}
.section-mark--fun-postings {
color: #8a5a17;
background: rgba(138, 90, 23, 0.12);
}
.section-mark--writing {
color: #73312a;
background: rgba(115, 49, 42, 0.12);
}
.section-mark--jazz-music {
color: #7b3f74;
background: rgba(123, 63, 116, 0.12);
}
.section-mark--languages {
color: #2d5f92;
background: rgba(45, 95, 146, 0.12);
}
.section-mark--ai-lab {
color: #24506f;
background: rgba(36, 80, 111, 0.12);
}
.section-mark--norway {
color: #46613e;
background: rgba(70, 97, 62, 0.12);
}
.section-mark--projects {
color: #6b4e85;
background: rgba(107, 78, 133, 0.12);
}
.section-mark--cv {
color: #5c564c;
background: rgba(92, 86, 76, 0.12);
}
.section-mark--business { color: var(--sec-business); background: rgba(31, 93, 95, 0.12); }
.section-mark--education { color: var(--sec-education); background: rgba(160, 118, 20, 0.12); }
.section-mark--family { color: var(--sec-family); background: rgba(139, 74, 65, 0.12); }
.section-mark--fun-postings { color: var(--sec-fun); background: rgba(138, 90, 23, 0.12); }
.section-mark--writing { color: var(--sec-writing); background: rgba(143, 34, 24, 0.12); }
.section-mark--jazz-music { color: var(--sec-jazz); background: rgba(123, 63, 116, 0.12); }
.section-mark--languages { color: var(--sec-languages); background: rgba(45, 95, 146, 0.12); }
.section-mark--ai-lab { color: var(--sec-ai-lab); background: rgba(27, 58, 95, 0.12); }
.section-mark--norway { color: var(--sec-norway); background: rgba(70, 97, 62, 0.12); }
.section-mark--projects { color: var(--sec-projects); background: rgba(107, 78, 133, 0.12); }
.section-mark--cv { color: var(--sec-cv); background: rgba(92, 86, 76, 0.12); }
.frontline {
display: grid;
@@ -365,7 +431,7 @@ img {
border: 1px solid var(--line);
border-radius: var(--radius-lg);
background: linear-gradient(180deg, var(--card-strong), var(--card));
box-shadow: var(--shadow);
box-shadow: var(--shadow-paper);
backdrop-filter: blur(10px);
}
@@ -476,10 +542,13 @@ img {
letter-spacing: 0.14em;
text-transform: uppercase;
text-decoration: none;
color: var(--ink);
background: rgba(255, 255, 255, 0.55);
transition:
transform 180ms ease,
background-color 180ms ease,
border-color 180ms ease;
transform var(--dur-base) var(--ease-out),
background-color var(--dur-base) var(--ease-out),
border-color var(--dur-base) var(--ease-out);
cursor: pointer;
}
.button:hover {
@@ -488,13 +557,22 @@ img {
.button--dark {
background: var(--ink);
color: #fff7eb;
color: var(--paper-white);
border-color: var(--ink);
}
.button--soft {
background: rgba(255, 255, 255, 0.55);
}
.button--stamp {
background: transparent;
color: var(--burgundy);
border: 1.5px solid var(--burgundy);
box-shadow: var(--shadow-stamp);
font-style: italic;
}
.hero-side {
display: grid;
gap: 1rem;
@@ -697,20 +775,23 @@ img {
.section-card {
height: 100%;
padding: 1.15rem;
border-radius: 1.4rem;
border-radius: var(--radius-lg);
border: 1px solid var(--line);
background: rgba(255, 253, 249, 0.78);
box-shadow: 0 10px 35px rgba(40, 28, 12, 0.05);
box-shadow: var(--shadow-card);
transition:
transform 180ms ease,
border-color 180ms ease,
box-shadow 180ms ease;
transform var(--dur-base) var(--ease-out),
border-color var(--dur-base) var(--ease-out),
box-shadow var(--dur-base) var(--ease-out);
text-decoration: none;
color: inherit;
display: block;
}
.section-card:hover {
transform: translateY(-4px);
border-color: rgba(115, 49, 42, 0.35);
box-shadow: 0 18px 35px rgba(40, 28, 12, 0.08);
border-color: rgba(143, 34, 24, 0.35);
box-shadow: var(--shadow-card-hover);
}
.section-card__header {
@@ -903,9 +984,9 @@ img {
.page-shell--cover {
background:
radial-gradient(circle at 10% 10%, rgba(147, 108, 32, 0.16), transparent 24%),
radial-gradient(circle at 85% 18%, rgba(31, 93, 95, 0.2), transparent 22%),
linear-gradient(180deg, rgba(255, 251, 242, 0.94), rgba(246, 240, 225, 0.82));
radial-gradient(circle at 10% 10%, rgba(160, 118, 20, 0.18), transparent 24%),
radial-gradient(circle at 85% 18%, rgba(31, 93, 95, 0.16), transparent 22%),
linear-gradient(180deg, rgba(251, 248, 240, 0.98), rgba(242, 233, 215, 0.88));
}
.cover-banner {
@@ -1009,8 +1090,8 @@ img {
inset: 0 0 auto;
height: min(42rem, 92vh);
background:
linear-gradient(125deg, rgba(255, 252, 246, 0.92), rgba(237, 228, 208, 0.76)),
radial-gradient(circle at top right, rgba(31, 93, 95, 0.18), transparent 30%);
linear-gradient(125deg, rgba(255, 252, 246, 0.92), rgba(234, 224, 200, 0.76)),
radial-gradient(circle at top right, rgba(31, 93, 95, 0.16), transparent 30%);
border-bottom: 1px solid var(--line);
}
@@ -1124,8 +1205,8 @@ img {
overflow: hidden;
background:
linear-gradient(160deg, rgba(12, 15, 22, 0.88), rgba(39, 24, 19, 0.72)),
radial-gradient(circle at top left, rgba(147, 108, 32, 0.26), transparent 28%);
box-shadow: 0 32px 80px rgba(31, 20, 9, 0.16);
radial-gradient(circle at top left, rgba(160, 118, 20, 0.26), transparent 28%);
box-shadow: var(--shadow-photo);
}
.cover-collage::after {
@@ -4831,13 +4912,13 @@ img {
}
}
.locale-copy__text {
.locale-copy .locale-copy__text {
display: none;
}
html[data-ui-lang="en"] .locale-copy__text[data-locale-option="en"],
html[data-ui-lang="fr"] .locale-copy__text[data-locale-option="fr"],
html[data-ui-lang="nb"] .locale-copy__text[data-locale-option="nb"] {
html[data-ui-lang="en"] .locale-copy .locale-copy__text[data-locale-option="en"],
html[data-ui-lang="fr"] .locale-copy .locale-copy__text[data-locale-option="fr"],
html[data-ui-lang="nb"] .locale-copy .locale-copy__text[data-locale-option="nb"] {
display: inline;
}
@@ -5076,6 +5157,504 @@ html[data-ui-lang="nb"] .locale-copy__text[data-locale-option="nb"] {
margin-top: 0.25rem;
}
/* =========================================================================
Design system components (from colors_and_type.css + kit.css)
========================================================================= */
/* Kicker / eyebrow reset (already defined above; keeping for reference) */
.kicker {
display: inline-flex;
align-items: center;
gap: 0.65rem;
font-family: var(--font-mono);
font-size: var(--t-micro);
letter-spacing: var(--track-mono);
text-transform: uppercase;
color: var(--teal);
}
.kicker::before {
content: "";
width: 2.4rem;
height: 1px;
background: currentColor;
}
/* Periodical double rule */
.rule-double {
height: 6px;
border-top: 1px solid var(--ink);
border-bottom: 1px solid var(--ink);
}
/* Stamp (pataphysique easter egg) */
.stamp {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.3rem 0.7rem;
border: 1.5px solid var(--burgundy);
border-radius: 0.2rem;
color: var(--burgundy);
font-family: var(--font-mono);
font-size: var(--t-micro);
letter-spacing: var(--track-mono);
text-transform: uppercase;
background: rgba(255, 252, 245, 0.4);
transform: rotate(-3deg);
box-shadow: var(--shadow-stamp);
opacity: 0.85;
font-style: italic;
}
.stamp--blue { color: var(--ink-blue); border-color: var(--ink-blue); box-shadow: inset 0 0 0 1px rgba(27, 58, 95, 0.35); }
.stamp--brass { color: var(--brass-deep); border-color: var(--brass-deep); }
/* Registration marks (printer's crosshair) */
.reg-mark {
display: inline-block;
width: 1.1rem;
height: 1.1rem;
background:
linear-gradient(currentColor, currentColor) center / 100% 1px no-repeat,
linear-gradient(currentColor, currentColor) center / 1px 100% no-repeat;
color: var(--ink-faint);
position: relative;
}
.reg-mark::after {
content: "";
position: absolute;
inset: 0;
border: 1px solid currentColor;
border-radius: 50%;
}
/* Corner registration marks */
.page-shell .reg-corner {
position: absolute;
width: 16px;
height: 16px;
color: rgba(18, 15, 10, 0.28);
pointer-events: none;
background:
linear-gradient(currentColor, currentColor) center / 100% 1px no-repeat,
linear-gradient(currentColor, currentColor) center / 1px 100% no-repeat;
}
.page-shell .reg-corner::after {
content: "";
position: absolute;
inset: 0;
border: 1px solid currentColor;
border-radius: 50%;
}
.page-shell .reg-corner.tl { top: 12px; left: 12px; }
.page-shell .reg-corner.tr { top: 12px; right: 12px; }
.page-shell .reg-corner.bl { bottom: 12px; left: 12px; }
.page-shell .reg-corner.br { bottom: 12px; right: 12px; }
/* Drop cap (1950s lit style) */
.dropcap::first-letter {
font-family: var(--font-display);
float: left;
font-size: 4.6em;
line-height: 0.82;
padding: 0.04em 0.08em 0 0;
color: var(--burgundy);
}
/* ---- Code blocks ---- */
code, pre, .mono {
font-family: var(--font-mono-tech);
font-size: 0.9em;
}
/* ---- Generic section/article page ---- */
.section-page, .article-page {
padding-bottom: 3rem;
background:
radial-gradient(circle at 84% 16%, rgba(31, 93, 95, 0.14), transparent 18%),
radial-gradient(circle at 14% 12%, rgba(143, 34, 24, 0.12), transparent 18%),
linear-gradient(180deg, rgba(251, 248, 240, 0.98), rgba(244, 235, 219, 0.92));
}
/* ---- Business page (teal accent, React-island page) ---- */
.business-page {
padding-bottom: 3rem;
background:
radial-gradient(circle at 82% 14%, rgba(31, 93, 95, 0.18), transparent 22%),
radial-gradient(circle at 12% 10%, rgba(160, 118, 20, 0.12), transparent 20%),
linear-gradient(180deg, rgba(251, 248, 240, 0.98), rgba(241, 236, 222, 0.94));
}
/* ---- Writing page (burgundy accent) ---- */
.writing-page {
padding-bottom: 3rem;
background:
radial-gradient(circle at 82% 14%, rgba(143, 34, 24, 0.14), transparent 20%),
radial-gradient(circle at 12% 10%, rgba(160, 118, 20, 0.12), transparent 18%),
linear-gradient(180deg, rgba(251, 248, 240, 0.98), rgba(244, 235, 219, 0.92));
}
/* ---- Projects page (purple accent) ---- */
.projects-page {
padding-bottom: 3rem;
background:
radial-gradient(circle at 84% 14%, rgba(107, 78, 133, 0.14), transparent 20%),
radial-gradient(circle at 12% 12%, rgba(160, 118, 20, 0.10), transparent 18%),
linear-gradient(180deg, rgba(251, 248, 240, 0.98), rgba(244, 236, 222, 0.92));
}
/* ---- Family Lab page ---- */
.family-lab-page {
padding-bottom: 3rem;
background:
radial-gradient(circle at 12% 12%, rgba(139, 74, 65, 0.14), transparent 20%),
radial-gradient(circle at 88% 14%, rgba(160, 118, 20, 0.12), transparent 22%),
linear-gradient(180deg, rgba(251, 248, 240, 0.98), rgba(244, 235, 219, 0.94));
}
/* =========================================================================
Issue / section page layouts
========================================================================= */
.issue-page { padding-bottom: 3rem; }
.issue-hero {
display: grid;
grid-template-columns: minmax(0, 1.1fr) minmax(300px, 0.9fr);
gap: 1rem;
padding: 2rem 0 1rem;
}
.issue-hero__copy h1 { margin: 0; max-width: 12ch; font-family: var(--font-display); font-size: clamp(3rem, 8vw, 6rem); line-height: 0.9; letter-spacing: -0.05em; font-weight: 400; }
.issue-hero__lede { max-width: 42rem; margin: 1rem 0 0; font-size: 1.12rem; line-height: 1.7; color: var(--ink-soft); }
.issue-hero__note { align-self: end; padding: 1.2rem; }
.issue-hero__note h2 { margin: 0; font-family: var(--font-display); font-size: clamp(1.6rem, 3.5vw, 2.6rem); line-height: 0.96; font-weight: 400; }
.issue-hero__note p { margin: 0.6rem 0 0; color: var(--ink-soft); line-height: 1.55; }
/* =========================================================================
AI Lab page
========================================================================= */
.ai-lab-page {
position: relative;
padding-bottom: 3rem;
background:
radial-gradient(circle at 82% 15%, rgba(27, 58, 95, 0.14), transparent 20%),
radial-gradient(circle at 14% 12%, rgba(160, 118, 20, 0.14), transparent 18%),
linear-gradient(180deg, rgba(251, 248, 240, 0.98), rgba(242, 233, 215, 0.92));
}
.ai-lab-page::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
background-image: linear-gradient(rgba(18, 15, 10, 0.035) 1px, transparent 1px);
background-size: 100% 18px;
opacity: 0.5;
z-index: 0;
}
.ai-lab-hero {
display: grid;
grid-template-columns: minmax(0, 1.02fr) minmax(320px, 0.98fr);
gap: 1rem;
align-items: end;
padding: 1.6rem 0 1rem;
}
.ai-lab-hero__copy h1 { margin: 0; max-width: 12ch; font-family: var(--font-display); font-size: clamp(3.2rem, 8vw, 6rem); line-height: 0.88; letter-spacing: -0.06em; font-weight: 400; }
.ai-lab-hero__lede { max-width: 42rem; margin: 1rem 0 0; font-size: 1.1rem; line-height: 1.75; color: var(--ink-soft); }
.ai-lab-hero__actions { display: flex; flex-wrap: wrap; gap: 0.85rem; margin-top: 1.4rem; }
.ai-lab-metrics { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1rem; padding: 0.4rem 0 1rem; }
.ai-lab-metric { padding: 1rem 1.05rem; border-top: 2px solid var(--ink); background: rgba(255, 251, 244, 0.78); }
.ai-lab-metric strong { display: block; font-family: var(--font-display); font-size: 2.4rem; line-height: 0.9; font-weight: 400; }
.ai-lab-metric span { display: block; margin-top: 0.4rem; font-family: var(--font-mono); font-size: 0.64rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--burgundy); }
.ai-lab-metric p { margin: 0.6rem 0 0; color: var(--ink-soft); line-height: 1.55; font-size: 0.92rem; }
.ai-lab-split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(320px, 0.96fr); gap: 1rem; padding: 0.2rem 0 1rem; }
.ai-lab-poster, .ai-lab-credit, .ai-lab-story, .ai-lab-diagram, .ai-lab-code { padding: 1.2rem; }
.ai-lab-poster img, .ai-lab-diagram img { width: 100%; border-radius: 1.2rem; }
.ai-lab-credit p, .ai-lab-story p { margin: 0.8rem 0 0; color: var(--ink-soft); line-height: 1.7; }
.ai-lab-story h2 { margin: 0; font-family: var(--font-display); font-size: clamp(2rem, 4.5vw, 3.4rem); line-height: 0.95; font-weight: 400; }
.ai-lab-code pre {
margin: 0;
overflow-x: auto;
padding: 1rem;
border-radius: 1rem;
background: #11151c;
color: #f3ead9;
font-family: var(--font-mono-tech);
font-size: 0.78rem;
line-height: 1.6;
}
.ai-lab-code__block p { margin: 0 0 0.5rem; font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--burgundy); }
/* =========================================================================
Jazz desk + Article page
========================================================================= */
.jazz-page, .article-page {
position: relative;
padding-bottom: 3rem;
background:
radial-gradient(circle at 84% 16%, rgba(31, 93, 95, 0.14), transparent 18%),
radial-gradient(circle at 14% 12%, rgba(143, 34, 24, 0.12), transparent 18%),
linear-gradient(180deg, rgba(251, 248, 240, 0.98), rgba(244, 235, 219, 0.92));
}
.jazz-page::before, .article-page::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
background: linear-gradient(transparent 0, transparent 97%, rgba(18, 15, 10, 0.03) 97%, rgba(18, 15, 10, 0.03) 100%);
background-size: 100% 10px;
opacity: 0.35;
z-index: 0;
}
.jazz-hero {
display: grid;
grid-template-columns: minmax(0, 1.12fr) minmax(300px, 0.88fr);
gap: 1rem;
padding: 2rem 0 1rem;
}
.jazz-hero__copy h1 { margin: 0; max-width: 11ch; font-family: var(--font-display); font-size: clamp(3rem, 8vw, 6rem); line-height: 0.9; letter-spacing: -0.05em; font-weight: 400; }
.jazz-hero__lede { max-width: 42rem; margin: 1rem 0 0; color: var(--ink-soft); font-size: 1.12rem; line-height: 1.7; }
.jazz-gallery { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; padding: 0.3rem 0 1rem; }
.jazz-gallery__figure {
margin: 0;
overflow: hidden;
border-radius: 1.4rem;
border: 1px solid var(--line);
background: rgba(255, 252, 246, 0.84);
box-shadow: var(--shadow-paper);
}
.jazz-gallery__figure img { width: 100%; height: clamp(16rem, 32vw, 26rem); object-fit: cover; }
.jazz-gallery__figure figcaption {
display: flex;
flex-wrap: wrap;
gap: 0.5rem 0.8rem;
padding: 0.85rem 1rem 0;
font-family: var(--font-mono);
font-size: 0.62rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--ink-faint);
}
.jazz-gallery__note { margin: 0.45rem 1rem 1rem; color: var(--ink-soft); line-height: 1.5; font-size: 0.9rem; }
.jazz-layout { display: grid; grid-template-columns: minmax(0, 1.08fr) minmax(320px, 0.92fr); gap: 1rem; padding: 0.2rem 0 1rem; }
.jazz-article, .article-body { padding: 1.4rem 1.6rem; }
.jazz-article p, .article-body p { margin: 0.9rem 0 0; color: var(--ink-strong); font-size: 1.04rem; line-height: 1.85; }
.jazz-article p:first-of-type::first-letter, .article-body .dropcap::first-letter {
font-family: var(--font-display);
float: left;
font-size: 4.6em;
line-height: 0.82;
padding: 0.04em 0.08em 0 0;
color: var(--burgundy);
}
.jazz-sidebar { display: grid; gap: 1rem; }
.jazz-sidebar__card { padding: 1.2rem; }
.jazz-list { list-style: none; margin: 0.9rem 0 0; padding: 0; display: grid; gap: 0.9rem; }
.jazz-list li { padding-top: 0.9rem; border-top: 1px solid var(--line); }
.jazz-list span { display: block; font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--burgundy); }
.jazz-list strong { display: block; margin-top: 0.4rem; font-family: var(--font-display); font-size: 1.4rem; line-height: 1; font-weight: 400; }
.jazz-list p { margin: 0.5rem 0 0; color: var(--ink-soft); line-height: 1.55; font-size: 0.93rem; }
.jazz-list a { display: inline-flex; margin-top: 0.7rem; font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.12em; text-transform: uppercase; text-decoration: none; color: var(--teal); }
.jazz-venues { padding: 0.8rem 0 1rem; }
.jazz-venue-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1rem; }
.jazz-venue-card { padding: 1rem 1.05rem; border-top: 2px solid var(--ink); background: rgba(255, 252, 246, 0.78); }
.jazz-venue-card h3 { margin: 0; font-family: var(--font-display); font-size: 1.6rem; line-height: 1; font-weight: 400; }
.jazz-venue-card p { margin: 0.65rem 0 0; color: var(--ink-soft); line-height: 1.55; font-size: 0.95rem; }
.jazz-venue-card a { display: inline-flex; margin-top: 0.6rem; font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.12em; text-transform: uppercase; text-decoration: none; color: var(--teal); }
/* =========================================================================
Norway page
========================================================================= */
.norway-page {
position: relative;
padding-bottom: 3rem;
background:
radial-gradient(circle at 82% 16%, rgba(70, 97, 62, 0.16), transparent 18%),
radial-gradient(circle at 12% 14%, rgba(45, 95, 146, 0.12), transparent 20%),
linear-gradient(180deg, rgba(251, 248, 240, 0.98), rgba(241, 236, 225, 0.94));
}
.norway-hero { display: grid; grid-template-columns: minmax(0, 1.08fr) minmax(320px, 0.92fr); gap: 1rem; padding: 2rem 0 1rem; }
.norway-hero__copy h1 { margin: 0; max-width: 11ch; font-family: var(--font-display); font-size: clamp(3rem, 7.5vw, 5.6rem); line-height: 0.92; font-weight: 400; letter-spacing: -0.05em; }
.norway-hero__lede { max-width: 38rem; margin: 1.25rem 0 0; font-size: 1.18rem; line-height: 1.58; }
.norway-pullquote {
padding: 1.4rem;
background:
radial-gradient(circle at top right, rgba(70, 97, 62, 0.14), transparent 24%),
linear-gradient(180deg, rgba(255, 253, 247, 0.92), rgba(247, 242, 233, 0.88));
}
.norway-pullquote p { margin: 0; font-family: var(--font-display); font-size: 1.7rem; line-height: 1.2; color: var(--ink); font-style: italic; }
.norway-pullquote cite { display: block; margin-top: 0.8rem; font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--sec-norway); font-style: normal; }
.norway-gallery { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1rem; padding: 0.8rem 0 1rem; }
.norway-gallery__figure { margin: 0; display: grid; gap: 0.5rem; }
.norway-gallery__figure img { width: 100%; aspect-ratio: 1.2; object-fit: cover; border-radius: 1.4rem; border: 1px solid var(--line); box-shadow: var(--shadow-paper); }
.norway-gallery__figure figcaption {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 0.5rem;
font-family: var(--font-mono);
font-size: 0.66rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--ink-soft);
}
/* =========================================================================
Education page
========================================================================= */
.education-page {
position: relative;
padding-bottom: 3rem;
background:
radial-gradient(circle at 88% 12%, rgba(160, 118, 20, 0.18), transparent 22%),
linear-gradient(180deg, rgba(251, 248, 240, 0.98), rgba(244, 235, 219, 0.92));
}
/* =========================================================================
CV page
========================================================================= */
.cv-page {
padding-bottom: 3rem;
background:
radial-gradient(circle at 90% 8%, rgba(92, 86, 76, 0.15), transparent 22%),
linear-gradient(180deg, rgba(251, 248, 240, 0.98), rgba(246, 240, 225, 0.94));
}
.cv-stripe {
display: grid;
grid-template-columns: 11rem minmax(0, 1fr);
gap: 1.5rem;
padding: 1.1rem 0;
border-top: 1px solid var(--line);
align-items: start;
}
.cv-stripe__year {
font-family: var(--font-mono);
font-size: 0.74rem;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--burgundy);
padding-top: 0.2rem;
}
.cv-stripe__year strong {
display: block;
font-family: var(--font-display);
font-size: 1.4rem;
color: var(--ink);
letter-spacing: -0.01em;
font-weight: 400;
margin-bottom: 0.2rem;
}
.cv-stripe__title { font-family: var(--font-display); font-size: 1.8rem; line-height: 1.05; font-weight: 400; margin: 0; }
.cv-stripe__org { display: block; margin-top: 0.4rem; color: var(--ink-soft); font-style: italic; }
.cv-stripe__detail { margin: 0.6rem 0 0; color: var(--ink-soft); line-height: 1.6; max-width: 56ch; }
/* =========================================================================
Family album
========================================================================= */
.family-page {
padding-bottom: 3rem;
background:
radial-gradient(circle at 12% 12%, rgba(139, 74, 65, 0.14), transparent 20%),
radial-gradient(circle at 88% 14%, rgba(160, 118, 20, 0.12), transparent 22%),
linear-gradient(180deg, rgba(251, 248, 240, 0.98), rgba(244, 235, 219, 0.94));
}
.family-album {
display: grid;
grid-template-columns: repeat(12, minmax(0, 1fr));
gap: 0.85rem;
}
.family-photo {
position: relative;
overflow: hidden;
border-radius: 0.6rem;
border: 1px solid rgba(18, 15, 10, 0.12);
background: var(--paper-white);
box-shadow: var(--shadow-paper);
}
.family-photo img { width: 100%; height: 100%; object-fit: cover; filter: saturate(0.9) contrast(1.05); }
.family-photo figcaption {
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 0.6rem 0.75rem;
background: linear-gradient(180deg, transparent, rgba(10, 12, 18, 0.6));
color: #f7f1e3;
font-family: var(--font-mono);
font-size: 0.6rem;
letter-spacing: 0.14em;
text-transform: uppercase;
}
.family-photo--12 { grid-column: span 12; aspect-ratio: 2.5; }
.family-photo--8 { grid-column: span 8; aspect-ratio: 1.6; }
.family-photo--6 { grid-column: span 6; aspect-ratio: 1.4; }
.family-photo--4 { grid-column: span 4; aspect-ratio: 1.0; }
.family-photo--3 { grid-column: span 3; aspect-ratio: 0.9; }
.family-caption {
grid-column: span 12;
padding: 1.2rem;
background: rgba(255, 252, 246, 0.8);
border-radius: 1rem;
border-top: 2px solid var(--ink);
}
/* =========================================================================
Responsive
========================================================================= */
@media (max-width: 1080px) {
.cover-hero__inner,
.venture-desk__grid,
.signal-deck,
.signal-strip,
.frontline,
.manifesto-grid,
.section-marquee__row,
.site-footer__top,
.ai-lab-split,
.ai-lab-metrics,
.ai-lab-hero,
.jazz-layout,
.jazz-hero,
.jazz-gallery,
.jazz-venue-grid,
.norway-hero,
.norway-gallery,
.issue-hero { grid-template-columns: 1fr; }
.section-grid > * { grid-column: span 6; }
.cv-stripe { grid-template-columns: 1fr; gap: 0.5rem; }
.family-album { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.family-photo--12, .family-photo--8 { grid-column: span 6; }
.family-photo--6, .family-photo--4, .family-photo--3 { grid-column: span 3; }
}
@media (max-width: 640px) {
.section-grid > * { grid-column: span 12; }
.footer-links { grid-template-columns: 1fr; }
.norway-gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.jazz-venue-grid { grid-template-columns: 1fr; }
.ai-lab-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (prefers-reduced-motion: reduce) {
.page-shell::before,
.page-shell::after { animation: none; }
* { transition-duration: 0.01ms !important; }
}
.cookie-toggle span {
display: grid;
gap: 0.2rem;
@@ -5111,3 +5690,84 @@ html[data-ui-lang="nb"] .locale-copy__text[data-locale-option="nb"] {
width: calc(100% - 1.5rem);
}
}
/* =====================================================================
LOCALE BAR — fixed flag strip at top of every page
===================================================================== */
.locale-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 9999;
height: 42px;
background: var(--burgundy);
display: flex;
align-items: center;
justify-content: center;
border-bottom: 1px solid var(--burgundy-deep);
}
.locale-bar__inner {
display: flex;
align-items: center;
gap: 0.25rem;
}
.locale-bar__btn {
display: inline-flex;
align-items: center;
gap: 0.45rem;
background: none;
border: none;
color: rgba(246, 240, 225, 0.7);
cursor: pointer;
font-family: var(--font-mono);
font-size: 0.82rem;
letter-spacing: 0.12em;
text-transform: uppercase;
padding: 0.35rem 0.9rem;
border-radius: var(--radius-xs);
transition: color var(--dur-fast) var(--ease-out),
background var(--dur-fast) var(--ease-out);
line-height: 1;
}
.locale-bar__btn:hover {
color: var(--paper);
background: rgba(255, 255, 255, 0.12);
}
.locale-bar__btn.is-active {
color: var(--paper-white);
background: rgba(255, 255, 255, 0.22);
font-weight: 600;
}
.locale-bar__btn span {
font-size: 0.78rem;
}
.locale-bar__sep {
color: rgba(246, 240, 225, 0.3);
font-family: var(--font-mono);
font-size: 1rem;
user-select: none;
padding: 0 0.1rem;
}
body {
padding-top: 42px;
}
@media (max-width: 600px) {
.locale-bar__btn span {
display: none;
}
.locale-bar__btn {
font-size: 1.1rem;
padding: 0.3rem 0.6rem;
letter-spacing: 0;
}
}