/* ── Fonts ──────────────────────────────────────────────────────────────
   Polish lives in latin-ext (ń, ę, ł, ż), so both subsets of both families
   are required — dropping the ext file leaves the UI's own words fallback. */
@font-face {
  font-family: 'Archivo'; font-weight: 100 900; font-style: normal; font-display: swap;
  src: url('fonts/archivo-latin.woff2') format('woff2');
  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+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,
    U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'Archivo'; font-weight: 100 900; font-style: normal; font-display: swap;
  src: url('fonts/archivo-latin-ext.woff2') format('woff2');
  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: 'Spline Sans Mono'; font-weight: 300 700; font-style: normal; font-display: swap;
  src: url('fonts/mono-latin.woff2') format('woff2');
  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+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,
    U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'Spline Sans Mono'; font-weight: 300 700; font-style: normal; font-display: swap;
  src: url('fonts/mono-latin-ext.woff2') format('woff2');
  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;
}

/* ── Tokens ─────────────────────────────────────────────────────────── */
:root {
  /* Dark only, and declared: it is what makes the native date picker and the
     select's dropdown come back dark instead of blinding. */
  color-scheme: dark;

  --navy:       #020619;
  --panel:      #0a1128;
  --console:    #2a2d46;
  --cyan:       #00c2ff;
  --cyan-deep:  #0096ff;
  --text:       #ffffffeb;
  --text-muted: #ffffff9e;
  --text-faint: #ffffff66;
  --border:     #ffffff17;
  --ok:         #4ade80;
  --late:       #fda4af;

  /* Muted on purpose, and cyan is missing on purpose: a tag must never be
     mistaken for the signal. app.js picks by hash(tag) % 10. */
  --tag-0: #8fa3c8;
  --tag-1: #c8b48f;
  --tag-2: #c89bb0;
  --tag-3: #9dc0a5;
  --tag-4: #b3a0cc;
  --tag-5: #c8a68f;
  --tag-6: #8fc4c0;
  --tag-7: #bfc88f;
  --tag-8: #a8aec8;
  --tag-9: #cc9f9f;

  --font: 'Archivo', system-ui, sans-serif;
  --mono: 'Spline Sans Mono', ui-monospace, monospace;

  /* Every transition and every hover lift goes through these two, so
     prefers-reduced-motion is one override instead of a wall of !important. */
  --t: 0.3s;
  --lift: -2px;
}

/* ── Reset ──────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html { -webkit-text-size-adjust: 100%; }

body {
  min-height: 100svh;
  display: flex; flex-direction: column;
  background: var(--navy); color: var(--text);
  font: 400 0.9375rem/1.45 var(--font);
  font-synthesis-weight: none;
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior-y: none;
}

/* Doubled so it outranks the display on .btn / .card without !important. */
[hidden][hidden] { display: none; }

button, input, select { font: inherit; color: inherit; }
button { background: none; border: 0; padding: 0; cursor: pointer; touch-action: manipulation; }
h1, h2 { font-size: inherit; font-weight: inherit; }
svg { display: block; }

:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; border-radius: 4px; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.sprite { position: absolute; width: 0; height: 0; }
.ico {
  width: 20px; height: 20px; flex: none;
  fill: none; stroke: currentColor;
  stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round;
}

/* The mono voice is annotation only — kickers, chips, dates, counts, the who
   line. Never a sentence. */
.mono, .kicker, .count, .chip, .badge, .row-who { font-family: var(--mono); }

.kicker { font-size: 0.8125rem; font-weight: 500; letter-spacing: 0.02em; color: var(--cyan); }
.count { margin-left: 0.35rem; font-size: 0.75rem; color: var(--text-faint); }

