/* Roboto jak w portfolio (@fontsource-variable/roboto, licencja OFL: fonts/Roboto-LICENSE.txt): latin
   i latin-ext, czyli także polskie znaki, jedna zmienna grubość 100–900. */
@font-face {
  font-family: "Roboto Variable";
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url(fonts/roboto-latin-ext-wght-normal.woff2) format("woff2-variations");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Roboto Variable";
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url(fonts/roboto-latin-wght-normal.woff2) format("woff2-variations");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Czarny motyw jak ciemny motyw Obsidiana: wartości --color-base-*, --canvas-dot-pattern i domyślnego
   koloru karty (#7e7e7e) z obsidian.asar. Bez cieni. Jeden motyw, bez trybu jasnego. */
:root {
  --font: "Roboto Variable", Roboto, system-ui, sans-serif;
  --bg: #1e1e1e;         /* tło jak canvas Figmy w trybie ciemnym */
  --grid: #333333;       /* --canvas-dot-pattern */
  --bar: #282828;        /* --background-secondary */
  --text: #dadada;       /* --text-normal */
  --muted: #b3b3b3;      /* --text-muted */
  --line: #333333;       /* --background-modifier-border */
  --card: #1e1e1e;       /* karta ma tło canvasu */
  --card-line: #7e7e7e;  /* --canvas-color karty bez koloru */
  --card-head: #282828;
  /* Karta z tekstem: jasnoszara jak --color-base-25 jasnego motywu Obsidiana, prawie biała, ale widoczna
     też na białym tle (prośba użytkownika), z tekstem jak --text-normal tego motywu. */
  --doc-bg: #efefef;
  --doc-ink: #222222;
  --button: #333333;     /* --interactive-normal na macOS */
  --button-hover: #3f3f3f;
  --field: #2e2e2e;      /* --background-modifier-form-field */
  --accent: #8a5cf5;     /* --color-accent: hsl(258, 88%, 66%) */
  --accent-hover: #a68af9;
  --on: #44cf6e;
  --wait: #e0de71;
  --off: #666666;
  --error: #fb464c;
  color-scheme: dark;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  background: var(--bg);
  color: var(--text);
  font: 14px/1.4 var(--font);
}

/* Tekst w przyciskach i paskach jest wyśrodkowany na wysokości wersalików (cap height), nie na całej linii
   z miejscem na akcenty i ogonki: `text-box` przycina linię od góry do wersalików, od dołu do linii bazowej.
   Odstęp w pionie = dawny odstęp + (wysokość linii − wysokość wersalików) / 2, więc wysokość się nie zmienia.
   Akcenty (Ź), wysokie litery (d, k) i ogonki (ę, g) wystają wtedy poza pudełko, więc taki tekst nie może
   leżeć w overflow: hidden bez odstępu w pionie. Do wielokropka: overflow-x: clip (ucina tylko w poziomie). */
button {
  font: inherit;
  color: var(--text);
  background: var(--button);
  border: 1px solid var(--line);
  border-radius: 7px;
  padding: 10px 12px;
  text-box: trim-both cap alphabetic;
  cursor: pointer;
}
button:hover { background: var(--button-hover); }
button:active { background: var(--card-head); }
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
button.primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
button:disabled { opacity: .55; cursor: default; }
button.link { border: none; background: none; color: var(--accent); padding: 0 4px; text-decoration: underline; }

#bar {
  position: fixed;
  inset: 0 0 auto 0;
  height: 46px;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 14px;
  background: var(--bar);
  border-bottom: 1px solid var(--line);
  z-index: 10;
  user-select: none;
  white-space: nowrap;
}
#device, #sync-text { text-box: trim-both cap alphabetic; }
.muted { color: var(--muted); }
.sync { display: inline-flex; align-items: center; gap: 7px; min-width: 0; }
#sync-text { min-width: 0; overflow-x: clip; text-overflow: ellipsis; }
.dot { width: 9px; height: 9px; border-radius: 50%; background: var(--off); flex: none; }
.sync.on .dot { background: var(--on); }
.sync.wait .dot { background: var(--wait); }
.sync.warn .dot { background: var(--error); }

#notice {
  position: fixed;
  top: 46px;
  left: 0;
  right: 0;
  padding: 12px 14px;
  text-box: trim-both cap alphabetic;
  background: var(--bar);
  color: var(--wait);
  border-bottom: 1px solid var(--line);
  z-index: 9;
}

#board {
  position: fixed;
  inset: 46px 0 0 0;
  overflow: hidden;
  cursor: default;
  touch-action: none;
  background: var(--bg);
}
#board.write { cursor: text; } /* narzędzie tekstu nad pustym miejscem; karty mają swój kursor */
#board.draw { cursor: crosshair; } /* narzędzie figur (doc.js: figury), tak samo */
#board.space, #board.space * { cursor: grab; }
#board.panning, #board.panning * { cursor: grabbing; -webkit-user-select: none; user-select: none; }
#grid { display: none; } /* canvas bez kropek siatki */
/* Karty rysowane przez GPU (gl.js): pod kartami w DOM, nad kropkami siatki. */
#gl { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
#gl[hidden] { display: none; }
/* Karta daleko od widoku bez układu, a strona na żywo, PDF i kalendarz w układzie, ale niewidoczne
   (gl.js: culling). Tylko na planszy: kopie kart w eksporcie i na slajdach mają te klasy po oryginale. */
#world > .card.culled, #groups > .card.culled { display: none; }
#world > .card.veiled, #groups > .card.veiled { visibility: hidden; }
/* Z daleka połączenia rysuje GPU, a połączenie pod kursorem znajduje gl.js (glEdgeAt). Z bliska linie
   daleko od widoku nie mają układu, jak karty: SVG z tysiącami linii przeglądarka przechodziła całe w każdej
   klatce przesuwania i powiększania. */
#world.gl-far #edges { display: none; }
#grid circle { fill: var(--grid); }
#world { position: absolute; left: 0; top: 0; width: 0; height: 0; transform-origin: 0 0; --unzoom: 1; --zm: 1; }
/* Linie wyrównania przy przyciąganiu (app.js: showSnaps), nad przesuwaną kartą: sama linia, jak
   przyłożona linijka, wyraźna, w kolorze akcentu, 2 piksele grubości na ekranie przy każdym powiększeniu. */
#snaps { position: absolute; left: 0; top: 0; width: 1px; height: 1px; overflow: visible; pointer-events: none; z-index: 6; }
#snaps line { stroke: var(--accent); stroke-width: 2px; vector-effect: non-scaling-stroke; }
/* Liczba punktów przy równym odstępie: biała na akcencie. Cyfry mają wysokość wersalików, więc linia
   bazowa o pół ich wysokości (.35em) pod środkiem etykiety stawia je na środku. */
#snaps rect { fill: var(--accent); }
#snaps text { fill: #fff; font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums; text-anchor: middle; dominant-baseline: alphabetic; }
/* Połączenia (app.js: drawEdges) jak .canvas-edges w Obsidianie: pod kartami, w kolorze obwódki karty.
   Linia ma 2 × --zm punktu (√(1 / powiększenie), jak --zoom-multiplier w Obsidianie), więc po oddaleniu
   jest cieńsza na ekranie, ale nie znika. Klika się szeroki niewidoczny pas wzdłuż linii. Zaznaczone albo
   pod kursorem wygląda tak samo, zaznaczenie widać tylko po uchwytach. */
#edges { position: absolute; overflow: visible; pointer-events: none; }
#edges .line { fill: none; stroke: var(--card-line); stroke-width: calc(2px * var(--zm)); }
#edges .hit { fill: none; stroke: transparent; stroke-width: calc(24px * var(--zm)); stroke-linecap: round; pointer-events: stroke; cursor: pointer; }
#edges .head { fill: var(--card-line); stroke: var(--card-line); stroke-width: 1px; stroke-linejoin: round; }
#world.connecting #edges .hit { pointer-events: none; }
/* Uchwyty zaznaczonego połączenia (app.js: placeHandles) jak w FigJamie, nad kartami i w stałym rozmiarze
   na ekranie: kółka na końcach, kreski wzdłuż odcinków łamanej, kropka na krzywej. Pole do łapania jest
   większe niż to, co widać. */
#edge-handles { position: absolute; left: 0; top: 0; z-index: 5; }
.edge-handle { position: absolute; width: calc(24px * var(--unzoom)); height: calc(24px * var(--unzoom)); translate: -50% -50%; cursor: move; }
.edge-handle::before { content: ''; position: absolute; left: 50%; top: 50%; translate: -50% -50%; background: var(--accent); }
.edge-handle.end::before { width: calc(16px * var(--unzoom)); height: calc(16px * var(--unzoom)); border: calc(2px * var(--unzoom)) solid var(--accent); border-radius: 50%; background: var(--bg); box-sizing: border-box; }
.edge-handle.bend::before { width: calc(12px * var(--unzoom)); height: calc(12px * var(--unzoom)); border-radius: 50%; }
.edge-handle.segment { width: calc(18px * var(--unzoom)); height: calc(32px * var(--unzoom)); cursor: ew-resize; }
.edge-handle.segment.flat { width: calc(32px * var(--unzoom)); height: calc(18px * var(--unzoom)); cursor: ns-resize; }
.edge-handle.segment::before { width: calc(6px * var(--unzoom)); height: calc(22px * var(--unzoom)); border-radius: calc(3px * var(--unzoom)); }
.edge-handle.segment.flat::before { width: calc(22px * var(--unzoom)); height: calc(6px * var(--unzoom)); }

/* Pasek na dole na środku jak w Figmie: narzędzia, dodawanie kart, połączenie i ustawienia, rozdzielone
   kreską. Wybrane narzędzie w kolorze akcentu jak widok w pasku linku. Zoom w prawym rogu ma te same
   przyciski i tę samą wysokość. Ikony są symetryczne w polu 24 × 24, więc − i + stoją na środku. */
#tools, #zoom {
  position: fixed;
  bottom: 14px;
  display: flex;
  gap: 2px;
  padding: 4px;
  background: var(--bar);
  border: 1px solid var(--line);
  border-radius: 10px;
  z-index: 8;
  user-select: none;
}
#tools { left: 50%; transform: translateX(-50%); }
#zoom { right: 14px; }
#tools button, #zoom button { display: flex; padding: 6px; border: none; border-radius: 6px; background: none; color: var(--text); }
#tools button:hover, #zoom button:hover { background: rgba(255, 255, 255, .067); }
#tools button.on { background: var(--accent); color: #fff; }
#tools i, #zoom i { align-self: stretch; width: 1px; margin: 3px; background: var(--line); }
#tools svg, #zoom svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
/* Prostokąt zaznaczenia: kolory .canvas-selection z Obsidiana, nad kartami jak w Figmie, linia 1 piksel ekranu. */
#marquee {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 7;
  pointer-events: none;
  border: calc(1px * var(--unzoom)) solid var(--accent);
  background: rgba(138, 92, 245, .1); /* --accent, 10% */
}
#marquee[hidden] { display: none; }
/* Plan prezentacji przy najechaniu na Prezentację (doc.js: showDeckPlan): ramki wokół kart slajdów z numerami,
   przerywane miejsca nowych slajdów i obwódka prezentacji, do której dojdą. Linie i numery w pikselach ekranu,
   nad zaznaczonymi kartami, pod paskiem nad zaznaczeniem. */
.deck-plan { position: absolute; left: 0; top: 0; z-index: 4; pointer-events: none; }
.deck-plan[hidden] { display: none; }
.deck-plan > div { position: absolute; box-sizing: border-box; border-radius: calc(4px * var(--unzoom)); }
.plan-pile { border: calc(2px * var(--unzoom)) solid var(--accent); background: rgba(138, 92, 245, .08); }
.plan-slide { border: calc(2px * var(--unzoom)) dashed var(--accent); background: rgba(138, 92, 245, .06); }
.plan-bag { border: calc(1px * var(--unzoom)) solid var(--accent); }
/* Układ na slajdzie (doc.js: slideDropHover): w trakcie przeciągania kafelki po upuszczeniu, nad przeciąganą kartą,
   która zasłaniałaby swoje nowe miejsce. Nowe kafelki wypełnione, miejsca kart już na slajdzie w obwódce. */
.deck-plan.tile-plan { z-index: 6; }
.tile-in { border: calc(2px * var(--unzoom)) solid var(--accent); background: rgba(138, 92, 245, .18); }
.tile-old { border: calc(1px * var(--unzoom)) dashed var(--accent); background: rgba(138, 92, 245, .04); }
.tile-frame { border: calc(2px * var(--unzoom)) dashed var(--accent); }
/* Uchwyt podziału między kafelkami slajdu w układzie (doc.js: tileBar): pas w przerwie, a w nim kreska, jak
   w Split View na iPadzie, w pikselach ekranu. */
.tile-bar { position: absolute; z-index: 5; cursor: col-resize; touch-action: none; }
.tile-bar.across { cursor: row-resize; }
.tile-bar[hidden] { display: none; }
.tile-bar::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  width: calc(4px * var(--unzoom));
  height: min(calc(48px * var(--unzoom)), 50%);
  border-radius: calc(2px * var(--unzoom));
  background: var(--accent);
  opacity: .5;
}
.tile-bar.across::after { width: min(calc(48px * var(--unzoom)), 50%); height: calc(4px * var(--unzoom)); }
.tile-bar:hover::after, .tile-bar.on::after { opacity: 1; }
/* Uchwyt rzędu albo kolumny kafelków (doc.js: tileTeamShow): nad lewym górnym rogiem grupy, poza uchwytami rozmiaru
   karty, w wyglądzie paska nad zaznaczeniem i w pikselach ekranu. Po najechaniu obwódka całej grupy (.tile-ring).
   W trakcie przeciągania go nie widać, ale zostaje w DOM, bo trzyma wskaźnik. */
