feat(prezentacja): kliknięcie powiększa kosmogram na pełne okno (PRE-25) #29

Merged
gitea merged 1 commits from feat/qol-wheel-zoom into master 2026-07-24 16:48:41 +00:00
5 changed files with 145 additions and 0 deletions
Binary file not shown.
@@ -50,6 +50,24 @@ td.glyph { color: var(--accent); white-space: nowrap; }
.wheel-fig figcaption { margin-top: .4rem; }
/* glify wewnątrz SVG dziedziczą monochromatyczny stack fontów (DAN-18) */
svg.wheel text.glyph { font-size: inherit; }
/* Powiększenie na pełne okno (PRE-25). Kliknięcie rozciąga wykres na cały ekran,
ponowne wraca. z-index 1100 CELOWO pomiędzy: ponad kontrolki Leafleta (1000),
ale PONIŻEJ okna postępu (1200) — gdy trwa pisanie horoskopu, log operacji ma
zostać na wierzchu. */
.wheel-fig svg.wheel { cursor: zoom-in; }
.wheel-fig svg.wheel:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
.wheel-fig.zoomed {
position: fixed; inset: 0; z-index: 1100; margin: 0;
display: flex; flex-direction: column; align-items: center; justify-content: center;
gap: .5rem; padding: 1rem; background: var(--bg);
}
/* min() po obu wymiarach: koło jest kwadratowe, więc ograniczamy je krótszym
bokiem okna — inaczej na szerokim ekranie wystawałoby w pionie. */
.wheel-fig.zoomed svg.wheel {
max-width: min(96vw, 92vh); width: min(96vw, 92vh); height: auto; cursor: zoom-out;
}
body.wheel-zoom-open { overflow: hidden; } /* nie przewijaj treści pod spodem */
form { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 1.25rem; }
.row { display: flex; gap: .5rem; }
.row input[type=text] { flex: 1; }
@@ -0,0 +1,60 @@
// Powiększanie kosmogramu na pełne okno (PRE-25).
//
// Koło rysujemy w kolumnie tekstu, więc na małym ekranie glify i stopnie robią
// się nieczytelne. Kliknięcie rozciąga wykres na całe okno, ponowne wraca do
// strony. SVG skaluje się bez utraty jakości, więc nie potrzeba drugiej wersji
// rysunku ani żadnej biblioteki.
//
// Podpięte globalnie i osłonięte sprawdzeniem, czy koło w ogóle jest na stronie
// — dzięki temu zadziała też na przyszłej zakładce „Skompiluj", bez zmian tutaj.
(function () {
'use strict';
var ZOOMED = 'zoomed';
function ready(fn) {
if (document.readyState !== 'loading') fn();
else document.addEventListener('DOMContentLoaded', fn);
}
ready(function () {
var figures = document.querySelectorAll('.wheel-fig');
if (!figures.length) return;
Array.prototype.forEach.call(figures, function (fig) {
var svg = fig.querySelector('svg.wheel');
if (!svg) return;
// Klawiatura ma działać tak samo jak mysz — to nie jest ozdobnik, tylko
// jedyny sposób na obejrzenie szczegółów wykresu.
svg.setAttribute('tabindex', '0');
svg.setAttribute('role', 'button');
svg.setAttribute('aria-label', 'Kosmogram — kliknij, aby powiększyć na całe okno');
function toggle() {
var on = fig.classList.toggle(ZOOMED);
// Blokujemy przewijanie strony pod spodem: inaczej scroll „przecieka"
// na treść, której i tak nie widać.
document.body.classList.toggle('wheel-zoom-open', on);
svg.setAttribute('aria-label', on
? 'Kosmogram powiększony — kliknij lub naciśnij Esc, aby wrócić'
: 'Kosmogram — kliknij, aby powiększyć na całe okno');
if (on) svg.focus();
}
svg.addEventListener('click', toggle);
svg.addEventListener('keydown', function (e) {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggle(); }
});
// Klik w tło (poza samym rysunkiem) też zamyka — naturalne dla nakładki.
fig.addEventListener('click', function (e) {
if (e.target === fig && fig.classList.contains(ZOOMED)) toggle();
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && fig.classList.contains(ZOOMED)) toggle();
});
});
});
})();
@@ -10,6 +10,9 @@
odtworzyć współrzędne, ZANIM geo.js zbuduje mapę — mapa startuje od razu
we właściwym miejscu, bez przestawiania. #}
<script defer src="/static/formsync.js"></script>
{# Powiększanie kosmogramu (PRE-25) — globalnie, bo koło pojawi się też na
zakładce „Skompiluj"; skrypt sam sprawdza, czy jest co powiększać. #}
<script defer src="/static/wheelzoom.js"></script>
</head>
<body>
<main>
@@ -0,0 +1,64 @@
"""Powiększanie kosmogramu na pełne okno (PRE-25).
Test strukturalny, jak przy pozostałych rzeczach żyjących w JS/CSS. Osobno
pilnujemy warstwy nakładki: to jedyna liczba w tym pliku, którą łatwo zmienić
bez zastanowienia i zepsuć coś, czego się nie widzi na pierwszy rzut oka.
"""
import pathlib
import re
APP = pathlib.Path(__file__).resolve().parents[1] / "app"
JS = (APP / "static" / "wheelzoom.js").read_text(encoding="utf-8")
CSS = (APP / "static" / "styles.css").read_text(encoding="utf-8")
BASE = (APP / "templates" / "base.html").read_text(encoding="utf-8")
def test_script_is_loaded_globally():
"""Ładowane w base.html, bo koło pojawi się też na zakładce Skompiluj."""
assert "wheelzoom.js" in BASE
def test_toggles_on_click():
assert "addEventListener('click'" in JS
assert "classList.toggle" in JS
def test_closes_with_escape():
assert "'Escape'" in JS
def test_reachable_from_keyboard():
"""Bez tego jedyny sposób na obejrzenie szczegółów wykresu wymagałby myszy."""
assert "tabindex" in JS
assert "'Enter'" in JS
def test_page_scroll_is_locked_while_zoomed():
"""Inaczej przewijanie „przecieka" na treść pod spodem, której i tak nie widać."""
assert "wheel-zoom-open" in JS
assert re.search(r"body\.wheel-zoom-open\s*\{[^}]*overflow:\s*hidden", CSS)
def test_zoomed_layer_sits_above_map_but_below_progress():
"""Warstwy: kontrolki Leafleta 1000 < kosmogram < okno postępu 1200.
Gdy trwa pisanie horoskopu, log operacji ma zostać na wierzchu."""
zoom = re.search(r"\.wheel-fig\.zoomed\s*\{[^}]*z-index:\s*(\d+)", CSS)
progress = re.search(r"\.progress-overlay\s*\{[^}]*z-index:\s*(\d+)", CSS)
assert zoom and progress, "nie znaleziono warstw w CSS"
assert 1000 < int(zoom.group(1)) < int(progress.group(1))
def test_zoomed_wheel_is_bounded_by_shorter_window_side():
"""Koło jest kwadratowe — ograniczamy je krótszym bokiem okna, inaczej na
szerokim ekranie wystawałoby w pionie."""
block = re.search(r"\.wheel-fig\.zoomed svg\.wheel\s*\{[^}]*\}", CSS)
assert block and "vh" in block.group(0) and "vw" in block.group(0)
def test_cursor_signals_the_affordance():
assert "zoom-in" in CSS and "zoom-out" in CSS
def test_does_nothing_without_a_wheel():
"""Na zakładkach bez kosmogramu skrypt ma po prostu wyjść."""
assert "if (!figures.length) return;" in JS