/* ── Buttons ────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.6rem;
  min-height: 44px; padding: 0 1.4rem; border-radius: 999px;
  font-size: 0.875rem; font-weight: 600;
  transition: transform var(--t), box-shadow var(--t), background var(--t), border-color var(--t);
}
.btn:active { filter: brightness(1.12); }
.btn-sm { padding: 0 0.9rem; font-size: 0.8125rem; }

.btn-primary { background: linear-gradient(135deg, var(--cyan), var(--cyan-deep)); color: var(--navy); }
.btn-primary:hover { transform: translateY(var(--lift)); box-shadow: 0 0 32px #00c2ff40; }

.btn-ghost { background: #ffffff0a; border: 1px solid var(--border); color: var(--text); }
.btn-ghost:hover { border-color: #00c2ff59; box-shadow: 0 0 24px #00c2ff40; transform: translateY(var(--lift)); }

.btn-danger { border: 1px solid #fda4af40; color: var(--late); }
.btn-danger:hover { background: #fda4af1a; }
/* Armed for the second press: filled, so "Na pewno?" cannot be mistaken for the
   resting state. Still the sanctioned error hue, not a new accent. */
.btn-danger.is-armed { background: var(--late); border-color: var(--late); color: var(--navy); font-weight: 600; }

/* ── Fields ─────────────────────────────────────────────────────────── */
.field { display: grid; gap: 0.35rem; }
.field > span { font-size: 0.8125rem; font-weight: 600; color: var(--text-muted); }

input, select {
  width: 100%; min-height: 44px; padding: 0.7rem 0.9rem;
  background: #0206198c; color: var(--text);
  border: 1px solid var(--border); border-radius: 10px;
  transition: border-color var(--t), box-shadow var(--t);
}
/* 16px exactly, or iOS zooms the page in on focus and then pans under the thumb. */
input { font-size: 1rem; }
input::placeholder { color: var(--text-faint); }
/* The cyan border plus the 3px ring is the focus indicator, so the global
   outline would only draw a second one on top of it. */
input:focus, select:focus, input:focus-visible, select:focus-visible {
  outline: none; border-color: var(--cyan); box-shadow: 0 0 0 3px #00c2ff26;
}
/* The text-input skin above has to come off entirely, not just its size: on the
   frame :focus recolours the border, Chrome drops the native checkbox widget and
   lays the element out as a plain box — with that padding it flashes 44px wide. */
input[type="checkbox"] {
  width: auto; min-height: 0; padding: 0; border: 0; background: none;
  accent-color: var(--cyan); margin-right: 0.35rem;
}
/* Fading the whole control takes the hairline with it, and a field with no edge
   reads as missing rather than disabled. Mute the contents, keep the shape. */
