Dialog jako jedna vrstva, hlavicka bez rozmazani, znacka Zive odstranena

Otevreny dialog sekal video vedle portalu. Posledni rozmazana vrstva na
dashboardu byla lepici hlavicka (backdrop-blur-xl); pod prekryvem dialogu
ji prohlizec prepocitaval pri kazdem prekresleni (blikajici kurzor,
kolecko obnovy dat). Hlavicka ma plnou barvu, dialog je jeden fixed prekryv
s vycentrovanym panelem. Tecka a znacka Zive odstraneny z dashboardu
i z ukazkoveho panelu na webu, duplicitni blok prefers-reduced-motion
v index.css sloucen. Dokumentace 05, 22, 99.
This commit is contained in:
JiriUhlir
2026-09-09 20:55:13 +02:00
parent ea9387bea9
commit 5d41600578
10 changed files with 73 additions and 100 deletions
+3 -2
View File
@@ -20,8 +20,9 @@ V postrannim menu je pod Nastavenim tlacitko **Simulace**.
Portal drzi jedno SSE spojeni pro celou aplikaci. Zajistuje ho
`EventStreamProvider` v `web/src/components/dashboard/`.
- Stav spojeni ukazuje `LiveIndicator` v horni liste. Uzivatel musi poznat,
ze data nejsou ziva.
- Stav spojeni se v liste neukazuje. Ukazatel "Zive" s teckou byl
odstranen (rozhodnuti zadavatele); vypadek spojeni se pozna podle toho,
ze se prestanou objevovat bubliny udalosti.
- Prichozi udalosti ukazuje `EventToasts` jako bubliny vpravo dole.
- Data se obnovuji sama. `useApiQuery` ma volitelny `refetchOn` se seznamem typu
udalosti, po kterych se ma dotaz zopakovat. Vice udalosti tesne po sobe se
+13 -13
View File
@@ -193,22 +193,22 @@ karta si za to drzela vlastni rozmazanou vrstvu ve skladaci. Pri dvaceti
kartach na obrazovce to vytizilo GPU natolik, ze vedle portalu sekalo
prehravani videa.
Rozmazani patri jen tam, kde pod nim neco opravdu projizdi:
Na dashboardu **nema `backdrop-filter` nic**, ani lepici hlavicka. Rozmazani
zustalo jen na verejnem webu, kde neni zadny stream ani dialog:
| Kde | Trida |
| -------------------------- | ----------------- |
| lepici hlavicka dashboardu | `backdrop-blur-xl` |
| lepici hlavicka webu | `backdrop-blur-xl` |
| Kde | Trida |
| ----------------------------- | ------------------ |
| lepici hlavicka webu | `backdrop-blur-xl` |
| prekryv menu na verejnem webu | `backdrop-blur-xl` |
Podklad modalu a prekryv menu v portalu rozmazani **nemaji**: je to vrstva
pres celou obrazovku a pod ni lezi hlavicka, ktera rozmazana je. Kazda
animace pod dialogem (pulz tecky "Zive", spinner, obnova seznamu) pak
nutila prohlizec prepocitat rozmazani cele plochy, jeste pres to druhe -
a vedle portalu sekalo video pri kazdem otevrenem dialogu. Tmavy prekryv
s vyssim krytim (`bg-ink-950/85`) dela tutez praci zadarmo. Ze stejneho
duvodu tecka "Zive" nepulzuje: pulz ma jen stav, kdy se spojeni teprve
navazuje nebo obnovuje.
Duvod je dialog. Jeho prekryv (`bg-ink-950/85`) lezi pres cely viewport,
tedy i pres hlavicku. Rozmazana vrstva pod prekryvem se prepocitava pri
kazdem prekresleni pod nim, a v otevrenem dialogu se prekresluje porad:
blika kurzor ve fokusovanem poli, toci se kolecko obnovy dat, prichazi
bublina udalosti. Bez dialogu se hlavicka neprekresluje a rozmazani nic
nestoji, s dialogem bezelo cely cas a vedle portalu sekalo video. Plna barva
hlavicky (`bg-ink-900`) vypada pod tmavym prekryvem stejne a nestoji nic.
Tecka a znacka "Zive" v hlavicce byly odstraneny uplne.
**Zadna nekonecna animace nad velkou rozmazanou plochou.** Zare na prihlaseni
a na webu jsou staticke: animovat pruhlednost objektu s `blur(120px)` znamena
+23
View File
@@ -64,6 +64,29 @@ konektoru a provoznich dat, audit). V `tests/setup.ts` je `SEED_FILE`
prazdny, aby testy v pameti nezavisely na obsahu repa. Nove `scripts/**/*.mjs`
v eslintu jako Node ESM, `SEED_FILE` v `.env.example`.
## 2026-09-09 - Otevreny dialog sekal video (potreti): hlavicka bez rozmazani
Po dvou predchozich opravach (`.glass` a podklad dialogu bez rozmazani)
sekani trvalo, a to na kazdem dialogu a jen kdyz je otevreny. Spolecny
jmenovatel vsech dialogu je `Modal.tsx`: portal do `document.body`, prekryv
`fixed inset-0` pres cely viewport, zamek scrollu a focus do prvniho pole.
Sam o sobe nic trvale nedela. Co zmeni pro zbytek stranky: prekryv lezi
nad lepici hlavickou dashboardu, ktera mela jako posledni `backdrop-blur-xl`.
Rozmazana vrstva pod prekryvem se prepocitava pri kazdem prekresleni pod
nim, a v otevrenem dialogu se prekresluje porad (blikajici kurzor ve
fokusovanem poli, kolecko obnovy dat po udalosti ze streamu, bublina
udalosti). Bez dialogu se hlavicka neprekresluje a rozmazani nic nestoji.
Hlavicka dashboardu ma plnou barvu (`bg-ink-900`) bez `backdrop-blur`
(`DashboardLayout.tsx`). Dialog je jedna vrstva: prekryv `fixed inset-0`
s plnou vyskou a sirkou obrazovky a v nem vycentrovany panel (`m-auto`);
driv byl obal s posuvnikem a pod nim zvlast druhy fixed prvek s pozadim. Tecka a znacka "Zive" jsou odstraneny uplne, na
dashboardu i v ukazkovem panelu na webu (`LiveIndicator.tsx` smazan,
`Hero.tsx`, klic `panel.live` z prekladu). V `index.css` byl blok
`prefers-reduced-motion` dvakrat, zustal jeden. Rozmazani ma uz jen
hlavicka a menu verejneho webu, viz
[22-znacka-a-design.md](22-znacka-a-design.md).
## 2026-09-09 - Otevreny dialog sekal video (podruhe)
Po predchozi oprave (`.glass` bez rozmazani) zustalo rozmazani na podkladu
@@ -24,7 +24,6 @@ import { ErrorBoundary } from '@/components/ErrorBoundary';
import { useAuth, type AuthUser } from '@/auth/AuthContext';
import { EventStreamProvider } from '@/components/dashboard/EventStreamProvider';
import { EventToasts } from '@/components/dashboard/EventToasts';
import { LiveIndicator } from '@/components/dashboard/LiveIndicator';
import { Logo } from '@/components/layout/Logo';
import { cn } from '@/lib/cn';
import { CollectionsProvider, useAccess } from '@/lib/collections';
@@ -287,8 +286,14 @@ function DashboardShell() {
<div className="lg:pl-64">
<ImpersonationBanner />
{/* Topbar */}
<header className="sticky top-0 z-30 flex h-18 items-center gap-4 border-b border-ink-600/60 bg-ink-900/85 px-5 backdrop-blur-xl sm:px-7">
{/*
Topbar. Plna barva, zadne `backdrop-blur`. Rozmazana hlavicka pres
celou sirku byla posledni na dashboardu a pod otevrenym dialogem
(prekryv pres cely viewport lezi nad ni) prohlizec rozmazani
prepocital pri kazdem prekresleni pod prekryvem - blikajici kurzor
v poli dialogu stacil, aby vedle portalu sekalo video.
*/}
<header className="sticky top-0 z-30 flex h-18 items-center gap-4 border-b border-ink-600/60 bg-ink-900 px-5 sm:px-7">
<button
type="button"
onClick={() => setSidebarPath(location.pathname)}
@@ -311,8 +316,6 @@ function DashboardShell() {
<p className="truncate text-xs text-white/40">{accessLabel(user, access.data)}</p>
</div>
<LiveIndicator className="hidden sm:inline-flex" />
<div className="flex items-center gap-3">
<div className="hidden text-right sm:block">
<p className="text-sm font-medium text-white">{user?.name}</p>
@@ -1,46 +0,0 @@
import { useEventStream } from '@/components/dashboard/EventStreamProvider';
import { cn } from '@/lib/cn';
/*
* Pulz jen kdyz se neco deje (pripojuji, obnovuji). Stav "Zive" je vychozi
* a trva hodiny; nekonecna animace v hlavicce s `backdrop-blur` nutila
* prohlizec prepocitavat rozmazani porad dokola, a s otevrenym dialogem
* (dalsi vrstva pres celou obrazovku) to sekalo video vedle portalu.
*/
const meta = {
connecting: { label: 'Připojuji', dot: 'bg-warn-400', text: 'text-warn-400', pulse: true },
open: { label: 'Živě', dot: 'bg-ok-400', text: 'text-ok-400', pulse: false },
reconnecting: {
label: 'Obnovuji spojení',
dot: 'bg-warn-400',
text: 'text-warn-400',
pulse: true,
},
closed: { label: 'Odpojeno', dot: 'bg-white/40', text: 'text-white/45', pulse: false },
} as const;
/** Stav spojeni se streamem. Uzivatel musi poznat, ze data nejsou zive. */
export function LiveIndicator({ className }: { className?: string }) {
const { status } = useEventStream();
const current = meta[status];
return (
<span
className={cn(
'inline-flex items-center gap-1.5 rounded-full border border-ink-600/70 px-2.5 py-1 text-xs font-medium',
current.text,
className,
)}
title={
status === 'open'
? 'Dashboard dostává změny okamžitě, bez obnovování stránky.'
: 'Změny se teď nemusí projevovat okamžitě.'
}
>
<span
className={cn('size-1.5 rounded-full', current.dot, current.pulse && 'animate-pulse')}
/>
{current.label}
</span>
);
}
+1 -5
View File
@@ -270,11 +270,7 @@ function HeroPanel() {
<p className="truncate text-xs font-semibold text-white">Acme s.r.o.</p>
<p className="truncate text-[0.65rem] text-white/40">{t('panel.role')}</p>
</div>
<span className="ml-auto inline-flex items-center gap-1.5 rounded-full border border-ok-400/35 bg-ok-500/12 px-2 py-0.5 text-[0.65rem] text-ok-400">
<span className="size-1.5 rounded-full bg-ok-400" />
{t('panel.live')}
</span>
<span className="grid size-7 shrink-0 place-items-center rounded-xl bg-brand-500/20 text-[0.6rem] font-bold text-brand-200">
<span className="ml-auto grid size-7 shrink-0 place-items-center rounded-xl bg-brand-500/20 text-[0.6rem] font-bold text-brand-200">
JK
</span>
</div>
+20 -15
View File
@@ -10,10 +10,13 @@ import { cn } from '@/lib/cn';
* Pri otevreni zamkne scroll stranky a preda focus dovnitr.
*
* **Vykresluje se portalem do `document.body`, ne na miste volani.** Samo
* `position: fixed` nestaci: rodic s `backdrop-filter` (nase `.glass`, tedy
* skoro kazdy panel a karta) je pro fixed potomka containing block a dialog
* se pak vejde do te karty misto pres celou obrazovku. Bez portalu je tedy
* dialog v karte konektoru maly a nepouzitelny.
* `position: fixed` nestaci: rodic s `transform`, `filter` nebo
* `backdrop-filter` je pro fixed potomka containing block a dialog se pak
* vejde do te karty misto pres celou obrazovku. Bez portalu byl dialog
* v karte konektoru maly a nepouzitelny.
*
* Prekryv nema rozmazani a nic pod nim ho mit nesmi: rozmazana vrstva pod
* prekryvem se prepocitava pri kazdem prekresleni (i blikajici kurzor).
*/
export function Modal({
open,
@@ -73,22 +76,24 @@ export function Modal({
if (!open) return null;
return createPortal(
<div className="fixed inset-0 z-[60] flex items-start justify-center overflow-y-auto p-4 sm:items-center sm:p-6">
{/*
Prekryv bez rozmazani, schvalne. `backdrop-blur` pres celou obrazovku
znamena, ze kazda animace pod dialogem (pulzujici tecka, spinner,
obnova seznamu) prepocita rozmazani cele plochy, a to jeste pres
rozmazanou hlavicku pod nim. Vedle portalu pak sekalo video. Tmavy
prekryv s vyssi krytim dela tutez praci zadarmo.
*/}
<div className="fixed inset-0 bg-ink-950/85" onClick={onClose} aria-hidden />
/*
* Jedna vrstva: tmavy prekryv pres celou obrazovku, uprostred panel.
* Zadne rozmazani, zadna animace. Klik mimo panel zavira.
* `m-auto` na panelu ho vycentruje a u vysokeho dialogu necha horni
* okraj dosazitelny posuvnikem prekryvu.
*/
<div
className="fixed inset-0 z-[60] flex h-screen w-screen overflow-y-auto bg-ink-950/85 p-4 sm:p-6"
onClick={(event) => {
if (event.target === event.currentTarget) onClose();
}}
>
<div
role="dialog"
aria-modal="true"
aria-label={title}
className={cn(
'relative my-auto w-full max-w-3xl rounded-2xl border border-ink-600/70 bg-ink-900 shadow-2xl',
'm-auto w-full max-w-3xl rounded-2xl border border-ink-600/70 bg-ink-900 shadow-2xl',
className,
)}
>
-1
View File
@@ -77,7 +77,6 @@ export const cs = {
'panel.ago24min': 'před 24 min',
'panel.ago1h': 'před 1 hod',
'panel.ago3h': 'před 3 hod',
'panel.live': 'Živě',
// ------------------------------------------------------------ reference
'logos.title': 'Pracujeme pro firmy, které nemají čas na ruční práci',
-1
View File
@@ -77,7 +77,6 @@ export const en: Partial<Record<MessageKey, string>> = {
'panel.ago24min': '24 min ago',
'panel.ago1h': '1 hour ago',
'panel.ago3h': '3 hours ago',
'panel.live': 'Live',
// ------------------------------------------------------------ reference
'logos.title': 'Trusted by teams with no time for busywork',
+5 -12
View File
@@ -183,8 +183,10 @@
* vrstvu ve skladaci. Pri padesati kartach a zivem streamu udalosti to
* vytizilo GPU natolik, ze vedle toho sekalo prehravani videa.
*
* Rozmazani zustava tam, kde neco opravdu prekryva: lepici hlavicka
* a podklady modalu maji svoje `backdrop-blur-*` primo u sebe.
* Na dashboardu uz `backdrop-filter` nema nic: pod otevrenym dialogem
* (prekryv pres cely viewport) se kazda rozmazana vrstva prepocitava pri
* kazdem prekresleni, i kdyz jen blika kurzor. Jedine rozmazani zustalo
* v hlavicce verejneho webu, kde neni zadny stream ani dialog.
*/
@utility glass {
background-color: --alpha(var(--color-ink-800) / 70%);
@@ -225,13 +227,4 @@
*
* Nekonecne animace jsou to jedine, co na klidne strance porad zamestnava
* skladac - a prave ty jsou tady zbytne.
*/
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
*/