diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d0479b..f9b0404 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +### Added — deltag.aarhus.dk Project +- Hearing detail page prototype for Aarhus Kommune's citizen participation platform +- Interactive mock with 784 simulated hearing responses, filtering, sorting, MitID login, submission form +- Variant switching between open and closed hearing states +- Statistics (SVG line chart, category breakdown), interactive map, glossary tooltips +- Editor content requirements document (redaktionelt indhold) + ### Changed - Migrated from MkDocs + Material theme to VitePress - Adopted ITK Dev brand colors (teal/cyan) replacing neutral black/grey palette diff --git a/docs/.vitepress/sidebar.mts b/docs/.vitepress/sidebar.mts index d56362e..ae640dd 100644 --- a/docs/.vitepress/sidebar.mts +++ b/docs/.vitepress/sidebar.mts @@ -37,11 +37,23 @@ const agenticOrchestration: DefaultTheme.SidebarItem[] = [ }, ] +const deltagAarhus: DefaultTheme.SidebarItem[] = [ + { + text: 'deltag.aarhus.dk', + items: [ + { text: 'Overview', link: '/projects/deltag-aarhus/' }, + { text: 'Redaktionelt indhold', link: '/projects/deltag-aarhus/editor-content-requirements' }, + { text: 'Interactive Mocks', link: '/projects/deltag-aarhus/mocks' }, + ], + }, +] + export function sidebar(): DefaultTheme.Sidebar { return { '/projects/climate-nudging/': climateNudging, '/projects/salary-negotiation/': salaryNegotiation, '/projects/agentic-orchestration/': agenticOrchestration, + '/projects/deltag-aarhus/': deltagAarhus, } } diff --git a/docs/index.md b/docs/index.md index d4bb0c1..8bcd234 100644 --- a/docs/index.md +++ b/docs/index.md @@ -18,4 +18,8 @@ features: details: Can we build a shared, open-source platform for mapping, automating, orchestrating, and measuring business processes? link: /projects/agentic-orchestration/ linkText: View project + - title: deltag.aarhus.dk + details: Prototype af en høringsdetalje-side til Aarhus Kommunes borgerdeltagelsesplatform — med 784 simulerede høringssvar, MitID-login og variantskift. + link: /projects/deltag-aarhus/ + linkText: View project --- diff --git a/docs/projects/deltag-aarhus/editor-content-requirements.md b/docs/projects/deltag-aarhus/editor-content-requirements.md new file mode 100644 index 0000000..f732cb6 --- /dev/null +++ b/docs/projects/deltag-aarhus/editor-content-requirements.md @@ -0,0 +1,156 @@ +# Redaktionelt indhold — Høringsdetalje side + +Oversigt over indhold som redaktører skal bidrage med per høring, samt hvad der kan genereres automatisk fra eksisterende data. + +## Oversigt + +| Sektion | Redaktør | Automatisk | Bemærkning | +|---------|:--------:|:----------:|------------| +| Hero-billede | X | | Billede skal uploades per høring | +| Afgørelsesbanner | X | delvist | Redaktør skriver tekst; dato kan trækkes fra dagsordensystem | +| Afgørelsesdialog | X | delvist | Redaktør skriver resumé; links til referat/video kan trækkes fra byrådets dagsordensystem | +| Overskrift og manchet | X | | Fritekst per høring | +| Metadata (svarfrist, type, ID) | | X | Findes i høringsdata | +| Tags/kategorier | X | | Redaktør vælger fra taksonomi | +| Brødtekst | X | | Fritekst med mulighed for ordforklaringer | +| Ordforklaringer (glossary) | X | delvist | Redaktør markerer termer; forklaringstekst kan trækkes fra central ordliste | +| Materialer | X | | Redaktør uploader filer og angiver navne | +| HTML-forhåndsvisning af materialer | | X | Kan genereres automatisk fra uploadet PDF | +| Svarfrist og slettedato | | X | Findes i høringsdata | +| Kontaktoplysninger | X | delvist | Redaktør vælger afdeling; kontaktdata kan trækkes fra kontaktregister | +| Høringssvar | | X | Indsendt af borgere via formularen | +| Høringssvar-kategorier | delvist | delvist | Kategori-taksonomi vedligeholdes af redaktør; tildeling kan evt. automatiseres med AI | +| Kommentarer | | X | Indsendt af borgere | +| Statistik — linjediagram | | X | Beregnes automatisk fra indkomne svar | +| Statistik — kategorifordeling | | X | Beregnes automatisk fra kategoriserede svar | +| Geografisk fordeling (kort) | | X | Beregnes fra afsenderadresse/postnummer | +| Info-boks (Mere viden) | X | | Redaktør vælger links fra vidensbase | +| Relaterede aktiviteter | X | delvist | Redaktør knytter relationer; billeder og metadata trækkes automatisk | +| Projekter CTA | | X | Global komponent — konfigureres én gang | +| Footer | | X | Global komponent | + +--- + +## Detaljer per sektion + +### 1. Hero-billede + +**Redaktøropgave:** Upload et billede der repræsenterer høringsområdet. + +- Anbefalet størrelse: 1600 x 500 px +- Alt-tekst skal udfyldes + +### 2. Afgørelsesbanner og -dialog + +**Redaktøropgave:** +- Kort bannerbesked (1 sætning) med mødedato og udfald +- Resumé af afgørelsen (2-3 afsnit) til afgørelsesdialogen + +**Automatisk:** +- Mødedato kan trækkes fra byrådets dagsordensystem +- Link til referat (PDF) og video kan trækkes automatisk, hvis byrådets system har en API +- Link til vedtaget lokalplan kan genereres fra dokumentsystemet + +**Redaktøren skal levere:** +- Link til hvidbog (behandling af høringssvar) — dette dokument produceres manuelt +- Eventuelle tilpasninger af resuméteksten + +### 3. Overskrift, manchet og brødtekst + +**Redaktøropgave:** Fritekst. Ingen automatisering mulig — dette er det primære redaktionelle indhold. + +### 4. Ordforklaringer (glossary) + +**Redaktøropgave:** Markere fagtermer i brødteksten der skal have en forklaring. + +**Automatisk:** +- Forklaringstekst og "læs mere"-link kan trækkes fra en **central ordliste/taksonomi** i CMS +- Systemet kan evt. automatisk foreslå termer der bør markeres (baseret på ordlisten) +- Kræver oprettelse og vedligeholdelse af en ordliste med fagtermer og forklaringer + +**Vedligeholdelse af ordliste:** +- Ordlisten bør forvaltes centralt (ikke per høring) +- Hver term: navn, kort forklaring (maks 2 sætninger), link til uddybende side +- Eksempler: omdannelsesområde, boligbebyggelse, kommuneplan, rammeområde, bebyggelsesprocent, LAR-løsning + +### 5. Materialer + +**Redaktøropgave:** +- Upload dokumenter (PDF, billeder) +- Angiv dokumentnavn +- Vælg om dokumentet skal have HTML-forhåndsvisning + +**Automatisk:** +- Filtype og størrelse detekteres automatisk +- HTML-forhåndsvisning kan genereres automatisk fra PDF (kræver PDF-til-HTML konvertering i backend) +- Ikon vælges automatisk ud fra filtype + +### 6. Kontaktoplysninger + +**Redaktøropgave:** Vælg ansvarlig afdeling fra liste. + +**Automatisk:** +- Adresse, telefon og e-mail trækkes fra kontaktregister baseret på valgt afdeling + +### 7. Høringssvar og kommentarer + +**Fuldt automatisk.** Borgere indsender via formularen. Kræver ingen redaktørindsats. + +**Redaktøropgave (valgfri):** +- Moderation af upassende indhold +- Tildeling af kategori til høringssvar (hvis ikke borgeren selv vælger) + +### 8. Statistik + +**Fuldt automatisk.** Alle tre statistiksektioner (linjediagram, kategorifordeling, kort) genereres fra de indkomne høringssvar. + +**Forudsætninger:** +- Linjediagram: hvert svar har en indsendt-dato → aggregeres per dag +- Kategorifordeling: hvert svar har en kategori → aggregeres per kategori +- Kort: hvert svar har en afsender-lokation (postnummer eller adresse) → geokodes og vises + +### 9. Info-boks (Mere viden) + +**Redaktøropgave:** Vælg 2-4 links til relevante sider fra vidensbasen. + +**Mulighed for automatisering:** Links kan foreslås automatisk baseret på høringstype (fx "lokalplan" → vis altid links om lokalplanprocessen). + +### 10. Relaterede aktiviteter + +**Redaktøropgave:** Knyt høringen til relaterede dialoger, høringer eller aktiviteter. + +**Automatisk:** +- Billede, titel, type, dato, svar-antal og lokation trækkes fra den relaterede aktivitets egen data +- Redaktøren behøver kun at vælge relationen — resten vises automatisk + +--- + +## Systembehov for automatisering + +For at maksimere den automatiske indholdsgenerering kræves: + +| System / data | Bruges til | +|---------------|------------| +| Byrådets dagsordensystem (API) | Afgørelsesdato, referat-PDF, video-link | +| Dokumentsystem | Vedtagne lokalplaner, materialefiler | +| Central ordliste i CMS | Glossary-forklaringer og links | +| Kontaktregister | Afdelingsoplysninger | +| PDF-til-HTML konvertering | Forhåndsvisning af materialer | +| Geokodning af postnumre | Geografisk kort over høringssvar | +| Aktivitets-/høringsregister | Relaterede aktiviteter, metadata | + +--- + +## Estimeret redaktørtid per høring + +| Opgave | Estimat | +|--------|---------| +| Hero-billede og metadata | 5 min | +| Overskrift, manchet, brødtekst | 30-60 min | +| Markering af glossary-termer | 5-10 min | +| Upload af materialer | 10-15 min | +| Afgørelsestekst (efter vedtagelse) | 15-20 min | +| Relationer og info-links | 5 min | +| **Total** | **ca. 1-2 timer** | + +Resten genereres automatisk fra data. diff --git a/docs/projects/deltag-aarhus/index.md b/docs/projects/deltag-aarhus/index.md new file mode 100644 index 0000000..a29968a --- /dev/null +++ b/docs/projects/deltag-aarhus/index.md @@ -0,0 +1,82 @@ +**Project:** deltag.aarhus.dk · **Status:** Draft · **Date:** April 2026 + +# deltag.aarhus.dk — Høringsdetalje + +**Prototype af en høringsdetalje-side til Aarhus Kommunes borgerdeltagelsesplatform.** + +--- + +## Baggrund + +deltag.aarhus.dk skal give borgere mulighed for at deltage i offentlige høringer, dialoger og andre demokratiske processer. Denne prototype demonstrerer en høringsdetalje-side — den side borgerne ser når de klikker ind på en konkret høring. + +Prototypen bruger et realistisk scenarie: en høring om vindmøller ved Vosnæs (Lokalplan nr. 1237 med miljøvurderingsrapport), med 784 simulerede høringssvar. + +--- + +## Hvad prototypen viser + +### Variantskift (åben/afsluttet) + +Prototypen kan skifte mellem to tilstande via mock-banneret: + +- **Åben høring** — borgere kan indsende høringssvar og kommentere andres svar +- **Afsluttet høring** — svarfristen er udløbet, "Skriv et høringssvar"-knappen er deaktiveret, og afgørelsesbannerets vises + +### Høringssvar + +- 784 simulerede høringssvar i et responsivt 4-kolonne grid +- Filtrering efter kategori (Miljø/Natur, Støj/Sundhed, Landskab/Visuel, Proces/Andet) +- Sortering (Nyeste, Ældste, Flest synes om, Flest kommentarer) +- Kontinuerlig "Vis flere"-indlæsning (16 ad gangen) +- Modal med fuldt svarindhold, kommentarer og svar-formuler +- Tastaturnavigation (piletaster, Escape) +- URL-dyblink til individuelt svar (`#svar-{id}`) + +### MitID login og indsendelse + +- Mock MitID-loginflow (åben variant) +- Indsendelsesformular med kategori, titel og brødtekst +- Kommentarformular på individuelle høringssvar + +### Statistik og kort + +- SVG-linjediagram over indsendte svar pr. dag (10-ugers høringsperiode) +- Kategorifordeling +- Interaktivt kort (Leaflet.js) med Vosnæs-koordinater + +### Øvrige features + +- Ordforklaringer (glossary) med tooltips på fagtermer +- Afgørelsesmodal med mødereferat, video og hvidbog +- Materialeliste med dokumentforhåndsvisning +- Relaterede aktiviteter (høringer og dialoger) +- Fuldt responsivt layout (mobil, tablet, desktop) +- Tilgængelig: ARIA-attributter, fokusstyring, tastaturnavigation + +--- + +## Teknisk opbygning + +Prototypen er vanilla HTML/CSS/JS uden build-trin: + +- **HTML:** Én `index.html` med semantisk markup +- **CSS:** 24 komponent-filer + designtokens med 93 custom properties +- **JS:** 15 moduler med centraliseret state management (`window.DeltagMock.state`) +- **Data:** 784 procedurelt genererede høringssvar med 15 kommentarer hver + +--- + +## Redaktionelt indhold + +Se [Redaktionelt indhold — Høringsdetalje side](editor-content-requirements.md) for en oversigt over hvad redaktører skal levere per høring, og hvad der kan genereres automatisk. + +**Estimat:** ca. 1–2 timer redaktørtid per høring (efter systemintegrationer er bygget). + +--- + +## Interaktiv prototype + +Åbn prototypen ↗ + +Prøv at skifte mellem åben og afsluttet variant via linkene i det blå mock-banner øverst. diff --git a/docs/projects/deltag-aarhus/mocks.md b/docs/projects/deltag-aarhus/mocks.md new file mode 100644 index 0000000..f01cac1 --- /dev/null +++ b/docs/projects/deltag-aarhus/mocks.md @@ -0,0 +1,10 @@ +**Project:** deltag.aarhus.dk + +# Interaktive Mocks + +Interaktive HTML-prototyper der demonstrerer den foreslåede høringsdetalje-side. Åbn dem for at afprøve interaktionerne direkte i browseren. + +--- + +**Høringsdetalje — Vindmøller ved Vosnæs ↗** +Komplet høringsdetalje-side med 784 simulerede høringssvar, filtrering, sortering, MitID-login, indsendelsesformular, statistik, kort, ordforklaringer og afgørelsesmodal. Skift mellem åben og afsluttet variant via mock-banneret. diff --git a/docs/projects/salary-negotiation/estimeringsnotat.md b/docs/projects/salary-negotiation/estimeringsnotat.md index 75dee56..f68f49d 100644 --- a/docs/projects/salary-negotiation/estimeringsnotat.md +++ b/docs/projects/salary-negotiation/estimeringsnotat.md @@ -1,6 +1,6 @@ --- protected: true -passwordHash: "9bf67668583e1070cfecddfca89e1e67fcd583dfb43c3ea79ec63ecf598136ae" +passwordHash: "92b0332dda228edf8ebd0208937b772f02625380fbb04a63d90b97662b183661" passwordGroup: "salary-negotiation" --- diff --git a/docs/projects/salary-negotiation/index.md b/docs/projects/salary-negotiation/index.md index 76fcc50..e35dbc2 100644 --- a/docs/projects/salary-negotiation/index.md +++ b/docs/projects/salary-negotiation/index.md @@ -1,7 +1,7 @@ --- title: Lønforhandlingssystem protected: true -passwordHash: "9bf67668583e1070cfecddfca89e1e67fcd583dfb43c3ea79ec63ecf598136ae" +passwordHash: "92b0332dda228edf8ebd0208937b772f02625380fbb04a63d90b97662b183661" passwordGroup: "salary-negotiation" --- diff --git a/docs/projects/salary-negotiation/mocks.md b/docs/projects/salary-negotiation/mocks.md index a35b17f..1b46695 100644 --- a/docs/projects/salary-negotiation/mocks.md +++ b/docs/projects/salary-negotiation/mocks.md @@ -1,6 +1,6 @@ --- protected: true -passwordHash: "9bf67668583e1070cfecddfca89e1e67fcd583dfb43c3ea79ec63ecf598136ae" +passwordHash: "92b0332dda228edf8ebd0208937b772f02625380fbb04a63d90b97662b183661" passwordGroup: "salary-negotiation" --- diff --git a/docs/public/projects/deltag-aarhus/mocks/AAK_02_hoejrejusteret_dark.svg b/docs/public/projects/deltag-aarhus/mocks/AAK_02_hoejrejusteret_dark.svg new file mode 100644 index 0000000..df0d51f --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/AAK_02_hoejrejusteret_dark.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/projects/deltag-aarhus/mocks/AAK_02_venstrejusteret_hvid.svg b/docs/public/projects/deltag-aarhus/mocks/AAK_02_venstrejusteret_hvid.svg new file mode 100644 index 0000000..aad9e73 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/AAK_02_venstrejusteret_hvid.svg @@ -0,0 +1,30 @@ + + + AAK_02_venstrejusteret_hvid + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/projects/deltag-aarhus/mocks/AAK_02_venstrejusteret_sh.svg b/docs/public/projects/deltag-aarhus/mocks/AAK_02_venstrejusteret_sh.svg new file mode 100644 index 0000000..b75cf6b --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/AAK_02_venstrejusteret_sh.svg @@ -0,0 +1,30 @@ + + + AAK_02_venstrejusteret_sh + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/projects/deltag-aarhus/mocks/css/base.css b/docs/public/projects/deltag-aarhus/mocks/css/base.css new file mode 100644 index 0000000..05daa6c --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/base.css @@ -0,0 +1,109 @@ +/* Global reset, base element styles, and utility classes. + Container patterns used across components: + - Centered: max-width: var(--container-max); margin: 0 auto; padding-inline: 116px; + - Edge-aware: margin-inline: max(40px, calc((100% - var(--container-max)) / 2)); + The second pattern guarantees minimum 40px side margin at any viewport width. */ + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + font-size: 16px; + -webkit-text-size-adjust: 100%; + scroll-behavior: smooth; +} + +body { + font-family: var(--font-family); + font-size: var(--fs-base); + font-weight: var(--fw-regular); + line-height: var(--lh-body); + color: var(--text-primary); + background-color: var(--bg-primary); + letter-spacing: 0.05em; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body.modal-open { + overflow: hidden; +} + +img { + display: block; + max-width: 100%; + height: auto; +} + +a { + color: var(--text-link); + text-decoration: underline; + transition: color var(--transition-fast); +} + +a:hover { + color: var(--text-link-hover); +} + +button { + font-family: inherit; + font-size: inherit; + cursor: pointer; + border: none; + background: none; +} + +h1, h2, h3, h4, h5, h6 { + font-weight: var(--fw-bold); + line-height: var(--lh-heading); + color: var(--text-primary); +} + +p { + max-width: 45rem; +} + +ul, ol { + list-style: none; +} + +:focus-visible { + outline: 2px solid var(--petroleum); + outline-offset: 2px; +} + +/* Layout utilities */ + +.container { + max-width: var(--container-max); + margin-inline: auto; + padding-inline: calc(var(--spacer) * 1.5); +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/body-section.css b/docs/public/projects/deltag-aarhus/mocks/css/body-section.css new file mode 100644 index 0000000..e3c6b4d --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/body-section.css @@ -0,0 +1,100 @@ +/* Body Section — main content area with two-column grid layout and contact card sidebar */ + +.body-section { + max-width: var(--container-max); + /* Edge-aware centering; switches to margin: 0 auto at 960px when sidebar stacks */ + margin: 0 max(40px, calc((100% - var(--container-max)) / 2)); + padding: 48px 116px 0; + display: grid; + grid-template-columns: 1fr auto; + gap: 60px; +} + +.body-section__content { + display: flex; + flex-direction: column; + gap: 32px; +} + +.body-section__text { + font-size: var(--fs-base); + line-height: 24px; +} + +.body-section__text p + p { + margin-top: 16px; +} + +.body-section__dates { + display: flex; + gap: 32px; + font-family: var(--font-family-inter); + font-size: var(--fs-base); + color: #474747; +} + +.body-section__dates strong { + color: var(--black); + font-weight: var(--fw-regular); +} + +/* Contact Card */ +.contact-card { + background: var(--petroleum-100); + border-bottom: 2px solid var(--petroleum-200); + border-radius: 3px; + padding: 40px; + overflow: hidden; + display: flex; + flex-direction: column; + gap: 16px; + width: 340px; + align-self: start; +} + +.contact-card__department { + font-size: var(--fs-small); + font-weight: var(--fw-bold); + color: var(--petroleum); +} + +.contact-card__name { + font-size: 18px; + font-weight: var(--fw-bold); +} + +.contact-card__details { + font-size: var(--fs-base); + display: flex; + flex-direction: column; + gap: 12px; + line-height: 1.5; +} + +.contact-card__details a { + color: var(--text-primary); +} + +@media (max-width: 1200px) { + .body-section { + padding: 48px 40px 0; + } +} + +@media (max-width: 960px) { + .body-section { + grid-template-columns: 1fr; + margin: 0 auto; + padding: 32px 40px 0; + } + + .contact-card { + width: 100%; + } +} + +@media (max-width: 640px) { + .body-section { + padding: 32px 16px 0; + } +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/decision-banner.css b/docs/public/projects/deltag-aarhus/mocks/css/decision-banner.css new file mode 100644 index 0000000..6ba6772 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/decision-banner.css @@ -0,0 +1,44 @@ +/* Decision Banner — yellow notification banner for hearing decisions */ + +.decision-banner { + max-width: var(--container-max); + /* Pulls banner up over the hero image; edge-aware centering guarantees 40px minimum side margin */ + margin: -60px max(40px, calc((100% - var(--container-max)) / 2)) 0; + position: relative; + z-index: 1; + background: var(--yellow-50); + border-bottom: 8px solid var(--yellow-100); + border-radius: 4px; + padding: 16px 116px 24px; +} + +.decision-banner__title { + font-size: 24px; + font-weight: var(--fw-regular); + line-height: 36px; + color: var(--black); +} + +.decision-banner__text { + font-family: var(--font-family-inter); + font-size: var(--fs-base); + line-height: 24px; + color: var(--black); +} + +.decision-banner__link { + color: var(--black); +} + +@media (max-width: 1200px) { + .decision-banner { + padding: 16px 60px 24px; + } +} + +@media (max-width: 640px) { + .decision-banner { + padding: 16px 24px 24px; + margin-inline: 16px; + } +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/footer.css b/docs/public/projects/deltag-aarhus/mocks/css/footer.css new file mode 100644 index 0000000..fc2668f --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/footer.css @@ -0,0 +1,78 @@ +/* Footer — site footer with three-column layout, contact info, and bottom links */ + +.footer { + background: var(--gray-900); + color: var(--white); + padding: 48px 0 0; + position: relative; +} + +.footer__inner { + max-width: var(--container-max); + margin: 0 auto; + padding: 0 135px; + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 48px; +} + +.footer__col-title { + font-size: 18px; + font-weight: var(--fw-bold); + margin-bottom: 24px; +} + +.footer__text { + font-size: var(--fs-small); + line-height: 20px; +} + +.footer__text + .footer__text { + margin-top: 8px; +} + +.footer__phone { + font-size: var(--fs-base); + font-weight: var(--fw-bold); + text-transform: uppercase; + margin-top: 24px; +} + +.footer__logo { + margin-top: 32px; +} + +.footer__logo svg { + height: 40px; + width: auto; + fill: var(--white); +} + +.footer__bottom { + max-width: var(--container-max); + margin: 0 auto; + padding: 32px 135px; + border-top: 1px solid var(--gray-600); + margin-top: 32px; +} + +.footer__bottom-links { + font-size: var(--fs-small); + color: var(--gray-600); +} + +.footer__bottom-links a { + color: var(--gray-600); + text-decoration: none; +} + +.footer__bottom-links a:hover { + color: var(--white); +} + +@media (max-width: 960px) { + .footer__inner { + grid-template-columns: 1fr; + padding: 0 24px; + } +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/glossary.css b/docs/public/projects/deltag-aarhus/mocks/css/glossary.css new file mode 100644 index 0000000..fb8653b --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/glossary.css @@ -0,0 +1,55 @@ +/* Glossary — inline term definitions with tooltip popups */ + +.glossary-term { + position: relative; + display: inline; + border-bottom: 1px dashed var(--petroleum); +} + +.glossary-term__icon { + display: inline-flex; + align-items: center; + justify-content: center; + background: none; + border: none; + cursor: pointer; + color: var(--petroleum); + font-size: 14px; + padding: 0 2px; + vertical-align: super; + line-height: 1; +} + +.glossary-term__icon:hover { + color: var(--petroleum-800); +} + +.glossary-term__tooltip { + display: none; + position: absolute; + bottom: calc(100% + 8px); + left: 50%; + transform: translateX(-50%); + background: var(--white); + border: 1px solid var(--border-default); + box-shadow: 0 4px 16px rgb(0 0 0 / 12%); + padding: 16px; + width: 320px; + font-family: var(--font-family-inter); + font-size: var(--fs-small); + line-height: 20px; + color: var(--text-primary); + z-index: 100; + border-radius: 8px; +} + +.glossary-term__tooltip a { + display: block; + margin-top: 8px; + color: var(--petroleum); + font-weight: var(--fw-semibold); +} + +.glossary-term--open > .glossary-term__tooltip { + display: block; +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/hearing-header.css b/docs/public/projects/deltag-aarhus/mocks/css/hearing-header.css new file mode 100644 index 0000000..4a3c2fd --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/hearing-header.css @@ -0,0 +1,150 @@ +/* Hearing Header — teal header block with title, lead text, and metadata sidebar */ + +.hearing-header { + max-width: var(--container-max); + /* Edge-aware centering: auto-centers above container-max, 40px minimum below */ + margin: 24px max(40px, calc((100% - var(--container-max)) / 2)) 0; + position: relative; + z-index: 1; + background: var(--petroleum-100); + border-bottom: 8px solid var(--petroleum-200); + border-radius: 4px; + padding: 60px 116px; + display: flex; + flex-direction: column; + gap: 24px; +} + +.hearing-header__label { + font-size: var(--fs-small); + font-weight: var(--fw-bold); + color: var(--petroleum); +} + +.hearing-header__content { + display: flex; + /* Matches design comp spacing */ + gap: 27px; +} + +.hearing-header__main { + flex: 1; + display: flex; + flex-direction: column; + gap: 27px; + /* Constrains text column width per design grid */ + max-width: 734px; +} + +.hearing-header__title { + font-size: 40px; + font-weight: var(--fw-bold); + line-height: 1.2; +} + +.hearing-header__lead { + font-size: 24px; + line-height: 36px; + color: var(--text-primary); +} + +.hearing-header__sidebar { + width: 208px; + flex-shrink: 0; + border-left: 2px solid var(--petroleum-200); + display: flex; + flex-direction: column; + gap: 27px; +} + +.hearing-header__meta-item { + padding-left: 24px; +} + +.hearing-header__meta-label { + display: flex; + gap: 8px; + align-items: center; + color: var(--petroleum-muted); + font-family: var(--font-family-inter); + font-size: var(--fs-base); + line-height: 24px; +} + +.hearing-header__meta-label i { + width: 16px; + text-align: center; + line-height: 1; +} + +.hearing-header__meta-value { + font-family: var(--font-family-inter); + font-size: var(--fs-base); + font-weight: var(--fw-semibold); + line-height: 24px; + padding-left: 24px; + margin-top: 2px; +} + +/* Project link in sidebar */ +.hearing-header__meta-link { + color: var(--petroleum); + text-decoration: underline; + font-weight: var(--fw-semibold); +} + +.hearing-header__meta-link:hover { + color: var(--petroleum-800); +} + +.hearing-header__tags { + font-family: var(--font-family-inter); + font-size: var(--fs-base); + font-weight: var(--fw-semibold); + color: var(--petroleum-muted); + line-height: 24px; +} + +@media (max-width: 1200px) { + .hearing-header { + padding: 40px 60px; + } +} + +@media (max-width: 960px) { + .hearing-header__content { + flex-direction: column; + } + + .hearing-header__sidebar { + width: 100%; + border-left: none; + border-top: 2px solid var(--petroleum-200); + padding-top: 24px; + flex-direction: row; + flex-wrap: wrap; + } + + .hearing-header__meta-item { + padding-left: 0; + } +} + +@media (max-width: 640px) { + .hearing-header { + padding: 24px; + } + + .hearing-header__title { + font-size: 28px; + } + + .hearing-header__lead { + font-size: 18px; + line-height: 28px; + } + + .hearing-header { + margin-inline: 16px; + } +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/hero.css b/docs/public/projects/deltag-aarhus/mocks/css/hero.css new file mode 100644 index 0000000..6fabf67 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/hero.css @@ -0,0 +1,14 @@ +/* Hero — full-width hero image section */ + +.hero { + width: 100%; + height: 500px; + overflow: hidden; + position: relative; +} + +.hero__image { + width: 100%; + height: 100%; + object-fit: cover; +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/horingssvar.css b/docs/public/projects/deltag-aarhus/mocks/css/horingssvar.css new file mode 100644 index 0000000..811faf2 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/horingssvar.css @@ -0,0 +1,292 @@ +/* Horingssvar — hearing responses section with card grid, dropdowns, pagination, and submit button */ + +.horingssvar-section { + max-width: var(--container-max); + margin: 0 auto; + padding: 0 116px 48px; +} + +.horingssvar-section__header { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 16px; + margin-bottom: 24px; +} + +.horingssvar-section__title { + font-size: var(--fs-teaser); + font-weight: var(--fw-bold); + margin-right: auto; +} + +.horingssvar-section__controls { + display: flex; + align-items: center; + gap: 32px; + flex-wrap: wrap; + width: 100%; + margin-bottom: 32px; +} + +/* Dropdown */ +.dropdown { + position: relative; + display: inline-block; +} + +.dropdown__button { + display: flex; + align-items: center; + gap: 10px; + font-size: 20px; + color: var(--black); + padding: 4px 0; + background: none; + border: none; + cursor: pointer; + font-family: var(--font-family); +} + +.dropdown__button i { + font-size: var(--fs-base); +} + +.dropdown__menu { + display: none; + position: absolute; + top: 100%; + left: 0; + /* Above card content, below nav (100) */ + z-index: 20; + background: var(--white); + border: 1px solid var(--border-default); + box-shadow: var(--shadow-card); + min-width: 200px; + padding: 8px 0; +} + +.dropdown__menu--open { + display: block; +} + +.dropdown__item { + display: block; + width: 100%; + text-align: left; + padding: 8px 16px; + font-size: var(--fs-base); + color: var(--text-primary); + background: none; + border: none; + cursor: pointer; + font-family: var(--font-family); +} + +.dropdown__item:hover { + background: var(--gray-100); +} + +.dropdown__item--active { + font-weight: var(--fw-bold); + color: var(--petroleum); +} + +.btn-primary { + display: inline-flex; + align-items: center; + justify-content: center; + background: var(--petroleum); + color: var(--white); + font-family: var(--font-family-inter); + font-size: var(--fs-base); + font-weight: var(--fw-regular); + padding: 8px 24px; + border: 1px solid var(--petroleum); + border-radius: 4px; + text-decoration: none; + cursor: pointer; + transition: background var(--transition-fast); + line-height: 24px; + margin-left: auto; +} + +.btn-primary:hover { + background: var(--petroleum-800); + color: var(--white); +} + +/* Card Grid */ +.horingssvar-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; + margin-bottom: 48px; +} + +/* Horingssvar Card */ +.horingssvar-card { + background: var(--white); + border: 1px solid var(--gray-200); + border-radius: 4px; + padding: 24px 20px; + min-height: 240px; + display: flex; + flex-direction: column; + gap: 18px; + cursor: pointer; + transition: box-shadow var(--transition-card-in), + transform var(--transition-card-in); +} + +.horingssvar-card:hover { + box-shadow: var(--shadow-card); + /* Subtle lift on hover for depth feedback */ + transform: translateY(-2px); + transition: box-shadow var(--transition-card-out), + transform var(--transition-card-out); +} + +.horingssvar-card__title { + font-family: var(--font-family-inter); + font-size: 18px; + font-weight: var(--fw-semibold); + line-height: 28px; + color: var(--text-primary); +} + +.horingssvar-card__description { + font-family: var(--font-family-inter); + font-size: var(--fs-small); + line-height: 20px; + color: var(--text-primary); + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + height: 61px; +} + +.horingssvar-card__meta { + display: flex; + flex-direction: column; + gap: 8px; + font-family: var(--font-family-inter); + font-size: var(--fs-small); + line-height: 20px; + color: var(--text-primary); +} + +.horingssvar-card__meta-item { + display: flex; + align-items: center; + gap: 8px; +} + +.horingssvar-card__meta-item i { + width: 14px; + text-align: center; + color: var(--gray-600); +} + +/* Like button (open variant) */ +.horingssvar-card__like-btn { + display: flex; + align-items: center; + gap: 8px; + font-family: var(--font-family-inter); + font-size: var(--fs-small); + line-height: 20px; + color: var(--text-primary); + background: none; + border: none; + padding: 0; + cursor: pointer; +} + +.horingssvar-card__like-btn:hover { + color: var(--petroleum); +} + +.horingssvar-card__like-btn--liked { + color: var(--petroleum); + cursor: default; +} + +.horingssvar-card__divider { + height: 1px; + background: var(--gray-200); + margin-top: auto; +} + +/* Pagination */ +.horingssvar-pagination { + display: flex; + justify-content: center; + align-items: baseline; + gap: 8px; + font-family: var(--font-family-inter); + font-size: 18px; + font-weight: var(--fw-semibold); + padding-bottom: 16px; + border-bottom: 1px solid var(--gray-200); +} + +.horingssvar-pagination__count { + color: var(--gray-650); +} + +.horingssvar-pagination__show-all { + color: var(--petroleum); + text-decoration: underline; + cursor: pointer; + background: none; + border: none; + font-family: var(--font-family-inter); + font-size: 18px; + font-weight: var(--fw-semibold); +} + +.horingssvar-pagination__show-all:hover { + color: var(--petroleum-800); +} + +@media (max-width: 1200px) { + .horingssvar-grid { + grid-template-columns: repeat(2, 1fr); + } + + .horingssvar-section { + padding: 0 40px 48px; + } +} + +@media (max-width: 960px) { + .horingssvar-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 640px) { + .horingssvar-grid { + grid-template-columns: 1fr; + } + + .horingssvar-section { + padding: 0 16px 48px; + } + + .horingssvar-section__controls { + flex-direction: column; + align-items: flex-start; + } + + .btn-primary { + margin-left: 0; + width: 100%; + } + + .dropdown__button { + font-size: 18px; + } +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/info-box.css b/docs/public/projects/deltag-aarhus/mocks/css/info-box.css new file mode 100644 index 0000000..3ea92d8 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/info-box.css @@ -0,0 +1,49 @@ +/* Info Box — blue highlighted information panel with header and body */ + +.info-box { + max-width: 787px; + margin: 0 auto 48px; + padding-inline: 116px; + max-width: var(--container-max); +} + +.info-box__header { + background: var(--blue-200); + padding: 12px 32px; + display: flex; + align-items: center; + min-height: 67px; + border-radius: 4px 4px 0 0; +} + +.info-box__title { + font-family: var(--font-family-inter); + font-size: 20px; + font-weight: var(--fw-semibold); + color: var(--black); +} + +.info-box__body { + background: var(--blue-50); + padding: 32px; + border-radius: 0 0 4px 4px; +} + +.info-box__body p { + font-family: var(--font-family-inter); + font-size: var(--fs-base); + line-height: 24px; +} + +.info-box__body a { + color: var(--black); + display: block; + line-height: 24px; +} + +@media (max-width: 1200px) { + .info-box { + margin-left: 40px; + margin-right: 40px; + } +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/map.css b/docs/public/projects/deltag-aarhus/mocks/css/map.css new file mode 100644 index 0000000..0cb2961 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/map.css @@ -0,0 +1,24 @@ +/* Map — embedded map section with bordered container */ + +.map-box-section { + max-width: var(--container-max); + margin: 0 auto; + padding: 0 116px 48px; + position: relative; + z-index: 1; +} + +.map-box { + width: 100%; + height: 450px; + border-radius: 4px; + overflow: hidden; + z-index: 1; + position: relative; +} + +@media (max-width: 1200px) { + .map-box-section { + padding-inline: 40px; + } +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/materials.css b/docs/public/projects/deltag-aarhus/mocks/css/materials.css new file mode 100644 index 0000000..5a1fc53 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/materials.css @@ -0,0 +1,89 @@ +/* Materials — downloadable documents/files list section */ + +.materials-section { + max-width: var(--container-max); + margin: 0 auto; + /* Right padding includes 400px for sidebar+gap alignment with body-section above */ + padding: 32px calc(116px + 400px) 48px 116px; +} + +.materials__heading { + font-size: var(--fs-teaser); + font-weight: var(--fw-bold); + margin-bottom: 16px; +} + +.materials__list { + display: flex; + flex-direction: column; + gap: 4px; +} + +.materials__item { + display: flex; + align-items: center; + gap: 12px; + background: var(--petroleum-100); + padding: 6px 18px; + height: 50px; + text-decoration: none; + color: var(--text-primary); + transition: background var(--transition-fast); +} + +.materials__item:hover { + background: var(--petroleum-200); + color: var(--text-primary); +} + +.materials__item-name { + font-weight: var(--fw-bold); + font-size: var(--fs-small); + white-space: nowrap; +} + +.materials__item-meta { + display: flex; + align-items: center; + gap: 6px; + color: var(--gray-650); +} + +.materials__item-preview-badge { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: var(--fs-xs); + font-weight: var(--fw-semibold); + color: var(--petroleum); + background: var(--petroleum-100); + padding: 2px 8px; + border-radius: 4px; +} + +.materials__item-icon { + font-size: 14px; +} + +.materials__item-size { + font-size: var(--fs-xs); +} + +@media (max-width: 1200px) { + .materials-section { + /* Maintains sidebar-width offset at tablet */ + padding: 32px calc(40px + 400px) 48px 40px; + } +} + +@media (max-width: 960px) { + .materials-section { + padding: 24px 40px 32px; + } +} + +@media (max-width: 640px) { + .materials-section { + padding: 24px 16px 32px; + } +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/mock-banner.css b/docs/public/projects/deltag-aarhus/mocks/css/mock-banner.css new file mode 100644 index 0000000..b13ec98 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/mock-banner.css @@ -0,0 +1,44 @@ +/* Mock Banner — red development/staging indicator banner at top of page */ + +.mock-banner { + position: sticky; + top: 0; + z-index: 200; + background: #d32f2f; + color: #fff; + text-align: center; + font-family: var(--font-family-inter); + font-size: var(--fs-xs); + font-weight: var(--fw-semibold); + padding: 6px 16px; + letter-spacing: 0.5px; +} + +.mock-banner__link { + color: #fff; + text-decoration: underline; + margin-left: 4px; +} + +.mock-banner__link:hover { + color: #ffcdd2; +} + +.mock-banner__divider { + margin: 0 8px; + opacity: 0.5; +} + +.mock-banner__variants { + margin-left: 8px; +} + +.mock-banner__link--variant { + margin: 0 4px; +} + +.mock-banner__link--active { + text-decoration: none; + border-bottom: 2px solid #fff; + padding-bottom: 1px; +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/modal-decision.css b/docs/public/projects/deltag-aarhus/mocks/css/modal-decision.css new file mode 100644 index 0000000..78d7b2f --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/modal-decision.css @@ -0,0 +1,41 @@ +/* Modal Decision — decision modal with related document links */ + +.decision-modal__links { + border-top: 1px solid var(--gray-200); + padding-top: 24px; + display: flex; + flex-direction: column; + gap: 12px; +} + +.decision-modal__links-title { + font-family: var(--font-family-inter); + font-size: var(--fs-base); + font-weight: var(--fw-semibold); + margin-bottom: 4px; +} + +.decision-modal__link { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + background: var(--gray-100); + text-decoration: none; + color: var(--text-primary); + font-family: var(--font-family-inter); + font-size: var(--fs-small); + transition: background var(--transition-fast); +} + +.decision-modal__link:hover { + background: var(--gray-200); +} + +.decision-modal__link i { + color: var(--petroleum); + font-size: 18px; + width: 24px; + text-align: center; + flex-shrink: 0; +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/modal-horingssvar.css b/docs/public/projects/deltag-aarhus/mocks/css/modal-horingssvar.css new file mode 100644 index 0000000..f4977e7 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/modal-horingssvar.css @@ -0,0 +1,126 @@ +/* Modal Horingssvar — hearing response modal with stats, comments section, and show-more button */ + +.modal__stats { + display: flex; + gap: 24px; + padding-top: 16px; + border-top: 1px solid var(--gray-200); + font-family: var(--font-family-inter); + font-size: var(--fs-small); + color: var(--text-primary); +} + +.modal__stat { + display: flex; + align-items: center; + gap: 8px; +} + +.modal__stat i { + color: var(--gray-600); +} + +/* Interactive like stat (open variant) */ +.modal__stat--interactive:hover { + color: var(--petroleum); +} + +.modal__stat--liked { + color: var(--petroleum); +} + +.modal__comments-section { + border-top: 1px solid var(--gray-200); + padding-top: 24px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.modal__comments-title { + font-family: var(--font-family-inter); + font-size: var(--fs-base); + font-weight: var(--fw-semibold); +} + +.modal__comment { + padding: 16px; + background: var(--gray-100); + display: flex; + flex-direction: column; + gap: 8px; +} + +.modal__comment-author { + font-family: var(--font-family-inter); + font-size: var(--fs-small); + font-weight: var(--fw-semibold); +} + +.modal__comment-date { + font-family: var(--font-family-inter); + font-size: var(--fs-xs); + color: var(--gray-650); +} + +.modal__comment-text { + font-family: var(--font-family-inter); + font-size: var(--fs-small); + line-height: 20px; +} + +.modal__comments-show-more { + font-family: var(--font-family-inter); + font-size: var(--fs-small); + font-weight: var(--fw-semibold); + color: var(--petroleum); + background: none; + border: 1px solid var(--petroleum); + padding: 10px 20px; + cursor: pointer; + transition: background var(--transition-fast), color var(--transition-fast); + align-self: center; +} + +.modal__comments-show-more:hover { + background: var(--petroleum); + color: var(--white); +} + +/* Comment form (open variant only) */ +.modal__comment-form { + display: flex; + flex-direction: column; + gap: 10px; + padding-top: 16px; + border-top: 1px solid var(--gray-200); + margin-top: 8px; +} + +.modal__comment-form-label { + font-family: var(--font-family-inter); + font-size: var(--fs-small); + font-weight: var(--fw-semibold); +} + +.modal__comment-input { + width: 100%; + min-height: 80px; + padding: 12px; + border: 1px solid var(--border-default); + border-radius: 4px; + font-family: var(--font-family); + font-size: var(--fs-small); + resize: vertical; +} + +.modal__comment-input:focus { + outline: 2px solid var(--petroleum); + outline-offset: -1px; + border-color: var(--petroleum); +} + +.modal__comment-submit { + align-self: flex-end; + padding: 8px 20px; +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/modal-material.css b/docs/public/projects/deltag-aarhus/mocks/css/modal-material.css new file mode 100644 index 0000000..8f76ed6 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/modal-material.css @@ -0,0 +1,49 @@ +/* Modal Material — material document modal with action buttons and rich content */ + +.material-modal__actions { + display: flex; + gap: 12px; + padding-bottom: 16px; + border-bottom: 1px solid var(--gray-200); +} + +.material-modal__action-btn { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + font-family: var(--font-family-inter); + font-size: var(--fs-small); + font-weight: var(--fw-semibold); + color: var(--petroleum); + border: 1px solid var(--petroleum); + text-decoration: none; + transition: background var(--transition-fast), color var(--transition-fast); +} + +.material-modal__action-btn:hover { + background: var(--petroleum); + color: var(--white); +} + +.material-modal__content { + font-family: var(--font-family-inter); + font-size: var(--fs-small); + line-height: 22px; + color: var(--text-primary); +} + +.material-modal__content h3 { + font-size: var(--fs-base); + font-weight: var(--fw-semibold); + margin-top: 24px; + margin-bottom: 8px; +} + +.material-modal__content h3:first-child { + margin-top: 0; +} + +.material-modal__content p { + margin-bottom: 12px; +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/modal-mitid.css b/docs/public/projects/deltag-aarhus/mocks/css/modal-mitid.css new file mode 100644 index 0000000..f77eb9d --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/modal-mitid.css @@ -0,0 +1,96 @@ +/* Modal MitID — mock MitID login flow */ + +.modal--mitid { + max-width: 440px; +} + +.mitid-flow { + display: flex; + flex-direction: column; + align-items: center; + gap: 20px; + padding: 16px 0; + text-align: center; +} + +.mitid-flow__logo { + display: flex; + align-items: center; + gap: 10px; + color: #0060a3; + font-size: 28px; +} + +.mitid-flow__logo-text { + font-family: var(--font-family-inter); + font-size: 24px; + font-weight: var(--fw-bold); + color: #0060a3; +} + +.mitid-flow__description { + font-family: var(--font-family-inter); + font-size: var(--fs-small); + line-height: 1.5; + color: var(--text-primary); + max-width: 320px; +} + +.mitid-flow__field { + width: 100%; + max-width: 300px; + text-align: left; +} + +.mitid-flow__label { + display: block; + font-family: var(--font-family-inter); + font-size: var(--fs-xs); + font-weight: var(--fw-semibold); + color: var(--gray-650); + margin-bottom: 6px; +} + +.mitid-flow__input { + width: 100%; + padding: 10px 14px; + border: 1px solid var(--border-default); + border-radius: 4px; + font-family: var(--font-family-inter); + font-size: var(--fs-base); + background: var(--gray-100); + color: var(--text-primary); +} + +.mitid-flow__hint { + font-family: var(--font-family-inter); + font-size: var(--fs-xs); + color: var(--gray-650); +} + +.mitid-flow__approve { + display: inline-flex; + align-items: center; + justify-content: center; + background: #0060a3; + color: var(--white); + font-family: var(--font-family-inter); + font-size: var(--fs-base); + font-weight: var(--fw-semibold); + padding: 12px 48px; + border: none; + border-radius: 4px; + cursor: pointer; + transition: background var(--transition-fast); +} + +.mitid-flow__approve:hover { + background: #004a7f; +} + +.mitid-flow__note { + font-family: var(--font-family-inter); + font-size: var(--fs-xs); + color: var(--gray-400); + font-style: italic; +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/modal-submission.css b/docs/public/projects/deltag-aarhus/mocks/css/modal-submission.css new file mode 100644 index 0000000..dcd2dc2 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/modal-submission.css @@ -0,0 +1,116 @@ +/* Modal Submission — høringssvar submission form */ + +.submission-form { + display: flex; + flex-direction: column; + gap: 20px; +} + +.submission-form__field { + display: flex; + flex-direction: column; + gap: 6px; +} + +.submission-form__label { + font-family: var(--font-family-inter); + font-size: var(--fs-small); + font-weight: var(--fw-semibold); + color: var(--text-primary); +} + +.submission-form__input, +.submission-form__select, +.submission-form__textarea { + width: 100%; + padding: 10px 14px; + border: 1px solid var(--border-default); + border-radius: 4px; + font-family: var(--font-family); + font-size: var(--fs-base); + color: var(--text-primary); + background: var(--white); +} + +.submission-form__input:read-only { + background: var(--gray-100); + color: var(--gray-650); +} + +.submission-form__textarea { + resize: vertical; + min-height: 120px; +} + +.submission-form__input:focus, +.submission-form__select:focus, +.submission-form__textarea:focus { + outline: 2px solid var(--petroleum); + outline-offset: -1px; + border-color: var(--petroleum); +} + +.submission-form__hint { + font-family: var(--font-family-inter); + font-size: var(--fs-xs); + color: var(--gray-650); + font-style: italic; +} + +.submission-form__notice { + display: flex; + gap: 12px; + padding: 14px 16px; + background: var(--gray-100); + border-radius: 4px; + font-family: var(--font-family-inter); + font-size: var(--fs-xs); + line-height: 1.5; + color: var(--gray-650); +} + +.submission-form__notice i { + flex-shrink: 0; + margin-top: 2px; + color: var(--gray-400); +} + +.submission-form__actions { + display: flex; + justify-content: flex-end; + padding-top: 8px; +} + +.submission-form__submit { + padding: 10px 32px; +} + +/* Success state shown after mock submission */ +.submission-success { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + padding: 32px 16px; + text-align: center; +} + +.submission-success__icon { + font-size: 48px; + color: #2e7d32; +} + +.submission-success__title { + font-family: var(--font-family-inter); + font-size: var(--fs-teaser); + font-weight: var(--fw-bold); + color: var(--text-primary); +} + +.submission-success__text { + font-family: var(--font-family-inter); + font-size: var(--fs-small); + line-height: 1.5; + color: var(--gray-650); + max-width: 400px; +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/modal.css b/docs/public/projects/deltag-aarhus/mocks/css/modal.css new file mode 100644 index 0000000..28bdd35 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/modal.css @@ -0,0 +1,169 @@ +/* Modal — shared base modal overlay, structure, header, body, meta, description, and navigation */ + +.modal-overlay { + display: none; + position: fixed; + inset: 0; + /* Above everything including nav (100) */ + z-index: 1000; + background: rgb(0 0 0 / 50%); + justify-content: center; + align-items: flex-start; + padding: 60px 20px; + overflow-y: auto; +} + +.modal-overlay--open { + display: flex; +} + +.modal { + background: var(--white); + width: 100%; + max-width: 800px; + /* Viewport minus overlay padding (60px top + 60px bottom) */ + max-height: calc(100vh - 120px); + position: relative; + box-shadow: 0 8px 32px rgb(0 0 0 / 20%); + display: flex; + flex-direction: column; +} + +.modal__header { + display: flex; + align-items: flex-start; + justify-content: space-between; + padding: 40px 40px 0; + gap: 24px; +} + +.modal__close { + flex-shrink: 0; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + color: var(--gray-600); + background: none; + border: none; + cursor: pointer; + transition: color var(--transition-fast); +} + +.modal__close:hover { + color: var(--text-primary); +} + +.modal__title { + font-family: var(--font-family-inter); + font-size: 24px; + font-weight: var(--fw-semibold); + line-height: 32px; + flex: 1; +} + +.modal__body { + padding: 24px 40px 40px; + display: flex; + flex-direction: column; + gap: 24px; + overflow-y: auto; + flex: 1; + /* Required for flex children to shrink below content height */ + min-height: 0; +} + +.modal__meta { + display: flex; + gap: 24px; + font-family: var(--font-family-inter); + font-size: var(--fs-small); + color: var(--gray-650); +} + +.modal__meta-item { + display: flex; + align-items: center; + gap: 8px; +} + +.modal__meta-item i { + color: var(--gray-600); +} + +.modal__description { + font-family: var(--font-family-inter); + font-size: var(--fs-base); + line-height: 24px; + color: var(--text-primary); +} + +.modal__description p + p { + margin-top: 16px; +} + +/* Modal Navigation */ +.modal__nav { + display: flex; + justify-content: space-between; + padding: 16px 40px; + border-top: 1px solid var(--gray-200); + background: var(--gray-100); +} + +.modal__nav-btn { + display: flex; + align-items: center; + gap: 8px; + font-family: var(--font-family-inter); + font-size: var(--fs-small); + font-weight: var(--fw-semibold); + color: var(--petroleum); + background: none; + border: none; + cursor: pointer; + padding: 8px 16px; + transition: color var(--transition-fast); +} + +.modal__nav-btn:hover { + color: var(--petroleum-800); +} + +.modal__nav-btn:disabled { + color: var(--gray-500); + cursor: not-allowed; +} + +.modal__nav-counter { + font-family: var(--font-family-inter); + font-size: var(--fs-small); + color: var(--gray-650); + display: flex; + align-items: center; +} + +@media (max-width: 640px) { + .modal { + margin: 0; + min-height: 100vh; + } + + .modal-overlay { + padding: 0; + } + + .modal__header { + padding: 24px 20px 0; + } + + .modal__body { + padding: 16px 20px 24px; + } + + .modal__nav { + padding: 12px 20px; + } +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/nav.css b/docs/public/projects/deltag-aarhus/mocks/css/nav.css new file mode 100644 index 0000000..dfb1891 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/nav.css @@ -0,0 +1,153 @@ +/* Navigation — main site navigation bar with logo, links, search, and compact scroll variant */ + +.nav { + position: sticky; + /* Offset below the mock banner (28px tall) */ + top: 28px; + /* Above content, below modals (1000) */ + z-index: 100; + background: var(--white); + box-shadow: var(--shadow-nav); + padding: 18px 36px 24px; + display: flex; + justify-content: center; + transition: padding 0.3s ease; +} + +.nav__inner { + display: flex; + gap: 48px; + align-items: flex-start; + width: 100%; + max-width: var(--container-max); +} + +.nav__logo { + flex-shrink: 0; + display: flex; + align-items: center; + align-self: center; +} + +.nav__logo img { + transition: height 0.3s ease; +} + +.nav__logo svg { + height: 40px; + width: auto; +} + +.nav__right { + flex: 1; + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 18px; + transition: gap 0.3s ease; +} + +.nav__sub-menu { + display: flex; + gap: 24px; + overflow: hidden; + max-height: 24px; + opacity: 1; + transition: max-height 0.3s ease, opacity 0.2s ease, margin 0.3s ease; +} + +/* Compact nav on scroll */ +.nav--compact { + padding: 8px 36px 12px; +} + +.nav--compact .nav__logo img { + height: 40px; +} + +.nav--compact .nav__inner { + align-items: center; +} + +.nav--compact .nav__right { + gap: 0; +} + +.nav--compact .nav__sub-menu { + max-height: 0; + opacity: 0; + margin: 0; +} + +.nav--compact .nav__link--active::after { + bottom: -19px; +} + +.nav__sub-link { + font-size: var(--fs-small); + color: var(--text-primary); + text-decoration: none; + padding-bottom: 3px; +} + +.nav__sub-link:hover { + color: var(--text-link); +} + +.nav__main-menu { + display: flex; + gap: 30px; + align-items: center; +} + +.nav__link { + font-size: 15px; + font-weight: var(--fw-bold); + color: var(--text-primary); + text-decoration: none; + position: relative; + padding-bottom: 4px; +} + +.nav__link:hover { + color: var(--petroleum); +} + +.nav__link--active { + color: var(--petroleum); +} + +.nav__link--active::after { + content: ""; + position: absolute; + /* Aligns indicator with bottom edge of full nav */ + bottom: -26px; + left: 0; + right: 0; + height: 5px; + background: var(--petroleum); +} + +.nav__divider { + width: 1px; + height: 18px; + background: var(--gray-300); +} + +.nav__search { + color: var(--petroleum); + font-size: 18px; + padding: 0; + display: flex; + align-items: center; +} + +@media (max-width: 640px) { + .nav__main-menu { + display: none; + } + + .nav__sub-menu { + display: none; + } +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/projects-cta.css b/docs/public/projects/deltag-aarhus/mocks/css/projects-cta.css new file mode 100644 index 0000000..4d1204f --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/projects-cta.css @@ -0,0 +1,77 @@ +/* Projects CTA — call-to-action section with background image and text card */ + +.projects-cta { + background-color: var(--blue-100); + background-size: cover; + background-position: center; + min-height: 500px; + display: flex; + align-items: center; + justify-content: center; + position: relative; + overflow: hidden; +} + +.projects-cta__inner { + display: flex; + align-items: center; + gap: 48px; + max-width: 1400px; + width: 100%; + padding: 48px 120px; +} + +.projects-cta__text-box { + background: var(--white); + border-radius: 6px; + padding: 60px 61px; + display: flex; + flex-direction: column; + gap: 22px; + max-width: 630px; + flex-shrink: 0; +} + +.projects-cta__label { + font-size: var(--fs-small); + font-weight: var(--fw-bold); +} + +.projects-cta__title { + font-size: 40px; + font-weight: var(--fw-bold); + line-height: 48px; +} + +.projects-cta__desc { + font-size: var(--fs-base); + line-height: 24px; +} + +.projects-cta__button { + display: inline-flex; + align-items: center; + justify-content: center; + background: var(--petroleum); + color: var(--white); + font-size: 15px; + padding: 12px 30px; + border-radius: 2px; + text-decoration: none; + letter-spacing: 0.5px; + width: 264px; + height: 48px; + transition: background var(--transition-fast); +} + +.projects-cta__button:hover { + background: var(--petroleum-800); + color: var(--white); +} + +@media (max-width: 960px) { + .projects-cta__inner { + flex-direction: column; + padding: 48px 24px; + } +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/related.css b/docs/public/projects/deltag-aarhus/mocks/css/related.css new file mode 100644 index 0000000..108747e --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/related.css @@ -0,0 +1,169 @@ +/* Related — related activities section with activity cards */ + +.related-section { + background: var(--gray-100); + padding: 56px 0; +} + +.related-section__title { + font-size: var(--fs-teaser); + font-weight: var(--fw-bold); + margin-bottom: 32px; +} + +.related-section__inner { + max-width: var(--container-max); + margin: 0 auto; + padding-inline: 116px; +} + +.related-section__grid { + display: flex; + gap: 20px; + flex-wrap: wrap; + max-width: var(--container-max); + margin: 0 auto; + padding-inline: 116px; +} + +/* Activity Card */ +.activity-card { + background: var(--white); + display: flex; + flex-direction: column; + width: 430px; + text-decoration: none; + color: var(--text-primary); + transition: box-shadow var(--transition-card-in); + position: relative; +} + +.activity-card:hover { + box-shadow: var(--shadow-card); + color: var(--text-primary); +} + +.activity-card__image { + height: 240px; + overflow: hidden; + position: relative; +} + +.activity-card__image img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform var(--transition-card-out); +} + +.activity-card:hover .activity-card__image img { + transform: scale(1.07); +} + +.activity-card__date-badge { + position: absolute; + bottom: 0; + left: 0; + background: var(--white); + width: 59px; + height: 70px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3px; + padding: 15px 14px; +} + +.activity-card__date-day { + font-size: 18px; + font-weight: var(--fw-bold); + text-align: center; +} + +.activity-card__date-month { + font-size: var(--fs-small); + text-align: center; +} + +.activity-card__body { + padding: 32px 29px; + display: flex; + flex-direction: column; + gap: 20px; + flex: 1; +} + +.activity-card__type { + font-size: var(--fs-xs); + font-weight: var(--fw-bold); + color: var(--petroleum); + letter-spacing: 0.1px; +} + +.activity-card__title { + font-size: 20px; + font-weight: var(--fw-bold); + line-height: 26px; + letter-spacing: 0.2px; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +.activity-card__details { + display: flex; + flex-direction: column; +} + +.activity-card__detail-row { + display: flex; + align-items: center; + gap: 13px; + padding: 10px 0; + border-bottom: 1px solid var(--gray-350); + font-size: var(--fs-small); +} + +.activity-card__detail-row:last-child { + border-bottom: none; +} + +.activity-card__detail-row i { + width: 25px; + text-align: center; + color: var(--petroleum); + font-size: 20px; +} + +.activity-card__arrow { + display: flex; + justify-content: flex-end; + color: var(--text-primary); + font-size: var(--fs-small); +} + +@media (max-width: 1200px) { + .related-section { + padding-inline: 40px; + } +} + +@media (max-width: 960px) { + .related-section__grid { + padding-left: 24px; + padding-right: 24px; + } + + .related-section__title { + padding-left: 24px; + } +} + +@media (max-width: 640px) { + .activity-card { + width: 100%; + } +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/statistics.css b/docs/public/projects/deltag-aarhus/mocks/css/statistics.css new file mode 100644 index 0000000..752decb --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/statistics.css @@ -0,0 +1,315 @@ +/* Statistics — charts section with bar charts, line charts, and category charts */ + +.statistics-section { + max-width: var(--container-max); + margin: 0 auto; + padding: 0 116px 48px; +} + +.statistics-section__heading { + font-size: var(--fs-teaser); + font-weight: var(--fw-bold); + margin-bottom: 24px; +} + +/* Line Chart */ +.line-chart { + display: flex; + gap: 12px; +} + +.line-chart__y-axis { + display: flex; + flex-direction: column; + justify-content: space-between; + font-family: var(--font-family-inter); + font-size: var(--fs-xs); + color: var(--gray-650); + text-align: right; + min-width: 30px; + padding-bottom: 28px; +} + +.line-chart__area { + flex: 1; + display: flex; + flex-direction: column; +} + +.line-chart__svg { + width: 100%; + height: 200px; +} + +.line-chart__x-axis { + display: flex; + justify-content: space-between; + font-family: var(--font-family-inter); + font-size: var(--fs-xs); + color: var(--gray-650); + padding-top: 8px; +} + +/* Statistics */ +.statistics { + max-width: none; + width: 100%; + margin: 0 0 48px; + padding: 40px; + border-radius: 8px; + border: 1px solid var(--border-default); + overflow: hidden; + display: flex; + flex-direction: column; + gap: 32px; +} + +.statistics:last-child { + margin-bottom: 0; +} + +.statistics__header { + display: flex; + flex-direction: column; + gap: 8px; +} + +.statistics__title { + font-family: var(--font-family-inter); + font-size: var(--fs-base); + font-weight: var(--fw-semibold); +} + +.statistics__subtitle { + font-family: var(--font-family-inter); + font-size: var(--fs-small); +} + +.statistics__chart { + display: flex; + gap: 16px; +} + +.statistics__y-label { + width: 20px; + display: flex; + align-items: center; + justify-content: center; +} + +.statistics__y-label span { + transform: rotate(-90deg); + white-space: nowrap; + font-family: var(--font-family-inter); + font-size: var(--fs-small); +} + +.statistics__y-axis { + width: 60px; + display: flex; + flex-direction: column; + justify-content: space-between; + height: 300px; +} + +.statistics__y-tick { + font-family: var(--font-family-inter); + font-size: var(--fs-small); + text-align: right; + height: 20px; + display: flex; + align-items: center; + justify-content: flex-end; +} + +.statistics__bars-wrapper { + flex: 1; + height: 300px; + position: relative; +} + +.statistics__grid-lines { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + justify-content: space-between; + pointer-events: none; +} + +.statistics__grid-line { + height: 1px; + background: var(--gray-200); +} + +.statistics__bars { + position: relative; + z-index: 1; + height: 100%; + display: flex; + justify-content: space-between; + align-items: flex-end; + padding: 0 20px; +} + +.statistics__bar-group { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-end; + gap: 12px; + width: 80px; + height: 100%; +} + +.statistics__bar { + width: 48px; + background: var(--blue); + border-radius: 4px 4px 0 0; + transition: opacity var(--transition-fast); +} + +.statistics__bar:hover { + opacity: 0.8; +} + +.statistics__bar-label { + font-family: var(--font-family-inter); + font-size: var(--fs-small); + text-align: center; +} + +.statistics__x-label { + text-align: center; + font-family: var(--font-family-inter); + font-size: var(--fs-small); + padding-top: 16px; +} + +.statistics__footer { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 16px; +} + +.statistics__legend { + display: flex; + align-items: center; + gap: 8px; + font-family: var(--font-family-inter); + font-size: var(--fs-small); +} + +.statistics__legend-color { + width: 12px; + height: 12px; + border-radius: 2px; + background: var(--blue); +} + +.statistics__total { + font-family: var(--font-family-inter); + font-size: var(--fs-small); +} + +/* Category Chart */ +.category-chart { + display: flex; + flex-direction: column; + gap: 20px; +} + +.category-chart__row { + display: flex; + align-items: center; + gap: 16px; +} + +.category-chart__label { + font-family: var(--font-family-inter); + font-size: var(--fs-small); + font-weight: var(--fw-semibold); + width: 110px; + flex-shrink: 0; + text-align: right; +} + +.category-chart__bar-track { + flex: 1; + height: 32px; + background: var(--gray-100); + border-radius: 4px; + overflow: hidden; +} + +.category-chart__bar { + height: 100%; + background: var(--blue); + border-radius: 4px; + /* Slower than default for animated bar fill effect */ + transition: width 0.6s ease; +} + +.category-chart__bar--green { + background: var(--green); +} + +.category-chart__bar--petroleum { + background: var(--petroleum); +} + +.category-chart__bar--orange { + background: var(--orange); +} + +.category-chart__value { + font-family: var(--font-family-inter); + font-size: var(--fs-small); + font-weight: var(--fw-semibold); + width: 40px; + flex-shrink: 0; +} + +.category-chart__legend { + display: flex; + gap: 20px; + font-family: var(--font-family-inter); + font-size: var(--fs-small); +} + +.category-chart__legend-item { + display: flex; + align-items: center; + gap: 6px; +} + +.category-chart__legend-color { + display: inline-block; + width: 12px; + height: 12px; + border-radius: 2px; + background: var(--blue); +} + +.category-chart__legend-color--green { + background: var(--green); +} + +.category-chart__legend-color--petroleum { + background: var(--petroleum); +} + +.category-chart__legend-color--orange { + background: var(--orange); +} + +@media (max-width: 1200px) { + .statistics-section { + padding-inline: 40px; + } + + .statistics { + margin-left: 0; + } +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/tokens.css b/docs/public/projects/deltag-aarhus/mocks/css/tokens.css new file mode 100644 index 0000000..588599f --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/tokens.css @@ -0,0 +1,111 @@ +/* Design tokens from deltag.aarhus.dk design system. + This file contains only custom properties — no component styles. + Inter is the UI font (cards, modals, stats, pagination). + The base font-family (Arial stack) is used for body/prose text. + Breakpoints: 1200px (tablet), 960px (small tablet), 640px (mobile). */ + +:root { + /* Brand */ + --petroleum: #008486; + --petroleum-100: #e5eef0; + --petroleum-200: #cfe0e2; + --petroleum-300: #b2dada; + --petroleum-400: #8fcfcf; + --petroleum-800: #3d6d6d; + --petroleum-900: #2a4a4a; + --petroleum-muted: #467176; + + /* Grayscale */ + --white: #fff; + --gray-50: #f6f6f6; + --gray-100: #f5f5f5; + --gray-200: #eaeaea; + --gray-300: #e6e6e6; + --gray-350: #dfdfdf; + --gray-400: #d6d6d6; + --gray-500: #9d9d9d; + --gray-600: #858585; + --gray-650: #666; + --gray-700: #525252; + --gray-800: #444; + --gray-900: #333; + --black: #1a1a1a; + + /* Accent */ + --pink: #ee0043; + --blue: #3661d8; + --blue-50: #eff6ff; + --blue-100: #ebeffb; + --blue-200: #bfdbfe; + --purple: #673ab7; + --green: #008850; + --yellow: #ffe13d; + --yellow-50: #fffbeb; + --yellow-100: #fef3c7; + --orange: #ff5f31; + --red: #d32f2f; + + /* Neutral Warm */ + --peach-100: #f7f0e8; + --peach-200: #e8dfd4; + --peach-700: #7a6f64; + + /* Semantic */ + --text-primary: var(--gray-900); + --text-secondary: var(--gray-700); + --text-muted: var(--gray-500); + --text-inverse: var(--white); + --text-link: var(--petroleum); + --text-link-hover: var(--petroleum-800); + + --bg-primary: var(--white); + --bg-secondary: var(--gray-50); + --bg-tertiary: var(--gray-200); + + --border-default: var(--gray-300); + --border-strong: var(--gray-900); + --border-subtle: var(--gray-200); + + /* Typography */ + --font-family: arial, -apple-system, blinkmacsystemfont, "Segoe UI", roboto, + "Helvetica Neue", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + "Segoe UI Symbol"; + --font-family-inter: "Inter", var(--font-family); + + --fs-display: 4rem; + --fs-h1: 2.2rem; + --fs-teaser: 1.625rem; + --fs-h2: 1.4rem; + --fs-lead: 1.3rem; + --fs-large: 1.25rem; + --fs-h3: 1.2rem; + --fs-h4: 1.1rem; + --fs-base: 1rem; + --fs-small: 0.875rem; + --fs-xs: 0.75rem; + + --fw-regular: 400; + --fw-semibold: 600; + --fw-bold: 700; + --fw-black: 900; + + --lh-body: 1.5; + --lh-heading: 1.4; + + /* Spacing */ + --spacer: 1rem; + + /* Shadows */ + --shadow-card: 0 0.25rem 1rem rgb(0 0 0 / 16%); + --shadow-nav: 0 2px 10px rgb(0 0 0 / 30%); + --shadow-medium: 0 0 2px rgb(0 0 0 / 2%), 1px 3px 16px rgb(0 0 0 / 8%); + + /* Layout */ + --container-max: 1352px; + --container-padding: 124px; + + /* Transitions */ + --transition-fast: 0.2s ease-in; + --transition-card-in: 200ms ease; + --transition-card-out: 400ms ease; +} diff --git a/docs/public/projects/deltag-aarhus/mocks/css/variant.css b/docs/public/projects/deltag-aarhus/mocks/css/variant.css new file mode 100644 index 0000000..8060b83 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/css/variant.css @@ -0,0 +1,69 @@ +/* Variant — conditional styles toggled by body.variant--open / body.variant--closed */ + +/* In the open variant the decision banner is hidden, so the hearing header + overlaps the hero image directly — matching the closed variant's visual weight. */ +body.variant--open .hearing-header { + margin-top: -50px; +} + +/* Tooltip on disabled submit button (closed variant) */ +.btn-primary-wrapper { + position: relative; + margin-left: auto; +} + +.btn-primary__tooltip { + display: none; + position: absolute; + bottom: calc(100% + 10px); + right: 0; + background: var(--gray-800); + color: var(--white); + font-family: var(--font-family-inter); + font-size: var(--fs-xs); + line-height: 1.4; + padding: 10px 14px; + border-radius: 4px; + white-space: nowrap; + pointer-events: none; + z-index: 10; +} + +.btn-primary__tooltip::after { + content: ""; + position: absolute; + top: 100%; + right: 24px; + border: 6px solid transparent; + border-top-color: var(--gray-800); +} + +.btn-primary:disabled { + background: var(--gray-400); + border-color: var(--gray-400); + color: var(--gray-600); + cursor: not-allowed; +} + +.btn-primary:disabled:hover { + background: var(--gray-400); + color: var(--gray-600); +} + +.btn-primary:disabled:hover + .btn-primary__tooltip, +.btn-primary:disabled:focus + .btn-primary__tooltip { + display: block; +} + +@media (max-width: 640px) { + .btn-primary-wrapper { + margin-left: 0; + width: 100%; + } + + .btn-primary__tooltip { + white-space: normal; + width: 260px; + right: 0; + } +} diff --git a/docs/public/projects/deltag-aarhus/mocks/hero-moeller.png b/docs/public/projects/deltag-aarhus/mocks/hero-moeller.png new file mode 100644 index 0000000..9ce7ff0 Binary files /dev/null and b/docs/public/projects/deltag-aarhus/mocks/hero-moeller.png differ diff --git a/docs/public/projects/deltag-aarhus/mocks/index.html b/docs/public/projects/deltag-aarhus/mocks/index.html new file mode 100644 index 0000000..64965cf --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/index.html @@ -0,0 +1,718 @@ + + + + + + Forslag til Lokalplan nr. 1237 — Vindmøller ved Vosnæs | deltag.aarhus.dk + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Dette er en mock — ikke den rigtige side. + Se kildekode + + Variant: + Åben + Afsluttet + +
+ + + + + +
+ Vindmøller i dansk landskab ved Vosnæs +
+ + + + + +
+ Høring +
+
+

