/* =============================================================================
   Source Sans 3 — the brand typeface

   Three faces, not one: declaring all three files under a single @font-face makes the browser pick
   whichever it downloads first for every weight, so semibold headings and light labels all came out
   the same. `font-display: swap` keeps the page readable in the fallback while the files arrive,
   which matters more here than avoiding the reflow — this is a tool, not a landing page.
   ============================================================================= */

@font-face {
  font-family: 'Source Sans 3';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('../fonts/SourceSans3-Light.ttf') format('truetype');
}

@font-face {
  font-family: 'Source Sans 3';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/SourceSans3-Regular.ttf') format('truetype');
}

@font-face {
  font-family: 'Source Sans 3';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../fonts/SourceSans3-SemiBold.ttf') format('truetype');
}

/* Loading screen */
.loading-screen {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100vh;
  background-color: var(--rz-base-100, #f5f5f5);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.loading-screen .logo {
  width: 130px;
  object-fit: fill;
}

.loading-screen .loading-bar {
  width: 130px;
  height: 2px;
  background: var(--rz-base-300, #cfcfcf);
  margin-top: 22px;
  position: relative;
  overflow: hidden;
}

.loading-screen .loading-bar::before {
  content: '';
  width: 68px;
  height: 2px;
  background: var(--rz-primary, #39B388);
  position: absolute;
  left: -34px;
  animation: bar 1.5s infinite ease;
}

@keyframes bar {
  50% {
    left: 96px;
  }
}

/* Blazor reveals this by setting an inline `display: block`, so the layout inside it must not depend
   on the root being a flex container — the inline style would win. Hence the absolute positioning.

   The colours below are hex on purpose, and so is the brand green on .loading-bar above. This
   stylesheet is linked from index.html, but tokens.css is linked from App.razor's <HeadContent> —
   which only reaches the DOM once the WebAssembly runtime has started and rendered the router. Both
   the loading screen and this error bar are on screen before that, and this one appears precisely
   when the runtime has failed, so a token here would resolve to nothing exactly when it is needed. */
#blazor-error-ui {
  background: #1f2937;
  bottom: 0;
  box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.25);
  color: #f9fafb;
  display: none;
  font-size: 0.875rem;
  left: 0;
  padding: 0.85rem 8.5rem 0.85rem 1.25rem;
  position: fixed;
  width: 100%;
  z-index: 1000;
}

#blazor-error-ui .reload {
  border: 1px solid rgba(249, 250, 251, 0.4);
  border-radius: 4px;
  color: #f9fafb;
  padding: 0.25rem 0.75rem;
  position: absolute;
  right: 3rem;
  text-decoration: none;
  top: 50%;
  transform: translateY(-50%);
}

#blazor-error-ui .reload:hover,
#blazor-error-ui .reload:focus-visible {
  background: rgba(249, 250, 251, 0.12);
}

#blazor-error-ui .dismiss {
  background: none;
  border: none;
  color: #f9fafb;
  cursor: pointer;
  font-family: inherit;
  font-size: 1.25rem;
  line-height: 1;
  padding: 0;
  position: absolute;
  right: 1rem;
  top: 50%;
  transform: translateY(-50%);
}

/* What an ErrorBoundary falls back to. A panel rather than the template's red banner: this replaces
   the page's whole content, so it has to read as a state the page can be in and offer a way out of
   it, not as a strip of alarm across the top. */
.page-error {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 1.5rem;
  border: 1px solid var(--rz-base-300);
  border-left: 3px solid var(--rz-danger);
  border-radius: 8px;
  background: var(--rz-base-background);
  box-shadow: var(--rz-shadow-1);
}

.page-error__icon {
  font-size: 1.75rem;
  color: var(--rz-danger);
  flex: none;
}

.page-error__body {
  flex: 1 1 20rem;
  min-width: 0;
}

.page-error__title {
  margin: 0 0 0.25rem;
  font-size: 1rem;
  font-weight: 600;
  color: var(--rz-text-color);
}

.page-error__message {
  margin: 0;
  font-size: 0.875rem;
  color: var(--rz-text-secondary-color);
}

