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
113 changes: 112 additions & 1 deletion .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ on:
- package-lock.json
- scripts/build_static_frontend.sh
- scripts/audit_public_assets.mjs
- scripts/html_metadata_tags.cjs
- scripts/check_deployment_governance.mjs
- .github/workflows/deploy-pages.yml
workflow_dispatch:
Expand Down Expand Up @@ -155,6 +156,8 @@ jobs:
no_script_style_path="${RUNNER_TEMP}/pages-no-script.css"
app_path="${RUNNER_TEMP}/pages-versorgungs-kompass.html"
registration_path="${RUNNER_TEMP}/pages-versorgungs-netzwerk.html"
root_share_image_path="${RUNNER_TEMP}/mitmachen-share-v1.png"
network_share_image_path="${RUNNER_TEMP}/versorgungs-netzwerk-share-v1.png"

curl --fail "${curl_retry_args[@]}" \
"${page_root}/?${cache_buster}" --output "$root_app_path"
Expand All @@ -177,6 +180,16 @@ jobs:
"${page_root}/mitmachen/versorgungs-netzwerk.html?${cache_buster}" --output "$registration_path"
curl --fail "${curl_retry_args[@]}" \
"${page_root}/data/data-service.js?${cache_buster}" >/dev/null
root_share_content_type="$(curl --fail "${curl_retry_args[@]}" \
"${page_root}/public/media/social/mitmachen-share-v1.png?${cache_buster}" \
--output "$root_share_image_path" --write-out '%{content_type}')"
network_share_content_type="$(curl --fail "${curl_retry_args[@]}" \
"${page_root}/public/media/social/versorgungs-netzwerk-share-v1.png?${cache_buster}" \
--output "$network_share_image_path" --write-out '%{content_type}')"
if [ "$root_share_content_type" != "image/png" ] || [ "$network_share_content_type" != "image/png" ]; then
echo "GitHub Pages does not serve the share previews as image/png." >&2
exit 1
fi