Forslag til Lokalplan nr. 1237 med miljøvurderingsrapport — Vindmøller ved Vosnæs

+

Planen udlægger området til tekniske anlæg og giver mulighed for opstilling af tre vindmøller med en totalhøjde på op til 150 meter samt tilhørende arbejdsarealer, veje og nettilslutningsanlæg.

+
+ +
+

Lokalplan, Miljøvurdering, Vosnæs, Grøn energi

+
+ + +
+
+
+

HøringenEn høring er en periode hvor borgere, organisationer og myndigheder kan komme med kommentarer og forslag til et bestemt planforslag, før det vedtages endeligt. Læs mere om høringer vedrører Forslag til Lokalplan nr. 1237 med tilhørende miljøvurderingsrapport for opstilling af vindmøller ved Vosnæs nord for Aarhus. Bemærk at høringssvar først gennemgås efter endt høringsfrist.

+
+
+

Aarhus Kommune har udarbejdet forslag til lokalplan for Vosnæs-området samt tillæg nr. 169 til Kommuneplan 2017Kommuneplanen er kommunens overordnede plan for arealanvendelse de næste 12 år. Den sætter rammerne for hvad der må bygges hvor. Læs mere om kommuneplanen. Der er gennemført en samlet miljøvurderingEn miljøvurdering er en systematisk vurdering af, hvordan et planforslag eller projekt påvirker miljøet — fx natur, støj, landskab og menneskers sundhed. Den er lovpligtig ved væsentlig miljøpåvirkning. Læs mere om miljøvurdering for alle planer og projekter i området.

