/* 10-primitives-overrides.css — part 10/10 of the ordered bp bundle (the former single bp.css,
   split by surface; the content is UNREORDERED). ORDER IS LOAD-BEARING: cascade ties
   resolve by document order, so hosts must link 01→10 in exactly this sequence —
   CssHygieneTests pins both the list and the order in both hosts. */

/* ── Shared spinner, confirm dialog, long-tail utility classes ─── */
/* The ONE .rp-spin definition (RpSpinner + every inline <span class="rp-spin">): --primary ink, 24px.
   Surfaces that sit the spinner on another ground override only the two border colours
   (.rp-notif-loading, .rp-rchat-empty, .rp-deckpick-loading, and the submit button below). */
.rp-spin { display: inline-block; width: 24px; height: 24px; border-radius: 50%;
  border: 2.5px solid color-mix(in srgb, var(--primary) 25%, transparent);
  border-top-color: var(--primary); animation: rp-spin-rotate .7s linear infinite; }
.rp-spin--sm { width: 16px; height: 16px; border-width: 2px; }
/* Inside the filled submit button the spinner must be the button's ink, not --primary on --primary. */
.rp-btn-submit .rp-spin { width: 15px; height: 15px; border-width: 2px;
  border-color: color-mix(in srgb, var(--primary-foreground) 40%, transparent);
  border-top-color: var(--primary-foreground); }
@keyframes rp-spin-rotate { to { transform: rotate(360deg); } }
.rp-confirm-dlg { max-width: 420px; }
.rp-confirm-dlg__title { font-size: 1.0625rem; font-weight: 600; color: var(--foreground); }
.rp-confirm-dlg__text {
  margin: 10px 0 0;
  font-size: .875rem;
  color: var(--muted-foreground);
  /* wave-9 T2: TutorHome's Decline confirm folds a consequences bullet list into this plain string —
     the first multi-line caller, so newlines need to actually break. */
  white-space: pre-line;
}
.rp-confirm-dlg__actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px; }
.rp-dim { color: var(--muted-foreground); font-size: .8125rem; }
.rp-icon-btn { display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px;
  border: none; background: transparent; border-radius: var(--radius-md); color: var(--muted-foreground);
  cursor: pointer; }
.rp-icon-btn:hover { background: var(--accent); color: var(--accent-foreground); }

/* ── Wave-9 Task 2: native Rp*-input primitives ──────────────────────────────────────────────────
   RpTextField/RpSelect/RpAutocomplete replace their MudBlazor-backed versions with plain HTML,
   reusing the existing rp-sched-dlg-input look (no new visual language — blueprint-pitfalls.md).
   rp-field is the shared label+control+hint column; rp-ac* is the native <input list=…><datalist>
   autocomplete (RpAutocomplete); rp-avatar is RpAvatar's circle; rp-busybtn-spin is RpBusyButton's
   inline spinner gap. */
.rp-field { display: flex; flex-direction: column; gap: 6px; }
.rp-field textarea.rp-sched-dlg-input { height: auto; min-height: 72px; padding: 10px 12px; resize: vertical; }
.rp-field select.rp-sched-dlg-input { cursor: pointer; }
.rp-field input.rp-sched-dlg-input[readonly] { background: var(--muted); cursor: default; }
.rp-field-counter { align-self: flex-end; font-size: 11.5px; color: var(--muted-foreground); margin-top: -2px; }
.rp-field-counter--low { color: var(--destructive); }
.rp-field-err { font-size: 12.5px; color: var(--destructive); }

/* ── Unified native form controls (owner feedback: dropdowns looked like unstyled system
   selects with a mis-aligned arrow, and raw system checkboxes appeared instead of styled
   controls) ──────────────────────────────────────────────────────────────────────────────
   These base rules normalise EVERY native <select> and checkbox/radio across the cabinet in one
   place, regardless of which bespoke class (rp-sched-dlg-input / rp-set-input / rp-gw-input /
   rp-onb-select / rp-adm-select / rp-tm-filter / …) the call site happens to carry — so the look
   is identical everywhere and future controls inherit it for free. No markup changes needed. */

/* Selects: drop the OS arrow, draw our own chevron. The !important flags are load-bearing — nearly
   every select class sets a `background:` shorthand (which resets background-image/position) and a
   `padding:` shorthand (which resets padding-right), both at higher specificity than this element
   selector; !important lets the shared chevron survive without touching each class. */
select {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='16'%20height='16'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%238b95a5'%20stroke-width='2.25'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='m6%209%206%206%206-6'/%3E%3C/svg%3E") !important;
  background-repeat: no-repeat !important;
  background-position: right 11px center !important;
  background-size: 15px !important;
  padding-right: 34px !important;
}
select:disabled { opacity: 0.6; cursor: default; }

/* Checkboxes & radios: replace the OS control with a brand-tinted box (checkbox) / circle (radio),
   drawn purely in CSS so it renders identically on every OS. The check/dot are background-image
   because pseudo-elements don't apply to replaced <input> elements. */
input[type="checkbox"],
input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  margin: 0;
  flex-shrink: 0;
  vertical-align: middle;
  border: 1.5px solid var(--input, var(--border));
  background-color: var(--card);
  background-repeat: no-repeat;
  background-position: center;
  cursor: pointer;
  transition: background-color .12s, border-color .12s;
}
input[type="checkbox"] { border-radius: 5px; }
input[type="radio"] { border-radius: 50%; }
input[type="checkbox"]:checked,
input[type="radio"]:checked {
  border-color: var(--primary);
  background-color: var(--primary);
}
input[type="checkbox"]:checked {
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='12'%20height='12'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='white'%20stroke-width='3.25'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M20%206%209%2017l-5-5'/%3E%3C/svg%3E");
  background-size: 12px;
}
input[type="radio"]:checked {
  background-image: radial-gradient(circle at center, #fff 0 34%, transparent 37%);
}
input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}
input[type="checkbox"]:disabled,
input[type="radio"]:disabled { opacity: 0.5; cursor: default; }