.page-error__actions {
  display: flex;
  gap: 0.5rem;
  flex: none;
}

/* Header logo container - aligns with sidebar */
.header-logo-container {
  display: flex;
  align-items: center;
  transition: width 0.25s var(--ease-out-quart);
  overflow: hidden;
}

.header-logo-container.expanded {
  width: calc(var(--rz-sidebar-width) - 84px);
}

.header-logo-container.collapsed {
  width: auto;
}

.rz-header .logo {
  height: 20px;
}

/* =============================================================================
   Application chrome — the header bar and the sidebar that frame every page.
   ============================================================================= */

/* The one border in the Portal drawn between two different surfaces rather than within one, so it is
   the darker of the two that has to carry it. A light hairline down the side of the dark sidebar
   reads as a seam of daylight; on the sidebar's own colour it reads as an edge. */
.rz-sidebar {
  border-right: none;
  box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25);
}

/* Radzen's default is a grey the width of a rule. The header is a persistent surface, not a section
   divider: it wants the same hairline as everything else, and a shadow only once the page beneath it
   has scrolled — which it cannot know, so it gets none. */
.rz-header {
  border-bottom: 1px solid var(--looc-border-color);
}

/* Muted text: a caption, a unit suffix, a field label, a sentence explaining the section above it.

   This exists because `rz-color-secondary` does not mean what it reads like. It is Radzen's utility
   for the secondary *palette* colour, and the Portal deliberately sets that to the brand green — so
   thirty-seven captions, timestamps and labels across the Portal were rendering in #39B388, which is
   about 2.5:1 on white and the least readable text on any page it appeared on.

   `!important` because the rule it replaces has it, and Radzen's text styles would otherwise win. */
.looc-text-muted {
  color: var(--rz-text-secondary-color) !important;
}

/* =============================================================================
   Links

   Only anchors that carry an href, and only where nothing else has claimed them: `tel:` and
   `mailto:` values in a data grid had no rule at all, so a telephone number was rendered in the
   browser's default blue and, once pressed, its default purple — the only two colours in the Portal
   that belong to no palette.
   ============================================================================= */
a[href]:not(.rz-button):not(.rz-navigation-item-link):not(.page-tab):not(.skip-link) {
  color: var(--rz-primary-dark);
  text-decoration: none;
}

a[href]:not(.rz-button):not(.rz-navigation-item-link):not(.page-tab):not(.skip-link):hover {
  text-decoration: underline;
}

.validation-message {
  color: var(--rz-danger);
}

/* --rz-warning on white is about 4.2:1, which is enough for a border and not for the word inside it.
   --looc-orange-text is the darkened brand orange that exists for exactly this. */
.incident-badge.rz-variant-outlined {
  color: var(--looc-orange-text);
}

.incident-menu {
  position: fixed;
  top: 50px;
  left: calc(100% - 400px);
  background-color: white;
}

.incident-menu [style*="cursor: pointer"]:hover {
  background-color: var(--rz-base-100);
  transition: background-color 0.2s ease;
}

.incident-row {
  border: none;
  box-shadow: none;
}

.incident-row:hover {
  animation-duration: 0.5s;
  background-color: var(--rz-secondary-lighter);
  border-radius: var(--rz-datalist-border-radius);
  cursor: pointer;
}

.title-section {
  margin-left: 1rem;
  border-left: var(--rz-sidebar-toggle-border);
  padding: var(--rz-sidebar-toggle-padding);
}

/* Everything between the breadcrumbs and the search sits in this, so the two ends of the header bar
   are the two ends of the header bar. It replaces a pair of `margin-left: auto` declarations that
   split the free space between them and left a page's buttons floating mid-bar. */
.header-spacer {
  flex: 1 1 auto;
  min-width: var(--looc-space-4);
}

/* =============================================================================
   Grid panel — the toolbar, the grid and its pager as one surface.

   Every list in the Portal is a RadzenDataGrid, so this is most of what the Portal looks like. The
   grid draws its own hairline; the panel takes that over so the toolbar above it and the pager below
   it sit inside the same rounded box rather than stacking three separate rectangles.
   ============================================================================= */