.tile-grip {
  position: absolute;
  z-index: 6;
  display: flex;
  padding: 2px 4px;
  background: var(--bar);
  border: 1px solid var(--button-hover);
  border-radius: 6px;
  color: var(--text);
  cursor: grab;
  touch-action: none;
  transform: scale(var(--unzoom)) translate(0, calc(-100% - 12px));
  transform-origin: 0 0;
}
.tile-grip:hover { background: var(--accent); border-color: var(--accent); color: #fff; }
.tile-grip svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2.5; stroke-linecap: round; pointer-events: none; }
#world.busy .tile-grip { opacity: 0; }
.tile-ring {
  position: absolute;
  z-index: 5;
  box-sizing: border-box;
  border: calc(2px * var(--unzoom)) solid var(--accent);
  border-radius: calc(4px * var(--unzoom));
  pointer-events: none;
}
.tile-ring[hidden], #world.busy .tile-ring { display: none; }
.deck-plan span {
  position: absolute;
  left: 0;
  top: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  min-width: 22px;
  height: 22px;
  padding: 0 7px;
  border-radius: 11px;
  background: var(--accent);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  text-box: trim-both cap alphabetic;
  transform: scale(var(--unzoom)) translate(-50%, -50%);
  transform-origin: 0 0;
}
#board.selecting, #board.selecting * { -webkit-user-select: none; user-select: none; }
#hint {
  position: absolute;
  left: 50%;
  top: 40%;
  transform: translate(-50%, -50%);
  text-align: center;
  color: var(--muted);
  pointer-events: none;
}

.card {
  position: absolute;
  left: 0;
  top: 0;
  background: var(--card);
  border: 1px solid var(--card-line);
  border-radius: 9px;
  display: flex;
  flex-direction: column;
}
.card.dragging { z-index: 5; }
/* Przezroczysta warstwa na całej karcie (jak .canvas-node-content-blocker w Obsidianie): za nią kartę się
   zaznacza i przesuwa. W trakcie pisania jej nie ma, więc kliknięcia trafiają w tekst. */
.card .head { position: absolute; inset: 0; border-radius: 8px; cursor: grab; }
.card.dragging .head { cursor: grabbing; }
.card:focus-within .head { display: none; }
.card:focus-within { border-color: var(--accent); }

/* Karta z obrazem: obraz wypełnia kartę. Wymiary karty mają proporcje obrazu; `contain` na wypadek
   innych. Karta nie przycina zawartości (rogi do zmiany rozmiaru wystają poza nią), więc zaokrąglenie
   ma sam obraz. */
.card.image img { display: block; width: 100%; height: 100%; object-fit: contain; border-radius: 8px; }
/* Na slajdzie obraz wypełnia swój kafelek, przycięty od środka (doc.js: układ na slajdzie); plik zostaje cały. */
.card.image.in-slide img { object-fit: cover; }
.card.image img[hidden] { display: none; }

/* Zaznaczona karta: ramka w kolorze akcentu jak w Obsidianie. Rozmiar zmienia się za niewidoczne
   uchwyty w rogach i na krawędziach. Obwódka i uchwyty mają stały rozmiar na ekranie przy każdym
   powiększeniu (--unzoom = 1 / powiększenie). Uchwyt sięga na zewnątrz karty na stałą odległość, a do środka najwyżej na 20% (róg) albo 10%
   (krawędź) jej boku: oddalona, mała na ekranie karta dalej przesuwa się za środek.
   Procenty liczą się od szerokości albo wysokości karty, zależnie od właściwości (left/width, top/height). */
.card.selected { z-index: 4; border-color: var(--accent); outline: calc(2px * var(--unzoom)) solid var(--accent); }
.card .grip {
  --out: calc(10px * var(--unzoom));
  --in: min(calc(10px * var(--unzoom)), 20%);
  position: absolute;
  width: calc(var(--out) + var(--in));
  height: calc(var(--out) + var(--in));
  z-index: 1;
}
.card .grip.nw { left: calc(-1px - var(--out)); top: calc(-1px - var(--out)); cursor: nwse-resize; }
.card .grip.ne { right: calc(-1px - var(--out)); top: calc(-1px - var(--out)); cursor: nesw-resize; }
.card .grip.sw { left: calc(-1px - var(--out)); bottom: calc(-1px - var(--out)); cursor: nesw-resize; }
.card .grip.se { right: calc(-1px - var(--out)); bottom: calc(-1px - var(--out)); cursor: nwse-resize; }
/* Kropki do łączenia na środkach boków (app.js: ports): środkiem na krawędzi karty, bez odstępu, który
   ma FigJam (prośba użytkownika). W spoczynku to kółko z obwódką w kolorze akcentu i przyciemnionym
   akcentem w środku (pusty środek był zbyt przezroczysty, pełny akcent zlewał się z obwódką). Po
   przybliżeniu ramka karty grubieje (1 punkt canvasu), więc kropka rośnie razem z nią: 4 piksele
   ekranu plus 3 punkty canvasu, czyli 8 pikseli przy 100%, 10 przy 200% i 16 przy 400%, ale nigdy
   mniej niż 8 ani więcej niż 16 (--dot, w punktach canvasu), żeby nie ginęła na grubej ramce i nie
   urosła za bardzo. Pod kursorem rośnie, jak w FigJamie, do pełnego koła 24 pikseli ze strzałką
   w stronę, w którą wyjdzie połączenie. Pole .port łapie kursor 16 pikseli ekranu na zewnątrz karty i tyle
   samo do środka, ale do środka najwyżej na 25% jej boku, więc oddalona, mała na ekranie karta dalej
   przesuwa się za środek. Łapie kursor tylko przy najechanej karcie, żeby nie zasłaniać kart obok. Widać
   je po najechaniu na kartę, ale nie w trakcie pisania, przesuwania, łączenia, przy kilku zaznaczonych
   kartach ani po oddaleniu poniżej 1/3 (app.js: CONNECT_ZOOM). --x i --y to środek kropki w polu. */
.card .port {
  --half: calc(16px * var(--unzoom));
  --in: min(var(--half), 25%);
  --rim: -.5px;
  --dot: clamp(8 * var(--unzoom), 3 + 4 * var(--unzoom), 16 * var(--unzoom));
  position: absolute;
  z-index: 3;
  opacity: 0;
  pointer-events: none;
  cursor: pointer;
}
.card .port::before, .card .port::after {
  content: '';
  position: absolute;
  left: var(--x);
  top: var(--y);
  translate: -50% -50%;
}
/* Kropka i strzałka mają stały rozmiar w punktach canvasu i zmieniają go tylko skalą (scale): przy
   ułamkowym rozmiarze w powiększonym canvasie Chromium (podgląd w VS Code) rysował kółko rozciągnięte
   w bok, a grubość borderu obie przeglądarki zaokrąglały, więc obwódka to pierścień bez rozmycia. */
.card .port::before {
  width: 16px;
  height: 16px;
  box-shadow: inset 0 0 0 3px var(--accent);
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 40%, var(--bg));
  scale: calc(var(--dot) / 16);
  transition: scale 100ms ease-out, background-color 100ms ease-out;
}
/* Strzałka z paska połączenia (app.js: EDGE_ICONS.arrow), skierowana w prawo i obracana do boku. */
.card .port::after {
  width: 14px;
  height: 14px;
  scale: var(--unzoom);
  background: #fff;
  mask: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><path d='M5 12h14M12 5l7 7-7 7'/></svg>") center / contain no-repeat;
  opacity: 0;
  transition: opacity 100ms ease-out;
}
.card .port:hover::before { scale: calc(24 / 16 * var(--unzoom)); background: var(--accent); }
.card .port:hover::after { opacity: 1; }
/* Krawędź karty to środek jej 1-pikselowej ramki: pół piksela za polem, od którego liczy się left i top
   (--rim). Karta z linkiem ma ramkę w środku (.card.link::after), więc pół piksela przed tym polem;
   z-index 3 trzyma kropkę nad tą ramką i nad .link-blocked. Zaznaczona karta ma jeszcze obwódkę
   2 pikseli ekranu na zewnątrz ramki (.card.selected), więc środek widocznej linii przesuwa się
   o 1 piksel ekranu na zewnątrz. */
.card.link .port { --rim: .5px; }
.card.selected > .port { --rim: calc(-.5px - 1px * var(--unzoom)); }
.card.link.selected > .port { --rim: calc(.5px - 1px * var(--unzoom)); }
.card .port[data-side="top"], .card .port[data-side="bottom"] { left: calc(50% - var(--half)); width: calc(2 * var(--half)); height: calc(var(--half) + var(--in)); --x: 50%; }
.card .port[data-side="left"], .card .port[data-side="right"] { top: calc(50% - var(--half)); height: calc(2 * var(--half)); width: calc(var(--half) + var(--in)); --y: 50%; }
.card .port[data-side="top"] { top: calc(var(--rim) - var(--half)); --y: var(--half); }
.card .port[data-side="bottom"] { bottom: calc(var(--rim) - var(--half)); --y: calc(100% - var(--half)); }
.card .port[data-side="left"] { left: calc(var(--rim) - var(--half)); --x: var(--half); }
.card .port[data-side="right"] { right: calc(var(--rim) - var(--half)); --x: calc(100% - var(--half)); }
.card .port[data-side="top"]::after { rotate: -90deg; }
.card .port[data-side="bottom"]::after { rotate: 90deg; }
.card .port[data-side="left"]::after { rotate: 180deg; }
.card:hover > .port { opacity: 1; pointer-events: auto; }
.card:focus-within > .port, #world.busy .port, #world.multi .port, #world.far .port, #board.panning .port, #board.selecting .port, #board.space .port, #board.write .port, #board.draw .port { display: none; }
/* Rysowane połączenie i koniec przeciągany kółkiem (app.js: markTarget): na karcie, do której dojdzie,
   widać kropki, a ta, na której stanie (.on), jest pełna jak pod kursorem, bez strzałki. */
#world.busy:not(.far) .card.target > .port { display: block; opacity: 1; }
.card .port.on::before { scale: calc(24 / 16 * var(--unzoom)); background: var(--accent); }

.card.image .note, .card.pdf .note {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 12px;
  text-align: center;
  color: var(--muted);
  overflow: hidden;
}
.card.image .note[hidden], .card.pdf .note[hidden] { display: none; }
.card.image .note small, .card.pdf .note small { margin-top: 4px; overflow-wrap: anywhere; }

.screen, .modal {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 20;
}
.screen { background: var(--bg); }
.modal { background: rgba(10, 10, 10, .4); } /* --background-modifier-cover */
.screen[hidden], .modal[hidden] { display: none; }
.panel {
  position: relative;
  width: min(460px, calc(100vw - 32px));
  background: var(--bar);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 22px 24px;
}
.panel h1, .panel h2 { margin: 0 0 12px; font-size: 19px; }
.panel h3 { margin: 0 0 8px; font-size: 15px; }
#update-status:empty { display: none; }
.panel p { margin: 0 0 12px; }
.panel button + button { margin-left: 6px; }
.close { position: absolute; right: 12px; top: 10px; border: none; background: none; font-size: 20px; padding: 0 6px; text-box: none; }
.or { margin: 14px 0 8px !important; }
.join { display: flex; gap: 8px; }
.join input {
  flex: 1;
  font: inherit;
  font-family: ui-monospace, Menlo, Consolas, monospace;
  text-transform: uppercase;
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--field);
  color: var(--text);
}
.code {
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 26px;
  letter-spacing: 1px;
  text-align: center;
  user-select: text;
  padding: 10px 0;
}
.error { color: var(--error); min-height: 0; }
.error:empty { display: none; }

/* Karta z linkiem: widoki jak w FigJamie (karta, kompakt) i strona na żywo. Ramka karty leży nad
   treścią (::after), więc strona na żywo ma dokładnie szerokość i wysokość karty. */
.card.link { border: none; }
.card.link::after {
  content: '';
  position: absolute;
  inset: 0;
  border: 1px solid var(--card-line);
  border-radius: 9px;
  pointer-events: none;
  z-index: 2;
}
.card.link.selected::after { border-color: var(--accent); }
.link-body {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: 9px;
  background: var(--card);
}
/* Karta i kompakt w proporcjach: treść w szerokości widoku, przeskalowana do rozmiaru karty (scaleLink
   w app.js). Strona na żywo tak samo, ale tylko w karcie mniejszej niż 320 × 160. Zaokrąglenie zostaje
   takie jak ramki karty (::after). */