/* Canonical toggle switch — 36×21 track / 16px thumb / on-thumb at 17.5px. Consolidated here from
   6 hand-rolled <button role="switch"> widgets that all share this exact geometry. Only the
   on-state trigger differs per widget (a `--on` class vs an `[aria-checked="true"]` attribute),
   enumerated in the two on-state groups below; component-specific extras (focus ring, disabled,
   row alignment) stay co-located at each call site. Add a new switch by joining its track/thumb/on
   selectors here — do NOT re-declare the geometry locally (that drift is exactly what wave-4 had
   to hand-unify).
   rp-cld-switch (lesson-dialog redesign, Task 8) deliberately did NOT join this group: its
   high-fidelity design reference specifies 34×20/16px, not this canonical 36×21 — see the
   dedicated .rp-cld-switch rule near the dialog's other rp-cld-* rules instead.
   rp-sitemob-switch (SP3) is the second exception, and the only MODIFIER one: it joins this group
   for everything but the geometry, then restates track and thumb at 52×44/24px because a switch a
   finger has to hit is not the same control as one a mouse pointer does — see its own rule at the
   end of the rp-sitemob-* block for the full argument. */
.rp-tm-sw,
.rp-notif-switch,
.rp-set-switch,
.rp-fin-price-switch,
.rp-adm-switch {
  position: relative;
  flex-shrink: 0;
  width: 36px;
  height: 21px;
  border: 0;
  padding: 0;
  border-radius: 999px;
  background: var(--border);
  cursor: pointer;
  transition: background 0.15s;
}
.rp-tm-sw-thumb,
.rp-notif-switch-thumb,
.rp-set-switch-thumb,
.rp-fin-price-switch-thumb,
.rp-adm-switch-thumb {
  position: absolute;
  top: 2.5px;
  left: 2.5px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #fff;
  box-shadow: var(--shadow-xs);
  transition: left 0.15s;
}
.rp-tm-sw--on,
.rp-notif-switch[aria-checked="true"],
.rp-set-switch[aria-checked="true"],
.rp-fin-price-switch[aria-checked="true"],
.rp-adm-switch[aria-checked="true"] { background: var(--primary); }
.rp-tm-sw--on .rp-tm-sw-thumb,
.rp-notif-switch[aria-checked="true"] .rp-notif-switch-thumb,
.rp-set-switch[aria-checked="true"] .rp-set-switch-thumb,
.rp-fin-price-switch[aria-checked="true"] .rp-fin-price-switch-thumb,
.rp-adm-switch[aria-checked="true"] .rp-adm-switch-thumb { left: 17.5px; }

/* Canonical feature button — radius-md / weight 600 / inline-flex. The standard cabinet action button
   shared across its page families (prof/stu/crsl/ted/sched/scard/tm/covw/th); consolidated here so the
   radius, weight and flex-centering can't drift per-family (wave-4 had to hand-unify exactly this).
   `height` stays local (some families raise it to 44px on mobile via their own media rules, which a
   later shared height would clobber by source order); per-family padding, font-size, gap, border/fill
   and justify stay local too, as do the --primary/--ghost/--outline variants. */
.rp-prof-btn,
.rp-stu-btn,
.rp-crsl-btn,
.rp-ted-btn,
.rp-sched-btn,
.rp-scard-btn,
.rp-tm-btn,
.rp-covw-btn {
  display: inline-flex;
  align-items: center;
  border-radius: var(--radius-md);
  font-weight: 600;
  cursor: pointer;
}

/* Canonical text field — full-width, 1px var(--input) frame / radius-md / card fill / 12px inset. Shared
   by the standard dialog/form inputs (sched-dlg/tm-dlg/ted/gw/set), so the frame can't drift per-family
   (border colour + radius + fill were the field-side of the wave-4 unification). `height` (42px), `font`
   and `font-size` stay local: gw raises height+font-size on mobile via its own media rule and `font:
   inherit` would reset the local size — a later shared value clobbers both by source order. Compound
   overrides (select padding, .rp-field textarea/[readonly], .rp-ac-wrap) still win on specificity. */
.rp-sched-dlg-input,
.rp-tm-dlg-input,
.rp-ted-input,
.rp-gw-input,
.rp-set-input {
  width: 100%;
  padding: 0 12px;
  border: 1px solid var(--input);
  border-radius: var(--radius-md);
  background: var(--card);
  color: var(--foreground);
}

.rp-ac-wrap { position: relative; display: flex; align-items: center; }
.rp-ac-wrap input.rp-sched-dlg-input { padding-right: 30px; }
.rp-ac-clear {
  position: absolute;
  right: 4px;
  width: 24px;
  height: 24px;
  border: none;
  background: transparent;
  border-radius: 50%;
  color: var(--muted-foreground);
  cursor: pointer;
  font-size: 15px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.rp-ac-clear:hover { background: var(--accent); color: var(--accent-foreground); }

.rp-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border-radius: 50%;
  background: var(--primary);
  color: var(--primary-foreground);
  font-weight: 600;
  width: 40px;
  height: 40px;
  font-size: 15px;
  flex-shrink: 0;
}
.rp-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }

.rp-busybtn-spin { margin-right: 6px; }

/* RpImageField's preview: no rule existed pre-wave-9 (MudImage's Fluid="true" supplied the
   max-width:100% responsive sizing via Mud's own CSS) — added here so dropping MudImage doesn't
   let a full-resolution cover image blow out its unconstrained parent (LessonCoverEditor,
   CourseOverviewPage, ImageBlockEditor all render it with no fixed-size wrapper). */
.rp-image-field-preview { max-width: 100%; height: auto; display: block; border-radius: var(--radius-md); }
.rp-image-field-placeholder { display: flex; align-items: center; justify-content: center; color: var(--muted-foreground); }

/* OnboardingChecklistCard — the "Начало работы" card on TutorHome (wave-9 T3, MudPaper/MudList/
   MudListItem → rp-card chrome + a plain checklist). Header/body/title/chip chrome is REUSED from
   rp-card/-card-header/-card-title/-card-body + rp-chip verbatim; only the list rows below are new.
   .rp-onboarding-done (line-through + muted opacity on a completed item's label) already existed
   pre-wave-9 in app.css — reused unchanged. */
.rp-onboarding-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.rp-onboarding-item { display: flex; align-items: center; gap: 10px; padding: 8px 0; }
.rp-onboarding-item--disabled { color: var(--muted-foreground); }
.rp-onboarding-link {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1;
  min-width: 0;
  color: var(--foreground);
  text-decoration: none;
}
.rp-onboarding-link:hover { color: var(--primary); }
.rp-onboarding-ico--done { color: var(--success); flex-shrink: 0; display: inline-flex; }
.rp-onboarding-ico--todo { color: var(--primary); flex-shrink: 0; display: inline-flex; }
.rp-onboarding-ico--soon { color: var(--muted-foreground); flex-shrink: 0; display: inline-flex; }
.rp-onboarding-sub { margin: 0 0 12px; }
/* П-3 fix: collapsed-by-default compact state (progress bar + single next step). */
.rp-onboarding-progress { width: 100%; height: 6px; border-radius: 999px; background: var(--muted); overflow: hidden; margin-bottom: 12px; }
.rp-onboarding-progress-fill { height: 100%; background: var(--primary); border-radius: 999px; }
.rp-onboarding-next { justify-content: space-between; }
.rp-onboarding-next:hover { color: var(--primary); }
.rp-onboarding-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 8px;
}
.rp-onboarding-continue { display: inline-flex; align-items: center; gap: 6px; }