.grid-panel {
  background: var(--rz-base-background-color);
  border: 1px solid var(--looc-border-color);
  border-radius: var(--looc-radius-md);
  /* Clips the grid's square top corners to the panel's rounded ones. */
  overflow: hidden;
}

.grid-panel .rz-data-grid {
  border: none;
  border-radius: 0;
}

/* Right-aligned, so the primary action is in the same corner whether or not the list has a title or
   controls of its own. */
.grid-toolbar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--looc-space-2);
  flex-wrap: wrap;
  padding: var(--looc-space-2) var(--looc-space-3);
  border-bottom: 1px solid var(--looc-border-color);
}

/* `:not(:has(> *))` rather than `:empty`, which counts the whitespace text nodes Razor leaves
   between elements and so would never match. A list whose only control is conditional — the
   incident list's "Acknowledge selected" — otherwise carries an empty bar whenever it is not shown. */
.grid-toolbar:not(:has(> *)) {
  display: none;
}

.grid-toolbar__title {
  margin: 0 auto 0 0;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--rz-text-color);
}

/* A card's title with the buttons that act on that card, on one line. The alternative the Portal had
   been using — a title, the content, then a lone button under the card — leaves the button with
   nothing to say which card it belongs to, and on a page with two cards that is a real question. */
.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--looc-space-3);
  flex-wrap: wrap;
  margin-bottom: var(--looc-space-4);
  padding-bottom: var(--looc-space-3);
  border-bottom: 1px solid var(--looc-border-color);
}

.card-header__title {
  margin: 0;
}

/* =============================================================================
   Filter bar — the controls that decide what a page is showing.

   A row of date pickers and labels laid straight onto the page background has no edge, so it reads
   as content that has come loose rather than as the page's controls. Given the same surface, border
   and radius as the grid panel below it, the page becomes two objects instead of a scattering.
   ============================================================================= */
.filter-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--looc-space-2) var(--looc-space-4);
  padding: var(--looc-space-3) var(--looc-space-4);
  margin-bottom: var(--looc-space-4);
  background: var(--rz-base-background-color);
  border: 1px solid var(--looc-border-color);
  border-radius: var(--looc-radius-md);
}

/* One labelled control. The label stays with its input when the bar wraps, which a flat row of
   alternating labels and pickers does not. */
.filter-bar__field {
  display: flex;
  align-items: center;
  gap: var(--looc-space-2);
}

/* A bar of more than one row of controls: the rows stack, the fields inside each still wrap. */
.filter-bar--stacked {
  display: block;
}

.filter-bar__row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--looc-space-2) var(--looc-space-4);
}

/* Separates rows that mean different things — one refetches, the next only redraws. */
.filter-bar__row--divided {
  margin-top: var(--looc-space-3);
  padding-top: var(--looc-space-3);
  border-top: 1px solid var(--looc-border-color);
}

.filter-bar__row-label {
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--rz-text-secondary-color);
}

/* =============================================================================
   Forms
   ============================================================================= */

/* Columns, for a form long enough that one column of full-width fields becomes a scroll. The floor
   is generous — a label, a value and a hint need the room — so this is two columns on a dialog and
   one on a phone without a media query. */
.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: 0 var(--looc-space-6);
}

/* The buttons that finish a form. Right-aligned, above a rule, with anything destructive pushed to
   the opposite end — the same arrangement as a dialog's footer, so a form on a page and a form in a
   dialog do not put Save in two different places. */
.form-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--looc-space-2);
  flex-wrap: wrap;
  margin-top: var(--looc-space-6);
  padding-top: var(--looc-space-4);
  border-top: 1px solid var(--looc-border-color);
}

.form-footer__destructive {
  margin-right: auto;
}

/* A heading inside a card, above a group of fields. Smaller than the card's own title and larger
   than a field label, which is the gap a bare <h3> at the browser's default size does not fit. */
.section-heading {
  margin: var(--looc-space-6) 0 var(--looc-space-3);
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--rz-text-secondary-color);
}

.section-heading:first-child {
  margin-top: 0;
}

