/*
 * portal.css — the Smart Mirror portal's design layer on top of the vendored
 * Bootstrap 5.3.8 (dark colour mode only). It implements the ROGR corporate
 * design language from the logomanual (blue #008CD2, dark grey #3C4141, mono
 * grey #9B9B9B, Open Sans, the negative logotype on dark surfaces) and is
 * owned by this project: tokens, navbar, cards, tables, validation and the
 * confirm modal first, then the portal-only pieces at the end (autocomplete,
 * pairing code, row groups, the hub/widget shell, the template picker).
 */


@font-face {
  font-family: "Open Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/open-sans-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: "Open Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/open-sans-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+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Open Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("fonts/open-sans-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: "Open Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("fonts/open-sans-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+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Open Sans";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/open-sans-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: "Open Sans";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/open-sans-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+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root,
[data-bs-theme="dark"] {
  color-scheme: dark;
  --rogr-blue: #008cd2;
  --rogr-blue-hover: #0079b8;
  --rogr-blue-active: #006fa6;
  --rogr-blue-light: #3fa9e0;
  --rogr-grey: #3c4141;
  --rogr-grey-light: #9b9b9b;

  --bs-font-sans-serif: "Open Sans", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --bs-body-font-size: 0.9375rem;

  --bs-body-bg: #1f2324;
  --bs-body-bg-rgb: 31, 35, 36;
  --bs-body-color: #eceeee;
  --bs-body-color-rgb: 236, 238, 238;
  --bs-emphasis-color: #ffffff;
  --bs-emphasis-color-rgb: 255, 255, 255;
  --bs-secondary-color: var(--rogr-grey-light);
  --bs-secondary-color-rgb: 155, 155, 155;
  --bs-secondary-bg: #2a2f30;
  --bs-secondary-bg-rgb: 42, 47, 48;
  --bs-tertiary-color: #7d8283;
  --bs-tertiary-color-rgb: 125, 130, 131;
  --bs-tertiary-bg: #262b2c;
  --bs-tertiary-bg-rgb: 38, 43, 44;
  --bs-border-color: #5a6161;
  --bs-border-color-translucent: rgba(255, 255, 255, 0.08);
  --bs-heading-color: #ffffff;

  --bs-primary: var(--rogr-blue);
  --bs-primary-rgb: 0, 121, 184;
  --bs-primary-text-emphasis: #6cc4f0;
  --bs-primary-bg-subtle: #0a2f43;
  --bs-primary-border-subtle: #0b5a82;
  --bs-link-color: var(--rogr-blue-light);
  --bs-link-color-rgb: 63, 169, 224;
  --bs-link-hover-color: #6cc4f0;
  --bs-link-hover-color-rgb: 108, 196, 240;
  --bs-link-decoration: none;
  --bs-focus-ring-color: rgba(0, 140, 210, 0.35);

  --bs-secondary-text-emphasis: #c4c7c7;
  --bs-dark-rgb: 60, 65, 65;
}

a:hover { text-decoration: underline; }
.btn:hover { text-decoration: none; }

/* Links inside copy need an underline at rest too (colour alone is not
 * enough to mark them as links, WCAG 1.4.1); everywhere a link is really a
 * styled control (nav, rows, dropdown items, tabs, the brand mark) gets an
 * explicit reset instead of inheriting this. */
p a, li a, .muted a, .text-secondary a, .form-text a, dd a { text-decoration: underline; text-underline-offset: .15em; }
.btn, .nav-link, .list-row, .list-row-main, .sidebar-link, .bottom-link, .segmented a, .dropdown-item, .navbar-brand { text-decoration: none; }

/* Focus rings on the custom link-shaped rows/menus that don't get Bootstrap's
 * own focus styling (they carry text-decoration: none above, not a .btn/.form-control
 * class). scroll-padding keeps a focused/linked-to row clear of the fixed
 * navbar and phone bottom bar when the page jumps to it. */
.sidebar-link:focus-visible, .bottom-link:focus-visible, .list-row:focus-visible, .list-row-main:focus-visible, .segmented a:focus-visible, .dropdown-item:focus-visible, .sidebar-mirror:focus-visible { outline: 2px solid var(--rogr-blue-light); outline-offset: 2px; }
html { scroll-padding-top: 5rem; scroll-padding-bottom: 5.5rem; }

/* ---------- buttons in brand blue ----------
 * .btn-primary fills a large surface with white text on top, so it uses a
 * darker step of the brand blue than --rogr-blue (kept for borders, focus
 * rings, links and the .pv accent) to clear WCAG contrast on that much
 * paint; hover/active step down further, same as before. */
.btn-primary {
  --bs-btn-color: #fff;
  --bs-btn-bg: #0079b8;
  --bs-btn-border-color: #0079b8;
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: #006fa6;
  --bs-btn-hover-border-color: #006fa6;
  --bs-btn-focus-shadow-rgb: 38, 157, 216;
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: #00608f;
  --bs-btn-active-border-color: #00608f;
  --bs-btn-disabled-color: #fff;
  --bs-btn-disabled-bg: #0079b8;
  --bs-btn-disabled-border-color: #0079b8;
}
.btn-outline-primary {
  --bs-btn-color: var(--rogr-blue-light);
  --bs-btn-border-color: var(--rogr-blue);
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: var(--rogr-blue);
  --bs-btn-hover-border-color: var(--rogr-blue);
  --bs-btn-focus-shadow-rgb: 0, 140, 210;
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: var(--rogr-blue-active);
  --bs-btn-active-border-color: var(--rogr-blue-active);
  --bs-btn-disabled-color: var(--rogr-blue-light);
  --bs-btn-disabled-border-color: var(--rogr-blue);
}
.btn-outline-secondary {
  --bs-btn-color: #c4c7c7;
  --bs-btn-border-color: #7a8181;
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: var(--rogr-grey);
  --bs-btn-hover-border-color: var(--rogr-grey);
  --bs-btn-active-bg: var(--rogr-grey);
  --bs-btn-active-border-color: var(--rogr-grey);
}
.btn-dark { --bs-btn-bg: var(--rogr-grey); --bs-btn-border-color: var(--rogr-grey); }

/* ---------- forms ---------- */
.form-control, .form-select {
  --bs-body-bg: #1a1e1f;
  background-color: #1a1e1f;
  border-color: #767d7d;
}
.form-control:focus, .form-select:focus {
  border-color: var(--rogr-blue);
  box-shadow: 0 0 0 0.25rem rgba(0, 140, 210, 0.25);
}
.form-control::placeholder { color: #828888; }
/* Unchecked border: a real control boundary (unlike --bs-border-color's other,
 * decorative uses on cards/dividers), so it gets its own 3:1-on-card value
 * instead of inheriting --bs-border-color. */
.form-check-input { border-color: #767d7d; }
.form-check-input:checked { background-color: var(--rogr-blue); border-color: var(--rogr-blue); }
.form-check-input:focus { border-color: var(--rogr-blue); box-shadow: 0 0 0 0.25rem rgba(0, 140, 210, 0.25); }
.form-label { color: var(--rogr-grey-light); font-size: 0.8125rem; margin-bottom: 0.25rem; }
.form-text { color: var(--rogr-grey-light); }

/* ---------- navbar ---------- */
.app-nav {
  --bs-navbar-padding-y: 0.75rem;
  --bs-navbar-color: #c4c7c7;
  --bs-navbar-hover-color: #fff;
  --bs-navbar-active-color: #fff;
  background-color: #2a2f30;
  border-bottom: 1px solid var(--rogr-grey);
}
.app-nav .navbar-nav { gap: 0.25rem; }
.app-nav .nav-link { padding: 0.375rem 0.75rem; border-radius: var(--bs-border-radius); }
.app-nav .nav-link:hover { background-color: rgba(255, 255, 255, 0.06); }
.app-nav .nav-link.active { background-color: rgba(0, 140, 210, 0.18); color: #fff; }
/* Mirror switcher dropdown (2+ mirrors): the current mirror is the toggle,
   the open menu marks it with the same brand tint as the active nav link. */
.app-nav .dropdown-menu { --bs-dropdown-link-active-bg: rgba(0, 140, 210, 0.18); --bs-dropdown-link-active-color: #fff; }
.app-nav .dropdown-item.active { font-weight: 600; }
.app-nav .navbar-toggler { border-color: var(--rogr-grey); }
.app-nav .navbar-toggler-icon { filter: invert(1) grayscale(1); }
/* Protective zone per logomanual: the wordmark height is 85 units, the dot
 * 24 - at 26 px height that is ~7 px around; navbar padding covers it. */
.brand-mark { height: 26px; width: auto; display: block; }
.brand-sub { color: var(--rogr-grey-light); font-size: 0.875rem; letter-spacing: 0.02em; padding-left: 0.75rem; border-left: 1px solid var(--rogr-grey); line-height: 1.2; }

/* ---------- page chrome ---------- */
.page-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.75rem; margin-bottom: 1.5rem; }
.page-head h1 { font-weight: 700; }
footer { color: var(--rogr-grey-light); border-color: var(--rogr-grey) !important; }

.card {
  --bs-card-bg: #2a2f30;
  --bs-card-border-color: var(--rogr-grey);
  --bs-card-cap-bg: rgba(255, 255, 255, 0.03);
  --bs-card-cap-color: var(--bs-body-color);
}
.card-header { border-bottom-color: var(--rogr-grey); }
.card-footer { border-top-color: var(--rogr-grey); background-color: rgba(255, 255, 255, 0.02); }

.table {
  --bs-table-bg: transparent;
  --bs-table-color: var(--bs-body-color);
  --bs-table-border-color: var(--rogr-grey);
  --bs-table-hover-bg: rgba(255, 255, 255, 0.035);
  --bs-table-hover-color: var(--bs-body-color);
  --bs-table-striped-bg: rgba(255, 255, 255, 0.02);
}
.table > thead th { color: var(--rogr-grey-light); font-weight: 600; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; white-space: nowrap; border-bottom-width: 1px; }
.table > :not(caption) > * > * { padding: 0.6rem 0.75rem; }
.table-sm > :not(caption) > * > * { padding: 0.3rem 0.4rem; }
.card > .table-responsive > .table { margin-bottom: 0; }
.card > .table-responsive > .table > :not(caption) > tr:last-child > *,
.card > .table-responsive > .table > tbody > tr:last-child > * { border-bottom: 0; }
.tx-party { min-width: 16rem; }
.row-review > * { background-color: rgba(255, 193, 7, 0.07); box-shadow: inset 3px 0 0 rgba(255, 193, 7, 0.6); }
.row-review > *:not(:first-child) { box-shadow: none; }

.badge { font-weight: 600; letter-spacing: 0.02em; }
.text-bg-dark { border: 1px solid #555b5b; }

/* ---------- dashboard ---------- */
.kpi-label { color: var(--rogr-grey-light); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; }
.kpi-value { font-size: 1.375rem; font-weight: 700; line-height: 1.2; white-space: nowrap; }
.kpi-note { color: var(--rogr-grey-light); font-size: 0.8125rem; }
.progress, .progress-stacked { --bs-progress-bg: var(--rogr-grey); --bs-progress-height: 0.75rem; --bs-progress-bar-bg: var(--rogr-blue); }
.limit-bar .progress { transition: width 0.3s ease; }

/* ---------- item tables (invoice lines, customer template) ---------- */
.items { table-layout: auto; }
.items .row-no { color: var(--rogr-grey-light); width: 2.5rem; text-align: right; font-variant-numeric: tabular-nums; }
.items td { white-space: nowrap; }
.items td:nth-child(2) { width: 100%; }
.items td:nth-child(2) .form-control { min-width: 12rem; }
.items .w-qty { width: 5.5rem; }
.items .w-price { width: 7rem; }
.items .w-pct { width: 4.75rem; }
.items .w-unit { width: 5.25rem; }
.items .w-kind { width: 8.5rem; }
.items .form-control-sm, .items .form-select-sm { min-width: 0; }
.remove-row { --bs-btn-color: var(--rogr-grey-light); --bs-btn-hover-color: #ff6b6b; font-size: 1.1rem; line-height: 1; padding: 0.1rem 0.4rem; }

/* ---------- detail lists ---------- */
.detail-list dt { color: var(--rogr-grey-light); font-weight: 600; font-size: 0.8125rem; }
.detail-list dd { margin-bottom: 0.5rem; }

/* ---------- login ---------- */
.login-wrap { min-height: 60vh; }
.login-card { max-width: 420px; align-self: flex-start; }
.login-logo { height: 40px; width: auto; }

/* ---------- alerts & modal ---------- */
.alert { border-width: 0; border-left: 4px solid; }
.alert-success { border-left-color: #75b798; }
.alert-danger { border-left-color: #ea868f; }
.alert-warning { border-left-color: #ffda6a; }
.modal-content { background-color: #2a2f30; border-color: var(--rogr-grey); }
.modal-header, .modal-footer { border-color: var(--rogr-grey); }

.text-nowrap { white-space: nowrap; }
.tabular { font-variant-numeric: tabular-nums; }

/* ---------- validation: show only what is wrong ----------
 * Bootstrap's .was-validated also paints every valid control green with a
 * check icon; on dense item tables that is noise, so valid controls keep
 * their normal look and only :invalid ones are highlighted. */
.was-validated .form-control:valid, .was-validated .form-control:valid:focus,
.was-validated .form-select:valid, .was-validated .form-select:valid:focus,
.was-validated .form-check-input:valid { border-color: #767d7d; box-shadow: none; }
.was-validated .form-control:valid { background-image: none; padding-right: 0.75rem; }
.was-validated .form-control-sm:valid { padding-right: 0.5rem; }
.was-validated .form-select:valid { background-image: var(--bs-form-select-bg-img); padding-right: 2.25rem; }
.was-validated .form-select-sm:valid { padding-right: 1.75rem; }
.was-validated .form-control:valid:focus, .was-validated .form-select:valid:focus { border-color: var(--rogr-blue); box-shadow: 0 0 0 0.25rem rgba(0, 140, 210, 0.25); }
.was-validated .form-check-input:valid:checked { background-color: var(--rogr-blue); border-color: var(--rogr-blue); }
.was-validated .form-check-input:valid ~ .form-check-label { color: inherit; }
.items .invalid-feedback { font-size: 0.75rem; margin-top: 0.15rem; white-space: normal; max-width: 12rem; }

/* ---------- dashboard income chart (inline SVG, styled here – no inline styles) ---------- */
.income-chart { display: block; width: 100%; height: auto; max-height: 140px; }
.income-chart .bar:not(.bar-unpaid) { fill: var(--rogr-blue); opacity: 0.55; }
.income-chart .bar-current { opacity: 1; }
.income-chart .bar-unpaid { opacity: 0.9; }
.income-chart .stripe { fill: var(--rogr-grey-light); opacity: 0.7; }
.chart-legend i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; vertical-align: -1px; margin: 0 2px 0 6px; }
.chart-legend .legend-received { background: var(--rogr-blue); }
.chart-legend .legend-unpaid { background: repeating-linear-gradient(45deg, var(--rogr-grey-light) 0 2px, transparent 2px 4px); border: 1px solid var(--rogr-grey-light); }
.income-chart .bar-label { fill: var(--rogr-grey-light); font-size: 13px; text-anchor: middle; font-family: var(--bs-font-sans-serif); }


/* =====================================================================
 * Portal-specific layer (mirror-api). Class names below are load-bearing
 * for app.js and the tests (stack/data-label, autocomplete, row-group,
 * pair-code, error-summary) — keep them.
 * ===================================================================== */

.form-switch { margin-bottom: 1rem; }

/* Floating labels on dark inputs. */
.form-floating > label { color: var(--rogr-grey-light); }
.form-floating > .form-control:focus ~ label, .form-floating > .form-control:not(:placeholder-shown) ~ label, .form-floating > .form-select ~ label { color: var(--rogr-grey-light); }
.form-floating > .form-control:focus ~ label::after, .form-floating > .form-control:not(:placeholder-shown) ~ label::after, .form-floating > .form-select ~ label::after { background-color: #1a1e1f; }
/* A floating textarea keeps its rows: Bootstrap pins every floating control to one line
   (the notifications page's topic labels, up to 20 lines). */
.form-floating > textarea.form-control { height: auto; min-height: 9rem; }
.form-control[type="color"] { padding: .25rem; width: 3rem; }

/* The release upload input is pinned byte-for-byte by a test (no class
 * attribute available), so the bare type selector dresses it up. */
.card input[type="file"]:not(.form-control) { display: block; width: 100%; padding: .375rem .75rem; background-color: #1a1e1f; border: 1px solid #767d7d; border-radius: var(--bs-border-radius); color: var(--bs-body-color); font: inherit; }

/* Row groups (news feeds, calendars, commute routes): 5 rows, separated. */
.row-group { display: grid; gap: .75rem; margin-bottom: 1rem; }
.row-group > div { padding-bottom: .75rem; border-bottom: 1px solid var(--rogr-grey); }
.row-group > div:last-child { border-bottom: none; padding-bottom: 0; }
.day-checkboxes { display: flex; flex-wrap: wrap; gap: .25rem 1rem; margin-bottom: .5rem; }
.day-checkboxes .form-check-inline { margin-right: 0; }
.route-card { border: 1px solid var(--rogr-grey); border-radius: var(--bs-border-radius); padding: 0.875rem; margin-bottom: 0.875rem; }

/* Commute (task audit): day-of-week picker as pills with a real 44/40px hit
 * area (the checkbox itself is invisible but still the thing that's focused
 * and toggled — .day-checkboxes above stays for whatever still uses it). */
.day-pills { display: flex; flex-wrap: wrap; gap: .35rem; }
.day-pills label {
  position: relative; min-width: 44px; min-height: 40px; display: inline-flex; align-items: center;
  justify-content: center; padding: 0 .6rem; border: 1px solid #767d7d; border-radius: 999px; cursor: pointer;
}
.day-pills input { position: absolute; opacity: 0; inset: 0; margin: 0; }
.day-pills input:checked + span { color: #fff; }
.day-pills label:has(input:checked) { background: rgba(0, 121, 184, .35); border-color: #0079b8; }
.day-pills label:has(input:focus-visible) { outline: 2px solid var(--rogr-blue-light); outline-offset: 2px; }

/* Commute geocode fallback: a <details> holding the raw lat/lon fields for
 * when the address search comes up empty. */
.coords { margin: .25rem 0 .75rem; }
.coords summary { cursor: pointer; color: var(--rogr-grey-light); font-size: .875rem; }
.coords-ok { font-size: .875rem; color: #75b798; }

/* Geocode autocomplete listbox (app.js), block-level wrapper for iOS Safari. */
.autocomplete { position: relative; display: block; }
.autocomplete-list { position: absolute; top: 100%; left: 0; right: 0; z-index: 1040; margin-top: .25rem; background-color: #262b2c; border: 1px solid var(--rogr-grey); border-radius: var(--bs-border-radius); box-shadow: var(--bs-box-shadow); max-height: 220px; overflow-y: auto; list-style: none; padding: .25rem 0; }
.autocomplete-list li { padding: .5rem .75rem; cursor: pointer; font-size: .875rem; color: #c4c7c7; }
.autocomplete-list li:not(.autocomplete-note):hover, .autocomplete-list li.active { background-color: rgba(0, 140, 210, 0.18); color: #fff; }
/* The "nothing found" / "search failed" item is a message, not a choice. */
.autocomplete-list .autocomplete-note { cursor: default; color: var(--rogr-grey-light); }

.pair-code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 1.25rem; text-transform: uppercase; letter-spacing: .35em; }

/* Legacy text helpers still used in mirror.html. */
.meta, .muted { color: var(--rogr-grey-light); font-size: .875rem; }

/* Validation summary keeps its bare class (pinned by a test); it looks like a danger alert. */
.error-summary { --bs-alert-color: var(--bs-danger-text-emphasis); --bs-alert-bg: var(--bs-danger-bg-subtle); --bs-alert-border-color: var(--bs-danger-border-subtle); position: relative; padding: 1rem; margin-bottom: 1rem; color: var(--bs-alert-color); background-color: var(--bs-alert-bg); border: 0; border-left: 4px solid #ea868f; border-radius: var(--bs-border-radius); }
.error-summary ul { margin: .35rem 0 0 1.1rem; padding: 0; }

/* ---------- layout editor (mirror.html tab „Rozložení", app.js §5) ---------- */
.le-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem; margin-bottom: 1rem; }
.le-toolbar .form-select { width: auto; }
.le-body { display: grid; grid-template-columns: minmax(0, 1fr) 240px; gap: 1.5rem; align-items: start; }
.le-lists { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; }
.le-col { background: var(--bs-tertiary-bg); border-radius: .5rem; padding: .75rem; }
.le-h { font-size: .8rem; text-transform: uppercase; letter-spacing: .06em; color: var(--rogr-grey-light); margin: 0 0 .5rem; }
.le-list { list-style: none; margin: 0; padding: 0; min-height: 2.5rem; }
.le-item, .le-rotation { background: var(--bs-secondary-bg); border: 1px solid var(--bs-border-color); border-radius: .375rem; padding: .4rem .5rem; margin-bottom: .4rem; }
.le-rotation { border-style: dashed; }
.le-rotation .le-rot-list { margin-top: .4rem; padding-left: .75rem; border-left: 2px solid var(--rogr-blue); }
.le-row { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem .5rem; }
.le-handle { cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none; color: var(--rogr-grey-light); padding: 0 .25rem; font-size: 1.1rem; }
.le-label { font-weight: 600; margin-right: auto; }
.le-disabled { opacity: .55; }
.le-off, .le-hint { font-size: .75rem; color: var(--rogr-grey-light); }
.le-hint { margin: .25rem 0; }
.le-mini { font-size: .8rem; display: inline-flex; align-items: center; gap: .25rem; margin: 0; }
.le-interval { width: 4.5rem; display: inline-block; }
.le-kbd { display: inline-flex; align-items: center; gap: .25rem; }
.le-kbd .btn-link { min-width: 24px; min-height: 24px; padding: .25rem; display: inline-flex; align-items: center; justify-content: center; }
/* Below the desktop pointer-precision assumption, bump the up/down/remove
 * buttons to the 44px touch target (WCAG 2.5.8/2.5.5). */
@media (max-width: 575px) {
  .le-kbd .btn-link { min-width: 44px; min-height: 44px; }
}
.le-moveto { width: auto; display: inline-block; font-size: .75rem; }
.le-ghost { position: fixed; z-index: 1060; list-style: none; pointer-events: none; opacity: .9; box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .4); transform: rotate(-1.5deg); }
.le-dragging { opacity: .35; }
.le-placeholder { height: 2.25rem; border: 2px dashed var(--rogr-blue); border-radius: .375rem; margin-bottom: .4rem; background: rgba(0, 140, 210, .08); }
.le-incomplete { border-color: #d9a400; }
/* The board itself is the shared .pv component below; .le-preview only
   reserves the editor's slot for it. The wrap is centred so the board sits
   in the middle of its column on the desktop grid and in the middle of the
   page once that grid collapses to one column on a phone. */
.le-preview { min-height: 8rem; }
.le-preview-wrap { margin-inline: auto; }
/* Only the caption is centred — text-align on the wrap would cascade into
   the board and centre every widget label with it. */
.le-preview-wrap > .muted { text-align: center; }
/* Badge classes belong to the shared board (app.js §7), not to the editor
   that first had them. */
.pv-badge { position: absolute; bottom: 0; left: 0; right: 0; font-size: .55rem; line-height: 1.2; text-align: center; padding: 1px 2px; }
.pv-badge-over { background: #dc3545; color: #fff; }
.pv-badge-under { background: rgba(255, 255, 255, .15); color: #fff; }
/* wave 3a §1.5: one badge per column, one line per finding, and the editor's
   promote button under them (pvDraw, badges: "all"). */
.pv-badge { display: flex; flex-direction: column; align-items: center; gap: 1px; }
.pv-badge-line { display: block; }
.pv-promote { font-size: .55rem; line-height: 1.2; padding: 0 .3rem; border: 1px solid #fff; border-radius: .2rem; color: #fff; background: rgba(0, 0, 0, .45); }
@media (max-width: 720px) {
  .le-body { grid-template-columns: 1fr; }
  .le-preview-wrap { max-width: 260px; }
}

/* "TV" tab's evening-start time input — no wider than it needs to be. */
.tv-time { max-width: 14rem; }

/* =====================================================================
   Mirror hub shell (spec 2026-09-08 §2/§8) — phone bottom bar, desktop
   sidebar, sync line. Bootstrap keeps the grid/forms; these are the
   product-specific components the redesign adds.
   ===================================================================== */
.mirror-shell { display: grid; gap: 1.5rem; }
@media (min-width: 768px) { .mirror-shell { grid-template-columns: 15rem minmax(0, 1fr); } }
.mirror-main { min-width: 0; }

.sidebar { display: none; }
@media (min-width: 768px) {
  .sidebar { display: block; position: sticky; top: 5rem; align-self: start; }
}
.sidebar-title { display: block; font-weight: 700; margin-bottom: 0.75rem; }
.sidebar-nav { display: flex; flex-direction: column; gap: 0.125rem; }
.sidebar-link {
  display: block; padding: 0.6rem 0.75rem; border-radius: var(--bs-border-radius);
  color: var(--bs-body-color); text-decoration: none; min-height: 44px;
}
.sidebar-link:hover { background-color: rgba(255, 255, 255, 0.06); text-decoration: none; }
.sidebar-link.is-active { background-color: rgba(0, 140, 210, 0.18); color: #fff; font-weight: 600; }

/* Widget page on a wide screen: main menu | widget menu | the form. The
   menu is the Obsah list without its switches and summaries; below 992 px
   the phone/tablet flow (list page → widget page → Zpět na obsah) stays. */
.widget-nav { display: none; }
@media (min-width: 992px) {
  .widget-shell { display: grid; grid-template-columns: 21rem minmax(0, 1fr); gap: 1.5rem; align-items: start; }
  /* order:-1 puts the menu back in the left column; widget.html renders it
     after .widget-main so the form comes first in the DOM (UX audit). */
  .widget-nav { display: block; order: -1; position: sticky; top: 5rem; max-height: calc(100vh - 6rem); overflow-y: auto; padding-right: .25rem; }
  .widget-main { min-width: 0; }
}
.widget-nav-title { display: block; font-weight: 700; color: var(--bs-body-color); text-decoration: none; margin-bottom: 0.75rem; }
.widget-nav-title:hover { color: #fff; }
.widget-nav-head { margin-top: .75rem; }
/* The current widget's row in the side column: brand tint plus the accent
 * edge the sidebar uses for its own active entry. */
.widget-nav .list-row.is-active { background-color: rgba(0, 140, 210, 0.18); box-shadow: inset 3px 0 0 var(--rogr-blue); }

.bottom-bar {
  position: fixed; inset: auto 0 0 0; z-index: 1030;
  display: flex; justify-content: space-around;
  background-color: #2a2f30; border-top: 1px solid var(--rogr-grey);
  padding-bottom: env(safe-area-inset-bottom, 0);
}
@media (min-width: 768px) { .bottom-bar { display: none; } }
.bottom-link {
  flex: 1; text-align: center; padding: 0.75rem 0.25rem; min-height: 48px;
  color: var(--rogr-grey-light); text-decoration: none; font-size: 0.8125rem;
}
.bottom-link.is-active { color: #fff; box-shadow: inset 0 2px 0 var(--rogr-blue); }
body.has-bottom-bar { padding-bottom: 4.5rem; }
@media (min-width: 768px) { body.has-bottom-bar { padding-bottom: 0; } }

/* Hub: from 992 px the preview sits beside the section list instead of
   pushing it below the fold. */
@media (min-width: 992px) { .hub-grid { display: grid; grid-template-columns: 18rem minmax(0, 1fr); gap: 1.5rem; align-items: start; } }

/* WiFi QR box (the /portal/mirror/{id}/wifi page — the same two rules
   static/qr.css gives the standalone /qr page, which layout.html does not
   link). The QR must sit on white to scan reliably from a phone screen. */
#qr-box { display: inline-block; background: #fff; padding: 1rem; border-radius: 1rem; line-height: 0; }
#qr-box img, #qr-box canvas { width: 100%; max-width: 300px; height: auto; }

/* Sync state: on the hub only, beside the status pill (the list rows carry
   the same text inline, fleet-style). */
.hub-state { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 0.75rem; }
.sync { font-size: 0.8125rem; color: var(--rogr-grey-light); }
/* The bullet is decorative (the sync- state class + the text after it
 * already say the state); the "/" alt-text syntax keeps a screen reader
 * from announcing "black circle" on top of that text. */
.sync::before { content: "●" / ""; margin-right: 0.4rem; }
.sync-synced::before { color: #75b798; }
.sync-pending::before { color: #ffda6a; }
.sync-offline::before { color: #ea868f; }

/* list rows: the hub's four entries, the widget list, the people list */
.list-rows { list-style: none; margin: 0; padding: 0; }
.list-rows > li + li { border-top: 1px solid var(--rogr-grey); }
.list-row {
  display: flex; align-items: center; gap: 0.75rem; min-height: 56px;
  padding: 0.75rem 1rem; color: var(--bs-body-color); text-decoration: none;
}
a.list-row:hover { background-color: rgba(255, 255, 255, 0.04); text-decoration: none; }
/* The hub's rows are one <a class="list-row">, but the widget list's rows are
   an <li class="list-row"> holding several controls (the title link, the
   toggle form, the decorative chevron) — so the hover highlight hangs off the row
   itself there, and the links inside inherit the row's colour instead of the
   default link blue + underline. */
li.list-row:hover { background-color: rgba(255, 255, 255, 0.04); }
a.list-row-main, a.list-row-main:hover, a.list-row-chevron, a.list-row-chevron:hover {
  text-decoration: none;
}
a.list-row-main, a.list-row-main:hover { color: inherit; }
.list-row-main { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.list-row-title { font-weight: 600; }
.list-row-sub { color: var(--rogr-grey-light); font-size: 0.8125rem; }
.list-row-chevron { color: var(--rogr-grey-light); font-size: 1.25rem; }
/* status dot on the mirror list (/portal/, 2+ mirrors): green online, red for
   a mirror that went quiet, grey for one that has never reported — the same
   three states the hub's sync line uses. */
.row-dot { flex: 0 0 auto; width: 0.5rem; height: 0.5rem; border-radius: 50%; background-color: var(--rogr-grey-light); }
.row-dot-online { background-color: #75b798; }
.row-dot-offline { background-color: #ea868f; }
.row-dot-idle { background-color: var(--rogr-grey-light); }

/* ---------- layout preview board (app.js §7) ----------
   One component behind the hub, the Vzhled page, the template thumbnails and
   the advanced editor. Item heights are set from JS (the CSP forbids
   style="" in markup) and are the share of the column the widget really
   takes; the fill item fades out at the bottom exactly like the mirror's own
   fill mask. */
.pv {
  aspect-ratio: 9 / 16; display: flex; flex-direction: column; gap: 2%;
  padding: 3%; background-color: #000; border-radius: 0.5rem; border: 1px solid var(--rogr-grey);
}
.pv-header { flex: 0 0 auto; height: 11%; border-bottom: 1px solid rgba(255, 255, 255, 0.25); }
.pv-cols { flex: 1; display: flex; gap: 3%; min-height: 0; }
/* A column clips its own overflow: an over-full stack is reported by the
   editor's badge, it must never paint over the caption or the card title
   below the board. */
/* gap: 2% must equal layoutNominalGap in layout.go (served as data-nominal
   "gap") — the Go save-time warnings and the JS overflow sum both count it. */
.pv-col { display: flex; flex-direction: column; gap: 2%; min-width: 0; min-height: 0; overflow: hidden; position: relative; }
.pv-col.pv-over { outline: 1px solid #dc3545; outline-offset: 2px; }
.pv-box {
  flex: 0 0 auto; overflow: hidden; border-radius: 3px; padding: 2px 4px;
  font-size: 0.7rem; line-height: 1.25; color: #e9ecef;
  background-color: rgba(0, 140, 210, 0.32);
}
.pv-box.pv-fill { background-color: transparent; background-image: linear-gradient(rgba(0, 140, 210, 0.32) 62%, rgba(0, 140, 210, 0)); }
.pv-box.pv-rot { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.45); }
/* Sizes: the page-level board, and the thumbnail on a template card, where
   the geometry is the whole message and a label would only be noise. */
/* Both sizes state width and height explicitly in the 9:16 the mirror has:
   with only a max-width the board grows to half a phone screen, and
   aspect-ratio cannot shrink it back against a max-height. */
.pv-lg { width: 11.25rem; height: 20rem; margin: 0 auto 0.75rem; }
.pv-mini { width: 4.5rem; height: 8rem; align-self: center; padding: 0.3rem; border-radius: 4px; }
.pv-mini .pv-box { font-size: 0; padding: 0; }

/* hub preview: the no-JS fallback list, upgraded to the board above by app.js */
.preview {
  display: flex; gap: 0.5rem; aspect-ratio: 9 / 16; max-width: 15rem;
  margin: 0 auto 0.75rem; padding: 0.5rem;
  background-color: #12191a; border: 1px solid var(--rogr-grey); border-radius: var(--bs-border-radius);
}
.preview-col { display: flex; flex-direction: column; gap: 0.35rem; min-width: 0; }
.preview-w1 { flex: 1; } .preview-w2 { flex: 2; } .preview-w3 { flex: 3; } .preview-w4 { flex: 4; }
.preview-item {
  background-color: rgba(0, 140, 210, 0.18); border-radius: 3px;
  padding: 0.25rem 0.35rem; font-size: 0.6875rem; color: #dfe3e3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* switch: a submit button drawn as a track+thumb, so a toggle works with
   JS off (app.js §6 upgrades the same button to fetch) */
.switch { margin: 0; }
.switch-btn {
  background: none; border: 0; padding: 0.5rem; min-width: 44px; min-height: 44px;
  display: inline-flex; align-items: center; cursor: pointer;
}
.switch-track {
  display: inline-block; width: 40px; height: 22px; border-radius: 11px;
  background-color: #767d7d; position: relative; transition: background-color 0.15s ease;
}
.switch-thumb {
  position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%;
  background-color: #dfe3e3; transition: transform 0.15s ease;
}
.switch-btn[aria-checked="true"] .switch-track { background-color: var(--rogr-blue); }
.switch-btn[aria-checked="true"] .switch-thumb { transform: translateX(18px); }
.switch-btn:focus-visible { outline: 2px solid var(--rogr-blue); outline-offset: 2px; }
.switch-btn.is-busy { opacity: .6; cursor: progress; }
@media (prefers-reduced-motion: reduce) { .switch-track, .switch-thumb { transition: none; } }

.group-head { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--rogr-grey-light); margin: 0 0 0.5rem; }

/* toast: Bootstrap's .toast look, pinned above the bottom bar. It carries a
 * real close button now (C3) and app.js drives the 7s auto-hide/pause-on-
 * hover timer with the .is-hiding class below — no CSS keyframe timer, so a
 * visitor without JS keeps the toast until they navigate away (their only
 * dismissal path is a page load, same as the flash-banner alert). */
.portal-toast {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: 5rem; z-index: 1080;
  max-width: min(28rem, calc(100vw - 2rem)); padding: 0.75rem 1rem;
  background-color: #2a2f30; border: 1px solid var(--rogr-grey); border-left: 4px solid #75b798;
  border-radius: var(--bs-border-radius); box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.4);
  color: var(--bs-body-color); font-size: 0.875rem;
  display: flex; align-items: flex-start; gap: .75rem; transition: opacity .3s;
}
@media (min-width: 768px) { .portal-toast { bottom: 1.5rem; } }
.portal-toast.is-hiding { opacity: 0; visibility: hidden; }
.portal-toast .btn-close { flex: none; margin-top: .15rem; }
@media (prefers-reduced-motion: reduce) { .portal-toast { transition: none; } }

/* Lidé (task 13): the role picker on person.html, and the list's status badge */
.segmented .form-check { margin-right: 1rem; min-height: 44px; display: inline-flex; align-items: center; gap: 0.35rem; }
/* new-person role picker (plain links, not radios — see person.html): the
   same .segmented container, styled as a row of link "pills"; the current
   role is the one carrying aria-current="page", no extra class needed. */
.segmented a { margin-right: 0.5rem; min-height: 44px; padding: 0 0.9rem; display: inline-flex; align-items: center;
  border: 1px solid var(--rogr-grey); border-radius: var(--bs-border-radius); color: var(--bs-body-color); text-decoration: none; }
.segmented a:hover { background-color: rgba(255, 255, 255, 0.06); text-decoration: none; }
.segmented a[aria-current="page"] { background-color: #0079b8; border-color: var(--rogr-blue); color: #fff; font-weight: 600; }
.list-row .badge { flex: none; }
/* Dirty-guard "Neuloženo" badge (C9) sits right after a label/heading. */
.badge-unsaved { margin-left: .5rem; }

/* Zařízení (task 15): status tiles grid */
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: 0.75rem; }
.tile { border: 1px solid var(--rogr-grey); border-radius: var(--bs-border-radius); padding: 0.75rem; }
.tile-label { color: var(--rogr-grey-light); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; }
.tile-value { font-size: 1.05rem; font-weight: 600; }

/* ---------- Vzhled: template picker (look.html, app.js §8) ----------
   Cards are labels wrapping a hidden radio, so the whole card is the hit
   target and the keyboard still lands on a real radio. The thumbnail is
   drawn from the template's own output for this mirror's widgets, so it
   tells the truth instead of being a fixed picture. */
.tpl-grid { display: grid; grid-template-columns: 1fr; gap: 0.75rem; border: 0; padding: 0; margin: 0 0 1rem; }
@media (min-width: 768px) { .tpl-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
/* Four cards per row from four generated templates on (wave 3a §2.2); a
   fifth card (Vlastní) wraps. */
@media (min-width: 992px) { .tpl-grid.tpl-grid-wide { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.tpl-card {
  position: relative;
  display: flex; flex-direction: column; gap: 0.5rem; cursor: pointer;
  padding: 0.75rem; border: 1px solid #767d7d; border-radius: var(--bs-border-radius);
  background-color: rgba(255, 255, 255, 0.02); transition: border-color 0.15s ease, background-color 0.15s ease;
}
.tpl-card:hover { border-color: var(--rogr-grey-light); }
.tpl-card:has(input:checked) { border-color: var(--rogr-blue); background-color: rgba(0, 140, 210, 0.12); }
.tpl-card:has(input:focus-visible) { outline: 2px solid var(--rogr-blue-light); outline-offset: 2px; }
/* Selected state was colour-only (border + tint); the checkmark gives it a
 * shape cue too. */
.tpl-card:has(input:checked)::after {
  content: "✓"; position: absolute; top: .5rem; right: .6rem; color: #fff; background: #0079b8;
  border-radius: 50%; width: 1.4rem; height: 1.4rem; display: inline-flex; align-items: center;
  justify-content: center; font-size: .8rem;
}
.tpl-thumb {
  display: flex; gap: 0.25rem; aspect-ratio: 9 / 16; max-height: 9rem; align-self: center;
  width: 100%; max-width: 5.25rem; padding: 0.3rem;
  background-color: #12191a; border-radius: 4px;
}
.tpl-col { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
.tpl-w1 { flex: 1; } .tpl-w2 { flex: 2; } .tpl-w3 { flex: 3; } .tpl-w4 { flex: 4; }
.tpl-item { background-color: rgba(0, 140, 210, 0.35); border-radius: 2px; min-height: 0.4rem; flex: 0 0 0.4rem; }
.tpl-item.tpl-l { flex-basis: 0.85rem; }
.tpl-item.tpl-fill { flex: 1 1 auto; background-color: rgba(0, 140, 210, 0.18); }
.tpl-text { display: flex; flex-direction: column; gap: 0.25rem; }
.tpl-name { font-weight: 600; font-size: 0.9375rem; overflow-wrap: normal; hyphens: manual; }
/* No line-clamp any more (it cut Czech copy off mid-word with no way to read
 * the rest); cards are free to grow, and overflow-wrap/hyphens: manual keep
 * long words from being broken mid-syllable since there's no line-clamp
 * height pressure left to justify it. */
.tpl-desc {
  color: var(--rogr-grey-light); font-size: 0.75rem; line-height: 1.3;
  overflow-wrap: normal; hyphens: manual;
}
/* Phones (after the base rules, which share their specificity): three templates as three rows (thumb beside name and description)
 * instead of a 2 + 1 grid that leaves the third card orphaned. */
@media (max-width: 767.98px) {
  .tpl-card { flex-direction: row; align-items: center; gap: 0.75rem; }
  .tpl-card .tpl-thumb { flex: 0 0 4rem; max-width: 4rem; max-height: 6.5rem; align-self: stretch; }
  .tpl-card .tpl-text { min-width: 0; }
}

/* ---------- Vzhled: placement rows (look.html, app.js §8) ----------
   Zone select + ▲▼ per widget. Both are real submits, so the page reorders
   without JS; §8 turns them into in-page moves and redraws the preview. */
.place-rows { list-style: none; margin: 0 0 0.75rem; padding: 0; border: 1px solid var(--rogr-grey); border-radius: var(--bs-border-radius); }
.place-rows > li + li { border-top: 1px solid var(--rogr-grey); }
.place-row {
  display: grid; grid-template-columns: 1fr auto; align-items: center;
  gap: 0.5rem 0.75rem; padding: 0.625rem 0.75rem;
}
.place-main { display: flex; flex-direction: column; min-width: 0; }
.place-title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.place-sub { color: var(--rogr-grey-light); font-size: 0.8125rem; }
.place-zone { grid-column: 1; width: auto; min-width: 7.5rem; }
.place-move { grid-column: 2; grid-row: 1 / span 2; display: flex; flex-direction: column; gap: 0.25rem; }
.place-move .btn { min-width: 44px; min-height: 44px; line-height: 1; }
@media (min-width: 576px) {
  .place-row { grid-template-columns: 1fr auto auto; }
  .place-zone { grid-column: 2; }
  .place-move { grid-column: 3; grid-row: 1; flex-direction: row; }
}

/* ---------- onboarding wizard (setup_*.html) ----------
   Four steps, one card, no bottom bar: the customer is not inside a mirror
   context yet. The connector line is drawn on the item, not between items,
   so a wrapped row cannot leave a dangling line. */
.setup-shell { max-width: 34rem; margin: 0 auto; }
.steps { list-style: none; display: flex; margin: 0 0 1.25rem; padding: 0; counter-reset: step; }
.step { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 0.35rem; position: relative; min-width: 0; }
.step + .step::before {
  content: ""; position: absolute; top: 0.875rem; right: 50%; left: -50%; height: 2px;
  background-color: var(--rogr-grey);
}
.step-dot {
  position: relative; z-index: 1; width: 1.75rem; height: 1.75rem; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background-color: var(--bs-body-bg); border: 2px solid var(--rogr-grey);
  color: var(--rogr-grey-light); font-size: 0.8125rem; font-weight: 600;
}
.step-label { color: var(--rogr-grey-light); font-size: 0.75rem; text-align: center; }
.step.is-done .step-dot { background-color: var(--rogr-blue); border-color: var(--rogr-blue); color: #fff; }
.step.is-done + .step::before { background-color: var(--rogr-blue); }
.step.is-current .step-dot { border-color: var(--rogr-blue); color: var(--bs-body-color); }
.step.is-current .step-label { color: var(--bs-body-color); font-weight: 600; }
.setup-actions { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap; }
/* Admin screenshot card (spec 2026-09-28 §3.4): the mirror's 2160×3840
   picture scaled to the card, the link opens it full size. */
.screenshot-img { width: 100%; height: auto; display: block; border-radius: 0.5rem; background: #000; }