/* AiCreditBalanceWidget — the AiFeaturesPanel credit chip (wave-9 T3, replaces MudTooltip+MudChip+MudLink). */
.rp-aicb { display: inline-flex; align-items: center; gap: 8px; }
.rp-aicb-buy { font-size: 12px; color: var(--muted-foreground); text-decoration: underline; }
.rp-aicb-buy:hover { color: var(--foreground); }

/* wave-9 T7 — Pages/Tests + Pages/Homework + Pages/Onboarding sweep (replaces the remaining Mud* usage
   there). rp-hws-decision--warning completes the tone set HomeworkSubmissionPage's read-only arm already
   uses (--success/--error) for HomeworkSubmissionDetail's 3-way decision banner (parent view can show a
   RevisionRequested attempt the student page never renders read-only, since it auto-enters redo instead). */
.rp-hws-decision--warning { background: var(--warning-muted); color: var(--warning-muted-foreground); }

/* ========================================================================
   Mud utility compat (wave-9 T9, LOCKED-11) — MudBlazor.min.css left the bundle entirely this
   task (package reference + <script>/<link> tags removed), but its own bare utility classes
   (d-flex, pa-8, gap-2, ...) are still literally written into class="..." attributes across
   Cabinet.Shared's .razor markup — pages converted off Mud components in earlier wave-9 tasks
   kept these utility classes as-is rather than migrating every call site to a named rp-* class
   (see wave9-minor-backlog.md item 14). Losing MudBlazor.min.css would otherwise silently drop
   every one of these declarations and reflow dozens of pages.

   This is NOT a general-purpose utility system — it re-emits ONLY the exact classes the
   following inventory found in use at T9 time, at MudBlazor's own scale (1 spacing unit = 4px:
   pa-8 = padding 32px, mt-1 = margin-top 4px, gap-2 = gap 8px), so already-converted layouts
   stay pixel-for-pixel identical. Re-run before trusting this block stale:

     grep -rhoE 'class="[^"]*"' src/Repetitum.Cabinet.Shared --include='*.razor' \
       | grep -oE '\b(d-[a-z-]+|flex-[a-z0-9-]+|justify-[a-z-]+|align-[a-z-]+|gap-[0-9]+|[mp][atblrxy]?-[0-9]+|rounded[a-z-]*|w-100|h-100)\b' \
       | sort | uniq -c | sort -rn

   Redistributing these call sites onto named rp-* classes (removing the need for this block
   entirely) is backlog, not this task's job — see wave9-minor-backlog.md item 14. */
.d-flex { display: flex; }
.d-block { display: block; }

.flex-column { flex-direction: column; }
.flex-wrap { flex-wrap: wrap; }
.flex-grow-1 { flex-grow: 1; }

.justify-center { justify-content: center; }
.align-center { align-items: center; }
.align-start { align-items: flex-start; }

.gap-1 { gap: 4px; }
.gap-2 { gap: 8px; }
.gap-3 { gap: 12px; }
.gap-4 { gap: 16px; }

.pa-3 { padding: 12px; }
.pa-8 { padding: 32px; }
.py-4 { padding-top: 16px; padding-bottom: 16px; }

.mt-1 { margin-top: 4px; }
.mt-2 { margin-top: 8px; }
.mt-3 { margin-top: 12px; }
.mb-1 { margin-bottom: 4px; }
.mb-2 { margin-bottom: 8px; }
.mb-3 { margin-bottom: 12px; }
.mb-4 { margin-bottom: 16px; }

/* ── Portal stacking ladder (consolidated — the ONLY place portal z-indexes are set) ──
   .blazorblueprint-portal is display:contents: it generates NO box, so position/z-index on the
   wrapper itself is a silent no-op (a wave-2 `z-index: 250` rule pretended otherwise for months).
   Portal children stack DIRECTLY against the shell chrome in the body context, and their Tailwind
   z-50 loses to it — symptom: a mobile dialog's close «✕» sat under the sticky topbar and clicks
   landed on the topbar. The explicit ladder (bp.css is un-layered, Tailwind utilities are
   @layer'ed, so these win without !important):
     shell chrome: topbar 90 · bottom nav 100 · more-sheet 200/201
     → 205 portal surfaces (dropdowns/popovers/tooltips — no dropdown nests inside a dialog, and
       an open kebab must close UNDER an awaited confirm: wave-9)
     → 220 dialog backdrop → 230 dialog panel
     → 240 toast viewport (a toast fired from a dialog handler must paint above the open dialog). */
.blazorblueprint-portal > * { z-index: 205; }
.blazorblueprint-portal > .fixed.inset-0 { z-index: 220; }
.blazorblueprint-portal [role="dialog"],
.blazorblueprint-portal [role="alertdialog"] { z-index: 230; }
body > .fixed.pointer-events-none { z-index: 240; }

/* 🔒 The icon picker is the one dropdown that opens INSIDE the phone editor's 202 layer, and the
   one place the ladder above cannot reach: BlazorBlueprint's positioning script writes
   `z-index: 50` INLINE onto the wrapper it creates, and an inline declaration beats every
   un-important stylesheet rule. At 50 the open menu paints UNDER the editor, so the form's own
   labels cover it and no icon can be picked at all — measured in a real browser at 390px, where
   the menu rendered at its full size and the hit test at its centre returned a form label.
   No test can see this: bUnit does not compute stacking. Hence the only !important in the
   ladder, and it is load-bearing. */
.blazorblueprint-portal > *:has(> .rp-icon-picker-menu) { z-index: 205 !important; }

/* Backdrop legibility. Blueprint's own bg-black/80 is opaque enough on a desktop monitor but the
   layer underneath still reads through on a phone screen, so add a blur: what is behind an open
   dialog must be unmistakably "not the thing you are looking at". */
.blazorblueprint-portal > .fixed.inset-0 {
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}