/* Edit and Delete above the record they act on — see CustomUiComponents/Inline/DisplayForm.razor. */
.detail-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--looc-space-2);
  margin-bottom: var(--looc-space-4);
}

/* This is needed to make the dialog content fill the available space */
.rz-dialog .rz-dialog-content {
  flex-basis: 100%;
}

/* Page content padding for top-level pages */
.page-content {
  padding: var(--looc-page-padding);
}

/* The variant used by pages that own a scroll region of their own — the reseller layout, whose page
   header stays put while the tab's content scrolls under it. Previously each of those redeclared
   `.page-content` globally with a different body, and load order decided which one a page got. */
.page-content--scroll {
  flex: 1;
  overflow-y: auto;
  min-width: 0;
}

/* Authorizing state container - centered loading */
.authorizing-container {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 200px;
}

/* The shared loading indicator — see Shared/LoadingWrapper.razor */
.loading-indicator {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 1.5rem 0;
  color: var(--rz-text-secondary-color);
  font-size: 0.875rem;
}

.authorizing-container .loading-indicator {
  flex-direction: column;
}

/* Clickable data grid rows - only when grid has RowSelect handler */
.clickable-rows .rz-data-row {
  cursor: pointer;
}

/* Radzen keeps a column title on one line and clips whatever does not fit, so "Power Consumption"
   arrived as "POWER CONSUMPTIO" and the reader had no way to know a letter was missing. Two lines of
   a small letter-spaced capital take less room than the column a nowrap title demands, and the
   header is the one row in a grid that can afford the height. */
.rz-data-grid .rz-column-title-content,
.rz-data-grid .rz-column-title {
  white-space: normal;
  overflow-wrap: break-word;
}

/* The buttons at the end of a grid row. Text buttons rather than filled ones, so twenty rows are
   twenty readings rather than sixty buttons — and coloured on hover, so the row still says the
   controls are live. */
.row-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--looc-space-1);
}

/* Neutral row actions are grey until hovered. Only the Base ones: a delete keeps its red, which is
   the whole reason it is not Base. */
.row-actions .rz-button.rz-variant-text.rz-base {
  color: var(--rz-text-secondary-color);
}

.row-actions .rz-button.rz-variant-text.rz-base:hover:not(:disabled) {
  color: var(--rz-primary-dark);
  background-color: var(--rz-primary-lighter);
  /* Overrides the global brightness filter, which does nothing to a transparent button. */
  filter: none;
}

.row-actions .rz-button.rz-variant-text.rz-danger:hover:not(:disabled) {
  background-color: var(--rz-danger-lighter);
  filter: none;
}

/* =============================================================================
   LOOC Animation System
   ============================================================================= */

/* Respect user preference for reduced motion */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Content fade-in animation */
@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Stagger animation utility classes */
.animate-fade-in {
  animation: fadeIn 0.3s var(--ease-out-quart) forwards;
}

.animate-fade-in-up {
  animation: fadeInUp 0.4s var(--ease-out-quart) forwards;
}

/* Stagger delays for dashboard cards */
.stagger-1 { animation-delay: 0ms; }
.stagger-2 { animation-delay: 50ms; }
.stagger-3 { animation-delay: 100ms; }
.stagger-4 { animation-delay: 150ms; }
.stagger-5 { animation-delay: 200ms; }

/* Content wrapper animation for LoadingWrapper */
.content-loaded {
  animation: fadeIn 0.25s var(--ease-out-quart) forwards;
}

/* Sidebar transition enhancement */
.rz-sidebar {
  transition: width 0.25s var(--ease-out-quart);
}

/* Card hover improvements with better easing */
.rz-card {
  transition: box-shadow 0.2s var(--ease-out-quart),
              transform 0.2s var(--ease-out-quart);
}

/* Interactive card lift effect */
.card-interactive {
  cursor: pointer;
}

.card-interactive:hover {
  box-shadow: var(--rz-shadow-3);
  transform: translateY(-2px);
}

.card-interactive:active {
  transform: translateY(0);
  transition-duration: 0.1s;
}

