/* The one legitimate !important in this file. `hidden` is the platform's own
   way to say "not here right now", and every component class below that sets
   `display` silently beats it: a `<button class="btn" hidden>` stayed on
   screen, and `.replay-no-frame[hidden]` further down is one component's
   private workaround for exactly this. Fixing it once here means `hidden`
   behaves the way it reads — including inside plugin code, which cannot patch
   this stylesheet. */
[hidden] { display: none !important; }

/* ---- buttons ---- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  padding: 9px 16px;
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  font-weight: 600; font-size: 13px;
  cursor: pointer;
  /* filter is in the list because .btn-primary:hover brightens with it — it
     was the one hover property that snapped instead of easing. */
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
    opacity var(--dur) var(--ease), transform var(--dur-fast) var(--ease),
    box-shadow var(--dur) var(--ease), filter var(--dur) var(--ease);
  white-space: nowrap;
}
.btn:disabled, .btn.disabled { opacity: .45; cursor: not-allowed; }
/* Signature placement #3: the primary action carries weblj's gradient, in the
   slightly deeper variant that keeps white label text at WCAG AA. Lifts on
   hover with a brand-tinted shadow instead of just brightening in place. */
.btn-primary { background: var(--grad-action); color: #fff; }
.btn-primary:hover:not(:disabled) { filter: brightness(1.12); transform: translateY(-1px); box-shadow: var(--shadow-lift); }
.btn-secondary { background: var(--n-100); color: var(--text); border-color: var(--border); }
.btn-secondary:hover:not(:disabled) { background: var(--n-200); border-color: var(--border-strong); transform: translateY(-1px); }
.btn-ghost { background: transparent; color: var(--text-muted); }
.btn-ghost:hover:not(:disabled) { background: var(--n-100); color: var(--text); }
/* Tinted rather than solid: a bright red slab would be the loudest thing on
   every dark panel it appears on, and it appears next to plenty of harmless
   actions. The red text and border still read unmistakably as destructive. */
.btn-danger { background: var(--danger-soft); color: var(--danger); border-color: oklch(0.71 0.19 22 / 0.45); }
.btn-danger:hover:not(:disabled) { background: oklch(0.71 0.19 22 / 0.26); border-color: var(--danger); }
/* Declared after the variants on purpose. You are always hovering a button you
   are pressing, and `.btn-primary:hover` / `.btn-secondary:hover` carry the
   same specificity as `.btn:active` — so while this lived above them the
   press-down was silently dead on the two variants people click most. */
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn-sm { padding: 6px 12px; font-size: 12px; }
.btn-icon { padding: 8px; }

/* ---- badges / pills ---- */
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  font-size: 12px; font-weight: 600;
  background: var(--n-100); color: var(--text-muted); border: 1px solid var(--border);
  /* A pill that wraps onto two lines reads as a broken control rather than a
     status chip. Labels here are short Slovenian phrases ("Ni podatka",
     "Obveščanje aktivno") that fit fine on one line — they only wrapped
     because a narrow flex/table cell squeezed them. */
  white-space: nowrap;
  /* …but not every badge holds a label we wrote. Plenty carry data — an error
     class name, a pixel provider, a plan id, a tenant name — and `nowrap`
     alone plus the default `min-width:auto` means such a badge refuses to
     shrink and paints straight across the row it sits in. These three let it
     give way instead: it still never wraps, it just stops at the edge. */
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
}
.badge::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
.badge-success { background: var(--success-soft); color: var(--success); }
.badge-warning { background: var(--warning-soft); color: var(--warning); }
.badge-danger { background: var(--danger-soft); color: var(--danger); }
.badge-primary { background: var(--primary-soft); color: var(--primary-text); }
.badge-neutral { background: var(--n-100); color: var(--text-muted); }

/* ---- inputs ---- */
.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: var(--sp-4); }
.field label { font-size: 12px; font-weight: 600; color: var(--text-muted); }
.field-hint { font-size: 12px; color: var(--text-faint); overflow-wrap: anywhere; }
/* Opt out the few non-text controls rather than listing every text-like type.
   The old enumeration missed type=url — which core/contact-profile.js generates
   for the social links — so those fields silently kept the browser default and
   sat two shades lighter than every other input in the same modal. */
input:not([type=checkbox]):not([type=radio]):not([type=file]):not([type=range]):not([type=hidden]),
textarea, select {
  width: 100%;
  padding: 9px 12px;
  border-radius: var(--r-sm);
  /* --border-control, not --border: a decorative hairline measures ~1.3:1
     against the card and left these wells with no perceivable edge. */
  border: 1px solid var(--border-control);
  /* Inputs read as wells cut into the card, not as raised chips — on a dark
     panel that is what tells you a field is editable. */
  background: var(--n-900);
  color: var(--text);
  outline: none;
  transition: border-color .12s ease, box-shadow .12s ease;
}
input::placeholder, textarea::placeholder { color: var(--text-faint); }
textarea { resize: vertical; min-height: 90px; font-family: inherit; }
input:focus, select:focus, textarea:focus {
  border-color: var(--primary-text);
  box-shadow: var(--ring);
}
input[readonly], textarea[readonly] { color: var(--text-muted); }
select { appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23a3aec7' stroke-width='1.5' fill='none'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 12px center; padding-right: 32px; }
select option { background: var(--surface-2); color: var(--text); }
input[type=file] {
  max-width: 100%; color: var(--text-muted); font-size: 13px;
}
input[type=file]::file-selector-button {
  margin-right: 10px; padding: 7px 12px; border-radius: var(--r-sm);
  border: 1px solid var(--border); background: var(--n-100); color: var(--text);
  font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
}
input[type=file]::file-selector-button:hover { background: var(--n-200); }
.switch { position: relative; display: inline-block; width: 38px; height: 22px; flex-shrink: 0; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch .track { position: absolute; inset: 0; background: var(--n-400); border-radius: var(--r-pill); transition: background var(--dur-fast) var(--ease); cursor: pointer; }
/* The real checkbox is 0x0 and transparent, so its own focus ring is drawn on
   nothing — a keyboard user tabbing onto a site-visibility toggle got no
   indication at all. Forward the ring to the visible proxy. */
.switch input:focus-visible + .track { outline: 2px solid var(--primary-text); outline-offset: 2px; }
.switch .track::before { content: ""; position: absolute; width: 16px; height: 16px; left: 3px; top: 3px; background: #fff; border-radius: 50%; transition: transform .15s; box-shadow: var(--shadow-sm); }
.switch input:checked + .track { background: var(--primary); }
.switch input:checked + .track::before { transform: translateX(16px); }

/* ---- tabs (segmented) ----
   On a dark ground the track has to recess and the selected pill has to lift;
   the light-theme version did the opposite and the selection disappeared. */
.segmented { display: inline-flex; padding: 3px; background: var(--n-900); border: 1px solid var(--border); border-radius: var(--r-sm); gap: 2px; }
.segmented button {
  border: none; background: transparent; padding: 7px 14px; border-radius: 7px;
  font-weight: 600; font-size: 13px; color: var(--text-muted); cursor: pointer;
}
.segmented button:hover { color: var(--text); }
.segmented button.active { background: var(--n-200); color: var(--text); }

/* ---- cards ----
   Elevation on dark comes from surface lightness plus a 1px top highlight;
   drop shadows just read as mud. The 2px top accent is the same brand
   gradient as the sidebar rail and primary buttons — a fourth, deliberate
   use of it, always in the same spot, so panels read as one family instead
   of a stack of identical grey rectangles. */
.card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--sp-6);
  box-shadow: inset 0 1px 0 oklch(1 0 0 / 0.04);
  overflow: hidden;
}
.card::before {
  content: ""; position: absolute; top: 0; left: var(--sp-6); right: var(--sp-6); height: 2px;
  background: var(--grad-brand); opacity: 0.55; border-radius: 0 0 3px 3px;
}
.card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--sp-4); gap: var(--sp-3); }
/* A classless <select> inherits width:100% from the global form rule; as a
   flex child of a space-between header that made the range picker eat the
   whole row and squash the heading beside it. */