/* ── Nested dialogs (a dialog opened from a dialog) ──
   The ladder above is FLAT: every backdrop is 220 and every panel 230. With two dialogs open the
   first dialog's panel therefore paints ABOVE the second dialog's backdrop, so the second one gets
   no separation at all and the two panels read as one pile of controls. Portals are appended to
   <body> in open order, so "a portal that follows another dialog's portal" is exactly "a nested
   dialog" — raise each such level above the previous panel. Two extra levels cover every nesting
   the cabinet actually produces (dialog → confirm → prompt). */
.blazorblueprint-portal:has([role="dialog"], [role="alertdialog"])
  ~ .blazorblueprint-portal > .fixed.inset-0 { z-index: 231; }
.blazorblueprint-portal:has([role="dialog"], [role="alertdialog"])
  ~ .blazorblueprint-portal [role="dialog"],
.blazorblueprint-portal:has([role="dialog"], [role="alertdialog"])
  ~ .blazorblueprint-portal [role="alertdialog"] { z-index: 232; }

.blazorblueprint-portal:has([role="dialog"], [role="alertdialog"])
  ~ .blazorblueprint-portal:has([role="dialog"], [role="alertdialog"])
  ~ .blazorblueprint-portal > .fixed.inset-0 { z-index: 233; }
.blazorblueprint-portal:has([role="dialog"], [role="alertdialog"])
  ~ .blazorblueprint-portal:has([role="dialog"], [role="alertdialog"])
  ~ .blazorblueprint-portal [role="dialog"],
.blazorblueprint-portal:has([role="dialog"], [role="alertdialog"])
  ~ .blazorblueprint-portal:has([role="dialog"], [role="alertdialog"])
  ~ .blazorblueprint-portal [role="alertdialog"] { z-index: 234; }

/* ── A dropdown opened INSIDE an open dialog (the lesson-card «…» kebab) ──
   A dropdown's content is NOT rendered in place: BbDropdownMenuContent hands it to
   BbFloatingPortal, which registers it as an Overlay portal — and BbPortalHost renders Container
   portals (that is where dialogs live) BEFORE Overlay ones, so a menu opened from inside a dialog
   is always a LATER sibling of that dialog's portal. The ladder above still leaves it at 120
   (div:has(> .rp-menu), 01-core.css), i.e. UNDER the 220 backdrop: the menu paints behind the dim
   layer and the click aimed at «Отменить занятие» lands on the backdrop, which closes the whole
   dialog — the kebab reads as a dead button. Raise a menu portal that follows a dialog portal above
   the 230 panel, still under the 240 toast viewport. !important because both the inline
   `z-index: 50` BbFloatingPortal stamps on its wrapper and the 120 rule it overrides demand it. */
.blazorblueprint-portal:has([role="dialog"], [role="alertdialog"])
  ~ .blazorblueprint-portal:has([role="menu"]) > * { z-index: 235 !important; }

/* ── Dialogs taller than the viewport must scroll INSIDE the panel ──
   The Bb dialog panel is position:fixed + translate-centered with no height cap, and the open
   dialog locks body scroll (body overflow:hidden) — so on a phone a tall dialog (e.g. publish-slots
   with weekly repeat expanded: 943px vs 844px viewport) ran off BOTH edges with its submit button
   unreachable and nothing scrollable. Cap every dialog panel to the small-viewport height (dvh —
   mobile URL-bar-aware) and scroll overflow inside. This is the ONLY height/scroll rule for
   dialog panels — per-dialog rp-* classes must not duplicate it (rp-land-dlg's old 88vh copy
   was removed in its favour). */
.blazorblueprint-portal [role="dialog"],
.blazorblueprint-portal [role="alertdialog"] {
  max-height: calc(100dvh - 24px);
  overflow-y: auto;
}

/* ── The dialog panel's grid column must be allowed to be NARROWER than its content ──
   Bb's panel is `display: grid` with no explicit template, so its single implicit column is sized
   `auto` — i.e. by the max-content of its widest child, with `max-width` capping only the PANEL box,
   never the track. Any child holding a `white-space: nowrap` run (the ellipsis idiom: nowrap +
   overflow:hidden + text-overflow) therefore contributes that whole run to the track, and every row is
   laid out wider than the panel holding it (measured on a standalone repro at 390px: a 492.5px track
   against a 360px content box — 134px of overflow on each child). The panel is also translate-centered,
   and live the surplus landed on the LEFT: «Занятия в составе курса» read «ве курса» at 390px, with the
   modules' titles beheaded to match. `min-width: 0` on the child does
   NOT help; it removes the automatic minimum size, not the intrinsic contribution to an `auto` track.
   One explicit `minmax(0, 1fr)` column fixes it for every dialog at once — the layout is unchanged
   (one column is what they all already had), only its floor drops from min-content to 0, which is
   what makes the children's own ellipsis actually engage. */
.blazorblueprint-portal [role="dialog"],
.blazorblueprint-portal [role="alertdialog"] { grid-template-columns: minmax(0, 1fr); }

/* ── Cabinet welcome (anonymous Home.razor landing) — role-aware entry, replaces the old dead-end stub ── */
.rp-welcome { max-width: 960px; margin: 0 auto; padding: 8px 4px 24px; }
.rp-welcome-title { font-size: 1.6rem; font-weight: 700; margin: 0 0 8px; }
.rp-welcome-lead { color: var(--muted-foreground); max-width: 640px; margin: 0 0 24px; line-height: 1.5; }
.rp-welcome-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.rp-welcome-card { display: flex; flex-direction: column; gap: 8px; padding: 20px; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--card); }
.rp-welcome-card-h { font-weight: 600; font-size: 1.05rem; }
.rp-welcome-card-t { color: var(--muted-foreground); font-size: .92rem; line-height: 1.45; margin: 0; flex: 1; }
.rp-welcome-btn { display: inline-block; text-align: center; padding: 10px 16px; border-radius: var(--radius-md); background: var(--primary); color: var(--primary-foreground); font-weight: 600; text-decoration: none; margin-top: 4px; }
.rp-welcome-btn:hover { filter: brightness(.95); }
.rp-welcome-link { color: var(--primary); text-decoration: none; font-size: .9rem; }
.rp-welcome-link:hover { text-decoration: underline; }

/* ── rp-coh* (потоки-на-курсе spec §1): the standalone /cohorts list + /cohorts/{Id} detail pages
   are gone — cohort UI now lives entirely in CohortDialog on the course overview (the track wizard's
   «+ Новый поток» handles creation). CohortDialog reuses rp-covw-* (crumb/head/grid/card/row/
   students-roster) verbatim; only the capacity pill (rp-coh-cap) and the share-link row
   (rp-coh-share-row/-input/-copy/-hint) are new. ── */