/* Button feedback enhancement */
.rz-button {
  transition: background-color 0.15s var(--ease-out-quart),
              box-shadow 0.15s var(--ease-out-quart),
              transform 0.1s var(--ease-out-quart);
}

.rz-button:active:not(:disabled) {
  transform: scale(0.98);
}

/* Tab indicator animation */
.page-tab {
  position: relative;
}

.page-tab::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--looc-tab-active-color);
  transform: scaleX(0);
  transition: transform 0.2s var(--ease-out-quart);
}

.page-tab.active::after {
  transform: scaleX(1);
}

/* Loading skeleton animation */
@keyframes shimmer {
  0% {
    background-position: -200% 0;
  }
  100% {
    background-position: 200% 0;
  }
}

.skeleton {
  background: linear-gradient(
    90deg,
    var(--rz-base-200) 25%,
    var(--rz-base-100) 50%,
    var(--rz-base-200) 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
  border-radius: 4px;
}

/* =============================================================================
   Success Notification Polish
   ============================================================================= */

@keyframes slideInRight {
  from {
    opacity: 0;
    transform: translateX(40px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes iconPulse {
  0% {
    transform: scale(0.8);
  }
  50% {
    transform: scale(1.05);
  }
  100% {
    transform: scale(1);
  }
}

.rz-notification-success {
  animation: slideInRight 0.35s var(--ease-out-quart) forwards;
  border-left: 3px solid var(--rz-primary);
}

.rz-notification-success .rz-icon {
  animation: iconPulse 0.4s var(--ease-out-quart) forwards;
}

/* =============================================================================
   Empty State — see CustomUiComponents/EmptyState.razor
   ============================================================================= */

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 2rem;
  gap: 0.5rem;
  text-align: center;
}

.empty-state-icon {
  font-size: 2.5rem;
  color: var(--rz-text-tertiary-color);
}

.empty-state-icon--success {
  color: var(--rz-success);
}

.empty-state-title {
  font-weight: 500;
  color: var(--rz-text-color);
}

.empty-state-message {
  font-size: 0.875rem;
  color: var(--rz-text-tertiary-color);
  max-width: 32rem;
}

.empty-state-actions {
  margin-top: 0.5rem;
}

/* Inside a data grid, where the state has to read as the body of a table rather than as part of
   the page around it. */
.empty-state--grid {
  padding: 3rem 2rem;
  background: var(--rz-base-50);
  border-radius: 8px;
}

.empty-state--grid .empty-state-title {
  color: var(--rz-text-secondary-color);
}

/* Emptiness that is good news — no open incidents — so it is not drawn as an absence. */
.empty-state--positive {
  background: var(--rz-success-lighter);
  border-radius: 8px;
}
/* =============================================================================
   Narrow viewports

   The Portal is desktop-optimised, and that is the right call for a tool used across many clients at
   once. It is still opened on a laptop at 1280 and on a tablet in a plant room, and until now the
   only @media rule in the whole stylesheet was the reduced-motion one — so a page's 24px inset and a
   six-tab strip simply overflowed with no way to reach what fell off the edge.
   ============================================================================= */

@media (max-width: 1279.98px) {
  :root {
    --looc-page-padding: var(--looc-space-4);
  }
}

@media (max-width: 767.98px) {
  :root {
    --looc-page-padding: var(--looc-space-3);
  }

  /* Fixed at 400px from the right edge, which put it off-screen on anything narrower than that. */
  .incident-menu {
    left: var(--looc-space-2);
    right: var(--looc-space-2);
    width: auto;
  }

  .page-error {
    padding: var(--looc-space-4);
  }

  .page-error__actions {
    flex: 1 1 100%;
  }
}

/* =============================================================================
   Quick jump — see Shared/QuickJump/QuickJumpPalette.razor
   ============================================================================= */

/* Shaped like the field it opens rather than like a button. It is the one control in the header that
   is not an action — pressing it starts a search — and giving it an input's recessed fill and a wide
   flat shape says so before it is pressed. */
.quick-jump-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--looc-space-2);
  min-width: 15rem;
  margin-right: var(--looc-space-4);
  padding: var(--looc-space-2) var(--looc-space-3);
  border: 1px solid var(--looc-border-color);
  border-radius: var(--rz-border-radius);
  background: var(--rz-base-50);
  color: var(--rz-text-secondary-color);
  font: inherit;
  font-size: 0.875rem;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 0.15s var(--ease-out-quart),
              border-color 0.15s var(--ease-out-quart);
}

.quick-jump-trigger:hover {
  background: var(--rz-base-100);
  border-color: var(--rz-base-300);
}

.quick-jump-trigger__icon {
  font-size: 1.125rem;
  color: var(--rz-text-tertiary-color);
}

/* Takes the room, so the shortcut is pushed to the far end and the label sits against the icon. */
.quick-jump-trigger__label {
  flex: 1;
  text-align: left;
}

.quick-jump-trigger__kbd {
  padding: 1px var(--looc-space-1);
  border: 1px solid var(--looc-border-color);
  border-radius: var(--looc-radius-sm);
  background: var(--rz-base-background-color);
  color: var(--rz-text-tertiary-color);
  font-family: inherit;
  font-size: 0.6875rem;
  line-height: 1.4;
}

.quick-jump-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.35);
  z-index: 1200;
}