.card-header > select { width: auto; flex: 0 0 auto; }
/* Inside a table the generic width:100% shrinks a control to its cell, and the
   cells here are already tight — the role picker collapsed to a single visible
   character ("L…" for Lastnik). The wrapper scrolls horizontally, so there is
   room to spend; take it. */
.table-wrap select { min-width: 132px; }
.card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--sp-4); }

/* ---- stat cards ---- */
.stat-card { position: relative; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); padding: var(--sp-4) var(--sp-6); box-shadow: inset 0 1px 0 oklch(1 0 0 / 0.04); overflow: hidden; transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.stat-card::before { content: ""; position: absolute; top: 0; left: var(--sp-4); right: var(--sp-4); height: 2px; background: var(--grad-brand); opacity: 0.4; }
.stat-card__label { font-size: 12px; color: var(--text-muted); font-weight: 600; margin-bottom: var(--sp-2); }
/* Readings line up column-to-column like an instrument panel. */
/* Most values are numbers, but several stat cards show a name instead ("Izbrana
   stranka", the active plan, a campaign). At 28px a long one runs well past the
   card, so let it break rather than overhang. `anywhere` rather than
   `break-word` on purpose: only `anywhere` also lowers the intrinsic minimum
   width, which is what actually stops the card from being pushed out of its
   grid track. Numbers are unaffected — they always fit. */
.stat-card__value { font-family: var(--font-mono); font-size: 28px; font-weight: 700; letter-spacing: -0.03em; font-variant-numeric: tabular-nums; color: var(--text); overflow-wrap: anywhere; }
.stat-card__delta { font-family: var(--font-mono); font-size: 12px; margin-top: var(--sp-2); display: inline-flex; align-items: center; gap: 4px; }
.stat-card__delta.up { color: var(--success); }
.stat-card__delta.down { color: var(--danger); }

/* ---- table ---- */
.table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--surface); box-shadow: inset 0 1px 0 oklch(1 0 0 / 0.04); }
table { width: 100%; border-collapse: collapse; min-width: 560px; }
thead th { text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .07em; color: var(--text-faint); font-weight: 700; padding: 11px var(--sp-4); background: var(--n-900); border-bottom: 1px solid var(--border); white-space: nowrap; }
tbody td { padding: 13px var(--sp-4); border-bottom: 1px solid var(--border); font-size: 14px; }
/* box-shadow is named here because the hover rail below is drawn with it —
   without it the rail snapped on while the background faded. */
tbody tr { transition: background var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease); }
tbody tr:last-child td { border-bottom: none; }
/* A brand-tinted left rail on hover, not just a flat grey wash — the same
   "rail marks position" idea as the sidebar's active nav item. */
tbody tr { box-shadow: inset 2px 0 0 0 transparent; }
tbody tr:hover { background: var(--n-100); box-shadow: inset 2px 0 0 0 var(--primary-text); }
td.mono, th.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* ---- progress ---- */
.progress { height: 6px; border-radius: var(--r-pill); background: var(--n-200); overflow: hidden; }
.progress > span { display: block; height: 100%; background: var(--primary); border-radius: var(--r-pill); }
.progress.secondary > span { background: var(--secondary); }

/* ---- empty / loading ---- */
.empty-state { text-align: center; padding: var(--sp-8) var(--sp-4); color: var(--text-muted); }
.empty-state svg { width: 40px; height: 40px; color: var(--text-faint); margin-bottom: var(--sp-3); }
.skeleton { background: linear-gradient(90deg, var(--n-100) 25%, var(--n-200) 37%, var(--n-100) 63%); background-size: 400% 100%; animation: shimmer 1.4s ease infinite; border-radius: var(--r-sm); }
@keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: -100% 0; } }

/* In-page actions that look like links. They used to be <a href="#">, which
   offers a destination that does not exist — copying or middle-clicking them
   led nowhere. They are buttons now and only need to read like links. */
.btn-link {
  background: none; border: 0; padding: 0; font: inherit; color: inherit;
  cursor: pointer; text-decoration: underline; text-underline-offset: 2px;
}
.btn-link:hover { color: var(--text); }
.btn-link:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--r-sm); }

/* A checkbox styled as a badge still has to be reachable by keyboard.
   `display:none` removes it from the tab order entirely, which made the
   per-user permission toggles in the Računi tab mouse-only. */
.check-in-badge { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
.badge:focus-within { box-shadow: var(--ring); }

/* Determinate loading panel (core/loader.js). Replaces the content-less
   shimmering skeleton: an exact percentage, the step being waited on, and a
   bar that only ever moves when something actually finished. */
.loading-panel { max-width: 460px; margin: var(--sp-8) auto; padding: var(--sp-5); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md); }
.loading-panel__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); margin-bottom: var(--sp-3); }
.loading-panel__title { font-weight: 600; font-size: 15px; }
.loading-panel__pct { font-size: 15px; font-weight: 700; color: var(--primary-text); font-variant-numeric: tabular-nums; }
.loading-panel__step { margin-top: var(--sp-3); font-size: 13px; min-height: 1.2em; }
/* tokens.css already zeroes every transition under prefers-reduced-motion. */
.loading-panel .progress > span { transition: width var(--dur) var(--ease); }

/* Used only while a tab module downloads (core/router.js): there is no
   fraction to report yet, so the bar sweeps rather than claiming a number it
   does not have. tokens.css caps the animation under prefers-reduced-motion,
   which leaves a static partial bar — still better than an empty box. */
.progress > span.progress__indeterminate {
  width: 35%;
  animation: progress-sweep 1.1s var(--ease-loop) infinite;
}
@keyframes progress-sweep {
  0% { margin-left: -35%; }
  100% { margin-left: 100%; }
}

/* ---- toast ---- */
#toast-root { position: fixed; bottom: var(--sp-4); right: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-2); z-index: 100; }
.toast { background: var(--surface-2); color: var(--text); border: 1px solid var(--border-strong); padding: 11px 16px; border-radius: var(--r-sm); font-size: 14px; box-shadow: var(--shadow-md); animation: toast-in .15s ease; }
.toast.error { background: var(--danger); color: oklch(0.16 0.04 25); border-color: transparent; font-weight: 600; }
.toast.success { background: var(--success); color: oklch(0.16 0.04 158); border-color: transparent; font-weight: 600; }
@keyframes toast-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }

/* ---- modal ---- */
.modal-backdrop { position: fixed; inset: 0; background: oklch(0.07 0.03 270 / .72); backdrop-filter: blur(3px); display: flex; align-items: center; justify-content: center; z-index: 90; padding: var(--sp-4); animation: modal-backdrop-in var(--dur) var(--ease); }
/* The one corner cut, at the scale a modal is big enough to actually show it
   at — same motif as the marketing panels, kept off the smaller shared cards
   where an unaudited child element could sit right in that corner. */
.modal {
  background: var(--surface-2); border: 1px solid var(--border-strong);
  border-radius: var(--r-lg); padding: var(--sp-6); width: 100%; max-width: 480px;
  box-shadow: var(--shadow-md); max-height: 88vh; overflow-y: auto;
  clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
  animation: modal-in var(--dur-slow) var(--ease);
}
.modal--wide { max-width: 1080px; }
@keyframes modal-backdrop-in { from { opacity: 0; } }
@keyframes modal-in { from { opacity: 0; transform: translateY(10px) scale(.98); } }

.api-key-reveal .code-panel { flex: 1 1 260px; min-width: 0; }
.api-key-reveal {
  margin-bottom: var(--sp-5);
  padding: var(--sp-4);
  border: 1px solid color-mix(in oklch, var(--success) 35%, var(--border));
  border-radius: var(--r-md);
  background: var(--success-soft);
}

/* ---- misc ---- */
/* An action row inside a card must wrap rather than clip. Buttons pushed past
   the card edge are simply unreachable, and the labels here are Slovenian —
   longer than the English ones these widths were originally eyeballed for. */
/* Also `.badge` (a status chip is nowrap and just as unshrinkable as a
   button) and `.modal` (dialogs have the same action rows as cards, and the
   video trim editor's two long buttons overflowed one). */