/* CohortDialog (потоки-на-курсе): the cohorts detail ported into a dialog. */
.rp-coh-view-dlg { max-width: 560px; }

.rp-coh-cap {
  display: inline-flex; align-items: center; height: 22px; padding: 0 10px;
  border-radius: 999px; background: var(--secondary); color: var(--secondary-foreground);
  font-size: 12px; font-weight: 600; flex-shrink: 0;
}

.rp-coh-share-row { display: flex; align-items: center; gap: 8px; padding: 4px 16px 10px; }
.rp-coh-share-input {
  flex: 1; min-width: 0; height: 34px; box-sizing: border-box;
  border: 1px solid var(--input); border-radius: var(--radius-md); background: var(--muted);
  padding: 0 10px; font: inherit; font-size: 12.5px; color: var(--foreground);
}
.rp-coh-share-copy {
  height: 34px; padding: 0 14px; flex-shrink: 0;
  border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--card);
  color: var(--foreground); font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.rp-coh-share-copy:hover { background: var(--secondary); }
.rp-coh-share-hint { padding: 0 16px 13px; font-size: 12px; color: var(--muted-foreground); }
@media (max-width: 720px) { .rp-welcome-cards { grid-template-columns: 1fr; } }

/* ── RpSelect v2: styled dropdown list on desktop, native <select> on mobile ─────────────────────
   The component renders BOTH controls (see RpSelect.razor); these rules pick one per device.
   Desktop: the native select is hidden and a trigger button (carrying the SAME bespoke field
   class via InputClass, so the closed look is unchanged) opens a custom rp-sel-pop option list —
   the one part of a native select CSS can never style. Mobile/touch: the native select shows and
   the OS picker does its job. This block sits at the END of the file on purpose: the trigger's
   chevron/padding must out-cascade the field classes' background/padding shorthands at equal
   specificity (same trick as the global `select` chevron rule, without needing !important). */
.rp-sel { display: flex; flex-direction: column; gap: 6px; }
.rp-sel-host { position: relative; }
.rp-sel-native { display: none; }
.rp-sel-btn {
  display: flex;
  align-items: center;
  width: 100%;
  text-align: left;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='16'%20height='16'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%238b95a5'%20stroke-width='2.25'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='m6%209%206%206%206-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
  background-size: 15px;
  padding-right: 34px;
}
.rp-sel-btn:disabled { opacity: 0.6; cursor: default; }
.rp-sel-btn-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rp-sel-overlay { position: fixed; inset: 0; z-index: 25; background: transparent; }
.rp-sel-pop {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  min-width: 100%;
  max-width: min(360px, calc(100vw - 48px));
  z-index: 30;
  max-height: 300px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--card);
  box-shadow: var(--shadow-lg);
  padding: 4px;
}
.rp-sel-opt {
  display: block;
  width: 100%;
  padding: 8px 10px;
  border: 0;
  background: transparent;
  border-radius: var(--radius-sm);
  color: var(--foreground);
  font: inherit;
  font-size: 13.5px;
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rp-sel-opt:hover { background: var(--muted); }
.rp-sel-opt--sel { background: var(--primary); color: var(--primary-foreground); }
.rp-sel-opt--sel:hover { background: var(--primary); }
@media (max-width: 640px), (hover: none) and (pointer: coarse) {
  .rp-sel-native { display: block; }
  .rp-sel-btn, .rp-sel-overlay, .rp-sel-pop { display: none; }
}
/* Upward variant for triggers pinned to the bottom of the viewport (the reader's rate pill) —
   the default downward pop would clip below the screen edge. */
.rp-sel--up .rp-sel-pop { top: auto; bottom: calc(100% + 4px); }

/* ---- rp-helplink: contextual «Как это работает» link in page headers ---- */
.rp-helplink {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    margin-left: .6rem;
    font-size: .84rem;
    font-weight: 500;
    color: var(--muted-foreground);
    text-decoration: none;
    vertical-align: middle;
    white-space: nowrap;
}
.rp-helplink:hover { color: var(--primary); }
.rp-helplink:hover .rp-helplink-label { text-decoration: underline; }
@media (max-width: 640px) {
    .rp-helplink-label { display: none; } /* icon-only on phones; the icon stays a tap target */
}

/* In the chat inbox header the 320px column can't fit the label next to the
   announcement button - icon-only there at every width (matches the <=640px treatment),
   and the default .6rem lead-in margin clips the announcement button by ~5px. */
.rp-chat-head-row .rp-helplink-label { display: none; }
.rp-chat-head-row .rp-helplink { margin-left: .25rem; }

/* ── Router-level error screen (Routes.razor ErrorBoundary/ErrorContent) ──
   Replaces the old inline style="padding:2rem". Deliberately plain: this renders when the app is
   already broken, so it must not depend on any component that could itself be failing. */
.rp-approuterr { max-width: 560px; margin: 0 auto; padding: 40px 20px; text-align: center; }
.rp-approuterr h1 { font-size: 1.35rem; font-weight: 700; margin: 0 0 8px; }
.rp-approuterr p { color: var(--muted-foreground); margin: 0 0 20px; line-height: 1.5; }
.rp-approuterr-actions { display: flex; gap: 12px; justify-content: center; align-items: center; flex-wrap: wrap; }
.rp-approuterr-details { margin-top: 28px; text-align: left; font-size: 12.5px; }
.rp-approuterr-details summary { cursor: pointer; color: var(--muted-foreground); }
.rp-approuterr-details code {
  display: block; margin-top: 8px; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--muted);
  color: var(--muted-foreground); word-break: break-word; white-space: pre-wrap;
}

/* ── SP4 «Адрес вашего сайта» (rp-siteaddr-*) ───────────────────────────────
   Окно 660px по макету хендоффа: цветная шапка 60px, тело 22px, карточки на
   границе. Заменяет прежнюю панель настройки домена, разворачивавшуюся в потоке страницы.
   На ≤640px окно занимает почти весь экран (ширина 100%, без скруглений) —
   реальная высота ограничена общим инвариантом ниже по файлу
   (.blazorblueprint-portal [role="dialog"] { max-height: calc(100dvh - 24px) }),
   который этот блок сознательно НЕ дублирует. SP3 заменит окно отдельной
   страницей; до тех пор 660px на телефоне были бы обрезаны по краям.

   Класс приезжает на элемент [role="dialog"] внутри .blazorblueprint-portal.
   Если простое правило проигрывает утилитам Blueprint (у него своя ширина и
   свой padding), поднять специфичность так же, как это уже сделано для
   .rp-sched-dlg--pop: .blazorblueprint-portal [role="dialog"].rp-siteaddr-dlg */