/* Anchored near the top rather than centred: the reader's eyes are already at the header they
   pressed the shortcut from, and a centred panel jumps them to the middle of the screen and back. */
.quick-jump {
  position: fixed;
  top: 12vh;
  left: 50%;
  transform: translateX(-50%);
  width: min(40rem, calc(100vw - 2rem));
  max-height: 70vh;
  display: flex;
  flex-direction: column;
  background: var(--rz-base-background-color);
  border: 1px solid var(--rz-base-300);
  border-radius: var(--looc-radius-lg);
  box-shadow: 0 24px 48px rgba(15, 23, 42, 0.24);
  overflow: hidden;
  z-index: 1201;
  animation: fadeInUp 0.16s var(--ease-out-quart) forwards;
}

.quick-jump__search {
  display: flex;
  align-items: center;
  gap: var(--looc-space-3);
  padding: var(--looc-space-3) var(--looc-space-4);
  border-bottom: 1px solid var(--rz-base-200);
}

.quick-jump__search-icon {
  color: var(--rz-text-tertiary-color);
}

.quick-jump__input {
  flex: 1;
  border: none;
  outline: none;
  background: transparent;
  font: inherit;
  font-size: 1rem;
  color: var(--rz-text-color);
}

.quick-jump__input::placeholder {
  color: var(--rz-text-tertiary-color);
}

.quick-jump__results {
  overflow-y: auto;
  padding: var(--looc-space-2) 0;
}

.quick-jump__group {
  padding: var(--looc-space-2) var(--looc-space-4) var(--looc-space-1);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--rz-text-tertiary-color);
}

.quick-jump__result {
  display: flex;
  align-items: center;
  gap: var(--looc-space-3);
  width: 100%;
  padding: var(--looc-space-2) var(--looc-space-4);
  border: none;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
  color: var(--rz-text-color);
}

/* One highlight for both the keyboard and the mouse: two different "current" rows in one list is
   the fastest way to make a palette feel broken. */
.quick-jump__result.is-highlighted {
  background: var(--rz-primary-lighter);
}

.quick-jump__result-icon {
  color: var(--rz-text-tertiary-color);
  font-size: 1.125rem;
  flex: none;
}

