Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions CLAUDE.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ Die App selbst bleibt abhängigkeitsfrei; für die Tests wird einmalig Playwrigh
```bash
npm install # nur Dev: @playwright/test
npx playwright install chromium
npm test # 376 Tests, ~150 s
npm test # 379 Tests, ~150 s
npm run test:ui # interaktiver Modus
```

Expand Down Expand Up @@ -309,7 +309,7 @@ datenlotse/
│ ├── sample-landesbehoerde.csv # Beispiel: fiktive Landesebene (12 Datensätze)
│ ├── sample-verein.csv # Beispiel: fiktiver gemeinnütziger Träger (12 Datensätze)
│ └── template.csv # Leere Vorlage zum eigenen Befüllen
├── tests/ # Playwright-End-to-End-Tests (376 Tests in 14 Dateien)
├── tests/ # Playwright-End-to-End-Tests (379 Tests in 14 Dateien)
│ ├── helpers.js # openApp/loadSample/Download-Helfer
│ ├── smoke.spec.js # Views, Routing, Dashboard, HTML-Validität, Anrede
│ ├── import.spec.js # CSV-Parser, Ableitung, Formel-Injection
Expand Down
Binary file modified assets/fonts/webfonts/fa-regular-400.subset.woff2
Binary file not shown.
Binary file modified assets/fonts/webfonts/fa-solid-900.subset.woff2
Binary file not shown.
98 changes: 88 additions & 10 deletions css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,42 @@ a { color: var(--c-accent); }
border: 2px solid rgba(66, 0, 147, 0.15);
box-shadow: var(--shadow-md);
}
/* Marke und Aussage sind zwei verschiedene Dinge und standen bis v66 als ein
Satz mit Doppelpunkt in einer Farbe und einer Größe – „DatenLotse" ging darin
unter. Jetzt trägt die Marke die Akzentfarbe und das volle Gewicht, der Claim
steht kleiner und in der Textfarbe darunter. */
.hero-headline {
font-size: 34px;
font-weight: 800;
letter-spacing: -1px;
line-height: 1.15;
margin-bottom: 16px;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.hero-brand {
font-size: 46px;
font-weight: 800;
letter-spacing: -1.6px;
line-height: 1.05;
color: var(--c-accent);
}
.hero-claim {
font-size: 21px;
font-weight: 600;
letter-spacing: -0.3px;
line-height: 1.3;
color: var(--c-text);
max-width: 620px;
/* setzt die Marke optisch ab, ohne eine zweite Überschrift zu behaupten */
padding-top: 12px;
border-top: 2px solid rgba(66, 0, 147, 0.12);
}
.hero-actions {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
margin-top: 28px;
}
.hero-sub {
font-size: 16px;
color: var(--c-muted);
Expand Down Expand Up @@ -236,12 +264,57 @@ a { color: var(--c-accent); }
padding: 6px 14px;
border-radius: 999px;
}
.hero-from-graf {
margin-top: 22px;
font-size: 13px;
/* ── Die DatenGraf-Brücke ────────────────────────────────────────
Eigener Abschnitt weit unten statt einer grauen Zeile unter dem Hero-Button:
sie richtet sich an eine Minderheit, und wer schon kartiert hat, sucht nicht
mehr nach dem Einstieg. */
.graf-bridge {
max-width: 980px;
margin: var(--home-gap) auto 0;
padding: 0 24px;
}
.graf-bridge-panel {
background: #fff;
border: 1px solid var(--glass-border);
border-left: 4px solid var(--c-accent);
border-radius: 18px;
box-shadow: var(--shadow-md);
padding: 26px 28px;
display: flex;
gap: 20px;
align-items: flex-start;
}
.graf-bridge-icon {
width: 46px; height: 46px;
flex-shrink: 0;
border-radius: 12px;
display: flex; align-items: center; justify-content: center;
background: linear-gradient(135deg, #420093 0%, #6c2fbf 100%);
color: #fff;
font-size: 18px;
}
.graf-bridge-body { min-width: 0; }
.graf-bridge-title {
margin: 0 0 8px;
font-size: 19px;
font-weight: 800;
letter-spacing: -0.3px;
color: var(--c-accent);
}
.graf-bridge-text {
margin: 0;
font-size: 14px;
line-height: 1.65;
color: var(--c-muted);
}
.hero-from-graf a { font-weight: 600; }
.graf-bridge-text strong { color: var(--c-text); }
.graf-bridge-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 18px;
}
.graf-bridge-actions .btn { text-decoration: none; }

/* ── Status-Dashboard (Startseite) ───────────────────────────────
Eigenes Panel statt frei stehender Kartenreihe: die Startseite zeigte
Expand Down Expand Up @@ -2463,7 +2536,7 @@ details[open] > .about-summary .about-caret { transform: rotate(180deg); }
.gov-note i { color: var(--c-accent); margin-right: 4px; }

/* ── Daten-Kompass ───────────────────────────────────────────── */
.hero-cta { font-size: 15px; padding: 14px 28px; margin: 28px 0 0; }
.hero-cta { font-size: 15px; padding: 14px 28px; margin: 0; }
.kompass-view { max-width: 1040px; margin: 28px auto 0; padding: 0 24px 48px; }
.kompass-view.hidden { display: none; }
.kompass-score {
Expand Down Expand Up @@ -2574,7 +2647,6 @@ details[open] > .about-summary .about-caret { transform: rotate(180deg); }
:root { --home-gap: 30px; }
.hero-logo { width: 132px; }
.dashboard-panel { padding: 20px 16px 22px; }
.hero-headline { font-size: 26px; }
.module-grid { padding: 0 16px 48px; }
.inventory-view, .wissen-view, .vorlagen-view, .dashboard { padding-left: 16px; padding-right: 16px; }
.inv-tabs { flex-wrap: wrap; }
Expand All @@ -2595,6 +2667,12 @@ details[open] > .about-summary .about-caret { transform: rotate(180deg); }
.steps-section { padding: 0 16px; }
.steps-panel { padding: 22px 18px 22px; border-radius: 14px; }
.steps-title { font-size: 18px; }
.hero-brand { font-size: 34px; letter-spacing: -1px; }
.hero-claim { font-size: 17px; }
.hero-actions .btn { flex: 1 1 auto; justify-content: center; }
.graf-bridge { padding: 0 16px; }
.graf-bridge-panel { padding: 20px 18px; flex-direction: column; gap: 14px; border-radius: 14px; }
.graf-bridge-actions .btn { flex: 1 1 auto; justify-content: center; }
.view-hero { padding: 22px 18px 20px; margin: 22px 0 22px; border-radius: 14px; }
.view-hero .inventory-title { font-size: 27px; }
.view-hero .inventory-hint { padding-top: 14px; margin-top: 16px; }
Expand Down
2 changes: 1 addition & 1 deletion datenschutz/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
}
</script>
<link rel="stylesheet" href="/assets/fonts/inter/inter.css">
<link rel="stylesheet" href="/assets/fonts/fa/icons.min.css?v=66">
<link rel="stylesheet" href="/assets/fonts/fa/icons.min.css?v=67">
<link rel="stylesheet" href="/css/tokens.css">
<link rel="stylesheet" href="/css/styles.css">
</head>
Expand Down
43 changes: 32 additions & 11 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,10 @@
<link rel="stylesheet" href="assets/fonts/inter/inter.css">
<!-- Zugeschnitten auf die verwendeten Icons (tools/build-icons.py) – das
vollstaendige all.min.css bleibt als Quelle im Repo, wird aber nicht geladen. -->
<link rel="stylesheet" href="assets/fonts/fa/icons.min.css?v=66">
<link rel="stylesheet" href="assets/fonts/fa/icons.min.css?v=67">

<link rel="stylesheet" href="css/tokens.css?v=66">
<link rel="stylesheet" href="css/styles.css?v=66">
<link rel="stylesheet" href="css/tokens.css?v=67">
<link rel="stylesheet" href="css/styles.css?v=67">
</head>
<body>

Expand Down Expand Up @@ -184,14 +184,16 @@
<section class="hero home-only" id="hero">
<div class="hero-panel">
<img src="logo.svg" alt="DatenLotse" class="hero-logo">
<h1 class="hero-headline">DatenLotse: Daten verstehen, ordnen und als Open Data ver&ouml;ffentlichen</h1>
<h1 class="hero-headline">
<span class="hero-brand">DatenLotse</span>
<span class="hero-claim">Daten verstehen, ordnen und als Open Data ver&ouml;ffentlichen</span>
</h1>
<p class="hero-sub">Vom <strong>Ist-Zustand</strong> Ihrer Datenlandschaft bis zur Ver&ouml;ffentlichung als <strong>Open Data</strong>: Welche Best&auml;nde lassen sich <strong>&ouml;ffnen</strong>, wie werden sie nach dem Standard <strong>DCAT-AP.de</strong> beschrieben &ndash; und was muss vorher gekl&auml;rt oder gesch&uuml;tzt werden?</p>

<button class="btn btn-primary hero-cta" id="hero-kompass-btn"><i class="fas fa-compass"></i> Daten-Kompass starten</button>
<p class="hero-from-graf">
<i class="fas fa-file-csv"></i>
Schon kartiert? <a href="#modul-inventar">DatenGraf-CSV importieren</a> und direkt mit dem Inventar starten.
</p>
<div class="hero-actions">
<button class="btn btn-primary hero-cta" id="hero-kompass-btn"><i class="fas fa-compass"></i> Sofort loslegen</button>
<button class="btn btn-secondary hero-cta" id="hero-tour-btn"><i class="fas fa-route"></i> Rundgang starten</button>
</div>
</div>
</section>

Expand Down Expand Up @@ -324,6 +326,25 @@ <h2 class="module-title">Risiko &amp; Pseudonymisierung</h2>

</section>

<!-- ── Die DatenGraf-Brücke ───────────────────────────────────
Stand bis v66 als graue Zeile unter dem Hero-Button und richtete sich
an eine Minderheit: wer schon kartiert hat, sucht nicht mehr nach dem
Einstieg. Als eigener Abschnitt weiter unten trifft sie die Leute an
der Stelle, an der sie die Module ohnehin überflogen haben. -->
<section class="graf-bridge home-only" id="graf-bridge" aria-labelledby="graf-bridge-title">
<div class="graf-bridge-panel">
<div class="graf-bridge-icon"><i class="fas fa-file-csv"></i></div>
<div class="graf-bridge-body">
<h2 class="graf-bridge-title" id="graf-bridge-title">Schon mit DatenGraf kartiert?</h2>
<p class="graf-bridge-text">Dann brauchen Sie hier nicht von vorn anzufangen. DatenLotse liest <strong>genau das CSV-Schema</strong>, das DatenGraf exportiert: Aus jeder Kombination aus Quelle und Datentyp wird ein Datensatz-Kandidat nach DCAT-AP.de &ndash; samt Vorbelegung von Zugriffsrechten und Aktualisierungszyklus aus Ihrer Kartierung. Sie erg&auml;nzen nur noch, was DatenGraf nicht kennt: Lizenz, Schlagw&ouml;rter und die Adresse, unter der die Daten liegen.</p>
<div class="graf-bridge-actions">
<button class="btn btn-primary" id="graf-bridge-import"><i class="fas fa-file-import"></i> DatenGraf-CSV importieren</button>
<a class="btn btn-secondary" href="https://datengraf.nozilla.net/" target="_blank" rel="noopener"><i class="fas fa-diagram-project"></i> Noch nicht kartiert? Zu DatenGraf</a>
</div>
</div>
</div>
</section>

<!-- ── Inventar-View (Modul 2, erscheint nach Import) ────────── -->
<section class="inventory-view hidden" id="inventory-view">
<div class="view-hero">
Expand Down Expand Up @@ -902,7 +923,7 @@ <h2 class="consult-card-headline">Umsetzung besprechen</h2>
Brands-Familie und h&auml;tte daf&uuml;r 116 KB Schrift nachgeladen. -->
<a href="https://github.com/daimpad/datenlotse" target="_blank" rel="noopener" title="GitHub Repository" aria-label="DatenLotse auf GitHub"><svg class="footer-github" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg></a>
</nav>
<span class="footer-version">v66</span>
<span class="footer-version">v67</span>
</footer>

<!-- ── FAQ-Modal ─────────────────────────────────────────────── -->
Expand Down Expand Up @@ -1118,7 +1139,7 @@ <h2 id="license-title"><i class="fas fa-id-card"></i> Lizenz-Wegweiser</h2>
</div>
</div>

<script src="js/app.js?v=66"></script>
<script src="js/app.js?v=67"></script>

<!-- Anonyme Seitenzählung (GoatCounter). Der EINZIGE externe Aufruf der
Seite: keine Cookies, kein LocalStorage, keine Kennung, kein
Expand Down
6 changes: 5 additions & 1 deletion js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,7 @@ function clearState() {
Teile defensiv. grafRows wird mitgesichert (anders als im
LocalStorage), damit der Import-Kontext vollständig ist. */
const PROJECT_SCHEMA = 1;
const APP_VERSION = 'v66';
const APP_VERSION = 'v67';

function buildProjectJSON() {
return JSON.stringify({
Expand Down Expand Up @@ -2718,6 +2718,10 @@ document.getElementById('faq-btn')?.addEventListener('click', () => showModal('f
document.getElementById('faq-close-btn')?.addEventListener('click', () => showModal('faq-backdrop', false));
document.getElementById('cta-btn')?.addEventListener('click', () => navTo('kompass'));
document.getElementById('hero-kompass-btn')?.addEventListener('click', () => navTo('kompass'));
document.getElementById('hero-tour-btn')?.addEventListener('click', startTour);
// Die DatenGraf-Brücke führt direkt in den Dateidialog: wer schon kartiert hat,
// braucht das Erklär-Modal nicht mehr, das `open-inventory-btn` davorschaltet.
document.getElementById('graf-bridge-import')?.addEventListener('click', pickAndImport);
document.getElementById('open-phase45-btn')?.addEventListener('click', () => showModal('phase45-backdrop', true));
document.getElementById('phase45-close-btn')?.addEventListener('click', () => showModal('phase45-backdrop', false));

Expand Down
57 changes: 57 additions & 0 deletions tests/smoke.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,63 @@ test.describe('Grundgerüst & Navigation', () => {
expect(sub).toMatch(/öffnen/i);
});

/* v67: „DatenLotse: Daten verstehen …" stand als ein Satz mit Doppelpunkt in
einer Farbe und einer Größe – die Marke ging darin unter. Marke und Aussage
sind jetzt getrennt ausgezeichnet; der Test misst den Unterschied, statt ihn
zu behaupten (eine reine Klassen-Prüfung bliebe grün, wenn beide gleich aussähen). */
test('Marke und Aussage im Hero sind sichtbar voneinander abgesetzt', async ({ page }) => {
await openApp(page);
const h1 = page.locator('#hero h1');
await expect(h1.locator('.hero-brand')).toHaveText('DatenLotse');
await expect(h1.locator('.hero-claim')).toContainText(/Open Data/i);
// kein Doppelpunkt mehr hinter der Marke
expect(await h1.locator('.hero-brand').innerText()).not.toContain(':');

const stil = await page.evaluate(() => {
const g = el => { const c = getComputedStyle(document.querySelector(el)); return { size: parseFloat(c.fontSize), color: c.color }; };
return { marke: g('.hero-brand'), claim: g('.hero-claim') };
});
expect(stil.marke.size).toBeGreaterThan(stil.claim.size + 8);
expect(stil.marke.color).not.toBe(stil.claim.color);
});

/* v67: Der Hero bot nur „Daten-Kompass starten". Wer sich erst orientieren
will, hatte im Hero keinen Weg dorthin – der Rundgang lag im wegklickbaren
Hinweis und in der Seitenleiste. */
test('der Hero bietet beide Einstiege an – sofort loslegen und Rundgang', async ({ page }) => {
await openApp(page);
await expect(page.locator('#hero-kompass-btn')).toContainText('Sofort loslegen');
await expect(page.locator('#hero-tour-btn')).toContainText('Rundgang');

await page.locator('#hero-tour-btn').click();
await expect(page.locator('#tour-layer')).toBeVisible();
await expect(page.locator('#tour-card')).toContainText('Schritt 1');
await page.locator('#tour-skip').click();

await page.locator('#hero-kompass-btn').click();
await expect(page.locator('#kompass-view')).toBeVisible();
});

/* v67: Die DatenGraf-Brücke stand als graue Zeile unter dem Hero-Button und
richtete sich an eine Minderheit. Jetzt ein eigener Abschnitt weiter unten –
unterhalb der Modul-Karten, mit eigener Erklärung. */
test('die DatenGraf-Brücke ist ein eigener Abschnitt unterhalb der Module', async ({ page }) => {
await openApp(page);
const bridge = page.locator('#graf-bridge');
await expect(bridge).toBeVisible();
await expect(bridge.locator('h2')).toBeVisible();
// Erklärung, nicht nur ein Link
expect((await bridge.locator('.graf-bridge-text').innerText()).length).toBeGreaterThan(120);
// …und sie steht wirklich weiter unten als die Modul-Karten
const [module, bruecke] = await page.evaluate(() => [
document.getElementById('module-grid').getBoundingClientRect().top,
document.getElementById('graf-bridge').getBoundingClientRect().top,
]);
expect(bruecke).toBeGreaterThan(module);
// im Hero steht sie nicht mehr
expect(await page.locator('#hero .graf-bridge, #hero .hero-from-graf').count()).toBe(0);
});

/* v66: Der Hero trug sieben Bausteine (Logo, Titel, Einleitung, Chips,
Zusage, Aktion, Import-Zeile). Die vier Schritte stehen jetzt in einem
eigenen Abschnitt – mit `<h2>`, denn je Ansicht bleibt genau eine `<h1>`. */
Expand Down
2 changes: 1 addition & 1 deletion tools/generate-wissen.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ function seite({ slug, title, description, lead, bodyHTML, jsonld, pfad, eigenst
<meta name="twitter:image" content="${BASE}/social-preview.png">
${daten.map(d => ` <script type="application/ld+json">\n ${JSON.stringify(d, null, 2).replace(/\n/g, '\n ')}\n </script>`).join('\n')}
<link rel="stylesheet" href="/assets/fonts/inter/inter.css">
<link rel="stylesheet" href="/assets/fonts/fa/icons.min.css?v=66">
<link rel="stylesheet" href="/assets/fonts/fa/icons.min.css?v=67">
<link rel="stylesheet" href="/css/tokens.css">
<link rel="stylesheet" href="/css/styles.css">
</head>
Expand Down
2 changes: 1 addition & 1 deletion wissen/glossar/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@
}
</script>
<link rel="stylesheet" href="/assets/fonts/inter/inter.css">
<link rel="stylesheet" href="/assets/fonts/fa/icons.min.css?v=66">
<link rel="stylesheet" href="/assets/fonts/fa/icons.min.css?v=67">
<link rel="stylesheet" href="/css/tokens.css">
<link rel="stylesheet" href="/css/styles.css">
</head>
Expand Down
2 changes: 1 addition & 1 deletion wissen/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
}
</script>
<link rel="stylesheet" href="/assets/fonts/inter/inter.css">
<link rel="stylesheet" href="/assets/fonts/fa/icons.min.css?v=66">
<link rel="stylesheet" href="/assets/fonts/fa/icons.min.css?v=67">
<link rel="stylesheet" href="/css/tokens.css">
<link rel="stylesheet" href="/css/styles.css">
</head>
Expand Down
2 changes: 1 addition & 1 deletion wissen/kommunale-satzungen/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
}
</script>
<link rel="stylesheet" href="/assets/fonts/inter/inter.css">
<link rel="stylesheet" href="/assets/fonts/fa/icons.min.css?v=66">
<link rel="stylesheet" href="/assets/fonts/fa/icons.min.css?v=67">
<link rel="stylesheet" href="/css/tokens.css">
<link rel="stylesheet" href="/css/styles.css">
</head>
Expand Down
2 changes: 1 addition & 1 deletion wissen/lizenzen/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
}
</script>
<link rel="stylesheet" href="/assets/fonts/inter/inter.css">
<link rel="stylesheet" href="/assets/fonts/fa/icons.min.css?v=66">
<link rel="stylesheet" href="/assets/fonts/fa/icons.min.css?v=67">
<link rel="stylesheet" href="/css/tokens.css">
<link rel="stylesheet" href="/css/styles.css">
</head>
Expand Down
2 changes: 1 addition & 1 deletion wissen/pruefwerkzeuge/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
}
</script>
<link rel="stylesheet" href="/assets/fonts/inter/inter.css">
<link rel="stylesheet" href="/assets/fonts/fa/icons.min.css?v=66">
<link rel="stylesheet" href="/assets/fonts/fa/icons.min.css?v=67">
<link rel="stylesheet" href="/css/tokens.css">
<link rel="stylesheet" href="/css/styles.css">
</head>
Expand Down
2 changes: 1 addition & 1 deletion wissen/rechtsgrundlagen-laender/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
}
</script>
<link rel="stylesheet" href="/assets/fonts/inter/inter.css">
<link rel="stylesheet" href="/assets/fonts/fa/icons.min.css?v=66">
<link rel="stylesheet" href="/assets/fonts/fa/icons.min.css?v=67">
<link rel="stylesheet" href="/css/tokens.css">
<link rel="stylesheet" href="/css/styles.css">
</head>
Expand Down
Loading
Loading