.rp-siteaddr-dlg { width: 660px; max-width: 100%; padding: 0; overflow: hidden; }

/* Overlay tint (handoff mDom: rgba(18,56,79,.5)). Confirmed by rendering the dialog in bUnit and
   printing its markup: BlazorBlueprint always renders the backdrop as a PLAIN SIBLING div
   (class="fixed inset-0 z-50 bg-black/80 …", no bespoke class of its own) directly inside the same
   .blazorblueprint-portal wrapper as the [role="dialog"] content — so it's addressable, but only by
   scoping through the portal wrapper that holds THIS dialog's class (:has(), same trick the
   nested-dialog z-index ladder above already uses) rather than repainting every dialog's backdrop. */
.blazorblueprint-portal:has([role="dialog"].rp-siteaddr-dlg) > .fixed.inset-0 {
  background: rgba(18, 56, 79, 0.5);
}

.rp-siteaddr-head {
  display: flex; align-items: center; gap: 10px; height: 60px; padding: 0 12px 0 22px;
  background: var(--primary); color: var(--primary-foreground);
}
.rp-siteaddr-title { font-size: 17px; font-weight: 700; flex: 1; }
/* Same treatment as SiteBlockFrame's rp-site-frame-help: light text on the var(--primary) fill, via
   --primary-foreground rather than a literal white (a light-fill dark-mode template can carry a dark
   --primary-foreground, see that rule's own comment). */
.rp-siteaddr-help {
  color: var(--primary-foreground); opacity: 0.85; font-size: 13px; font-weight: 600;
  text-decoration: underline; white-space: nowrap;
}
.rp-siteaddr-help:hover { opacity: 1; }
.rp-siteaddr-close {
  /* gap only matters on a phone, where an arrow-left icon precedes the «Назад» label — a single
     desktop text node ignores it. */
  display: inline-flex; align-items: center; gap: 6px; height: 44px; padding: 0 16px;
  border: 1px solid rgba(255, 255, 255, 0.45); border-radius: var(--radius-md);
  background: transparent; color: var(--primary-foreground);
  font: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
}
.rp-siteaddr-body { padding: 22px 22px 26px; }
.rp-siteaddr-body--loading { display: flex; justify-content: center; padding: 40px 22px; }
.rp-siteaddr-card {
  border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 18px 20px;
}
.rp-siteaddr-cardhead { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.rp-siteaddr-cardtitle { font-size: 14px; font-weight: 700; }
.rp-siteaddr-badge {
  display: inline-flex; align-items: center; gap: 5px; height: 26px; padding: 0 10px;
  border-radius: 999px; font-size: 12px; font-weight: 700;
}
/* Same success token pair the domain panel this dialog replaces used for its «Подтверждён» badge —
   not a literal: color-mix(...)/#1d7a54 read fine in light but gave ~2.7:1 contrast on dark --card. */
.rp-siteaddr-badge--ok { background: var(--success-muted); color: var(--success-muted-foreground); }
.rp-siteaddr-hostrow { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.rp-siteaddr-host { font-size: 19px; font-weight: 800; letter-spacing: -0.01em; overflow-wrap: anywhere; }
.rp-siteaddr-copy {
  display: inline-flex; align-items: center; gap: 7px; height: 40px; padding: 0 14px;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--card); color: var(--foreground);
  font: inherit; font-size: 13.5px; font-weight: 600; cursor: pointer;
}
.rp-siteaddr-hint { font-size: 13px; color: var(--muted-foreground); line-height: 1.55; margin-top: 8px; }
.rp-siteaddr-sechead { font-size: 16px; font-weight: 700; margin: 22px 0 4px; }
.rp-siteaddr-secopt { font-weight: 500; color: var(--muted-foreground); }
.rp-siteaddr-secintro {
  font-size: 13.5px; color: var(--muted-foreground); line-height: 1.6; margin: 0 0 16px;
}
.rp-siteaddr-secintro strong { color: var(--foreground); }
.rp-siteaddr-upsell { display: flex; flex-direction: column; gap: 12px; align-items: flex-start; }
.rp-siteaddr-upsell-text { margin: 0; font-size: 14px; }
.rp-siteaddr-upsell-btn {
  display: inline-flex; align-items: center; height: 44px; padding: 0 18px;
  border-radius: var(--radius-md); background: var(--primary); color: var(--primary-foreground);
  font-size: 14.5px; font-weight: 600; text-decoration: none;
}

/* Свой домен — форма подключения, шаг 1/2 (pending), шаг 2/2 (verified). */
.rp-siteaddr-addrow { display: flex; gap: 10px; }
.rp-siteaddr-input {
  flex: 1; height: 48px; padding: 0 16px; border: 1px solid var(--input);
  border-radius: var(--radius-md); background: var(--card); color: var(--foreground);
  font: inherit; font-size: 15px;
}
.rp-siteaddr-add {
  height: 48px; padding: 0 20px; border: 0; border-radius: var(--radius-md);
  background: var(--primary); color: var(--primary-foreground);
  font: inherit; font-size: 14.5px; font-weight: 600; cursor: pointer; white-space: nowrap;
}
.rp-siteaddr-add:disabled { opacity: .55; cursor: default; }
/* margin-bottom (not the sibling-combinator +margin-top the file used before) — a domain card is
   NEVER the last child of .rp-siteaddr-body (the add form or the upsell card always follows it, see
   the markup), so the gap is never dangling; it also covers dom+dom (via normal-flow margin
   collapsing with the next card's implicit zero margin-top) without a second rule. */
.rp-siteaddr-dom {
  border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 18px 20px;
  margin-bottom: 16px;
}
.rp-siteaddr-domhead { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }
.rp-siteaddr-domhost { font-size: 16px; font-weight: 800; overflow-wrap: anywhere; }
/* Warning token pair from the rest of the cabinet (light+dark) — not the literal color-mix/hex the
   handoff mock used, which has no dark-mode value. The confirmed-domain badge below reuses
   rp-siteaddr-badge--ok (same success token pair, same text «Домен подтверждён» vs the platform
   card's «Работает всегда») rather than a byte-identical --ok2 twin. */
.rp-siteaddr-badge--wait { background: var(--warning-muted); color: var(--warning-muted-foreground); }
.rp-siteaddr-step { font-size: 14px; line-height: 1.55; margin-bottom: 10px; }
.rp-siteaddr-codebox {
  display: flex; align-items: flex-start; gap: 10px; background: var(--muted);
  border-radius: var(--radius-md); padding: 12px 14px; margin-bottom: 10px;
}
.rp-siteaddr-code {
  flex: 1; font-family: var(--font-mono); font-size: 13px; line-height: 1.7; overflow-wrap: anywhere;
}
.rp-siteaddr-codekey { color: var(--muted-foreground); }
.rp-siteaddr-codecopy {
  display: inline-flex; align-items: center; gap: 7px; height: 38px; padding: 0 12px;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--card); color: var(--foreground);
  font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; white-space: nowrap;
}
.rp-siteaddr-note {
  font-size: 13px; color: var(--muted-foreground); line-height: 1.55; margin-bottom: 14px;
}
.rp-siteaddr-note strong { color: var(--foreground); }
.rp-siteaddr-domactions { display: flex; gap: 10px; flex-wrap: wrap; }
.rp-siteaddr-verify {
  height: 46px; padding: 0 22px; border: 0; border-radius: var(--radius-md);
  background: var(--primary); color: var(--primary-foreground);
  font: inherit; font-size: 14.5px; font-weight: 600; cursor: pointer;
}
.rp-siteaddr-remove {
  height: 46px; padding: 0 16px; border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--card); color: var(--destructive);
  font: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
}
/* Handoff (:813 vs :792): the verified card's lone «Удалить домен» is 44px — 2px shorter than the
   pending card's paired «Проверить»/«Удалить» — matching the mock exactly, no visual parity implied. */