+

Planerne kan ses på Aarhus Lokalplanportal samt fysisk på Dokk1 Hovedbibliotek fra den 5. juni 2025. Lokalplanen udlægger området til tekniske anlægTekniske anlæg dækker over installationer som vindmøller, solcelleanlæg, transformerstationer og lignende infrastruktur, der er nødvendig for energiforsyning og teknisk drift. Læs mere om tekniske anlæg og muliggør opstilling af tre vindmøller med en totalhøjde på op til 150 meter.

+
+ + +
+ + + +
+ + +
+

Materialer (4)

+
+ + Forslag til Lokalplan nr. 1237 og kommuneplantillæg + + Forhåndsvisning + (PDF - 2.4 mb.) + + + + + Miljøvurderingsrapport + + (PDF - 8.7 mb.) + + + + + Ikke-teknisk resumé af miljøvurdering + + (PDF - 1.2 mb.) + + + + + Forslag til §25-tilladelse til vindmølleprojekt + + (PDF - 0.8 mb.) + + + +
+
+ + +
+
+

Høringssvar (784)

+
+
+ + + + +
+ + Svarfristen er overskredet — der kan ikke længere indsendes høringssvar eller kommentarer. +
+
+ +
+ +
+ +
+ Viser 1 - 16 + +
+
+ + +
+