input:disabled, select:disabled {
  color: var(--text-faint); background: #02061940;
  border-color: #ffffff0d; cursor: default;
}
input:disabled::placeholder { color: #ffffff2e; }
button:disabled { opacity: 0.45; cursor: default; }

/* ── Auth ───────────────────────────────────────────────────────────── */
.auth {
  flex: 1; display: grid; place-content: center; justify-items: center;
  gap: 0.9rem; padding: 1.4rem;
}
.card {
  width: min(360px, 100%);
  display: flex; flex-direction: column; gap: 0.9rem;
  padding: 1.4rem;
  background: #2a2d4659; border: 1px solid var(--border); border-radius: 14px;
}
.auth-err { max-width: 360px; text-align: center; font-size: 0.875rem; color: var(--late); }

/* ── Bar ────────────────────────────────────────────────────────────── */
.bar {
  display: flex; align-items: center; gap: 0.9rem;
  padding: max(0.6rem, env(safe-area-inset-top)) max(0.9rem, env(safe-area-inset-right))
           0.6rem max(0.9rem, env(safe-area-inset-left));
  border-bottom: 1px solid var(--border);
}
.status {
  flex: 1; min-width: 0;
  font-size: 0.8125rem; color: var(--text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.net {
  margin: 0.6rem 0.9rem 0; padding: 0.6rem 0.9rem;
  border: 1px solid #fda4af40; border-radius: 10px;
  font-size: 0.8125rem; color: var(--late);
}

/* ── Panes ──────────────────────────────────────────────────────────── */
.panes {
  display: grid; gap: 0.9rem;
  padding: 0.9rem;
  padding-bottom: calc(0.9rem + env(safe-area-inset-bottom));
}
.pane {
  /* min-width:0 — the single-column mobile grid track is auto-sized, so without
     it one wide row widens the pane past the viewport and the page scrolls. */
  display: flex; flex-direction: column; min-height: 0; min-width: 0;
  background: var(--panel); border: 1px solid var(--border); border-radius: 14px;
}
.pane-head { display: flex; align-items: center; padding: 0.9rem 0.9rem 0.6rem; }
/* clip, not hidden: it must not turn every pane into a scroll container on
   mobile. Last line of defence against a row escaping sideways. */
.rows { min-height: 0; overflow-x: clip; scrollbar-width: thin; scrollbar-color: #ffffff26 transparent; }
.pane > .btn { margin: 0.6rem 0.9rem 0.9rem; font-size: 0.8125rem; }

.add { display: flex; gap: 0.6rem; padding: 0 0.9rem 0.6rem; }
.add input { flex: 1; min-width: 0; }
.add-go {
  flex: none; display: grid; place-items: center;
  width: 44px; height: 44px; border-radius: 10px;
  border: 1px solid var(--border); color: var(--text-muted);
  transition: color var(--t), border-color var(--t);
}
.add-go:hover { color: var(--cyan); border-color: #00c2ff59; }

/* ── Rows ───────────────────────────────────────────────────────────── */
.row {
  display: flex; align-items: center; gap: 0.6rem;
  min-height: 44px; padding: 0 0.9rem;
  border-top: 1px solid var(--border);
  transition: background var(--t);
}
.row:hover { background: #ffffff08; }
.row, .pane-head { -webkit-user-select: none; user-select: none; }

/* 44px wide because it is the hardest control here to hit; the negative margin
   spends the row's own padding so the icon barely moves. */
.row-grip {
  flex: none; display: grid; place-items: center;
  width: 44px; height: 44px; margin-left: -0.9rem;
  color: var(--text-faint); cursor: grab;
  /* The pointer drag owns this element; without it the page scrolls instead. */
  touch-action: none;
}
.row-grip:hover { color: var(--text); }
.row-grip .ico { stroke-width: 2.75; }

/* The ring is a pseudo-element so the button keeps its full 44px target while
   the drawn circle stays small enough for a 44px row. */
.row-check {
  flex: none; position: relative; display: grid; place-items: center;
  width: 44px; height: 44px; margin: 0 -0.6rem;
  color: transparent;
}
.row-check::before {
  content: ''; position: absolute; inset: 12px;
  border: 1.5px solid var(--text-faint); border-radius: 50%;
  transition: border-color var(--t);
}
.row-check:hover::before { border-color: var(--cyan); }
.row-check .ico { position: relative; width: 15px; height: 15px; stroke-width: 2.5; }
.row-check[aria-checked="true"], .row--done .row-check { color: var(--ok); }
.row-check[aria-checked="true"]::before, .row--done .row-check::before { border-color: #4ade8059; }

/* `flex: 1 1 auto`, not `flex: 1`: a zero basis makes the body unshrinkable, so
   the badge would keep its full nowrap width and the title would pay for it. */
.row-body {
  flex: 1 1 auto; min-width: 0; overflow: hidden;
  display: flex; align-items: center; gap: 0.6rem;
  padding: 0.6rem 0; text-align: left; color: inherit;
}
/* Zero basis, so the title claims what is left rather than bidding its
   unwrapped one-line width — that bid is what truncated chips while free space
   sat beside a wrapped title. And break-word, not anywhere: `anywhere` drops
   the min-content width to one character, which is what let the chip and the
   badge crush the title into mid-word shreds. The longest word is now the floor
   everything else yields to, and only a monster word gets split. */
.row-title { flex: 1; overflow-wrap: break-word; }

.row--done { color: var(--text-muted); }
.row--done .row-title { color: var(--text-faint); text-decoration: line-through; text-decoration-thickness: 1px; }
/* `by` is a login of up to 40 chars — capped and ellipsised, or it alone is
   wider than a phone. */
.row-who {
  flex: none; max-width: 15ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 0.75rem; color: var(--text-faint);
}

/* Colour comes from data-tag-color, set by app.js from hash(tag) % 10.
   18ch is room for a real client name — a chip reading INVESTK… on a 2560px
   screen is just a bug — but never more than 45% of the row, and it yields
   three times faster than the title, which matters more than the tag. */
.chip {
  --tag: var(--text-muted);
  flex-shrink: 3; max-width: min(18ch, 45%);
  padding: 0.2rem 0.65rem; border-radius: 999px;
  border: 1px solid; border-color: color-mix(in srgb, var(--tag) 35%, transparent);
  font-size: 0.75rem; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--tag);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chip[data-tag-color="0"] { --tag: var(--tag-0); }
.chip[data-tag-color="1"] { --tag: var(--tag-1); }
.chip[data-tag-color="2"] { --tag: var(--tag-2); }
.chip[data-tag-color="3"] { --tag: var(--tag-3); }
.chip[data-tag-color="4"] { --tag: var(--tag-4); }
.chip[data-tag-color="5"] { --tag: var(--tag-5); }
.chip[data-tag-color="6"] { --tag: var(--tag-6); }
.chip[data-tag-color="7"] { --tag: var(--tag-7); }
.chip[data-tag-color="8"] { --tag: var(--tag-8); }
.chip[data-tag-color="9"] { --tag: var(--tag-9); }

/* Shrinkable and free to fold at its ` · ` joints: nowrap, "01.09 · co miesiąc"
   is 130px of annotation taking room from the title, which matters more. */
.badge { text-align: right; font-size: 0.75rem; letter-spacing: 0.04em; color: var(--text-faint); }
.badge--late { color: var(--late); }
/* White, not cyan: an overdue row is the only thing here allowed to shout, and
   cyan means "your hand is here", not "this is due". */
.badge--today { padding: 0.1rem 0.5rem; border-radius: 999px; background: #ffffff14; color: var(--text); }

/* ── Drag ───────────────────────────────────────────────────────────── */
/* app.js pins this row with inline position/top/left/width; the only job left
   here is to make it look picked up. */
.row--drag {
  z-index: 40;
  background: #2a2d4699; border: 1px solid #00c2ff59; border-radius: 10px;
  box-shadow: 0 12px 40px #020619cc;
  cursor: grabbing;
}
.drop-line {
  margin: 0 0.9rem;
  background: #00c2ff14; border: 1.5px dashed #00c2ff59; border-radius: 10px;
}

/* ── Sheet ──────────────────────────────────────────────────────────── */
/* max-width resets the UA's calc(100% - 6px - 2em), which otherwise leaves the
   bottom sheet floating ~18px off each edge with square bottom corners. */
.sheet {
  width: min(440px, 100%); max-width: 100%; max-height: 100dvh; overflow-y: auto;
  margin: auto auto 0; padding: 0;
  background: var(--panel); color: var(--text);
  border: 1px solid var(--border); border-radius: 14px 14px 0 0;
}
.sheet::backdrop { background: #02071eb8; }
.sheet[open] { animation: sheet-in var(--t); }
@keyframes sheet-in { from { transform: translateY(14px); opacity: 0; } }

.sheet-inner {
  display: flex; flex-direction: column; gap: 0.9rem;
  padding: 0.9rem 1.4rem max(1.4rem, env(safe-area-inset-bottom));
}
.sheet-close {
  order: -1;  /* last in the DOM so Enter submits Zapisz, first on screen */
  align-self: flex-end; display: grid; place-items: center;
  width: 44px; height: 44px; margin: 0 -0.9rem -0.9rem 0;
  color: var(--text-faint);
}
.sheet-close:hover { color: var(--text); }
.sheet-monthly { display: flex; align-items: center; gap: 0.9rem; flex-wrap: wrap; }
.sheet-monthly label { display: flex; align-items: center; min-height: 44px; font-size: 0.875rem; color: var(--text-muted); }
.sheet-monthly select { width: auto; }
.sheet-actions { display: flex; gap: 0.6rem; margin-top: 0.35rem; }
.sheet-actions .btn-primary { flex: 1; }

@media (min-width: 640px) {
  .sheet { margin: auto; border-radius: 14px; }
}

/* ── Toasts ─────────────────────────────────────────────────────────── */
/* Private rows: a muted lock, never a second accent — the row is ordinary work
   that happens to be yours alone, not a warning. */
.row-lock { flex: none; display: flex; color: var(--text-faint); }
.row-lock .ico { width: 16px; height: 16px; }
.sheet-private { display: flex; align-items: center; font-size: 0.875rem; color: var(--text-muted); }
.sheet-private.is-off { opacity: 0.45; cursor: default; }

/* Anything you toggle by clicking its text: a second click selects the word and
   paints a native highlight over it. These are controls, not prose. */
.sheet-monthly label, .sheet-private, .pane-head { user-select: none; -webkit-user-select: none; }
/* And where text IS selectable, the highlight answers to the palette. */
::selection { background: #00c2ff33; color: var(--text); }

/* The one place the control room allows itself a joke. It sits above the toast,
   never takes pointer events, and simply does not appear when there is no cat. */
.cat {
  position: fixed; z-index: 61; pointer-events: none;
  left: 50%; bottom: calc(4.6rem + env(safe-area-inset-bottom));
  transform: translateX(-50%) translateY(0.6rem);
  opacity: 0; transition: opacity var(--t), transform var(--t);
}
.cat--in  { opacity: 1; transform: translateX(-50%) translateY(0); }
.cat--out { opacity: 0; }
.cat img {
  display: block; width: 168px; height: 168px; object-fit: cover;
  border-radius: 14px; border: 1px solid var(--border);
}

.toasts {
  position: fixed; z-index: 60;
  left: 50%; transform: translateX(-50%);
  bottom: calc(0.9rem + env(safe-area-inset-bottom));
  display: flex; flex-direction: column-reverse; gap: 0.6rem;
  width: min(420px, calc(100% - 1.8rem));
  pointer-events: none;
}
.toast {
  display: flex; align-items: center; gap: 0.9rem;
  padding: 0.6rem 0.6rem 0.6rem 0.9rem; border-radius: 14px;
  background: #0a1128f2; border: 1px solid var(--border);
  font-size: 0.875rem;
  pointer-events: auto;
  animation: toast-in var(--t);
}
.toast--err { border-color: #fda4af40; color: var(--late); }
/* The invite toast carries a whole URL and it has nowhere to break. */
.toast span { min-width: 0; overflow-wrap: anywhere; }
.toast--out { animation: toast-out 0.2s forwards; }
.toast button {
  margin-left: auto; flex: none;
  min-height: 44px; padding: 0 0.9rem; border-radius: 999px;
  background: #ffffff0f; color: var(--text); font-weight: 600; font-size: 0.8125rem;
}
.toast button:hover { background: #ffffff1f; }
/* The clipboard fallback: a selected input holding the invite link. */
.toast input { flex: 1; min-width: 0; min-height: 36px; padding: 0.35rem 0.6rem; font-size: 0.8125rem; }
@keyframes toast-in { from { transform: translateY(10px); opacity: 0; } }
@keyframes toast-out { to { transform: translateY(6px); opacity: 0; } }

/* ── Mobile: stacked and collapsible ────────────────────────────────── */
@media (max-width: 999px) {
  .pane-head { min-height: 44px; cursor: pointer; }
  .pane-head::after { content: '[−]'; margin-left: auto; font-size: 0.75rem; color: var(--text-faint); }
  .pane--collapsed .pane-head::after { content: '[+]'; }
  /* The contract only asks for .rows, but a collapsed pane that still shows
     its add field or "pokaż starsze" is not collapsed. */
  .pane--collapsed > :not(.pane-head) { display: none; }
}

/* ── Desktop: three columns, each scrolling on its own ──────────────── */
@media (min-width: 1000px) {
  /* :has() rather than a body class — only the app shell locks the page; the
     auth card must stay free to scroll on a short screen. */
  html:has(.panes), body:has(.panes) { height: 100%; overflow: hidden; }
  .panes {
    flex: 1;
    /* Without min-height:0 the grid children size to their content and the
       three panes grow the page instead of scrolling inside themselves. */
    min-height: 0;
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.9fr) minmax(0, 0.9fr);
  }
  .rows { flex: 1; overflow-y: auto; }
}

/* ── Reduced motion ─────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  :root { --t: 1ms; --lift: 0; }
  .sheet[open], .toast { animation: none; }
}
