// sections.jsx — Civify landing page sections // Depends on: logo.jsx, app-icons.jsx, i18n.js, frames/ios-frame.jsx, app-screens.jsx, app-lesson.jsx (function () { const { useState, useEffect, useRef } = React; const Icon = window.Icon; // ── live phone preview (reuses the real app) ──────────────── function PhoneHero({ scale = 0.58 }) { // Static, non-interactive preview — the live app lives on the demo page. return ( ); } // ── NAV ───────────────────────────────────────────────────── function Nav({ t, lang, setLang }) { const [open, setOpen] = useState(false); const langs = ['sv', 'en']; return (
{open && (
{langs.map((l) => ( ))}
)}
{t.nav.demo}
); } // ── HERO ──────────────────────────────────────────────────── // Clustered around the phone (upper-right), warm tones to match the hero. const HERO_STARS = [ { top: '4%', left: '60%', s: 13, d: 0, dur: 9, c: '#F0A91E' }, { top: '2%', left: '82%', s: 18, d: 0.7, dur: 10.5, c: '#F2570A' }, { top: '14%', left: '95%', s: 11, d: 1.4, dur: 11, c: '#F0A91E' }, { top: '30%', left: '54%', s: 9, d: 2.1, dur: 9.5, c: '#F7B733' }, { top: '46%', left: '90%', s: 14, d: 0.9, dur: 12, c: '#F0A91E' }, { top: '58%', left: '67%', s: 10, d: 1.7, dur: 11.5, c: '#F2570A' }, { top: '24%', left: '74%', s: 8, d: 2.6, dur: 10, c: '#F7B733' }, ]; function HeroStars() { return ( ); } function Hero({ t }) { return (
{t.hero.eyebrow}

{t.hero.title_a}
{t.hero.title_b}

{t.hero.subtitle}

{t.hero.stat1v}{t.hero.stat1}
{t.hero.stat2v}{t.hero.stat2}
{t.hero.stat3v}{t.hero.stat3}
★ {t.hero.slogan}
); } // ── PROBLEM ───────────────────────────────────────────────── function Problem({ t }) { const P = t.problem; const cards = [ { ic: 'globe', t: P.p1t, b: P.p1b }, { ic: 'layers', t: P.p2t, b: P.p2b }, { ic: 'ballot', t: P.p3t, b: P.p3b }, ]; return (
{P.kicker}

{P.title}

{P.body}

{cards.map((c, i) => (

{c.t}

{c.b}

))}
); } // ── WHAT / SOLUTION ───────────────────────────────────────── function What({ t }) { const W = t.what; const pillars = [ { ic: 'grid', t: W.c1t, b: W.c1b }, { ic: 'trophy', t: W.c2t, b: W.c2b }, { ic: 'globe', t: W.c3t, b: W.c3b }, ]; return (
{W.kicker}

{W.title}

{W.body}

{pillars.map((p, i) => (

{p.t}

{p.b}

))}
); } // ── FEATURES ──────────────────────────────────────────────── function Features({ t }) { const F = t.features; const icons = ['grid', 'flame', 'trophy', 'bolt', 'chart', 'globe']; return (
{F.kicker}

{F.title}