Statistik

+
+
+

Indkomne høringssvar over tid

+

Antal svar per uge i høringsperioden (5. jun – 14. aug 2025)

+
+ + +
+ +
+
+

Fordeling på kategorier

+

Antal høringssvar fordelt på kategori

+
+ + +
+
+ + +
+
+
+

Geografisk fordeling af høringssvar

+

Kortet viser hvor høringssvarene kommer fra. Større cirkler angiver flere svar fra området.

+
+
+
+
+ + +
+
+

Mere viden om vindenergi og lokalplaner

+
+
+

På aarhus.dk har vi samlet viden om vindmølleprojekter og lokalplanprocessen. Se bl.a. disse sider.

+ Vindenergi i Aarhus Kommune + Hvad er en lokalplan + Miljøvurdering af planer og projekter +
+
+ + + + + +
+
+
+ Aarhus projekter +

Igangværende projekter i Aarhus

+

Se hvilke projekter der aktuelt er igang og som du kan bidrage til som borger i Aarhus Kommune

+ Se igangværende projekter +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/projects/deltag-aarhus/mocks/js/compact-nav.js b/docs/public/projects/deltag-aarhus/mocks/js/compact-nav.js new file mode 100644 index 0000000..d666919 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/js/compact-nav.js @@ -0,0 +1,22 @@ +window.DeltagMock = window.DeltagMock || {}; +var DM = window.DeltagMock; + +/* ========================================================================== + Compact Nav on Scroll + ========================================================================== */ + +DM.initCompactNav = function() { + var nav = document.querySelector(".nav"); + /* Hysteresis: different thresholds for compact/expand prevent jitter during slow scrolling */ + var compactAt = 80; + var expandAt = 30; + + window.addEventListener("scroll", function() { + var y = window.scrollY; + if (!nav.classList.contains("nav--compact") && y > compactAt) { + nav.classList.add("nav--compact"); + } else if (nav.classList.contains("nav--compact") && y < expandAt) { + nav.classList.remove("nav--compact"); + } + }, { passive: true } /* passive: true improves scroll performance by signaling no preventDefault */); +}; diff --git a/docs/public/projects/deltag-aarhus/mocks/js/data.js b/docs/public/projects/deltag-aarhus/mocks/js/data.js new file mode 100644 index 0000000..77dd85e --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/js/data.js @@ -0,0 +1,488 @@ +window.DeltagMock = window.DeltagMock || {}; +var DM = window.DeltagMock; + +/* ========================================================================== + Mock Data — Vindmøller ved Vosnæs + ========================================================================== */ + +DM.horingssvarData = [ + { + id: 1, + title: "Støjgener fra vindmøller truer beboernes sundhed", + description: "Vi er dybt bekymrede over den lavfrekvente støj, som tre 150 meter høje vindmøller vil påføre de omkringliggende beboelser. Studier viser, at lavfrekvent støj og infralyd kan forårsage søvnforstyrrelser, hovedpine og stress.", + fullDescription: "Vi er dybt bekymrede over den lavfrekvente støj, som tre 150 meter høje vindmøller vil påføre de omkringliggende beboelser. Studier viser, at lavfrekvent støj og infralyd kan forårsage søvnforstyrrelser, hovedpine og stress.\n\nDe nuværende støjgrænser tager ikke tilstrækkeligt hensyn til lavfrekvent støj, og der er dokumenteret tilfælde fra andre vindmølleprojekter, hvor naboer har oplevet betydelige helbredsproblemer.\n\nVi kræver, at der gennemføres en uafhængig sundhedsundersøgelse, inden projektet vedtages.", + author: "Maria Jensen", + date: "16. juni 2025", + comments: 12, + likes: 89, + category: "stoej", + commentsList: [ + { author: "Peter Hansen", date: "17. jun 2025", text: "Helt enig. Vi har allerede problemer med støj fra trafik. Vindmøller oveni er uacceptabelt." }, + { author: "Anne Larsen", date: "17. jun 2025", text: "WHO anbefaler maksimalt 40 dB om natten. Det kan ikke overholdes her." }, + { author: "Jens Olsen", date: "18. jun 2025", text: "Min nabo i Nordjylland har vindmøller 800 meter fra huset. Han kan ikke sove." }, + { author: "Mette Friis", date: "18. jun 2025", text: "Har kommunen overhovedet undersøgt sundhedseffekterne? Det tvivler jeg på." }, + { author: "Bo Vang", date: "19. jun 2025", text: "Infralyd er et veldokumenteret problem. Det er ikke noget, man bare kan ignorere." }, + { author: "Karen Winther", date: "20. jun 2025", text: "Der bør laves målinger af eksisterende baggrundsstøj som reference." }, + { author: "Lars Winding", date: "21. jun 2025", text: "Støjberegningerne i rapporten er baseret på ideelle vindforhold. Virkeligheden er anderledes." }, + { author: "Hanne Juhl", date: "22. jun 2025", text: "Vi bor i Studstrup og frygter for vores nattesøvn." }, + { author: "Flemming Toft", date: "23. jun 2025", text: "Skræmmende at se hvor tæt møllerne er på beboelse." }, + { author: "Dorte Kruse", date: "24. jun 2025", text: "Børnenes soverum vender mod Vosnæs. Det er helt uacceptabelt." }, + { author: "Christian Leth", date: "25. jun 2025", text: "Kan kommunen garantere, at ingen beboere vil opleve støjgener? Nej, det kan de ikke." }, + { author: "Ulla Gram", date: "26. jun 2025", text: "Det er en skam at sundhed ikke prioriteres højere end vindenergi." } + ] + }, + { + id: 2, + title: "Landskabet ved Vosnæs er nationalt bevaringsværdigt", + description: "Vosnæs-området er udpeget som bevaringsværdigt kulturlandskab af national betydning. Tre 150 meter høje vindmøller vil fuldstændig ødelægge de visuelle kvaliteter og det unikke kystlandskab.", + fullDescription: "Vosnæs-området er udpeget som bevaringsværdigt kulturlandskab af national betydning. Tre 150 meter høje vindmøller vil fuldstændig ødelægge de visuelle kvaliteter og det unikke kystlandskab.\n\nDet er paradoksalt, at kommunen på den ene side anerkender områdets kulturhistoriske værdi, og på den anden side ønsker at placere industrielle anlæg midt i det.\n\nVisualiseringerne i miljørapporten viser tydeligt, at møllerne vil dominere horisonten i hele området og være synlige fra store dele af Kalø Vig.", + author: "Thomas Pedersen", + date: "18. juni 2025", + comments: 8, + likes: 76, + category: "landskab", + commentsList: [ + { author: "Lisa Møller", date: "19. jun 2025", text: "Vosnæs er et af de smukkeste steder i kommunen. Det må ikke ødelægges." }, + { author: "Karl Sørensen", date: "19. jun 2025", text: "Turister vælger området netop for naturen. Vindmøller skræmmer dem væk." }, + { author: "Rikke Holm", date: "20. jun 2025", text: "Har kommunen vurderet påvirkningen af ejendomsværdier? De vil styrtdykke." }, + { author: "Søren Dahl", date: "21. jun 2025", text: "Vindmøller hører til på havet eller i industriområder, ikke i fredede landskaber." }, + { author: "Camilla Frost", date: "22. jun 2025", text: "Kystlandskabet langs Kalø Vig er enestående. Det kan ikke erstattes." }, + { author: "Henrik Berg", date: "23. jun 2025", text: "Visualiseringerne taler for sig selv. Det er en katastrofe for landskabet." }, + { author: "Tine Holm", date: "24. jun 2025", text: "Fredningsnævnet bør inddrages, hvis ikke allerede sket." }, + { author: "Ole Mortensen", date: "25. jun 2025", text: "Man kan ikke tale om grøn omstilling og samtidig ødelægge naturen." } + ] + }, + { + id: 3, + title: "Truede arter i Vosnæs-området vil blive påvirket", + description: "Området ved Vosnæs er levested for flere truede arter, herunder flagermus og stor vandsalamander. Vindmøller udgør en alvorlig trussel mod flagermusbestanden, som er beskyttet under EU's habitatdirektiv.", + fullDescription: "Området ved Vosnæs er levested for flere truede arter, herunder flagermus og stor vandsalamander. Vindmøller udgør en alvorlig trussel mod flagermusbestanden, som er beskyttet under EU's habitatdirektiv.\n\nMiljøvurderingsrapporten er utilstrækkelig i sin behandling af faunaen. Der er kun foretaget sporadiske undersøgelser, og de er gennemført uden for den primære aktivitetsperiode for flagermus.\n\nVi kræver en grundig, helårsbaseret undersøgelse af dyrelivet, inden der træffes beslutning.", + author: "Birgit Rasmussen", + date: "20. juni 2025", + comments: 10, + likes: 92, + category: "miljoe", + commentsList: [ + { author: "Sven Nielsen", date: "21. jun 2025", text: "Flagermus er strengt beskyttede. Kommunen kan ikke bare ignorere det." }, + { author: "Karen Winther", date: "21. jun 2025", text: "Vi har observeret mindst 5 flagermusarter ved Vosnæs om sommeren." }, + { author: "Lars Winding", date: "22. jun 2025", text: "Stor vandsalamander er Bilag IV-art. Projektet kan være i strid med EU-lovgivning." }, + { author: "Hanne Juhl", date: "23. jun 2025", text: "Havørne fouragerer også i området. Det er dokumenteret." }, + { author: "Flemming Toft", date: "24. jun 2025", text: "Undersøgelserne er lavet i november. Det er helt utilstrækkeligt." }, + { author: "Dorte Kruse", date: "25. jun 2025", text: "Danmark har forpligtet sig til at beskytte biodiversiteten. Det gælder også her." }, + { author: "Christian Leth", date: "26. jun 2025", text: "Der dør tusindvis af flagermus ved vindmøller hvert år. Det er fakta." }, + { author: "Ulla Gram", date: "27. jun 2025", text: "Natura 2000-området ligger kun få kilometer væk. Det er bekymrende." }, + { author: "Per Vestergaard", date: "28. jun 2025", text: "En grundig undersøgelse vil tage mindst et år. Det bør man afvente." }, + { author: "Helle Storm", date: "29. jun 2025", text: "Fugle og flagermus har ingen stemme. Vi må tale for dem." } + ] + }, + { + id: 4, + title: "Ejendomsværdier vil falde drastisk", + description: "Erfaringer fra andre vindmølleprojekter viser, at ejendomspriserne i nærområdet kan falde med 10-30%. For mange familier er boligen den største investering, og et sådant værditab er ødelæggende.", + fullDescription: "Erfaringer fra andre vindmølleprojekter viser, at ejendomspriserne i nærområdet kan falde med 10-30%. For mange familier er boligen den største investering, og et sådant værditab er ødelæggende.\n\nDen nuværende kompensationsordning via Energistyrelsen dækker slet ikke det reelle værditab. Mange naboer vil sidde med en bolig, de ikke kan sælge.\n\nKommunen bør fremlægge en realistisk vurdering af de økonomiske konsekvenser for naboerne, inden projektet vedtages.", + author: "Henrik Christensen", + date: "22. juni 2025", + comments: 15, + likes: 124, + category: "proces", + commentsList: [ + { author: "Mette Friis", date: "22. jun 2025", text: "Vi har lige købt hus i Studstrup. Vindmøller var aldrig nævnt!" }, + { author: "Ole Mortensen", date: "23. jun 2025", text: "Kompensationsordningen er en dårlig vittighed. Den dækker ingenting." }, + { author: "Pia Lund", date: "23. jun 2025", text: "Vi har investeret 3 millioner i vores hus. Det vil vi aldrig se igen." }, + { author: "Rasmus Bloch", date: "24. jun 2025", text: "Bankerne vil ikke låne til boliger nær vindmøller. Det er velkendt." }, + { author: "Niels Eriksen", date: "24. jun 2025", text: "Hvem kompenserer os for det psykiske pres dette projekt medfører?" }, + { author: "Susanne Krog", date: "25. jun 2025", text: "Vi har kontaktet ejendomsmægler. Huset er usælgeligt nu." }, + { author: "Erik Bak", date: "25. jun 2025", text: "Retsligt bør kommunen stå til ansvar for bevidst værdiforringelse." }, + { author: "Vibeke Frost", date: "26. jun 2025", text: "Vores pensionsplan var at sælge huset. Den plan er ødelagt." }, + { author: "Jørgen Dam", date: "27. jun 2025", text: "750 meter fra en 150 meter høj mølle. Hvem vil købe det?" }, + { author: "Lone Vestergaard", date: "28. jun 2025", text: "Et fald på 20% svarer til 600.000 kr for os. Det er vores opsparing." }, + { author: "Annette Holm", date: "29. jun 2025", text: "Andre kommuner har droppet projekter pga. naboprotester. Gør det samme." }, + { author: "Karsten Lund", date: "30. jun 2025", text: "Det er ikke rimeligt at privatpersoner skal bære omkostningen for kommunens klimamål." }, + { author: "Ruth Borg", date: "1. jul 2025", text: "Vi har samlet 1.200 underskrifter mod projektet." }, + { author: "Mark Jensen", date: "2. jul 2025", text: "Syddjurs Kommune har også borgere der protesterer. Det berører alle." }, + { author: "Stine Brink", date: "3. jul 2025", text: "Kommunen bør købe de berørte ejendomme til markedspris inden vindmøller." } + ] + }, + { + id: 5, + title: "Vindressourcen ved Vosnæs er utilstrækkelig", + description: "Vosnæs er ikke et optimalt sted for vindenergi. Vindforholdene er markant dårligere end ved kysten eller til havs, hvilket betyder at møllerne kun vil producere en brøkdel af deres kapacitet.", + fullDescription: "Vosnæs er ikke et optimalt sted for vindenergi. Vindforholdene er markant dårligere end ved kysten eller til havs, hvilket betyder at møllerne kun vil producere en brøkdel af deres kapacitet.\n\nIfølge miljørapporten vil møllerne kun reducere CO2-udledningen med ca. 0,03% af Aarhus' samlede udledning. Det er en forsvindende lille gevinst i forhold til de massive negative konsekvenser for natur, landskab og naboer.\n\nPengene ville give langt mere CO2-reduktion investeret i havvindmøller eller solceller på industritagene.", + author: "Camilla Thomsen", + date: "25. juni 2025", + comments: 7, + likes: 58, + category: "proces", + commentsList: [ + { author: "Anders Berg", date: "26. jun 2025", text: "0,03% CO2-reduktion. Er det virkelig det hele værd?" }, + { author: "Sofie Gram", date: "26. jun 2025", text: "Havvind giver 3-4 gange så meget energi per mølle. Byg dem der." }, + { author: "Peter Hansen", date: "27. jun 2025", text: "Kapacitetsfaktoren for landvind i DK er kun ca. 25%. Det er lavt." }, + { author: "Anne Larsen", date: "28. jun 2025", text: "Solceller på kommunale bygninger ville give mere energi per investeret krone." }, + { author: "Jens Olsen", date: "29. jun 2025", text: "Energistyrelsen anbefaler selv havvind fremfor landvind." }, + { author: "Lisa Møller", date: "30. jun 2025", text: "Vindressourcekortene viser tydeligt at Vosnæs ikke er optimalt." }, + { author: "Karl Sørensen", date: "1. jul 2025", text: "Investér i energieffektivisering i stedet. Det giver mere CO2-reduktion." } + ] + }, + { + id: 6, + title: "Skyggekast og blinkende lys vil genere naboerne", + description: "Vindmøller på 150 meters højde kaster lange skygger, som rammer nabobeboelser i morgen- og aftentimerne. Derudover vil de røde advarselslys være synlige i en radius af mange kilometer om natten.", + fullDescription: "Vindmøller på 150 meters højde kaster lange skygger, som rammer nabobeboelser i morgen- og aftentimerne. Derudover vil de røde advarselslys være synlige i en radius af mange kilometer om natten.\n\nSkyggekast skaber en flimrende effekt, der kan udløse epileptiske anfald og er generelt meget generende. Den maksimale skyggetid på 10 timer om året er sat alt for højt.\n\nDe blinkende røde lys om natten vil forvandle hele landskabet til et industriområde.", + author: "Lars Winding", + date: "27. juni 2025", + comments: 5, + likes: 45, + category: "stoej", + commentsList: [ + { author: "Hanne Juhl", date: "28. jun 2025", text: "Skyggekast er en daglig plage for naboer. 10 timer om året lyder af lidt, men det er koncentreret." }, + { author: "Flemming Toft", date: "29. jun 2025", text: "De røde lys kan ses fra Aarhus. Det er visuelt forurening." }, + { author: "Dorte Kruse", date: "30. jun 2025", text: "Børn sover med gardinerne åbne om sommeren. Blinkende røde lys er forstyrrende." }, + { author: "Christian Leth", date: "1. jul 2025", text: "Radar-baseret behovsstyring af lys findes, men bruges sjældent i DK." }, + { author: "Ulla Gram", date: "2. jul 2025", text: "Vi nyder nattemørket her. Det vil blive ødelagt." } + ] + }, + { + id: 7, + title: "Positiv for den grønne omstilling i Aarhus", + description: "Jeg støtter forslaget om vindmøller ved Vosnæs. Aarhus har brug for at tage ansvar for den grønne omstilling, og landbaseret vindenergi er en vigtig del af løsningen på klimakrisen.", + fullDescription: "Jeg støtter forslaget om vindmøller ved Vosnæs. Aarhus har brug for at tage ansvar for den grønne omstilling, og landbaseret vindenergi er en vigtig del af løsningen på klimakrisen.\n\nDet er let at sige nej til vindmøller i ens egen baghave, men vi har alle et ansvar. Klimaforandringerne rammer os alle, og vi er nødt til at acceptere at vedvarende energi kræver plads.\n\nJeg opfordrer byrådet til at vedtage planen og vise at Aarhus er en kommune der handler.", + author: "Rasmus Bloch", + date: "29. juni 2025", + comments: 9, + likes: 34, + category: "miljoe", + commentsList: [ + { author: "Ida Kvist", date: "30. jun 2025", text: "Enig! Vi kan ikke bare sige nej til alt. Klimaet kræver handling." }, + { author: "Martin Rye", date: "30. jun 2025", text: "Modigt indlæg. De fleste høringssvar er desværre kun negative." }, + { author: "Gitte Skou", date: "1. jul 2025", text: "Men kunne man ikke bygge dem på havet i stedet?" }, + { author: "Mikkel Skov", date: "1. jul 2025", text: "Landvind er billigere end havvind. Det handler også om økonomi." }, + { author: "Sven Nielsen", date: "2. jul 2025", text: "Enig i princippet, men placeringen er forkert. Der er bedre alternativer." }, + { author: "Hanne Juhl", date: "3. jul 2025", text: "Grøn omstilling ja, men ikke på bekostning af beskyttet natur." }, + { author: "Bo Vang", date: "4. jul 2025", text: "Det er nemt at støtte når man bor langt fra møllerne." }, + { author: "Karen Winther", date: "5. jul 2025", text: "Solceller på alle kommunale tage ville give mere energi uden gener." }, + { author: "Lars Winding", date: "6. jul 2025", text: "Respekt for dit synspunkt, men fakta taler imod denne placering." } + ] + }, + { + id: 8, + title: "Skødstrup Skole med 1.350 elever er for tæt på", + description: "Skødstrup Skole ligger inden for få kilometers afstand af de planlagte vindmøller. Med 1.350 elever er det en af kommunens største skoler, og børnenes trivsel bør veje tungt i beslutningen.", + fullDescription: "Skødstrup Skole ligger inden for få kilometers afstand af de planlagte vindmøller. Med 1.350 elever er det en af kommunens største skoler, og børnenes trivsel bør veje tungt i beslutningen.\n\nStøj og skyggekast kan påvirke koncentrationsevnen og læringsmiljøet. Skolens udearealer vender direkte mod Vosnæs.\n\nVi opfordrer byrådet til at tænke på børnene og deres ret til et godt læringsmiljø.", + author: "Niels Eriksen", + date: "1. juli 2025", + comments: 6, + likes: 71, + category: "stoej", + commentsList: [ + { author: "Tine Holm", date: "2. jul 2025", text: "1.350 børn. Det er et kæmpe ansvar at ignorere." }, + { author: "Rasmus Bloch", date: "3. jul 2025", text: "Skolen bør inddrages i høringen som part." }, + { author: "Susanne Krog", date: "4. jul 2025", text: "Vores børn er på skolen 6-8 timer dagligt. De fortjener ro." }, + { author: "Erik Bak", date: "5. jul 2025", text: "Forældrebestyrelsen har indsendt en fælles protest." }, + { author: "Vibeke Frost", date: "6. jul 2025", text: "Støj i klasseværelserne er allerede et problem. Vindmøller forværrer det." }, + { author: "Jørgen Dam", date: "7. jul 2025", text: "Hvad med børnenes ret til sundhed og trivsel? FN's børnekonvention?" } + ] + }, + { + id: 9, + title: "Procedurefejl i høringsprocessen", + description: "Vi mener, at høringsprocessen er behæftet med væsentlige procedurefejl. Borgermødet var utilstrækkeligt informeret, og vigtige dokumenter blev først tilgængelige kort før høringsfristen.", + fullDescription: "Vi mener, at høringsprocessen er behæftet med væsentlige procedurefejl. Borgermødet var utilstrækkeligt informeret, og vigtige dokumenter blev først tilgængelige kort før høringsfristen.\n\nDesuden er høringsfristen for kort i forhold til materialets omfang. Miljøvurderingsrapporten alene er på flere hundrede sider, og borgerne har reelt ikke haft tid til at sætte sig ordentligt ind i materialet.\n\nVi kræver, at høringsfristen forlænges med minimum 4 uger.", + author: "Susanne Krog", + date: "3. juli 2025", + comments: 8, + likes: 53, + category: "proces", + commentsList: [ + { author: "Bo Vang", date: "4. jul 2025", text: "Dokumenterne er umulige at gennemskue for almindelige borgere." }, + { author: "Rikke Holm", date: "4. jul 2025", text: "Borgermødet var en fiasko. Ingen reelle svar fra kommunen." }, + { author: "Søren Dahl", date: "5. jul 2025", text: "Høringsfristen bør absolut forlænges. Materialet er enormt." }, + { author: "Camilla Frost", date: "6. jul 2025", text: "Vi modtog brev om høringen kun 2 uger inden fristen." }, + { author: "Henrik Berg", date: "7. jul 2025", text: "E-Boks-brevene blev sendt i etaper. Mange modtog dem sent." }, + { author: "Tine Holm", date: "8. jul 2025", text: "Kommunen bør holde mindst tre borgermøder, ikke bare ét." }, + { author: "Ole Mortensen", date: "9. jul 2025", text: "Transparens i processen er fuldstændig fraværende." }, + { author: "Ida Kvist", date: "10. jul 2025", text: "Mange ældre borgere har svært ved at navigere i det digitale materiale." } + ] + }, + { + id: 10, + title: "Alternativ placering på havnen bør undersøges", + description: "I stedet for at placere vindmøller i et bevaringsværdigt naturområde, bør kommunen undersøge mulighederne for placering på eller nær Aarhus Havn, hvor der allerede er industriel aktivitet.", + fullDescription: "I stedet for at placere vindmøller i et bevaringsværdigt naturområde, bør kommunen undersøge mulighederne for placering på eller nær Aarhus Havn, hvor der allerede er industriel aktivitet.\n\nHavnen ville give bedre vindforhold, ingen naboklager og ingen påvirkning af beskyttet natur. Det er uforståeligt, at denne mulighed ikke er undersøgt mere grundigt.\n\nKommunen bør fremlægge en sammenlignende analyse af alternative placeringer.", + author: "Erik Bak", + date: "5. juli 2025", + comments: 4, + likes: 67, + category: "proces", + commentsList: [ + { author: "Gitte Skou", date: "6. jul 2025", text: "Havnen er det oplagte sted. Mere vind, ingen naboer, ingen natur at ødelægge." }, + { author: "Vibeke Frost", date: "7. jul 2025", text: "Industriområder bør altid prioriteres over naturområder." }, + { author: "Jørgen Dam", date: "8. jul 2025", text: "Offshore vindmøller er fremtiden. Landvind er forældet teknologi." }, + { author: "Lone Vestergaard", date: "9. jul 2025", text: "Kommunen har aldrig ordentligt forklaret hvorfor netop Vosnæs er valgt." } + ] + }, + { + id: 11, + title: "Turismen i området vil lide skade", + description: "Vosnæs og Kalø Vig-området tiltrækker tusindvis af turister årligt. Vindmøller på 150 meter vil ødelægge den naturskønne oplevelse, som er grundlaget for turismen i området.", + fullDescription: "Vosnæs og Kalø Vig-området tiltrækker tusindvis af turister årligt. Vindmøller på 150 meter vil ødelægge den naturskønne oplevelse, som er grundlaget for turismen i området.\n\nLokale overnatningssteder, restauranter og aktivitetsudbydere er afhængige af turismen. Et fald i besøgende vil ramme hele den lokale økonomi.\n\nKommunen bør vurdere de økonomiske konsekvenser for turismesektoren.", + author: "Vibeke Frost", + date: "7. juli 2025", + comments: 3, + likes: 41, + category: "landskab", + commentsList: [ + { author: "Mikkel Skov", date: "8. jul 2025", text: "Vi driver B&B nær Vosnæs. Vindmøller vil koste os gæster." }, + { author: "Sven Nielsen", date: "9. jul 2025", text: "Kalø Slotsruin er en stor attraktion. Vindmøller i baggrunden er grotesk." }, + { author: "Hanne Juhl", date: "10. jul 2025", text: "Molslaboratoriet og nationalparken trækker naturturister. Vindmøller passer ikke ind." } + ] + }, + { + id: 12, + title: "Børn og unge siger nej til vindmøller ved Vosnæs", + description: "Vi er elever fra Skødstrup Skole og vi vil gerne have lov til at lege udenfor uden støj fra vindmøller. Vi er bange for at det bliver ubehageligt at være udenfor.", + fullDescription: "Vi er elever fra Skødstrup Skole og vi vil gerne have lov til at lege udenfor uden støj fra vindmøller. Vi er bange for at det bliver ubehageligt at være udenfor.\n\nVores lærere har fortalt os om høringen, og vi synes det er vigtigt at børn også bliver hørt. Vi har samlet underskrifter fra 200 elever, der er imod vindmøller så tæt på vores skole.\n\nVi elsker naturen ved Vosnæs og vil gerne have den kan forblive som den er.", + author: "8.B, Skødstrup Skole", + date: "9. juli 2025", + comments: 11, + likes: 156, + category: "stoej", + commentsList: [ + { author: "Anders Berg", date: "10. jul 2025", text: "Fantastisk at børnene engagerer sig! De har ret til at blive hørt." }, + { author: "Sofie Gram", date: "10. jul 2025", text: "200 underskrifter fra elever. Det bør veje tungt." }, + { author: "Peter Hansen", date: "11. jul 2025", text: "Børneperspektivet mangler fuldstændig i miljørapporten." }, + { author: "Anne Larsen", date: "11. jul 2025", text: "Det er rørende at børnene tager ansvar. Politikerne bør lytte." }, + { author: "Jens Olsen", date: "12. jul 2025", text: "FN's børnekonvention artikel 12: børn har ret til at blive hørt." }, + { author: "Lisa Møller", date: "13. jul 2025", text: "Mine børn går også på Skødstrup Skole. Vi bakker 100% op." }, + { author: "Karl Sørensen", date: "14. jul 2025", text: "Stærkt indlæg! Børnenes fremtid bør prioriteres." }, + { author: "Rikke Holm", date: "15. jul 2025", text: "Håber byrådet læser dette grundigt." }, + { author: "Søren Dahl", date: "16. jul 2025", text: "Børnene forstår hvad der er på spil. Det gør politikerne tilsyneladende ikke." }, + { author: "Camilla Frost", date: "17. jul 2025", text: "Vi er stolte af jer! Bliv ved med at kæmpe." }, + { author: "Henrik Berg", date: "18. jul 2025", text: "Mest bevægende høringssvar hidtil. Tak, 8.B!" } + ] + }, + { + id: 13, + title: "Foreningen mod vindmøller ved Vosnæs: samlet indsigelse", + description: "Som forening med over 800 medlemmer indgiver vi hermed en samlet indsigelse mod Forslag til Lokalplan nr. 1237. Vi mener, at projektet er i strid med planlovens intentioner om borgerbeskyttelse.", + fullDescription: "Som forening med over 800 medlemmer indgiver vi hermed en samlet indsigelse mod Forslag til Lokalplan nr. 1237. Vi mener, at projektet er i strid med planlovens intentioner om borgerbeskyttelse.\n\nVi har engageret uafhængige eksperter i støj, natur og landskab, som alle konkluderer, at projektet vil have uacceptable konsekvenser for området.\n\nVi forbeholder os ret til at indbringe sagen for Planklagenævnet og om nødvendigt domstolene, hvis byrådet vedtager planen.\n\nVores juridiske rådgiver vurderer, at der er væsentlige mangler i miljøvurderingen.", + author: "Foreningen mod vindmøller ved Vosnæs", + date: "11. juli 2025", + comments: 14, + likes: 203, + category: "proces", + commentsList: [ + { author: "Karsten Lund", date: "12. jul 2025", text: "800 medlemmer er et stærkt signal. Byrådet kan ikke ignorere det." }, + { author: "Ruth Borg", date: "12. jul 2025", text: "Juridisk bistand er afgørende. Godt at foreningen tager det alvorligt." }, + { author: "Mark Jensen", date: "13. jul 2025", text: "Planklagenævnet har omgjort lignende sager. Der er håb." }, + { author: "Stine Brink", date: "13. jul 2025", text: "Jeg melder mig ind i foreningen. Sammen er vi stærkere." }, + { author: "Lars Winding", date: "14. jul 2025", text: "De uafhængige ekspertrapporter bør vedhæftes som bilag." }, + { author: "Hanne Juhl", date: "14. jul 2025", text: "Syddjurs Kommune bør også protestere. Deres borgere rammes også." }, + { author: "Flemming Toft", date: "15. jul 2025", text: "Kommunen bør betale for den juridiske kamp. Det er deres ansvar." }, + { author: "Dorte Kruse", date: "16. jul 2025", text: "Vi støtter foreningen økonomisk og moralsk." }, + { author: "Christian Leth", date: "17. jul 2025", text: "En retssag er dyrt, men nødvendigt hvis demokratiet svigter." }, + { author: "Ulla Gram", date: "18. jul 2025", text: "Pressen bør dække denne sag mere intensivt." }, + { author: "Per Vestergaard", date: "19. jul 2025", text: "Foreningen gør et vigtigt arbejde for hele lokalsamfundet." }, + { author: "Helle Storm", date: "20. jul 2025", text: "Tak for jeres engagement. Vi bakker jer op." }, + { author: "Knud Erik", date: "21. jul 2025", text: "Donerede 500 kr til den juridiske fond. Hver krone tæller." }, + { author: "Søren Dahl", date: "22. jul 2025", text: "Folkelig modstand i denne skala bør respekteres af et demokratisk byråd." } + ] + }, + { + id: 14, + title: "Studstrup Borgerforening: vindmøller truer vores landsby", + description: "Studstrup Borgerforening repræsenterer 450 husstande i umiddelbar nærhed af de planlagte vindmøller. Vi frygter for landsbyens fremtid og beboernes livskvalitet.", + fullDescription: "Studstrup Borgerforening repræsenterer 450 husstande i umiddelbar nærhed af de planlagte vindmøller. Vi frygter for landsbyens fremtid og beboernes livskvalitet.\n\nStudstrup er en attraktiv landsby med tilflytning og et stærkt fællesskab. Vindmøller vil gøre det svært at tiltrække nye beboere og kan føre til fraflytning.\n\nVi opfordrer byrådet til at beskytte de borgere, der allerede bor her, frem for at tilgodese et vindmølleprojekt med minimal klimaeffekt.", + author: "Studstrup Borgerforening", + date: "13. juli 2025", + comments: 6, + likes: 98, + category: "landskab", + commentsList: [ + { author: "Knud Erik", date: "14. jul 2025", text: "450 husstande. Det er et helt lokalsamfund der protesterer." }, + { author: "Ruth Borg", date: "15. jul 2025", text: "Studstrup er et fantastisk sted at bo. Det skal det forblive." }, + { author: "Mark Jensen", date: "16. jul 2025", text: "Vi flyttede til Studstrup for naturen. Vindmøller var ikke med i planerne." }, + { author: "Stine Brink", date: "17. jul 2025", text: "Kommunen lover byudvikling i Studstrup OG vindmøller. Det hænger ikke sammen." }, + { author: "Annette Holm", date: "18. jul 2025", text: "Borgerforeningens rolle er at beskytte fællesskabet. Det gør I her." }, + { author: "Karsten Lund", date: "19. jul 2025", text: "Lokalpolitikerne bør komme ud og møde borgerne ansigt til ansigt." } + ] + }, + { + id: 15, + title: "Syddjurs Kommune: vores borgere rammes også", + description: "Som nabokommune til Aarhus vil vi påpege, at vindmøllerne ved Vosnæs også vil påvirke borgere i Syddjurs Kommune. Vi bør have været inddraget langt tidligere i processen.", + fullDescription: "Som nabokommune til Aarhus vil vi påpege, at vindmøllerne ved Vosnæs også vil påvirke borgere i Syddjurs Kommune. Vi bør have været inddraget langt tidligere i processen.\n\nFlere af vores borgere i Rønde-området vil kunne se og høre vindmøllerne fra deres boliger. De har samme ret til at blive hørt som Aarhus-borgere.\n\nVi opfordrer til et tværkommunalt samarbejde om placering af vindmøller i regionen.", + author: "Borgere fra Syddjurs Kommune", + date: "1. august 2025", + comments: 4, + likes: 55, + category: "proces", + commentsList: [ + { author: "Per Vestergaard", date: "2. aug 2025", text: "Kommunegrænser stopper ikke støj og visuel påvirkning." }, + { author: "Helle Storm", date: "3. aug 2025", text: "Vi i Rønde kan tydeligt se Vosnæs. 150 meter høje møller vil dominere horisonten." }, + { author: "Knud Erik", date: "4. aug 2025", text: "Tværkommunalt samarbejde er fornuftigt. Vindmøller påvirker hele regionen." }, + { author: "Ruth Borg", date: "5. aug 2025", text: "Regionens naturværdier tilhører alle, ikke kun Aarhus Kommune." } + ] + }, + { + id: 16, + title: "Vi vil have medbestemmelse over vores nærområde", + description: "Den demokratiske proces omkring dette vindmølleprojekt har været dybt utilfredsstillende. Borgerne føler sig overhørt og magtesløse over for en beslutning, der fundamentalt ændrer vores hverdag.", + fullDescription: "Den demokratiske proces omkring dette vindmølleprojekt har været dybt utilfredsstillende. Borgerne føler sig overhørt og magtesløse over for en beslutning, der fundamentalt ændrer vores hverdag.\n\nVi kræver en folkeafstemning i de berørte distrikter. Det er vores nærområde, og vi bør have en reel stemme i beslutningen.\n\nEt enkelt borgermøde og en digital høringsperiode er ikke tilstrækkelig inddragelse, når det drejer sig om et projekt af denne størrelse.", + author: "Karsten Lund", + date: "10. august 2025", + comments: 13, + likes: 87, + category: "proces", + commentsList: [ + { author: "Ulla Gram", date: "10. aug 2025", text: "Demokrati handler om mere end at stemme hvert fjerde år." }, + { author: "Per Vestergaard", date: "11. aug 2025", text: "En folkeafstemning er den eneste retfærdige løsning." }, + { author: "Helle Storm", date: "11. aug 2025", text: "Vi skal have mere borgermøder og workshops." }, + { author: "Knud Erik", date: "12. aug 2025", text: "Kommunen taler om borgerinddragelse, men handler ikke derefter." }, + { author: "Søren Dahl", date: "12. aug 2025", text: "Online høring er fint, men det erstatter ikke fysisk dialog." }, + { author: "Camilla Frost", date: "13. aug 2025", text: "Mange ældre borgere deltager ikke digitalt. De overses." }, + { author: "Henrik Berg", date: "13. aug 2025", text: "Borgermødet var overbooket. Hundredevis fik ikke plads." }, + { author: "Tine Holm", date: "13. aug 2025", text: "Minimum tre borgermøder burde være standard for denne type projekter." }, + { author: "Ole Mortensen", date: "14. aug 2025", text: "Kommunen bør opsøge borgerne, ikke omvendt." }, + { author: "Ida Kvist", date: "14. aug 2025", text: "Børn og unge bør også have en stemme. Det er deres fremtid." }, + { author: "Martin Rye", date: "14. aug 2025", text: "Et permanent borgerpanel ville sikre løbende dialog." }, + { author: "Gitte Skou", date: "14. aug 2025", text: "Referater og beslutningsnotater bør offentliggøres straks." }, + { author: "Mikkel Skov", date: "14. aug 2025", text: "784 høringssvar og overvældende modstand. Lyt til borgerne!" } + ] + } +]; + +/* ========================================================================== + Mock Data Generator + ========================================================================== */ + +DM.generateMockData = function(baseData, totalCount) { + if (baseData.length >= totalCount) return baseData; + + var titles = [ + "Støjgenerne er uacceptable for naboer", "Beskyt det bevaringsværdige landskab", + "Flagermus og fugle trues af vindmøller", "Ejendomsværdierne vil falde markant", + "Vindressourcen er for lav på denne placering", "Skyggekast rammer vores boliger", + "Børnenes trivsel bør prioriteres", "Høringsprocessen er mangelfuld", + "Alternativ placering bør undersøges", "Turismen vil blive skadet", + "Naturen ved Kalø Vig skal bevares", "Infralyden er sundhedsskadelig", + "Kommunen bør vælge solenergi i stedet", "Klimaeffekten er minimal", + "Kompensationsordningen er utilstrækkelig", "Visuel forurening af kysten", + "Grundvandet kan blive forurenet", "Tværkommunalt samarbejde mangler", + "Folkeafstemning i berørte områder", "Vindmøller hører til på havet", + "Røde advarselslys generer om natten", "Byggeperioden vil skabe massive gener", + "Vores landsby mister sin identitet", "150 meter er for højt til dette område", + "Lokalplanen strider mod kommuneplanen", "Naboerstatningen er en hån", + "Fuglenes trækruter krydser området", "Støjgrænser overholdes ikke i praksis", + "Kulturhistoriske værdier truet", "Effekten på lokale erhverv er ikke vurderet", + "Havvindmøller er et bedre alternativ", "Vi kræver uafhængig støjmåling", + "Rekreative områder forringes", "Risikovurderingen er mangelfuld", + "Iskast fra møller udgør en sikkerhedsrisiko", "Nattemørket ødelægges af lys", + "Energistyrelsen fraråder denne type placering", "Vores børn kan ikke lege udenfor", + "Lokal modstand bør respekteres", "Vi bakker op om grøn omstilling — men ikke her" + ]; + + var descriptions = [ + "Vi er dybt bekymrede over konsekvenserne af vindmøller så tæt på beboelse. Støj, skyggekast og visuel påvirkning vil forringe vores livskvalitet markant.", + "Vosnæs-området er udpeget som bevaringsværdigt og bør beskyttes mod industrielle anlæg. Vindmøller hører ikke hjemme i dette landskab.", + "Kommunen bør undersøge alternative placeringer grundigt, inden der træffes beslutning om vindmøller ved Vosnæs. Havnen eller industriområder er bedre egnet.", + "De truede dyrearter i området fortjener beskyttelse. Flagermus og fugle vil blive påvirket alvorligt af vindmøller på denne placering.", + "Høringsprocessen har været utilstrækkelig og udemokratisk. Borgerne fortjener mere tid og bedre muligheder for at blive hørt.", + "Ejendomsværdierne i nærområdet vil falde drastisk. Det er uacceptabelt at privatpersoner skal bære omkostningen for kommunens klimamål.", + "Vi støtter den grønne omstilling, men denne placering er forkert. Der findes bedre alternativer med færre negative konsekvenser.", + "Klimaeffekten af tre vindmøller er minimal i det store billede. Investeringen ville give mere CO2-reduktion brugt på andre tiltag.", + "Børnenes trivsel og sundhed bør veje tungt i beslutningen. Skoler og institutioner i nærområdet vil blive påvirket.", + "Lokal turisme og erhvervsliv vil lide skade. Vosnæs og Kalø Vig er kendt for sin naturskønhed, ikke for industrianlæg." + ]; + + var authors = [ + "Maria Jensen", "Thomas Pedersen", "Birgit Rasmussen", "Henrik Christensen", + "Camilla Thomsen", "Lars Winding", "Rasmus Bloch", "Niels Eriksen", + "Susanne Krog", "Erik Bak", "Vibeke Frost", "Jørgen Dam", + "Flemming Toft", "Lone Vestergaard", "Annette Holm", "Karsten Lund", + "Mette Friis", "Ole Mortensen", "Pia Lund", "Karen Winther", + "Peter Hansen", "Anne Larsen", "Jens Olsen", "Lisa Møller", + "Karl Sørensen", "Rikke Holm", "Søren Dahl", "Bo Vang", + "Dorte Kruse", "Christian Leth", "Ulla Gram", "Per Vestergaard", + "Helle Storm", "Knud Erik", "Ruth Borg", "Mark Jensen", + "Stine Brink", "Tine Holm", "Ida Kvist", "Martin Rye", + "Gitte Skou", "Mikkel Skov", "Sven Nielsen", "Hanne Juhl", + "Anders Berg", "Sofie Gram", "Camilla Frost", "Henrik Berg" + ]; + + var categories = ["miljoe", "stoej", "landskab", "proces"]; + + var commentTexts = [ + "Helt enig. Vindmøllerne vil ødelægge vores nærområde.", "Det bør undersøges nærmere af uafhængige eksperter.", + "Vi har lignende erfaringer fra andre vindmølleprojekter.", "Godt formuleret. Håber politikerne lytter denne gang.", + "Der er brug for handling mod dette projekt.", "Kan kun støtte dette synspunkt fuldt ud.", + "Kommunen har ignoreret borgernes bekymringer i årevis.", "En uvildig undersøgelse af støj og natur er nødvendig.", + "Vi bør stå sammen som lokalsamfund i denne sag.", "Det handler om vores sundhed og livskvalitet.", + "Politikerne bør komme ud og se forholdene med egne øjne.", "Naturen ved Vosnæs er uerstattelig.", + "Grøn omstilling skal ikke ske på bekostning af borgerne.", "Vi skal finde alternativer der ikke ødelægger landskabet.", + "Tak for at bringe dette op. Vi tænker det samme i vores husstand." + ]; + + var months = ["januar", "februar", "marts", "april", "maj", "juni", + "juli", "august", "september", "oktober", "november", "december"]; + + /* Location suffixes for title variation */ + var locations = [ + "Vosnæs", "Studstrup", "Skødstrup", "Løgten", "Risskov", + "Hjortshøj", "Lystrup", "Rønde", "Hornslet", "Mørke" + ]; + + /* Seeded PRNG for reproducible mock data — same seed always produces the same items */ + function seededRandom(seed) { + var x = Math.sin(seed) * 10000; + return x - Math.floor(x); + } + + var result = baseData.slice(); + + for (var i = baseData.length + 1; i <= totalCount; i++) { + var r = function(n) { return seededRandom(i * 997 + n); }; + var pick = function(arr, n) { return arr[Math.floor(r(n) * arr.length)]; }; + + var titleBase = pick(titles, 1); + /* Hearing period: 5 Jun — 14 Aug 2025 */ + var month = 5 + Math.floor(r(2) * 3); + var day = 1 + Math.floor(r(3) * 28); + var year = 2025; + var commentCount = Math.floor(r(5) * 30); + var likeCount = Math.floor(r(6) * 80); + + var numComments = 1 + Math.floor(r(7) * 5); + var commentsList = []; + for (var c = 0; c < numComments; c++) { + var cDay = 1 + Math.floor(seededRandom(i * 113 + c * 7) * 28); + var cMonth = month < 11 ? month + 1 : 0; + var shortMonth = months[cMonth].substring(0, 3); + commentsList.push({ + author: pick(authors, 10 + c), + date: cDay + ". " + shortMonth + " " + year, + text: pick(commentTexts, 20 + c) + }); + } + + result.push({ + id: i, + title: titleBase, + description: pick(descriptions, 8), + fullDescription: pick(descriptions, 8) + "\n\n" + pick(descriptions, 9), + author: pick(authors, 11), + date: day + ". " + months[month] + " " + year, + comments: commentCount, + likes: likeCount, + category: pick(categories, 12), + commentsList: commentsList + }); + } + + return result; +}; + +// Generate and append mock data to reach 784 total +DM.horingssvarData.push.apply( + DM.horingssvarData, + DM.generateMockData(DM.horingssvarData, 784).slice(DM.horingssvarData.length) +); diff --git a/docs/public/projects/deltag-aarhus/mocks/js/dropdown.js b/docs/public/projects/deltag-aarhus/mocks/js/dropdown.js new file mode 100644 index 0000000..2778366 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/js/dropdown.js @@ -0,0 +1,55 @@ +window.DeltagMock = window.DeltagMock || {}; +var DM = window.DeltagMock; + +/* ========================================================================== + Dropdowns + ========================================================================== */ + +DM.initDropdowns = function() { + document.querySelectorAll(".dropdown").forEach(function(dropdown) { + var button = dropdown.querySelector(".dropdown__button"); + var menu = dropdown.querySelector(".dropdown__menu"); + var items = dropdown.querySelectorAll(".dropdown__item"); + var buttonText = dropdown.querySelector(".dropdown__button-text"); + + button.addEventListener("click", function(e) { + e.stopPropagation(); + // Close other dropdowns + document.querySelectorAll(".dropdown__menu--open").forEach(function(m) { + if (m !== menu) m.classList.remove("dropdown__menu--open"); + }); + document.querySelectorAll('.dropdown__button[aria-expanded="true"]').forEach(function(b) { + if (b !== button) b.setAttribute("aria-expanded", "false"); + }); + + var isOpen = menu.classList.toggle("dropdown__menu--open"); + button.setAttribute("aria-expanded", isOpen); + }); + + items.forEach(function(item) { + item.addEventListener("click", function() { + var value = item.dataset.value; + buttonText.textContent = item.textContent; + + items.forEach(function(i) { + i.classList.remove("dropdown__item--active"); + i.setAttribute("aria-selected", "false"); + }); + item.classList.add("dropdown__item--active"); + item.setAttribute("aria-selected", "true"); + + menu.classList.remove("dropdown__menu--open"); + button.setAttribute("aria-expanded", "false"); + + if (dropdown.dataset.dropdown === "sort") { + DM.state.currentSort = value; + } else if (dropdown.dataset.dropdown === "category") { + DM.state.currentCategory = value; + } + + DM.state.visibleCount = DM.state.ITEMS_PER_PAGE; + DM.renderGrid(); + }); + }); + }); +}; diff --git a/docs/public/projects/deltag-aarhus/mocks/js/glossary.js b/docs/public/projects/deltag-aarhus/mocks/js/glossary.js new file mode 100644 index 0000000..8941580 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/js/glossary.js @@ -0,0 +1,32 @@ +window.DeltagMock = window.DeltagMock || {}; +var DM = window.DeltagMock; + +/* ========================================================================== + Glossary Tooltips + ========================================================================== */ + +DM.initGlossary = function() { + document.querySelectorAll(".glossary-term__icon").forEach(function(btn) { + btn.addEventListener("click", function(e) { + e.stopPropagation(); + var term = btn.closest(".glossary-term"); + var wasOpen = term.classList.contains("glossary-term--open"); + + // Close all open tooltips + document.querySelectorAll(".glossary-term--open").forEach(function(t) { + t.classList.remove("glossary-term--open"); + }); + + if (!wasOpen) { + term.classList.add("glossary-term--open"); + } + }); + }); + + // Close tooltips on outside click + document.addEventListener("click", function() { + document.querySelectorAll(".glossary-term--open").forEach(function(t) { + t.classList.remove("glossary-term--open"); + }); + }); +}; diff --git a/docs/public/projects/deltag-aarhus/mocks/js/horingssvar.js b/docs/public/projects/deltag-aarhus/mocks/js/horingssvar.js new file mode 100644 index 0000000..2f6bc54 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/js/horingssvar.js @@ -0,0 +1,110 @@ +window.DeltagMock = window.DeltagMock || {}; +var DM = window.DeltagMock; + +/* ========================================================================== + Rendering + ========================================================================== */ + +function sortData(data, sortBy) { + var sorted = data.slice(); + switch (sortBy) { + case "likes": + sorted.sort(function(a, b) { return b.likes - a.likes; }); + break; + case "newest": + sorted.sort(function(a, b) { return b.id - a.id; }); + break; + case "oldest": + sorted.sort(function(a, b) { return a.id - b.id; }); + break; + case "comments": + sorted.sort(function(a, b) { return b.comments - a.comments; }); + break; + } + return sorted; +} + +function filterData(data, category) { + if (category === "all") return data; + return data.filter(function(item) { return item.category === category; }); +} + +DM.formatNumber = function(num) { + return num.toLocaleString("da-DK"); +}; + +function createCard(item, index) { + var card = document.createElement("article"); + card.className = "horingssvar-card"; + card.setAttribute("role", "button"); + card.setAttribute("tabindex", "0"); + card.setAttribute("aria-label", "Åbn høringssvar: " + item.title); + card.dataset.index = index; + + var isOpen = DM.state.variant === "open"; + var likeClass = isOpen ? "horingssvar-card__like-btn" : "horingssvar-card__meta-item"; + var likeTag = isOpen ? "button" : "span"; + + card.innerHTML = + '