.card .flex:has(> .btn),
.card .flex:has(> .flex > .btn),
.card .flex:has(> .badge),
.modal .flex:has(> .btn),
.modal .flex:has(> .flex > .btn) { flex-wrap: wrap; }
.flex { display: flex; }
.flex-col { display: flex; flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
/* Both were in use before they existed: `.justify-end` on seven dialog action
   rows (api-requests, plugins, contact-profile) and `.justify-center` on the
   "Ni definiranih strani" empty state. Undefined, they computed to the
   flex-start default, so every one of those button rows sat hard against the
   left edge of its dialog instead of where the markup asked for it. */
.justify-end { justify-content: flex-end; }
.justify-center { justify-content: center; }
.gap-2 { gap: var(--sp-2); }
.gap-3 { gap: var(--sp-3); }
.gap-4 { gap: var(--sp-4); }
.text-muted { color: var(--text-muted); }
.text-faint { color: var(--text-faint); }
/* overflow:hidden because the contents are not always the two initials this
   was sized for — the plugin catalogue puts `plugin.icon` in here, which is
   staff-entered free text. Anything longer than a glyph or two used to paint
   out over the name beside it. */
.avatar { width: 32px; height: 32px; border-radius: 50%; background: var(--primary-soft); color: var(--primary-text); display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 12px; font-family: var(--font-display); flex-shrink: 0; overflow: hidden; }
.topbar .menu-btn { display: none; }
/* app.js paints a <label> and a <button> in here. `.flex` on the label makes it
   a block-level flex container, so in a plain block parent the two controls
   stacked one above the other and filled 50px of a 60px topbar. They are one
   control pair and belong on one line. */
#staff-mode-switcher { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
#staff-mode-switcher .btn { flex-shrink: 0; }
/* Below this the switch label and the full button text simply do not fit
   beside the tab title in a 60px topbar — together they wanted ~500px of a
   375px screen and pushed the whole page into horizontal scroll on EVERY
   site-mode tab. Both controls stay, and both keep their accessible names
   (aria-label/title in app.js); only the visible words go. */
@media (max-width: 720px) {
  #staff-mode-switcher [data-collapsible] { display: none; }
}
.workspace-switcher {
  display: inline-flex; align-items: center; gap: 3px; padding: 3px;
  border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--n-900);
}
.workspace-switcher button {
  border: 0; border-radius: 7px; padding: 6px 10px; background: transparent;
  color: var(--text-muted); font-size: 11px; font-weight: 700; cursor: pointer; white-space: nowrap;
}
.workspace-switcher button.active { color: var(--text); background: var(--n-200); }
.site-admin-heading {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 20px;
  margin-bottom: 18px; padding-bottom: 18px; border-bottom: 1px solid var(--border);
}
.site-admin-heading h1 { margin: 5px 0 6px; }
.site-admin-heading p { margin: 0; max-width: 760px; }
/* Three long Slovenian tabs need ~479px of natural width, so this overflowed
   its own rounded border well before phone widths. Scroll like .error-tabs. */
.site-admin-subnav {
  display: inline-flex; gap: 4px; margin-bottom: 24px; padding: 4px; max-width: 100%; overflow-x: auto;
  border: 1px solid var(--border); border-radius: var(--r-md); background: var(--n-900);
}
.site-admin-subnav button { white-space: nowrap; flex: 0 0 auto; }
.site-admin-subnav button {
  border: 0; border-radius: var(--r-sm); padding: 9px 14px; background: transparent;
  color: var(--text-muted); font-weight: 700; cursor: pointer;
}
.site-admin-subnav button.active { color: var(--text); background: var(--n-200); }
.backup-safety-note {
  display: flex; align-items: center; gap: 10px; margin-bottom: 22px; padding: 14px 16px;
  border: 1px solid color-mix(in oklch, var(--success) 35%, var(--border));
  border-radius: var(--r-md); background: var(--success-soft); color: var(--text-muted);
}
.backup-safety-note strong { flex: 0 0 auto; color: var(--success); }
.backup-timeline { position: relative; display: flex; flex-direction: column; gap: 14px; padding-left: 28px; }
.backup-timeline::before {
  content: ""; position: absolute; top: 8px; bottom: 8px; left: 8px; width: 2px;
  background: var(--border);
}
.backup-timeline__item { position: relative; }
.backup-timeline__dot {
  position: absolute; top: 18px; left: -27px; width: 16px; height: 16px;
  border: 4px solid var(--surface); border-radius: 50%; background: var(--primary);
  box-shadow: 0 0 0 1px var(--border);
}
.backup-timeline__card {
  display: flex; flex-direction: column; align-items: flex-start; gap: 12px;
  padding: 17px 18px; border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface); box-shadow: var(--shadow-sm);
}
.backup-timeline__head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; width: 100%;
}
.backup-timeline__head small { display: block; margin-top: 5px; color: var(--text-faint); }
.backup-timeline__meta { display: flex; flex-wrap: wrap; gap: 8px 14px; color: var(--text-faint); font-size: 12px; }
.backup-timeline__meta code { color: var(--text-muted); }

/* ---- dashboard / admin flows ---- */
.hero-panel {
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding: 28px; border: 1px solid oklch(0.62 0.24 265 / 0.28);
  border-radius: var(--r-lg);
  /* A trace of the brand gradient bled into the surface, not a wash — the
     greeting should feel like weblj without competing with the numbers. */
  background:
    radial-gradient(120% 140% at 88% 0%, oklch(0.58 0.26 300 / 0.16), transparent 55%),
    linear-gradient(135deg, oklch(0.62 0.24 265 / 0.14), var(--surface) 60%);
}
.hero-panel h1 { font-size: clamp(24px, 3vw, 34px); margin: 4px 0 6px; }
.hero-panel p { margin: 0; }
.eyebrow { color: var(--primary-text); font-size: 11px; font-weight: 800; letter-spacing: .12em; }
/* start, not stretch: a short card should not inherit a tall neighbour's
   height and leave a slab of empty surface under its content. */