.rp-siteaddr-remove--solo { height: 44px; }
.rp-siteaddr-error {
  font-size: 13.5px; color: var(--destructive); line-height: 1.5; margin-bottom: 10px;
}

@media (max-width: 640px) {
  /* No max-height here on purpose — the shared .blazorblueprint-portal [role="dialog"] rule
     (max-height: calc(100dvh - 24px)) is more specific (0,2,0 vs 0,1,0) and always wins, and its own
     comment asks per-dialog classes not to duplicate it. */
  .rp-siteaddr-dlg { width: 100%; height: 100%; border-radius: 0; }
  .rp-siteaddr-body { padding: 18px 16px 24px; }
  .rp-siteaddr-host { font-size: 17px; }
  .rp-siteaddr-hostrow { align-items: stretch; flex-direction: column; }
  .rp-siteaddr-copy { justify-content: center; height: 44px; }
  /* flex:1 у поля рассчитан на СТРОКУ. Как только строка становится колонкой, flex-basis:0%
     начинает управлять высотой, и поле схлопывается с 48px до высоты строки текста (~21px).
     Замерено в браузере на 390px; bUnit этого не видит — он не считает CSS. */
  .rp-siteaddr-addrow { flex-direction: column; }
  .rp-siteaddr-input { flex: none; }
  .rp-siteaddr-add { height: 48px; }
  .rp-siteaddr-codebox { flex-direction: column; }
  .rp-siteaddr-codecopy { justify-content: center; width: 100%; height: 44px; }
}

/* ── rp-sitemob* (SP3 — MobileEditorShell, «Мой сайт - редизайн.dc.html» §3a) ───────────────── */
.rp-sitemob {
  position: fixed;
  inset: 0;
  z-index: 202;
  display: flex;
  flex-direction: column;
  background: var(--background);
}
.rp-sitemob-head {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 56px;
  padding: 0 10px;
  background: var(--primary);
  color: var(--primary-foreground);
  flex-shrink: 0;
}
.rp-sitemob-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 44px;
  padding: 0 12px;
  border: 0;
  background: transparent;
  color: var(--primary-foreground);
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.rp-sitemob-title {
  flex: 1;
  text-align: center;
  font-size: 15.5px;
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rp-sitemob-headpad { width: 80px; flex-shrink: 0; }
.rp-sitemob-body {
  flex: 1;
  overflow-y: auto;
  /* This layer is a hand-rolled position:fixed sheet, NOT a BbDialog — nothing above it locks the
     page's own scroll. Without containment, a scroll that reaches the end of the form keeps going in
     the block feed underneath, and «Назад» drops the tutor somewhere else in the list than where
     they opened the block. */
  overscroll-behavior: contain;
  padding: 18px 18px 24px;
}
.rp-sitemob-foot {
  padding: 12px 16px 18px;
  background: var(--card);
  border-top: 1.5px solid var(--primary);
  flex-shrink: 0;
}
.rp-sitemob-primary {
  width: 100%;
  height: 52px;
  border: 0;
  border-radius: var(--radius-md);
  background: var(--primary);
  color: var(--primary-foreground);
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  cursor: pointer;
}
/* 🔴 Final review, Important: «Отменить и вернуться» sat at 40px DIRECTLY under the 52px
   «Сохранить», with zero space between them (measured gap 0px at every width). A finger that misses
   the discard lands on the publish button — and here «Сохранить» IS publishing to the live site. The
   44px floor plus a real gap makes the two separable by touch. Unconditional like every sibling in
   this block: the shell mounts on MobileEditorShell's own JS `_mobile` gate (coarse pointer OR
   ≤640px), so a second, narrower CSS breakpoint on top of it would only reopen the tablet hole the
   .rp-sitemob-switch comment below describes. */
.rp-sitemob-secondary {
  width: 100%;
  height: 44px;
  margin-top: 6px;
  border: 0;
  background: transparent;
  color: var(--muted-foreground);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  text-decoration: underline;
}
/* Task 3 — LandingFieldForm: the one mobile form built from a block's ILandingFieldSet, living inside
   MobileEditorShell's rp-sitemob-body. */
.rp-sitemob-field { display: block; margin-bottom: 16px; }
.rp-sitemob-label {
  display: block;
  font-size: 15px;
  font-weight: 700;
  margin-bottom: 6px;
}
.rp-sitemob-input {
  width: 100%;
  height: 48px;
  padding: 0 14px;
  border: 1px solid var(--input);
  border-radius: var(--radius-md);
  background: var(--card);
  color: var(--foreground);
  font: inherit;
  /* 16px is load-bearing on iOS: a smaller font makes Safari zoom the page on focus. */
  font-size: 16px;
}
.rp-sitemob-area {
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--input);
  border-radius: var(--radius-md);
  background: var(--card);
  color: var(--foreground);
  font: inherit;
  font-size: 16px;
  line-height: 1.5;
  resize: vertical;
}
.rp-sitemob-hint {
  display: block;
  font-size: 12.5px;
  color: var(--muted-foreground);
  margin-top: 5px;
}
.rp-sitemob-list { margin-bottom: 18px; }
.rp-sitemob-listlabel { font-size: 15px; font-weight: 700; margin-bottom: 10px; }
.rp-sitemob-item {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 14px 16px;
  background: var(--card);
  margin-bottom: 12px;
}
.rp-sitemob-item .rp-sitemob-input { height: 44px; }
.rp-sitemob-itemdel {
  height: 44px;
  padding: 0 14px;
  border: 0;
  background: transparent;
  color: var(--destructive);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.rp-sitemob-add {
  width: 100%;
  height: 48px;
  border: 1px dashed var(--muted-foreground);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--muted-foreground);
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
}
/* Task 4 — the shell's own body rows: the block's guidance line above the form, and the switch rows
   below it (visibility for every block, plus the feedback-form flag on «Контакты»). */