grep --extended-regexp --ignore-case --quiet 'synthetisch|fiktiv' "$demo_data_path"
grep --extended-regexp --quiet 'dataMode:[[:space:]]*"demo"' "$runtime_config_path"
Expand All @@ -186,7 +199,14 @@ jobs:
for app_label in Versorgung Auswertung Aktivitäten Stakeholder Expertenkreis Hospitationen Beobachtungen Fragebogen Dashboard Formate Teams; do
grep --fixed-strings --quiet "$app_label" "$app_path"
done
node - "$app_path" "$registration_path" "$root_app_path" "$manifest_path" "$no_script_style_path" <<'NODE'
node - \
"$app_path" \
"$registration_path" \
"$root_app_path" \
"$manifest_path" \
"$no_script_style_path" \
"$root_share_image_path" \
"$network_share_image_path" <<'NODE'
const fs = require("node:fs");
const html = fs.readFileSync(process.argv[2], "utf8");
const positions = ["./data/demo-data.js", "./data/demo-api.js", "./data/data-service.js"].map((value) => html.indexOf(value));
Expand All @@ -203,6 +223,97 @@ jobs:
if (/data\/(?:runtime-config|demo-data|demo-api)\.js/i.test(registrationHtml)) {
throw new Error("Die statische Netzwerkregistrierung bindet unerwartet Demo-Datenadapter ein.");
}
for (const [label, document, expected] of [
["Startseite", html, {
canonical: "https://timofrank.github.io/mitmachen/",
title: "Jetzt #Mitmachen: Gemeinsam Versorgung besser machen",
description: "Entdecken Sie vier Kompasse, die Menschen, Wissen und Ideen verbinden – in einer öffentlichen Demo mit ausschließlich fiktiven Daten.",
image: "https://timofrank.github.io/mitmachen/public/media/social/mitmachen-share-v1.png"
}],
["Versorgungs-Netzwerk", registrationHtml, {
canonical: "https://timofrank.github.io/mitmachen/mitmachen/versorgungs-netzwerk.html",
title: "Ihre Erfahrung zählt: Digitale Versorgung besser machen",
description: "Entdecken Sie die Idee des Versorgungs-Netzwerks – als Konzeptdemo mit fiktiven Angaben, ohne echte Anmeldung, Übermittlung oder Speicherung.",
image: "https://timofrank.github.io/mitmachen/public/media/social/versorgungs-netzwerk-share-v1.png"
}]
]) {
for (const marker of [
`<link rel="canonical" href="${expected.canonical}" />`,
`<meta property="og:title" content="${expected.title}" />`,
`<meta property="og:description" content="${expected.description}" />`,
`<meta property="og:image" content="${expected.image}" />`,
'<meta property="og:image:width" content="1200" />',
'<meta property="og:image:height" content="630" />',
'<meta name="twitter:card" content="summary_large_image" />'
]) {
if (!document.includes(marker)) {
throw new Error(`${label} enthält nicht den erwarteten Social-Preview-Vertrag: ${marker}`);
}
}
}
function crc32(buffer) {
let crc = 0xffffffff;
for (const byte of buffer) {
crc ^= byte;
for (let bit = 0; bit < 8; bit += 1) {
crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
}
}
return (crc ^ 0xffffffff) >>> 0;
}
function inspectPng(image) {
if (
image.length < 45
|| image.subarray(0, 8).toString("hex") !== "89504e470d0a1a0a"
) {
return null;
}
let offset = 8;
let width;
let height;
let sawHeader = false;
let sawImageData = false;
let sawEnd = false;
while (offset + 12 <= image.length) {
const length = image.readUInt32BE(offset);
const typeStart = offset + 4;
const dataStart = offset + 8;
const dataEnd = dataStart + length;
const chunkEnd = dataEnd + 4;
if (chunkEnd > image.length) return null;
const type = image.subarray(typeStart, dataStart).toString("ascii");
if (crc32(image.subarray(typeStart, dataEnd)) !== image.readUInt32BE(dataEnd)) return null;
if (!sawHeader) {
if (type !== "IHDR" || length !== 13) return null;
width = image.readUInt32BE(dataStart);
height = image.readUInt32BE(dataStart + 4);
sawHeader = true;
} else if (type === "IHDR") {
return null;
}
if (type === "IDAT") sawImageData = true;
if (type === "IEND") {
if (length !== 0 || chunkEnd !== image.length) return null;
sawEnd = true;
offset = chunkEnd;
break;
}
offset = chunkEnd;
}
if (!sawHeader || !sawImageData || !sawEnd || offset !== image.length) return null;
return { width, height };
}
for (const imagePath of [process.argv[7], process.argv[8]]) {
const image = fs.readFileSync(imagePath);
const dimensions = inspectPng(image);
if (
dimensions?.width !== 1200
|| dimensions?.height !== 630
|| image.length > 600_000
) {
throw new Error(`Share-Vorschau ist kein valides PNG mit 1200 x 630 Pixeln unter 600 KB: ${imagePath}`);
}
}
const rootHtml = fs.readFileSync(process.argv[4], "utf8");
const noScriptCss = fs.readFileSync(process.argv[6], "utf8");
if (
Expand Down
22 changes: 22 additions & 0 deletions dokumentation/assets/social/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Share-Vorschauen

Die HTML-Dateien in diesem Ordner sind die reproduzierbaren Browserquellen für
die 1200 × 630 Pixel großen Open-Graph-Bilder der öffentlichen `#Mitmachen`-Demo.

- `mitmachen-share-v1.html` rendert
`public/media/social/mitmachen-share-v1.png`.
- `versorgungs-netzwerk-share-v1.html` rendert
`public/media/social/versorgungs-netzwerk-share-v1.png`.
- `share-card-preview.html` zeigt beide Bilder zusammen mit den zugehörigen
Vorschautiteln und -beschreibungen für die visuelle Abnahme.
- `share-card-thumbnail-check.html` zeigt beide Motive zusätzlich in 300 × 158
und 160 × 84 Pixeln.

Die Grafiken verwenden ausschließlich projektinterne Markenassets,
synthetische Demo-Inhalte und kein gematik-Unternehmenslogo. Bei einer
inhaltlichen Änderung erhält die Datei eine neue Versionsnummer, damit bereits
von Messengern gecachte Linkvorschauen nicht unbemerkt veraltet bleiben.

Die Motive sind bewusst auf Marke, eine große Botschaft und ein grobes Motiv
reduziert. Tragende Schrift ist mindestens 80 Pixel groß; die Abnahme erfolgt
zusätzlich in einer kompakten Vorschau mit weniger als 400 Pixel Kartenbreite.
178 changes: 178 additions & 0 deletions dokumentation/assets/social/mitmachen-share-v1.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=1200, initial-scale=1" />
<link rel="icon" href="data:," />
<title>#Mitmachen – Share-Vorschau</title>
<style>
:root {
font-family: Arial, Helvetica, sans-serif;
color: #ffffff;
background: #061652;
}

* {
box-sizing: border-box;
}

html,
body {
width: 1200px;
height: 630px;
margin: 0;
overflow: hidden;
}

body {
background:
radial-gradient(circle at 92% 12%, rgba(0, 255, 101, 0.22), transparent 28%),
radial-gradient(circle at 72% 110%, rgba(85, 158, 232, 0.26), transparent 38%),
linear-gradient(132deg, #061652 0%, #0b2b70 62%, #0b466e 100%);
}

.share-card {
position: relative;
display: grid;
grid-template-rows: auto 1fr;
width: 100%;
height: 100%;
padding: 48px 60px 52px;
}

.share-card::before {
position: absolute;
right: -120px;
bottom: -260px;
width: 720px;
height: 720px;
border: 2px solid rgba(255, 255, 255, 0.1);
border-radius: 50%;
box-shadow:
0 0 0 70px rgba(255, 255, 255, 0.025),
0 0 0 140px rgba(255, 255, 255, 0.018);
content: "";
pointer-events: none;
}

.share-card__top,
.share-card__content {
position: relative;
z-index: 1;
}

.share-card__top {
display: flex;
align-items: center;
justify-content: space-between;
}

.mitmachen-lockup {
display: block;
width: 292px;
height: auto;
}

.demo-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 144px;
padding: 13px 22px;
border-radius: 13px;
background: #00e45e;
color: #05245c;
font-size: 30px;
font-weight: 800;
letter-spacing: 0.035em;
text-transform: uppercase;
box-shadow: 0 14px 28px rgba(0, 8, 48, 0.22);
}

.share-card__content {
display: grid;
grid-template-columns: 660px 364px;
gap: 56px;
align-items: center;
padding-top: 38px;
}

h1 {
margin: 0;
font-size: 82px;
font-weight: 800;
letter-spacing: -3.4px;
line-height: 0.96;
}

h1 span {
color: #86ffb5;
}

.modules {
display: grid;
grid-template-columns: repeat(2, 168px);
gap: 28px;
align-content: center;
justify-content: center;
}

.module {
display: grid;
width: 168px;
height: 168px;
place-items: center;
border-radius: 36px;
background: rgba(99, 172, 255, 0.18);
}

.module--stakeholder {
background: rgba(67, 179, 145, 0.2);
}

.module--hospitation {
background: rgba(224, 164, 77, 0.2);
}

.module--format {
background: rgba(169, 128, 218, 0.22);
}

.module img {
display: block;
width: 116px;
height: 116px;
}
</style>
</head>
<body>
<main class="share-card" aria-labelledby="share-title">
<header class="share-card__top">
<img
class="mitmachen-lockup"
src="../../../public/brand/mitmachen/lockup-horizontal-on-dark.svg"
alt="#Mitmachen"
/>
<span class="demo-badge">Demo</span>
</header>

<section class="share-card__content">
<h1 id="share-title">Gemeinsam<br />Versorgung<br /><span>besser machen.</span></h1>
<div class="modules" aria-label="Vier Kompasse">
<div class="module">
<img src="../../../public/brand/versorgungs-kompass/mark-on-dark.svg" alt="" />
</div>
<div class="module module--stakeholder">
<img src="../../../public/brand/modules/stakeholder/mark-on-dark.svg" alt="" />
</div>
<div class="module module--hospitation">
<img src="../../../public/brand/modules/hospitation/mark-on-dark.svg" alt="" />
</div>
<div class="module module--format">
<img src="../../../public/brand/modules/formate/mark-on-dark.svg" alt="" />
</div>
</div>
</section>
</main>
</body>
</html>
Loading
Loading