.card.link .link-body { transform: scale(var(--k, 1)); transform-origin: 0 0; border-radius: calc(9px / var(--k, 1)); }
.link-image { display: block; flex: 1; min-height: 0; width: 100%; object-fit: cover; background: var(--card-head); }
.link-image[hidden] { display: none; }
/* Pod obrazem tylko tytuł w jednej linii i adres strony, jak w FigJamie. Bez obrazu oba na środku karty. */
.link-text { flex: 1; min-height: 0; display: flex; flex-direction: column; justify-content: center; gap: 3px; padding: 10px 14px 11px; }
.link-image:not([hidden]) + .link-text { flex: none; }
.link-title { font-weight: 600; line-height: 20px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.link-site { display: flex; align-items: center; gap: 6px; min-width: 0; color: var(--muted); font-size: 12px; line-height: 18px; }
.link-host { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.link-icon { position: relative; flex: none; width: 16px; height: 16px; }
.link-icon img, .link-icon svg { position: absolute; inset: 0; width: 16px; height: 16px; border-radius: 3px; }
.link-icon svg { fill: none; stroke: currentColor; stroke-width: 1.2; }
.link-icon img[hidden], .link-icon img:not([hidden]) + svg { display: none; }

/* Kompakt: ikona i tytuł w jednej linii. */
.card.link[data-view="compact"] .link-text { flex-direction: row; justify-content: flex-start; align-items: center; gap: 8px; padding: 0 12px; }
.card.link[data-view="compact"] .link-site { order: -1; }
.card.link[data-view="compact"] .link-host { display: none; }
.card.link[data-view="compact"] .link-title { min-width: 0; }

/* Na żywo: strona w ramce. Białe tło jak w przeglądarce, dla stron bez własnego tła. Do kliknięcia
   (.active) ramkę zakrywa przezroczysta warstwa: kliknięcie włącza stronę, przeciąganie przesuwa
   kartę, kółko przesuwa canvas. Przy przesuwaniu, zmianie rozmiaru, z wciśniętą Spacją i w trakcie
   gestu kółka, który przesuwa albo powiększa widok (app.js: wheelKind), ramka nie łapie kursora. */
.card.link[data-view="live"] .link-image,
.card.link[data-view="live"] .link-text { display: none; }
/* Zaokrąglenie ma sama ramka, a nie przycinające ją .link-body (overflow: hidden z border-radius): przy takim
   przycięciu WebKit w każdej klatce powiększania rysował od nowa maskę każdej strony, a 9 stron dawało klatki
   po 75–110 ms. Na rogach wygląda tak samo. */
.card.link[data-view="live"] .link-body { overflow: visible; }
.link-frame { display: block; width: 100%; height: 100%; border: 0; border-radius: inherit; background: #fff; }
.link-frame.player { background: transparent; } /* odtwarzacz muzyki z własnym zaokrąglonym tłem */
/* Strona poza ekranem albo po mocnym oddaleniu (app.js: sleepFrames) zostaje wczytana, ale okno jej nie rysuje. */
.card.link.asleep .link-frame { visibility: hidden; }
.card.link:focus-within .head { display: block; } /* przycisk paska albo strona z fokusem */
.card.link.active .head { pointer-events: none; }
#world.busy iframe, #board.panning iframe, #board.selecting iframe, #board.wheeling iframe, #board.space iframe { pointer-events: none; }
.link-blocked {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 20px;
  text-align: center;
  color: var(--muted);
  pointer-events: none;
}
.link-blocked[hidden] { display: none; }
.link-blocked p { margin: 0 0 8px; }
.link-blocked button { pointer-events: auto; }

/* Pasek nad zaznaczeniem (app.js: placeSelBar), jak w Obsidianie i FigJamie, zamiast menu pod prawym
   przyciskiem: Uporządkuj · Sekcja · Usuń · Eksportuj. Zaznaczony link ma na nim najpierw swoje widoki
   jak w FigJamie (.link-bar: Otwórz · Karta · Kompakt · Na żywo · Tekst). Stała wielkość na ekranie,
   10 pikseli ekranu nad zaznaczeniem, na środku jego szerokości, jak pasek połączenia. */
.sel-bar, .edge-bar {
  position: absolute;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 4px;
  background: var(--bar);
  border: 1px solid var(--button-hover);
  border-radius: 10px;
  transform: scale(var(--unzoom)) translate(-50%, calc(-100% - 10px));
  transform-origin: 0 0;
  white-space: nowrap;
  user-select: none;
}
.sel-bar[hidden], #world.busy .sel-bar, #board.selecting .sel-bar { display: none; }
.link-bar { display: contents; }
/* Kilka zaznaczonych kart: bez zmiany rozmiaru, za rogi przesuwa się całe zaznaczenie. */
#world.multi .card.selected .grip { display: none; }
.sel-bar button, .edge-bar button { display: flex; padding: 6px; border: none; border-radius: 6px; background: none; color: var(--text); }
.sel-bar button:hover, .edge-bar button:hover { background: rgba(255, 255, 255, .067); }
.sel-bar button.on, .edge-bar button.on { background: var(--accent); color: #fff; }
.sel-bar button[hidden] { display: none; }
.sel-bar i, .edge-bar i { align-self: stretch; width: 1px; margin: 3px 3px; background: var(--line); }
.sel-bar svg, .edge-bar svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
/* Proporcje karty (app.js: ratioMenu): przycisk z ikoną i obecnymi proporcjami, a nad nim lista jak menu
   ramki w Figmie, w wyglądzie paska: prostokąt w danych proporcjach, nazwa i ptaszek przy obecnych. Gdy nad
   paskiem brakuje miejsca, lista jest pod nim (.below). */
.sel-bar button { align-items: center; gap: 4px; }
.sel-bar button span { padding-right: 4px; font-size: 13px; text-box: trim-both cap alphabetic; }
.sel-bar button.open { background: rgba(255, 255, 255, .067); }
/* Strzałka przy Prezentacji (doc.js: deckMore): wąska, tuż przy przycisku, otwiera listę Dodaj albo Nowa. */
.sel-bar button.more { margin-left: -2px; padding: 6px 1px; }
.sel-bar button.more svg { width: 14px; height: 14px; }
.ratio-menu {
  position: absolute;
  bottom: calc(100% + 6px);
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 4px;
  background: var(--bar);
  border: 1px solid var(--button-hover);
  border-radius: 10px;
}
.ratio-menu.below { top: calc(100% + 6px); bottom: auto; }
.ratio-menu[hidden] { display: none; }
.sel-bar .ratio-menu button { gap: 8px; }
.ratio-menu button span { flex: 1; min-width: 64px; padding: 0; text-align: left; }
.ratio-menu .tick { width: 16px; height: 16px; visibility: hidden; }
.ratio-menu button.on .tick { visibility: visible; }
.sel-bar .ratio-menu button.on { background: none; color: var(--text); }
.sel-bar .ratio-menu button.on:hover { background: rgba(255, 255, 255, .067); }
/* Pasek nad zaznaczonym tekstem w trakcie pisania (doc.js: textBar): Zakreśl i kółka kolorów tekstu, nad
   kartami. Wybrany kolor ma pierścień w kolorze akcentu, jak kolor slajdów. */
.text-bar { z-index: 6; }
.text-bar .swatch span { width: 16px; height: 16px; margin: 2px; padding: 0; border: 1px solid rgba(255, 255, 255, .25); border-radius: 50%; }
.sel-bar.text-bar .swatch.on { background: none; }
.sel-bar.text-bar .swatch.on:hover { background: rgba(255, 255, 255, .067); }
.text-bar .swatch.on span { outline: 2px solid var(--accent); outline-offset: 1px; }
body.exporting, body.exporting * { cursor: progress !important; }
/* Okno eksportu (app.js: openExport) jak w FigJamie: nagłówek z kreską pod spodem, podgląd, format
   (PNG, PDF) w wierszu z nazwą po lewej, a na dole za kreską Eksportuj po prawej. Podgląd leży na środku
   pola o stałej wysokości. Tekst wyrównany do wysokości wersalików, a kółko wyboru do jego środka. */
.export-panel { width: min(560px, calc(100vw - 32px)); padding: 0; }
.export-head { display: flex; align-items: center; justify-content: space-between; padding: 16px 12px 16px 24px; border-bottom: 1px solid var(--line); }
.export-panel h2 { margin: 0; font-size: 15px; text-box: trim-both cap alphabetic; }
.export-head .close { position: static; display: flex; padding: 4px; border-radius: 6px; }
.export-head .close:hover { background: rgba(255, 255, 255, .067); }
.export-head .close svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
#export-preview { display: flex; align-items: center; justify-content: center; height: 280px; margin: 20px 24px; }
#export-preview canvas { display: block; max-width: 100%; }
#export-preview p { margin: 0; }
.export-row { display: flex; gap: 16px; padding: 0 24px 20px; }
.export-row[hidden] { display: none; }
.export-label { width: 120px; padding-top: 4px; text-box: trim-both cap alphabetic; }
.export-formats { display: flex; flex-direction: column; gap: 12px; }
.export-formats label { display: flex; align-items: center; gap: 10px; cursor: pointer; }
.export-formats label[hidden] { display: none; }
.export-formats input { width: 16px; height: 16px; margin: 0; accent-color: var(--accent); cursor: pointer; }
.export-formats span { text-box: trim-both cap alphabetic; }
.export-foot { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--line); }
/* Prezentacja: szersze okno z siatką slajdów, a wiersze Jako i Format obok siebie, żeby okno nie wyszło
   poza ekran laptopa. */
#export.wide .export-panel { display: grid; grid-template-columns: auto 1fr; width: min(880px, calc(100vw - 32px)); }
.export-foot button[hidden] { display: none; }
#export.wide :is(.export-head, #export-preview, .export-foot) { grid-column: 1 / -1; }
#export.wide .export-label { width: auto; }
#export.wide #export-preview { display: block; height: 360px; }
#export.wide #export-bg { grid-column: 1 / -1; }
#export-bg .export-label { padding-top: 7px; }

/* Kolor slajdów (doc.js: kolor slajdów): kółka gotowych kolorów, tęczowe kółko z systemowym wyborem koloru
   i pole na kod. Wybrany kolor ma pierścień w kolorze akcentu. W oknie eksportu w wierszu Tło, a nad paskiem
   nad zaznaczonymi slajdami (kółko w kolorze slajdów na początku paska) w wyglądzie paska, jak lista Proporcje. */
.slide-colors { display: flex; align-items: center; gap: 8px; }
.slide-colors .slide-swatch { position: relative; flex: none; width: 24px; height: 24px; padding: 0; border: 1px solid rgba(255, 255, 255, .2); border-radius: 50%; cursor: pointer; }
.slide-colors .slide-swatch.on { outline: 2px solid var(--accent); outline-offset: 2px; }
.slide-colors .slide-swatch.own { background: conic-gradient(#ff4d4d, #ffd84d, #4dd96b, #4dccff, #6a4dff, #ff4dd8, #ff4d4d); }
.slide-swatch.own input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; padding: 0; border: none; opacity: 0; cursor: pointer; }
.slide-hex {
  width: 84px;
  height: 26px;
  margin-left: 4px;
  padding: 0 8px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--field);
  color: var(--text);
  font: 13px ui-monospace, SFMono-Regular, "Cascadia Mono", Menlo, Consolas, monospace;
  outline: none;
  -webkit-user-select: text;
  user-select: text;
}
.slide-hex:focus { border-color: var(--accent); }
.slide-color-menu {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  padding: 10px;
  background: var(--bar);
  border: 1px solid var(--button-hover);
  border-radius: 10px;
  transform: translateX(-50%);
}
.slide-color-menu.below { top: calc(100% + 6px); bottom: auto; }
.slide-color-menu[hidden] { display: none; }
.sel-bar .slide-color-button span { width: 18px; height: 18px; margin: 1px; padding: 0; border: 1px solid rgba(255, 255, 255, .3); border-radius: 50%; }
.slide-grid { display: grid; grid-template-columns: repeat(3, 1fr); align-content: start; gap: 16px 12px; height: 100%; overflow-y: auto; }
.slide-cell { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.slide-thumb { position: relative; aspect-ratio: 16 / 9; background: #fff; border-radius: 2px; overflow: hidden; }
#export-preview .slide-thumb canvas { width: 100%; height: 100%; }
.slide-num { color: var(--muted); font-size: 12px; text-box: trim-both cap alphabetic; }
.slide-warn { position: absolute; right: 6px; top: 6px; padding: 4px 6px; border-radius: 4px; background: var(--error); color: #fff; font-size: 11px; line-height: 1; }
/* Pasek nad zaznaczonym połączeniem (app.js: placeEdgeTools): linia, strzałka, strzałki w obie strony,
   za kreską prosta, krzywa albo łamana, a na końcu kosz. Wygląda jak pasek nad kartami (wyżej) i tak samo
   stoi nad całą linią (jak w FigJamie). */
.edge-bar[hidden], #world.busy .edge-bar { display: none; }

/* Sekcja (app.js: sekcje) jak grupa w Obsidianie: obwódka w kolorze karty, tło z 7% tego koloru, pod
   połączeniami i kartami. Łapie się ją za dowolne miejsce, jak każdą kartę (app.js: dragWith). Przy
   narzędziu tekstu i figur przepuszcza kliknięcia do canvasu, żeby w sekcji dało się dodać kartę. Nazwa nad lewym górnym rogiem rośnie po oddaleniu jak --zoom-multiplier w Obsidianie (--zm)
   i jest wyrównana do wysokości wersalików: odstęp w pionie 4 + (21 − 15) / 2 px. */
#groups { position: absolute; left: 0; top: 0; z-index: 0; }
.card.group { background: color-mix(in srgb, var(--card-line) 7%, transparent); }
/* Zaznaczona albo przesuwana sekcja zostaje pod mniejszymi sekcjami i kalendarzami, które w niej leżą
   (bez z-index z .card.selected i .card.dragging): slajd z pełnym tłem zasłaniał po kliknięciu swój kalendarz. */
#groups > .card.group.selected, #groups > .card.group.dragging { z-index: auto; }
.card.group:focus-within .head { display: block; } /* w trakcie pisania nazwy sekcję dalej da się złapać */
#board.write .card.group, #board.draw .card.group { pointer-events: none; }
.card.group .grip, .group-label { pointer-events: auto; }
.group-label {
  position: absolute;
  left: 0;
  top: calc(-4px * var(--zm));
  transform: translate(0, -100%) scale(var(--zm));
  transform-origin: bottom left;
  max-width: calc(100% / var(--zm));
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 21px;
  line-height: 1;
  padding: 7px 8px;
  text-box: trim-both cap alphabetic;
  border-radius: 4px;
  color: var(--muted);
  background: color-mix(in srgb, var(--card-line) 10%, transparent);
  cursor: grab;
}
.group-label[hidden] { display: none; }
.card.dragging .group-label { cursor: grabbing; }
.group-label[contenteditable="plaintext-only"], .group-label[contenteditable="true"] {
  cursor: text;
  text-overflow: clip;
  color: var(--text);
  background: var(--bg);
  outline: 2px solid var(--card-line);
  -webkit-user-select: text;
  user-select: text;
}

/* Kopia karty w obrazie z eksportu (app.js: exportCard): strona na żywo jako jej tytuł i adres, a na nich
   obraz podglądu, jeśli strona go ma. Oba z zaokrągleniem ramki, którą zastępują (.link-frame), bo .link-body
   strony na żywo ich nie przycina. */
.export-frame { position: relative; flex: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 3px; padding: 10px 14px 11px; overflow: hidden; border-radius: inherit; text-align: center; }
.export-frame .link-title { max-width: 100%; }
.export-frame .link-site { display: block; }
.export-frame img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.export-shot { position: relative; width: 100%; height: 100%; overflow: hidden; border-radius: inherit; }
.export-shot img { position: absolute; display: block; max-width: none; }

/* Rozmiar strony na żywo: rogi i krawędzie, szerokość i wysokość osobno. Karta i kompakt: tylko rogi,
   w proporcjach jak obraz. */
.card.link:not([data-view="live"]) .grip.edge { display: none; }
/* Karta z ustawionymi proporcjami (app.js: proporcje) tak samo: tylko rogi. */
.card.ratio .grip.edge { display: none; }
/* Krawędź zaczyna się tam, gdzie kończy się róg (jego --in). */
.card .grip.edge {
  --edge-out: calc(5px * var(--unzoom));
  --edge-in: min(calc(5px * var(--unzoom)), 10%);
  width: auto;
  height: auto;
}
.card .grip.n, .card .grip.s { left: var(--in); right: var(--in); height: calc(var(--edge-out) + var(--edge-in)); cursor: ns-resize; }
.card .grip.n { top: calc(-1px - var(--edge-out)); }
.card .grip.s { bottom: calc(-1px - var(--edge-out)); }
.card .grip.e, .card .grip.w { top: var(--in); bottom: var(--in); width: calc(var(--edge-out) + var(--edge-in)); cursor: ew-resize; }
.card .grip.e { right: calc(-1px - var(--edge-out)); }
.card .grip.w { left: calc(-1px - var(--edge-out)); }

/* Okno „+ Link”: zwykły adres, nie kod parowania (.join input). */
#link-url { text-transform: none; font-family: inherit; }
.panel .link-note { margin: 12px 0 0; }

/* Karta PDF jak w canvasie Obsidiana (.pdf-embed): pasek u góry z kreską pod spodem (.pdf-toolbar),
   pod nim strony na tle karty, 10 px odstępu między stronami (removePageBorders w PDF.js). Treść
   przycina się do zaokrąglenia karty, a uchwyty rozmiaru wystają poza nią. Do kliknięcia (.active)
   całość zakrywa `head`, jak stronę na żywo: kliknięcie włącza PDF, przeciąganie przesuwa kartę. */
.pdf-body {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: 8px;
  background: var(--card);
}
.pdf-body[hidden] { display: none; }
.pdf-bar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 2px;
  height: 36px;
  padding: 0 6px;
  border-bottom: 1px solid var(--line);
  user-select: none;
  white-space: nowrap;
  overflow: hidden;
}
.pdf-bar button { flex: none; display: flex; padding: 4px; border: none; border-radius: 6px; background: none; color: var(--muted); }
.pdf-bar button:hover { background: rgba(255, 255, 255, .067); color: var(--text); }
.pdf-bar svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
.pdf-bar i { align-self: stretch; width: 1px; margin: 9px 6px; background: var(--line); }
.pdf-bar b { flex: 1; }
/* Numer strony jak w Obsidianie (.pdf-page-input): pole z numerem i „z N” obok, oba wyśrodkowane
   na wysokości wersalików. */
.pdf-page {
  min-width: 28px;
  height: 22px;
  padding: 0 6px;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: var(--field);
  color: var(--text);
  font-family: inherit;
  font-size: 12px;
  line-height: 20px;
  text-align: center;
  outline: none;
}
.pdf-page:focus { border-color: var(--accent); }
.pdf-count { margin-left: 6px; color: var(--muted); font-size: 12px; text-box: trim-both cap alphabetic; }
.pdf-box { position: relative; flex: 1; min-height: 0; }
.pdf-scroll { position: absolute; inset: 0; overflow: auto; overscroll-behavior: contain; outline: none; }
/* Bez cieni, także pod notatkami z pliku PDF. */
.card.pdf .popup { box-shadow: none; }
.card.pdf:focus-within .head { display: block; } /* pole z numerem strony albo PDF z fokusem */
.card.pdf.active .head { pointer-events: none; }

/* Karta z tekstem (doc.js): karta tekstowa albo notatka .md, markdown na żywo. Tekst płynie bez stron,
   a dłuższy wydłuża kartę. Strony A4 (794 × 1123 punktów, margines 96, numer na dole) powstają dopiero
   w eksporcie i w jego podglądzie, z typografią notatki w Obsidianie (obsidian.asar: 16 px, linia 1,5,
   nagłówki --h1-size… --h6-size). Tekst bez kursora, edytor (CodeMirror) i strony mają te same linie i te
   same klasy; reguły CodeMirror nadpisują tu selektory o większej wadze, bo jego style wchodzą do okna później. */
.card.doc, .doc-text, .doc-print {
  --doc-font: var(--font);
  --doc-mono: ui-monospace, SFMono-Regular, "Cascadia Mono", Menlo, Consolas, monospace;
  --doc-faint: #666666;  /* --text-faint */
  --doc-code: #242424;   /* --background-primary-alt */
  --doc-line: var(--line);
  --doc-accent: var(--accent);
  --doc-hl: rgb(255 208 0 / 50%); /* zakreślenie: żółte jak zakreślacz, przejrzyste, żeby było widać zaznaczenie */
}
/* Jasnoszara karta z ciemnym tekstem (kolory jasnego motywu Obsidiana: --text-muted, --code-background
   o krok ciemniejsze od karty). Zmienne także na .doc-text, bo reguła wyżej ustawia je na nim samym. */
.card.doc, .card.doc .doc-text {
  --text: var(--doc-ink);
  --muted: #5c5c5c;
  --doc-faint: #999999;
  --doc-code: #e4e4e4;
  --doc-line: #d0d0d0;
}
.card.doc { background: var(--doc-bg); color: var(--text); color-scheme: light; }
/* Na canvasie jak dotychczasowe pole tekstowe: 14 px, linia 20 px, 19 px od góry i dołu i 24 px z boku
   (więcej niż 16 px w Obsidianie: prośba użytkownika, „ciężko przeczytać tak blisko”). Wielkie litery
   pierwszego wiersza i linia pisma ostatniego są wtedy ok. 24 px od krawędzi, tak jak tekst z boku, więc
   odstęp jest równy dookoła i przy jednej linijce, i przy długim tekście. Karta z samym tekstem ma
   2 + 38 + 20 × liczba linii punktów i kończy się na kropkach siatki. Pierwszy nagłówek bez odstępu nad
   sobą. Tekstu bez edytora nie zaznacza się przeciąganiem: to przesuwa kartę. Edytor wypełnia całą kartę,
   także wyższą niż tekst. */
.card.doc .doc-text { font-size: 14px; line-height: 20px; }
.doc-body, .doc-editor, .doc-note { padding: 19px 24px; }
.doc-body { -webkit-user-select: none; user-select: none; }
.doc-editor { flex-grow: 1; display: flex; flex-direction: column; }
.doc-editor .cm-editor { flex-grow: 1; }
.doc-body.doc-text > .cm-line:first-child, .doc-editor .cm-content > .cm-line:first-child { padding-top: 0; }
.doc-body[hidden], .doc-editor[hidden], .doc-note[hidden] { display: none; }
.doc-note { color: var(--muted); }
/* Obraz w tekście nie szerszy niż karta. */
.card .md-img { max-width: 100%; height: auto !important; }
/* Niewidoczna kopia tekstu: szerokość najdłuższego słowa albo najdłuższej linii (doc.js: docMinWidth). */
.doc-text.doc-min, .doc-text.doc-max { position: absolute; left: 0; top: 0; width: min-content; visibility: hidden; pointer-events: none; word-break: normal; overflow-wrap: normal; }
.doc-text.doc-max { width: max-content; }
/* Strona w eksporcie. */
.doc-page { position: absolute; left: 0; top: 0; width: 794px; height: 1123px; }
.doc-page-body { position: absolute; left: 96px; top: 96px; width: 602px; }
.doc-page-num { position: absolute; left: 0; right: 0; bottom: 42px; text-align: center; font: 12px/12px var(--doc-font); color: var(--doc-faint); }
/* Niewidoczne pole, w którym mierzą się linie stron (doc.js: measure). */
.doc-measure { position: fixed; left: -10000px; top: 0; width: 602px; visibility: hidden; pointer-events: none; }
/* Miejsce, w które trafi obraz przeciągany na tekst dokumentu. */
.doc-drop { position: absolute; left: 24px; right: 24px; margin-top: calc(-1px * var(--unzoom)); border-top: calc(2px * var(--unzoom)) solid var(--accent); pointer-events: none; z-index: 2; }
/* Podgląd dokumentu w oknie eksportu: strony obok siebie, przewijane w bok, a gdy się mieszczą, na środku. */
.doc-strip { display: flex; gap: 12px; width: 100%; height: 100%; overflow-x: auto; overflow-y: hidden; }
.doc-strip > :first-child { margin-left: auto; }
.doc-strip > :last-child { margin-right: auto; }
.doc-thumb { flex: none; height: 100%; aspect-ratio: 794 / 1123; background: #fff; border-radius: 2px; overflow: hidden; }
#export-preview .doc-thumb canvas { width: 100%; height: 100%; }

.doc-text {
  font-family: var(--doc-font);
  font-size: 16px;
  font-weight: 400;
  line-height: 1.5;
  color: var(--text);
  text-align: left;
  white-space: break-spaces; /* jak .cm-lineWrapping w CodeMirror */
  word-break: break-word;
  overflow-wrap: anywhere;
  tab-size: 4;
  /* Bez dzielenia wyrazów z łącznikiem (prośba użytkownika) i bez text-wrap: pretty, bo WebKit skraca nim
     wszystkie wiersze akapitu do równej długości, a przy ramce karty wygląda to jak pusty pas z prawej. */
}
.doc-text .cm-editor.cm-focused { outline: none; }
.doc-text .cm-editor .cm-scroller { flex-grow: 1; font-family: inherit; line-height: inherit; overflow: visible; }
.doc-text .cm-editor .cm-scroller .cm-content { padding: 0; caret-color: var(--text); }
.doc-text div.cm-line { padding: 0; }
.doc-text .cm-line.md-gone { display: none; }
.doc-text .cm-line.md-h1 { font-size: 1.618em; line-height: 1.2; font-weight: 700; padding-top: .6em; }
.doc-text .cm-line.md-h2 { font-size: 1.462em; line-height: 1.2; font-weight: 680; padding-top: .6em; }
.doc-text .cm-line.md-h3 { font-size: 1.318em; line-height: 1.3; font-weight: 660; padding-top: .5em; }
.doc-text .cm-line.md-h4 { font-size: 1.188em; line-height: 1.4; font-weight: 640; padding-top: .4em; }
.doc-text .cm-line.md-h5 { font-size: 1.076em; line-height: 1.5; font-weight: 620; padding-top: .3em; }
.doc-text .cm-line.md-h6 { font-size: 1em; line-height: 1.5; font-weight: 600; padding-top: .3em; }
/* Nagłówek w kilku wierszach ma je podobnej długości. Kawałek z klasą md-nb (słowo z jednej litery ze
   spacją itp., doc.js: GLUE) nie łamie się. */
.doc-text .cm-line:is(.md-h1, .md-h2, .md-h3, .md-h4, .md-h5, .md-h6) { text-wrap-style: balance; }
.doc-text .md-nb { text-wrap-mode: nowrap; }
.doc-text .cm-line.md-pre { font-family: var(--doc-mono); font-size: .875em; background: var(--doc-code); padding: 0 16px; }
.doc-text .cm-line.md-pre-first { border-top-left-radius: 4px; border-top-right-radius: 4px; }
.doc-text .cm-line.md-pre-last { border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; }
.doc-text .cm-line.md-quote { border-left: 2px solid var(--doc-accent); padding-left: 1em; }
.doc-text .cm-line.md-d1 { padding-left: 1.6em; }
.doc-text .cm-line.md-d2 { padding-left: 3.2em; }
.doc-text .cm-line.md-d3 { padding-left: 4.8em; }
.doc-text .cm-line.md-d4 { padding-left: 6.4em; }
.doc-text .cm-line.md-d5 { padding-left: 8em; }
.doc-text .cm-line.md-d6 { padding-left: 9.6em; }
.doc-text .cm-line.md-done { color: var(--doc-faint); text-decoration: line-through; }
/* Ciąg dalszy linii z poprzedniej strony: bez odstępu nad nagłówkiem. */
.doc-text .cm-line.md-cont { padding-top: 0; }
.doc-text .md-strong { font-weight: 700; }
.doc-text .md-em { font-style: italic; }
.doc-text .md-strike { text-decoration: line-through; }
.doc-text .md-code { font-family: var(--doc-mono); font-size: .875em; background: var(--doc-code); border-radius: 3px; }
.doc-text .md-link { color: var(--doc-accent); text-decoration: underline; text-underline-offset: 2px; }
.doc-text .md-mark { color: var(--doc-faint); }
/* Zakreślenie (==tekst==) i kolor tekstu (styl w linii, doc.js: stylePairs). */
.doc-text .md-hl { background: var(--doc-hl); }
/* Punktor, numer i pole zadania wiszą na lewo od tekstu, we wcięciu linii. */
.doc-text .md-bullet, .doc-text .md-task { display: inline-block; width: 1.6em; margin-left: -1.6em; text-align: center; }
.doc-text .md-bullet::before { content: '•'; color: var(--doc-faint); }
.doc-text .md-num { display: inline-flex; justify-content: flex-end; width: 1.6em; margin-left: -1.6em; padding-right: .35em; }
.doc-text .md-task input { width: 13px; height: 13px; margin: 0; vertical-align: -1px; accent-color: var(--doc-accent); }
.doc-text .md-hr { display: inline-block; width: 100%; vertical-align: middle; border-top: 2px solid var(--doc-line); }
.doc-text .md-img { display: inline-block; vertical-align: top; border-radius: 4px; }
.doc-text .md-box { display: inline-block; vertical-align: top; } /* miejsce na kartę w dokumencie obok kart (doc.js: boxLine) */
.doc-text .md-miss { color: var(--doc-faint); font-style: italic; }

/* PDF z dokumentu: jasne strony, jak eksport do PDF w Obsidianie. */
.doc-print { background: #fff; }
.doc-print, .doc-print .doc-text {
  --text: #222222;
  --doc-faint: #ababab;
  --doc-code: #f6f6f6;
  --doc-line: #e0e0e0;
}
.doc-print .doc-page { background: #fff; outline: none; border-radius: 0; }

/* Slajd prezentacji (doc.js: prezentacja): 1280 × 720 punktów, treść w polu z marginesem 96 × 72. Kolory
   z koloru slajdów canvasu (doc.js: paintSlide, zmienne --slide-*), bez niego jasny jak strona dokumentu. Tekst, obrazy i linki stoją w nim w miejscach z układu (slidePlace), tekst
   w wielkości, przy której się mieści; pierwszy nagłówek bez odstępu nad sobą. Slajd z samym tytułem ma
   tekst na środku. Kolory slajdu dostaje tylko tekst, bo link to kopia karty z canvasu w kolorach aplikacji
   (w eksporcie bez planszy, więc --unzoom i krój jak na niej). */
.slide { position: absolute; left: 0; top: 0; width: 1280px; height: 720px; background: var(--slide-bg, #fff); overflow: hidden; transform-origin: 0 0; color-scheme: light; }
/* Pole zadania rośnie z tekstem slajdu w tej samej proporcji co w karcie (13 px przy tekście 14 px).
   `font-size: inherit`, bo pole formularza nie dziedziczy wielkości tekstu i em liczyłby się od 13 px. */
.slide-text .md-task input { font-size: inherit; width: calc(13em / 14); height: calc(13em / 14); vertical-align: -.07em; }
.slide .doc-text, .slide-measure .doc-text {
  --text: var(--slide-text, #222222);
  --doc-faint: var(--slide-faint, #ababab);
  --doc-code: var(--slide-code, #f6f6f6);
  --doc-line: var(--slide-line, #e0e0e0);
  --doc-accent: var(--slide-accent, var(--accent));
}
.slide-card { --unzoom: 1; --zm: 1; color: var(--text); font: 14px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; }
.slide-body { position: absolute; left: 48px; top: 48px; width: 1184px; height: 624px; overflow: hidden; } /* doc.js: SLIDE_PAD_X, SLIDE_PAD_Y */
.slide-body > * { position: absolute; }
.slide-text { line-height: 1.35; }
.slide-text > .cm-line:first-child { padding-top: 0; }
/* Nagłówki slajdu większe niż w dokumencie (2, 1,6 i 1,3 wielkości tekstu; mój wybór). */
.slide-text .cm-line.md-h1 { font-size: 2em; }
.slide-text .cm-line.md-h2 { font-size: 1.6em; }
.slide-text .cm-line.md-h3 { font-size: 1.3em; }
.slide.title .slide-text { text-align: center; }
.slide-body > .md-img { object-fit: cover; } /* przycięty jak w kafelku na canvasie (.card.image.in-slide) */
/* Każdy kafelek slajdu (obraz, strona, PDF, kalendarz) ma ten sam róg co karta na canvasie (.card), żeby siatka
   kafelków była równa (prośba użytkownika: „radius zawsze konsekwentny i spójny na rogach”). --tile-r to ten
   róg w punktach slajdu; na slajdzie z canvasu razy skala ramki (doc.js: frameSlide, TILE_R). Kopia karty jest
   przeskalowana (doc.js: cardInSlot), więc ma ten sam róg w swoich punktach (--slot-r) i, jak obraz, nie ma
   szarej obwódki karty. */
.slide { --tile-r: 9px; }
.slide-body > .md-img, .slide-pdf { border-radius: var(--tile-r); }
.slide-card > .card:not(.shape) { border-color: transparent; border-radius: var(--slot-r); }
.slide-card > .card.link::after { border-color: transparent; border-radius: var(--slot-r); }
.slide-card > .card.link .link-body { border-radius: calc(var(--slot-r) / var(--k, 1)); }
/* Pod stroną na żywo bez ciemnego tła karty: na wygładzonej krawędzi strony w ułamku piksela prześwitywało szarą
   kreską, którą na canvasie zakrywa obwódka. */
.slide-card > .card.link[data-view="live"], .slide-card > .card.link[data-view="live"] .link-body { background: none; }
.slide-card > .card :is(.cal, .pdf-body) { border-radius: calc(var(--slot-r) - 1px); }
.slide-miss { color: var(--doc-faint); font-size: 16px; font-style: italic; }
/* W pokazie tekst, który się nie mieści, i PDF przewijają się w swoim miejscu (doc.js: showPdfIn). Pasek
   przewijania widać od razu, żeby było wiadomo, że jest dalszy ciąg. PDF: jasne strony na szarym tle. */
.slide-scroll, .slide-pdf { overflow-y: auto; overscroll-behavior: contain; outline: none; }
.slide-pdf { position: absolute; inset: 0; background: #e8e8e8; }
.slide-pdf-note { margin: 0; padding: 24px; color: #666; font: 16px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; }
.slide-scroll::-webkit-scrollbar, .slide-pdf::-webkit-scrollbar { width: 10px; }
.slide-scroll::-webkit-scrollbar-thumb, .slide-pdf::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, .22); border: 2px solid transparent; border-radius: 5px; background-clip: padding-box; }
.slide-scroll::-webkit-scrollbar-thumb { background-color: var(--slide-thumb, rgba(0, 0, 0, .22)); }
.slide-measure { position: fixed; left: -10000px; top: 0; visibility: hidden; pointer-events: none; }
/* Slajd z kart na canvasie w pokazie (doc.js: frameSlide): karty w swoich miejscach na całym slajdzie, bez marginesu. */
.slide.framed .slide-body { left: 0; top: 0; width: 1280px; height: 720px; }

/* Prezentacja obok kart (doc.js): slajd na canvasie to sekcja w kolorze slajdów, a karta z tekstem na nim ma
   tekst slajdu, bez tła i ramki (widać ją po najechaniu i w zaznaczeniu): 28 punktów (DECK_FS), linia 1,35
   i nagłówki jak .slide-text, bo pokaz rysuje slajd tak, jak wygląda tutaj. Kolory z koloru slajdów canvasu
   (doc.js: markFrames nadpisuje zmienne), tu jasny slajd jak domyślnie. */
.slide-frame, .in-slide {
  --frame-bg: #ffffff;
  --frame-text: #222222;
  --frame-faint: #ababab;
  --frame-code: #f6f6f6;
  --frame-line: #e0e0e0;
  --frame-accent: var(--accent);
  --frame-scheme: light;
}
.card.group.slide-frame { background: var(--frame-bg); }
.card.doc.in-slide { background: transparent; color-scheme: var(--frame-scheme); }
.card.doc.in-slide:not(.selected):not(:focus-within) { border-color: transparent; }
.card.doc.in-slide:hover:not(.selected):not(:focus-within) { border-color: var(--frame-line); }
.card.doc.in-slide, .card.doc.in-slide .doc-text {
  --text: var(--frame-text);
  --muted: var(--frame-faint);
  --doc-faint: var(--frame-faint);
  --doc-code: var(--frame-code);
  --doc-line: var(--frame-line);
  --doc-accent: var(--frame-accent);
}
.card.doc.in-slide .doc-text { font-size: 28px; line-height: 1.35; }
.card.doc.in-slide .doc-text .cm-line.md-h1 { font-size: 2em; }
.card.doc.in-slide .doc-text .cm-line.md-h2 { font-size: 1.6em; }
.card.doc.in-slide .doc-text .cm-line.md-h3 { font-size: 1.3em; }
.card.doc.in-slide .doc-text .md-task input { font-size: inherit; width: calc(13em / 14); height: calc(13em / 14); vertical-align: -.07em; }

/* Dokument obok kart (doc.js: makeDoc): strona A4 na canvasie to biała sekcja z numerem na dole, jak strona
   w eksporcie, a karta z tekstem na niej ma tekst strony (16 punktów, linia 1,5: te same linie co w podziale
   na strony), bez tła i ramki, jak na slajdzie. W eksporcie i druku strona jest bez obwódki i zaokrągleń
   (.page-print). */
.card.group.page-frame { background: #ffffff; }
.page-num { position: absolute; left: 0; right: 0; bottom: 42px; text-align: center; font: 12px/12px -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; color: #999999; pointer-events: none; }
.card.doc.in-page { background: transparent; }
.card.doc.in-page:not(.selected):not(:focus-within) { border-color: transparent; }
.card.doc.in-page:hover:not(.selected):not(:focus-within) { border-color: #e0e0e0; }
.card.doc.in-page .doc-text { font-size: 16px; line-height: 1.5; }
.page-print .card.group.page-frame { border-color: transparent; border-radius: 0; }

/* Pokaz prezentacji (doc.js: pokaz) jak w Figmie i Keynote: czarne tło na całe okno, slajd na środku,
   a na dole pasek z numerem slajdu, który znika razem z kursorem, gdy mysz stoi (.awake). */
#present { position: fixed; inset: 0; z-index: 50; background: #000; overflow: hidden; cursor: none; }
#present[hidden] { display: none; }
#present.awake { cursor: default; }
.present-stage { position: absolute; inset: 0; }
.present-bar {
  position: absolute;
  left: 50%;
  bottom: 24px;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 4px;
  background: var(--bar);
  border: 1px solid var(--button-hover);
  border-radius: 10px;
  transform: translateX(-50%);
  opacity: 0;
  transition: opacity .2s;
  user-select: none;
}
#present.awake .present-bar, .present-bar:hover { opacity: 1; }
.present-bar span { min-width: 64px; text-align: center; font-variant-numeric: tabular-nums; text-box: trim-both cap alphabetic; }
.present-bar button { display: flex; padding: 6px; border: none; border-radius: 6px; background: none; color: var(--text); }
.present-bar button:hover:not(:disabled) { background: rgba(255, 255, 255, .067); }
.present-bar button:disabled { opacity: .35; }
.present-bar i { align-self: stretch; width: 1px; margin: 3px 3px; background: var(--line); }
.present-bar svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }

/* Kalendarz na canvasie (calendar.js): nagłówek z miesiącem i przyciskami, nazwy dni i 6 tygodni
   w siatce, która wypełnia kartę przy każdym rozmiarze. Numer dnia na środku u góry, jak w widoku
   miesiąca w Kalendarzu Google; dzisiejszy w kółku w kolorze akcentu, dni sąsiednich miesięcy
   przygaszone. Leży pod kartami jak sekcja (#groups) i, jak ona, przepuszcza kliknięcia przy narzędziu
   tekstu. Przyciski są nad warstwą do przesuwania (`head`). Tekst wyrównany do wysokości wersalików. */
.cal { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; border-radius: 8px; user-select: none; }
.cal-top { flex: none; display: flex; align-items: center; gap: 12px; height: 60px; padding: 0 12px 0 20px; }
.cal-title { flex: 1; min-width: 0; overflow-x: clip; text-overflow: ellipsis; white-space: nowrap; font-size: 20px; font-weight: 600; text-box: trim-both cap alphabetic; }
.cal-nav { position: relative; z-index: 2; display: flex; gap: 2px; } /* nad niewidocznymi uchwytami rozmiaru (z-index 1), które po oddaleniu sięgają w głąb karty */
.cal-nav button { display: flex; padding: 6px; border: none; border-radius: 6px; background: none; color: var(--muted); }
.cal-nav button:hover { background: rgba(255, 255, 255, .067); color: var(--text); }
.cal-nav button span { padding: 4.5px 4px; font-size: 13px; text-box: trim-both cap alphabetic; }
.cal-nav svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
.cal-week { flex: none; display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); border-bottom: 1px solid var(--line); }
.cal-week span { padding: 10px 4px; overflow: hidden; text-align: center; font-size: 12px; color: var(--muted); text-box: trim-both cap alphabetic; }
.cal-grid { flex: 1; min-height: 0; display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); grid-template-rows: repeat(6, minmax(0, 1fr)); }
.cal-grid > .cal-day { display: flex; flex-direction: column; align-items: center; padding-top: 6px; border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.cal-grid > .cal-day:nth-child(7n) { border-right: none; }
.cal-grid > .cal-day:nth-child(n + 36) { border-bottom: none; }
.cal-grid > .cal-day > span { width: 28px; padding: calc((28px - 1cap) / 2) 0; border-radius: 50%; text-align: center; font-size: 13px; text-box: trim-both cap alphabetic; }
.cal-grid > .cal-day.out > span { color: var(--off); }
.cal-grid > .cal-day.today > span { background: var(--accent); color: #fff; font-weight: 600; }
.card.calendar:focus-within .head { display: block; }
#board.write .card.calendar, #board.draw .card.calendar { pointer-events: none; }
.card.calendar .grip { pointer-events: auto; }

/* Wydarzenia w dniach kalendarza (calendar.js), jak w widoku miesiąca Kalendarza Google: całodniowe
   i wielodniowe jako pasek w przyciemnionym akcencie, z godziną jako kropka, godzina i nazwa. Leżą pod
   warstwą do przesuwania, więc podświetla je calHover, a chwyta calGrab. Dzień, do którego trafi
   przeciągana karta albo wydarzenie, ma ramkę w kolorze akcentu. */
.cal-nav i { align-self: stretch; width: 1px; margin: 7px 4px; background: var(--line); }
.cal-day { position: relative; min-height: 0; }
.cal-evs { align-self: stretch; display: flex; flex-direction: column; gap: 2px; min-height: 0; padding: 4px 4px 0; overflow: hidden; }
.cal-ev, .cal-more { flex: none; display: flex; align-items: center; gap: 5px; height: 20px; padding: 0 6px; border-radius: 4px; font-size: 12px; white-space: nowrap; overflow: hidden; }
.cal-ev::before { content: ''; flex: none; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
.cal-ev.bar::before, .cal-ev:has(> i)::before { display: none; }
.cal-ev.bar { background: color-mix(in srgb, var(--accent) 30%, var(--bg)); }
.cal-ev.hot { background: rgba(255, 255, 255, .08); }
.cal-ev.bar.hot { background: color-mix(in srgb, var(--accent) 42%, var(--bg)); }
/* Wydarzenie pokazane w podglądzie (calPeek): w kolorze akcentu, jak zaznaczone wydarzenie w Kalendarzu
   Apple, żeby wśród wielu wydarzeń było od razu widać, którego dotyczy podgląd. */
.cal-ev.open, .cal-ev.bar.open { background: var(--accent); color: #fff; }
.cal-ev.open::before { background: #fff; }
.cal-ev.open .cal-time { color: inherit; }
.cal-day.out .cal-ev.open { opacity: 1; }
.cal-ev.lifted { opacity: .35; }
.cal-ev i { flex: none; display: flex; }
.cal-ev i svg { width: 12px; height: 12px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
/* Wielokropek przy długiej nazwie: overflow-x: clip, żeby akcenty i ogonki (Ź, g, ę) nad i pod przyciętym
   do wersalików tekstem zostały widoczne. */
.cal-time { flex: none; color: var(--muted); font-variant-numeric: tabular-nums; text-box: trim-both cap alphabetic; }
.cal-name { min-width: 0; overflow-x: clip; text-overflow: ellipsis; text-box: trim-both cap alphabetic; }
.cal-day.out .cal-ev { opacity: .6; }
.cal-more { color: var(--muted); }
.cal-more span { text-box: trim-both cap alphabetic; }
.cal-more.hot, .cal-more.open { background: rgba(255, 255, 255, .08); color: var(--text); }
/* Dzień z wydarzeniami, w którym żadne się nie mieści (mały kalendarz): kropka obok numeru. */
.cal-day.busy::after { content: ''; position: absolute; top: 18px; right: 8px; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
.cal-day.drop { background: color-mix(in srgb, var(--accent) 12%, transparent); box-shadow: inset 0 0 0 2px var(--accent); }
.card .head.cal-hot { cursor: pointer; }
#board.cal-dragging, #board.cal-dragging * { cursor: grabbing !important; }
.cal-ghost { position: fixed; left: 0; top: 0; z-index: 30; pointer-events: none; color: var(--text); background: var(--card-head); outline: 1px solid var(--accent); }
.cal-ghost.bar { background: color-mix(in srgb, var(--accent) 30%, var(--bg)); }
.cal-ghost i svg { width: 1em; height: 1em; }
/* Wkładanie i wyjmowanie (calendar.js): karta nad dniem przygaszona, żeby było widać dzień, w dniu miejsce
   na wydarzenie z nazwą, puszczona karta zmniejsza się do niego (.cal-fly), a wydarzenie, które właśnie
   trafiło do dnia, przez chwilę ma kolor tego miejsca (.landed). Ramka i miejsce tylko wchodzą płynnie:
   znikają od razu, bo zrzut kalendarza dla GPU (gl.js) złapałby je w połowie przejścia. */
.card.dragging { transition: opacity 120ms ease-out; }
.card.cal-in { opacity: .35; }
.cal-day.drop { animation: cal-drop 120ms ease-out; }
@keyframes cal-drop { from { background-color: transparent; box-shadow: inset 0 0 0 0 var(--accent); } }
.cal-slot { animation: cal-appear 120ms ease-out; }
@keyframes cal-appear { from { opacity: 0; } }
.cal-slot.cal-slot-ev { position: static; flex: none; display: flex; align-items: center; height: 20px; padding: 0 6px; white-space: nowrap; overflow: hidden; text-box: none; }
.cal-ev.landed, .cal-yd.landed { animation: cal-landed 600ms ease-out; }
@keyframes cal-landed { from { background-color: color-mix(in srgb, var(--accent) 55%, transparent); color: #fff; } }
.cal-fly { position: fixed; left: 0; top: 0; z-index: 30; margin: 0; transform-origin: 0 0; pointer-events: none; }
@media (prefers-reduced-motion: reduce) {
  .card.dragging { transition: none; }
  .cal-day.drop, .cal-slot, .cal-ev.landed, .cal-yd.landed { animation: none; }
}

/* Widoki kalendarza (calendar.js): zakładki Dzień, Tydzień, Miesiąc, Rok w nagłówku, jak przełącznik
   w Kalendarzu Apple; w wąskiej karcie w drugim wierszu nagłówka. Zakładka przeciągnięta z nagłówka to
   pod kursorem obrys nowej karty z tym widokiem. */
.cal { container-type: inline-size; }
.cal-tabs { position: relative; z-index: 2; flex: none; display: flex; gap: 2px; padding: 2px; border-radius: 8px; background: var(--card-head); }
.cal-tabs button { display: flex; justify-content: center; padding: 0; border: none; border-radius: 6px; background: none; color: var(--muted); }
.cal-tabs button span { padding: 7.5px 10px; font-size: 13px; text-box: trim-both cap alphabetic; }
.cal-tabs button:hover { color: var(--text); }
.cal-tabs button.on { background: var(--button-hover); color: var(--text); }
@container (width < 760px) {
  .cal-top { flex-wrap: wrap; row-gap: 14px; height: auto; padding: 20px 12px 14px 20px; }
  .cal-tabs { order: 3; flex: 1 0 100%; }
  .cal-tabs button { flex: 1; }
}
.cal-fit { flex: 1; min-height: 0; position: relative; display: flex; flex-direction: column; }
.cal-view { flex: 1; min-height: 0; display: flex; flex-direction: column; }
/* Karta mniejsza niż najmniejszy układ widoku (calScale): widok w tym układzie, pomniejszony w całości. */
.cal-view.scaled { position: absolute; left: 0; top: 0; flex: none; transform: scale(var(--k)); transform-origin: 0 0; }
.cal-tear { position: fixed; left: 0; top: 0; z-index: 30; pointer-events: none; padding: 14px 16px; border: 1.5px solid var(--accent); border-radius: 8px; background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--text); font-size: 13px; text-box: trim-both cap alphabetic; }

/* Dzień i tydzień: kolumny dni z datą, wydarzeniami całodniowymi i godzinami. Godziny wypełniają kartę
   (--cal-hours wierszy), wydarzenie z godziną to blok od początku do końca, a zachodzące na siebie stoją
   obok siebie. Krótki blok ma godzinę i nazwę w jednym wierszu. Puszczane wydarzenie albo karta pokazuje
   w godzinach blok z godziną początku (.cal-slot). */
.cal-tw-head, .cal-tw-all, .cal-tw-body { display: grid; grid-template-columns: 52px repeat(var(--cal-cols), minmax(0, 1fr)); }
.cal-tw-head { flex: none; border-bottom: 1px solid var(--line); }
.cal-tw-day { display: flex; align-items: center; justify-content: center; gap: 6px; min-width: 0; padding: 8px 4px; font-size: 12px; color: var(--muted); }
.cal-tw-name { min-width: 0; overflow-x: clip; text-overflow: ellipsis; white-space: nowrap; text-box: trim-both cap alphabetic; }
.cal-tw-num { flex: none; min-width: 24px; padding: calc((24px - 1cap) / 2) 4px; border-radius: 12px; text-align: center; font-size: 13px; color: var(--text); font-variant-numeric: tabular-nums; text-box: trim-both cap alphabetic; }
.cal-tw-day.today .cal-tw-num { background: var(--accent); color: #fff; font-weight: 600; }
.cal-tw-all { flex: none; border-bottom: 1px solid var(--line); }
.cal-all-label { padding: 11px 8px 0 0; text-align: right; font-size: 10px; color: var(--muted); text-box: trim-both cap alphabetic; }
.cal-allday { min-width: 0; min-height: calc(var(--cal-all) * 22px + 6px); border-left: 1px solid var(--line); }
.cal-allday .cal-evs { padding: 4px; }
.cal-tw-body { flex: 1; min-height: 0; grid-template-rows: minmax(0, 1fr); }
.cal-hours, .cal-lines { grid-row: 1; display: grid; grid-template-rows: repeat(var(--cal-hours), minmax(0, 1fr)); min-height: 0; }
.cal-hours { grid-column: 1; }
.cal-hours span { padding: 5px 8px 0 0; text-align: right; font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; text-box: trim-both cap alphabetic; }
.cal-lines { grid-column: 2 / -1; }
.cal-lines div + div { border-top: 1px solid var(--line); }
.cal-col { grid-row: 1; min-width: 0; border-left: 1px solid var(--line); }
.cal-ev.blk { position: absolute; flex-direction: column; align-items: stretch; justify-content: flex-start; gap: 6px; padding: 6px 6px 3px; background: color-mix(in srgb, var(--accent) 30%, var(--bg)); }
.cal-ev.blk::before { display: none; }
.cal-ev.blk .cal-name { order: -1; font-weight: 600; }
.cal-ev.blk i { position: absolute; top: 5px; right: 5px; }
.cal-ev.blk:has(> i) .cal-name { padding-right: 16px; }
.cal-ev.blk.short { flex-direction: row; align-items: center; gap: 5px; padding: 0 6px; }
.cal-ev.blk.short .cal-name { order: 0; }
.cal-ev.blk.hot { background: color-mix(in srgb, var(--accent) 42%, var(--bg)); }
.cal-ev.blk.open { background: var(--accent); }
.cal-ghost.blk { position: fixed; background: color-mix(in srgb, var(--accent) 30%, var(--bg)); }
.cal-slot { position: absolute; left: 2px; right: 2px; z-index: 1; padding: 6px; border-radius: 4px; background: color-mix(in srgb, var(--accent) 55%, transparent); color: #fff; font-size: 12px; font-variant-numeric: tabular-nums; pointer-events: none; text-box: trim-both cap alphabetic; }

/* Rok: 12 miesięcy po 6 tygodni, bez dni sąsiednich miesięcy, jak widok roku w Kalendarzu Apple. Dzień
   z wydarzeniami ma tło w przyciemnionym akcencie, dzisiejszy pełny akcent. Nazwa miesiąca i dzień
   z wydarzeniami działają jak przyciski (calGrab). */
.cal-v-year { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-rows: repeat(3, minmax(0, 1fr)); gap: 12px 28px; padding: 4px 20px 16px; }
.cal-v-year.tall { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: repeat(4, minmax(0, 1fr)); gap: 8px 16px; } /* wąska karta (calScale) */
.cal-ym { min-width: 0; min-height: 0; display: flex; flex-direction: column; gap: 8px; }
.cal-ym-name { align-self: flex-start; margin-left: -6px; padding: 5px 6px; border-radius: 6px; font-size: 14px; font-weight: 600; text-box: trim-both cap alphabetic; }
.cal-ym-name.hot { background: rgba(255, 255, 255, .08); }
.cal-ym-week, .cal-ym-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.cal-ym-week span { text-align: center; font-size: 10px; color: var(--muted); text-box: trim-both cap alphabetic; }
.cal-ym-grid { flex: 1; min-height: 0; grid-template-rows: repeat(6, minmax(0, 1fr)); }
.cal-yd { display: flex; align-items: center; justify-content: center; }
.cal-yd > span { min-width: 22px; padding: calc((22px - 1cap) / 2) 0; border-radius: 11px; text-align: center; font-size: 11px; font-variant-numeric: tabular-nums; text-box: trim-both cap alphabetic; }
.cal-yd.has > span { background: color-mix(in srgb, var(--accent) 30%, var(--bg)); }
.cal-yd.has.hot > span { background: color-mix(in srgb, var(--accent) 42%, var(--bg)); }
.cal-yd.today > span { background: var(--accent); color: #fff; font-weight: 600; }

/* 4 miesiące: kolumna na miesiąc, a w niej 31 wierszy dni, które wypełniają kartę, więc ten sam numer dnia
   jest na tej samej wysokości w każdej kolumnie. W wierszu numer (dzisiejszy w akcencie), dzień tygodnia
   i wydarzenia obok siebie: dzielą miejsce po równo, krótsze nie szersze niż ich nazwa. W wąskiej karcie,
   bez miejsca na wydarzenia, kropka na końcu wiersza (.busy). */
.cal-v-months { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
.cal-mc { min-width: 0; min-height: 0; display: grid; grid-template-rows: auto repeat(31, minmax(0, 1fr)); }
.cal-mc + .cal-mc { border-left: 1px solid var(--line); }
.cal-mc-name { padding: 10px 12px; overflow-x: clip; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 600; text-box: trim-both cap alphabetic; }
.cal-md { display: flex; align-items: center; gap: 4px; min-width: 0; padding: 0 6px 0 4px; border-top: 1px solid var(--line); }
.cal-md > .cal-md-num { flex: none; min-width: 22px; padding: calc((18px - 1cap) / 2) 4px; border-radius: 9px; text-align: center; font-size: 12px; font-variant-numeric: tabular-nums; text-box: trim-both cap alphabetic; }
.cal-md.today > .cal-md-num { background: var(--accent); color: #fff; font-weight: 600; }
.cal-md-wd { flex: none; min-width: 32px; font-size: 11px; color: var(--muted); white-space: nowrap; text-box: trim-both cap alphabetic; }
.cal-md > .cal-evs { flex: 1 1 0; align-self: stretch; flex-direction: row; align-items: center; gap: 2px; padding: 0; }
.cal-md .cal-ev { flex: 1 1 0; min-width: 0; max-width: max-content; }
.cal-md .cal-slot-ev { flex: 0 1 auto; min-width: 0; }
.cal-md.busy::after { top: calc(50% - 3px); }

/* Okno wydarzenia: nazwa, wiersze z ikoną (czas, powtarzanie, miejsce, notatka), stopka. Pytanie o zakres
   zmiany powtarzanego wydarzenia (.asking) zastępuje wiersze w tym samym oknie. */
.event-panel { width: min(540px, calc(100vw - 32px)); }
.event-form { display: flex; flex-direction: column; gap: 14px; padding: 18px 24px 20px; max-height: calc(100vh - 180px); overflow: auto; }
.event-panel input:not([type]), .event-panel input[type="date"], .event-panel input[type="time"], .event-panel input[type="number"], .event-panel select, .event-panel textarea {
  font: inherit;
  color: var(--text);
  background: var(--field);
  border: 1px solid var(--line);
  border-radius: 7px;
  padding: 6px 8px;
  outline: none;
}
.event-panel input:focus, .event-panel select:focus, .event-panel textarea:focus { border-color: var(--accent); }
.event-panel [hidden] { display: none !important; }
.event-panel .event-title { font-size: 18px; padding: 8px 10px; }
.event-row { display: flex; align-items: flex-start; gap: 12px; }
.event-row > svg { flex: none; width: 18px; height: 18px; margin-top: 8px; fill: none; stroke: var(--muted); stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
.event-fill { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: 10px; }
textarea.event-fill { display: block; resize: vertical; min-height: 64px; }
.event-line { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.event-dash, .event-word { color: var(--muted); text-box: trim-both cap alphabetic; }
.event-check, .event-ends label, .event-scope label { display: flex; align-items: center; gap: 8px; cursor: pointer; }
.event-check span, .event-ends span, .event-scope span { text-box: trim-both cap alphabetic; }
.event-panel input[type="checkbox"], .event-panel input[type="radio"] { width: 16px; height: 16px; margin: 0; accent-color: var(--accent); cursor: pointer; }
.event-panel input[type="number"] { width: 72px; }
.event-custom { align-self: stretch; display: flex; flex-direction: column; align-items: flex-start; gap: 12px; padding: 14px; border: 1px solid var(--line); border-radius: 8px; }
.event-days { display: flex; flex-wrap: wrap; gap: 4px; }
.event-days button { min-width: 40px; padding: 8px 6px; border-radius: 15px; font-size: 12px; }
.event-days button.on { background: var(--accent); border-color: var(--accent); color: #fff; }
.event-ends { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; }
.event-ends input[type="date"], .event-ends input[type="number"] { margin-left: 2px; }
.event-scope { display: none; flex-direction: column; gap: 14px; padding: 20px 24px; }
.event-scope p { margin: 0 0 4px; }
.event-panel.asking .event-form { display: none; }
.event-panel.asking .event-scope { display: flex; }
.event-foot { gap: 8px; }
.event-foot b { flex: 1; }
.event-foot button + button { margin-left: 0; }
#event-scope-cancel, #event-scope-ok, .event-panel.asking #event-delete, .event-panel.asking #event-save { display: none; }
.event-panel.asking #event-scope-cancel, .event-panel.asking #event-scope-ok { display: block; }
.event-list { display: flex; flex-direction: column; padding: 8px; max-height: 60vh; overflow: auto; }
.event-item { display: flex; gap: 16px; align-items: center; padding: 12px 16px; border: none; background: none; border-radius: 7px; text-align: left; }
.panel .event-item + .event-item { margin-left: 0; }
.event-item:hover { background: rgba(255, 255, 255, .067); }
.event-when { flex: none; width: 110px; color: var(--muted); font-variant-numeric: tabular-nums; }
.event-what { min-width: 0; padding: 3px 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Podgląd wydarzenia (calendar.js: calPeek), jak okienko wydarzenia w Kalendarzu Google: ikony u góry,
   pełna nazwa, kiedy, powtarzanie, miejsce, notatka i to, co jest w karcie (strona z linku jak na karcie,
   obraz, plik). Leży na canvasie obok wydarzenia, w punktach canvasu i w skali tekstu kalendarza (nazwa
   miesiąca 20, dni 13, wydarzenia 12), nad kartami. Bez cienia: obwódka jak na pasku nad zaznaczeniem.
   Tekst wyrównany do wysokości wersalików. */
.cal-peek { position: absolute; left: 0; top: 0; z-index: 6; display: flex; flex-direction: column; width: 280px; max-height: 440px; background: var(--bar); border: 1px solid var(--button-hover); border-radius: 10px; font-size: 13px; }
.cal-peek[hidden], .cal-peek [hidden] { display: none !important; }
/* Dzióbek wskazuje wydarzenie (calPeekPlace ustawia wysokość, .flip: podgląd z lewej). Kwadrat obrócony
   o 45° z obwódką na dwóch bokach, w tle podglądu: przykrywa jego obwódkę, więc wychodzi z niej bez przerwy.
   Pod przyciskami (z-index -1 nad tłem podglądu), więc nie zasłania ×. */
.cal-peek-tail { position: absolute; z-index: -1; left: -6px; top: 0; width: 11px; height: 11px; background: var(--bar); border: solid var(--button-hover); border-width: 0 0 1px 1px; transform: rotate(45deg); }
.cal-peek-tail.flip { left: auto; right: -6px; border-width: 1px 1px 0 0; }
.cal-peek-bar { position: relative; flex: none; display: flex; justify-content: flex-end; gap: 2px; padding: 4px 4px 0; user-select: none; }
.cal-peek-bar button { display: flex; padding: 5px; border: none; border-radius: 6px; background: none; color: var(--muted); }
.cal-peek-bar button:hover { background: rgba(255, 255, 255, .067); color: var(--text); }
.cal-peek-bar i { width: 1px; margin: 6px 3px; background: var(--line); }
.cal-peek-bar svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
/* Przewijana część zaczyna się 4 px wyżej, pod paskiem z ikonami (stąd position: relative paska), a ma 4 px
   więcej odstępu u góry: nazwa stoi tam, gdzie stała, a akcent nad wielką literą (Ź) mieści się w odstępie. */
.cal-peek-main { min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 11px; margin-top: -4px; padding: 6px 14px 14px; }
.cal-peek h2 { margin: 0; font-size: 15px; font-weight: 600; line-height: 1.3; overflow-wrap: anywhere; text-box: trim-both cap alphabetic; }
#event-peek-when { margin: -2px 0 3px; color: var(--muted); font-size: 12px; text-box: trim-both cap alphabetic; }
.cal-peek-row { display: flex; align-items: flex-start; gap: 9px; }
.cal-peek-row svg { flex: none; width: 14px; height: 14px; margin-top: calc((1cap - 14px) / 2); fill: none; stroke: var(--muted); stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
.cal-peek-row span { min-width: 0; white-space: pre-wrap; overflow-wrap: anywhere; text-box: trim-both cap alphabetic; }
.cal-peek-body { display: flex; flex-direction: column; gap: 9px; }
.cal-peek-body:empty { display: none; }
.cal-peek-link, .cal-peek-file { display: flex; width: 100%; padding: 0; overflow: hidden; border: 1px solid var(--line); border-radius: 8px; background: var(--card); font-size: 13px; text-align: left; text-box: none; }
.cal-peek-link:hover, .cal-peek-file:hover { background: var(--card); border-color: var(--card-line); }
.cal-peek-link { flex-direction: column; }
.cal-peek-shot { display: block; width: 100%; aspect-ratio: 1.91; object-fit: cover; background: var(--card-head); }
.cal-peek-link-title { padding: 10px 10px 0; font-weight: 600; line-height: 1.35; overflow-wrap: anywhere; text-box: trim-both cap alphabetic; }
.cal-peek-site { display: flex; align-items: center; gap: 6px; min-width: 0; padding: 8px 10px; color: var(--muted); font-size: 12px; }
.cal-peek-link-title + .cal-peek-site { padding-top: 6px; }
.cal-peek-site > span:last-child, .cal-peek-file span { min-width: 0; overflow-x: clip; text-overflow: ellipsis; white-space: nowrap; text-box: trim-both cap alphabetic; }
.cal-peek-file { align-items: center; gap: 8px; padding: 7px 10px; }
.cal-peek-file i { flex: none; display: flex; color: var(--muted); }
.cal-peek-file svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
.cal-peek-image { display: block; align-self: flex-start; max-width: 100%; max-height: 200px; border-radius: 6px; }
.cal-peek-text { white-space: pre-wrap; overflow-wrap: anywhere; color: var(--muted); font-size: 12px; line-height: 1.5; }
.cal-peek-text:empty { display: none; }
.cal-peek-wait { margin: 0; color: var(--muted); }

/* Łączenie i rozdzielanie kart (doc.js: mergeGhosts, mergeWrite, splitWrite): nowa karta rośnie razem z dopisywanym tekstem (wysokość
   z animacji, tekst poniżej przycięty), a przy jej dolnej krawędzi, gdzie powstaje tekst, jest mgła: miękkie
   plamy jaśniejszego akcentu i bieli, rozmyte i wtopione w kartę. Na czytanej karcie leży jej kopia bez tła
   i obwódki: obwódka się zapala, a tekst kopii jest przezroczysty poza paskiem światła, który przesuwa się
   przez niego z góry na dół (gradient przycięty do liter). */
/* Nad zwykłymi kartami, a pod zaznaczoną nową kartą (.card.selected: z-index 4). */
.card.merge-ghost { z-index: 3; pointer-events: none; background: transparent; border-color: transparent; }
.card.merge-ghost .merge-read, .card.merge-ghost .merge-read * { color: transparent; }
/* Tło bloku kodu i zakreślenia widać z karty pod kopią; w kopii zasłaniałoby jej tekst. */
.card.merge-ghost .merge-read * { background-color: transparent; }
.card.merge-ghost .merge-read {
  background: linear-gradient(to bottom, transparent 38%, var(--accent-hover) 50%, transparent 62%) no-repeat;
  background-size: 100% 300%;
  background-position: 0 100%;
  -webkit-background-clip: text;
  background-clip: text;
}
.card.merging { overflow: hidden; min-height: 0 !important; }
.merge-fog {
  position: absolute;
  left: -24px;
  right: -24px;
  bottom: -12px;
  height: 84px;
  pointer-events: none;
  background:
    radial-gradient(40% 60% at 30% 65%, color-mix(in srgb, var(--accent-hover) 28%, transparent), transparent),
    radial-gradient(35% 55% at 70% 55%, rgb(255 255 255 / 9%), transparent),
    radial-gradient(45% 60% at 55% 90%, color-mix(in srgb, var(--accent) 22%, transparent), transparent);
  filter: blur(14px);
}

/* Figury (doc.js: figury): karta z tekstem z kształtem. Tło i obwódkę rysuje SVG na całej karcie (.shape-fig): linia
   1 punktu canvasu jak ramka karty, w trakcie pisania w kolorze akcentu, a zaznaczona o 2 piksele ekranu grubsza,
   jak ramka z obwódką zaznaczonej karty (.card.selected). Leży pół punktu od brzegu karty, a grubsza rośnie w obie
   strony, więc kropki do łączenia stoją na środku linii (--rim). Tło jak tło karty z tekstem (--doc-bg, a bez niego
   --card). Sama karta jest przezroczysta i nie łapie kursora: łapie warstwa do przesuwania przycięta do figury,
   więc rogi karty poza elipsą, trójkątem i rombem przepuszczają kliknięcia do canvasu. Uchwyty rozmiaru zostają
   w rogach karty, jak w Figmie. Kropki lewego i prawego boku trójkąta stoją na skosach, ćwierć szerokości od
   boku karty (app.js: sidePoint).
   Tekst stoi na środku prostokąta, który mieści się w figurze: --sx to odstęp z boku w procentach szerokości,
   --cy środek w pionie, --px i --py odstępy tekstu (doc.js: SHAPES). Leży poza układem karty, a wysokość,
   w której się mieści, karta dostaje z doc.js (fitDocs). Edytor zajmuje całą figurę (w trójkącie dolną
   połowę) z tymi samymi odstępami, więc tekst w trakcie pisania łamie się i stoi tak samo. */
.card.shape { background: none; border-color: transparent; border-radius: 0; pointer-events: none; }
.card.shape.selected, .card.shape:focus-within { border-color: transparent; outline: none; }
.card.shape > .head { border-radius: 0; pointer-events: auto; }
.card.shape[data-shape="circle"] > .head { clip-path: ellipse(50% 50% at 50% 50%); }
.card.shape[data-shape="triangle"] > .head { clip-path: polygon(50% 0, 100% 100%, 0 100%); }
.card.shape[data-shape="diamond"] > .head { clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%); }
.card.shape > .grip, .card.shape > .doc-body, .card.shape > .doc-editor { pointer-events: auto; }
.shape-fig { position: absolute; left: -1px; top: -1px; width: calc(100% + 2px); height: calc(100% + 2px); overflow: visible; pointer-events: none; }
.shape-fig path, .shape-draft path { fill: var(--doc-bg, var(--card)); stroke: var(--card-line); stroke-width: 1px; stroke-linejoin: round; }
.card.shape:focus-within .shape-fig path { stroke: var(--accent); }
.card.shape.selected .shape-fig path, .shape-draft path { stroke: var(--accent); stroke-width: calc(1px + 2px * var(--unzoom)); }
.card.shape[data-shape="rectangle"] .shape-fig path, .shape-draft.rectangle path { stroke-linejoin: miter; }
.card.shape.selected > .port { --rim: -.5px; }
.card.shape[data-shape="triangle"] > .port[data-side="left"] { left: calc(25% + var(--rim) - var(--half)); }
.card.shape[data-shape="triangle"] > .port[data-side="right"] { right: calc(25% + var(--rim) - var(--half)); }
.card.shape > .doc-body:not(.doc-min, .doc-max) { position: absolute; left: var(--sx); right: var(--sx); top: var(--cy); translate: 0 -50%; padding: var(--py) var(--px); text-align: center; }
.card.shape > .doc-editor { position: absolute; inset: 0; padding: var(--py) calc(var(--sx) + var(--px)); justify-content: center; text-align: center; }
.card.shape[data-shape="triangle"] > .doc-editor { top: 50%; }
.card.shape > .doc-editor .cm-editor { flex: none; }
/* Na slajdzie tekst figury ma kolor tekstu slajdu (.slide .doc-text), więc figura też: tło slajdu i obrys w kolorze
   jego tekstu, jak rysunek na slajdzie. */
.slide .shape-fig path { fill: var(--slide-bg, #ffffff); stroke: var(--slide-text, #222222); }
/* Figura w trakcie przeciągania narzędziem figur: tak, jak będzie wyglądać zaznaczona. */
.shape-draft { position: absolute; left: 0; top: 0; overflow: visible; pointer-events: none; z-index: 6; }
/* Lista figur nad przyciskiem Figury na dolnym pasku, jak lista proporcji: ikona i nazwa. */
#shape-menu {
  position: absolute;
  bottom: calc(100% + 6px);
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 4px;
  background: var(--bar);
  border: 1px solid var(--line);
  border-radius: 10px;
}
#shape-menu[hidden] { display: none; }
#tools #shape-menu button { align-items: center; gap: 8px; }
#shape-menu span { min-width: 64px; padding-right: 4px; font-size: 13px; text-align: left; text-box: trim-both cap alphabetic; }
#tools button.open { background: rgba(255, 255, 255, .067); }

/* Druk (app.js: printPages): okno drukuje tylko strony z #print, każdą na osobnej kartce w rozmiarze strony
   PDF (@page z nazwą, dopisywane przy druku), bez reszty okna. */
#print { display: none; }
@media print {
  html, body { width: auto; height: auto; overflow: visible; background: #fff; }
  body > :not(#print) { display: none !important; }
  #print { display: block; }
  .print-page { overflow: hidden; break-after: page; }
  .print-page:last-child { break-after: auto; }
  .print-page img { display: block; width: 100%; height: 100%; }
}

/* Obraz zaznaczenia do druku (app.js: exportCanvas z `paper`): jasne kolory jak na papierze, np. kalendarz
   z białym tłem i ciemnym tekstem zamiast czarnego z ekranu. Tak samo kalendarz na stronie dokumentu obok kart
   (doc.js: markFrames), już na canvasie. */
.paper, .card.calendar.in-page {
  --bg: #ffffff;
  --grid: #ffffff;
  --bar: #f2f2f2;
  --text: #222222;
  --muted: #5c5c5c;
  --line: #d9d9d9;
  --card: #ffffff;
  --card-line: #b3b3b3;
  --card-head: #f2f2f2;
  --button: #e6e6e6;
  --button-hover: #e6e6e6;
  --field: #f2f2f2;
  --off: #a6a6a6;
}
/* Kolor tekstu dziedziczy się już policzony z <body>, więc kalendarz na stronie ustawia go sam z jasnych zmiennych. */
.card.calendar.in-page { color: var(--text); }
/* Najechanie w jasnym kalendarzu na stronie: przyciemnienie zamiast rozjaśnienia, które na bieli nie widać. */
.card.calendar.in-page :is(.cal-nav button:hover, .cal-ev.hot, .cal-more.hot, .cal-more.open, .cal-ym-name.hot) { background: rgba(0, 0, 0, .06); }

/* Mazak (app.js: mazak): kreska w kolorze tekstu, grubości INK_W punktów canvasu, z okrągłymi końcami. Karta
   z kreską nie ma tła ani ramki, a kliknięcia łapie tylko pas 16 punktów wzdłuż linii (.ink-hit; pointer-events:
   stroke działa bez względu na widoczność), więc reszta jej prostokąta przepuszcza je do canvasu. Na slajdzie
   kreska ma kolor tekstu slajdu (doc.js: markFrames). Z mazakiem nic na canvasie nie łapie kliknięć, więc
   kreska idzie też przez karty. */
.card.drawing { background: none; border: none; border-radius: 0; pointer-events: none; }
.card.drawing > svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.card.drawing path, .ink-draft path { fill: none; stroke: var(--text); stroke-width: 4px; stroke-linecap: round; stroke-linejoin: round; }
.card.drawing path.ink-hit { stroke-width: 16px; visibility: hidden; pointer-events: stroke; cursor: grab; }
.card.drawing.dragging path.ink-hit { cursor: grabbing; }
.card.drawing .grip { pointer-events: auto; }
/* Bez kropek do łączenia: na środkach boków prostokąta często leży sama linia (szczyt koła, środek kreski),
   więc kropka zasłaniałaby ją i zamiast zaznaczenia zaczynałoby się połączenie. */
.card.drawing > .port { display: none; }
.card.drawing.in-slide path { stroke: var(--frame-text); }
.ink-draft { position: absolute; left: 0; top: 0; width: 1px; height: 1px; overflow: visible; pointer-events: none; }
#board.marker { cursor: crosshair; }
#board.marker #world, #board.marker #world * { pointer-events: none !important; }

/* Ustawienia jak w Obsidianie (index.html: #settings, app.js: ustawienia): działy po lewej, a po prawej tytuł
   działu i jego ustawienia, każde w wierszu z nazwą, opisem i przełącznikiem z prawej. Przełącznik jak
   w Obsidianie (.checkbox-container: 40 × 22, kółko 18, wyłączony --color-base-35), bez cieni. */
.settings-panel { display: flex; width: min(860px, calc(100vw - 64px)); height: min(600px, calc(100vh - 96px)); padding: 0; overflow: hidden; }
.settings-nav { flex: 0 0 200px; display: flex; flex-direction: column; gap: 2px; padding: 12px; border-right: 1px solid var(--line); }
.settings-nav button { border: none; background: none; color: var(--muted); text-align: left; padding: 10px; border-radius: 6px; }
.settings-nav button + button { margin-left: 0; }
.settings-nav button:hover, .settings-nav button.on { background: rgba(255, 255, 255, .067); color: var(--text); }
.settings-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.settings-tab { flex: 1; overflow-y: auto; padding: 8px 24px 24px; }
.settings-tab[hidden] { display: none; }
.settings-tab > p:first-child { margin-top: 16px; }
.setting { display: flex; align-items: center; gap: 24px; padding: 16px 0; border-top: 1px solid var(--line); cursor: pointer; }
.setting:first-child { border-top: none; }
.setting-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.setting-name, .setting-desc { text-box: trim-both cap alphabetic; }
.setting-desc { color: var(--muted); font-size: 12px; }
.toggle { position: relative; flex: none; width: 40px; height: 22px; }
.toggle input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.toggle i { position: absolute; inset: 0; border-radius: 11px; background: #3f3f3f; pointer-events: none; transition: background-color .15s ease-in-out; }
.toggle i::after { content: ''; position: absolute; left: 2px; top: 2px; width: 18px; height: 18px; border-radius: 50%; background: #fff; transition: translate .15s ease-in-out; }
.toggle input:checked + i { background: var(--accent); }
.toggle input:checked + i::after { translate: 18px 0; }
.toggle input:focus-visible + i { outline: 2px solid var(--accent-hover); outline-offset: 2px; }

/* ---------- Szkło z portfolio ----------
   Wygląd kart i przycisków wzięty z portfolio (~/Desktop/portfolio, src/styles/global.css, część „Glass”).
   Karta z tekstem, obrazem, stroną albo PDF ma szklaną krawędź zamiast szarej ramki: włos światła dookoła,
   pełny u góry, słabnący na bokach i lekko odbity od dołu (faza, nie równy pierścień), nad włosem cienia,
   który czyta się jako grubość szyby. Sekcje, kalendarz, figury, kreski mazaka i karty na slajdach zostają
   jak były. Z daleka (GPU, gl.js) krawędzi nie ma: przy 50% to pół piksela. */
:root {
  --glass-hair: 1px;
  --glass-lit: .38;
  --glass-dim: .05;
  --glass-back: .14;
  /* Zaokrąglenie karty. W portfolio 56/1080 szerokości karty, ok. 20 px przy karcie ok. 390 px na ekranie
     laptopa; na canvasie karty mają różne rozmiary, więc stałe, w punktach canvasu. */
  --r-card: 20px;
  /* Tło karty z tekstem: o krok jaśniejsze od canvasu, tak jak karta portfolio (#1c1c1c) na czarnej stronie. */
  --surface: #2a2a2a;
  --s-control: 56px; /* 3.5rem portfolio przy 16 px; tu html ma 14 px */
  --s-gap: 1svmin;
  --s-gutter: 4svmin;
  --d-fade: .3s;
  --d-press: .1s;
  --e-hop: cubic-bezier(.4, 0, .2, 1);
}

.card:is(.doc, .image, .pdf, .link):not(.shape, .in-slide, .in-page, .merge-ghost) {
  --card-line: transparent;
  border-radius: var(--r-card);
}
.card:is(.doc, .image, .pdf, .link):not(.shape, .in-slide, .in-page, .merge-ghost) > .head { border-radius: var(--r-card); }
.card.image:not(.in-slide, .in-page) img, .card.pdf:not(.in-slide, .in-page) .pdf-body { border-radius: calc(var(--r-card) - 1px); }
.card.link:not(.in-slide, .in-page) .link-body { border-radius: calc(var(--r-card) / var(--k, 1)); }

/* Światło na krawędzi. Na zewnętrznym brzegu karty, także nad jej przezroczystą ramką (1 px), pod kropkami
   do łączenia (.port, z-index 3). */
.card:is(.doc, .image, .pdf, .link):not(.shape, .in-slide, .in-page, .merge-ghost)::before {
  content: '';
  position: absolute;
  inset: -1px;
  z-index: 2;
  border-radius: var(--r-card);
  pointer-events: none;
  padding: var(--glass-hair);
  background: conic-gradient(
    rgb(255 255 255 / var(--glass-lit)) 0deg,
    rgb(255 255 255 / var(--glass-lit)) 26deg,
    rgb(255 255 255 / var(--glass-dim)) 72deg,
    rgb(255 255 255 / var(--glass-dim)) 108deg,
    rgb(255 255 255 / var(--glass-back)) 150deg,
    rgb(255 255 255 / var(--glass-back)) 210deg,
    rgb(255 255 255 / var(--glass-dim)) 252deg,
    rgb(255 255 255 / var(--glass-dim)) 288deg,
    rgb(255 255 255 / var(--glass-lit)) 334deg,
    rgb(255 255 255 / var(--glass-lit)) 360deg
  );
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
}
.card.link:not(.in-slide, .in-page)::before { inset: 0; } /* karta z linkiem nie ma ramki */

/* Włos cienia pod światłem: grubość szyby. Karta z linkiem ma już ::after (ramka zaznaczenia), więc dostaje
   go tam. */
.card:is(.doc, .image, .pdf):not(.shape, .in-slide, .in-page, .merge-ghost)::after {
  content: '';
  position: absolute;
  inset: -1px;
  z-index: 2;
  border-radius: var(--r-card);
  pointer-events: none;
  box-shadow: inset 0 0 0 calc(var(--glass-hair) + 1px) rgb(0 0 0 / .1);
}
.card.link:not(.in-slide, .in-page)::after {
  border-radius: var(--r-card);
  box-shadow: inset 0 0 0 calc(var(--glass-hair) + 1px) rgb(0 0 0 / .1);
}

/* Karta z tekstem jak karta portfolio: ciemna, z białym tekstem; opisy w połowie jego koloru (--c-dim). */
.card.doc:not(.shape, .in-slide, .in-page, .merge-ghost),
.card.doc:not(.shape, .in-slide, .in-page, .merge-ghost) .doc-text {
  --text: #fff;
  --muted: rgb(255 255 255 / .5);
  --doc-faint: rgb(255 255 255 / .35);
  --doc-code: rgb(255 255 255 / .08);
  --doc-line: rgb(255 255 255 / .16);
}
.card.doc:not(.shape, .in-slide, .in-page, .merge-ghost) { background: var(--surface); color-scheme: dark; }

/* Przyciski na dole: okrągłe szklane przyciski portfolio (.control). Cała tarcza to soczewka na tym, co pod
   nią (#glass-lens z glass.js), z ledwo widocznym wypełnieniem i tą samą krawędzią co karta, mocniejszą na
   małej tarczy. Pod kursorem przycisk rośnie i jaśnieje, pod naciśnięciem ustępuje. Wybrane narzędzie ma
   jaśniejsze wypełnienie, jak przycisk, który właśnie odpowiada. */
#tools, #zoom {
  bottom: var(--s-gutter);
  gap: var(--s-gap);
  padding: 0;
  background: none;
  border: none;
  border-radius: 0;
}
#zoom { right: var(--s-gutter); }
#tools > i, #zoom > i { width: 0; margin: 0; background: none; } /* dawna kreska: zostaje podwójny odstęp */
#tools > button, #zoom > button {
  --glass-lit: .6;
  --glass-back: .24;
  --glass-dim: .07;
  --glass-fill: .06;
  --glass-fill-on: .14;
  position: relative;
  width: var(--s-control);
  height: var(--s-control);
  padding: 0;
  border-radius: 50%;
  display: grid;
  place-items: center;
  cursor: pointer;
  color: var(--text);
  background: rgb(255 255 255 / var(--glass-fill));
  backdrop-filter: url(#glass-lens) blur(1px) saturate(1.5);
  box-shadow:
    inset 0 0 0 calc(var(--glass-hair) + .5px) rgb(0 0 0 / .12),
    0 2px 10px rgb(0 0 0 / .5);
  transition: scale var(--d-fade) var(--e-hop), background-color var(--d-fade);
}
#tools > button::before, #zoom > button::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
  padding: var(--glass-hair);
  background: conic-gradient(
    rgb(255 255 255 / var(--glass-lit)) 0deg,
    rgb(255 255 255 / var(--glass-lit)) 26deg,
    rgb(255 255 255 / var(--glass-dim)) 72deg,
    rgb(255 255 255 / var(--glass-dim)) 108deg,
    rgb(255 255 255 / var(--glass-back)) 150deg,
    rgb(255 255 255 / var(--glass-back)) 210deg,
    rgb(255 255 255 / var(--glass-dim)) 252deg,
    rgb(255 255 255 / var(--glass-dim)) 288deg,
    rgb(255 255 255 / var(--glass-lit)) 334deg,
    rgb(255 255 255 / var(--glass-lit)) 360deg
  );
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
}
@media (hover: hover) {
  #tools > button:hover, #zoom > button:hover { scale: 1.08; background: rgb(255 255 255 / var(--glass-fill-on)); }
}
#tools > button:active, #zoom > button:active { scale: .94; transition-duration: var(--d-press); }
#tools > button.on { background: rgb(255 255 255 / var(--glass-fill-on)); color: var(--text); }
#tools > button svg, #zoom > button svg { width: 24px; height: 24px; stroke-width: 1.5; }
@media (prefers-reduced-motion: reduce) {
  #tools > button:hover, #zoom > button:hover, #tools > button:active, #zoom > button:active { scale: 1; }
}

/* ---------- Oglądanie ---------- */
/* Strona bez /admin/ (index.html: data-mode, app.js: oglądanie): bez górnego paska, narzędzi i podpowiedzi,
   plansza na całe okno. Domyślnie, więc oglądający nie widzi pasków panelu nawet przez chwilę. Przeciąganie
   po karcie przesuwa widok, a kliknięcie włącza stronę, film albo PDF albo otwiera link. */
html:not([data-mode="admin"]) :is(#bar, #tools, #hint) { display: none !important; }
/* :where: bez dodatkowej wagi, więc kursor w trakcie przesuwania (#board.panning) i ze Spacją zostaje. */
:where(html:not([data-mode="admin"])) #board { inset: 0; cursor: grab; }
:where(html:not([data-mode="admin"])) #notice { top: 0; }
:where(html:not([data-mode="admin"])) .card .head { cursor: inherit; }
:where(html:not([data-mode="admin"])) :is(.card.link, .card.pdf) .head { cursor: pointer; }