.rp-sitemob-lead {
  font-size: 13.5px;
  color: var(--muted-foreground);
  line-height: 1.55;
  margin: 0 0 18px;
}
.rp-sitemob-vis {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 0;
  border-top: 1px solid var(--border);
}
.rp-sitemob-vislabel { flex: 1; font-size: 15px; font-weight: 600; }
/* The phone switch of «Мой сайт - редизайн.dc.html» §3a: track 52×44 (width unchanged from the
   original 52x30; height raised for the Task 6 44px hit-target audit — see the rp-site-auto-plaque
   comment above for the measurement method), thumb 24 centred at a 10px inset ((44-24)/2), and
   left 25 when on. The THUMB has to be restated alongside the track: the canonical rule further up
   pins it at 16px / top 2.5px / left 2.5px with an on-state of left 17.5px, so raising only the
   track leaves a small circle riding off-centre and stopping short of the right edge.
   🔴 Review finding: an earlier version of this raise lived inside `@media (max-width: 640px)`,
   copying the pattern used for `.rp-site-hidden-btn`/`.rp-site-auto-plaque` below. That pattern is
   wrong HERE specifically: `.rp-sitemob-switch` renders ONLY inside `MobileEditorShell`, which
   mounts on the JS-computed `_mobile` flag (coarse pointer OR ≤640px) — not on the CSS breakpoint.
   A coarse-pointer tablet in landscape (e.g. 1024px) opens the shell with `_mobile = true` but never
   crosses `max-width:640px`, so the media-query version left the switch at its old 30px on exactly
   the device the whole `_mobile` threshold exists to catch. Every sibling rule in this rp-sitemob-*
   block (`.rp-sitemob-back`, `-itemdel`, `-add`, the list-item `.rp-sitemob-input`) is unconditional
   for the same reason: the component's own JS gate already IS the "is this mobile" check, so a
   second, narrower CSS gate on top of it only reintroduces the gap the JS gate was built to close.
   `.rp-site-hidden-btn`/`.rp-site-auto-plaque` below stay media-query-gated on purpose — they render
   in the block FEED, which mounts unconditionally (a genuine desktop mouse user sees it too), so an
   unconditional height raise there would violate "desktop sizes stay untouched" instead of fixing it. */
.rp-sitemob-switch { width: 52px; height: 44px; }
.rp-sitemob-switch .rp-set-switch-thumb {
  top: 10px;
  left: 3px;
  width: 24px;
  height: 24px;
}
.rp-sitemob-switch[aria-checked="true"] .rp-set-switch-thumb { left: 25px; }
/* Commit hint under the bottom bar's buttons — the shell covers the whole page, so this is the only
   place left to say «this is not on the site yet». Mirrors rp-site-frame-hint on the desktop frame. */
.rp-sitemob-foothint {
  margin: 10px 0 0;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--muted-foreground);
  text-align: center;
}

/* 🔴 Final review, Important — the 44px hit-target floor for the SHARED controls the phone editor
   hosts. RpImageUpload (its «Загрузить» label and its «Удалить» text link), LandingIconPicker's
   trigger and SiteThemeControls' «Свой цвет» disclosure are all desktop-era components reused
   verbatim inside the shell, so the earlier sweep — which only walked the block feed and the page
   header — never reached them. Measured with a standalone repro (real tokens.css + the
   blazorblueprint.css preflight, whose box-sizing reset the numbers depend on, + real bp.css,
   Playwright getBoundingClientRect at 375/1024/1280): 19.5px for the «Удалить» link, 27.5px for
   «Свой цвет», 36px for the icon trigger, and 38px for the upload label on anything wider than the
   ≤600px query at rp-prof-btn's own call site.
   Scoped by CONTEXT (.rp-sitemob-body), not by breakpoint and not on the components themselves:
   the shell renders only under the JS `_mobile` gate, so this raise reaches exactly the phone
   editor and cannot leak into the profile page, the desktop popover or the inline landing editor
   that mount the very same classes. */
.rp-sitemob-body .rp-prof-btn { height: 44px; }
.rp-sitemob-body .rp-btn-textlink {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.rp-sitemob-body .rp-icon-picker-trigger { width: 44px; height: 44px; }
.rp-sitemob-body .rp-site-hexmore { min-height: 44px; }

/* SP5 on the phone: the AI offer rides in the shell's BODY, above the field form — its footer is
   already carrying Сохранить/Отменить plus the commit hint, and a third button there does not fit
   375px. Same 44px floor SP3 set for everything it first exposed on a phone. */
.rp-sitemob-airow { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 12px; }
.rp-sitemob-aibtn, .rp-sitemob-undobtn {
    display: inline-flex; align-items: center; gap: 6px;
    min-height: 44px; padding: 0 14px; border-radius: 8px;
    background: transparent; border: 1px solid var(--border); color: inherit;
    font: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
}
.rp-sitemob-aibtn:disabled { opacity: .55; cursor: default; }
.rp-sitemob-undobtn { border-style: dashed; }
/* Final review, Minor 6: the disabled button's own `title` is a hover-only affordance — useless on a
   touch screen, where SP5 is the first `disabled` control of this kind to reach a phone at all. Same
   wording as the desktop tooltip, printed as a line instead. */
.rp-sitemob-airow-reason { flex-basis: 100%; margin: -4px 0 12px; font-size: 12.5px; color: var(--muted-foreground); }