{F.items.map((it, i) => (

{it.t}

{it.b}

))}
); } // ── COURSE CARD (used by Courses) ─────────────────────────── function CourseCard({ course, color, tint }) { const [hovState, setHov] = useState(false); const isTouch = typeof window !== 'undefined' && window.matchMedia && window.matchMedia('(hover: none)').matches; const hov = hovState; // visual lift only on real hover const showDesc = hovState || isTouch; // description always visible on touch return (
setHov(true)} onMouseLeave={() => setHov(false)} style={{ width: 192, flexShrink: 0, scrollSnapAlign: 'start', background: hov ? tint : 'var(--card)', border: `1.5px solid ${hov ? color + '44' : 'var(--line)'}`, borderRadius: 20, padding: '18px 16px 16px', transition: 'transform .2s, box-shadow .2s, background .18s, border-color .18s', transform: hov ? 'translateY(-5px)' : 'none', boxShadow: hov ? `0 22px 40px -18px ${color}44` : '0 2px 8px -4px rgba(100,60,10,.06)', cursor: 'default', position: 'relative', }} >
{course.title}
{course.desc}
Kommer snart
); } // ── COURSE RAIL (one level row: swipe + arrows + dots) ────── const CARD_W = 192, CARD_GAP = 14, STEP = CARD_W + CARD_GAP; function CourseRail({ lvl, padX }) { const railRef = useRef(null); const [active, setActive] = useState(0); const [pages, setPages] = useState(1); const [overflow, setOverflow] = useState(false); // page-based metrics: how many cards are visible → how many "pages" exist const metrics = () => { const el = railRef.current; if (!el) return null; const visible = Math.max(1, Math.floor(el.clientWidth / STEP)); const step = visible * STEP; const total = Math.max(1, Math.ceil(lvl.courses.length / visible)); const max = el.scrollWidth - el.clientWidth; return { el, step, total, max }; }; const measure = () => { const m = metrics(); if (!m) return; setPages(m.total); setOverflow(m.max > 4); const a = m.max - m.el.scrollLeft <= 2 ? m.total - 1 : Math.round(m.el.scrollLeft / m.step); setActive(Math.min(m.total - 1, Math.max(0, a))); }; useEffect(() => { measure(); window.addEventListener('resize', measure); return () => window.removeEventListener('resize', measure); }, []); const goToPage = (p) => { const m = metrics(); if (!m) return; const idx = Math.max(0, Math.min(m.total - 1, p)); const to = Math.min(idx * m.step, m.max); const el = m.el; if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) { el.scrollLeft = to; return; } // manual tween — native smooth scroll gets cancelled by scroll-snap here const from = el.scrollLeft; const start = performance.now(); const dur = 360; const ease = (t) => 1 - Math.pow(1 - t, 3); const frame = (now) => { const k = Math.min(1, (now - start) / dur); el.scrollLeft = from + (to - from) * ease(k); if (k < 1) requestAnimationFrame(frame); }; requestAnimationFrame(frame); }; const renderArrow = (dir) => { const disabled = dir < 0 ? active <= 0 : active >= pages - 1; return ( ); }; return (
{lvl.n} Nivå {lvl.n} · {lvl.name} {overflow && ( {renderArrow(-1)} {renderArrow(1)} )} Svep för fler
{lvl.courses.map((c, i) => ( ))}
{overflow && pages > 1 && (
{Array.from({ length: pages }).map((_, i) => (
)}
); } // ── COURSES ────────────────────────────────────────────────── function Courses() { const levels = [ { n: 1, name: 'Grunderna', color: '#2563EB', tint: '#EFF6FF', courses: [ { title: 'Europas geografi', icon: 'globe', desc: 'Lär dig om Europas länder, gränser och geografi – från Atlanten till Svartahavet.' }, { title: 'Europas historia', icon: 'layers', desc: 'Från antiken till vår tid – de händelser som formade en kontinent.' }, { title: 'Vad är EU?', icon: 'ballot', desc: 'EU:s grundläggande syfte, värderingar och hur det hela började.' }, { title: 'EU:s institutioner',icon: 'grid', desc: 'Parlamentet, rådet, kommissionen – vem beslutar om vad?' }, ]}, { n: 2, name: 'Hur EU fungerar', color: '#7C3AED', tint: '#F5F3FF', courses: [ { title: 'Inre marknaden', icon: 'chart', desc: 'Fri rörlighet för varor, tjänster, kapital och personer.' }, { title: 'Schengen', icon: 'globe', desc: 'Hur gränsfritt resande fungerar och vilka länder som ingår.' }, { title: 'Eurozonen', icon: 'bolt', desc: 'Den gemensamma valutan euro och hur den påverkar dig.' }, { title: 'Budget och ekonomi',icon: 'layers', desc: 'Hur EU:s budget sätts och vad pengarna faktiskt går till.' }, ]}, { n: 3, name: 'Medborgaren', color: '#059669', tint: '#ECFDF5', courses: [ { title: 'Dina rättigheter', icon: 'shield', desc: 'Vilka rättigheter du har som EU-medborgare – och hur du använder dem.' }, { title: 'Studera och arbeta i EU', icon: 'trophy', desc: 'Erasmus, arbetsrätt och hur du tar tillvara på EU:s möjligheter.' }, { title: 'Resa i Europa', icon: 'globe', desc: 'Reserätt, sjukvård utomlands och praktiska tips.' }, ]}, { n: 4, name: 'Europas framtid', color: '#D97706', tint: '#FFFBEB', courses: [ { title: 'Klimat och energi', icon: 'flame', desc: 'EU:s klimatmål, Green Deal och omställningen till ren energi.' }, { title: 'Säkerhet och försvar', icon: 'shield', desc: 'EU:s roll i säkerhetspolitiken och samarbetet med NATO.' }, { title: 'Utvidgning och kandidatländer', icon: 'globe', desc: 'Vilka länder vill gå med i EU och hur processen ser ut.' }, ]}, { n: 5, name: 'Mästarutmaningar', color: '#DC2626', tint: '#FEF2F2', courses: [ { title: 'Flaggor', icon: 'ballot', desc: 'Testa om du kan identifiera alla EU-länders flaggor.' }, { title: 'Huvudstäder', icon: 'globe', desc: 'Para ihop varje land med sin huvudstad.' }, { title: 'EU-quiz', icon: 'trophy', desc: 'Det stora kunskapstestet om allt du lärt dig i Civify.' }, { title: 'Historiska händelser', icon: 'layers', desc: 'Viktiga datum och vändpunkter i Europas historia.' }, { title: 'Fördrag och institutioner', icon: 'grid', desc: 'Maastricht, Lissabon och andra avgörande fördrag.' }, ]}, ]; const padX = 'max(32px, calc((100vw - 1148px) / 2))'; return (
Kurskatalog

Hela kursvägen

Civify byggs ut i fem nivåer – från grunderna till mästarutmaningar. Idag finns EU:s institutioner tillgänglig i demon. Resten av kurserna nedan planeras och kommer att läggas till framöver.

Under utveckling
{levels.map((lvl) => ( ))}
); } // ── PARTNERS (dark) ───────────────────────────────────────── function Partners({ t }) { const P = t.partners; const cards = [ { ic: 'person', t: P.p1t, b: P.p1b }, { ic: 'chart', t: P.p2t, b: P.p2b }, { ic: 'shield', t: P.p3t, b: P.p3b }, ]; return (
{P.kicker}

{P.title}

{P.body}

{cards.map((c, i) => (

{c.t}

{c.b}

))}
{P.cta} e.currentTarget.style.color='rgba(255,255,255,0.9)'} onMouseLeave={e => e.currentTarget.style.color='rgba(255,255,255,0.55)'} >Civifier@gmail.com
); } // ── CTA ───────────────────────────────────────────────────── function CTASection({ t }) { const C = t.cta; return (

{C.title}

{C.body}

{C.primary} {C.secondary}
); } // ── FOOTER ────────────────────────────────────────────────── function Footer({ t, lang, setLang }) { const F = t.footer; return ( ); } Object.assign(window, { CivifyNav: Nav, CivifyHeroSec: Hero, CivifyProblem: Problem, CivifyWhat: What, CivifyFeatures: Features, CivifyCourses: Courses, CivifyPartners: Partners, CivifyCTA: CTASection, CivifyFooter: Footer }); })();