.dashboard-grid { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(300px, .8fr); gap: 20px; align-items: start; }
.overview-stats { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.stat-card--button { border: 1px solid var(--border); text-align: left; cursor: pointer; color: var(--text); transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.stat-card--button:hover { border-color: color-mix(in oklch, var(--primary) 45%, var(--border)); transform: translateY(-2px); box-shadow: var(--shadow-lift); }
.stat-card__link { color: var(--primary-text); font-size: 12px; font-weight: 700; margin-top: 8px; }
.activity-row {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  width: 100%; padding: 11px 0; border-bottom: 1px solid var(--border);
}
.activity-row:last-child { border-bottom: 0; }
/* `flex: 1 1 auto` matters as much as the min-width:0 next to it. Without a
   grow factor this column only ever gets its content width, so a long trailing
   span took the entire row and collapsed this one to ZERO — at which point the
   message inside it had no box left and painted straight over its neighbour.
   That is the "text across the border" people were seeing on the dashboard. */
/* `flex: 1 1 0` — the zero basis is the point, not the grow factor. With
   `auto` this column contributes its own (long) content width to the row, the
   row overflows, and the browser then shrinks BOTH children proportionally —
   which clipped the timestamp beside it to "pred 3 d…" even though it had
   room. At basis 0 only the trailing element's width counts toward the row, so
   the timestamp keeps its natural size and this column simply takes the rest. */
.activity-row > div { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; }
/* The trailing slot is normally a timestamp, which is why it was pinned at its
   natural width — but marketing.js and the pixel list put a UTM code, a
   provider name or a status badge here too. `0 1 auto` keeps a timestamp at
   full width (it always fits) while letting the long cases give way. */
.activity-row > span { flex: 0 1 auto; min-width: 0; max-width: 50%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }
/* `.activity-row > div` above out-specifies the `.flex` utility (0,1,1 vs
   0,1,0), so a child that asked for a ROW was silently forced into a column.
   Give the utility classes their direction back. */
.activity-row > div.flex { flex-direction: row; align-items: center; }
.activity-row strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.activity-row small { color: var(--text-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 460px; }
.activity-row--button { border: 0; background: transparent; text-align: left; cursor: pointer; transition: color var(--dur-fast) var(--ease), padding-left var(--dur-fast) var(--ease); }
.activity-row--button:hover { color: var(--primary-text); padding-left: 4px; }
/* .check-card is a row: a clickable checkbox+copy toggle (.check-card__toggle)
   plus an optional trailing action button (e.g. "jump to this tab"), kept as
   a sibling rather than nested inside the <label> so the button's own click
   doesn't also toggle the checkbox underneath it. */
.check-card {
  display: flex; align-items: center; gap: 12px;
  padding: 13px 15px; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--shadow-sm);
}
.check-card__toggle {
  display: grid; grid-template-columns: 32px minmax(0, 1fr); align-items: center; gap: 12px;
  flex: 1; min-width: 0; cursor: pointer;
}
.check-card input { position: absolute; opacity: 0; pointer-events: none; }
/* Same as .switch: the input is visually gone, so the ring has to move to the
   tick box the user actually sees. */
.check-card input:focus-visible + .check-card__mark { outline: 2px solid var(--primary-text); outline-offset: 2px; }
.check-card__mark {
  width: 30px; height: 30px; display: grid; place-items: center; border-radius: 50%;
  background: var(--n-100); color: transparent; border: 2px solid var(--n-400); font-weight: 800;
}
.check-card strong, .check-card small { display: block; }
.check-card__copy { min-width: 0; }
.check-card small { color: var(--text-faint); margin-top: 2px; line-height: 1.4; }
.check-card__action { flex: 0 0 auto; }
.check-card.done { border-color: color-mix(in oklch, var(--success) 35%, var(--border)); background: var(--success-soft); }
.check-card.done .check-card__mark { background: var(--success); color: oklch(0.16 0.04 158); border-color: var(--success); }
.onboarding-page { width: min(100%, 920px); }
.onboarding-heading {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 20px;
  margin-bottom: 16px;
}
.onboarding-heading h1 { margin-bottom: 4px; }
.onboarding-heading p { margin: 0; }
.onboarding-heading > strong { flex: 0 0 auto; color: var(--primary-text); font: 700 13px var(--font-mono); padding-top: 8px; }
.onboarding-progress { margin-bottom: 16px; }
.onboarding-contact-card {
  display: flex; align-items: center; justify-content: space-between; gap: 24px;
  margin-bottom: 16px; background: var(--primary-soft);
  border-color: color-mix(in oklch, var(--primary) 24%, var(--border));
}
.onboarding-contact-card h3 { margin: 4px 0; }
.onboarding-contact-card p { margin: 0; color: var(--text-muted); overflow-wrap: anywhere; }
.onboarding-checklist { display: flex; flex-direction: column; gap: 10px; }
.list-card { display: grid; grid-template-columns: 1fr auto; gap: 8px 16px; padding: 14px 0; border-bottom: 1px solid var(--border); }
.list-card > div:first-child { display: flex; flex-direction: column; }
.list-card small { color: var(--text-faint); }
.list-card code { grid-column: 1 / -1; color: var(--text-muted); background: var(--n-100); padding: 8px 10px; border-radius: var(--r-sm); overflow-x: auto; overflow-wrap: anywhere; }
.detail-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-bottom: 16px; }
.detail-grid > div { display: flex; flex-direction: column; gap: 4px; padding: 12px; background: var(--n-100); border-radius: var(--r-sm); }
.detail-grid small { color: var(--text-faint); }
.code-panel {
  max-height: 300px; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere;
  background: var(--n-900); color: var(--n-700); border-radius: var(--r-sm);
  padding: 14px; font: 11px/1.6 var(--font-mono); border: 1px solid var(--border);
}
.code-panel--tall { max-height: 480px; }
.error-detail-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; }
/* The 32-character hex fingerprint shown here has no break opportunity at all. */
.error-detail-head > div { min-width: 0; }
.error-detail-head .flex { min-width: 0; flex-wrap: wrap; }
.error-detail-head .mono { overflow-wrap: anywhere; }
.error-detail-head h2 { margin: 10px 0 4px; overflow-wrap: anywhere; }
.error-detail-head p { margin: 0 0 18px; color: var(--text-muted); overflow-wrap: anywhere; }
.error-detail-stats { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.error-detail-stats strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.error-tabs { display: flex; gap: 2px; overflow-x: auto; margin: 18px 0 14px; border-bottom: 1px solid var(--border); }
.error-tabs button { border: 0; border-bottom: 2px solid transparent; background: transparent; color: var(--text-muted); padding: 10px 14px; font-weight: 700; cursor: pointer; white-space: nowrap; }
.error-tabs button.active { color: var(--primary-text); border-bottom-color: var(--primary-text); }
.error-tabs span { margin-left: 5px; color: var(--text-faint); font: 600 10px var(--font-mono); }
.error-tab-panel { display: none; min-height: 260px; }
.error-tab-panel.active { display: block; }
.error-actions { display: flex; justify-content: space-between; gap: 12px; margin-top: 18px; padding-top: 18px; border-top: 1px solid var(--border); }
.replay-player { border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; background: var(--n-900); }
.replay-toolbar { min-height: 48px; display: flex; align-items: center; gap: 12px; padding: 8px 12px; color: var(--n-700); border-bottom: 1px solid oklch(100% 0 0 / .12); overflow: hidden; }
.replay-toolbar > * { min-width: 0; }
.replay-toolbar .mono { overflow: hidden; text-overflow: ellipsis; }
.replay-toolbar select { width: 80px; padding-block: 6px; background-color: var(--n-800); color: #fff; border-color: oklch(100% 0 0 / .16); }
.replay-stage { position: relative; height: min(47vh, 480px); background: var(--n-800); }
.replay-stage iframe { width: 100%; height: 100%; border: 0; background: #fff; transform-origin: top left; }
.replay-no-frame { position: absolute; inset: 0; display: grid; place-items: center; padding: 24px; color: var(--n-600); text-align: center; }
.replay-no-frame[hidden], .replay-stage iframe[hidden] { display: none; }
.replay-range { display: block; width: calc(100% - 24px); margin: 12px; accent-color: var(--primary-text); }
.replay-event { min-height: 54px; display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 10px; padding: 0 14px 12px; color: #fff; }
.replay-event > div { min-width: 0; }
.replay-event strong, .replay-event small { display: block; }
.replay-event small { color: var(--n-600); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.replay-event code { color: var(--n-600); }
.replay-event-strip { display: flex; gap: 4px; overflow-x: auto; padding: 9px 12px; background: var(--n-800); border-top: 1px solid oklch(100% 0 0 / .1); }
.replay-event-strip button { display: inline-flex; align-items: center; gap: 5px; flex: 0 0 auto; max-width: 150px; padding: 6px 8px; border: 1px solid transparent; border-radius: 6px; background: transparent; color: var(--n-600); cursor: pointer; }
.replay-event-strip button.active { border-color: var(--primary-text); color: #fff; background: oklch(100% 0 0 / .08); }
.replay-event-strip small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.replay-dot { display: inline-block; width: 8px; height: 8px; flex: 0 0 auto; border-radius: 50%; background: var(--n-400); }
.replay-dot--danger { background: var(--danger); }
.replay-dot--warning { background: var(--warning); }
.replay-dot--primary { background: var(--primary); }
.breadcrumb-list { max-height: 460px; overflow: auto; border: 1px solid var(--border); border-radius: var(--r-sm); }
.breadcrumb-row { display: grid; grid-template-columns: auto 110px minmax(140px, .5fr) 1fr; align-items: center; gap: 10px; padding: 10px 12px; border-bottom: 1px solid var(--border); }
.breadcrumb-row:last-child { border-bottom: 0; }
/* `strong` sits in a hard 110px track (90px on a phone) and holds the
   breadcrumb type, which is data — without a clip it painted over the
   timestamp beside it. */
.breadcrumb-row code, .breadcrumb-row small, .breadcrumb-row strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.breadcrumb-row code, .breadcrumb-row small { color: var(--text-muted); }
.feature-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin-top: 7px; }
.feature-check { display: flex; align-items: center; gap: 8px; padding: 10px; border: 1px solid var(--border); border-radius: var(--r-sm); cursor: pointer; }
.feature-check:has(input:checked) { background: var(--primary-soft); border-color: color-mix(in oklch, var(--primary) 35%, var(--border)); }
.field-label { display: block; color: var(--text-muted); font-size: 12px; font-weight: 600; }
.custom-plugin-cta {
  display: flex; align-items: center; justify-content: space-between; gap: 18px;
  margin-top: 24px; padding: 24px; border-radius: var(--r-lg);
  border: 1px dashed color-mix(in oklch, var(--primary) 45%, var(--border)); background: var(--primary-soft);
}
.custom-plugin-cta p { margin: 4px 0 0; }
.plugin-detail-hero { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.plugin-detail-hero .avatar { width: 48px; height: 48px; border-radius: var(--r-sm); }
.plugin-detail-hero h2, .plugin-detail-hero p { margin: 0; }
.plugin-meta-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
/* :nth-child(odd) so this only spans when the count is odd. The class is
   shared with widgetPlacementModal, which has exactly TWO fields — there the
   unconditional span dropped "Pozicija" onto its own full-width row and left a
   hole beside "Stran". */
.plugin-meta-grid .field:last-child:nth-child(odd) { grid-column: 1 / -1; }
.version-stepper { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px; }
.version-stepper .btn { padding-inline: 13px; }
.plugin-icon-select { width: 100%; font-size: 18px; }
.plugin-widget-editor {
  margin-bottom: 10px; padding: 14px; border: 1px solid var(--border);
  border-radius: var(--r-sm); background: var(--n-100);
}
.plugin-widget-editor .plugin-meta-grid .field:last-child { grid-column: auto; }

/* Atributi: tabelarni urejevalnik za tip "json" (cenik, lastnosti izdelkov,
   delovni čas ...). Vsak stolpec dobi svojo `size` (glej columnCharSize() v
   attributes.js) — to pa deluje samo, če ga širina dejansko posluša namesto
   raztezanja na 100 % celice. Splošno pravilo za input/textarea/select v tem
   projektu ima 4 :not() v selektorju (visoka specifičnost), zato ga navadna
   .json-grid-table input pravilo ne bi preglasilo brez !important tukaj.
   Modal za ta tip preklopi na .modal--wide (glej attributeModal), da ima
   tabela dovolj prostora, preden se stolpci sploh začnejo krčiti. */
.json-grid-table input.json-cell, .json-grid-table input.json-col-name {
  width: auto !important;
  max-width: 360px;
}
.json-grid-table thead th input { font-weight: 700; }
.json-fields-list input { min-width: 0; }

/* Atributi: tabelarni urejevalnik za tip "gallery" (galerija slik). */
.gallery-grid { display: flex; flex-wrap: wrap; gap: 10px; }
.gallery-thumb {
  position: relative; width: 96px; height: 96px; border-radius: var(--r-sm);
  overflow: hidden; border: 1px solid var(--border); background: var(--n-100);
}
.gallery-thumb img, .gallery-thumb video { width: 100%; height: 100%; object-fit: cover; display: block; }
.gallery-thumb .gallery-remove-kept, .gallery-thumb .gallery-remove-pending {
  position: absolute; top: 2px; right: 2px; padding: 4px; background: var(--scrim); color: var(--text); border-radius: var(--r-sm);
}
.gallery-thumb-badge { position: absolute; top: 4px; left: 4px; }
.gallery-loop-toggle {
  position: absolute; bottom: 2px; left: 2px; display: flex; align-items: center; gap: 3px;
  padding: 2px 6px; border-radius: var(--r-pill); background: var(--scrim); color: var(--text); font-size: 11px;
  cursor: pointer; user-select: none;
}
.gallery-loop-toggle input { margin: 0; width: 12px; height: 12px; }

/* Urejanje strani: trim/crop urejevalnik za video v galeriji (video-clip-editor.js). */
/* width:fit-content plus a height cap on the video: a phone-shot portrait clip
   (1080x1920) in a 640px-wide frame would otherwise be 1138px tall, pushing the
   crop box — which is positioned in percentages of this frame — most of the way
   off the screen, where its handles cannot be reached. Shrink-wrapping the
   frame to the letterboxed video keeps the percentage geometry honest. */
.video-editor-frame {
  position: relative; width: fit-content; max-width: min(640px, 100%); margin: 0 auto 16px;
  background: #000; border-radius: var(--r-sm); overflow: hidden;
}
.video-editor-frame video { display: block; max-width: 100%; max-height: min(60vh, 520px); }
.video-editor-crop {
  position: absolute; border: 2px solid var(--primary);
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, .5); cursor: move; touch-action: none;
}
.video-editor-crop-handle {
  position: absolute; width: 14px; height: 14px; background: var(--primary);
  border: 2px solid #fff; border-radius: 50%; touch-action: none;
}
/* Inside the crop box, not straddling it. The frame is overflow:hidden and
   the box opens at 0,0 covering the whole frame, so at -8px the two top
   handles and the left pair were clipped away entirely — the editor opened
   with half its resize grips missing. */
.video-editor-crop-handle[data-handle="nw"] { top: 0; left: 0; cursor: nwse-resize; }
.video-editor-crop-handle[data-handle="ne"] { top: 0; right: 0; cursor: nesw-resize; }
.video-editor-crop-handle[data-handle="sw"] { bottom: 0; left: 0; cursor: nesw-resize; }
.video-editor-crop-handle[data-handle="se"] { bottom: 0; right: 0; cursor: nwse-resize; }
.video-editor-times { display: flex; gap: 16px; flex-wrap: wrap; }
.video-editor-times .field { flex: 1; min-width: 180px; }
.plugin-version-row {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 11px 0; border-bottom: 1px solid var(--border);
}
.plugin-version-row:last-child { border-bottom: 0; }
.plugin-version-row > div { min-width: 0; display: flex; flex-direction: column; }
.plugin-version-row small { color: var(--text-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.plan-pills { display: flex; flex-wrap: wrap; gap: 10px; }
.plan-pill { padding: 10px 16px; border: 1px solid var(--border); border-radius: var(--r-pill); text-transform: capitalize; font-weight: 700; }
.plan-pill.active { color: var(--primary-text); background: var(--primary-soft); border-color: var(--primary-text); }
.admin-hero {
  display: flex; align-items: center; justify-content: space-between; gap: 24px;
  padding: 28px; border-radius: var(--r-lg); color: #fff;
  border: 1px solid oklch(0.62 0.24 265 / 0.3);
  background:
    radial-gradient(120% 160% at 88% 0%, oklch(0.58 0.26 300 / 0.42), transparent 60%),
    linear-gradient(135deg, oklch(0.26 0.1 268), oklch(0.2 0.07 272));
}
.admin-hero h1 { color: #fff; margin: 4px 0 8px; font-size: clamp(26px, 4vw, 38px); }
.admin-hero p { color: oklch(0.87 0.03 265); margin: 0; max-width: 620px; }
.admin-hero .eyebrow { color: oklch(0.83 0.12 288); }
.admin-hero .badge { background: oklch(100% 0 0 / .12); color: #fff; border: 1px solid oklch(100% 0 0 / .18); }
.admin-stats { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.stat-card__value--text { font-family: var(--font-display); font-size: 17px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.admin-action-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.admin-action-card {
  position: relative; display: flex; flex-direction: column; align-items: flex-start; min-height: 168px;
  padding: 22px; border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface); color: var(--text); text-align: left; cursor: pointer; box-shadow: var(--shadow-sm);
  /* Its hover moves three properties and had no transition at all, so the
     whole card jumped. Matches .stat-card, which does the same lift. */
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.admin-action-card:hover { border-color: color-mix(in oklch, var(--primary) 45%, var(--border)); transform: translateY(-2px); box-shadow: var(--shadow-lift); }
.admin-action-card strong { font: 700 16px var(--font-display); margin: 18px 0 6px; }
.admin-action-card p { margin: 0; max-width: 520px; }
/* Absolutely positioned over the card, directly above the heading. With no
   width bound a longer meta string ("Preverjanje vsakih 5 min") ran into the
   title underneath it. */
.admin-action-card__meta { position: absolute; top: 18px; right: 18px; max-width: calc(100% - 44px); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-faint); font: 600 11px var(--font-mono); }
.admin-action-card__link { color: var(--primary-text); font-weight: 700; font-size: 12px; margin-top: auto; padding-top: 14px; }

/* Analytics workspace */
.analytics-header {
  display: flex; align-items: flex-end; justify-content: space-between; gap: 24px;
  margin-bottom: 20px;
}
.analytics-header h1 { margin: 3px 0 4px; }
.analytics-header p, .analytics-section-intro p, .analytics-chart-card .card-header p,
.analytics-ranking .card-header p, .web-vitals .card-header p { margin: 0; color: var(--text-muted); }
.analytics-range { display: inline-flex; flex-wrap: wrap; gap: 4px; padding: 4px; border: 1px solid var(--border); border-radius: var(--r-pill); background: var(--surface); }
.analytics-range button {
  border: 0; border-radius: var(--r-pill); padding: 7px 12px; color: var(--text-muted);
  background: transparent; font: 700 11px var(--font-body); cursor: pointer; white-space: nowrap;
}
.analytics-range button.active { color: #fff; background: var(--grad-action); }
.analytics-nav {
  /* `top: 0` stuck this to the VIEWPORT, not to the space under the chrome —
     and .topbar is also sticky at top:0 with z-index 30 in the same stacking
     context, so the only control that switches between the eight analytics
     panels slid underneath the opaque topbar and vanished on scroll. */
  position: sticky; top: var(--topbar-h); z-index: 20; display: flex; gap: 2px; overflow-x: auto;
  margin: 0 0 22px; padding: 4px; border: 1px solid var(--border); border-radius: var(--r-lg);
  background: color-mix(in oklch, var(--surface) 92%, transparent); backdrop-filter: blur(12px);
}
.analytics-nav button {
  /* `flex: 0 0 auto`, not the old `1 0 auto` with a 105px floor. Growing made
     the eight Slovenian labels claim ~931px of a container that only ever has
     ~980px at 1280px and far less below that; the floor then stopped the
     narrow ones giving any of it back. At natural width the row simply
     overflows and the container's own overflow-x scrolls it, which is what it
     was always meant to do. Shrinking is wrong here too — it just clips the
     labels inside their own buttons. */
  display: inline-flex; align-items: center; justify-content: center; gap: 7px; flex: 0 0 auto;
  min-width: 0; white-space: nowrap; border: 0; border-radius: var(--r-sm); padding: 10px 11px;
  color: var(--text-muted); background: transparent; font: 700 12px var(--font-body); cursor: pointer;
}
.analytics-nav button.active { color: var(--primary-text); background: var(--primary-soft); }
.live-pulse {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--success);
  box-shadow: 0 0 0 0 color-mix(in oklch, var(--success) 45%, transparent);
  animation: analytics-pulse 1.7s infinite;
}
@keyframes analytics-pulse {
  70% { box-shadow: 0 0 0 7px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
.analytics-panel { display: none; }
.analytics-panel.active { display: block; animation: analytics-in .18s ease-out; }
@keyframes analytics-in { from { opacity: .35; transform: translateY(3px); } }
.analytics-notice {
  margin-bottom: 18px; padding: 13px 16px; border: 1px solid color-mix(in oklch, var(--primary) 25%, var(--border));
  border-radius: var(--r-sm); color: var(--text-muted); background: var(--primary-soft); font-size: 13px;
}
.analytics-notice strong { color: var(--text); }
.analytics-notice code { color: var(--primary-text); word-break: break-word; }
.analytics-kpi-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; margin-bottom: 20px; }
.analytics-kpi-grid--compact { margin-bottom: 18px; }
.analytics-metric {
  min-width: 0; padding: 17px; border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface); box-shadow: var(--shadow-sm);
}
.analytics-metric > span { display: block; color: var(--text-muted); font-size: 12px; font-weight: 700; }
.analytics-metric > strong { display: block; margin: 5px 0 9px; font: 750 clamp(23px, 3vw, 31px) var(--font-display); letter-spacing: -.035em; }
.analytics-metric > div { display: flex; align-items: center; gap: 7px; min-width: 0; }
.analytics-metric > strong { overflow-wrap: anywhere; }
.analytics-metric small { color: var(--text-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 10px; }
.analytics-metric--small > strong { margin-bottom: 0; }
/* `0 0 auto` is right for the "+12 %" it usually holds, but this slot also
   renders a comparison label from the data. Capping and clipping keeps a long
   one inside the metric card, which has a visible border and does NOT clip. */
.analytics-delta { flex: 0 1 auto; min-width: 0; max-width: 55%; overflow: hidden; padding: 3px 6px; border-radius: var(--r-pill); font: 700 9px var(--font-mono); white-space: nowrap; }
.analytics-delta.up { color: var(--success); background: color-mix(in oklch, var(--success) 10%, transparent); }
.analytics-delta.down { color: var(--danger); background: color-mix(in oklch, var(--danger) 10%, transparent); }
.analytics-delta.neutral { color: var(--text-faint); background: var(--n-100); }
.analytics-grid { display: grid; gap: 18px; margin-bottom: 18px; }
.analytics-grid--wide { grid-template-columns: minmax(0, 1.8fr) minmax(280px, .8fr); }
.analytics-grid--two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.analytics-grid--three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.analytics-grid--live { grid-template-columns: minmax(0, 1.3fr) minmax(330px, .7fr); }
.analytics-chart-card { min-width: 0; overflow: hidden; }
.analytics-chart-card .card-header { align-items: flex-start; }
.chart-legend { display: flex; flex-wrap: wrap; gap: 11px; color: var(--text-muted); font-size: 10px; }
.chart-legend span::before { content: ""; display: inline-block; width: 8px; height: 8px; margin-right: 5px; border-radius: 50%; }
.chart-legend .views::before { background: #5b5fe8; }
.chart-legend .sessions::before { background: #11a37f; }
.chart-legend .users::before { background: #f59e0b; }
/* The SVG is emitted with viewBox="0 0 900 280" and no preserveAspectRatio,
   so a fixed 280px height letterboxed it inside a wider box. Matching the
   ratio makes the drawing fill its container at every width. */
.analytics-chart { display: block; width: 100%; height: auto; aspect-ratio: 900 / 280; overflow: visible; color: var(--text); }
.analytics-chart-empty { display: grid; place-items: center; height: 280px; color: var(--text-faint); }
.analytics-hourly { display: flex; align-items: stretch; gap: 3px; height: 236px; padding-top: 14px; }
.analytics-hourly > div { display: flex; flex: 1 1 0; flex-direction: column; justify-content: flex-end; min-width: 2px; height: 100%; }
.analytics-hourly i { display: block; min-height: 3px; border-radius: 4px 4px 1px 1px; background: linear-gradient(180deg, var(--secondary), var(--primary)); opacity: .82; }
/* Each hour column is ~9px wide at a phone width, so the axis label has no
   room to spare — clip rather than let it paint across its neighbours. */
.analytics-hourly span { min-height: 16px; padding-top: 4px; color: var(--text-faint); font: 8px var(--font-mono); text-align: center; overflow: hidden; }
.analytics-section-intro { display: flex; justify-content: space-between; gap: 20px; margin: 0 0 18px; }
.analytics-section-intro h2 { margin: 0 0 4px; }
.analytics-ranking { min-width: 0; }
.analytics-ranking-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px 12px;
  margin-top: 12px; padding-bottom: 2px;
}
.analytics-ranking-row > div:first-child { display: flex; min-width: 0; justify-content: space-between; gap: 8px; }
.analytics-ranking-row span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }
.analytics-ranking-row small { color: var(--text-faint); font: 9px var(--font-mono); }
.analytics-ranking-row > strong { font: 700 11px var(--font-mono); }
.analytics-ranking-bar { grid-column: 1 / -1; height: 4px; overflow: hidden; border-radius: 4px; background: var(--n-100); }
.analytics-ranking-bar i { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--primary), var(--secondary)); }
.realtime-hero {
  display: grid; grid-template-columns: minmax(260px, 2fr) repeat(3, minmax(118px, .55fr)); gap: 1px;
  margin-bottom: 18px; overflow: hidden; border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--border);
}
.realtime-hero > div { min-height: 102px; min-width: 0; display: flex; flex-direction: column; justify-content: center; padding: 18px 16px; background: var(--surface); }
.realtime-hero > div:first-child { display: grid; grid-template-columns: auto auto 1fr; align-items: center; gap: 10px; }
.realtime-hero > div:first-child strong { font: 750 42px var(--font-display); }
.realtime-hero > div:first-child span:last-child { color: var(--text-muted); font-size: 12px; }
/* A four-digit event count at a fixed 27px was wider than the 100px cell and
   painted across the hairline into the next one. */
.realtime-mini strong { font: 750 clamp(20px, 2.1vw, 27px) var(--font-display); overflow-wrap: anywhere; }
.realtime-mini span { color: var(--text-muted); font-size: 11px; }
.analytics-live-map [data-map], .analytics-live-map svg { max-width: 100%; }
.analytics-live-list { max-height: 250px; margin-top: 12px; overflow: auto; border-top: 1px solid var(--border); }
.analytics-live-list > div {
  display: grid; grid-template-columns: auto minmax(90px, .7fr) minmax(100px, 1fr) auto;
  align-items: center; gap: 8px; padding: 9px 2px; border-bottom: 1px solid var(--border); font-size: 11px;
}
.analytics-live-list code { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-muted); }
.analytics-live-list small { color: var(--text-faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.analytics-device-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--n-400); }
.analytics-device-dot.desktop { background: var(--primary); }
.analytics-device-dot.mobile { background: var(--secondary); }
.analytics-device-dot.tablet { background: var(--warning); }
.analytics-event-feed { max-height: 485px; overflow: auto; }
.analytics-event-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 10px;
  padding: 9px 0; border-bottom: 1px solid var(--border);
}
.analytics-event-row > div { min-width: 0; }
.analytics-event-row time { white-space: nowrap; }
.analytics-event-row > span { display: grid; place-items: center; width: 22px; height: 22px; color: var(--primary-text); border-radius: 50%; background: var(--primary-soft); }
.analytics-event-row > span.conversion { color: var(--warning); }
.analytics-event-row strong, .analytics-event-row small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.analytics-event-row strong { font-size: 11px; }
.analytics-event-row small, .analytics-event-row time { color: var(--text-faint); font-size: 9px; }
.analytics-mini-row { display: grid; grid-template-columns: minmax(70px, 1fr) 1fr auto; align-items: center; gap: 9px; margin: 10px 0; font-size: 11px; }
.analytics-mini-row > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.analytics-mini-row > i { height: 5px; overflow: hidden; border-radius: 5px; background: var(--n-100); }
.analytics-mini-row b { display: block; height: 100%; background: var(--secondary); }
.analytics-mini-row strong { font: 700 10px var(--font-mono); }
.analytics-events-table { overflow-x: auto; }
.analytics-events-row {
  display: grid; grid-template-columns: auto minmax(170px, 1.4fr) minmax(100px, .7fr) 90px 80px;
  align-items: center; gap: 12px; min-width: 620px; padding: 10px 4px; border-bottom: 1px solid var(--border);
}
/* Without min-width:0 this column refused to shrink, and its children had no
   clip at all — a tracked path with a query string is one unbreakable token
   and painted straight over the channel column beside it. */
.analytics-events-row > div { min-width: 0; }
.analytics-events-row > div strong, .analytics-events-row > div small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.analytics-events-row > div small, .analytics-events-row > span:not(.analytics-event-icon), .analytics-events-row time { color: var(--text-faint); font-size: 10px; }
.analytics-event-icon { display: grid; place-items: center; width: 24px; height: 24px; color: var(--primary-text); border-radius: 50%; background: var(--primary-soft); }
.web-vitals { margin-bottom: 18px; }
.web-vitals > div:last-child { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
.web-vitals > div:last-child > div {
  display: grid; grid-template-columns: auto 1fr; gap: 2px 7px; align-items: center;
  padding: 13px; border: 1px solid var(--border); border-radius: var(--r-sm);
}
.web-vitals span { width: 8px; height: 8px; border-radius: 50%; }
.web-vitals span.good { background: var(--success); }
.web-vitals span.needs-work { background: var(--warning); }
.web-vitals b { grid-column: 1 / -1; font: 750 21px var(--font-display); overflow-wrap: anywhere; }
.web-vitals small { grid-column: 1 / -1; color: var(--text-faint); font-size: 9px; }
.analytics-plugin-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin-bottom: 18px; }
.analytics-plugin-card { min-width: 0; }
.analytics-plugin-head { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 10px; }
.analytics-plugin-head > div { min-width: 0; }
.analytics-plugin-head h3 { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.analytics-plugin-head code { color: var(--text-faint); font-size: 9px; }
.analytics-plugin-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; margin: 14px 0; overflow: hidden; border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--border); }
.analytics-plugin-stats > div { padding: 12px; background: var(--surface); }
.analytics-plugin-stats strong, .analytics-plugin-stats span { display: block; }
.analytics-plugin-stats strong { font: 750 22px var(--font-display); }
.analytics-plugin-stats span { color: var(--text-faint); font-size: 9px; }
.analytics-plugin-actions > span { display: flex; justify-content: space-between; gap: 8px; padding: 6px 0; border-bottom: 1px solid var(--border); font-size: 10px; }
.analytics-plugin-actions > span strong { font-family: var(--font-mono); }

/* ---- overflow discipline -------------------------------------------------
 *
 * One shared cause sat behind most of the "text runs over the border" reports,
 * and it is a spec default rather than a mistake in any one screen: a flex or
 * grid child has `min-width: auto`, so it refuses to shrink below its own
 * content. Put a customer's e-mail address, domain, campaign code or error
 * class name in one and the child stops giving way — instead it pushes its
 * whole row wider than the panel and paints across the edge.
 *
 * The rules below fix that structurally, so a screen added later inherits the
 * behaviour instead of having to remember it:
 *
 *   1. the text side of a two-part row may shrink,
 *   2. the control side may not (a squashed button is worse than a wrapped one),
 *   3. containers that draw a visible edge clip, as a last-resort backstop.
 *
 * Deliberately NOT a blanket `* { min-width: 0 }`: that also lets buttons and
 * icons collapse, which trades one visual bug for a subtler one.
 */

/* 1 — In a `justify-between` row the first child is the copy and the rest are
   controls. This is the single most repeated layout in the app. */
.flex.justify-between > :first-child,
.card-header > :first-child,
.activity-row > :first-child,
.list-card > :first-child,
.plugin-version-row > :first-child,
.check-card__copy,
.backup-timeline__head > :first-child {
  min-width: 0;
}

/* 2 — Individual controls keep their size; only the copy gives way. Note this
   is deliberately limited to leaf controls. A GROUP of controls must do the
   opposite: pinning a button row at its natural width stops it being bounded
   by the panel at all, so instead of wrapping onto a second line it simply
   hangs over the edge. Groups shrink and wrap; the buttons inside them don't. */
.card-header > .btn,
.flex.justify-between > .btn,
.flex.justify-between > .switch,
.activity-row > .btn,
.list-card > .btn {
  flex-shrink: 0;
}
.card-header > .flex,
.flex.justify-between > .flex {
  min-width: 0;
  flex-wrap: wrap;
}

/* 3 — Data-bearing text wraps rather than overhangs. `anywhere` (not
   `break-word`) because only `anywhere` also lowers the intrinsic minimum
   width, which is what lets the containing flex/grid track shrink at all.
   It still breaks a word only when that word genuinely cannot fit, so
   ordinary Slovenian prose is untouched. */
.card p, .card h3, .stat-card, .list-card, .check-card, .detail-grid > div,
.analytics-metric, .analytics-plugin-card, .backup-timeline__card,
.plugin-version-row, .feature-check, .plan-pill, .empty-state,
.onboarding-contact-card, .custom-plugin-cta, .hero-panel, .admin-hero,
.admin-action-card, .modal, .api-key-reveal, .analytics-notice,
.backup-safety-note, td, .toast {
  overflow-wrap: anywhere;
}

/* 4 — Backstop. Every selector here draws its own border or fill, so anything
   that still escapes would visibly cross a line the user can see. `.card`,
   `.stat-card` and `.table-wrap` already clip; these were the ones that did
   not. Clipping is the floor, not the fix — rules 1-3 are what keep content
   inside in the first place. */
.analytics-metric, .analytics-plugin-stats > div, .realtime-hero > div,
.web-vitals > div:last-child > div, .detail-grid > div, .feature-check,
.check-card, .backup-timeline__card, .analytics-live-list > div,
.analytics-event-row, .analytics-events-row, .breadcrumb-row, .plan-pill,
.api-key-reveal, .analytics-notice, .backup-safety-note, .replay-event {
  overflow: hidden;
}

/* 5 — Long unbroken machine strings (ids, fingerprints, keys, URLs, JSON) show
   up in every staff screen. `.mono` is the marker the app already uses for
   them, so it is the right hook. */
.mono, code, .code-panel { overflow-wrap: anywhere; }
/* …except where the app has deliberately asked for one clipped line — those
   set their own nowrap and must keep it. */
.mono[class*="nowrap"], .activity-row > span.mono { overflow-wrap: normal; }

/* `minmax(0, 1fr)` rather than a bare `1fr` on every collapse below. An `fr`
   track's automatic minimum is `auto`, i.e. its content's min-content width —
   so a single wide child (a table, a long domain, a code block) silently makes
   the "one column" track wider than the screen and the whole page scrolls
   sideways. The explicit 0 minimum is what actually pins the track to the
   container and lets the child's own overflow rules handle the rest. */
@media (max-width: 1050px) {
  .dashboard-grid { grid-template-columns: minmax(0, 1fr); }
  .overview-stats, .admin-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .analytics-kpi-grid, .analytics-plugin-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Its narrowest moment is 901px, not 760px: the 252px sidebar is still in
     the grid above 900px, so four vitals cards are tighter here than they are
     on a phone. */
  .web-vitals > div:last-child { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Same 901px squeeze as .web-vitals above, same reason: the 252px sidebar is
     still in the grid there, so this row is tighter just above the sidebar's
     own breakpoint than it ever is on a phone. Its four tracks have a ~619px
     floor against ~591px of content column, so the last tile overflowed and
     `overflow: hidden` ate its padding. Collapsing only at 760px was too late. */
  .realtime-hero { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .realtime-hero > div:first-child { grid-column: 1 / -1; }
  .analytics-grid--wide, .analytics-grid--live { grid-template-columns: minmax(0, 1fr); }
  .analytics-grid--three { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 760px) {
  .hero-panel, .custom-plugin-cta { align-items: flex-start; flex-direction: column; }
  /* Same "title block + trailing control" pattern as .hero-panel: on a phone
     the trailing badge/button squeezes the heading into a two-word-per-line
     column, so give the heading the full width and drop the control below. */
  .admin-hero, .site-admin-heading, .onboarding-heading,
  .error-detail-head, .analytics-section-intro {
    align-items: flex-start; flex-direction: column;
  }
  /* Once these become columns, `align-items: flex-start` stops meaning
     "align to the top" and starts meaning "size to your own content" — it is
     the cross axis that flipped, not just the direction. So the title block
     inside each of them sized to its longest line and hung off the right edge
     of the panel: a long heading or a customer's domain painted straight
     across the border on every phone. Capping the children at the container
     width is what makes them wrap instead. */
  .hero-panel > *, .custom-plugin-cta > *, .admin-hero > *,
  .site-admin-heading > *, .onboarding-heading > *, .error-detail-head > *,
  .analytics-section-intro > *, .onboarding-contact-card > *,
  .analytics-header > * {
    max-width: 100%;
    min-width: 0;
  }
  .feature-grid { grid-template-columns: minmax(0, 1fr); }
  .card-header { align-items: flex-start; flex-wrap: wrap; }
  /* Wrapping the header itself is not enough when the trailing slot is a group
     of its own — a segmented switch next to a button, say. That group has to be
     allowed to wrap internally too, or it stays one unbreakable block and
     overhangs the panel exactly as before. */
  .card-header > .flex { flex-wrap: wrap; }
  /* Both are horizontal strips of choices whose number is not fixed (one
     button per page of the site, per date range, …). Scrolling is the honest
     behaviour for those on a phone; growing past the screen edge is not. */
  .segmented, .site-admin-subnav { max-width: 100%; overflow-x: auto; }
  /* The replay time ("00:00 / 00:04") is a single unbreakable reading; without
     nowrap it folded onto three lines inside the toolbar. */
  .replay-toolbar { flex-wrap: wrap; }
  .replay-toolbar .mono { white-space: nowrap; }
  .admin-action-grid { grid-template-columns: minmax(0, 1fr); }
  .error-detail-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* A full Slovenian timestamp does not fit one line in a half-width cell;
     let it wrap instead of ellipsing away the year. */
  .error-detail-stats strong { white-space: normal; overflow: visible; overflow-wrap: anywhere; }
  .error-actions { flex-direction: column; }
  .error-actions > .flex, .error-actions .btn { width: 100%; }
  .breadcrumb-row { grid-template-columns: auto 90px 1fr; }
  .breadcrumb-row code { display: none; }
  .plugin-meta-grid { grid-template-columns: minmax(0, 1fr); }
  .plugin-meta-grid .field:last-child { grid-column: auto; }
  .onboarding-contact-card { align-items: flex-start; flex-direction: column; }
  .analytics-header { align-items: flex-start; flex-direction: column; }
  .analytics-kpi-grid, .analytics-grid--two, .analytics-grid--three, .analytics-plugin-grid { grid-template-columns: minmax(0, 1fr); }
  .analytics-range { max-width: 100%; overflow-x: auto; flex-wrap: nowrap; }
  .web-vitals > div:last-child { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .analytics-live-list > div { grid-template-columns: auto minmax(90px, 1fr) auto; }
  .analytics-live-list code { display: none; }
}

@media (max-width: 900px) {
  .topbar .menu-btn { display: inline-flex; }
}

@media (max-width: 520px) {
  /* Two up rather than one: these readings are short (a count, "0 min",
     "99,9 %"), so a full-width card per stat pushed the table four screens
     down for no gain. Trim the side padding so two still breathe at 375px. */
  .overview-stats, .admin-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); }
  .overview-stats .stat-card, .admin-stats .stat-card { padding: var(--sp-3) var(--sp-4); }
  .overview-stats .stat-card::before, .admin-stats .stat-card::before { left: var(--sp-3); right: var(--sp-3); }
  .overview-stats .stat-card__value, .admin-stats .stat-card__value { font-size: 22px; }
  /* The rule above out-specifies .stat-card__value--text, which exists exactly
     to render a NAME rather than a number at a smaller size — so at 520px the
     site name was blown back up to 22px and ellipsed after a few characters. */
  .overview-stats .stat-card__value--text, .admin-stats .stat-card__value--text { font-size: 15px; }
  .workspace-switcher button { padding: 6px 8px; }
  /* Both are nowrap rows whose trailing chip/button is pinned; only at phone
     widths is there genuinely not enough room, so the wrap is scoped here
     rather than applied unconditionally (which regressed 520-1130px). */
  .backup-timeline__head { flex-wrap: wrap; }
  .backup-safety-note { flex-wrap: wrap; }
  .check-card { flex-wrap: wrap; }
  .check-card__action { width: 100%; margin-left: 42px; }
  .workspace-switcher button[data-mode="client"] { font-size: 0; }
  .workspace-switcher button[data-mode="client"]::after { content: "Stranka"; font-size: 11px; }
  .analytics-kpi-grid { grid-template-columns: minmax(0, 1fr); }
  .realtime-hero { grid-template-columns: minmax(0, 1fr); }
  .realtime-hero > div:first-child { grid-column: auto; }
  .web-vitals > div:last-child { grid-template-columns: minmax(0, 1fr); }
}