' + item.title + '

' + + '

' + item.description + '

' + + '
' + + '' + + ' ' + + DM.formatNumber(item.comments) + ' kommentarer' + + '' + + '<' + likeTag + ' class="' + likeClass + '" data-item-id="' + item.id + '">' + + ' ' + + '' + DM.formatNumber(item.likes) + ' synes om' + + '' + + '
'; + + if (isOpen) { + var likeBtn = card.querySelector(".horingssvar-card__like-btn"); + likeBtn.addEventListener("click", function(e) { + e.stopPropagation(); + item.likes++; + likeBtn.querySelector(".horingssvar-card__like-count").textContent = DM.formatNumber(item.likes); + likeBtn.classList.add("horingssvar-card__like-btn--liked"); + likeBtn.disabled = true; + }); + } + + card.addEventListener("click", function() { DM.openModal(index); }); + card.addEventListener("keydown", function(e) { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + DM.openModal(index); + } + }); + + return card; +} + +DM.renderGrid = function() { + var state = DM.state; + var grid = document.getElementById("horingssvar-grid"); + var countEl = document.getElementById("horingssvar-count"); + var paginationCount = document.getElementById("pagination-count"); + var showMoreBtn = document.getElementById("show-all-btn"); + + state.filteredData = sortData(filterData(DM.horingssvarData, state.currentCategory), state.currentSort); + + grid.innerHTML = ""; + var shown = Math.min(state.visibleCount, state.filteredData.length); + var displayData = state.filteredData.slice(0, shown); + + displayData.forEach(function(item, index) { + grid.appendChild(createCard(item, index)); + }); + + countEl.textContent = state.filteredData.length; + paginationCount.textContent = "Viser 1 - " + shown + " af " + state.filteredData.length; + + if (shown >= state.filteredData.length) { + showMoreBtn.style.display = "none"; + } else { + showMoreBtn.textContent = "Vis flere"; + showMoreBtn.style.display = ""; + } +}; diff --git a/docs/public/projects/deltag-aarhus/mocks/js/main.js b/docs/public/projects/deltag-aarhus/mocks/js/main.js new file mode 100644 index 0000000..d93ed89 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/js/main.js @@ -0,0 +1,83 @@ +window.DeltagMock = window.DeltagMock || {}; +var DM = window.DeltagMock; + +/* ========================================================================== + Initialization + ========================================================================== */ + +function init() { + var state = DM.state; + var overlay = document.getElementById("modal-overlay"); + + DM.renderGrid(); + DM.initDropdowns(); + + // Show More + document.getElementById("show-all-btn").addEventListener("click", function() { + state.visibleCount += state.ITEMS_PER_PAGE; + DM.renderGrid(); + }); + + // Modal close + overlay.querySelector(".modal__close").addEventListener("click", DM.closeModal); + + overlay.addEventListener("click", function(e) { + if (e.target === overlay) DM.closeModal(); + }); + + // Modal navigation + document.getElementById("modal-prev").addEventListener("click", function() { DM.navigateModal(-1); }); + document.getElementById("modal-next").addEventListener("click", function() { DM.navigateModal(1); }); + + // Keyboard + document.addEventListener("keydown", function(e) { + if (state.currentModalIndex === -1) return; + + switch (e.key) { + case "Escape": + DM.closeModal(); + break; + case "ArrowLeft": + DM.navigateModal(-1); + break; + case "ArrowRight": + DM.navigateModal(1); + break; + } + }); + + // Focus trap in modal + DM.initFocusTrap(); + + // Close dropdowns on outside click + document.addEventListener("click", function() { + document.querySelectorAll(".dropdown__menu--open").forEach(function(m) { + m.classList.remove("dropdown__menu--open"); + }); + document.querySelectorAll('.dropdown__button[aria-expanded="true"]').forEach(function(b) { + b.setAttribute("aria-expanded", "false"); + }); + }); + + // Deep-link: open modal from URL hash + var hash = window.location.hash; + if (hash.indexOf("#svar-") === 0) { + var id = parseInt(hash.replace("#svar-", ""), 10); + var index = state.filteredData.findIndex(function(item) { return item.id === id; }); + if (index !== -1) { + DM.openModal(index); + } + } +} + +document.addEventListener("DOMContentLoaded", function() { + init(); + DM.initMap(); + DM.initMaterialModal(); + DM.initDecisionModal(); + DM.initGlossary(); + DM.initCompactNav(); + DM.initVariant(); + DM.initMitIDModal(); + DM.initSubmissionModal(); +}); diff --git a/docs/public/projects/deltag-aarhus/mocks/js/map.js b/docs/public/projects/deltag-aarhus/mocks/js/map.js new file mode 100644 index 0000000..85e9700 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/js/map.js @@ -0,0 +1,53 @@ +window.DeltagMock = window.DeltagMock || {}; +var DM = window.DeltagMock; + +/* ========================================================================== + Map + ========================================================================== */ + +DM.initMap = function() { + var map = L.map("map", { scrollWheelZoom: false }).setView([56.18, 10.15], 12); + + L.tileLayer("https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png", { + attribution: '© OpenStreetMap © CARTO', + subdomains: "abcd", + maxZoom: 20, + }).addTo(map); + + /* Hearing response locations on land around Skødstrup, Løgten and Aarhus. + All points placed well inland to avoid the bay. */ + var responseLocations = [ + { lat: 56.209, lng: 10.148, count: 45, label: "Skødstrup" }, + { lat: 56.192, lng: 10.175, count: 32, label: "Løgten" }, + { lat: 56.155, lng: 10.210, count: 28, label: "Aarhus C" }, + { lat: 56.178, lng: 10.120, count: 22, label: "Lystrup" }, + { lat: 56.168, lng: 10.195, count: 18, label: "Risskov" }, + { lat: 56.218, lng: 10.105, count: 15, label: "Hjortshøj" }, + { lat: 56.148, lng: 10.125, count: 12, label: "Brabrand" }, + { lat: 56.195, lng: 10.095, count: 10, label: "Trige" }, + { lat: 56.162, lng: 10.165, count: 8, label: "Vejlby" }, + { lat: 56.140, lng: 10.165, count: 6, label: "Viby" }, + { lat: 56.175, lng: 10.080, count: 4, label: "Sabro" }, + ]; + + var maxCount = Math.max.apply(null, responseLocations.map(function(l) { return l.count; })); + + responseLocations.forEach(function(loc) { + var ratio = loc.count / maxCount; + var radius = 20 + ratio * 60; + var opacity = 0.25 + ratio * 0.4; + + L.circleMarker([loc.lat, loc.lng], { + radius: radius, + fillColor: "#3661d8", + color: "#3661d8", + weight: 1, + fillOpacity: opacity, + opacity: 0.6, + }) + .addTo(map) + .bindPopup( + "" + loc.label + "
" + loc.count + " høringssvar" + ); + }); +}; diff --git a/docs/public/projects/deltag-aarhus/mocks/js/modal-decision.js b/docs/public/projects/deltag-aarhus/mocks/js/modal-decision.js new file mode 100644 index 0000000..b3332bf --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/js/modal-decision.js @@ -0,0 +1,16 @@ +window.DeltagMock = window.DeltagMock || {}; +var DM = window.DeltagMock; + +/* ========================================================================== + Decision Modal + ========================================================================== */ + +DM.initDecisionModal = function() { + var controller = DM.createModalController("decision-modal-overlay", "decision-modal-close"); + var openBtn = document.getElementById("open-decision-modal"); + + openBtn.addEventListener("click", function(e) { + e.preventDefault(); + controller.open(); + }); +}; diff --git a/docs/public/projects/deltag-aarhus/mocks/js/modal-horingssvar.js b/docs/public/projects/deltag-aarhus/mocks/js/modal-horingssvar.js new file mode 100644 index 0000000..0e4d61a --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/js/modal-horingssvar.js @@ -0,0 +1,132 @@ +window.DeltagMock = window.DeltagMock || {}; +var DM = window.DeltagMock; + +/* ========================================================================== + Horingssvar Modal Content + ========================================================================== */ + +DM.updateModalContent = function() { + var state = DM.state; + var item = state.filteredData[state.currentModalIndex]; + if (!item) return; + + document.getElementById("modal-title").textContent = item.title; + document.getElementById("modal-author").textContent = item.author; + document.getElementById("modal-date").textContent = item.date; + document.getElementById("modal-comments-count").textContent = DM.formatNumber(item.comments) + " kommentarer"; + + var likesEl = document.getElementById("modal-likes-count"); + likesEl.textContent = DM.formatNumber(item.likes) + " synes om"; + + /* Make the likes stat clickable in the open variant */ + var likeStat = likesEl.parentElement; + if (state.variant === "open") { + likeStat.classList.add("modal__stat--interactive"); + likeStat.setAttribute("role", "button"); + likeStat.setAttribute("tabindex", "0"); + likeStat.style.cursor = "pointer"; + + var likeHandler = function(e) { + e.preventDefault(); + item.likes++; + likesEl.textContent = DM.formatNumber(item.likes) + " synes om"; + likeStat.classList.add("modal__stat--liked"); + likeStat.removeEventListener("click", likeHandler); + likeStat.removeEventListener("keydown", likeKeyHandler); + likeStat.style.cursor = "default"; + likeStat.removeAttribute("role"); + likeStat.removeAttribute("tabindex"); + }; + var likeKeyHandler = function(e) { + if (e.key === "Enter" || e.key === " ") { e.preventDefault(); likeHandler(e); } + }; + + likeStat.addEventListener("click", likeHandler); + likeStat.addEventListener("keydown", likeKeyHandler); + } + + var descEl = document.getElementById("modal-description"); + descEl.innerHTML = item.fullDescription + .split("\n\n") + .map(function(p) { return "

