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
This commit is contained in:
@@ -44,5 +44,5 @@
|
||||
<path class="flow" d="M716 247 H772"/>
|
||||
<g filter="url(#soft3)"><rect class="good" x="776" y="210" width="360" height="76" rx="16"/></g>
|
||||
<text class="t item" x="804" y="242">▪ git (Gitea) — tylko zalogowani</text>
|
||||
<text class="t cap" x="804" y="268">docelowo za VPN, poza publicznym internetem</text>
|
||||
<text class="t cap" x="804" y="268">poza ścieżką czytelnika — awaria nie kładzie strony</text>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
@@ -11,8 +11,11 @@ draft: false
|
||||
|
||||
Ten portal to **statyczne pliki HTML** generowane z plików Markdown trzymanych
|
||||
w repozytorium git. Redaktor dostaje **graficzny edytor w przeglądarce** —
|
||||
a mimo to w produkcji nie działa żadna aplikacja, żadna baza danych.
|
||||
Ten artykuł został napisany i opublikowany dokładnie tą ścieżką.
|
||||
a mimo to **po stronie publicznej** nie działa żadna aplikacja ani baza:
|
||||
między czytelnikiem a treścią jest wyłącznie serwer plików. Jedyny działający
|
||||
serwis (Gitea) obsługuje edycję i stoi poza ścieżką czytelnika — wracamy do
|
||||
tego uczciwie niżej. Ten artykuł został napisany i opublikowany dokładnie tą
|
||||
ścieżką.
|
||||
|
||||
## Od zapisu do publikacji
|
||||
|
||||
@@ -62,14 +65,25 @@ i jednorazowy kontener builda odpalany przez systemd timer.
|
||||
|
||||
## Dwie klasy rozwiązań
|
||||
|
||||
| | Statyczne + git-CMS *(to demo)* | CMS z własną bazą |
|
||||
Linia podziału jest często mylona: nie chodzi o to, czy treść leży w bazie
|
||||
czy w plikach, tylko o to, **czy między czytelnikiem a treścią stoi
|
||||
działająca aplikacja**.
|
||||
|
||||
| | Statyczne + git-CMS *(to demo)* | CMS serwujący stronę aplikacją |
|
||||
|---|---|---|
|
||||
| Przykłady | Sveltia, Decap (+ Astro / Hugo / Eleventy) | Strapi, WordPress, Grav, TinaCMS |
|
||||
| Treść żyje w | plikach Markdown w gicie | bazie danych |
|
||||
| W produkcji działa | serwer plików | aplikacja + baza, non stop |
|
||||
| Utrzymanie | skrypt builda | patche, aktualizacje, backupy bazy |
|
||||
| Przykłady | Sveltia, Decap (+ Astro / Hugo / Eleventy) | Strapi, WordPress, Drupal, Grav, TinaCMS |
|
||||
| Treść żyje w | plikach Markdown w gicie | zwykle w bazie; Grav — w plikach, Tina — w gicie |
|
||||
| W produkcji działa | serwer plików | aplikacja (zwykle + baza), non stop |
|
||||
| Utrzymanie | skrypt builda + serwis gitowy | patche, aktualizacje, backupy bazy |
|
||||
| Historia zmian | git, wbudowana | zależnie od produktu (bywa płatna) |
|
||||
|
||||
Dwa doprecyzowania, żeby tabela nie upraszczała: **Grav** trzyma treść
|
||||
w plikach bez żadnej bazy — ale stronę i tak serwuje aplikacja PHP, więc pod
|
||||
względem powierzchni ataku i utrzymania należy do prawej kolumny. **TinaCMS**
|
||||
trzyma treść wręcz w Markdownie w gicie, ale do działania edytora wymaga
|
||||
stale działającego backendu z bazą — jest hybrydą, która dziedziczy koszty
|
||||
obu światów.
|
||||
|
||||
Ograniczenia poszczególnych rozwiązań, które zweryfikowaliśmy podczas
|
||||
researchu:
|
||||
|
||||
@@ -104,9 +118,31 @@ konkretnych produktów.
|
||||
|
||||
| | Statyczne + git | CMS z bazą |
|
||||
|---|---|---|
|
||||
| Plusy | brak publicznie działającej aplikacji = brak presji łatek bezpieczeństwa; koszt utrzymania bliski zera; treść w otwartym formacie przetrwa każdą wymianę narzędzi | jeśli z czasem będą potrzebne funkcje aplikacyjne (konta, personalizacja, treści silnie strukturalne), platforma już je ma |
|
||||
| Plusy | strona publiczna bez aplikacji = brak presji łatek na ścieżce czytelnika; do utrzymania zostaje serwis gitowy (najlepiej ten, który zespół i tak ma); treść w otwartym formacie przetrwa każdą wymianę narzędzi | jeśli z czasem będą potrzebne funkcje aplikacyjne (konta, personalizacja, treści silnie strukturalne), platforma już je ma |
|
||||
| Minusy | przy bardzo dużej skali (tysiące stron) buildy rosną i wymagają optymalizacji; zaawansowane funkcje redakcyjne zależą od tempa rozwoju narzędzi open-source | kilka dużych migracji wersji w 6 lat (zmiany łamiące); baza wymaga backupów i testów odtwarzania; stała powierzchnia ataku; ryzyko przenoszenia funkcji do płatnych planów |
|
||||
|
||||
## „Czy Gitea to nie jest po prostu drugi CMS?"
|
||||
|
||||
Uczciwe pytanie: Gitea też ma logowanie, bazę i wymaga aktualizacji
|
||||
bezpieczeństwa, a dla edytorów musi być dostępna spoza VPN. Czy różnica nie
|
||||
sprowadza się więc do tego, że zamiast CMS-a utrzymujemy Giteę?
|
||||
|
||||
Częściowo tak — i dlatego nie twierdzimy, że utrzymanie jest zerowe. Różnica
|
||||
polega na trzech rzeczach:
|
||||
|
||||
- **Gitea stoi poza ścieżką czytelnika.** Może mieć przerwę, aktualizację,
|
||||
a nawet zostać przejęta — portal dalej stoi i serwuje bezpieczne pliki.
|
||||
Najgorszy scenariusz włamania to wandalizm treści: widoczny, z pełną
|
||||
historią, odwracalny jednym revertem. W CMS-ie serwującym stronę ten sam
|
||||
incydent oznacza przejętą stronę publiczną.
|
||||
- **Zespół już utrzymuje Giteę.** Docelowo repozytorium treści może żyć na
|
||||
istniejącej instancji zespołu — wtedy krańcowy koszt utrzymania jest
|
||||
bliski zera, a logowanie załatwia jedna integracja z AAI.
|
||||
- **Klasa oprogramowania.** Gitea to pojedynczy program w Go z SQLite:
|
||||
aktualizacja to podmiana obrazu i restart, bez ekosystemu pluginów i bez
|
||||
drzewa zależności npm po stronie serwera. Platformy CMS to frameworki
|
||||
aplikacyjne z regularnymi migracjami głównych wersji.
|
||||
|
||||
## Gdzie jeszcze może się przydać
|
||||
|
||||
Ten sam wzorzec — edytor graficzny nad repozytorium Markdown — nie jest
|
||||
|
||||
+14
-14
@@ -193,7 +193,9 @@ img { max-width: 100%; height: auto; display: block; }
|
||||
/* Header — Galaxy Blue band, reverse (white) logo per BRAND B.1.2; translucent
|
||||
over content where backdrop blur is supported (AA computed: worst underlying
|
||||
white → bar (31,31,96), muted nav text 9.4:1). Solid Galaxy fallback. */
|
||||
.site-header { background: var(--galaxy); position: sticky; top: 0; z-index: 10; }
|
||||
/* Lifted shadow separates the bar (and the mobile dropdown panels
|
||||
underneath it) from content — the header floats above the page. */
|
||||
.site-header { background: var(--galaxy); position: sticky; top: 0; z-index: 10; box-shadow: var(--shadow-lifted); }
|
||||
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
|
||||
.site-header {
|
||||
background: color-mix(in srgb, var(--galaxy) 88%, transparent);
|
||||
@@ -225,14 +227,15 @@ img { max-width: 100%; height: auto; display: block; }
|
||||
}
|
||||
/* Underline-grow (the current nav-hover convention): a 2px rule scales in
|
||||
from the left on hover and stays put on the active page. Transform-only;
|
||||
its transition lives in the no-preference block like every other motion. */
|
||||
.nav a.navlink::after {
|
||||
its transition lives in the no-preference block like every other motion.
|
||||
::before by codebase convention — ::after belongs to the external arrow. */
|
||||
.nav a.navlink::before {
|
||||
content: ""; position: absolute; left: 10px; right: 10px; bottom: 4px;
|
||||
height: 2px; border-radius: 1px; background: #fff;
|
||||
transform: scaleX(0); transform-origin: 0 50%;
|
||||
}
|
||||
.nav a.navlink:hover { color: #fff; }
|
||||
.nav a.navlink:hover::after, .nav a.navlink[aria-current='page']::after { transform: scaleX(1); }
|
||||
.nav a.navlink:hover::before, .nav a.navlink[aria-current='page']::before { transform: scaleX(1); }
|
||||
.nav a.navlink[aria-current='page'] { color: #fff; }
|
||||
/* AAI sign-in entry — ghost/outline: Cherenkov fill is reserved for the page's
|
||||
primary CTA, the header only whispers. */
|
||||
@@ -802,7 +805,7 @@ mark { background: var(--tint-cherenkov); color: inherit; border-radius: calc(va
|
||||
.card:hover, .search-results li:hover { transform: translateY(-2px); } /* the one elevate gesture (no cover zoom) */
|
||||
|
||||
/* Nav underline-grow + press feedback (micro-interaction pair) */
|
||||
.nav a.navlink::after { transition: transform var(--dur) var(--ease); }
|
||||
.nav a.navlink::before { transition: transform var(--dur) var(--ease); }
|
||||
.btn:active, .nav a.navlink:active { transform: translateY(1px); }
|
||||
|
||||
/* === Scroll-driven effects (CSS only, compositor-only, no JS) ===
|
||||
@@ -811,17 +814,14 @@ mark { background: var(--tint-cherenkov); color: inherit; border-radius: calc(va
|
||||
@supports (animation-timeline: view()) {
|
||||
/* Cards drift in as they enter the viewport; items already on screen
|
||||
start past the entry range, so nothing flashes at load. */
|
||||
.card { animation: card-in linear both; animation-timeline: view(); animation-range: entry 0% entry 35%; }
|
||||
/* Header casts its shadow only once the page is actually scrolled. */
|
||||
.site-header::before {
|
||||
content: ""; position: absolute; inset: 0; z-index: -1;
|
||||
box-shadow: var(--shadow-lifted); opacity: 0;
|
||||
animation: fade-in linear both; animation-timeline: scroll(); animation-range: 0 80px;
|
||||
}
|
||||
/* Range tuned on a real trackpad: entry 0–35% completed within a flick
|
||||
and read as "no effect"; 0–75% keeps the drift visible at normal
|
||||
scrolling speed while still finishing before the card is fully in. */
|
||||
.card { animation: card-in linear both; animation-timeline: view(); animation-range: entry 0% entry 75%; }
|
||||
/* Article reading progress — functional state, same Cherenkov budget line
|
||||
as the TOC scrollspy rule (navigation feedback, not decoration). */
|
||||
.read-progress {
|
||||
position: fixed; top: 56px; left: 0; width: 100%; height: 2px; z-index: 9;
|
||||
position: fixed; top: 56px; left: 0; width: 100%; height: 3px; z-index: 9;
|
||||
background: var(--cherenkov); transform-origin: 0 50%;
|
||||
animation: progress linear both; animation-timeline: scroll(root);
|
||||
}
|
||||
@@ -840,6 +840,6 @@ mark { background: var(--tint-cherenkov); color: inherit; border-radius: calc(va
|
||||
to { transform: translate3d(5%, 5%, 0) rotate(10deg) scale(1.16); opacity: 1; }
|
||||
}
|
||||
@keyframes rise { from { opacity: 0; transform: translateY(14px); } }
|
||||
@keyframes card-in { from { opacity: 0; transform: translateY(14px); } }
|
||||
@keyframes card-in { from { opacity: 0; transform: translateY(28px); } }
|
||||
@keyframes fade-in { from { opacity: 0; } }
|
||||
@keyframes progress { from { transform: scaleX(0); } to { transform: scaleX(1); } }
|
||||
|
||||
Reference in New Issue
Block a user