Compare commits

..
75 Commits
Author SHA1 Message Date
ctao ec4ff5dfa2 content split + copy audit + css polish: content (news/pages/uploads) moves to the ctao/content repo (build overlays it — two-repo build.sh, link-content.sh for local dev); portal copy trimmed to source-documents-only (dashboard empty states, minimal privacy/disclaimer, no mock badges); css: inverted selection on navy bands, featured-card text-track floor, 44px toc target, mobile search dates on own line, footer link row gap; DESIGN.md rewritten as a short handoff 2026-09-03 18:54:01 +02:00
ctao 2e6d374b4b article headlines: --fs-h2 instead of --fs-h1 (long news titles filled the viewport at 56px; editorial 32-40px convention) 2026-09-03 15:14:44 +02:00
ctao c4d21b59be review round 2: deterministic checks (npm run check: build + zero-dep link checker; tsconfig with docs-recommended include/exclude, astro check dropped — OOMs at 12GB on a 200-entry collection), sitemap static pages derived from route files, non-recursive content glob (id/slug trap), --header-h token, honest CDN comments (sveltia UI fonts), preview.css drift (prose a wrap, img block, h1 size) 2026-09-03 15:11:43 +02:00
ctao cec8018c18 handoff pass: vendored sveltia bundle, astro pinned ^7.2.10, root README + refreshed deploy runbook, review fixes (coverless card grid, shared NewsCard/lib, real site origin, nginx security headers, linger step, dead config removed, preview.css sync, tsconfig) 2026-09-03 14:56:53 +02:00
ctao c64528a1d3 cors: full-origin ALLOW_DOMAIN (no SCHEME key in 1.27) + Authorization in allowed headers 2026-07-29 10:47:02 +02:00
ctao 746b015e85 vhost switch: Sveltia backend + Gitea ROOT_URL to https ingress domains, CORS locked to the portal origin 2026-07-29 10:35:26 +02:00
ctao a74eef0e47 ui iteration: nav underline moved to ::before (restores external arrow on Data), card reveal tuned (entry 75%, 28px), reading progress 3px, permanent lifted header shadow; article honesty pass: app-in-path class line (Grav/Tina clarified), 'is Gitea a second CMS' section, no-VPN claim removed 2026-07-29 07:55:22 +02:00
ctao c6de06f017 fix: esbuild css minifier — lightningcss folds animation-timeline into the animation shorthand (invalid), silently killing scroll-driven effects in production 2026-07-28 21:25:59 +02:00
ctao bbbb090cce ui polish pass: scroll-driven effects (card reveal, header shadow, reading progress), nav underline-grow, card meta icons, og:image svg fallback, apple-touch-icon+favicon, hero 472->180KB + preload, article cover CLS guard, ::selection, per-article lang, robots noindex (demo); article: db-cms comparison diagram, 6-year pros/cons, reuse section 2026-07-28 20:57:51 +02:00
ctao 54de9d3ee9 content: demo article tone pass (neutral wording) 2026-07-28 20:08:36 +02:00
ctao f6340a89b3 article meta: inline icons (tag/author/date/read-time) in the existing .ico stroke family 2026-07-28 20:06:49 +02:00
ctao 93712a1481 content: demo article — custom SVG diagrams (machine architecture, publish flow, attack surface) + cover, dated to sit second on the list 2026-07-28 20:03:44 +02:00
ctao 71b9808a11 content: demo presentation article (architecture, links, static-vs-database comparison) 2026-07-28 19:53:25 +02:00
ctao c0edb2033d cms: 1:1 article preview (mirror .prose tokens/rules), webp upload transformations to keep repo lean 2026-07-28 19:49:37 +02:00
ctao 145490fe60 Update Article “the-ctao-eric-council-visits-ctao-north-in-la-palma” 2026-07-28 17:37:51 +00:00
ctao 48fa17c351 Update Article “the-ctao-eric-council-visits-ctao-north-in-la-palma” 2026-07-28 17:36:58 +00:00
ctao ba629e2a96 cms: oauth-only sign-in (documented auth_methods), note on localhost-only local-repo button 2026-07-28 19:33:51 +02:00
ctao d06c2cf0c0 cms: CTAO branding on sign-in (app_title, logo.src instead of deprecated logo_url) 2026-07-28 19:29:04 +02:00
ctao c0d06f4a17 fix: /admin redirect loses port behind tunnel/proxy (absolute_redirect off, trailing slash in footer link) 2026-07-28 19:24:34 +02:00
ctao 99f5601456 deploy log: OAuth app via API, Sveltia pointed at machine Gitea, editor-cycle latency measured (12s/21s) 2026-07-28 18:35:41 +02:00
ctao 29385a5ca7 content: remove latency-test article 2026-07-28 16:31:58 +00:00
ctao 5744c16a1c content: simulate a Sveltia editor save (test) 2026-07-28 16:30:34 +00:00
ctao bc0201252d cms: point Sveltia at the machine Gitea (localhost:3000, machine OAuth app_id) 2026-07-28 18:24:40 +02:00
ctao 46ec5e2e3c deploy: npm ci --ignore-scripts (kills postinstall vector + esbuild ETXTBSY race); persistent npm cache; deployment log 2026-07-28 15:39:00 +02:00
ctao 80d886c0cb deploy: join gitea netns instead of bridge (machine kernel lacks ip_tables for rootless netavark); pin image digests; push-to-create repo 2026-07-28 14:07:44 +02:00
ctaoandClaude Fable 5 ed7a9c01e7 deploy: machine deployment — quadlets (gitea, nginx, isolated build network), poll+build pipeline, runbook
Reviewed (Opus security review, all findings verified) and tested end-to-end
locally: cold build 23s, no-op poll 45ms, incremental rebuild 4s, prune,
gitea-down grace, real bridge network with container DNS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012jWfn3RwPfFGTtBddm36Uy
2026-07-28 13:44:46 +02:00
ctao 4102974927 test: trigger deploy pipeline (empty commit) 2026-07-28 13:33:05 +02:00
ctao beb585e5dc fix: eyebrow/UI font jump — Inter inlined too; zero font swaps site-wide 2026-07-26 00:18:49 +02:00
ctao 47902f7278 fix: hero headline font jump on load — Space Grotesk inlined as data URI in the stylesheet (renders correctly from first paint; no swap-triggered balance reflow) 2026-07-26 00:12:28 +02:00
ctao/demoandClaude Fable 5 0bb378e0cc fix: typing-in-header scroll jump — global scroll-padding-top made Chrome's per-keystroke caret reveal walk the page to 0; offset moved to anchor targets (main [id] scroll-margin-top)
Chrome re-scrolls the focused editable into view on every keystroke and
honours the viewport's scroll-padding-top while ignoring the editable's
own scroll-margin (crbug.com/41492445) — so the sticky-header search
field, which sits above the 76px line and can never be scrolled below
it, dragged the page toward 0 one keystroke at a time, and the previous
negative-scroll-margin counter could not work. CDP harness bisection:
of all suspects (smooth scroll, sticky header, overflow clip,
animations, ambient layer, suggest siblings) only removing the global
scroll-padding stops the jump. TOC/skip anchor offset now lives as
scroll-margin-top on the in-content targets; harness confirms scrollY
constant while typing and anchors still land below the header.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012jWfn3RwPfFGTtBddm36Uy
2026-07-25 23:56:05 +02:00
ctao 694b7d2003 fix: page jumped to top while typing in header search — caret-visibility scrolling honours scroll-padding-top; negative scroll-margin on header inputs cancels it (suggestions re-enabled) 2026-07-25 18:59:57 +02:00
ctao 61f5d8444f fix: scroll jumps while typing — overflow-anchor:none on live search lists (Chrome scroll anchoring) 2026-07-25 18:50:54 +02:00
ctao affa3262ef fix: search scroll jump (atomic list swap, no clear-before-await); services grid back to 6 tiles (2 subdued planned) + planned line for the rest 2026-07-25 18:33:06 +02:00
ctao 1ef2cab89e fix: navigation blink root-caused — defer search-client.js (was parser-blocking mid-body), npm run demo serves the production build through the tunnel; DESIGN.md documents the VT removal + font-swap decision 2026-07-25 17:48:48 +02:00
ctao c9161af655 style: login card refined per guidebook — SG-medium h2 headline, D.3.4 hierarchy, logo min-size, token rhythm 2026-07-25 17:35:28 +02:00
ctao d0eeedb62b Revert "feat: login rewritten — split-screen auth (white form column + living aurora pane, tagline), 2026 convention"
This reverts commit 185380ffa5.
2026-07-25 17:25:42 +02:00
ctao 185380ffa5 feat: login rewritten — split-screen auth (white form column + living aurora pane, tagline), 2026 convention 2026-07-25 17:03:40 +02:00
ctao e7c804c9da fix: zero font jumping on navigation — preload both brand fonts + font-display: optional (no late swaps; brand-approved Arial if cold) 2026-07-25 15:36:31 +02:00
ctao 075d44dce3 feat: hero photo — LST-1 under the Milky Way (full-res original fetched from ctao.org) 2026-07-25 15:17:33 +02:00
ctao 6e3f8e23b6 style: one protagonist per surface — photo hero keeps Ken Burns only (flares removed), login gets Gemini-style large soft aurora; services trimmed to 4 real tiles + planned line 2026-07-25 14:55:01 +02:00
ctao 95f8f7b1a6 fix: lava-lamp blobs — defined 30% cores, alpha .5-.6, screen blend, 9s cycle, wide travel (motion objectively 12x stronger: 0.6%->7.6% px delta) 2026-07-25 14:44:23 +02:00
ctao 4082be0c37 fix: hero motion clearly perceptible — Ken Burns on the photo layer, glow opacity pulse, faster cycles (12s/26s) 2026-07-25 14:21:47 +02:00
ctao 97d1a0aa43 content: unpublish test article (will be recreated live during the demo) 2026-07-25 13:45:10 +02:00
ctao 3362b214c3 fix: final-review findings — own-lead descriptions, PHS integration landing, planned tiles, 404, sitemap math, cleanups 2026-07-25 13:13:27 +02:00
ctao ffe54bc766 fix: light dismiss for header menu/search overlays (outside tap closes, never activates) 2026-07-25 12:33:09 +02:00
ctaoandClaude Fable 5 4fa35e5317 fix: mobile gutters, header search tiers, suggest wiring + empty-state, news pagination, hero CTAs, ghost button
- padding-block on .page/.page-head/.article: container gutter survived only >=1176px (shorthand padding zeroed it) — mobile content sat flush left
- restore <=1024px tiers: field hidden, magnifier + menu visible (header no longer overflows at 375px)
- re-wire hdr-q suggestions lost in the field restore; newsSearch inits null-safe
- focused-empty search state: recent queries (localStorage, cap 4) + latest articles (Baymard autocomplete guidance)
- suggest panel 480px (Baymard/DocSearch width convention)
- /news page 1 = 25 items (featured + 24 = even 3-col rows), later pages 24, custom slicing
- hero CTAs: primary Browse news (/news, spec-first real service), ghost Explore the data (external); mock Proposals out of hero
- .btn.btn--ghost compound selector — .btn base defined later was filling ghosts with Cherenkov

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012jWfn3RwPfFGTtBddm36Uy
2026-07-25 12:16:41 +02:00
eaf511a976 feat: Apple-grade pass — scale, type, photographic hero, de-noise, single-signal chrome, lighter header
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012jWfn3RwPfFGTtBddm36Uy
2026-07-25 11:47:30 +02:00
ctao 3202b853a7 Update Article “the-ctao-eric-council-visits-ctao-north-in-la-palma” 2026-07-25 09:39:48 +00:00
ctao d564f840e8 Update Article “the-ctao-eric-council-visits-ctao-north-in-la-palma” 2026-07-25 09:39:25 +00:00
ctao 0516cae5b2 content: Opus editorial workflow — headings for 132 articles, markdown hygiene for 150 (206 reviewed); disclaimer content credit 2026-07-25 07:54:14 +02:00
ctao d014445248 feat: page composition per IA research — section order, tile icons, cross-page coherence 2026-07-25 07:53:07 +02:00
ctao 419e9a2dd1 feat: content-UX — header search, metadata de-noise, icon/microcopy coherence, article composition 2026-07-25 07:32:11 +02:00
ctao a8833ce736 content: manual editorial pass over all news articles (headings, quotes, captions) 2026-07-25 07:02:35 +02:00
ctao 4ace039233 feat: astro built-ins — news pagination, prefetch, RSS, OG/SEO meta, TOC scrollspy (Starlight-pattern) 2026-07-25 06:57:58 +02:00
ctao 2de0cf148e fix: unclip search-suggest dropdown — nebula clip moved to dedicated .band-bg layer; suggest above sticky header 2026-07-24 23:44:54 +02:00
ctao 3b736af25e feat: design system — ambient motion, spacing/elevation/surface tokens, right-rail TOC, footer links, DESIGN.md 2026-07-24 23:39:17 +02:00
ctao ea4ebd2d70 feat: round 2 — responsive header (width-math tiers), brand footer, editorial article, modern search; 2026 upgrades (view transitions, text-wrap, self-hosted fonts, inline SVG icons, color-mix, subgrid, container queries) 2026-07-24 23:14:09 +02:00
ctao a8c0e802e2 feat: pages collection (privacy/disclaimer/contact) editable in CMS 2026-07-24 23:11:57 +02:00
ctao cf35739a64 feat: portal IA per spec (home entry point, /news, /proposals+redirect, /support), live search + suggestions, article TOC, mobile disclosure menu, CMS preview styles, modernization pass 1 2026-07-24 23:00:07 +02:00
ctao 11e7eb484f content: import remaining ctao.org news (206 articles total, stress test) 2026-07-24 22:37:52 +02:00
ctao 009a6ae52a feat: CTAO-branded CMS sign-in (logo_url plaque asset), EN editor title 2026-07-24 22:19:40 +02:00
ctao 7c949bbb3c style: ctao.org-inspired pill buttons + larger radius; reorder: ERIC Council featured, test article second 2026-07-24 22:14:20 +02:00
ctao 02f30af010 feat: CTAO brand redesign (official tokens, Inter+Space Grotesk, logo), spec-based nav/footer, EN UI, card covers + featured lead, mock badges, Sveltia logo; remove fake demo articles 2026-07-24 22:09:13 +02:00
ctao 778ab25063 config: switch Sveltia backend to localhost (tunnels closed) 2026-07-24 21:18:44 +02:00
ctao 7286121f71 content: restore inline images lost in import conversion 2026-07-24 21:11:02 +02:00
ctao 366c95aa86 content: import 29 news articles with images from ctao.org 2026-07-24 21:05:16 +02:00
ctao f8181396b1 Update Artykuł “testowy-artykuł-tytuł” +2 2026-07-24 18:41:28 +00:00
ctao 757752d91f demo: move test-article source note to the top 2026-07-24 20:40:19 +02:00
ctao 45c5e3c407 demo: richer Markdown formatting in test article (headings, table, lists, quote) + prose styles 2026-07-24 20:37:49 +02:00
ctao 228ad5b60b fix: cover as public-path string (Sveltia uploads) + render cover in article 2026-07-24 20:34:22 +02:00
ctao ed45ff6d54 Create Artykuł “testowy-artykuł-tytuł” +2 2026-07-24 18:31:33 +00:00
ctao 8a2bf69576 restyle: flat Cherenkov-blue design, WCAG (skip link, focus, labels), auto dark mode 2026-07-24 20:20:29 +02:00
ctao 4180882065 fix: new Gitea tunnel URL in CMS config 2026-07-24 20:14:02 +02:00
ctao c2016e723f fix: gitea backend + allowedHosts + admin route 2026-07-24 19:59:45 +02:00
ctao a1c85d73c7 CTAO portal demo (Astro + Sveltia) 2026-07-24 18:09:00 +02:00
578 changed files with 10505 additions and 2447 deletions
-4
View File
@@ -1,4 +0,0 @@
*.sh text eol=lf
*.service text eol=lf
*.timer text eol=lf
*.container text eol=lf
-13
View File
@@ -2,16 +2,3 @@ node_modules/
dist/
.astro/
.DS_Store
# Content lives in the ctao/content repo (deploy copies it in; npm run content:link links ../content locally)
src/content/news/
src/content/pages/
public/uploads/
# Session scratch
.tmp/
# Local secrets and editor state
.env*
.vscode/
.idea/
+21
View File
@@ -0,0 +1,21 @@
<!doctype html>
<!-- Screenshot probe: headless Chrome clamps --window-size below ~500px, so a
fixed-width iframe gives a true narrow layout viewport. Copied into dist/
by shoot.sh; not part of the site. -->
<meta charset="utf-8">
<title>probe</title>
<style>html,body{margin:0;background:#b0b4c0}iframe{border:0;display:block}</style>
<script>
var q = new URLSearchParams(location.search);
var f = document.createElement('iframe');
f.src = q.get('p') || '/';
f.style.width = (q.get('w') || 390) + 'px';
f.style.height = (q.get('h') || 2000) + 'px';
f.onload = function () {
var s = +(q.get('s') || 0);
// behavior:instant — the site sets scroll-behavior:smooth, which would
// leave the screenshot mid-animation
if (s) f.contentWindow.scrollTo({ top: s, behavior: 'instant' });
};
document.addEventListener('DOMContentLoaded', function () { document.body.appendChild(f); });
</script>
Binary file not shown.

After

Width:  |  Height:  |  Size: 675 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

+45
View File
@@ -0,0 +1,45 @@
#!/bin/zsh
# Screenshot helper for the CSS polish pass. $1 = output dir (before|after)
# Narrow widths go through dist/__probe.html (a fixed-width iframe): headless
# Chrome clamps --window-size below ~500px, so 360/390/480 shots would silently
# be 500px-wide renders cropped to the window.
set -e
CH="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
OUT="$(cd "$(dirname "$0")" && pwd)/$1"
mkdir -p "$OUT"
cp "$(dirname "$0")/__probe.html" "$ROOT/dist/__probe.html"
shot() { # name url w h
"$CH" --headless=new --disable-gpu --hide-scrollbars --window-size=$3,$4 \
--screenshot="$OUT/$1.png" "$2" >/dev/null 2>&1
}
wide() { shot "$1" "http://localhost:4330$2" $3 $4; } # name path w h
narrow() { # name path w h (h = iframe height)
shot "$1" "http://localhost:4330/__probe.html?w=$3&h=$4&p=$2" $(($3 + 20)) $(($4 + 10))
}
W='/news/ctao-and-cabildo-of-la-palma-organise-women-of-ctao-2026'
wide home "/" 1440 2600
narrow home-390 "/" 390 2200
wide news "/news" 1440 2600
narrow news-390 "/news" 390 2400
wide news-768 "/news" 768 2200
wide art6 "/news/6" 1440 2600
narrow art6-390 "/news/6" 390 2600
wide artwomen "$W" 1440 2600
narrow artwomen-390 "$W" 390 2600
wide artwomen-1280 "$W" 1280 2000
wide search "/search?q=lst" 1440 2000
narrow search-390 "/search?q=lst" 390 1800
wide support "/support" 1440 1800
narrow support-360 "/support" 360 1500
wide privacy "/pages/privacy" 1440 1400
narrow privacy-390 "/pages/privacy" 390 1500
wide nf "/404-nonexistent" 1440 1200
narrow nf-390 "/404-nonexistent" 390 1300
wide proposals "/proposals" 1440 1600
narrow proposals-390 "/proposals" 390 1700
wide dashboard "/dashboard" 1440 1600
narrow dashboard-480 "/dashboard" 480 1900
wide login "/login" 1440 1200
narrow login-390 "/login" 390 1100
echo "shots in $OUT"
+32
View File
@@ -0,0 +1,32 @@
# Design handoff
All styling lives in **`src/styles/global.css`** — design tokens in `:root`
(colors, type scale, spacing, radius, shadows, `--header-h`) + plain-CSS
components below them. No frameworks, no preprocessors, no runtime JS for
presentation. Pages are `.astro` templates (plain HTML).
## Brand (official: CTAO Brand Guidelines, April 2024 — PDF in the project docs)
- Colors: Galaxy Blue `#00004A`, Cherenkov Blue `#00E4D8`, Moon Gray
`#F5F5F5`, Cosmic Azure `#007AFF` (links/focus), Interstellar Indigo
`#00009C`. Cherenkov is an ACCENT (brand book rations it — currently:
primary CTA, hero subtitle, logo flash); it fails AA as text on white.
- Type: **Inter** for everything, **Space Grotesk** (weight 500) only for
communicative headlines (h1/h2). Sizes come from the `--fs-*` scale.
- Logo files in `public/brand/`; always "CTAO", min 100px wide, no effects.
## Practical notes (the non-obvious ones)
- **`public/admin/preview.css` mirrors the `.prose` styles** so the CMS
editor preview matches articles 1:1 — when you touch `.prose` or its
tokens, update the mirror (both files carry a KEEP IN SYNC comment).
- Fonts ship **inlined as data: URIs** in global.css (no font requests, no
swap flash). The woff2 files in `public/fonts/` look unused but feed the
CMS preview — keep them.
- All `animation`/`transition` rules sit in the single
`prefers-reduced-motion: no-preference` block at the end of global.css.
- Scroll-driven effects need `vite.build.cssMinify: 'esbuild'`
(astro.config.mjs) — the default minifier breaks `animation-timeline`.
- `--header-h` drives the sticky-header offsets (anchors, TOC rail, reading
progress); change the header height only through the token.
- Deterministic gate before pushing: `npm run check` (build + link check).
+66 -42
View File
@@ -1,54 +1,78 @@
# CTAO Science Portal
# CTAO Science Portal — static site + git-based CMS
Static Astro site with a git-based CMS (Sveltia). Code lives here; editorial
content lives in the separate [`ctao/content`](https://astro-git.isl-dev.grid.cyfronet.pl/ctao/content)
repository. The Home page and four Example pages are editable; the rest are placeholders.
Public portal for CTAO news, built as a fully static site. Editors get a
browser WYSIWYG editor (Sveltia CMS) that commits Markdown to a Gitea repo;
a build job turns commits into static HTML. No application or database runs
on the public path.
- Live demo: https://astro.isl-dev.grid.cyfronet.pl
- Editor: https://astro.isl-dev.grid.cyfronet.pl/admin/ (Gitea sign-in)
Live demo: <https://astro.isl-dev.grid.cyfronet.pl> ·
Gitea on the demo machine: <https://astro-git.isl-dev.grid.cyfronet.pl>
## Run locally
## Repository layout & deployment
Node 22.19 or newer. Clone the content repository next to this one:
Code and content are SEPARATE repositories, so editorial commits never mix
with development:
```sh
git clone https://astro-git.isl-dev.grid.cyfronet.pl/ctao/content.git ../content
| Repo | Holds | Source of truth | Who commits |
|---|---|---|---|
| `ctao-portal` (this one, Bitbucket) | Templates, CSS, deploy | development | the team |
| `ctao/content` (Gitea on the demo machine) | `news/`, `pages/`, `uploads/` | editorial | Sveltia CMS / editors |
The demo machine polls ITS Gitea (`ctao/portal` mirror + `ctao/content`) and
republishes on a push to either — so a push to Bitbucket does NOT deploy;
deploying code = pushing it to the machine's Gitea (a deliberate step). The
build overlays content onto code (`deploy/build.sh`); the content paths are
gitignored here.
## Stack
- **Astro** (static output). Pages are `.astro` templates (plain HTML with a
JS frontmatter block). No React/Vue/Svelte components, no client-side
framework.
- **Plain CSS** — everything lives in `src/styles/global.css` as design
tokens (custom properties) + rules. No Tailwind, no preprocessor.
The design system (tokens, type scale, spacing, motion policy) is
documented in `DESIGN.md` — read it before touching styles.
- **Sveltia CMS** — a single prebuilt JS bundle, vendored in
`public/vendor/sveltia-cms.js` and loaded on `/admin/`
(`src/pages/admin/index.astro`), configured by `public/admin/config.yml`.
It runs entirely in the editor's browser and talks to the Gitea API
(OAuth PKCE). There is no CMS server. To update it:
`curl -sL https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js -o public/vendor/sveltia-cms.js`
- Runtime dependencies: none beyond Astro. This is deliberate — keep it
that way.
## Develop
```
npm ci
git clone https://astro-git.isl-dev.grid.cyfronet.pl/ctao/content.git ../ctao-content
./scripts/link-content.sh # copies news/pages/uploads into the dev tree
npm run dev # http://localhost:4321
npm run build # static output in dist/
npm run check # deterministic gate: build + internal-link check
```
The site reads `../content` directly through links, so pull that repository
for the latest content. To use a checkout elsewhere:
`npm run content:link -- <path>`.
## Layout
Before pushing code, run `npm run check` (types, build, internal links).
## Edit content locally
With `npm run dev` running, open `http://localhost:4321/admin/` in Chrome or
Edge, choose **Work with Local Repository** and select the `../content` folder.
Saves write the Markdown files and show up on the local site on reload. They
are not committed: commit and push in `../content` to publish.
## Publishing
Pushing to `main` of `ctao/portal` (code) or `ctao/content` (content) on
Gitea rebuilds and publishes the live demo automatically; the online editor
commits to `ctao/content` directly. Pushing to Bitbucket does not deploy.
Server setup is in [deploy/README.md](deploy/README.md).
## Where things live
| Path | Purpose |
| Path | What |
|---|---|
| `src/layouts/Base.astro` | Header, navigation, footer, page shell |
| `src/layouts/ContentPage.astro` | Layout of the editable pages (Home and Examples) |
| `src/data/examples.json` | Example pages: menu labels, order, slugs |
| `src/data/placeholders.json` | Placeholder pages |
| `src/styles/global.css` | All site styles |
| `public/admin/config.yml` | CMS fields and Gitea connection |
| `scripts/preview-styles.mjs` | Builds the CMS preview styles from `global.css` |
| `scripts/content.mjs` | Links the content checkout into the site |
| `src/pages/` | Routes (`.astro` templates), incl. `news/`, `search`, RSS/sitemap |
| `src/layouts/Base.astro` | HTML shell: head, header/nav, footer |
| `src/content/news/*.md` | Articles (from the content repo — gitignored here; schema in `src/content.config.ts`) |
| `src/styles/global.css` | All CSS: tokens + components + prose |
| `DESIGN.md` | Design handoff: where the tokens live, brand basics, practical notes |
| `public/admin/` | CMS config (`config.yml`) + editor preview styles (`preview.css`) |
| `public/uploads/` | Editor-uploaded media (from the content repo — gitignored here) |
| `deploy/` | Runbook + container/systemd units for the demo machine — see `deploy/README.md` |
To add an Example page, add it to `src/data/examples.json` and
`public/admin/config.yml`, and commit its Markdown file to `ctao/content`.
## Editing content
Editors use `/admin/` (link in the footer) and sign in with a Gitea account.
Saving commits to `main`; the machine polls and republishes automatically
(seconds). Full history/rollback = git history in Gitea. Articles with
`draft: true` are excluded from the build.
Note for styling work: `public/admin/preview.css` mirrors the `.prose` rules
from `global.css` so the editor preview matches the site 1:1 — keep them in
sync (both files carry a KEEP IN SYNC comment).
+23 -3
View File
@@ -1,9 +1,29 @@
import { defineConfig } from 'astro/config';
// Static output (zero runtime) — the whole point of the git-based approach.
export default defineConfig({
output: 'static',
// Public origin — used to build absolute URLs (canonical, og:*, RSS,
// sitemap). Change when the portal moves to its production domain.
site: 'https://astro.isl-dev.grid.cyfronet.pl',
trailingSlash: 'always',
build: { format: 'directory' },
compressHTML: true,
// Built-in prefetch on every internal link (no per-link attributes needed).
// Default 'hover' strategy: near-instant navigation without the bandwidth
// cost of 'viewport' on a 24-card grid; auto-falls back to 'tap' on
// data-saver / slow connections (Astro handles that).
prefetch: { prefetchAll: true },
server: { port: 4321, host: true },
vite: {
build: {
// Lightning CSS (the default minifier) illegally folds animation-timeline
// INTO the `animation` shorthand (`animation: ... view()`), which browsers
// reject — silently killing every scroll-driven effect in production.
// esbuild minifies without property merging.
cssMinify: 'esbuild',
},
server: {
// Vite blocks unknown Host headers by default; the demo is viewed through an
// ephemeral cloudflared quick tunnel, so allow any *.trycloudflare.com host.
allowedHosts: ['.trycloudflare.com'],
},
},
});
+199
View File
@@ -0,0 +1,199 @@
# Deployment log — CTAO portal demo on strapi-experimental.cyfronet
Living document: what was done, what broke, what is left. Update it as work
proceeds so any agent (or human) can resume without the chat history.
Started 2026-07-28.
## Goal
Run the git-based CMS demo (Gitea + Sveltia + Astro static build) on the
Cyfronet test machine so the SUSS-PORT team can click through it. Shape must
be production-viable, not throwaway. Machine footprint: one directory
(`~/ctao-portal-demo/`) + 4 unit files, fully removable (README "Uninstall").
## Ground rules (from the user, non-negotiable)
- **Write every command out in chat, marked read-only / write, BEFORE running
it** — the user reviews on a phone and cannot see truncated tool calls.
- Non-invasive verification first, debugging second: confirm each precondition
instead of fixing after the fact.
- Never read secrets (`~/.ssh/*`, tokens). Mask token URLs in any output
(`sed 's|://[^@]*@|://***@|g'`). Secrets stay in 0600 files on the machine.
- No sudo, nothing outside `$HOME` on the machine, no traces for the admin.
- Temp/working files under `.tmp/<date>-<topic>/` in the repo, never `/tmp`.
- Commit messages: **no "Co-Authored-By: Claude"**, no session links.
## Architecture (decided, see also README.md in this dir)
```
editor → Sveltia (/admin, static) → commit → Gitea :3000 (container, SQLite)
│ polled every 10 s
build.sh (systemd user timer)
└─ podman run node+git, --network=container:ctao-demo-gitea
npm ci (only if lockfile changed) → astro build
→ releases/<sha>/ → atomic symlink flip
│
nginx :8080 serves releases/current
│ (TODO) ingress vhost → team access
```
## Machine facts (verified 2026-07-28, read-only)
- Rocky Linux 9.7, host `outline.openstacklocal`, IP 192.168.10.15, 2 vCPU,
3.6 GB RAM, no swap. User `strapi`, no sudo, `Linger=yes`, user systemd running.
- Podman 5.6.0 rootless. Host has node 24.17, npm, curl, jq, rsync — **no git**,
**no nginx** (hence the build container / nginx container).
- Neighbours: pod-strapi (`:1337`), Outline (`:9091`), code-server (`127.0.0.1:41787`).
- **VPN does NOT route to machine ports** (`curl 192.168.10.15:1337` times out);
only the admin-managed HTTPS ingress reaches the machine
(`https://strapi.isl-dev.grid.cyfronet.pl` → 204). Hence ports 3000/8080 are
reachable for us only via `ssh -L`, and the team needs vhosts from Hubert.
## DONE
### 1. Local: files authored + reviewed + tested (before touching the machine)
- Wrote quadlets, `build.sh`, systemd service/timer, `nginx.conf`,
`Containerfile.build`, `README.md` (runbook incl. uninstall).
- **Opus security review** — 8 findings, every one verified personally:
BLOCKER `--network=host` (build code could reach host loopback: code-server
= RCE, Strapi) → replaced; `OnUnitActiveSec` is start-relative, not
end-relative (agent corrected me) → `OnUnitInactiveSec`; missing `mkdir` for
`~/.config/*`; nginx-before-first-build race; prune `pipefail` landmine;
noisy unit failure when Gitea is down; CORS `SCHEME` note; RAM headroom.
All fixed. No hallucinated findings.
- **Local end-to-end test** against the local Gitea (`:3010`), sandbox
`.tmp/2026-07-28-deploy-test/`: cold build 23 s · no-op poll 45 ms ·
incremental rebuild 4 s · prune ✓ · Gitea-down grace ✓ · anonymous clone
(no token in `.git/config`) ✓.
- Commits (local Gitea repo `ctao/portal`): `ed7a9c0`, `80d886c`.
### 2. Machine: install steps 1–7 of README
- Created `~/ctao-portal-demo/{gitea-data,gitea-config,releases,state,bin,config}`
and `~/.config/{containers/systemd,systemd/user}`.
- Copied unit files, `build.sh`, `nginx.conf`, `Containerfile.build`.
- Pulled + **pinned by digest**: gitea 1.27-rootless, nginx stable-alpine,
node:24-alpine; built `localhost/ctao-portal-build:1`.
- Gitea running (`:3000`, API version 1.27.1), admin `ctao` created with
`--random-password` → `~/ctao-portal-demo/state/initial-admin.txt` (0600),
push token → `state/push-token.txt` (0600). **Never printed in chat.**
- Content pushed via git bundle (78 MB, one-off bootstrap): repo `ctao/portal`
at `80d886c`, public, anonymously clonable. Bundle deleted afterwards.
- **First build on the machine: 44 s** (`npm ci` ~35 s + astro build 6.5 s),
225 pages. Incremental rebuilds should be ~10 s (measure to confirm).
- nginx container running, `http://localhost:8080/` → **200, `<title>CTAO
Science Portal</title>`**.
- Poll timer enabled (`ctao-portal-build.timer`), ticking every ~10 s, journal
clean.
### 3. Problems hit on the machine (and the fixes)
1. **Rootless bridge network impossible**: `netavark: modprobe ip_tables:
Operation not permitted` → quadlet restart loop. Root cause: kernel module
not loaded, rootless cannot modprobe, no sudo. **Fix:** dropped
`ctao-demo.network`; build joins Gitea's netns
(`--network=container:ctao-demo-gitea`), so it reaches Gitea on
`localhost:3000` while the host loopback stays unreachable (pasta).
Same security property as the bridge, zero privileges. Lesson written to
`.skills/podman-quadlet/SKILL.md`.
2. **Push rejected**: admin was created with `--must-change-password` →
`remote: Update your password`. **Fix:** `gitea admin user
must-change-password --unset ctao`.
3. **My rc-chain bug**: `... | sed` made `$?` the sed's status, so a failed
push printed "push-ok" and the bundle was deleted prematurely → had to
re-upload 78 MB. **Fix:** capture output in a var, check rc of the real
command. (Also why the upload "took so long" — it ran twice.)
4. **Push-created repo was private** despite `DEFAULT_PRIVATE=public` —
push-to-create has its own key. **Fix:** PATCH via API to public +
`GITEA__repository__DEFAULT_PUSH_CREATE_PRIVATE=false` in the quadlet.
5. **`npm ci` failed: `ETXTBSY` spawning `esbuild`** (postinstall race in a
rootless container, ~5 min wasted). **Fix:** `npm ci --ignore-scripts`
(also removes the malicious-postinstall vector) + persistent npm cache
volume `~/ctao-portal-demo/npm-cache`. Validated locally, then on machine.
### 4. Left on the machine (known, not cleaned — user asked to be told, not tidied)
- Orphan systemd entry `ctao-demo-network.service` (not-found/active-exited),
ghost of the removed network quadlet. Harmless; clears with
`systemctl --user stop ctao-demo-network.service`.
- `/tmp/storage-run-1003/` (~136 KB) — podman runroot from ssh calls without
`XDG_RUNTIME_DIR`. Disappears on reboot. (`storage-run-1001` is NOT ours.)
- Red journal entries from the netavark restart loop (history, not a live fault).
- Disk: 11 G → 9.6 G free (images ~590 MB + repo/content). RAM available ~1.3 G.
### 5. Pipeline proven end-to-end on the machine (2026-07-28)
- Pushed `46ec5e2` to the machine's Gitea (incremental bundle, 6 KB — this is
what ongoing syncs cost, vs the 78 MB one-off bootstrap).
- **The poller published it automatically in 26 s end-to-end** (push →
visible), of which the build itself was 21 s. That is the demo's publish
latency: **~30 s worst case** on 2 vCPU (vs 44 s for the very first build
which included `npm ci`). Cold-cache builds only happen when the lockfile
changes.
- Verified after publish: `/` 200, `/admin/` 200 (Sveltia), an article page
200; neighbours untouched (Strapi 204, Outline 200); RAM available 1.2 G,
disk free 9.0 G.
### 6. Sveltia wired to the machine + editor cycle proven (2026-07-28)
- **OAuth app created via API, no UI clicking**: temp `write:user` token from
`gitea admin user generate-access-token` (CLI in container) → POST
`/user/applications/oauth2` → temp token deleted (needed basic auth from
`initial-admin.txt`; token-auth DELETE returns 401). App: "Sveltia CMS
(demo)", `client_id cf1c44ac-ebe6-4a97-bf2f-f7f26ef1126c`, PKCE
(`confidential_client=false`), redirect_uris: `http://localhost:18080/admin/`
and `http://localhost:8080/admin/`.
- **Found & fixed**: `public/admin/config.yml` still pointed at the MAC's Gitea
(`localhost:3010` + old app_id) — CMS login on the machine could never work.
Switched to `localhost:3000` + machine app_id (`bc02012`). localhost:3000 is
valid both on the machine and through the tunnel (same-port trick).
- **Editor cycle simulated exactly like Sveltia does it** (Gitea contents API):
create article via API → **live on the portal in 12 s**; delete via API →
**gone in 21 s**. Repo left clean (`29385a5`). Earlier full-push rebuild:
16 s build. Publish latency ≈ **10–30 s** depending on poll-tick alignment.
- Tunnel run from the Mac with keepalive (`-o ServerAliveInterval=30`), portal
mapped to **18080** locally (user request: non-default port), Gitea kept at
**3000** (must match ROOT_URL). First tunnel died after idle — keepalive fixed.
- Push token scope confirmed minimal (`write:repository` only) — cannot manage
users/apps; file format is `<name>:<40-hex>` (grep the hex out).
## TODO (next agent starts here)
1. ~~Commit the `--ignore-scripts` fix and mirror to the machine~~ — DONE
(`46ec5e2`, and it doubled as the live pipeline test above).
2. ~~Measure incremental publish latency on the machine~~ — DONE: **26 s
end-to-end**, build 21 s.
3. ~~Verify the loop through `ssh -L`~~ — mostly DONE: tunnel
`-L 18080:localhost:8080 -L 3000:localhost:3000` (with ServerAlive
keepalive) verified with real content. **Remaining: the user clicks
"Sign in with Gitea" at `http://localhost:18080/admin/`** and edits an
article in the browser — the only step that needs a human + browser
(PKCE login). Credentials: `initial-admin.txt` on the machine.
4. ~~Sveltia OAuth app~~ — DONE via API (see §6). Config committed and
auto-published. Only the browser login test remains (see 3).
5. **Message to Hubert** (only after we have seen it working):
- two ingress vhosts → `192.168.10.15:8080` (portal) and `:3000` (gitea),
same mechanism as `strapi.isl-dev…:1337`;
- are vhosts public-internet or VPN-scopable? (Gitea preferably VPN-only);
- does Cyfronet offer static-file hosting on the ingress itself? If yes we
drop our nginx container and just rsync builds there.
6. **After vhosts arrive**: replace both `TODO(vhost)` in
`ctao-demo-gitea.container` (ROOT_URL, CORS origin + `GITEA__cors__SCHEME=https`),
`daemon-reload` + restart, update Sveltia config, re-test login.
7. **Backup**: nightly `tar` of `~/ctao-portal-demo/gitea-data` (the only
stateful thing; everything else is rebuildable). Not yet written.
8. **Decided but not implemented — split into two repos** (`portal` = code,
`portal-content` = markdown + uploads). Rationale: editors get write access
ONLY to content, so repo-push cannot alter `package.json`/templates that the
build executes; also keeps code history clean. Cost: `build.sh` clones two
repos and overlays content (~10 lines), polls two SHAs. Do this for the
production version, not mid-demo.
9. Demo choreography: the test article is `draft: true`; the user creates one
live during the demo. With the poller running, a Sveltia save publishes
automatically — no manual rebuild needed (unlike the Mac preview setup).
## Command cheat-sheet (machine)
```
export XDG_RUNTIME_DIR=/run/user/$(id -u) # needed for systemctl --user over ssh
systemctl --user status ctao-demo-gitea ctao-demo-web ctao-portal-build.timer
journalctl --user -u ctao-portal-build -n 20 --no-pager # build times land here
~/ctao-portal-demo/bin/build.sh # manual build
readlink ~/ctao-portal-demo/releases/current # which sha is live
curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/
```
+9 -54
View File
@@ -1,10 +1,10 @@
# Deploy: CTAO portal demo on strapi-experimental.cyfronet
# Deploy — CTAO portal demo on strapi-experimental.cyfronet
Everything runs rootless as user `strapi`. Footprint on the machine:
ONE directory (`~/ctao-portal-demo/`) + 4 unit files in `~/.config/`.
Uninstall restores the machine exactly (see bottom). No secrets in any file.
Two Gitea repos by design: `ctao/portal` (code, developed by the team) and
Two Gitea repos by design — `ctao/portal` (code, developed by the team) and
`ctao/content` (Markdown + uploads, committed by the CMS). The build overlays
content onto code; a push to EITHER republishes the site.
@@ -29,44 +29,9 @@ missing). Everything else runs inside containers.
| 3000 | Gitea | https://astro-git.isl-dev.grid.cyfronet.pl (ingress vhost) |
| 8080 | portal (nginx, static) | https://astro.isl-dev.grid.cyfronet.pl (ingress vhost) |
## Publish the simplified portal from `main`
1. Check that `ctao/content:main` contains the four Example files in `pages/`
(listed in `src/data/examples.json`) and the uploads they reference. They
are already committed there (`a3452d7`, "demo lorem ipsum pages"). News and
other page files can remain in the content repository; this portal loads
only the four Example files.
2. Push the content branch to Gitea as `ctao/content:main`, and the code
branch as `ctao/portal:main`. These are separate pushes; Bitbucket is
not the deployment remote.
3. Copy the updated `build.sh` to `~/ctao-portal-demo/bin/build.sh` and the
updated `ctao-portal-build.service` to `~/.config/systemd/user/`. Reload
systemd with `systemctl --user daemon-reload`. The service explicitly sets
`CODE_BRANCH=main` and `CONTENT_BRANCH=main`; the script defaults agree.
4. Start the build service or let its timer run. It overlays `pages/` and
`uploads/`, clears the previous news overlay, and runs `npm run check`
before the atomic release switch. Missing Example pages fail the build.
5. Verify the four `/example/…/` pages and empty `/news/`. Sign in at `/admin/`,
save an Example title, and check that a commit appears on
`ctao/content:main` and the rebuilt page reflects it.
The OAuth client and public portal origin stay the same. Sveltia's
`backend.branch` in `public/admin/config.yml` must always match
`CONTENT_BRANCH`. Branches are selected independently; a single `BRANCH`
environment variable is no longer used. For a separate preview hostname,
update the site origin, OAuth redirect, and Gitea CORS together.
To undo the simplification in shared code history, use
`git revert -m 1 <simplification-merge-commit>` on `main` and publish the
resulting commit. The previous portal code is also preserved at `0772462`.
For an immediate deployment rollback, restore the previous release and worker
script/service configuration. Content has its own history: reverting a code
commit does not revert CMS edits. Keep the old content files available if
the previous portal may be restored.
## Install (each step reviewed before running; [W] = writes to the machine)
1. **[W]** `loginctl enable-linger $USER`. Without lingering every user unit
1. **[W]** `loginctl enable-linger $USER` — without lingering every user unit
dies at logout and nothing starts after a reboot. Verify:
`loginctl show-user $USER -p Linger` → `Linger=yes`.
2. **[W]** `mkdir -p ~/ctao-portal-demo/{gitea-data,gitea-config,releases,state,bin,config} ~/.config/containers/systemd ~/.config/systemd/user`
@@ -84,16 +49,15 @@ the previous portal may be restored.
(Upgrading later = pick new digests deliberately, update the pins in the
`.container` files / `Containerfile.build`, re-pull, re-build.)
5. **[W]** `systemctl --user daemon-reload && systemctl --user start ctao-demo-gitea`
6. **[W]** Create the Gitea admin. Run it interactively in a terminal so the
6. **[W]** Create the Gitea admin — run interactively in a terminal so the
password never lands in a file or shell history:
`podman exec -it ctao-demo-gitea gitea admin user create --admin --username <you> --email <you@…> --random-password`
7. **[W]** In the Gitea UI: create org `ctao` with repos `portal` and
`content` (both public read). The content `main` branch needs the four
Example pages described above. Then, from your workstation, push both over
`content` (both public read). Then, from your workstation, push both over
the vhost with a repo-scoped token:
`git push https://<user>:<token>@astro-git.isl-dev.grid.cyfronet.pl/ctao/portal.git main`
`git push https://<user>:<token>@astro-git.isl-dev.grid.cyfronet.pl/ctao/content.git main`
8. **[W]** `systemctl --user enable --now ctao-portal-build.timer`. The first run
8. **[W]** `systemctl --user enable --now ctao-portal-build.timer` — first run
clones + `npm ci` + builds (minutes); later runs are seconds. Wait until
`journalctl --user -u ctao-portal-build -n 5` shows `published <sha>`
(starting nginx earlier just serves 404s until the first build lands).
@@ -101,10 +65,6 @@ the previous portal may be restored.
10. **[R]** Verify: `curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/`
and `journalctl --user -u ctao-portal-build -n 20` (shows measured build times).
Editor accounts need WRITE access to `ctao/content` (Gitea → repo →
Collaborators, or a team); public read alone lets them sign in but every
save fails.
## CMS sign-in (Sveltia ↔ Gitea OAuth)
- Gitea OAuth2 app (PKCE, `confidential_client=false`, no secret) with
@@ -134,11 +94,6 @@ loginctl disable-linger $USER # only if nothing else of yours should survive l
## Notes
- A release that fails to build is not retried until either repo gets a new
commit (`state/last-failed` guards against a 10 s rebuild loop). To force a
retry of the SAME release (e.g. after fixing `bin/build.sh` itself):
`rm ~/ctao-portal-demo/state/last-failed`.
- **Only pushes to the machine's Gitea auto-deploy** (code and content repos
alike). Changes to `deploy/*` need a manual
re-copy: `build.sh` → `bin/`, `nginx.conf` → `config/` +
@@ -152,12 +107,12 @@ loginctl disable-linger $USER # only if nothing else of yours should survive l
lockfile changes. Routine rebuilds are fully offline.
- Memory caps (`MemoryHigh`) keep us polite next to Outline + Strapi;
the build container is capped at 1 GB via `podman run --memory`. Watch the
first `npm ci` + build in the journal; if it OOMs inside its cgroup
first `npm ci` + build in the journal — if it OOMs inside its cgroup
(contained, just retries), raise the cap.
- Build isolation: build containers join the Gitea container's network
namespace (`--network=container:ctao-demo-gitea`): repo/npm code sees
namespace (`--network=container:ctao-demo-gitea`) — repo/npm code sees
Gitea on localhost:3000 but cannot reach host loopback services. (Rootless
netavark bridges don't work here: no `ip_tables` kernel module, no sudo.)
- Secrets inventory: Gitea admin password (typed interactively, lives only
in Gitea's DB) and repo-scoped push tokens (managed in Gitea); that's the
in Gitea's DB) and repo-scoped push tokens (managed in Gitea) — that's the
complete list. Build/poll/serve use none.
+23 -32
View File
@@ -7,7 +7,7 @@
#
# Two repositories by design: code (templates/CSS, developed by the team) and
# content (Markdown + uploads, committed by the CMS). The build overlays
# content onto code, so an editor publishing a page and a developer
# content onto code, so an editor publishing an article and a developer
# shipping CSS never mix histories — either change republishes the site.
set -euo pipefail
@@ -17,8 +17,7 @@ BASE="${BASE:-$HOME/ctao-portal-demo}"
GITEA_URL="${GITEA_URL:-http://localhost:3000}" # published by ctao-demo-gitea
CODE_REPO="${CODE_REPO:-ctao/portal}" # owner/repo in Gitea
CONTENT_REPO="${CONTENT_REPO:-ctao/content}"
CODE_BRANCH="${CODE_BRANCH:-main}"
CONTENT_BRANCH="${CONTENT_BRANCH:-main}"
BRANCH="${BRANCH:-main}"
BUILD_IMAGE="${BUILD_IMAGE:-localhost/ctao-portal-build:1}"
# The build joins the Gitea container's network namespace: localhost inside
# the build = Gitea's loopback (port 3000), host loopback stays unreachable.
@@ -27,7 +26,7 @@ BUILD_IMAGE="${BUILD_IMAGE:-localhost/ctao-portal-build:1}"
BUILD_NETNS="${BUILD_NETNS:-container:ctao-demo-gitea}"
CODE_URL="${CODE_URL:-http://localhost:3000/$CODE_REPO.git}"
CONTENT_URL="${CONTENT_URL:-http://localhost:3000/$CONTENT_REPO.git}"
KEEP="${KEEP:-3}" # previous releases kept besides `current` (rollback targets)
KEEP="${KEEP:-3}" # released builds to retain
mkdir -p "$BASE/repo" "$BASE/content" "$BASE/releases" "$BASE/state" "$BASE/npm-cache"
# Host prerequisites (everything else runs inside containers). Fail loud —
@@ -38,28 +37,24 @@ done
# --- 1. Cheap poll: both branch heads via the local Gitea API ---
head_of() {
curl -fsS --max-time 5 "$GITEA_URL/api/v1/repos/$1/branches/$2" \
curl -fsS --max-time 5 "$GITEA_URL/api/v1/repos/$1/branches/$BRANCH" \
| jq -r '.commit.id' || true
}
code_sha=$(head_of "$CODE_REPO" "$CODE_BRANCH")
content_sha=$(head_of "$CONTENT_REPO" "$CONTENT_BRANCH")
code_sha=$(head_of "$CODE_REPO")
content_sha=$(head_of "$CONTENT_REPO")
# Gitea down/unreachable is a transient, not a unit failure — exit 0 quietly
# instead of painting the journal red every 10 s. Name the repo: a 404 here
# also means "repo/branch missing or renamed", not just "Gitea down".
[[ "$code_sha" =~ ^[0-9a-f]{40}$ ]] || { echo "poll failed for $CODE_REPO@$CODE_BRANCH (gitea down, or repo/branch missing) — skipping"; exit 0; }
[[ "$content_sha" =~ ^[0-9a-f]{40}$ ]] || { echo "poll failed for $CONTENT_REPO@$CONTENT_BRANCH (gitea down, or repo/branch missing) — skipping"; exit 0; }
# instead of painting the journal red every 10 s.
for sha in "$code_sha" "$content_sha"; do
if [[ ! "$sha" =~ ^[0-9a-f]{40}$ ]]; then
echo "poll failed (gitea unreachable?) — skipping this tick"
exit 0
fi
done
release="${code_sha:0:12}-${content_sha:0:12}" # code+content pin the release
# Skip only if this pair is both recorded AND still present in releases/
# (a deleted release dir must trigger a rebuild, not an eternal skip).
[[ "$release" == "$(cat "$BASE/state/last-built" 2>/dev/null)" \
&& -d "$BASE/releases/$release" ]] && exit 0
# A release that already failed is not retried until either repo moves —
# otherwise one bad commit (e.g. broken frontmatter) turns into a full
# rebuild every 10 s on a shared VM. The failure is loud once, then quiet.
if [[ "$release" == "$(cat "$BASE/state/last-failed" 2>/dev/null)" ]]; then
echo "skipping $release — build failed before; push a fix to retry"
exit 0
fi
echo "building code=$code_sha content=$content_sha"
t0=$(date +%s)
@@ -74,7 +69,7 @@ podman run --rm --network="$BUILD_NETNS" --memory=1g \
-e ASTRO_TELEMETRY_DISABLED=1 \
-e CODE_SHA="$code_sha" -e CODE_URL="$CODE_URL" \
-e CONTENT_SHA="$content_sha" -e CONTENT_URL="$CONTENT_URL" \
-e RELEASE="$release" -e CODE_BRANCH="$CODE_BRANCH" -e CONTENT_BRANCH="$CONTENT_BRANCH" \
-e RELEASE="$release" -e BRANCH="$BRANCH" \
-v "$BASE/repo:/work/repo:z" \
-v "$BASE/content:/work/content:z" \
-v "$BASE/releases:/work/releases:z" \
@@ -82,20 +77,19 @@ podman run --rm --network="$BUILD_NETNS" --memory=1g \
-w /work "$BUILD_IMAGE" sh -ec '
git config --global safe.directory "/work/repo"
git config --global --add safe.directory "/work/content"
sync_clone() { # $1 dir $2 url $3 sha $4 branch
[ -d "$1/.git" ] || git clone --branch "$4" "$2" "$1"
sync_clone() { # $1 dir $2 url $3 sha
[ -d "$1/.git" ] || git clone --branch "$BRANCH" "$2" "$1"
git -C "$1" remote set-url origin "$2" # self-heal if the URL changes
git -C "$1" fetch --quiet origin "$4"
git -C "$1" fetch --quiet origin "$BRANCH"
# --force: the working copy is disposable; a stray tracked-file edit
# must not wedge every future build.
git -C "$1" checkout --quiet --force "$3"
}
sync_clone repo "$CODE_URL" "$CODE_SHA" "$CODE_BRANCH"
sync_clone content "$CONTENT_URL" "$CONTENT_SHA" "$CONTENT_BRANCH"
# Overlay content onto code (these paths are gitignored in the code repo).
# mkdir -p: checkout prunes the emptied parent dirs, cp needs them back.
sync_clone repo "$CODE_URL" "$CODE_SHA"
sync_clone content "$CONTENT_URL" "$CONTENT_SHA"
# Overlay content onto code (these paths are gitignored in the code repo)
rm -rf repo/src/content/news repo/src/content/pages repo/public/uploads
mkdir -p repo/src/content repo/public
cp -a content/news repo/src/content/news
cp -a content/pages repo/src/content/pages
cp -a content/uploads repo/public/uploads
cd repo
@@ -109,13 +103,10 @@ podman run --rm --network="$BUILD_NETNS" --memory=1g \
npm ci --ignore-scripts --no-audit --no-fund
echo "$lock" > .deps-hash
fi
# Validate the overlay and links before publishing; never seed demo content
# in place of an incomplete content branch.
npm run check
npm run build
rm -rf "../releases/$RELEASE"
cp -a dist "../releases/$RELEASE"
' || { echo "$release" > "$BASE/state/last-failed"; echo "BUILD FAILED for $release (see above) — will not retry until a new commit"; exit 1; }
rm -f "$BASE/state/last-failed"
'
# --- 3. Atomic publish: symlink flip via rename(2) — no half-published moment.
# mv -T is GNU (the target host is Rocky); when testing on macOS put a
+1 -1
View File
@@ -40,7 +40,7 @@ Environment=GITEA__service__DISABLE_REGISTRATION=true
Environment=GITEA__mailer__ENABLED=false
# Repos default to public so the build pipeline can clone anonymously —
# content is the public site anyway. The `ctao/portal` repo itself is
# created in the UI (README step 7).
# created in the UI (README step 6).
Environment=GITEA__repository__DEFAULT_PRIVATE=public
# Sveltia is served from the portal vhost and calls the Gitea API cross-origin
# — CORS locked to exactly that origin. ALLOW_DOMAIN takes FULL origins with
+1 -3
View File
@@ -4,12 +4,10 @@
# activate a service that is still running.
[Unit]
Description=CTAO portal demo — rebuild if the code or content repo has new commits
Description=CTAO portal demo — rebuild if the content repo has new commits
[Service]
Type=oneshot
Environment=CODE_BRANCH=main
Environment=CONTENT_BRANCH=main
ExecStart=%h/ctao-portal-demo/bin/build.sh
# Polite neighbour on the shared VM:
Nice=10
+1 -1
View File
@@ -6,7 +6,7 @@
# OnBootSec covers reboot. Enable: systemctl --user enable --now ctao-portal-build.timer
[Unit]
Description=CTAO portal demo — poll the code and content repos every 10 s
Description=CTAO portal demo — poll the content repo every 10 s
[Timer]
OnActiveSec=5
+10 -13
View File
@@ -8,14 +8,11 @@
"name": "ctao-portal",
"version": "0.1.0",
"dependencies": {
"astro": "7.2.10"
"astro": "^7.2.10"
},
"devDependencies": {
"@astrojs/check": "0.9.10",
"typescript": "6.0.3"
},
"engines": {
"node": ">=22.19.0"
"@astrojs/check": "^0.9.10",
"typescript": "^6.0.3"
}
},
"node_modules/@astrojs/check": {
@@ -3594,9 +3591,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.18",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
"integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
"version": "3.3.16",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
"funding": [
{
"type": "github",
@@ -3794,9 +3791,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.28",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
"integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==",
"version": "8.5.22",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.22.tgz",
"integrity": "sha512-KBDEIpLrvpv16pp3K0Fw+UCoZfopFjjgeB+0tA/aaThfEE74kKDLrgg603YvOWJyg3+WYtyq3xYsQWsIyZlPqQ==",
"funding": [
{
"type": "opencollective",
@@ -3813,7 +3810,7 @@
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.18",
"nanoid": "^3.3.16",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
+4 -11
View File
@@ -4,23 +4,16 @@
"version": "0.1.0",
"private": true,
"scripts": {
"content:link": "node scripts/content.mjs --relink",
"predev": "node scripts/content.mjs && node scripts/preview-styles.mjs",
"dev": "astro dev",
"prebuild": "node scripts/content.mjs && node scripts/preview-styles.mjs",
"build": "astro build",
"preview": "astro preview",
"precheck": "node scripts/content.mjs && node scripts/preview-styles.mjs",
"check": "astro check && astro build && node scripts/check-links.mjs"
"check": "astro build && node scripts/check-links.mjs"
},
"dependencies": {
"astro": "7.2.10"
"astro": "^7.2.10"
},
"devDependencies": {
"@astrojs/check": "0.9.10",
"typescript": "6.0.3"
},
"engines": {
"node": ">=22.19.0"
"@astrojs/check": "^0.9.10",
"typescript": "^6.0.3"
}
}
+36 -81
View File
@@ -1,10 +1,11 @@
# Sveltia CMS config — backend: Gitea behind the Cyfronet ingress (HTTPS
# vhost). Editors log in via Gitea OAuth (PKCE, no client secret); edits are
# committed to the ctao/content repo. Gitea's ROOT_URL and CORS are pinned to
# committed to the ctao/portal repo. Gitea's ROOT_URL and CORS are pinned to
# these origins in deploy/ctao-demo-gitea.container.
backend:
name: gitea
# Keep this branch in sync with CONTENT_BRANCH in deploy/build.sh.
# Content lives in its OWN repo (editorial history separate from code —
# the build overlays it onto the portal code, see deploy/build.sh).
repo: ctao/content
branch: main
base_url: https://astro-git.isl-dev.grid.cyfronet.pl
@@ -44,82 +45,36 @@ media_libraries:
optimize: true
collections:
- name: home
label: "Home page"
files:
- name: home
label: "Home page"
file: "pages/home.md"
format: frontmatter
fields:
- { name: title, label: "Title", widget: string }
- { name: introduction, label: "Introduction", widget: text, required: false }
- name: images
label: "Images"
label_singular: "image"
widget: list
required: false
max: 2
hint: "Up to two images, shown side by side."
fields:
- { name: image, label: "Image", widget: image }
- { name: alt, label: "Alternative text", widget: string, required: false, hint: "Describe the image; leave empty only for decorative images." }
- { name: caption, label: "Caption", widget: string, required: false }
- { name: body, label: "Body", widget: markdown }
- { name: buttonLabel, label: "Button label", widget: string, required: false }
- { name: buttonUrl, label: "Button destination", widget: string, required: false, hint: "A site path such as /example/lorem-ipsum/, or an https:// URL." }
- name: examples
label: "Example pages"
files:
- name: lorem-ipsum
label: "Lorem ipsum"
file: "pages/lorem-ipsum.md"
format: frontmatter
fields:
- { name: title, label: "Title", widget: string }
- { name: introduction, label: "Introduction", widget: text, required: false }
- { name: image, label: "Image", widget: image, required: false }
- { name: imageAlt, label: "Image alternative text", widget: string, required: false, hint: "Describe the image; leave empty only for decorative images." }
- { name: imageCaption, label: "Image caption", widget: string, required: false }
- { name: body, label: "Body", widget: markdown }
- { name: buttonLabel, label: "Button label", widget: string, required: false }
- { name: buttonUrl, label: "Button destination", widget: string, required: false, hint: "A site path such as /example/lorem-ipsum/, or an https:// URL." }
- name: dolor-sit-amet
label: "Dolor sit amet"
file: "pages/dolor-sit-amet.md"
format: frontmatter
fields:
- { name: title, label: "Title", widget: string }
- { name: introduction, label: "Introduction", widget: text, required: false }
- { name: image, label: "Image", widget: image, required: false }
- { name: imageAlt, label: "Image alternative text", widget: string, required: false, hint: "Describe the image; leave empty only for decorative images." }
- { name: imageCaption, label: "Image caption", widget: string, required: false }
- { name: body, label: "Body", widget: markdown }
- { name: buttonLabel, label: "Button label", widget: string, required: false }
- { name: buttonUrl, label: "Button destination", widget: string, required: false, hint: "A site path such as /example/lorem-ipsum/, or an https:// URL." }
- name: consectetur-adipiscing
label: "Consectetur adipiscing"
file: "pages/consectetur-adipiscing.md"
format: frontmatter
fields:
- { name: title, label: "Title", widget: string }
- { name: introduction, label: "Introduction", widget: text, required: false }
- { name: image, label: "Image", widget: image, required: false }
- { name: imageAlt, label: "Image alternative text", widget: string, required: false, hint: "Describe the image; leave empty only for decorative images." }
- { name: imageCaption, label: "Image caption", widget: string, required: false }
- { name: body, label: "Body", widget: markdown }
- { name: buttonLabel, label: "Button label", widget: string, required: false }
- { name: buttonUrl, label: "Button destination", widget: string, required: false, hint: "A site path such as /example/lorem-ipsum/, or an https:// URL." }
- name: sed-do-eiusmod
label: "Sed do eiusmod"
file: "pages/sed-do-eiusmod.md"
format: frontmatter
fields:
- { name: title, label: "Title", widget: string }
- { name: introduction, label: "Introduction", widget: text, required: false }
- { name: image, label: "Image", widget: image, required: false }
- { name: imageAlt, label: "Image alternative text", widget: string, required: false, hint: "Describe the image; leave empty only for decorative images." }
- { name: imageCaption, label: "Image caption", widget: string, required: false }
- { name: body, label: "Body", widget: markdown }
- { name: buttonLabel, label: "Button label", widget: string, required: false }
- { name: buttonUrl, label: "Button destination", widget: string, required: false, hint: "A site path such as /example/lorem-ipsum/, or an https:// URL." }
- name: news
label: "News"
label_singular: "Article"
folder: "news"
create: true
slug: "{{slug}}"
extension: md
format: frontmatter
summary: "{{title}} · {{date}}"
fields:
- { name: title, label: "Title", widget: string }
- { name: description, label: "Short description", widget: text }
- { name: date, label: "Date", widget: datetime, date_format: "YYYY-MM-DD", time_format: false }
- { name: category, label: "Category", widget: string, default: "news" }
- { name: author, label: "Author", widget: string, default: "CTAO" }
- { name: cover, label: "Cover image", widget: image, required: false }
- { name: lang, label: "Language (if not English, e.g. pl)", widget: string, required: false }
- { name: draft, label: "Draft (unpublished)", widget: boolean, default: false }
- { name: body, label: "Body", widget: markdown }
- name: pages
label: "Pages"
label_singular: "Page"
folder: "pages"
create: true
slug: "{{slug}}"
extension: md
format: frontmatter
summary: "{{title}}"
fields:
- { name: title, label: "Title", widget: string }
- { name: description, label: "Description", widget: text, required: false }
- { name: body, label: "Body", widget: markdown }
+56 -455
View File
@@ -1,470 +1,71 @@
/* Generated by scripts/preview-styles.mjs; edit src/styles/global.css. */
/* ==========================================================================
CTAO Science Portal: content page mockup
Colours and type follow the CTAO Brand Guidelines (April 2024).
The editor preview (public/admin/preview.css) is generated from the
hero, prose and button sections below by scripts/preview-styles.mjs.
========================================================================== */
/* Fonts
========================================================================== */
/* Entry-preview styles for the Sveltia editor, registered via the documented
CMS.registerPreviewStyle API (bare element selectors, per the official docs
example). This file MIRRORS the article styles 1:1 — the design tokens from
:root and the `.prose` block in src/styles/global.css — with `.prose`
dropped from selectors (the preview pane has no such wrapper).
KEEP IN SYNC: when you touch `.prose` or the tokens in global.css, update
the corresponding value here (a matching reminder sits next to `.prose`).
Fonts: same self-hosted woff2 files the site uses, same origin. (The
Sveltia bundle still loads its own UI fonts from jsDelivr — only the
PREVIEW pane is CDN-free.) */
@font-face {
font-family: "Inter";
font-weight: 400 700;
font-display: swap;
src: url("/assets/fonts/inter-latin.woff2") format("woff2");
font-family: "Inter"; font-weight: 400 700; font-display: swap;
src: url("/fonts/inter-latin.woff2") format("woff2");
}
@font-face {
font-family: "Space Grotesk";
font-weight: 500 700;
font-display: swap;
src: url("/assets/fonts/space-grotesk-latin.woff2") format("woff2");
font-family: "Space Grotesk"; font-weight: 500 700; font-display: swap;
src: url("/fonts/space-grotesk-latin.woff2") format("woff2");
}
/* Tokens
========================================================================== */
:root {
/* Brand main colours (D.2.1) */
--galaxy-blue: #00004a;
--cherenkov-blue: #00e4d8;
--moon-gray: #f5f5f5;
/* Brand secondary colours (D.2.2) */
--white: #ffffff;
--cosmic-azure: #007aff;
--interstellar-indigo: #00009c;
/* Text */
--text: #111111;
--text-muted: #555555;
/* Cosmic Azure at 84% brightness: 5.4:1 on white, 3.5:1 against body text */
--link: #0066d6;
/* Type: Inter for everything, Space Grotesk for headlines (D.3) */
--font-body: "Inter", Arial, sans-serif;
--font-head: "Space Grotesk", Arial, sans-serif;
/* Layout */
--content-width: 85rem;
--gutter: clamp(1rem, 4vw, 2.5rem);
--header-height: 7rem;
--header-height-scrolled: 5rem;
/* Large decorative corners (hero, header, search panel): grows with the
viewport instead of switching at breakpoints */
--radius-lg: min(15vw, 180px);
--radius-sm: 15px;
/* How far photos are pulled up into the hero */
--hero-overlap: clamp(6rem, 11.25vw, 9rem);
/* Dropdown submenus (Example, language) */
--radius-menu: 8px;
--transition: 0.3s ease-in-out;
/* Film grain tile over the hero background, also hides gradient banding */
--grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='.05'/%3E%3C/svg%3E");
}
/* Base
========================================================================== */
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
scroll-padding-top: var(--header-height-scrolled);
/* tokens copied from global.css :root */
--galaxy: #00004a;
--moon: #f5f5f5;
--text: #101228;
--muted: #46536a;
--border: #e2e5ee;
--link: #0057c2;
--font-body: "Inter", Arial, system-ui, sans-serif;
--font-display: "Space Grotesk", var(--font-body);
--fs-s: 0.9rem;
--fs-l: 1.125rem;
--fs-xl: 1.5rem;
--fs-h2: clamp(1.75rem, 3.5vw, 2.5rem);
--radius: 16px;
}
/* preview shell ≈ article column */
body {
margin: 0;
font-family: var(--font-body);
font-size: 1rem;
line-height: 1.5;
color: var(--text);
background: var(--white);
-webkit-font-smoothing: antialiased;
}
body.no-scroll {
overflow: hidden;
}
img {
display: block;
max-width: 100%;
height: auto;
}
a {
color: var(--link);
text-decoration: none;
}
a:hover,
a:focus-visible {
text-decoration: underline;
text-underline-offset: 0.15em;
}
button {
padding: 0;
font: inherit;
color: inherit;
background: none;
border: 0;
cursor: pointer;
}
/* Focus ring: Link blue on light backgrounds, Cherenkov Blue on dark ones */
:focus-visible {
outline: 2px solid var(--link);
outline-offset: 3px;
}
.site-header :focus-visible,
.page-hero :focus-visible,
.site-footer :focus-visible {
outline-color: var(--cherenkov-blue);
}
/* Utilities
========================================================================== */
.container {
width: min(100% - 2 * var(--gutter), var(--content-width));
margin-inline: auto;
}
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.skip-link {
position: absolute;
top: -4rem;
left: 1rem;
z-index: 100;
padding: 0.5rem 1rem;
color: var(--galaxy-blue);
background: var(--white);
border-radius: var(--radius-sm);
}
.skip-link:focus {
top: 1rem;
}
.arrow {
display: inline-block;
transition: transform 0.25s ease;
}
a:hover .arrow,
button:hover .arrow {
transform: translateX(4px);
}
@keyframes panel-in {
from {
opacity: 0;
transform: translateY(-6px);
}
to {
opacity: 1;
transform: none;
}
}
/* Page hero
========================================================================== */
.page-hero {
margin-top: calc(-1 * var(--header-height));
padding: calc(var(--header-height) + 3rem) 0 calc(var(--hero-overlap) + 3rem);
color: var(--white);
background: var(--grain), var(--galaxy-blue);
border-bottom-right-radius: var(--radius-lg);
}
/* Pages without a photo: no room needed for the figure to overlap */
.page-hero--compact {
padding-bottom: 4rem;
}
.breadcrumb ol {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin: 0 0 1rem;
padding: 0;
font-size: 0.875rem;
list-style: none;
}
.breadcrumb li + li::before {
content: "›";
margin-right: 0.5rem;
color: rgba(255, 255, 255, 0.5);
}
.breadcrumb a {
color: rgba(255, 255, 255, 0.6);
}
.breadcrumb a:hover {
color: var(--white);
}
.breadcrumb [aria-current] {
font-weight: 500;
color: var(--white);
}
/* Headline: Space Grotesk medium, white on dark (D.3.4) */
.page-hero__title {
max-width: 24ch;
margin: 0 0 1.25rem;
font-family: var(--font-head);
font-size: clamp(2rem, 5vw, 3.75rem);
font-weight: 500;
line-height: 1.05;
letter-spacing: -0.02em;
text-wrap: balance;
overflow-wrap: anywhere;
}
.page-hero__lead {
max-width: 70ch;
margin: 0;
font-size: 1.0625rem;
font-weight: 500;
line-height: 1.4;
margin: 0 auto;
padding: 24px;
}
/* Photo pulled up over the hero; flat, no shadow */
.page-figure {
margin: calc(-1 * var(--hero-overlap)) 0 4rem;
}
.page-figure img {
width: 100%;
border-radius: var(--radius-sm);
}
.page-figure figcaption {
margin-top: 0.75rem;
font-size: 0.8125rem;
color: var(--text-muted);
}
/* Two photos side by side (Home), pulled up over the hero together; they
stack by themselves once each would be narrower than 18rem */
.page-figures {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
gap: 1.5rem;
margin: calc(-1 * var(--hero-overlap)) 0 4rem;
}
.page-figures .page-figure {
margin: 0;
}
/* Same shape for both photos, whatever the uploads' proportions */
.page-figures img {
aspect-ratio: 3 / 2;
object-fit: cover;
}
/* Prose
========================================================================== */
.prose {
padding-bottom: 5rem;
overflow-wrap: anywhere;
}
.prose--no-image {
padding-top: 4rem;
}
.prose > * {
max-width: 64rem;
}
/* Galaxy Blue headline on light background (D.3.4) */
.prose h2 {
margin: 3rem 0 1rem;
font-family: var(--font-head);
font-size: clamp(1.75rem, 3vw, 2.375rem);
font-weight: 500;
line-height: 1.1;
letter-spacing: -0.02em;
color: var(--galaxy-blue);
}
.prose h2:first-child {
margin-top: 0;
}
.prose p {
margin: 0 0 1.25rem;
font-size: 1.0625rem;
line-height: 1.6;
}
/* Text links: colour only at rest, underline on hover and focus */
.prose a:not(.btn):hover,
.prose a:not(.btn):focus-visible {
text-decoration: underline;
text-underline-offset: 0.15em;
}
/* Cherenkov Blue fails contrast as text on white, so it is the accent bar */
.prose .highlight,
.prose blockquote {
margin: 2.5rem 0;
padding-left: 1.5rem;
font-size: 1.375rem;
font-weight: 500;
line-height: 1.35;
color: var(--galaxy-blue);
border-left: 4px solid var(--cherenkov-blue);
}
.prose blockquote p {
font-size: inherit;
line-height: inherit;
}
.prose h3,
.prose h4,
.prose h5,
.prose h6 {
font-family: var(--font-head);
color: var(--galaxy-blue);
}
.prose li {
margin-bottom: 0.5rem;
}
.prose img {
border-radius: var(--radius-sm);
}
.prose pre {
padding: 1rem;
overflow-x: auto;
background: var(--moon-gray);
}
.prose code {
overflow-wrap: normal;
}
.prose table {
display: block;
overflow-x: auto;
border-collapse: collapse;
}
.prose th,
.prose td {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid #ddd;
}
.prose hr {
margin: 2rem 0;
border: 0;
border-top: 1px solid #ddd;
}
/* Placeholder pages: empty body until their content exists */
.placeholder-body {
min-height: 12rem;
}
/* Buttons
========================================================================== */
.btn {
display: inline-flex;
align-items: center;
gap: 0.75rem;
height: 2.75rem;
padding: 0 1.5rem;
font-size: 0.9375rem;
font-weight: 700;
border-radius: 999px;
}
/* Underline the label only: a flex container would pass it on to the arrow */
.btn:hover,
.btn:focus-visible {
text-decoration: none;
}
.btn:hover .btn__label,
.btn:focus-visible .btn__label {
text-decoration: underline;
text-underline-offset: 0.15em;
}
.btn--primary {
color: var(--white);
background: linear-gradient(
90deg,
var(--galaxy-blue) 0%,
var(--cosmic-azure) 70%,
var(--cherenkov-blue) 100%
);
}
.btn--primary:hover {
filter: brightness(1.1);
}
/* Preferences
========================================================================== */
/* Windows High Contrast: drop the decorative hero background behind system text */
@media (forced-colors: active) {
.page-hero {
background-image: none;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation: none !important;
transition: none !important;
}
}
/* Preview frame
========================================================================== */
/* There is no site header above the hero */
.page-hero {
margin-top: 0;
padding-top: 3rem;
/* = global.css base heading/link rules = */
h1, h2 { font-family: var(--font-display); font-weight: 500; letter-spacing: -0.025em; line-height: 1.1; }
h3 { font-weight: 600; }
h1, h2, h3 { overflow-wrap: break-word; text-wrap: balance; }
a { color: var(--link); text-decoration: underline; text-underline-offset: 3px; }
/* = global.css `.prose` rules, selectors unwrapped = */
body { line-height: 1.65; overflow-wrap: break-word; }
a { overflow-wrap: anywhere; }
h1 { color: var(--galaxy); font-size: var(--fs-h2); } /* mirrors `.article h1` */
h2 { color: var(--galaxy); margin: 44px 0 12px; font-size: var(--fs-xl); }
h3 { color: var(--galaxy); margin: 30px 0 8px; font-size: var(--fs-l); }
p { margin: 0 0 16px; text-wrap: pretty; }
ul, ol { margin: 0 0 16px; padding-left: 22px; }
li { margin: 6px 0; }
blockquote {
margin: 22px 0; padding: 12px 20px; border-left: 3px solid var(--galaxy);
background: var(--moon); color: var(--text);
}
img { display: block; max-width: 100%; height: auto; border: 1px solid var(--border); border-radius: var(--radius); }
img + em, p > em:only-child { color: var(--muted); font-size: var(--fs-s); }
hr { border: 0; border-top: 1px solid var(--border); margin: 32px 0; }
table { display: block; width: 100%; overflow-x: auto; border-collapse: collapse; margin: 0 0 16px; font-size: var(--fs-s); }
th { text-align: left; font-weight: 600; color: var(--galaxy); }
th, td { padding: 10px 12px; border-bottom: 1px solid var(--border); }
thead th { border-bottom: 2px solid var(--galaxy); }
Binary file not shown.
Binary file not shown.
-102
View File
@@ -1,102 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 28.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 153 32" style="enable-background:new 0 0 153 32;" xml:space="preserve">
<style type="text/css">
.st0{display:none;}
.st1{display:inline;}
.st2{display:inline;clip-path:url(#SVGID_00000049184525615463858320000015264053225314800771_);}
.st3{fill:#FFFFFF;}
.st4{fill:#00E5D9;}
.st5{fill:#00E4D8;}
</style>
<g class="st0">
<defs>
<rect id="SVGID_1_" x="0.9" width="151.4" height="32"/>
</defs>
<clipPath id="SVGID_00000109021271204444604050000000952066022526769593_" class="st1">
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
</clipPath>
<g style="display:inline;clip-path:url(#SVGID_00000109021271204444604050000000952066022526769593_);">
<path class="st3" d="M19.3,0c10.9,0,17.1,4.8,18,12.6h-9.1c-0.6-2.8-3.4-5.1-8.8-5.1c-6.3,0-9.4,3.2-9.4,8.5
c0,5.3,3.1,8.6,9.4,8.6c5.5,0,8.3-2.4,8.8-5.2h9.1c-0.9,7.9-7,12.7-18,12.7C7.4,32,0.9,26.4,0.9,16C0.9,5.6,7.4,0,19.3,0z"/>
<path class="st3" d="M62.3,8.4c0.2-0.1,0.4-0.1,0.6-0.1c4,0,8,0,12.1,0c0.2,0,0.3,0,0.5,0c0.1,0,0.2,0,0.3-0.1c0-0.1,0-0.2,0-0.3
c0-2.3,0-4.6,0-6.9c0-0.1,0-0.2,0-0.3c-0.2,0-0.5,0-0.7,0c-11.4,0-22.8,0-34.3,0c-0.2,0-0.3,0-0.4,0c-0.1,0-0.2,0-0.3,0.1
c0,0.2,0,0.4,0,0.5c0,0.8,0,1.6,0,2.4c0,1.3,0,2.6,0,3.9c0,0.2,0,0.4,0.1,0.5c0.2,0.1,0.4,0.1,0.6,0.1c4,0,8.1,0,12.1,0
c0.2,0,0.3,0,0.5,0c0.1,0,0.2,0,0.3,0.1c0,0.2,0,0.5,0,0.7c0,6.6,0,13.1,0,19.7c0,0.2,0,0.4,0,0.5c0,0.1,0,0.2,0,0.3
c0.1,0,0.1,0,0.1,0c2.7-0.5,5.4-1.1,8.1-1.6c0.1,0,0.2-0.1,0.3-0.1c0-0.2,0-0.4,0-0.6c0-6,0-12,0-18.1c0-0.2,0-0.4,0-0.5
C62.2,8.6,62.3,8.5,62.3,8.4L62.3,8.4z M53.7,30.2c0,0.3-0.1,0.6,0,0.8c0.4,0.1,6.6,0.1,8.2,0c0.1,0,0.2,0,0.3,0
c0-0.1,0.1-0.1,0.1-0.2c0-0.6,0-1.3,0-1.9c0,0,0-0.1-0.1-0.2C59.4,29.2,56.5,29.7,53.7,30.2L53.7,30.2z"/>
<path class="st3" d="M151.5,11.4c0.1,0,0.2,0,0.2,0c0.1,0.1,0.1,0.2,0.1,0.3c0.1,0.6,0.2,1.3,0.3,1.9c0.2,1.3,0.2,2.6,0.1,3.8
c-0.1,2-0.6,4-1.5,5.9c-1,2.2-2.6,3.9-4.5,5.3c-1.2,0.8-2.5,1.5-3.9,2c-1.1,0.4-2.3,0.7-3.4,0.9c-0.5,0.1-1.1,0.2-1.6,0.3
c-0.5,0.1-0.9,0.1-1.4,0.1c-0.1,0-0.1,0-0.2,0c-0.7,0.1-1.5,0.1-2.2,0.1c-0.7,0-1.5,0-2.2,0c-0.3,0-0.5,0-0.8-0.1
c-0.1,0-0.3,0-0.4,0c-2.2-0.2-4.3-0.6-6.4-1.3c-1.4-0.5-2.7-1.2-4-2c-2.7-1.9-4.5-4.5-5.4-7.7c-0.1-0.5-0.3-1.1-0.3-1.6
c0-0.1,0-0.1,0-0.2c0.1-0.1,0.2-0.1,0.4-0.2c0.5-0.1,1.1-0.2,1.6-0.3c0.9-0.2,1.8-0.4,2.7-0.5c0.9-0.2,1.8-0.4,2.7-0.6
c0.3-0.1,0.5-0.1,0.8-0.2c0.2,0,0.3-0.1,0.5-0.1c0.1,0,0.1,0.1,0.2,0.2c0,0.4,0.1,0.8,0.2,1.2c0.2,0.9,0.6,1.8,1.2,2.6
c0.6,0.8,1.3,1.4,2.2,1.9c0.7,0.4,1.5,0.7,2.4,0.9c0.6,0.1,1.2,0.3,1.8,0.3c0.8,0.1,1.7,0.2,2.5,0.2c0.4,0,0.7,0,1.1,0
c0.1,0,0.3,0,0.4,0c1.3-0.1,2.5-0.3,3.7-0.7c0.5-0.2,1-0.4,1.5-0.7c1.6-0.9,2.7-2.3,3.2-4.1c0.2-0.6,0.3-1.2,0.3-1.8
c0-0.5,0.1-1,0.1-1.4c0-0.7-0.1-1.4-0.2-2.1c0-0.1,0-0.2,0-0.3c0,0,0-0.1,0-0.1c0,0,0.1,0,0.1-0.1c0.1,0,0.3-0.1,0.4-0.1
c1.5-0.3,2.9-0.6,4.4-0.9c0.7-0.1,1.5-0.3,2.2-0.4c0.2,0,0.4-0.1,0.6-0.1c0.1,0,0.1-0.1,0.2-0.1c0,0,0,0,0-0.1
C151.2,11.3,151.3,11.3,151.5,11.4C151.5,11.4,151.5,11.4,151.5,11.4L151.5,11.4z"/>
<path class="st3" d="M151.1,11.5c-0.4,0-0.7,0.1-1.1,0.1c-0.7,0.1-1.4,0.2-2.1,0.4c-0.7,0.1-1.3,0.2-2,0.4c-0.7,0.1-1.3,0.2-2,0.3
c-0.1,0-0.3,0.1-0.4,0.1c-0.1,0-0.3,0-0.4,0c-0.1-0.2-0.2-0.3-0.2-0.5c-0.5-1.4-1.4-2.4-2.6-3.2c-0.5-0.3-1-0.6-1.5-0.8
c-0.7-0.3-1.5-0.5-2.3-0.6c-1.3-0.2-2.6-0.3-3.8-0.3c-0.7,0-1.4,0.1-2.1,0.2c-0.8,0.1-1.6,0.3-2.4,0.5c-0.5,0.2-1,0.4-1.4,0.6
c-1.8,1-3,2.5-3.5,4.5c-0.2,0.7-0.3,1.5-0.3,2.2c0,0.2,0,0.4,0,0.7c0,0.1,0,0.2-0.1,0.2c-0.1,0.1-0.3,0.1-0.4,0.1
c-0.6,0.1-1.3,0.2-1.9,0.3c-0.6,0.1-1.3,0.2-1.9,0.3c-0.7,0.1-1.4,0.2-2.1,0.4c-0.7,0.1-1.3,0.2-2,0.4c-0.1,0-0.3,0.1-0.4,0
c-0.1,0-0.1-0.1-0.1-0.2c0-0.1,0-0.3,0-0.4c0-0.9-0.1-1.8,0-2.7c0.1-2.3,0.7-4.5,1.8-6.6c1.1-2,2.6-3.6,4.4-4.8
c1.3-0.9,2.7-1.5,4.2-2c1.1-0.4,2.3-0.6,3.4-0.8c0.6-0.1,1.3-0.2,1.9-0.3c0.6-0.1,1.3-0.1,1.9-0.1c1.2,0,2.4,0,3.6,0
c0.2,0,0.4,0,0.5,0c0.1,0,0.2,0,0.3,0c0.8,0,1.6,0.2,2.5,0.3c1.3,0.2,2.5,0.5,3.7,0.9c1.4,0.5,2.8,1.1,4.1,2
c1.9,1.3,3.4,2.9,4.5,5c0.4,0.8,0.8,1.7,1,2.6c0,0.1,0,0.2,0.1,0.2c0,0,0,0.1,0,0.1c-0.1,0-0.1,0.1-0.2,0.1l0,0
C151.4,11.5,151.2,11.4,151.1,11.5L151.1,11.5L151.1,11.5z"/>
<path class="st3" d="M151,11.5c0.1-0.1,0.1-0.1,0.2-0.1c0.1,0,0.2,0,0.3,0C151.4,11.5,151.2,11.5,151,11.5L151,11.5z"/>
<path class="st3" d="M84.5,23.5C84.5,23.4,84.6,23.4,84.5,23.5c2.7-5.4,5.3-10.8,8-16.1c0,0,0-0.1,0.1-0.1c0,0,0-0.1,0.1-0.1
c0,0,0,0,0,0c0,0,0,0,0.1,0c0.1,0.1,0.2,0.3,0.2,0.4c0.9,1.8,1.7,3.5,2.6,5.3c1.2,2.4,2.4,4.8,3.6,7.2c0.1,0.2,0.1,0.3,0.3,0.5
c0.6-0.1,1.2-0.2,1.8-0.3c1.1-0.2,2.1-0.4,3.2-0.5c1-0.2,2-0.3,3-0.5c0.1,0,0.2-0.1,0.3-0.1c-0.1-0.2-0.2-0.3-0.2-0.5
c-1.4-2.8-2.8-5.6-4.2-8.4c-1.5-2.9-2.9-5.8-4.4-8.7c-0.1-0.2-0.1-0.4-0.3-0.5c-0.1,0-0.1,0-0.2,0c-3.8,0-7.6,0-11.4,0
c-0.1,0-0.1,0-0.2,0c0,0,0,0-0.1,0c0,0,0,0-0.1,0C82.6,9,78.5,17.2,74.4,25.3c0,0,0,0.1,0,0.1c0,0,0.1,0.1,0.1,0
C77.8,24.8,81.2,24.1,84.5,23.5z M100,21.8c0.3,0.7,4.4,9,4.6,9.3c0.1,0,0.2,0,0.3,0c2.9,0,5.8,0,8.7,0c0,0,0,0,0.1,0
c0,0,0,0,0.1-0.1c0-0.1-0.1-0.2-0.1-0.2c-0.1-0.2-0.3-0.5-0.4-0.8c-1.6-3.1-3.2-6.3-4.7-9.4c-0.1-0.2-0.1-0.3-0.3-0.5
C105.5,20.7,102.8,21.2,100,21.8L100,21.8z M73.7,26.8c-0.7,1.4-1.4,2.7-2,4.1c0,0.1,0,0.2-0.1,0.3h9.1c0.2-0.3,2.6-5,2.9-5.8
c0-0.1,0-0.1,0.1-0.2c-0.1,0-0.2,0-0.3,0C80.2,25.6,76.9,26.2,73.7,26.8L73.7,26.8z M93.8,29C93.8,29,93.8,29,93.8,29
c0.1-0.5,0.1-1,0-1.5c0-0.5-0.1-1.1,0-1.6c0-0.3,0.1-0.7,0.2-1c0.3-1.2,1.1-2,2.1-2.4c0.5-0.2,1.1-0.4,1.6-0.6
c0.4-0.2,0.9-0.3,1.3-0.5c-0.1-0.1-0.2-0.1-0.3-0.1c-1,0-1.9,0-2.9,0c-0.9,0-1.7-0.4-2.4-1c-0.4-0.4-0.8-0.9-1-1.5
c-0.3-0.8-0.6-1.6-0.8-2.4c0-0.1,0-0.1-0.2-0.1c0,0.2,0,0.3,0,0.5c0,0.8,0,1.7,0,2.5c0,0.8-0.2,1.5-0.7,2.1
c-0.5,0.7-1.1,1.1-1.8,1.3c-0.8,0.3-1.6,0.6-2.4,0.9c-0.1,0-0.1,0.1-0.2,0.1c0.1,0.1,0.3,0.1,0.4,0.1c0.9,0,1.8,0,2.8,0
c0.2,0,0.4,0,0.6,0.1c1.3,0.3,2.1,1.1,2.6,2.3c0.1,0.2,0.2,0.5,0.3,0.7C93.3,27.6,93.5,28.3,93.8,29L93.8,29z"/>
<path class="st4" d="M93.8,29C93.8,29,93.8,29,93.8,29c0.1-0.5,0.1-1,0-1.5c0-0.5-0.1-1.1,0-1.6c0-0.3,0.1-0.7,0.2-1
c0.3-1.2,1.1-2,2.1-2.4c0.5-0.2,1.1-0.4,1.6-0.6c0.4-0.2,0.8-0.3,1.3-0.5c-0.1-0.1-0.2-0.1-0.3-0.1c-1,0-1.9,0-2.9,0
c-0.9,0-1.7-0.4-2.4-1c-0.4-0.4-0.8-0.9-1-1.5c-0.3-0.8-0.6-1.6-0.8-2.4c0-0.1,0-0.1-0.2-0.1c0,0.2,0,0.3,0,0.5c0,0.8,0,1.7,0,2.5
c0,0.8-0.2,1.5-0.7,2.1c-0.5,0.7-1.1,1.1-1.8,1.3c-0.8,0.3-1.6,0.6-2.4,0.9c-0.1,0-0.1,0.1-0.2,0.1c0.1,0.1,0.3,0.1,0.4,0.1
c0.9,0,1.8,0,2.8,0c0.2,0,0.4,0,0.6,0.1c1.3,0.3,2.1,1.1,2.6,2.3c0.1,0.2,0.2,0.5,0.3,0.7C93.3,27.6,93.5,28.3,93.8,29L93.8,29z"
/>
</g>
</g>
<g id="Logotype">
<g>
<g>
<polygon class="st3" points="100.4,22.3 104.7,30.8 113.9,30.8 108.6,20.6 "/>
<g>
<path class="st3" d="M122.9,16.1c0,0,0-0.1,0-0.1c0-5.2,2.9-8.3,10.2-8.3c5.7,0,8.8,1.9,9.8,5.2l8.9-1.4
c-1.9-7-8.3-11.1-18.7-11.1c-12.5,0-19.3,5.8-19.3,15.6c0,0.5,0,1.1,0.1,1.6L122.9,16.1L122.9,16.1z"/>
<path class="st3" d="M143.1,13.3c0.2,0.8,0.3,1.7,0.3,2.6c0,5.2-3,8.4-10.3,8.4c-6.5,0-9.6-2.5-10.1-6.8l-8.8,1.8
c1.4,7.8,8,12.2,18.9,12.2c12.4,0,19.3-5.9,19.3-15.7c0-1.6-0.2-3.1-0.5-4.5L143.1,13.3L143.1,13.3z"/>
</g>
<g>
<polygon class="st3" points="73.5,27 71.5,30.8 80.8,30.8 83.5,25.4 "/>
<polygon class="st3" points="98.6,1.2 86.8,1.2 74.6,24.7 84.9,22.6 92.7,7.2 99.2,19.9 107.6,18.6 "/>
</g>
<g>
<g>
<polygon class="st3" points="53.6,30.2 53.6,30.8 62.2,30.8 62.2,28.8 "/>
<polygon class="st3" points="53.6,29.1 62.2,27.3 62.2,8.5 75.7,8.5 75.7,1.2 40.1,1.2 40.1,8.5 53.6,8.5 "/>
</g>
<path class="st3" d="M19.3,0.4c10.9,0,17.1,4.7,18,12.3h-9.1c-0.6-2.7-3.4-5-8.8-5c-6.3,0-9.4,3.1-9.4,8.3
c0,5.2,3.1,8.4,9.4,8.4c5.5,0,8.3-2.3,8.8-5.1h9.1c-0.9,7.7-7.1,12.4-18,12.4C7.4,31.6,0.9,26.1,0.9,16S7.4,0.4,19.3,0.4
L19.3,0.4z"/>
</g>
</g>
<path class="st5" d="M94.7,23.8c1-1.4,3.3-2.1,5.2-2.7c-2,0.1-4.4,0.3-5.8-0.6c-1.4-1-2.1-3.3-2.7-5.2c0.1,2,0.3,4.4-0.6,5.8
c-1,1.4-3.3,2.1-5.2,2.7c2-0.1,4.4-0.3,5.8,0.6c1.4,1,2.1,3.3,2.7,5.2C93.9,27.6,93.7,25.2,94.7,23.8z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.1 KiB

-129
View File
@@ -1,129 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 28.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 153 32" style="enable-background:new 0 0 153 32;" xml:space="preserve">
<style type="text/css">
.st0{display:none;}
.st1{display:inline;}
.st2{display:inline;clip-path:url(#SVGID_00000021077498389242353310000002629454631782585475_);}
.st3{fill:#00004A;}
.st4{fill:#00E5D9;}
.st5{fill:#00E4D8;}
</style>
<g class="st0">
<defs>
<rect id="SVGID_1_" x="0.9" width="151.4" height="32"/>
</defs>
<clipPath id="SVGID_00000068637063279286702420000005043763006986931103_" class="st1">
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
</clipPath>
<g style="display:inline;clip-path:url(#SVGID_00000068637063279286702420000005043763006986931103_);">
<path class="st3" d="M19.3,0c10.9,0,17.1,4.8,18,12.6h-9.1c-0.6-2.8-3.4-5.1-8.8-5.1c-6.3,0-9.4,3.2-9.4,8.5
c0,5.3,3.1,8.6,9.4,8.6c5.5,0,8.3-2.4,8.8-5.2h9.1c-0.9,7.9-7,12.7-18,12.7C7.4,32,0.9,26.4,0.9,16C0.9,5.6,7.4,0,19.3,0z"/>
<path class="st3" d="M62.3,8.4c0.2-0.1,0.4-0.1,0.6-0.1c4,0,8,0,12.1,0c0.2,0,0.3,0,0.5,0c0.1,0,0.2,0,0.3-0.1c0-0.1,0-0.2,0-0.3
c0-2.3,0-4.6,0-6.9c0-0.1,0-0.2,0-0.3c-0.2,0-0.5,0-0.7,0c-11.4,0-22.8,0-34.3,0c-0.2,0-0.3,0-0.4,0c-0.1,0-0.2,0-0.3,0.1
c0,0.2,0,0.4,0,0.5c0,0.8,0,1.6,0,2.4c0,1.3,0,2.6,0,3.9c0,0.2,0,0.4,0.1,0.5c0.2,0.1,0.4,0.1,0.6,0.1c4,0,8.1,0,12.1,0
c0.2,0,0.3,0,0.5,0c0.1,0,0.2,0,0.3,0.1c0,0.2,0,0.5,0,0.7c0,6.6,0,13.1,0,19.7c0,0.2,0,0.4,0,0.5c0,0.1,0,0.2,0,0.3
c0.1,0,0.1,0,0.1,0c2.7-0.5,5.4-1.1,8.1-1.6c0.1,0,0.2-0.1,0.3-0.1c0-0.2,0-0.4,0-0.6c0-6,0-12,0-18.1c0-0.2,0-0.4,0-0.5
C62.2,8.6,62.3,8.5,62.3,8.4L62.3,8.4z M53.7,30.2c0,0.3-0.1,0.6,0,0.8c0.4,0.1,6.6,0.1,8.2,0c0.1,0,0.2,0,0.3,0
c0-0.1,0.1-0.1,0.1-0.2c0-0.6,0-1.3,0-1.9c0,0,0-0.1-0.1-0.2C59.4,29.2,56.5,29.7,53.7,30.2L53.7,30.2z"/>
<path class="st3" d="M151.5,11.4c0.1,0,0.2,0,0.2,0c0.1,0.1,0.1,0.2,0.1,0.3c0.1,0.6,0.2,1.3,0.3,1.9c0.2,1.3,0.2,2.6,0.1,3.8
c-0.1,2-0.6,4-1.5,5.9c-1,2.2-2.6,3.9-4.5,5.3c-1.2,0.8-2.5,1.5-3.9,2c-1.1,0.4-2.3,0.7-3.4,0.9c-0.5,0.1-1.1,0.2-1.6,0.3
c-0.5,0.1-0.9,0.1-1.4,0.1c-0.1,0-0.1,0-0.2,0c-0.7,0.1-1.5,0.1-2.2,0.1c-0.7,0-1.5,0-2.2,0c-0.3,0-0.5,0-0.8-0.1
c-0.1,0-0.3,0-0.4,0c-2.2-0.2-4.3-0.6-6.4-1.3c-1.4-0.5-2.7-1.2-4-2c-2.7-1.9-4.5-4.5-5.4-7.7c-0.1-0.5-0.3-1.1-0.3-1.6
c0-0.1,0-0.1,0-0.2c0.1-0.1,0.2-0.1,0.4-0.2c0.5-0.1,1.1-0.2,1.6-0.3c0.9-0.2,1.8-0.4,2.7-0.5c0.9-0.2,1.8-0.4,2.7-0.6
c0.3-0.1,0.5-0.1,0.8-0.2c0.2,0,0.3-0.1,0.5-0.1c0.1,0,0.1,0.1,0.2,0.2c0,0.4,0.1,0.8,0.2,1.2c0.2,0.9,0.6,1.8,1.2,2.6
c0.6,0.8,1.3,1.4,2.2,1.9c0.7,0.4,1.5,0.7,2.4,0.9c0.6,0.1,1.2,0.3,1.8,0.3c0.8,0.1,1.7,0.2,2.5,0.2c0.4,0,0.7,0,1.1,0
c0.1,0,0.3,0,0.4,0c1.3-0.1,2.5-0.3,3.7-0.7c0.5-0.2,1-0.4,1.5-0.7c1.6-0.9,2.7-2.3,3.2-4.1c0.2-0.6,0.3-1.2,0.3-1.8
c0-0.5,0.1-1,0.1-1.4c0-0.7-0.1-1.4-0.2-2.1c0-0.1,0-0.2,0-0.3c0,0,0-0.1,0-0.1c0,0,0.1,0,0.1-0.1c0.1,0,0.3-0.1,0.4-0.1
c1.5-0.3,2.9-0.6,4.4-0.9c0.7-0.1,1.5-0.3,2.2-0.4c0.2,0,0.4-0.1,0.6-0.1c0.1,0,0.1-0.1,0.2-0.1c0,0,0,0,0-0.1
C151.2,11.3,151.3,11.3,151.5,11.4C151.5,11.4,151.5,11.4,151.5,11.4L151.5,11.4z"/>
<path class="st3" d="M151.1,11.5c-0.4,0-0.7,0.1-1.1,0.1c-0.7,0.1-1.4,0.2-2.1,0.4c-0.7,0.1-1.3,0.2-2,0.4c-0.7,0.1-1.3,0.2-2,0.3
c-0.1,0-0.3,0.1-0.4,0.1c-0.1,0-0.3,0-0.4,0c-0.1-0.2-0.2-0.3-0.2-0.5c-0.5-1.4-1.4-2.4-2.6-3.2c-0.5-0.3-1-0.6-1.5-0.8
c-0.7-0.3-1.5-0.5-2.3-0.6c-1.3-0.2-2.6-0.3-3.8-0.3c-0.7,0-1.4,0.1-2.1,0.2c-0.8,0.1-1.6,0.3-2.4,0.5c-0.5,0.2-1,0.4-1.4,0.6
c-1.8,1-3,2.5-3.5,4.5c-0.2,0.7-0.3,1.5-0.3,2.2c0,0.2,0,0.4,0,0.7c0,0.1,0,0.2-0.1,0.2c-0.1,0.1-0.3,0.1-0.4,0.1
c-0.6,0.1-1.3,0.2-1.9,0.3c-0.6,0.1-1.3,0.2-1.9,0.3c-0.7,0.1-1.4,0.2-2.1,0.4c-0.7,0.1-1.3,0.2-2,0.4c-0.1,0-0.3,0.1-0.4,0
c-0.1,0-0.1-0.1-0.1-0.2c0-0.1,0-0.3,0-0.4c0-0.9-0.1-1.8,0-2.7c0.1-2.3,0.7-4.5,1.8-6.6c1.1-2,2.6-3.6,4.4-4.8
c1.3-0.9,2.7-1.5,4.2-2c1.1-0.4,2.3-0.6,3.4-0.8c0.6-0.1,1.3-0.2,1.9-0.3c0.6-0.1,1.3-0.1,1.9-0.1c1.2,0,2.4,0,3.6,0
c0.2,0,0.4,0,0.5,0c0.1,0,0.2,0,0.3,0c0.8,0,1.6,0.2,2.5,0.3c1.3,0.2,2.5,0.5,3.7,0.9c1.4,0.5,2.8,1.1,4.1,2
c1.9,1.3,3.4,2.9,4.5,5c0.4,0.8,0.8,1.7,1,2.6c0,0.1,0,0.2,0.1,0.2c0,0,0,0.1,0,0.1c-0.1,0-0.1,0.1-0.2,0.1l0,0
C151.4,11.5,151.2,11.4,151.1,11.5L151.1,11.5L151.1,11.5z"/>
<path class="st3" d="M151,11.5c0.1-0.1,0.1-0.1,0.2-0.1c0.1,0,0.2,0,0.3,0C151.4,11.5,151.2,11.5,151,11.5L151,11.5z"/>
<path class="st3" d="M84.5,23.5C84.5,23.4,84.6,23.4,84.5,23.5c2.7-5.4,5.3-10.8,8-16.1c0,0,0-0.1,0.1-0.1c0,0,0-0.1,0.1-0.1
c0,0,0,0,0,0c0,0,0,0,0.1,0c0.1,0.1,0.2,0.3,0.2,0.4c0.9,1.8,1.7,3.5,2.6,5.3c1.2,2.4,2.4,4.8,3.6,7.2c0.1,0.2,0.1,0.3,0.3,0.5
c0.6-0.1,1.2-0.2,1.8-0.3c1.1-0.2,2.1-0.4,3.2-0.5c1-0.2,2-0.3,3-0.5c0.1,0,0.2-0.1,0.3-0.1c-0.1-0.2-0.2-0.3-0.2-0.5
c-1.4-2.8-2.8-5.6-4.2-8.4c-1.5-2.9-2.9-5.8-4.4-8.7c-0.1-0.2-0.1-0.4-0.3-0.5c-0.1,0-0.1,0-0.2,0c-3.8,0-7.6,0-11.4,0
c-0.1,0-0.1,0-0.2,0c0,0,0,0-0.1,0c0,0,0,0-0.1,0C82.6,9,78.5,17.2,74.4,25.3c0,0,0,0.1,0,0.1c0,0,0.1,0.1,0.1,0
C77.8,24.8,81.2,24.1,84.5,23.5z M100,21.8c0.3,0.7,4.4,9,4.6,9.3c0.1,0,0.2,0,0.3,0c2.9,0,5.8,0,8.7,0c0,0,0,0,0.1,0
c0,0,0,0,0.1-0.1c0-0.1-0.1-0.2-0.1-0.2c-0.1-0.2-0.3-0.5-0.4-0.8c-1.6-3.1-3.2-6.3-4.7-9.4c-0.1-0.2-0.1-0.3-0.3-0.5
C105.5,20.7,102.8,21.2,100,21.8L100,21.8z M73.7,26.8c-0.7,1.4-1.4,2.7-2,4.1c0,0.1,0,0.2-0.1,0.3h9.1c0.2-0.3,2.6-5,2.9-5.8
c0-0.1,0-0.1,0.1-0.2c-0.1,0-0.2,0-0.3,0C80.2,25.6,76.9,26.2,73.7,26.8L73.7,26.8z M93.8,29C93.8,29,93.8,29,93.8,29
c0.1-0.5,0.1-1,0-1.5c0-0.5-0.1-1.1,0-1.6c0-0.3,0.1-0.7,0.2-1c0.3-1.2,1.1-2,2.1-2.4c0.5-0.2,1.1-0.4,1.6-0.6
c0.4-0.2,0.9-0.3,1.3-0.5c-0.1-0.1-0.2-0.1-0.3-0.1c-1,0-1.9,0-2.9,0c-0.9,0-1.7-0.4-2.4-1c-0.4-0.4-0.8-0.9-1-1.5
c-0.3-0.8-0.6-1.6-0.8-2.4c0-0.1,0-0.1-0.2-0.1c0,0.2,0,0.3,0,0.5c0,0.8,0,1.7,0,2.5c0,0.8-0.2,1.5-0.7,2.1
c-0.5,0.7-1.1,1.1-1.8,1.3c-0.8,0.3-1.6,0.6-2.4,0.9c-0.1,0-0.1,0.1-0.2,0.1c0.1,0.1,0.3,0.1,0.4,0.1c0.9,0,1.8,0,2.8,0
c0.2,0,0.4,0,0.6,0.1c1.3,0.3,2.1,1.1,2.6,2.3c0.1,0.2,0.2,0.5,0.3,0.7C93.3,27.6,93.5,28.3,93.8,29L93.8,29z"/>
<path class="st4" d="M93.8,29C93.8,29,93.8,29,93.8,29c0.1-0.5,0.1-1,0-1.5c0-0.5-0.1-1.1,0-1.6c0-0.3,0.1-0.7,0.2-1
c0.3-1.2,1.1-2,2.1-2.4c0.5-0.2,1.1-0.4,1.6-0.6c0.4-0.2,0.8-0.3,1.3-0.5c-0.1-0.1-0.2-0.1-0.3-0.1c-1,0-1.9,0-2.9,0
c-0.9,0-1.7-0.4-2.4-1c-0.4-0.4-0.8-0.9-1-1.5c-0.3-0.8-0.6-1.6-0.8-2.4c0-0.1,0-0.1-0.2-0.1c0,0.2,0,0.3,0,0.5c0,0.8,0,1.7,0,2.5
c0,0.8-0.2,1.5-0.7,2.1c-0.5,0.7-1.1,1.1-1.8,1.3c-0.8,0.3-1.6,0.6-2.4,0.9c-0.1,0-0.1,0.1-0.2,0.1c0.1,0.1,0.3,0.1,0.4,0.1
c0.9,0,1.8,0,2.8,0c0.2,0,0.4,0,0.6,0.1c1.3,0.3,2.1,1.1,2.6,2.3c0.1,0.2,0.2,0.5,0.3,0.7C93.3,27.6,93.5,28.3,93.8,29L93.8,29z"
/>
</g>
</g>
<g id="Logotype" class="st0">
<g class="st1">
<g>
<polygon class="st3" points="100.4,22.3 104.7,30.8 113.9,30.8 108.6,20.6 "/>
<g>
<path class="st3" d="M122.9,16.1c0,0,0-0.1,0-0.1c0-5.2,2.9-8.3,10.2-8.3c5.7,0,8.8,1.9,9.8,5.2l8.9-1.4
c-1.9-7-8.3-11.1-18.7-11.1c-12.5,0-19.3,5.8-19.3,15.6c0,0.5,0,1.1,0.1,1.6L122.9,16.1L122.9,16.1z"/>
<path class="st3" d="M143.1,13.3c0.2,0.8,0.3,1.7,0.3,2.6c0,5.2-3,8.4-10.3,8.4c-6.5,0-9.6-2.5-10.1-6.8l-8.8,1.8
c1.4,7.8,8,12.2,18.9,12.2c12.4,0,19.3-5.9,19.3-15.7c0-1.6-0.2-3.1-0.5-4.5L143.1,13.3L143.1,13.3z"/>
</g>
<g>
<polygon class="st3" points="73.5,27 71.5,30.8 80.8,30.8 83.5,25.4 "/>
<polygon class="st3" points="98.6,1.2 86.8,1.2 74.6,24.7 84.9,22.6 92.7,7.2 99.2,19.9 107.6,18.6 "/>
</g>
<g>
<g>
<polygon class="st3" points="53.6,30.2 53.6,30.8 62.2,30.8 62.2,28.8 "/>
<polygon class="st3" points="53.6,29.1 62.2,27.3 62.2,8.5 75.7,8.5 75.7,1.2 40.1,1.2 40.1,8.5 53.6,8.5 "/>
</g>
<path class="st3" d="M19.3,0.4c10.9,0,17.1,4.7,18,12.3h-9.1c-0.6-2.7-3.4-5-8.8-5c-6.3,0-9.4,3.1-9.4,8.3
c0,5.2,3.1,8.4,9.4,8.4c5.5,0,8.3-2.3,8.8-5.1h9.1c-0.9,7.7-7.1,12.4-18,12.4C7.4,31.6,0.9,26.1,0.9,16S7.4,0.4,19.3,0.4
L19.3,0.4z"/>
</g>
</g>
<path class="st5" d="M94.7,23.8c1-1.4,3.3-2.1,5.2-2.7c-2,0.1-4.4,0.3-5.8-0.6c-1.4-1-2.1-3.3-2.7-5.2c0.1,2,0.3,4.4-0.6,5.8
c-1,1.4-3.3,2.1-5.2,2.7c2-0.1,4.4-0.3,5.8,0.6c1.4,1,2.1,3.3,2.7,5.2C93.9,27.6,93.7,25.2,94.7,23.8z"/>
</g>
</g>
<g id="Logotype_00000134236662009793237430000016451412871672361145_">
<g>
<g>
<polygon class="st3" points="100,22.3 104.3,30.7 113.5,30.7 108.2,20.5 "/>
<g>
<path class="st3" d="M122.4,16.1c0,0,0-0.1,0-0.1c0-5.2,2.9-8.2,10.2-8.2c5.7,0,8.7,1.9,9.8,5.2l8.8-1.4c-1.8-7-8.2-11-18.6-11
c-12.4,0-19.1,5.7-19.1,15.5c0,0.5,0,1.1,0.1,1.6L122.4,16.1L122.4,16.1z"/>
<path class="st3" d="M142.5,13.4c0.2,0.8,0.3,1.7,0.3,2.6c0,5.1-3,8.3-10.2,8.3c-6.4,0-9.5-2.5-10.1-6.7l-8.7,1.8
c1.4,7.7,7.9,12.2,18.8,12.2c12.4,0,19.1-5.8,19.1-15.6c0-1.6-0.2-3.1-0.5-4.4L142.5,13.4L142.5,13.4z"/>
</g>
<g>
<polygon class="st3" points="73.4,26.9 71.4,30.7 80.6,30.7 83.3,25.3 "/>
<polygon class="st3" points="98.4,1.3 86.6,1.3 74.5,24.7 84.7,22.5 92.5,7.3 98.9,19.9 107.2,18.6 "/>
</g>
<g>
<g>
<polygon class="st3" points="53.7,30.1 53.7,30.7 62.2,30.7 62.2,28.7 "/>
<polygon class="st3" points="53.7,29 62.2,27.3 62.2,8.5 75.6,8.5 75.6,1.3 40.2,1.3 40.2,8.5 53.7,8.5 "/>
</g>
<path class="st3" d="M19.6,0.5c10.8,0,17,4.7,17.8,12.2h-9.1c-0.6-2.7-3.3-5-8.8-5c-6.3,0-9.4,3.1-9.4,8.2s3.1,8.3,9.4,8.3
c5.4,0,8.2-2.3,8.8-5.1h9.1c-0.9,7.6-7,12.3-17.8,12.3C7.8,31.5,1.3,26.1,1.3,16S7.8,0.5,19.6,0.5L19.6,0.5z"/>
</g>
</g>
<path class="st3" d="M94.4,23.7c1-1.4,3.3-2.1,5.1-2.7c-2,0.1-4.4,0.3-5.8-0.6c-1.4-1-2.1-3.3-2.7-5.1c0.1,2,0.3,4.4-0.6,5.8
c-1,1.4-3.3,2.1-5.1,2.7c2-0.1,4.4-0.3,5.8,0.6c1.4,1,2.1,3.3,2.7,5.1C93.6,27.5,93.4,25.1,94.4,23.7z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 9.6 KiB

-11
View File
@@ -1,11 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" id="Ebene_1" x="0" y="0" viewBox="0 0 153 46.0">
<style>
.st3{fill:#ffffff}
.st5{fill:#00E4D8}
</style>
<g id="Logotype_00000134236662009793237430000016451412871672361145_">
<path d="m100 22.3 4.3 8.4h9.2l-5.3-10.2zm22.4-6.2V16c0-5.2 2.9-8.2 10.2-8.2 5.7 0 8.7 1.9 9.8 5.2l8.8-1.4c-1.8-7-8.2-11-18.6-11-12.4 0-19.1 5.7-19.1 15.5 0 .5 0 1.1.1 1.6zm20.1-2.7c.2.8.3 1.7.3 2.6 0 5.1-3 8.3-10.2 8.3-6.4 0-9.5-2.5-10.1-6.7l-8.7 1.8c1.4 7.7 7.9 12.2 18.8 12.2 12.4 0 19.1-5.8 19.1-15.6 0-1.6-.2-3.1-.5-4.4zM73.4 26.9l-2 3.8h9.2l2.7-5.4zm25-25.6H86.6L74.5 24.7l10.2-2.2 7.8-15.2 6.4 12.6 8.3-1.3zM53.7 30.1v.6h8.5v-2zm0-1.1 8.5-1.7V8.5h13.4V1.3H40.2v7.2h13.5zM19.6.5c10.8 0 17 4.7 17.8 12.2h-9.1c-.6-2.7-3.3-5-8.8-5-6.3 0-9.4 3.1-9.4 8.2s3.1 8.3 9.4 8.3c5.4 0 8.2-2.3 8.8-5.1h9.1c-.9 7.6-7 12.3-17.8 12.3C7.8 31.5 1.3 26.1 1.3 16S7.8.5 19.6.5" class="st3"/>
<path d="M94.4 23.7c1-1.4 3.3-2.1 5.1-2.7-2 .1-4.4.3-5.8-.6-1.4-1-2.1-3.3-2.7-5.1.1 2 .3 4.4-.6 5.8-1 1.4-3.3 2.1-5.1 2.7 2-.1 4.4-.3 5.8.6 1.4 1 2.1 3.3 2.7 5.1-.2-2-.4-4.4.6-5.8" class="st5"/>
</g>
<path d="M6.48 45.77Q5.41 45.77 4.62 45.44Q3.83 45.1 3.38 44.47Q2.94 43.83 2.9 42.92H4.53Q4.57 43.4 4.83 43.72Q5.09 44.04 5.52 44.2Q5.95 44.36 6.47 44.36Q7.01 44.36 7.43 44.19Q7.85 44.03 8.08 43.72Q8.32 43.42 8.32 43.03Q8.32 42.67 8.11 42.43Q7.9 42.2 7.53 42.03Q7.15 41.87 6.65 41.75L5.6 41.47Q4.45 41.18 3.8 40.57Q3.15 39.97 3.15 38.99Q3.15 38.18 3.59 37.57Q4.03 36.95 4.8 36.61Q5.56 36.28 6.52 36.28Q7.51 36.28 8.25 36.62Q8.99 36.96 9.41 37.56Q9.83 38.16 9.85 38.93H8.25Q8.18 38.33 7.71 38.01Q7.25 37.68 6.5 37.68Q5.98 37.68 5.6 37.84Q5.23 38 5.03 38.27Q4.83 38.55 4.83 38.9Q4.83 39.28 5.06 39.53Q5.3 39.78 5.66 39.93Q6.03 40.08 6.4 40.17L7.26 40.39Q7.77 40.51 8.26 40.72Q8.74 40.92 9.13 41.24Q9.52 41.55 9.75 41.99Q9.97 42.44 9.97 43.04Q9.97 43.85 9.56 44.47Q9.15 45.08 8.37 45.43Q7.58 45.77 6.48 45.77ZM18.24 45.75Q17.03 45.75 16.08 45.19Q15.12 44.63 14.57 43.57Q14.02 42.51 14.02 41.02Q14.02 39.53 14.58 38.47Q15.13 37.4 16.08 36.84Q17.04 36.28 18.24 36.28Q19 36.28 19.66 36.49Q20.32 36.71 20.84 37.12Q21.35 37.53 21.69 38.12Q22.02 38.72 22.13 39.47H20.47Q20.39 39.06 20.19 38.74Q20 38.43 19.71 38.21Q19.41 37.98 19.05 37.87Q18.68 37.76 18.27 37.76Q17.51 37.76 16.93 38.14Q16.34 38.52 16.01 39.25Q15.68 39.97 15.68 41.02Q15.68 42.08 16.02 42.8Q16.35 43.53 16.93 43.9Q17.52 44.27 18.27 44.27Q18.68 44.27 19.04 44.16Q19.41 44.04 19.7 43.82Q20 43.6 20.19 43.28Q20.39 42.97 20.47 42.56H22.14Q22.05 43.22 21.73 43.8Q21.42 44.38 20.91 44.82Q20.41 45.26 19.74 45.51Q19.06 45.75 18.24 45.75ZM28.13 36.4V45.63H26.48V36.4ZM32.81 45.63V36.4H38.81V37.79H34.46V40.28H38.49V41.66H34.46V44.24H38.83V45.63ZM43.31 45.63V36.4H45.18L48.38 41.48Q48.57 41.8 48.8 42.2Q49.02 42.6 49.26 43.09Q49.5 43.58 49.74 44.19H49.55Q49.51 43.66 49.48 43.12Q49.44 42.57 49.43 42.07Q49.41 41.58 49.41 41.24V36.4H51.09V45.63H49.21L46.33 41.05Q46.06 40.61 45.82 40.19Q45.57 39.76 45.3 39.22Q45.02 38.68 44.62 37.92H44.85Q44.89 38.59 44.92 39.2Q44.96 39.8 44.98 40.28Q45 40.75 45 41.05V45.63ZM59.7 45.75Q58.49 45.75 57.53 45.19Q56.58 44.63 56.03 43.57Q55.48 42.51 55.48 41.02Q55.48 39.53 56.03 38.47Q56.58 37.4 57.54 36.84Q58.5 36.28 59.7 36.28Q60.46 36.28 61.12 36.49Q61.78 36.71 62.29 37.12Q62.81 37.53 63.14 38.12Q63.48 38.72 63.59 39.47H61.92Q61.84 39.06 61.65 38.74Q61.45 38.43 61.16 38.21Q60.87 37.98 60.51 37.87Q60.14 37.76 59.73 37.76Q58.97 37.76 58.38 38.14Q57.8 38.52 57.47 39.25Q57.14 39.97 57.14 41.02Q57.14 42.08 57.47 42.8Q57.81 43.53 58.39 43.9Q58.97 44.27 59.72 44.27Q60.14 44.27 60.5 44.16Q60.86 44.04 61.16 43.82Q61.45 43.6 61.65 43.28Q61.85 42.97 61.93 42.56H63.59Q63.5 43.22 63.19 43.8Q62.88 44.38 62.37 44.82Q61.86 45.26 61.19 45.51Q60.52 45.75 59.7 45.75ZM67.93 45.63V36.4H73.93V37.79H69.59V40.28H73.61V41.66H69.59V44.24H73.95V45.63ZM84.45 45.63V36.4H87.91Q88.97 36.4 89.69 36.8Q90.41 37.2 90.78 37.89Q91.15 38.57 91.15 39.46Q91.15 40.34 90.78 41.03Q90.41 41.72 89.68 42.11Q88.95 42.51 87.88 42.51H85.59V41.15H87.66Q88.29 41.15 88.69 40.93Q89.09 40.71 89.28 40.33Q89.46 39.95 89.46 39.46Q89.46 38.96 89.28 38.59Q89.09 38.21 88.69 38Q88.28 37.78 87.65 37.78H86.11V45.63ZM99.41 45.75Q98.2 45.75 97.24 45.19Q96.28 44.63 95.72 43.57Q95.17 42.51 95.17 41.02Q95.17 39.53 95.72 38.47Q96.28 37.4 97.24 36.84Q98.2 36.28 99.41 36.28Q100.62 36.28 101.57 36.84Q102.53 37.4 103.08 38.47Q103.64 39.53 103.64 41.02Q103.64 42.51 103.08 43.57Q102.53 44.63 101.57 45.19Q100.62 45.75 99.41 45.75ZM99.41 44.27Q100.16 44.27 100.74 43.9Q101.32 43.52 101.65 42.8Q101.98 42.07 101.98 41.02Q101.98 39.96 101.65 39.23Q101.32 38.51 100.74 38.13Q100.16 37.76 99.41 37.76Q98.65 37.76 98.07 38.13Q97.49 38.51 97.16 39.24Q96.83 39.97 96.83 41.02Q96.83 42.07 97.16 42.8Q97.49 43.52 98.07 43.9Q98.65 44.27 99.41 44.27ZM108.03 45.63V36.4H111.48Q112.54 36.4 113.26 36.77Q113.99 37.14 114.36 37.81Q114.73 38.47 114.73 39.35Q114.73 40.24 114.35 40.89Q113.98 41.54 113.25 41.89Q112.52 42.24 111.45 42.24H108.99V40.87H111.23Q111.86 40.87 112.26 40.69Q112.66 40.52 112.85 40.18Q113.04 39.84 113.04 39.35Q113.04 38.86 112.85 38.51Q112.66 38.16 112.26 37.97Q111.86 37.78 111.23 37.78H109.68V45.63ZM113.22 45.63 110.98 41.44H112.79L115.07 45.63ZM118.71 37.79V36.4H126.06V37.79H123.21V45.63H121.56V37.79ZM129.7 45.63 132.92 36.4H135.02L138.31 45.63H136.5L134.86 40.84Q134.62 40.09 134.36 39.15Q134.1 38.22 133.78 37.02H134.13Q133.82 38.23 133.57 39.17Q133.32 40.12 133.09 40.84L131.51 45.63ZM131.58 43.34V42.01H136.44V43.34ZM142.37 45.63V36.4H144.02V44.24H148.1V45.63Z" class="st3"/>
</svg>

Before

Width:  |  Height:  |  Size: 5.4 KiB

-10
View File
@@ -1,10 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" id="Ebene_1" x="0" y="0" viewBox="0 0 153 46.0">
<style>
.st3{fill:#00004a}
</style>
<g id="Logotype_00000134236662009793237430000016451412871672361145_">
<path d="m100 22.3 4.3 8.4h9.2l-5.3-10.2zm22.4-6.2V16c0-5.2 2.9-8.2 10.2-8.2 5.7 0 8.7 1.9 9.8 5.2l8.8-1.4c-1.8-7-8.2-11-18.6-11-12.4 0-19.1 5.7-19.1 15.5 0 .5 0 1.1.1 1.6zm20.1-2.7c.2.8.3 1.7.3 2.6 0 5.1-3 8.3-10.2 8.3-6.4 0-9.5-2.5-10.1-6.7l-8.7 1.8c1.4 7.7 7.9 12.2 18.8 12.2 12.4 0 19.1-5.8 19.1-15.6 0-1.6-.2-3.1-.5-4.4zM73.4 26.9l-2 3.8h9.2l2.7-5.4zm25-25.6H86.6L74.5 24.7l10.2-2.2 7.8-15.2 6.4 12.6 8.3-1.3zM53.7 30.1v.6h8.5v-2zm0-1.1 8.5-1.7V8.5h13.4V1.3H40.2v7.2h13.5zM19.6.5c10.8 0 17 4.7 17.8 12.2h-9.1c-.6-2.7-3.3-5-8.8-5-6.3 0-9.4 3.1-9.4 8.2s3.1 8.3 9.4 8.3c5.4 0 8.2-2.3 8.8-5.1h9.1c-.9 7.6-7 12.3-17.8 12.3C7.8 31.5 1.3 26.1 1.3 16S7.8.5 19.6.5" class="st3"/>
<path d="M94.4 23.7c1-1.4 3.3-2.1 5.1-2.7-2 .1-4.4.3-5.8-.6-1.4-1-2.1-3.3-2.7-5.1.1 2 .3 4.4-.6 5.8-1 1.4-3.3 2.1-5.1 2.7 2-.1 4.4-.3 5.8.6 1.4 1 2.1 3.3 2.7 5.1-.2-2-.4-4.4.6-5.8" class="st3"/>
</g>
<path d="M6.48 45.77Q5.41 45.77 4.62 45.44Q3.83 45.1 3.38 44.47Q2.94 43.83 2.9 42.92H4.53Q4.57 43.4 4.83 43.72Q5.09 44.04 5.52 44.2Q5.95 44.36 6.47 44.36Q7.01 44.36 7.43 44.19Q7.85 44.03 8.08 43.72Q8.32 43.42 8.32 43.03Q8.32 42.67 8.11 42.43Q7.9 42.2 7.53 42.03Q7.15 41.87 6.65 41.75L5.6 41.47Q4.45 41.18 3.8 40.57Q3.15 39.97 3.15 38.99Q3.15 38.18 3.59 37.57Q4.03 36.95 4.8 36.61Q5.56 36.28 6.52 36.28Q7.51 36.28 8.25 36.62Q8.99 36.96 9.41 37.56Q9.83 38.16 9.85 38.93H8.25Q8.18 38.33 7.71 38.01Q7.25 37.68 6.5 37.68Q5.98 37.68 5.6 37.84Q5.23 38 5.03 38.27Q4.83 38.55 4.83 38.9Q4.83 39.28 5.06 39.53Q5.3 39.78 5.66 39.93Q6.03 40.08 6.4 40.17L7.26 40.39Q7.77 40.51 8.26 40.72Q8.74 40.92 9.13 41.24Q9.52 41.55 9.75 41.99Q9.97 42.44 9.97 43.04Q9.97 43.85 9.56 44.47Q9.15 45.08 8.37 45.43Q7.58 45.77 6.48 45.77ZM18.24 45.75Q17.03 45.75 16.08 45.19Q15.12 44.63 14.57 43.57Q14.02 42.51 14.02 41.02Q14.02 39.53 14.58 38.47Q15.13 37.4 16.08 36.84Q17.04 36.28 18.24 36.28Q19 36.28 19.66 36.49Q20.32 36.71 20.84 37.12Q21.35 37.53 21.69 38.12Q22.02 38.72 22.13 39.47H20.47Q20.39 39.06 20.19 38.74Q20 38.43 19.71 38.21Q19.41 37.98 19.05 37.87Q18.68 37.76 18.27 37.76Q17.51 37.76 16.93 38.14Q16.34 38.52 16.01 39.25Q15.68 39.97 15.68 41.02Q15.68 42.08 16.02 42.8Q16.35 43.53 16.93 43.9Q17.52 44.27 18.27 44.27Q18.68 44.27 19.04 44.16Q19.41 44.04 19.7 43.82Q20 43.6 20.19 43.28Q20.39 42.97 20.47 42.56H22.14Q22.05 43.22 21.73 43.8Q21.42 44.38 20.91 44.82Q20.41 45.26 19.74 45.51Q19.06 45.75 18.24 45.75ZM28.13 36.4V45.63H26.48V36.4ZM32.81 45.63V36.4H38.81V37.79H34.46V40.28H38.49V41.66H34.46V44.24H38.83V45.63ZM43.31 45.63V36.4H45.18L48.38 41.48Q48.57 41.8 48.8 42.2Q49.02 42.6 49.26 43.09Q49.5 43.58 49.74 44.19H49.55Q49.51 43.66 49.48 43.12Q49.44 42.57 49.43 42.07Q49.41 41.58 49.41 41.24V36.4H51.09V45.63H49.21L46.33 41.05Q46.06 40.61 45.82 40.19Q45.57 39.76 45.3 39.22Q45.02 38.68 44.62 37.92H44.85Q44.89 38.59 44.92 39.2Q44.96 39.8 44.98 40.28Q45 40.75 45 41.05V45.63ZM59.7 45.75Q58.49 45.75 57.53 45.19Q56.58 44.63 56.03 43.57Q55.48 42.51 55.48 41.02Q55.48 39.53 56.03 38.47Q56.58 37.4 57.54 36.84Q58.5 36.28 59.7 36.28Q60.46 36.28 61.12 36.49Q61.78 36.71 62.29 37.12Q62.81 37.53 63.14 38.12Q63.48 38.72 63.59 39.47H61.92Q61.84 39.06 61.65 38.74Q61.45 38.43 61.16 38.21Q60.87 37.98 60.51 37.87Q60.14 37.76 59.73 37.76Q58.97 37.76 58.38 38.14Q57.8 38.52 57.47 39.25Q57.14 39.97 57.14 41.02Q57.14 42.08 57.47 42.8Q57.81 43.53 58.39 43.9Q58.97 44.27 59.72 44.27Q60.14 44.27 60.5 44.16Q60.86 44.04 61.16 43.82Q61.45 43.6 61.65 43.28Q61.85 42.97 61.93 42.56H63.59Q63.5 43.22 63.19 43.8Q62.88 44.38 62.37 44.82Q61.86 45.26 61.19 45.51Q60.52 45.75 59.7 45.75ZM67.93 45.63V36.4H73.93V37.79H69.59V40.28H73.61V41.66H69.59V44.24H73.95V45.63ZM84.45 45.63V36.4H87.91Q88.97 36.4 89.69 36.8Q90.41 37.2 90.78 37.89Q91.15 38.57 91.15 39.46Q91.15 40.34 90.78 41.03Q90.41 41.72 89.68 42.11Q88.95 42.51 87.88 42.51H85.59V41.15H87.66Q88.29 41.15 88.69 40.93Q89.09 40.71 89.28 40.33Q89.46 39.95 89.46 39.46Q89.46 38.96 89.28 38.59Q89.09 38.21 88.69 38Q88.28 37.78 87.65 37.78H86.11V45.63ZM99.41 45.75Q98.2 45.75 97.24 45.19Q96.28 44.63 95.72 43.57Q95.17 42.51 95.17 41.02Q95.17 39.53 95.72 38.47Q96.28 37.4 97.24 36.84Q98.2 36.28 99.41 36.28Q100.62 36.28 101.57 36.84Q102.53 37.4 103.08 38.47Q103.64 39.53 103.64 41.02Q103.64 42.51 103.08 43.57Q102.53 44.63 101.57 45.19Q100.62 45.75 99.41 45.75ZM99.41 44.27Q100.16 44.27 100.74 43.9Q101.32 43.52 101.65 42.8Q101.98 42.07 101.98 41.02Q101.98 39.96 101.65 39.23Q101.32 38.51 100.74 38.13Q100.16 37.76 99.41 37.76Q98.65 37.76 98.07 38.13Q97.49 38.51 97.16 39.24Q96.83 39.97 96.83 41.02Q96.83 42.07 97.16 42.8Q97.49 43.52 98.07 43.9Q98.65 44.27 99.41 44.27ZM108.03 45.63V36.4H111.48Q112.54 36.4 113.26 36.77Q113.99 37.14 114.36 37.81Q114.73 38.47 114.73 39.35Q114.73 40.24 114.35 40.89Q113.98 41.54 113.25 41.89Q112.52 42.24 111.45 42.24H108.99V40.87H111.23Q111.86 40.87 112.26 40.69Q112.66 40.52 112.85 40.18Q113.04 39.84 113.04 39.35Q113.04 38.86 112.85 38.51Q112.66 38.16 112.26 37.97Q111.86 37.78 111.23 37.78H109.68V45.63ZM113.22 45.63 110.98 41.44H112.79L115.07 45.63ZM118.71 37.79V36.4H126.06V37.79H123.21V45.63H121.56V37.79ZM129.7 45.63 132.92 36.4H135.02L138.31 45.63H136.5L134.86 40.84Q134.62 40.09 134.36 39.15Q134.1 38.22 133.78 37.02H134.13Q133.82 38.23 133.57 39.17Q133.32 40.12 133.09 40.84L131.51 45.63ZM131.58 43.34V42.01H136.44V43.34ZM142.37 45.63V36.4H144.02V44.24H148.1V45.63Z" class="st3"/>
</svg>

Before

Width:  |  Height:  |  Size: 5.4 KiB

+110
View File
@@ -0,0 +1,110 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 640" role="img" aria-label="Architektura klasycznego CMS z bazą danych: aplikacja CMS z publicznym panelem admina działa non stop, obok baza danych i wolumen mediów; czytelnik dostaje strony renderowane na żądanie">
<defs>
<marker id="arrB" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0 0 L10 5 L0 10 z" fill="#00004a"/>
</marker>
<filter id="softB" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="2" stdDeviation="5" flood-color="#00004a" flood-opacity="0.10"/>
</filter>
<style>
.t { font-family: Inter, "Segoe UI", Arial, sans-serif; }
.box { fill: #ffffff; stroke: #00004a; stroke-width: 1.5; }
.sub { fill: #f5f5f5; stroke: #e2e5ee; stroke-width: 1; }
.ttl { font-weight: 700; font-size: 15.5px; fill: #00004a; letter-spacing: .04em; }
.cap { font-size: 12.5px; fill: #46536a; }
.lbl { font-size: 12px; fill: #46536a; paint-order: stroke; stroke: #ffffff; stroke-width: 4; }
.chipbg { fill: #e9ebf3; }
.chip { font-size: 11.5px; font-weight: 700; fill: #00004a; letter-spacing: .06em; }
.flow { stroke: #00004a; stroke-width: 1.8; fill: none; marker-end: url(#arrB); }
.pill { fill: #f5f5f5; stroke: #e2e5ee; }
.pilltxt { font-size: 12.5px; fill: #101228; }
</style>
</defs>
<!-- machine boundary (mirrors the static-demo diagram 1:1) -->
<rect x="340" y="40" width="830" height="570" rx="18" fill="#f8f9fc" stroke="#e2e5ee" stroke-width="1.5"/>
<text class="t" x="366" y="76" font-size="13" font-weight="700" fill="#00004a" letter-spacing=".18em">MASZYNA · CMS Z BAZĄ DANYCH</text>
<text class="t cap" x="366" y="96">np. WordPress, Strapi, Drupal — wspólny schemat tej klasy rozwiązań</text>
<!-- actor: redaktor -->
<g filter="url(#softB)">
<rect class="box" x="52" y="120" width="220" height="120" rx="16"/>
</g>
<circle cx="88" cy="156" r="11" fill="none" stroke="#00004a" stroke-width="1.8"/>
<path d="M72 186 q16 -18 32 0" fill="none" stroke="#00004a" stroke-width="1.8"/>
<text class="t ttl" x="116" y="162">Redaktor</text>
<text class="t cap" x="70" y="212">panel admina w przeglądarce</text>
<text class="t cap" x="70" y="228">logowanie do aplikacji</text>
<!-- actor: czytelnik -->
<g filter="url(#softB)">
<rect class="box" x="52" y="400" width="220" height="120" rx="16"/>
</g>
<circle cx="88" cy="436" r="11" fill="none" stroke="#00004a" stroke-width="1.8"/>
<path d="M72 466 q16 -18 32 0" fill="none" stroke="#00004a" stroke-width="1.8"/>
<text class="t ttl" x="116" y="442">Czytelnik</text>
<text class="t cap" x="70" y="492">publiczna strona portalu</text>
<text class="t cap" x="70" y="508">HTML składany na żądanie</text>
<!-- CMS application (tall box, always running) -->
<g filter="url(#softB)">
<rect class="box" x="400" y="120" width="320" height="400" rx="16"/>
</g>
<text class="t ttl" x="424" y="152">APLIKACJA CMS</text>
<rect class="chipbg" x="590" y="136" width="106" height="22" rx="11"/>
<text class="t chip" x="643" y="151" text-anchor="middle">NON STOP</text>
<rect class="sub" x="420" y="176" width="280" height="88" rx="12"/>
<text class="t" x="436" y="202" font-size="13.5" font-weight="700" fill="#101228">panel admina</text>
<text class="t cap" x="436" y="224">publiczny adres + logowanie</text>
<text class="t cap" x="436" y="242">z internetu</text>
<rect class="sub" x="420" y="278" width="280" height="88" rx="12"/>
<text class="t" x="436" y="304" font-size="13.5" font-weight="700" fill="#101228">renderowanie stron</text>
<text class="t cap" x="436" y="326">każde wejście czytelnika =</text>
<text class="t cap" x="436" y="344">praca aplikacji i bazy</text>
<rect class="sub" x="420" y="380" width="280" height="88" rx="12"/>
<text class="t" x="436" y="406" font-size="13.5" font-weight="700" fill="#101228">pluginy / rozszerzenia</text>
<text class="t cap" x="436" y="428">własny cykl aktualizacji</text>
<text class="t cap" x="436" y="446">i podatności</text>
<!-- database -->
<g filter="url(#softB)">
<rect class="box" x="790" y="120" width="340" height="185" rx="16"/>
</g>
<text class="t ttl" x="814" y="152">BAZA DANYCH</text>
<rect class="chipbg" x="972" y="136" width="106" height="22" rx="11"/>
<text class="t chip" x="1025" y="151" text-anchor="middle">NON STOP</text>
<text class="t cap" x="814" y="188">treść + konta użytkowników</text>
<text class="t cap" x="814" y="212">wymaga backupów i testów odtwarzania</text>
<text class="t cap" x="814" y="236">migracje schematu przy aktualizacjach</text>
<text class="t cap" x="814" y="266">historia zmian: zależnie od produktu,</text>
<text class="t cap" x="814" y="284">bywa funkcją płatną</text>
<!-- media volume -->
<g filter="url(#softB)">
<rect class="sub" x="790" y="400" width="340" height="120" rx="16"/>
</g>
<text class="t ttl" x="814" y="432">media/</text>
<rect class="chipbg" x="896" y="416" width="82" height="22" rx="11"/>
<text class="t chip" x="937" y="431" text-anchor="middle">WOLUMEN</text>
<text class="t cap" x="814" y="462">obrazy i pliki poza bazą</text>
<text class="t cap" x="814" y="480">osobny backup</text>
<!-- flows -->
<path class="flow" d="M272 180 H396"/>
<text class="t lbl" x="334" y="170" text-anchor="middle">logowanie do panelu</text>
<path class="flow" d="M400 460 H276"/>
<text class="t lbl" x="338" y="450" text-anchor="middle">HTML na żądanie</text>
<path class="flow" d="M720 212 H786"/>
<text class="t lbl" x="753" y="202" text-anchor="middle">SQL</text>
<path class="flow" d="M720 440 H786"/>
<text class="t lbl" x="753" y="430" text-anchor="middle">upload</text>
<!-- summary pills -->
<rect class="pill" x="380" y="552" width="360" height="36" rx="18"/>
<text class="t pilltxt" x="560" y="575" text-anchor="middle">2 procesy działają bez przerwy, także nocą</text>
<rect class="pill" x="770" y="552" width="360" height="36" rx="18"/>
<text class="t pilltxt" x="950" y="575" text-anchor="middle">aktualizacje bezpieczeństwa = stały obowiązek</text>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

+111
View File
@@ -0,0 +1,111 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 640" role="img" aria-label="Architektura demo na maszynie: redaktor i czytelnik łączą się z kontenerami podmana — Gitea, build Astro, wolumen releases i nginx">
<defs>
<marker id="arr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0 0 L10 5 L0 10 z" fill="#00004a"/>
</marker>
<filter id="soft" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="2" stdDeviation="5" flood-color="#00004a" flood-opacity="0.10"/>
</filter>
<style>
.t { font-family: Inter, "Segoe UI", Arial, sans-serif; }
.box { fill: #ffffff; stroke: #00004a; stroke-width: 1.5; }
.sub { fill: #f5f5f5; stroke: #e2e5ee; stroke-width: 1; }
.ttl { font-weight: 700; font-size: 15.5px; fill: #00004a; letter-spacing: .04em; }
.cap { font-size: 12.5px; fill: #46536a; }
.lbl { font-size: 12px; fill: #46536a; paint-order: stroke; stroke: #ffffff; stroke-width: 4; }
.chipbg { fill: #d2faf7; }
.chip { font-size: 11.5px; font-weight: 700; fill: #00004a; letter-spacing: .06em; }
.flow { stroke: #00004a; stroke-width: 1.8; fill: none; marker-end: url(#arr); }
.mono { font-family: "SF Mono", Consolas, Menlo, monospace; font-size: 12.5px; fill: #101228; }
</style>
</defs>
<!-- machine boundary -->
<rect x="340" y="40" width="830" height="570" rx="18" fill="#f8f9fc" stroke="#e2e5ee" stroke-width="1.5"/>
<text class="t" x="366" y="76" font-size="13" font-weight="700" fill="#00004a" letter-spacing=".18em">MASZYNA DEMO · PODMAN</text>
<text class="t cap" x="366" y="96">osobne kontenery pod systemd · wszystko w jednym katalogu użytkownika</text>
<!-- actor: redaktor -->
<g filter="url(#soft)">
<rect class="box" x="52" y="120" width="220" height="120" rx="16"/>
</g>
<circle cx="88" cy="156" r="11" fill="none" stroke="#00004a" stroke-width="1.8"/>
<path d="M72 186 q16 -18 32 0" fill="none" stroke="#00004a" stroke-width="1.8"/>
<text class="t ttl" x="116" y="162">Redaktor</text>
<text class="t cap" x="70" y="212">przeglądarka · <tspan font-weight="600">/admin/</tspan> (Sveltia)</text>
<text class="t cap" x="70" y="228">edycja WYSIWYG, podgląd 1:1</text>
<!-- actor: czytelnik -->
<g filter="url(#soft)">
<rect class="box" x="52" y="400" width="220" height="120" rx="16"/>
</g>
<circle cx="88" cy="436" r="11" fill="none" stroke="#00004a" stroke-width="1.8"/>
<path d="M72 466 q16 -18 32 0" fill="none" stroke="#00004a" stroke-width="1.8"/>
<text class="t ttl" x="116" y="442">Czytelnik</text>
<text class="t cap" x="70" y="492">publiczna strona portalu</text>
<text class="t cap" x="70" y="508">czysty, szybki HTML</text>
<!-- gitea -->
<g filter="url(#soft)">
<rect class="box" x="400" y="120" width="320" height="185" rx="16"/>
</g>
<text class="t ttl" x="424" y="152">GITEA</text>
<rect class="chipbg" x="490" y="136" width="52" height="22" rx="11"/>
<text class="t chip" x="516" y="151" text-anchor="middle">:3000</text>
<rect class="sub" x="420" y="172" width="280" height="112" rx="12"/>
<text class="t" x="436" y="198" font-size="13.5" font-weight="700" fill="#101228">repo ctao/portal</text>
<text class="t cap" x="436" y="220">treść: Markdown + obrazy (WebP)</text>
<text class="t cap" x="436" y="238">każda zmiana = commit</text>
<text class="t cap" x="436" y="256">pełna historia + rollback za darmo</text>
<!-- build (ephemeral) -->
<g filter="url(#soft)">
<rect x="790" y="120" width="340" height="185" rx="16" fill="#ffffff" stroke="#00004a" stroke-width="1.5" stroke-dasharray="7 5"/>
</g>
<text class="t ttl" x="814" y="152">BUILD · ASTRO</text>
<rect class="chipbg" x="948" y="136" width="164" height="22" rx="11"/>
<text class="t chip" x="1030" y="151" text-anchor="middle">KONTENER JEDNORAZOWY</text>
<text class="t cap" x="814" y="188">timer systemd (co ~10 s) sprawdza,</text>
<text class="t cap" x="814" y="206">czy w repo jest nowy commit</text>
<text class="t cap" x="814" y="234">jeśli tak: git fetch → checkout →</text>
<text class="t cap" x="814" y="252"><tspan font-weight="700" fill="#101228">Markdown → statyczny HTML</tspan></text>
<text class="t cap" x="814" y="280">po buildzie kontener znika</text>
<!-- releases volume -->
<g filter="url(#soft)">
<rect class="sub" x="790" y="400" width="340" height="150" rx="16"/>
</g>
<text class="t ttl" x="814" y="432">releases/</text>
<rect class="chipbg" x="908" y="416" width="82" height="22" rx="11"/>
<text class="t chip" x="949" y="431" text-anchor="middle">WOLUMEN</text>
<text class="t mono" x="814" y="466">9f2c1d/ poprzednia wersja</text>
<text class="t mono" x="814" y="488">71b980/ <tspan font-weight="700" fill="#00004a">← current (symlink)</tspan></text>
<text class="t cap" x="814" y="518">podmiana atomowa — zero przestoju</text>
<!-- nginx -->
<g filter="url(#soft)">
<rect class="box" x="400" y="400" width="320" height="150" rx="16"/>
</g>
<text class="t ttl" x="424" y="432">NGINX</text>
<rect class="chipbg" x="500" y="416" width="52" height="22" rx="11"/>
<text class="t chip" x="526" y="431" text-anchor="middle">:8080</text>
<text class="t cap" x="424" y="466">serwuje <tspan font-weight="600">releases/current</tspan></text>
<text class="t cap" x="424" y="484">wyłącznie statyczne pliki</text>
<text class="t cap" x="424" y="502">brak aplikacji · brak bazy · brak PHP</text>
<!-- flows -->
<path class="flow" d="M272 180 H396"/>
<text class="t lbl" x="334" y="170" text-anchor="middle">zapis przez API (OAuth)</text>
<path class="flow" d="M790 212 H724"/>
<text class="t lbl" x="757" y="202" text-anchor="middle">git fetch</text>
<path class="flow" d="M960 305 V396"/>
<text class="t lbl" x="968" y="354" text-anchor="start">nowa wersja strony</text>
<path class="flow" d="M790 475 H724"/>
<text class="t lbl" x="757" y="465" text-anchor="middle">current/</text>
<path class="flow" d="M400 475 H276"/>
<text class="t lbl" x="338" y="465" text-anchor="middle">statyczny HTML</text>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

Some files were not shown because too many files have changed in this diff Show More