" + p + "

"; }) + .join(""); + + // Comments + var commentsSection = document.getElementById("modal-comments"); + var commentsTitle = commentsSection.querySelector(".modal__comments-title"); + commentsSection.innerHTML = ""; + commentsSection.appendChild(commentsTitle); + + if (item.commentsList && item.commentsList.length > 0) { + var visibleComments = item.commentsList.slice(0, state.commentsVisible); + visibleComments.forEach(function(comment) { + var commentEl = document.createElement("div"); + commentEl.className = "modal__comment"; + commentEl.innerHTML = + '' + comment.author + '' + + '' + comment.date + '' + + ''; + commentsSection.appendChild(commentEl); + }); + + var remaining = item.commentsList.length - state.commentsVisible; + if (remaining > 0) { + var showMoreBtn = document.createElement("button"); + showMoreBtn.className = "modal__comments-show-more"; + showMoreBtn.textContent = "Vis flere kommentarer (" + remaining + " mere)"; + showMoreBtn.addEventListener("click", function() { + /* Progressive disclosure: load next batch on click */ + state.commentsVisible += state.COMMENTS_PER_PAGE; + DM.updateModalContent(); + }); + commentsSection.appendChild(showMoreBtn); + } + } + + /* Comment form — only shown in the open variant */ + if (state.variant === "open") { + var commentForm = document.createElement("form"); + commentForm.className = "modal__comment-form"; + commentForm.innerHTML = + '' + + '' + + ''; + commentsSection.appendChild(commentForm); + + commentForm.addEventListener("submit", function(e) { + e.preventDefault(); + var textarea = commentForm.querySelector("textarea"); + var text = textarea.value.trim(); + if (!text) return; + + item.commentsList.push({ + author: "Maria Jensen (dig)", + date: "I dag", + text: text + }); + item.comments++; + textarea.value = ""; + DM.updateModalContent(); + }); + } + + // Navigation + var prevBtn = document.getElementById("modal-prev"); + var nextBtn = document.getElementById("modal-next"); + var counter = document.getElementById("modal-counter"); + + prevBtn.disabled = state.currentModalIndex === 0; + nextBtn.disabled = state.currentModalIndex === state.filteredData.length - 1; + counter.textContent = (state.currentModalIndex + 1) + " af " + state.filteredData.length; +}; + +DM.navigateModal = function(direction) { + var state = DM.state; + var newIndex = state.currentModalIndex + direction; + if (newIndex < 0 || newIndex >= state.filteredData.length) return; + state.currentModalIndex = newIndex; + /* Reset to first page when switching to a different item */ + state.commentsVisible = state.COMMENTS_PER_PAGE; + DM.updateModalContent(); + window.history.replaceState(null, "", "#svar-" + state.filteredData[state.currentModalIndex].id); +}; diff --git a/docs/public/projects/deltag-aarhus/mocks/js/modal-material.js b/docs/public/projects/deltag-aarhus/mocks/js/modal-material.js new file mode 100644 index 0000000..bc74540 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/js/modal-material.js @@ -0,0 +1,16 @@ +window.DeltagMock = window.DeltagMock || {}; +var DM = window.DeltagMock; + +/* ========================================================================== + Material Document Modal + ========================================================================== */ + +DM.initMaterialModal = function() { + var controller = DM.createModalController("material-modal-overlay", "material-modal-close"); + var trigger = document.getElementById("material-lokalplan"); + + trigger.addEventListener("click", function(e) { + e.preventDefault(); + controller.open(); + }); +}; diff --git a/docs/public/projects/deltag-aarhus/mocks/js/modal-mitid.js b/docs/public/projects/deltag-aarhus/mocks/js/modal-mitid.js new file mode 100644 index 0000000..2aa19f5 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/js/modal-mitid.js @@ -0,0 +1,20 @@ +window.DeltagMock = window.DeltagMock || {}; +var DM = window.DeltagMock; + +/* ========================================================================== + MitID Login Modal (mock) + ========================================================================== */ + +DM.initMitIDModal = function() { + var controller = DM.createModalController("mitid-modal-overlay", "mitid-modal-close"); + + DM.openMitIDModal = controller.open; + + document.getElementById("mitid-approve-btn").addEventListener("click", function() { + controller.close(); + /* Small delay so the close animation completes before opening the next modal */ + setTimeout(function() { + if (DM.openSubmissionModal) DM.openSubmissionModal(); + }, 150); + }); +}; diff --git a/docs/public/projects/deltag-aarhus/mocks/js/modal-submission.js b/docs/public/projects/deltag-aarhus/mocks/js/modal-submission.js new file mode 100644 index 0000000..f5979df --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/js/modal-submission.js @@ -0,0 +1,33 @@ +window.DeltagMock = window.DeltagMock || {}; +var DM = window.DeltagMock; + +/* ========================================================================== + Høringssvar Submission Modal + ========================================================================== */ + +DM.initSubmissionModal = function() { + var controller = DM.createModalController("submission-modal-overlay", "submission-modal-close"); + var form = document.getElementById("submission-form"); + var modalBody = form.parentElement; + + DM.openSubmissionModal = controller.open; + + form.addEventListener("submit", function(e) { + e.preventDefault(); + + /* Show success state inside the modal instead of alert */ + modalBody.innerHTML = + '
' + + '' + + '

