From dc9baa07cf28156c07566c50b370a84433ce17d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Sun, 6 Sep 2026 08:11:41 +0200 Subject: [PATCH 1/3] =?UTF-8?q?fix(wydruk):=20front-end=20=E2=80=94=20bia?= =?UTF-8?q?=C5=82e=20t=C5=82o,=20ukryte=20chrome,=20skala=20dokumentu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Przepisany `_print.scss`. Trzy problemy, każdy z inną przyczyną: 1. TŁO. Foundation robi `* { background: transparent !important }`, ale selektor `*` przegrywa specyficznością z `html { background-color: #e6e6e6 !important }` z `_layout.scss` (0,0,1 vs 0,0,0) i w ogóle nie matchuje pseudo-elementów, więc gradient z `.gradient-background::before` — na dodatek `position: fixed` — szedł na papier jako szara plama. 2. ROZMIARY. Cały projekt wymiarowany jest w `rem`, a przeglądarka nie przelicza rem-ów na jednostki papierowe: `h1` Foundation (3rem) wychodził ~33 pt, a na stronie autora 24 pt. Teraz rem-base jest zakotwiczony w `pt` (deterministyczny wydruk niezależny od domyślnej czcionki przeglądarki), a tekst i nagłówki mają jawne rozmiary. `!important` na nagłówkach jest konieczne — komponenty mają własne, bardziej specyficzne reguły w rem. Usunięty też `div { font-size: 9pt }` z poprzedniej wersji: rozmiar wpisany na KAŻDY div zabija dziedziczenie. 3. CHROME. Ukryte: sticky-nav, title-bar, okruszki, stopka, menu boczne, baner cookies, baner serwera testowego, komunikaty flash oraz widgety wstrzykiwane JS-em (UserWay, Freshworks), których nie da się oznaczyć klasą w szablonie. Poza tym: - `.hide-on-print` NIGDY nie miała definicji w CSS (0 wystąpień w każdym skompilowanym arkuszu). Polegały na niej cztery szablony BPP ORAZ zewnętrzny `django-countdown`, więc definiujemy klasę zamiast przepisywać szablony na `hide-for-print` — pakietu z venv i tak nie poprawimy. - `@import "print"` przeniesiony z `common.scss` na koniec każdego `app-*.scss`: `common` wchodzi PRZED `foundation-includes`, więc dotąd nasz blok `@media print` stał w kaskadzie przed blokiem Foundation. - `@page` z django-constance (WYDRUK_MARGINES_*) w `bare.html` przeniesione ZA arkusze stylów. `@page` nie ma specyficzności ani `!important` — decyduje wyłącznie kolejność, a Foundation emituje własne `@page { margin: .5cm }`, które dotąd po cichu wygrywało. Ustawienia marginesów uczelni nie miały żadnego efektu. - Usunięty `div.grid-x { display: block }`: przy `display: block` komórki tracą kontekst flex, ale ZACHOWUJĄ `max-width` z klas `medium-*`, więc kafelki strony głównej drukowały się jako wąski słupek na kilka stron. Zweryfikowane realnym wydrukiem (headless Chrome, A4, dump produkcyjny): strona główna 7 -> 4 strony. Po wycięciu z obu buildów wszystkich bloków `@media print` reszta CSS-u jest bajt w bajt identyczna, więc wygląd na ekranie nie mógł się zmienić. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01YDWJPTz55i4uM2A9pFM4Qn --- src/bpp/static/scss/_print.scss | 348 ++++++++++++++++++++++++---- src/bpp/static/scss/app-blue.scss | 4 + src/bpp/static/scss/app-green.scss | 4 + src/bpp/static/scss/app-mwsl.scss | 4 + src/bpp/static/scss/app-orange.scss | 4 + src/bpp/static/scss/app-uafm.scss | 4 + src/bpp/static/scss/app-vizja.scss | 4 + src/bpp/static/scss/common.scss | 5 +- src/django_bpp/templates/bare.html | 23 +- 9 files changed, 345 insertions(+), 55 deletions(-) diff --git a/src/bpp/static/scss/_print.scss b/src/bpp/static/scss/_print.scss index 6b90b72a6..6dbbfef66 100644 --- a/src/bpp/static/scss/_print.scss +++ b/src/bpp/static/scss/_print.scss @@ -1,21 +1,113 @@ // ============================================================================= -// PRINT STYLES -// All @media print rules consolidated in one place -// Marginesy strony (@page) są teraz generowane dynamicznie przez szablon bare.html -// na podstawie ustawień django-constance (WYDRUK_MARGINES_*) +// PRINT STYLES (front-end) +// Wszystkie reguły @media print dla serwisu publicznego w jednym miejscu. +// +// Marginesy strony (@page) generuje dynamicznie szablon bare.html na +// podstawie ustawień django-constance (WYDRUK_MARGINES_*) — NIE dodawaj +// tu @page, bo nadpisze ustawienia uczelni. +// +// Odpowiednik dla panelu admina: src/bpp/static/bpp/scss/admin-print.scss +// (osobny plik, bo admin-themes.css jest linkowany z media="screen"). +// +// KOLEJNOŚĆ: ten plik jest importowany na KOŃCU każdego app-*.scss, już za +// `@import "foundation-includes"` — celowo NIE z common.scss, bo common +// wchodzi PRZED Foundation. Foundation emituje własny blok @media print +// (foundation-print-styles) z m.in. `* { background: transparent !important; +// color: black !important }` i `a[href]:after { content: ' (' attr(href) ')' }`; +// żeby móc je nadpisać bez eskalacji !important, musimy być w kaskadzie za nim. // ============================================================================= @media print { - // Hide skip-link in print (accessibility element not needed in print) - .skip-link { + + // =========================================================================== + // 1. SKALA TYPOGRAFII + // =========================================================================== + // + // Źródło problemu „wszystko drukuje się ogromne": rozmiary w całym projekcie + // są w rem, a _settings.scss ustawia $global-font-size: 92%. Nagłówek h1 + // Foundation to 3rem — na kartce ~33pt, wielkość plakatu. Przy druku + // przeglądarka NIE przelicza rem-ów na jednostki papierowe. + // + // Dwa niezależne pokrętła, oba potrzebne: + // + // 1. rem-base (`html`). Ustawiamy go w pt, żeby wydruk był DETERMINISTYCZNY: + // `92%` jest liczone od domyślnego rozmiaru czcionki przeglądarki, który + // czytelnik może mieć podkręcony (np. na 20px dla czytelności) — wtedy + // wydruk puchnie razem z ekranem. Wartość niżej jest też świadomie + // MNIEJSZA niż ekranowy odpowiednik (0.92 × 16px ≈ 11pt), bo ścieśnia + // paddingi i marginesy komponentów (one też są w rem), nie ruszając + // rozmiaru tekstu — ten ustawiamy jawnie w pt zaraz niżej. + // + // 2. Jawne rozmiary tekstu i nagłówków w pt. Mnożniki Foundation (2.5–3rem) + // są ekranowe i na papierze nie mają sensu przy żadnym rem-base. + html { + font-size: 9pt !important; + } + + body, + p { + // Rozmiar zwykłego dokumentu tekstowego, nie petit. + font-size: 10.5pt; + line-height: 1.35; + } + + // UWAGA: świadomie NIE ustawiamy tu font-size na `div`. Poprzednia wersja + // tego pliku miała `body, p, div { font-size: 9pt }` — wpisanie rozmiaru na + // KAŻDY div zabija dziedziczenie, więc zagnieżdżony kontekst (np. tabela w + // karcie) nie mógł już niczego zmniejszyć ani powiększyć względem rodzica. + // Sterowanie idzie przez rem-base wyżej. + + // !important jest tu konieczne, nie kosmetyczne: komponenty mają własne, + // BARDZIEJ SPECYFICZNE reguły nagłówków w rem (np. `.autor-header h1` + // z 2.2rem), które inaczej wygrywają z gołym `h1`. Wydruk autora wychodził + // przez to na 24pt. Arkusz druku ma normalizować skalę — stąd twarde + // pierwszeństwo. + h1 { font-size: 16pt !important; } + h2 { font-size: 14pt !important; } + h3 { font-size: 12.5pt !important; } + h4, + h5, + h6 { font-size: 11pt !important; } + + h1, h2, h3, h4, h5, h6 { + margin-top: 0.6em; + margin-bottom: 0.3em; + } + + small, + .small { + font-size: 9pt; + } + + // =========================================================================== + // 2. TŁO + // =========================================================================== + // + // Foundation robi `* { background: transparent !important }`, ale zostawia + // dwie dziury, przez które przechodzi szare tło serwisu: + // + // a) `_layout.scss` ma `html { background-color: #e6e6e6 !important }`. + // Oba selektory są !important, więc rozstrzyga specyficzność: + // `html` (0,0,1) bije `*` (0,0,0). + // b) gradient siedzi w `.gradient-background::before` — selektor `*` NIE + // matchuje pseudo-elementów, więc gradient przeżywa. Na dodatek jest + // `position: fixed`, czyli trafiłby na wydruk jako pełnostronicowa + // szara plama. + html, + body { + background: #fff !important; + background-color: #fff !important; + } + + .gradient-background::before { display: none !important; } - // Neutralize screen-only min-height:100vh rules. On screen they keep the - // footer pinned to the bottom of the viewport; in print 1vh == 1% of the - // PAGE box, so they force the content container to fill a whole page and - // push the footer onto a second, almost-blank page. See _layout.scss and - // _multiseek-base.scss for the source declarations. + // Neutralizacja reguł min-height:100vh przeznaczonych dla ekranu. Na ekranie + // trzymają stopkę przy dolnej krawędzi okna; przy druku 1vh to 1% wysokości + // PUDEŁKA STRONY, więc kontener treści rozpycha się na całą kartkę i spycha + // resztę na kolejną, prawie pustą. Źródła deklaracji: _layout.scss, + // _multiseek-base.scss. body, .gradient-background, .grid-container-fluid[data-sticky-container], @@ -23,24 +115,166 @@ min-height: 0 !important; } - // Basic typography - body, p, div { - font-size: 9pt; + // Kontener treści na ekranie jest białym prostokątem z cieniem na szarym + // tle; na papierze cień i ograniczona szerokość są zbędne. + .grid-container, + .grid-container-fluid { + max-width: none !important; + width: auto !important; + padding-left: 0 !important; + padding-right: 0 !important; + margin-left: 0 !important; + margin-right: 0 !important; + } + + // =========================================================================== + // 3. KLASY POMOCNICZE + // =========================================================================== + // + // `hide-on-print` to konwencja BPP. Nigdy nie miała definicji w CSS — była + // martwa w czterech szablonach (base.html ×2, test_server_detection.html, + // admin/base_site.html) ORAZ w zewnętrznym pakiecie django-countdown + // (countdown_banner.html), którego nie możemy edytować. Definiujemy ją tu + // zamiast przepisywać szablony na foundation-owe `hide-for-print`: pakiet + // zewnętrzny i tak zostałby przy starej nazwie. + .hide-on-print, + .hide-for-print { + display: none !important; + } + + // Element dostępnościowy, bez sensu na papierze. + .skip-link { + display: none !important; + } + + // =========================================================================== + // 4. CHROME SERWISU — nawigacja, stopka, banery, widgety + // =========================================================================== + + // Górny pasek nawigacji wraz ze sticky-wrapperem i mobilnym title-barem. + // Sam `.top-bar` ma w szablonie `hide-for-print`, ale opakowujący + // `nav.sticky-header` i `.title-bar` (hamburger) — nie. + nav.sticky-header, + .title-bar, + .top-bar, + .global-nav-container, + .global-search-modal, + .search-shortcut-banner, + .search-shortcut-hint { + display: none !important; + } + + // Okruszki, menu boczne, stopka. + #breadcrumbs-wrapper, + .left-menu-sidebar, + .footer, + .footer-container { + display: none !important; + } + + // Menu boczne rezerwuje miejsce na treść — po jego ukryciu odzyskujemy je. + .main-content-with-sidebar { + margin-left: 0 !important; + padding-left: 0 !important; + width: auto !important; + } + + // Baner cookies (nasz override `cookielaw/rejectable.html` ma już + // hide-for-print, ale wariant wbudowany w pakiet — #CookielawBanner — nie). + #CookielawBanner, + .cookie-pasek { + display: none !important; + } + + // Komunikaty flash, powiadomienia, przycisk supportu, banery serwera + // testowego, baner odliczania (django-countdown). + #messagesPlaceholder, + #messageTemplate, + .bpp-support-button, + .test-server-banner, + .test-server-watermark, + .countdown-banner, + .maintenance-banner { + display: none !important; + } + + // Widgety zewnętrzne wstrzykiwane przez JS: UserWay (dostępność WCAG) + // i Freshworks (zgłoszenia). Wstrzykiwane są do , więc nie da się + // ich oznaczyć klasą w szablonie — celujemy po ich własnych selektorach. + .uwy, + #userwayAccessibilityIcon, + #freshworks-container, + #freshworks-frame-wrapper, + #launcher-frame, + iframe[id^="freshworks"], + iframe[title*="Freshworks"] { + display: none !important; + } + + // =========================================================================== + // 5. ELEMENTY INTERAKTYWNE + // =========================================================================== + // + // Sterowanie, które na papierze jest tylko szumem. NIE ukrywamy tu + // formularzy jako całości — bywają widoki, gdzie wartości pól są treścią. + // Zasada podzialu: element