/* global React, ReactDOM, TweaksPanel, useTweaks, TweakSection, TweakRadio, TweakColor */
const { useState, useEffect, useMemo } = React;
// ─────────────────────────────────────────────────────────────────────────────
// i18n
// ─────────────────────────────────────────────────────────────────────────────
const T = {
fr: {
locale: 'fr-CA',
edition: 'CANADIAN PORTAL · ÉDITION FR-CA · MAI 2026',
updated: 'Mise à jour\u00a0: 06.05.2026',
notice: 'Site indépendant, non affilié au gouvernement',
nav: [
{ label: 'Immigrer', href: '#immigrer' },
{ label: 'Travailler', href: '#travailler' },
{ label: 'Vivre', href: '#vivre' },
{ label: 'Provinces', href: '#provinces' },
{ label: 'Actualités', href: '#actu' },
{ label: 'Guides', href: '#guides' },
{ label: 'Citoyenneté', href: '#citoyennete' },
],
subscribe: 'S’abonner',
searchPlaceholder: 'Chercher un guide, une province, un programme…',
live: 'EN DIRECT',
eyebrow: 'Vol. 07 · Édition de mai',
heroTitleA: 'Comprendre le',
heroTitleEm: 'Canada',
heroTitleB: 'avant de le',
heroTitleC: 'choisir.',
heroDek: 'Un journal indépendant pour préparer un projet d’immigration avec calme. Programmes officiels, vie quotidienne, finances, citoyenneté. Relus, sourcés, jamais alarmistes.',
ctaExplore: 'Explorer les rubriques',
ctaRead: 'Lire les guides récents',
metaGuides: 'Guides publiés',
metaProvinces: 'Provinces et territoires',
metaEdition: 'Édition à jour',
cardKicker: 'À LA UNE · 06.05.2026',
cardTitleA: 'Entrée express, 2026',
cardTitleB: 'les seuils des dernières rondes',
cardDek: 'Lecture mesurée des invitations récentes. Ce que les chiffres permettent réellement de prévoir, et ce qu’ils ne disent pas.',
cardReadTime: '10 min de lecture',
cardCta: 'Lire l’article →',
sec1Mono: '§ I · RUBRIQUES',
sec1Title: 'Sept entrées, un seul fil rouge.',
sec1Lead: 'Chaque rubrique regroupe des guides relus à la main, mis à jour, et croisés avec les sources officielles d’IRCC et des provinces.',
sec2Mono: '§ II · GUIDES RÉCENTS',
sec2Title: 'La rédaction cette semaine.',
sec2Lead: 'Filtrez par rubrique. Tous les articles sont datés et révisés à chaque changement officiel signalé par IRCC.',
all: 'Tout',
results: 'résultats',
result: 'résultat',
fullArticle: 'Lire l’article complet →',
minRead: 'min',
emptyTitle: 'Aucun guide ne correspond à ce filtre pour le moment.',
emptyCta: 'Tout afficher',
pull: 'Immigrer, c’est traduire sa vie dans un nouveau cadre administratif. Ce portail aide à le faire',
pullAccent: 'sans perdre la sienne.',
pullCredit: 'La rédaction',
sec3Mono: '§ III · INFOLETTRE',
sec3Title: 'Une lettre, deux fois par mois.',
sec3Lead: 'Les changements officiels qui méritent vraiment votre attention. Sans alarmisme, sans clickbait, avec les liens vers les sources.',
nlLabel: 'VOTRE COURRIEL',
nlPlaceholder: 'vous@exemple.ca',
nlFine1: 'Aucun spam. Désabonnement en un clic. Voir notre',
nlFineLink: 'politique de confidentialité',
footerBrand: 'Un journal indépendant pour préparer un projet canadien avec calme. Pédagogique, vérifié, jamais alarmiste.',
footerNotice: 'Site indépendant non affilié au gouvernement du Canada.',
fSections: 'Rubriques',
fAbout: 'À propos',
fFollow: 'Suivre',
fAboutLinks: ['Notre méthode', 'Contact', 'Mentions légales', 'Politique de confidentialité', 'Conditions d’utilisation'],
fFollowLinks: ['Infolettre', 'Instagram', 'YouTube', 'Pinterest', 'RSS'],
fCopy: '© 2026 CANADIAN PORTAL · TOUS DROITS RÉSERVÉS',
fMade: 'FAIT AVEC SOIN POUR FUTURS NOUVEAUX ARRIVANTS',
ticker: [
{ tag: 'NOUVEAU', text: 'Ontario PNP, nouveau tirage Tech Draw, seuil CRS 472' },
{ tag: 'IRCC', text: 'Entrée express, ronde du 06 mai, 4 500 invitations envoyées' },
{ tag: 'QUÉBEC', text: 'PEQ volet diplômés, ouverture des inscriptions le 15 mai 2026' },
{ tag: 'C.-B.', text: 'BC PNP Tech, 168 invitations cette semaine, score min. 105' },
{ tag: 'ALERTE', text: 'Permis postdiplôme, nouvelles règles dès juin 2026' },
{ tag: 'ALBERTA', text: 'AAIP rouvre la catégorie Tourism & Hospitality après 18 mois' },
{ tag: 'CITOYENNETÉ', text: 'Délais de traitement réduits à 10 mois en moyenne' },
{ tag: 'MANITOBA', text: 'MPNP, 312 candidats invités, secteurs prioritaires en santé' },
{ tag: 'NOUVEAU', text: 'Programme pilote pour les aides familiaux, ouverture confirmée' },
],
topics: [
{ id: 'immigrer', label: 'Immigrer', kicker: '01', count: 14, hue: 25,
title: 'Immigrer au Canada',
desc: 'Programmes de résidence permanente, Entrée express, candidats des provinces, critères et étapes avant la demande.' },
{ id: 'travailler', label: 'Travailler', kicker: '02', count: 11, hue: 165,
title: 'Travailler au Canada',
desc: 'Permis de travail, droits des travailleurs étrangers, transition vers la résidence permanente, employeurs et offres.' },
{ id: 'vivre', label: 'Vivre', kicker: '03', count: 9, hue: 230,
title: 'Vivre au Canada',
desc: 'Préparer l’arrivée, services d’établissement, logement, santé, finances, impôts et intégration au quotidien.' },
{ id: 'provinces', label: 'Provinces', kicker: '04', count: 10, hue: 290,
title: 'Provinces et territoires',
desc: 'Comparer les provinces selon immigration, emploi, coût de la vie et installation. Panoramas pratiques.' },
{ id: 'actu', label: 'Actualités', kicker: '05', count: 8, hue: 50,
title: 'Actualités IRCC',
desc: 'Décryptages prudents des changements officiels, annonces, mises à jour de programmes et tendances à surveiller.' },
{ id: 'guides', label: 'Guides', kicker: '06', count: 16, hue: 130,
title: 'Guides et conseils',
desc: 'Conseils concrets pour organiser un dossier, éviter les pièges, préparer son budget, communiquer avec les autorités.' },
{ id: 'citoyennete', label: 'Citoyenneté', kicker: '07', count: 7, hue: 340,
title: 'Citoyenneté canadienne',
desc: 'Repères sur la présence physique, le test, les langues et les étapes après la résidence permanente.' },
],
articles: [], },
en: {
locale: 'en-CA',
edition: 'CANADIAN PORTAL · EN-CA EDITION · MAY 2026',
updated: 'Updated\u00a0: 05.06.2026',
notice: 'Independent site, not affiliated with the Government of Canada',
nav: [
{ label: 'Immigrate', href: '#immigrer' },
{ label: 'Work', href: '#travailler' },
{ label: 'Live', href: '#vivre' },
{ label: 'Provinces', href: '#provinces' },
{ label: 'News', href: '#actu' },
{ label: 'Guides', href: '#guides' },
{ label: 'Citizenship', href: '#citoyennete' },
],
subscribe: 'Subscribe',
searchPlaceholder: 'Search a guide, a province, a program…',
live: 'LIVE',
eyebrow: 'Vol. 07 · May Edition',
heroTitleA: 'Understand',
heroTitleEm: 'Canada',
heroTitleB: 'before you',
heroTitleC: 'choose it.',
heroDek: 'An independent journal for planning your immigration project calmly. Official programs, daily life, finances, citizenship. Edited, sourced, never alarmist.',
ctaExplore: 'Explore sections',
ctaRead: 'Read recent guides',
metaGuides: 'Published guides',
metaProvinces: 'Provinces and territories',
metaEdition: 'Up-to-date edition',
cardKicker: 'FEATURED · 05.06.2026',
cardTitleA: 'Express Entry, 2026',
cardTitleB: 'thresholds of recent rounds',
cardDek: 'A measured reading of recent invitations. What the numbers really tell us, and what they don’t.',
cardReadTime: '10 min read',
cardCta: 'Read the article →',
sec1Mono: '§ I · SECTIONS',
sec1Title: 'Seven entries, one common thread.',
sec1Lead: 'Each section gathers guides reviewed by hand, kept up to date, and cross-checked with official IRCC and provincial sources.',
sec2Mono: '§ II · RECENT GUIDES',
sec2Title: 'The newsroom this week.',
sec2Lead: 'Filter by section. All articles are dated and revised whenever IRCC announces an official change.',
all: 'All',
results: 'results',
result: 'result',
fullArticle: 'Read the full article →',
minRead: 'min',
emptyTitle: 'No guide matches this filter at the moment.',
emptyCta: 'Show all',
pull: 'Immigrating means translating your life into a new administrative frame. This portal helps you do that',
pullAccent: 'without losing your own.',
pullCredit: 'The editors',
sec3Mono: '§ III · NEWSLETTER',
sec3Title: 'A letter, twice a month.',
sec3Lead: 'The official changes that truly deserve your attention. No alarmism, no clickbait, with links to the sources.',
nlLabel: 'YOUR EMAIL',
nlPlaceholder: 'you@example.ca',
nlFine1: 'No spam. Unsubscribe in one click. See our',
nlFineLink: 'privacy policy',
footerBrand: 'An independent journal to plan a Canadian project calmly. Educational, fact-checked, never alarmist.',
footerNotice: 'Independent site not affiliated with the Government of Canada.',
fSections: 'Sections',
fAbout: 'About',
fFollow: 'Follow',
fAboutLinks: ['Our method', 'Contact', 'Legal notice', 'Privacy policy', 'Terms of use'],
fFollowLinks: ['Newsletter', 'Instagram', 'YouTube', 'Pinterest', 'RSS'],
fCopy: '© 2026 CANADIAN PORTAL · ALL RIGHTS RESERVED',
fMade: 'MADE WITH CARE FOR FUTURE NEWCOMERS',
ticker: [
{ tag: 'NEW', text: 'Ontario PNP, new Tech Draw, CRS threshold 472' },
{ tag: 'IRCC', text: 'Express Entry, May 6 round, 4,500 invitations issued' },
{ tag: 'QUÉBEC', text: 'PEQ graduates stream, registrations open May 15, 2026' },
{ tag: 'B.C.', text: 'BC PNP Tech, 168 invitations this week, min. score 105' },
{ tag: 'ALERT', text: 'Postgraduate work permit, new rules from June 2026' },
{ tag: 'ALBERTA', text: 'AAIP reopens Tourism & Hospitality category after 18 months' },
{ tag: 'CITIZENSHIP', text: 'Processing times reduced to 10 months on average' },
{ tag: 'MANITOBA', text: 'MPNP, 312 candidates invited, priority sectors in healthcare' },
{ tag: 'NEW', text: 'Pilot program for caregivers, opening confirmed' },
],
topics: [
{ id: 'immigrer', label: 'Immigrate', kicker: '01', count: 14, hue: 25,
title: 'Immigrate to Canada',
desc: 'Permanent residence programs, Express Entry, provincial nominees, criteria and steps before applying.' },
{ id: 'travailler', label: 'Work', kicker: '02', count: 11, hue: 165,
title: 'Work in Canada',
desc: 'Work permits, rights of foreign workers, transitioning to permanent residence, employers and job offers.' },
{ id: 'vivre', label: 'Live', kicker: '03', count: 9, hue: 230,
title: 'Live in Canada',
desc: 'Prepare your arrival, settlement services, housing, healthcare, finances, taxes and daily integration.' },
{ id: 'provinces', label: 'Provinces', kicker: '04', count: 10, hue: 290,
title: 'Provinces and territories',
desc: 'Compare provinces by immigration, employment, cost of living and settlement. Practical overviews.' },
{ id: 'actu', label: 'News', kicker: '05', count: 8, hue: 50,
title: 'IRCC news',
desc: 'Careful analysis of official changes, announcements, program updates and trends to watch.' },
{ id: 'guides', label: 'Guides', kicker: '06', count: 16, hue: 130,
title: 'Guides and tips',
desc: 'Concrete advice to organize a file, avoid pitfalls, plan a budget, and communicate with authorities.' },
{ id: 'citoyennete', label: 'Citizenship', kicker: '07', count: 7, hue: 340,
title: 'Canadian citizenship',
desc: 'Reference points on physical presence, the test, languages, and steps after permanent residence.' },
],
articles: [], },
};
if (window.ARTICLES_FR) T.fr.articles = window.ARTICLES_FR;
if (window.ARTICLES_EN) T.en.articles = window.ARTICLES_EN;
// Hash routing helpers
function parseHash(h) {
if (!h || h === '#') return { kind: 'home' };
const m = h.replace(/^#/, '').match(/^(article|rubrique|page)\/(.+)$/);
if (!m) return { kind: 'home' };
if (m[1] === 'article') return { kind: 'article', id: parseInt(m[2], 10) };
if (m[1] === 'page') return { kind: 'page', id: m[2] };
return { kind: 'topic', id: m[2] };
}
function slugArticle(idx) { return `#article/${idx}`; }
function slugTopic(id) { return `#rubrique/${id}`; }
// ─────────────────────────────────────────────────────────────────────────────
// Logo
// ─────────────────────────────────────────────────────────────────────────────
function CPLogo({ size = 38 }) {
return (
Canadian
Portal
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Article art (richer placeholder illustrations)
// ─────────────────────────────────────────────────────────────────────────────
// Photo gallery is provided by cp-articles.js (window.ART_PHOTOS)
function ArticleArt({ kind, large = false }) {
const url = (window.ART_PHOTOS && (window.ART_PHOTOS[kind] || window.ART_PHOTOS.editorial)) || '';
const wrapStyle = {
width: '100%',
aspectRatio: large ? '4 / 3' : '3 / 2',
background: '#1a1815',
borderRadius: 6,
overflow: 'hidden',
position: 'relative',
border: '1px solid var(--line)',
};
const imgStyle = {
position: 'absolute', inset: 0, width: '100%', height: '100%',
objectFit: 'cover', display: 'block',
filter: 'saturate(0.92) contrast(1.04)',
};
const overlay = (
);
const corner = large ? (
À LA UNE
) : null;
return (

{overlay}
{corner}
);
}
// ─────────────────────────────────────────────────────────────────────────────
// App
// ─────────────────────────────────────────────────────────────────────────────
function App() {
const [tweaks, setTweak] = useTweaks(/*EDITMODE-BEGIN*/{
"palette": "warm",
"accentHue": 28,
"density": "comfortable"
}/*EDITMODE-END*/);
const [lang, setLang] = useState('fr');
const t = T[lang];
useEffect(() => {
const root = document.documentElement;
const palettes = {
warm: { bg: '#f6f1e7', card: '#fbf7ef', ink: '#1a1815', ink2: '#3a3530', ink3: '#6b645c', line: '#e3dccc', line2: '#cdc4b0' },
cool: { bg: '#eef0f2', card: '#f7f8fa', ink: '#101418', ink2: '#2c333a', ink3: '#5b6470', line: '#d8dde2', line2: '#b9c1cb' },
paper: { bg: '#f3eee5', card: '#ffffff', ink: '#161514', ink2: '#33312d', ink3: '#6c665b', line: '#e2dccd', line2: '#c7bea7' },
dark: { bg: '#15140f', card: '#1c1b16', ink: '#f3ede0', ink2: '#d6cfbe', ink3: '#8c8576', line: '#2e2c25', line2: '#3e3b32' },
};
const p = palettes[tweaks.palette] || palettes.warm;
root.style.setProperty('--bg', p.bg);
root.style.setProperty('--bg-card', p.card);
root.style.setProperty('--ink', p.ink);
root.style.setProperty('--ink-2', p.ink2);
root.style.setProperty('--ink-3', p.ink3);
root.style.setProperty('--line', p.line);
root.style.setProperty('--line-2', p.line2);
// Canada red — toned down further
root.style.setProperty('--accent', `oklch(0.52 0.18 ${tweaks.accentHue})`);
root.style.setProperty('--accent-deep', `oklch(0.42 0.17 ${tweaks.accentHue})`);
root.style.setProperty('--accent-soft', `oklch(0.94 0.04 ${tweaks.accentHue})`);
}, [tweaks.palette, tweaks.accentHue]);
const [activeTopic, setActiveTopic] = useState('all');
const [search, setSearch] = useState('');
const [navOpen, setNavOpen] = useState(false);
const [showSearch, setShowSearch] = useState(false);
const [route, setRoute] = useState(() => parseHash(window.location.hash));
useEffect(() => { document.body.classList.add('ready'); }, []);
// Hash routing — real pages via #article/ and #rubrique/
useEffect(() => {
const onHash = () => {
setRoute(parseHash(window.location.hash));
window.scrollTo({ top: 0, behavior: 'auto' });
};
window.addEventListener('hashchange', onHash);
return () => window.removeEventListener('hashchange', onHash);
}, []);
function navigate(hash) {
if (window.location.hash === hash) {
window.scrollTo({ top: 0, behavior: 'smooth' });
} else {
window.location.hash = hash;
}
}
const openArticle = route.kind === 'article' ? t.articles[route.id] : null;
const openTopic = route.kind === 'topic' ? t.topics.find(tp => tp.id === route.id) : null;
const filtered = useMemo(() => {
return t.articles.filter(a => {
if (activeTopic !== 'all' && a.tag !== activeTopic) return false;
if (search.trim()) {
const q = search.toLowerCase();
if (!a.title.toLowerCase().includes(q) && !a.dek.toLowerCase().includes(q) && !a.cat.toLowerCase().includes(q)) return false;
}
return true;
});
}, [activeTopic, search, t]);
const featured = filtered.find(a => a.featured) || filtered[0];
const rest = filtered.filter(a => a !== featured);
return (
{/* Top utility bar */}
{t.edition}
{t.updated}
•
{t.notice}
{/* Header */}
{t.subscribe}
{showSearch && (
)}
{navOpen && (
)}
{/* News Ticker (red band, under menu) */}
{t.live}
{t.ticker.map((it, i) => (
{it.tag}
{it.text}
◆
))}
{t.ticker.map((it, i) => (
{it.tag}
{it.text}
◆
))}
{/* Hero */}
{route.kind === 'home' && (<>
{t.eyebrow}
{t.heroTitleA} {t.heroTitleEm}
{t.heroTitleB} {t.heroTitleC}
{t.heroDek}
- 92{t.metaGuides}
- 13{t.metaProvinces}
- 2026{t.metaEdition}
{/* Topics */}
{t.sec1Mono}
{t.sec1Title}
{t.sec1Lead}
{/* Latest */}
{/* Pull quote */}
“
{t.pull} {t.pullAccent}
· {t.pullCredit}
{/* Newsletter */}
{t.sec3Mono}
{t.sec3Title}
{t.sec3Lead}
>)}
{/* Article page */}
{route.kind === 'article' && openArticle && (
)}
{/* Topic page */}
{route.kind === 'topic' && openTopic && (
)}
{/* Static pages (About / Privacy / Contact) */}
{route.kind === 'page' && (
)}
{/* Footer */}
{openArticle && false && (
setOpenArticle(null)}
onOpenArticle={(a) => setOpenArticle(a)}
ArticleArt={ArticleArt}
/>
)}
{openTopic && !openArticle && false && (
setOpenTopic(null)}
onOpenArticle={(a) => setOpenArticle(a)}
ArticleArt={ArticleArt}
/>
)}
{/* Tweaks */}
setTweak('palette', v)}
options={[
{ value: 'warm', label: 'Chaud' },
{ value: 'cool', label: 'Froid' },
{ value: 'paper', label: 'Papier' },
{ value: 'dark', label: 'Sombre' },
]}
/>
setTweak('accentHue', v)}
options={[
{ value: 28, color: 'oklch(0.56 0.22 28)', label: 'Rouge Canada' },
{ value: 28, color: 'oklch(0.50 0.16 28)', label: 'Rouge vif' },
{ value: 50, color: 'oklch(0.55 0.13 50)', label: 'Brique' },
{ value: 230, color: 'oklch(0.46 0.12 230)', label: 'Atlantique' },
]}
/>
setTweak('density', v)}
options={[
{ value: 'comfortable', label: 'Confortable' },
{ value: 'compact', label: 'Compacte' },
]}
/>
);
}
// ─────────────────────────────────────────────────────────────────────────────
// CSS
// ─────────────────────────────────────────────────────────────────────────────
const cssText = `
.cp-app { color: var(--ink); background: var(--bg); min-height: 100vh; }
.cp-app a { color: inherit; text-decoration: none; }
.cp-app button { font: inherit; cursor: pointer; }
/* News Ticker */
.news-ticker {
background: var(--accent);
color: white;
display: flex; align-items: stretch;
border-bottom: 2px solid var(--accent-deep);
overflow: hidden; position: relative;
height: 44px;
}
.ticker-label {
background: var(--accent-deep);
color: white;
display: inline-flex; align-items: center; gap: 8px;
padding: 0 18px;
font-family: var(--mono); font-size: 11px; font-weight: 700;
letter-spacing: 0.16em;
flex-shrink: 0; position: relative; z-index: 2;
box-shadow: 6px 0 12px -6px rgba(0,0,0,0.4);
}
.ticker-dot {
width: 8px; height: 8px; background: white; border-radius: 50%;
animation: tickerPulse 1.2s infinite;
}
@keyframes tickerPulse {
0% { box-shadow: 0 0 0 0 rgba(255,255,255,0.8); }
100% { box-shadow: 0 0 0 10px rgba(255,255,255,0); }
}
.ticker-track {
flex: 1; overflow: hidden; display: flex; align-items: center;
-webkit-mask-image: linear-gradient(90deg, transparent 0, #000 40px, #000 calc(100% - 40px), transparent 100%);
mask-image: linear-gradient(90deg, transparent 0, #000 40px, #000 calc(100% - 40px), transparent 100%);
}
.ticker-content {
display: inline-flex; align-items: center; white-space: nowrap;
animation: ticker 90s linear infinite; padding-left: 32px;
}
.news-ticker:hover .ticker-content { animation-play-state: paused; }
@keyframes ticker { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.ticker-item { display: inline-flex; align-items: center; gap: 12px; font-size: 13px; font-weight: 500; }
.ticker-tag {
font-family: var(--mono); font-size: 10.5px; font-weight: 700;
background: rgba(255,255,255,0.18); color: white;
padding: 3px 8px; border-radius: 3px; letter-spacing: 0.1em;
}
.ticker-text { color: rgba(255,255,255,0.96); }
.ticker-sep { color: rgba(255,255,255,0.45); margin: 0 18px; font-size: 10px; }
/* Utility bar */
.utility {
background: var(--accent-deep);
color: white;
border-bottom: 1px solid var(--accent-deep);
}
.utility-inner {
max-width: 1280px; margin: 0 auto; padding: 8px 32px;
display: flex; align-items: center; justify-content: space-between;
font-size: 11px; letter-spacing: 0.08em;
}
.util-mono { font-family: var(--mono); color: rgba(255,255,255,0.92); }
.util-right { display: flex; align-items: center; gap: 12px; }
.util-dot { opacity: 0.4; }
.util-notice { opacity: 0.7; }
/* Header */
.site-header {
background: var(--bg);
border-bottom: 1px solid var(--line);
}
.header-inner {
max-width: 1280px; margin: 0 auto; padding: 18px 32px;
display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 32px;
}
.brand-link { display: inline-flex; }
.primary-nav { display: flex; gap: 28px; justify-content: center; flex-wrap: wrap; }
.primary-nav a {
font-size: 14px; font-weight: 500; color: var(--ink-2);
position: relative; padding: 4px 0; transition: color .15s;
}
.primary-nav a:hover { color: var(--accent); }
.primary-nav a::after {
content: ''; position: absolute; left: 0; right: 100%; bottom: -2px;
height: 1px; background: var(--accent); transition: right .2s ease;
}
.primary-nav a:hover::after { right: 0; }
.header-actions { display: flex; align-items: center; gap: 14px; }
.lang-switch {
display: inline-flex; align-items: center; border: 1px solid var(--line-2);
border-radius: 999px; padding: 2px; gap: 0;
}
.lang-switch button {
border: 0; background: transparent; padding: 6px 12px; font-size: 12px;
font-family: var(--mono); font-weight: 600; letter-spacing: 0.08em;
color: var(--ink-3); border-radius: 999px;
}
.lang-switch button.is-on { background: var(--ink); color: var(--bg); }
.lang-sep { display: none; }
.icon-btn {
width: 36px; height: 36px; border-radius: 50%; border: 1px solid var(--line-2);
background: transparent; color: var(--ink); display: inline-flex; align-items: center; justify-content: center;
transition: all .15s;
}
.icon-btn:hover { background: var(--ink); color: var(--bg); border-color: var(--ink); }
.cta-link {
font-size: 13px; font-weight: 600; padding: 9px 18px;
border: 1px solid var(--accent); border-radius: 999px; background: var(--accent); color: white;
transition: all .15s;
}
.cta-link:hover { background: var(--accent-deep); border-color: var(--accent-deep); }
.hamburger { display: none; background: transparent; border: 0; width: 32px; height: 32px; flex-direction: column; gap: 4px; align-items: center; justify-content: center; }
.hamburger span { width: 22px; height: 1.5px; background: var(--ink); display: block; }
.search-drawer { border-top: 1px solid var(--line); background: var(--bg-card); animation: drop .2s ease; }
.search-inner { max-width: 1280px; margin: 0 auto; padding: 18px 32px; display: flex; align-items: center; gap: 14px; }
.search-inner input {
flex: 1; border: 0; background: transparent; outline: none;
font-family: var(--serif); font-size: 22px; color: var(--ink);
}
.search-inner input::placeholder { color: var(--ink-3); font-style: italic; }
.search-close { border: 1px solid var(--line-2); background: transparent; color: var(--ink-3); width: 32px; height: 32px; border-radius: 50%; font-size: 20px; line-height: 1; }
.mobile-nav { display: none; padding: 16px 32px; flex-direction: column; gap: 14px; border-top: 1px solid var(--line); }
.mobile-nav a { font-size: 16px; font-weight: 500; }
@keyframes drop { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
/* Hero */
.hero { max-width: 1280px; margin: 0 auto; padding: 64px 32px 48px; border-bottom: 1px solid var(--line); }
.hero-grid { display: grid; grid-template-columns: 1.25fr 1fr; gap: 56px; align-items: stretch; }
.eyebrow {
display: inline-flex; align-items: center; gap: 10px;
font-family: var(--mono); font-size: 11px; letter-spacing: 0.14em;
color: var(--ink-3); text-transform: uppercase; margin: 0 0 24px;
}
.eyebrow-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); display: inline-block; }
.hero-title {
font-family: var(--serif); font-weight: 500; letter-spacing: -0.02em;
font-size: clamp(48px, 7vw, 96px); line-height: 0.96; margin: 0 0 28px;
color: var(--ink); text-wrap: pretty;
}
.hero-title em { font-style: italic; font-weight: 400; color: var(--accent-deep); }
.hero-underline { position: relative; white-space: nowrap; }
.hero-underline::after {
content: ''; position: absolute; left: 0; right: 0; bottom: 8px; height: 12px;
background: var(--accent-soft); z-index: -1; border-radius: 2px;
}
.hero-dek { font-size: 19px; line-height: 1.55; color: var(--ink-2); max-width: 56ch; margin: 0 0 36px; text-wrap: pretty; }
.hero-actions { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 56px; }
.btn-primary {
display: inline-flex; align-items: center; gap: 10px;
padding: 14px 26px; background: var(--accent); color: white;
border: 1px solid var(--accent); border-radius: 999px; font-weight: 600; font-size: 14px;
transition: transform .15s, background .15s, box-shadow .15s;
box-shadow: 0 6px 20px -8px var(--accent);
}
.btn-primary:hover { background: var(--accent-deep); border-color: var(--accent-deep); transform: translateY(-1px); }
.btn-ghost {
display: inline-flex; align-items: center; gap: 10px;
padding: 14px 24px; background: transparent; color: var(--ink);
border: 1px solid var(--line-2); border-radius: 999px; font-weight: 500; font-size: 14px;
transition: all .15s;
}
.btn-ghost:hover { border-color: var(--ink); }
.hero-meta {
display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px;
list-style: none; padding: 24px 0 0; margin: 0; border-top: 1px solid var(--line);
}
.hero-meta li { display: flex; flex-direction: column; gap: 4px; }
.hero-meta strong { font-family: var(--serif); font-size: 36px; font-weight: 500; color: var(--ink); letter-spacing: -0.02em; }
.hero-meta span { font-family: var(--mono); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-3); }
.hero-card {
border: 1px solid var(--line-2); border-radius: 6px;
background: var(--bg-card); display: flex; flex-direction: column;
position: relative; overflow: hidden;
}
.hero-card-art { width: 100%; }
.hero-card-art > div { aspect-ratio: 4 / 3; border-radius: 0; border: 0; border-bottom: 1px solid var(--line); }
.hero-card-body { padding: 28px 28px 26px; display: flex; flex-direction: column; flex: 1; }
.hero-card-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; }
.card-mono { font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.12em; color: var(--ink-3); text-transform: uppercase; }
.card-dot { width: 8px; height: 8px; background: var(--accent); border-radius: 50%; animation: pulse 2s infinite; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
.hero-card-title { font-family: var(--serif); font-size: 28px; line-height: 1.15; margin: 0 0 16px; font-weight: 500; letter-spacing: -0.01em; }
.hero-card-dek { color: var(--ink-2); font-size: 15px; line-height: 1.55; margin: 0 0 20px; }
.hero-card-foot { display: flex; align-items: center; justify-content: space-between; padding-top: 18px; margin-top: auto; border-top: 1px solid var(--line); }
.card-cta { font-weight: 600; font-size: 13px; color: var(--accent); }
/* Sections */
.topics, .latest, .pull, .newsletter { max-width: 1280px; margin: 0 auto; padding: 80px 32px; }
.section-head { max-width: 720px; margin: 0 auto 48px; text-align: center; }
.section-mono { font-family: var(--mono); font-size: 11px; letter-spacing: 0.16em; color: var(--ink-3); display: block; margin-bottom: 14px; }
.section-head h2 { font-family: var(--serif); font-size: clamp(36px, 4.5vw, 56px); font-weight: 500; margin: 0 0 18px; letter-spacing: -0.02em; line-height: 1.05; }
.section-head p { color: var(--ink-2); font-size: 17px; line-height: 1.55; margin: 0; }
/* Topics */
.topic-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: 6px; overflow: hidden; }
.topic-card { background: var(--bg); padding: 32px 28px; min-height: 220px; display: flex; flex-direction: column; gap: 12px; transition: background .2s; position: relative; }
.topic-card:hover { background: var(--bg-card); }
.topic-card:hover .topic-arrow { transform: translateX(4px); color: var(--accent); }
.topic-num { font-family: var(--mono); font-size: 11px; letter-spacing: 0.16em; color: var(--ink-3); }
.topic-card:nth-child(1) .topic-num,
.topic-card:nth-child(7) .topic-num { color: var(--accent); }
.topic-title { font-family: var(--serif); font-size: 22px; font-weight: 500; margin: 0; letter-spacing: -0.01em; line-height: 1.2; }
.topic-desc { font-size: 14px; line-height: 1.55; color: var(--ink-2); margin: 0; flex: 1; }
.topic-foot { display: flex; align-items: center; justify-content: space-between; padding-top: 16px; border-top: 1px solid var(--line); font-family: var(--mono); font-size: 11px; letter-spacing: 0.1em; color: var(--ink-3); text-transform: uppercase; }
.topic-arrow { font-size: 18px; transition: transform .2s, color .2s; }
/* Filter bar */
.filter-bar { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px; margin-bottom: 36px; padding-bottom: 18px; border-bottom: 1px solid var(--line); }
.filter-tabs { display: flex; flex-wrap: wrap; gap: 8px; }
.filter-tab { display: inline-flex; align-items: center; gap: 8px; padding: 8px 16px; border: 1px solid var(--line-2); border-radius: 999px; background: transparent; color: var(--ink-2); font-size: 13px; font-weight: 500; transition: all .15s; }
.filter-tab:hover { border-color: var(--ink); color: var(--ink); }
.filter-tab.is-active { background: var(--accent); color: white; border-color: var(--accent); }
.tab-count { font-family: var(--mono); font-size: 10.5px; opacity: 0.7; }
/* Featured */
.featured-card { display: grid; grid-template-columns: 1.25fr 1fr; gap: 0; border: 1px solid var(--line-2); border-radius: 6px; overflow: hidden; background: var(--bg-card); margin-bottom: 60px; transition: transform .2s, box-shadow .2s; }
.featured-card:hover { transform: translateY(-2px); box-shadow: 0 18px 48px -28px rgba(0,0,0,0.35); }
.featured-art { background: var(--bg); border-right: 1px solid var(--line); }
.featured-art > div { aspect-ratio: 4/3; height: 100%; border: 0; border-radius: 0; }
.featured-body { padding: 48px 48px 44px; display: flex; flex-direction: column; gap: 18px; }
.featured-meta { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.badge { display: inline-block; padding: 4px 10px; background: var(--accent-soft); color: var(--accent-deep); font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; border-radius: 3px; }
.badge-sm { padding: 3px 8px; font-size: 10px; }
.meta-sep { color: var(--ink-3); }
.featured-title { font-family: var(--serif); font-size: clamp(28px, 3vw, 42px); margin: 0; line-height: 1.1; font-weight: 500; letter-spacing: -0.01em; text-wrap: balance; }
.featured-dek { font-size: 17px; line-height: 1.6; color: var(--ink-2); margin: 0; max-width: 50ch; }
.featured-cta { font-weight: 600; color: var(--accent); margin-top: auto; padding-top: 18px; border-top: 1px solid var(--line); }
/* Article grid — bigger cards */
.article-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 56px 36px; }
.article-grid.is-compact { grid-template-columns: repeat(3, 1fr); gap: 44px 28px; }
.article-card { display: flex; flex-direction: column; gap: 20px; transition: transform .15s; }
.article-card:hover { transform: translateY(-3px); }
.article-card:hover .card-title { color: var(--accent); }
.card-art { overflow: hidden; border-radius: 8px; }
.card-art > div { transition: transform .4s; aspect-ratio: 4/3; }
.is-compact .card-art > div { aspect-ratio: 4/3; }
.article-card:hover .card-art > div { transform: scale(1.03); }
.card-body { display: flex; flex-direction: column; gap: 12px; }
.card-meta { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.card-title { font-family: var(--serif); font-size: 26px; line-height: 1.18; margin: 0; font-weight: 500; letter-spacing: -0.01em; transition: color .2s; text-wrap: balance; }
.is-compact .card-title { font-size: 22px; }
.card-dek { color: var(--ink-2); font-size: 16px; line-height: 1.6; margin: 0; text-wrap: pretty; }
.is-compact .card-dek { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.card-foot { display: flex; align-items: center; justify-content: space-between; padding-top: 14px; margin-top: auto; border-top: 1px solid var(--line); }
.card-arrow { color: var(--ink-3); font-size: 18px; transition: transform .2s, color .2s; }
.article-card:hover .card-arrow { transform: translateX(4px); color: var(--accent); }
.empty-state { text-align: center; padding: 60px 0; color: var(--ink-3); }
.empty-state p { margin: 0 0 18px; font-family: var(--serif); font-size: 22px; }
/* Pull quote */
.pull { padding: 60px 32px; }
.pull-inner { max-width: 880px; margin: 0 auto; text-align: center; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); padding: 60px 0; position: relative; }
.quote-mark { font-family: var(--serif); font-size: 120px; line-height: 0.6; color: var(--accent); display: block; margin-bottom: -20px; }
.pull-inner p { font-family: var(--serif); font-size: clamp(28px, 3.6vw, 44px); line-height: 1.2; margin: 0 auto 24px; max-width: 26ch; font-weight: 500; letter-spacing: -0.01em; }
.pull-inner em { font-style: italic; color: var(--accent-deep); }
.quote-accent { background: linear-gradient(transparent 65%, var(--accent-soft) 65%); padding: 0 4px; }
.pull-credit { font-family: var(--mono); font-size: 11px; letter-spacing: 0.16em; color: var(--ink-3); }
/* Newsletter — softened red, white CTA for contrast */
.newsletter { background: var(--accent); color: white; max-width: none; padding: 80px 0; margin: 0; position: relative; overflow: hidden; }
.newsletter::before { content: ''; position: absolute; inset: 0; background: radial-gradient(circle at 80% 20%, var(--accent-deep) 0%, transparent 60%); opacity: 0.55; pointer-events: none; }
.newsletter-grid { max-width: 1280px; margin: 0 auto; padding: 0 32px; display: grid; grid-template-columns: 1fr 1fr; gap: 60px; align-items: center; position: relative; z-index: 1; }
.newsletter .section-mono { color: rgba(255,255,255,0.6); }
.newsletter h2 { font-family: var(--serif); font-size: clamp(36px, 4.5vw, 56px); font-weight: 500; margin: 14px 0 18px; letter-spacing: -0.02em; line-height: 1.05; color: white; }
.newsletter p { color: rgba(255,255,255,0.82); font-size: 17px; line-height: 1.55; margin: 0; }
.newsletter-form { display: flex; flex-direction: column; gap: 12px; }
.newsletter-form label { color: rgba(255,255,255,0.6); }
.newsletter-input-row { display: flex; gap: 10px; }
.newsletter-input-row input { flex: 1; padding: 16px 20px; border: 1px solid rgba(255,255,255,0.25); background: rgba(255,255,255,0.08); color: white; border-radius: 999px; font: inherit; font-size: 15px; outline: none; }
.newsletter-input-row input::placeholder { color: rgba(255,255,255,0.5); }
.newsletter-input-row input:focus { border-color: white; background: rgba(255,255,255,0.14); }
/* button on red — white background, accent text, never black */
.btn-on-red { padding: 14px 26px; background: white; color: var(--accent-deep); border: 1px solid white; border-radius: 999px; font-weight: 700; font-size: 14px; transition: transform .15s, background .15s, color .15s; }
.btn-on-red:hover { background: var(--accent-deep); color: white; border-color: var(--accent-deep); transform: translateY(-1px); }
.newsletter-fine { font-size: 12.5px; color: rgba(255,255,255,0.6) !important; }
.newsletter-fine a { color: white; text-decoration: underline; }
/* Footer */
.site-footer { border-top: 1px solid var(--line); background: var(--bg-card); padding: 80px 32px 40px; }
.footer-grid { max-width: 1280px; margin: 0 auto; display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; gap: 48px; }
.footer-grid > div { display: flex; flex-direction: column; gap: 10px; }
.footer-grid strong { font-family: var(--serif); font-size: 17px; margin-bottom: 6px; font-weight: 600; }
.footer-grid a { font-size: 14px; color: var(--ink-2); }
.footer-grid a:hover { color: var(--accent); }
.footer-brand p { font-size: 14px; color: var(--ink-2); line-height: 1.55; margin: 16px 0 0; max-width: 42ch; }
.footer-notice { font-family: var(--mono); font-size: 11px; letter-spacing: 0.1em; color: var(--ink-3); text-transform: uppercase; }
.footer-bottom { max-width: 1280px; margin: 60px auto 0; padding-top: 24px; border-top: 1px solid var(--line); display: flex; justify-content: space-between; flex-wrap: wrap; gap: 16px; }
/* Article & Topic Pages (real inline pages) */
.article-page, .topic-page { background: var(--bg); padding: 32px 0 0; }
.page-inner { max-width: 920px; margin: 0 auto; padding: 0 32px 80px; }
.crumbs { display: flex; align-items: center; gap: 10px; font-family: var(--mono); font-size: 11px; letter-spacing: 0.1em; color: var(--ink-3); text-transform: uppercase; margin: 0 0 36px; flex-wrap: wrap; }
.crumbs a { color: var(--ink-3); }
.crumbs a:hover { color: var(--accent); }
.crumb-sep { opacity: 0.5; }
.crumb-current { color: var(--ink); }
.article-head { margin-bottom: 28px; }
.detail-meta { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 22px; }
.article-title { font-family: var(--serif); font-size: clamp(36px, 5vw, 56px); line-height: 1.05; margin: 0 0 22px; font-weight: 500; letter-spacing: -0.02em; text-wrap: balance; }
.article-dek { font-family: var(--serif); font-size: 22px; line-height: 1.5; color: var(--ink-2); margin: 0; font-style: italic; max-width: 60ch; text-wrap: pretty; }
.article-hero { margin: 36px 0 44px; }
.article-hero > div { aspect-ratio: 16/9; border-radius: 6px; }
.article-body { max-width: 64ch; margin: 0 auto; }
.detail-lede { font-size: 20px; line-height: 1.6; color: var(--ink); font-weight: 500; margin: 0 0 32px; padding-bottom: 28px; border-bottom: 1px solid var(--line); }
.article-body h2 { font-family: var(--serif); font-size: 30px; font-weight: 500; letter-spacing: -0.01em; margin: 40px 0 18px; line-height: 1.2; }
.article-body p { font-size: 17.5px; line-height: 1.75; color: var(--ink-2); margin: 0 0 22px; }
.detail-quote { font-family: var(--serif); font-size: 24px; line-height: 1.4; color: var(--accent-deep); border-left: 3px solid var(--accent); padding: 10px 0 10px 26px; margin: 36px 0; font-style: italic; }
.detail-list { padding-left: 24px; margin: 22px 0 28px; }
.detail-list li { font-size: 16.5px; line-height: 1.65; color: var(--ink-2); margin-bottom: 12px; }
.detail-list li::marker { color: var(--accent); }
.article-related { margin-top: 72px; padding-top: 40px; border-top: 1px solid var(--line); }
.related-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; margin-top: 20px; }
.related-card { display: flex; flex-direction: column; gap: 10px; transition: transform .15s; }
.related-card:hover { transform: translateY(-2px); }
.related-card:hover .related-title { color: var(--accent); }
.related-art { overflow: hidden; border-radius: 4px; }
.related-art > div { aspect-ratio: 3/2; }
.related-meta { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.related-title { font-family: var(--serif); font-size: 17px; line-height: 1.25; margin: 0; font-weight: 500; transition: color .2s; text-wrap: balance; }
.topic-page-head { margin-bottom: 56px; padding-bottom: 36px; border-bottom: 1px solid var(--line); }
.topic-page-head .article-dek { font-style: normal; font-family: var(--sans); font-size: 19px; }
.topic-stats { margin-top: 20px; }
.topic-page-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 44px 32px; }
.back-bar { margin-top: 72px; padding-top: 32px; border-top: 1px solid var(--line); display: flex; justify-content: flex-start; }
@media (max-width: 720px) {
.page-inner { padding: 0 20px 60px; }
.related-grid { grid-template-columns: 1fr; }
.topic-page-grid { grid-template-columns: 1fr; }
}
/* Responsive */
@media (max-width: 1024px) {
.topic-grid { grid-template-columns: repeat(2, 1fr); }
.article-grid, .article-grid.is-compact { grid-template-columns: repeat(2, 1fr); }
.footer-grid { grid-template-columns: 1fr 1fr; }
.hero-grid { grid-template-columns: 1fr; }
.featured-card { grid-template-columns: 1fr; }
.newsletter-grid { grid-template-columns: 1fr; gap: 32px; }
.primary-nav { display: none; }
.hamburger { display: inline-flex; }
.mobile-nav { display: flex; }
}
@media (max-width: 600px) {
.header-inner, .utility-inner { padding-left: 20px; padding-right: 20px; }
.hero, .topics, .latest, .pull { padding-left: 20px; padding-right: 20px; }
.hero { padding-top: 40px; }
.topic-grid { grid-template-columns: 1fr; }
.article-grid, .article-grid.is-compact { grid-template-columns: 1fr; }
.footer-grid { grid-template-columns: 1fr; }
.hero-meta { grid-template-columns: 1fr 1fr 1fr; gap: 12px; }
.featured-body { padding: 28px; }
.util-right { display: none; }
.cta-link { display: none; }
}
`;
ReactDOM.createRoot(document.getElementById('root')).render();