Tak for dit høringssvar!

' + + '

Dit høringssvar er modtaget og vil indgå i den politiske behandling af Lokalplan nr. 1237. Du modtager en bekræftelse på e-mail.

' + + '

Dette er en mock — intet høringssvar er reelt indsendt.

' + + '
'; + + /* Auto-close after a few seconds */ + setTimeout(function() { + controller.close(); + /* Restore form for next use (page will reload on variant switch anyway) */ + }, 3000); + }); +}; diff --git a/docs/public/projects/deltag-aarhus/mocks/js/modal.js b/docs/public/projects/deltag-aarhus/mocks/js/modal.js new file mode 100644 index 0000000..03fd244 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/js/modal.js @@ -0,0 +1,109 @@ +window.DeltagMock = window.DeltagMock || {}; +var DM = window.DeltagMock; + +/* ========================================================================== + Modal Utilities + ========================================================================== */ + +/** + * Creates a reusable modal controller for any overlay/close-button pair. + * Returns { open, close } functions. + */ +DM.createModalController = function(overlayId, closeButtonId) { + var overlayEl = document.getElementById(overlayId); + var closeBtn = document.getElementById(closeButtonId); + + function open() { + overlayEl.hidden = false; + overlayEl.classList.add("modal-overlay--open"); + document.body.classList.add("modal-open"); + closeBtn.focus(); + } + + function close() { + overlayEl.hidden = true; + overlayEl.classList.remove("modal-overlay--open"); + document.body.classList.remove("modal-open"); + } + + // Wire close button + closeBtn.addEventListener("click", close); + + // Click outside to close + overlayEl.addEventListener("click", function(e) { + if (e.target === overlayEl) close(); + }); + + return { open: open, close: close, overlay: overlayEl }; +}; + +/* ========================================================================== + Horingssvar Modal + ========================================================================== */ + +var previousFocus = null; + +DM.openModal = function(index) { + var state = DM.state; + state.currentModalIndex = index; + state.commentsVisible = state.COMMENTS_PER_PAGE; + previousFocus = document.activeElement; + + DM.updateModalContent(); + + var overlay = document.getElementById("modal-overlay"); + overlay.hidden = false; + overlay.classList.add("modal-overlay--open"); + document.body.classList.add("modal-open"); + + // Update URL hash + window.history.replaceState(null, "", "#svar-" + state.filteredData[index].id); + + // Focus the close button + overlay.querySelector(".modal__close").focus(); +}; + +DM.closeModal = function() { + var state = DM.state; + var overlay = document.getElementById("modal-overlay"); + + overlay.hidden = true; + overlay.classList.remove("modal-overlay--open"); + document.body.classList.remove("modal-open"); + state.currentModalIndex = -1; + window.history.replaceState(null, "", window.location.pathname); + + if (previousFocus) { + previousFocus.focus(); + } +}; + +/* ========================================================================== + Focus Trap + ========================================================================== */ + +DM.initFocusTrap = function() { + var overlay = document.getElementById("modal-overlay"); + + overlay.addEventListener("keydown", function(e) { + if (e.key !== "Tab") return; + + var focusable = overlay.querySelectorAll( + 'button:not([disabled]), [href], [tabindex]:not([tabindex="-1"])' + ); + var first = focusable[0]; + var last = focusable[focusable.length - 1]; + + if (e.shiftKey) { + if (document.activeElement === first) { + e.preventDefault(); + last.focus(); + } + } else { + if (document.activeElement === last) { + e.preventDefault(); + first.focus(); + } + } + }); +}; diff --git a/docs/public/projects/deltag-aarhus/mocks/js/state.js b/docs/public/projects/deltag-aarhus/mocks/js/state.js new file mode 100644 index 0000000..be259d8 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/js/state.js @@ -0,0 +1,14 @@ +/* Shared application state. All modules read/write through DM.state to keep mutations traceable. */ +window.DeltagMock = window.DeltagMock || {}; + +window.DeltagMock.state = { + variant: new URLSearchParams(window.location.search).get("variant") === "closed" ? "closed" : "open", + currentSort: "comments", + currentCategory: "all", + visibleCount: 16, + currentModalIndex: -1, + filteredData: window.DeltagMock.horingssvarData.slice(), + ITEMS_PER_PAGE: 16, + COMMENTS_PER_PAGE: 5, + commentsVisible: 5 +}; diff --git a/docs/public/projects/deltag-aarhus/mocks/js/variant.js b/docs/public/projects/deltag-aarhus/mocks/js/variant.js new file mode 100644 index 0000000..ada61c2 --- /dev/null +++ b/docs/public/projects/deltag-aarhus/mocks/js/variant.js @@ -0,0 +1,64 @@ +window.DeltagMock = window.DeltagMock || {}; +var DM = window.DeltagMock; + +/* ========================================================================== + Variant Switching (open / closed) + ========================================================================== */ + +DM.initVariant = function() { + var state = DM.state; + var isOpen = state.variant === "open"; + + /* Body class drives CSS-only visibility rules in variant.css */ + document.body.classList.add(isOpen ? "variant--open" : "variant--closed"); + + /* Decision banner — hidden when hearing is still open */ + var decisionBanner = document.querySelector(".decision-banner"); + if (decisionBanner) { + decisionBanner.style.display = isOpen ? "none" : ""; + } + + /* Status value */ + var statusBadge = document.getElementById("hearing-status-badge"); + if (statusBadge) { + statusBadge.textContent = isOpen ? "Åben" : "Afsluttet"; + } + + /* Deadline display — show future date when open */ + var deadlineValue = document.getElementById("hearing-deadline-value"); + if (deadlineValue) { + deadlineValue.textContent = isOpen ? "14. oktober 2025" : "14. august 2025"; + } + + var bodyDeadline = document.getElementById("body-deadline"); + if (bodyDeadline) { + bodyDeadline.textContent = isOpen ? "14. oktober 2025" : "14. august 2025"; + } + + var bodyDeleteDate = document.getElementById("body-delete-date"); + if (bodyDeleteDate) { + bodyDeleteDate.textContent = isOpen ? "14. oktober 2025" : "14. august 2025"; + } + + /* Submit button — disabled when closed, active when open */ + var submitBtn = document.getElementById("submit-horingssvar-btn"); + if (submitBtn) { + if (isOpen) { + submitBtn.disabled = false; + submitBtn.addEventListener("click", function(e) { + e.preventDefault(); + if (DM.openMitIDModal) DM.openMitIDModal(); + }); + } else { + submitBtn.disabled = true; + } + } + + /* Highlight active variant link in mock banner */ + var variantLinks = document.querySelectorAll(".mock-banner__link--variant"); + variantLinks.forEach(function(link) { + if (link.dataset.variant === state.variant) { + link.classList.add("mock-banner__link--active"); + } + }); +};