.quick-jump__result-name {
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The breadcrumb is what tells two identically named controllers apart, so it is never the part
   that gets dropped first when the row runs out of room. */
.quick-jump__result-context {
  margin-left: auto;
  padding-left: var(--looc-space-3);
  font-size: 0.75rem;
  color: var(--rz-text-secondary-color);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 55%;
}

.quick-jump__note {
  padding: var(--looc-space-4);
  font-size: 0.875rem;
  color: var(--rz-text-secondary-color);
}

.quick-jump__note--warning {
  color: var(--rz-warning);
}

.quick-jump__footer {
  display: flex;
  gap: var(--looc-space-4);
  padding: var(--looc-space-2) var(--looc-space-4);
  border-top: 1px solid var(--rz-base-200);
  background: var(--rz-base-50);
  font-size: 0.75rem;
  color: var(--rz-text-tertiary-color);
}

.quick-jump kbd,
.quick-jump__kbd {
  display: inline-block;
  min-width: 1.25rem;
  padding: 0 0.25rem;
  border: 1px solid var(--rz-base-300);
  border-radius: var(--looc-radius-sm);
  background: var(--rz-base-100);
  font-family: inherit;
  font-size: 0.6875rem;
  line-height: 1.3;
  text-align: center;
  color: var(--rz-text-secondary-color);
}

@media (max-width: 767.98px) {
  .quick-jump {
    top: var(--looc-space-2);
    width: calc(100vw - 1rem);
    max-height: 85vh;
  }

  /* On a phone the field collapses to its magnifier: the label and the shortcut together take a
     quarter of the header, and the shortcut names keys the device does not have. */
  .quick-jump-trigger {
    min-width: 0;
    margin-right: var(--looc-space-2);
    gap: 0;
  }

  .quick-jump-trigger__label,
  .quick-jump-trigger__kbd {
    display: none;
  }
}

/* =============================================================================
   Stat card — see CustomUiComponents/StatCard.razor

   Four pages had each drawn this tile themselves, with four copies of these rules that had already
   drifted apart in padding and icon size.
   ============================================================================= */

.stat-card .stat-card__inner {
  display: flex;
  align-items: center;
  gap: var(--looc-space-4);
}

.stat-icon {
  width: 48px;
  height: 48px;
  border-radius: var(--looc-radius-lg);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  background: var(--rz-base-100);
}

.stat-icon .rz-icon {
  font-size: 1.5rem;
  color: var(--rz-text-secondary-color);
}

.stat-icon--primary { background: var(--rz-primary-lighter); }
.stat-icon--primary .rz-icon { color: var(--rz-primary); }

.stat-icon--success { background: var(--rz-success-lighter); }
.stat-icon--success .rz-icon { color: var(--rz-success); }

.stat-icon--warning { background: var(--rz-warning-lighter); }
.stat-icon--warning .rz-icon { color: var(--rz-warning); }

.stat-icon--danger { background: var(--rz-danger-lighter); }
.stat-icon--danger .rz-icon { color: var(--rz-danger); }

.stat-content {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* Tabular figures so a column of tiles does not jitter as the numbers change width. */
.stat-value {
  font-size: 1.75rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--rz-text-color);
  font-variant-numeric: tabular-nums;
}

.stat-value--primary { color: var(--rz-primary); }
.stat-value--success { color: var(--rz-success); }
.stat-value--warning { color: var(--rz-warning); }
.stat-value--danger { color: var(--rz-danger); }

.stat-label {
  font-size: 0.8125rem;
  color: var(--rz-text-secondary-color);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Skip link — see Shared/MainLayout.razor.
   Off-screen rather than hidden: display:none would take it out of the tab order, which is the one
   thing it exists to be in. */
.skip-link {
  position: fixed;
  top: -100%;
  left: var(--looc-space-2);
  z-index: 2000;
  padding: var(--looc-space-2) var(--looc-space-4);
  border-radius: var(--looc-radius-sm);
  background: var(--rz-primary);
  color: var(--rz-on-primary);
  font-size: 0.875rem;
  text-decoration: none;
}

.skip-link:focus-visible {
  top: var(--looc-space-2);
}

/* The target has tabindex="-1" so focus can land on it; it must not then draw a ring around the
   whole page. */
#main-content:focus {
  outline: none;
}

/* =============================================================================
   Rules lifted out of per-row components

   A `<style>` block inside a .razor is global and is emitted once per *instance*, so a component
   that renders per grid row or per matrix cell was shipping a hundred identical copies of the same
   stylesheet into the DOM. These four rendered per row or per cell.
   ============================================================================= */

/* Shared/Incidents/IncidentMenuEntry.razor — a real button that has to look like a row. */
.incident-entry {
  display: block;
  border: none;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

/* CustomUiComponents/Icons/SeverityIcon.razor */
.severity-icon {
  font-size: 1.5em;
}

/* The strategy matrix's cells.

   The switch rule was `.rz-switch { zoom: 0.6 !important }` — unscoped, so it shrank every Radzen
   switch in the Portal to 60%, including the ones on the device types page and inside every form
   built from BooleanBox. Now it reaches only switches inside the matrix, which is all it ever
   wanted, and no longer needs !important to do it. */
.strategy-matrix .rz-switch {
  zoom: 0.6;
  z-index: 5;
}

.strategy-matrix .gray-overlay,
.strategy-matrix .unavailable-strategy {
  position: relative;
  z-index: 1;
}

/* pointer-events: none so the text underneath stays selectable through the overlay. */
.strategy-matrix .gray-overlay::after,
.strategy-matrix .unavailable-strategy::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

.strategy-matrix .unavailable-strategy::after {
  background: repeating-linear-gradient(
    45deg,
    var(--rz-base-100) 0px,
    var(--rz-base-100) 10px,
    transparent 10px,
    transparent 20px
  );
}

/* The InfoTip button — see CustomUiComponents/InfoTip.razor.

   Deliberately quiet: an explanation offered beside every label becomes noise if each one is as loud
   as the label it explains. Tertiary grey is safe here because the icon is decoration beside text
   that already says what the field is, and the explanation itself reaches a screen reader through
   aria-label rather than through this colour. */
.info-tip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  padding: 0;
  border: none;
  background: none;
  color: var(--rz-text-tertiary-color);
  cursor: help;
  line-height: 1;
  vertical-align: middle;
  border-radius: var(--looc-radius-sm);
  transition: color 0.15s var(--ease-out-quart);
}

.info-tip:hover,
.info-tip:focus-visible {
  color: var(--rz-primary-dark);
}

.info-tip .rz-icon,
.info-tip .notranslate {
  font-size: 1rem;
}

/* Beside 11px letter-spaced capitals — a grid or matrix header — the body-sized icon is taller than
   the row it sits in and pushes the header text off its baseline. */
.info-tip--small .rz-icon,
.info-tip--small .notranslate {
  font-size: 0.875rem;
}

/* A label and its InfoTip are one phrase and must not be split across two lines by the gap between
   them. Used by every header and field label that carries help. */
.label-with-help {
  display: inline-flex;
  align-items: center;
  gap: var(--looc-space-1);
}

/* Help text under a form field, from the Hint parameter on the shared Form components.

   `.rz-form-field-helper` is Radzen's own slot, used directly by the device form before the shared
   components could carry a hint. Styled together so the two are indistinguishable — a form where
   half the hints are one size and half another looks unfinished rather than varied.

   Secondary, not tertiary: at 13px the tertiary grey is under the contrast floor, and this is the
   only text on the page saying what unit a number is in. */
.field-hint,
.rz-form-field-helper {
  margin-top: var(--looc-space-1);
  padding-inline: var(--looc-space-1);
  font-size: 0.8125rem;
  line-height: 1.35;
  color: var(--rz-text-secondary-color);
}

/* A validation error under a hint reads as a continuation of it unless it starts its own block. */
.field-hint + .validation-message,
.rz-form-field-helper + .validation-message {
  display: block;
  margin-top: var(--looc-space-1);
}

/* Radzen's tooltip has no width of its own, so a sentence of explanation came out as one long line
   running off the viewport. Every InfoTip in the Portal opens one of these.

   Both elements: the content is what holds the text, but the positioned wrapper is what the browser
   sizes, so constraining only the inner one leaves the bubble as wide as the sentence. */
.rz-tooltip,
.rz-tooltip-content {
  max-width: 24rem;
}

.rz-tooltip-content {
  white-space: normal;
  line-height: 1.4;
}

/* The device name in the strategy matrix — a button that reads as a link.
   See Strategies/Matrix/Components/DeviceNameLink.razor for why it is not a span. */
.device-name-link {
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  font-weight: 600;
  color: var(--rz-primary-dark);
  text-align: left;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
  border-radius: var(--looc-radius-sm);
}

.device-name-link:hover {
  color: var(--rz-primary-darker);
}
