/* GENERATED FILE -- DO NOT EDIT.
   Source: benchmarking/shared-ui/components.css
   Regenerate: python3 benchmarking/scripts/sync_shared_ui.py */
/* ============================================================================
   SHARED UI COMPONENTS — Skynet + HotelLab
   ----------------------------------------------------------------------------
   CANONICAL SOURCE. The copies in benchmarking/webapp/static/ and
   benchmarking/hotel-webapp/public/ are generated -- do not edit them.

   After editing this file run:
       python3 benchmarking/scripts/sync_shared_ui.py

   Loaded after tokens.css and before each app's own app.css, so an app can
   still override a component but starts from the same one.
   ============================================================================ */

/* --- Toast -----------------------------------------------------------------
   Neither app had one. Skynet completed every mutation silently (promote a
   supplier, save a retention policy, approve a batch); HotelLab had three
   ad-hoc status lines that appended to each other. One region, bottom-right,
   aria-live so it is announced without stealing focus.
   -------------------------------------------------------------------------- */
.toast-region {
  position: fixed;
  right: var(--space-4);
  bottom: var(--space-4);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-2);
  width: min(24rem, calc(100vw - var(--space-8)));
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-4);
  border: 1px solid var(--hairline-strong);
  border-left: 3px solid var(--ink-soft);
  border-radius: var(--radius-sm);
  background: var(--surface);
  box-shadow: var(--shadow-3);
  color: var(--ink);
  font-size: var(--text-md);
  line-height: 1.45;
  pointer-events: auto;
  /* Enters from the side it lives on. */
  animation: toast-in var(--dur) var(--ease) both;
}

.toast--success { border-left-color: var(--success); }
.toast--error { border-left-color: var(--danger); }
.toast--warn { border-left-color: var(--pending); }
.toast--info { border-left-color: var(--accent); }

.toast-icon { flex: 0 0 auto; margin-top: 0.1rem; }
.toast--success .toast-icon { color: var(--success); }
.toast--error .toast-icon { color: var(--danger); }
.toast--warn .toast-icon { color: var(--pending); }
.toast--info .toast-icon { color: var(--accent); }

.toast-body { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.toast-title { display: block; font-weight: var(--weight-semibold); }
.toast-detail { display: block; margin-top: 0.15rem; color: var(--ink-soft); font-size: var(--text-sm); }

.toast-dismiss {
  all: unset;
  flex: 0 0 auto;
  width: 1.5rem;
  height: 1.5rem;
  display: grid;
  place-items: center;
  border-radius: var(--radius-sm);
  color: var(--ink-soft);
  cursor: pointer;
  font-size: var(--text-lg);
  line-height: 1;
}
.toast-dismiss:hover { color: var(--ink); background: var(--surface-hover); }
.toast-dismiss:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.toast.is-leaving { animation: toast-out var(--dur-fast) var(--ease) both; }

@keyframes toast-in {
  from { opacity: 0; transform: translateY(0.5rem) scale(0.98); }
  to { opacity: 1; transform: none; }
}
/* Exit is shorter than enter so dismissing feels responsive. */
@keyframes toast-out {
  from { opacity: 1; transform: none; }
  to { opacity: 0; transform: translateX(0.75rem); }
}

@media (max-width: 560px) {
  .toast-region {
    right: var(--space-2);
    left: var(--space-2);
    bottom: var(--space-2);
    width: auto;
    align-items: stretch;
  }
}

/* --- Numeric cells ---------------------------------------------------------
   Money, counts and percentages need fixed-width digits or the columns fail to
   line up and a scan down them stops working. Both apps had this applied
   piecemeal, and missing on the columns where it matters most.
   -------------------------------------------------------------------------- */
.numeric {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
/* A whole column, headers included, so the heading sits over its own digits. */
.numeric-col { text-align: right; }
td.numeric-col,
th.numeric-col { font-variant-numeric: tabular-nums; }

/* --- Sortable table headers ------------------------------------------------
   Injected by components.js into th elements of a [data-sortable] table. The
   arrow is drawn from the aria-sort state, so the visual and the announced
   state cannot disagree -- and direction is never conveyed by colour alone.
   -------------------------------------------------------------------------- */
.table-sort-control {
  all: unset;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  cursor: pointer;
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
}
.table-sort-control:hover { color: var(--accent); }
.table-sort-control:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
/* Reserve the arrow's width at rest so sorting does not shift the header row. */
.table-sort-arrow {
  width: 0.55rem;
  flex: 0 0 auto;
  opacity: 0;
  font-size: var(--text-2xs);
  line-height: 1;
}
.table-sort-control:hover .table-sort-arrow { opacity: 0.5; }
.table-sort-arrow::before { content: "\2195"; }
[aria-sort="ascending"] .table-sort-arrow { opacity: 1; }
[aria-sort="ascending"] .table-sort-arrow::before { content: "\25B2"; }
[aria-sort="descending"] .table-sort-arrow { opacity: 1; }
[aria-sort="descending"] .table-sort-arrow::before { content: "\25BC"; }
[aria-sort="ascending"] .table-sort-control,
[aria-sort="descending"] .table-sort-control { color: var(--accent); }

/* --- Confirm dialog --------------------------------------------------------
   Built by components.js to replace native confirm() on destructive actions.
   Skynet had 7 native confirms; HotelLab used native confirm for its two
   real deletes while reserving its styled dialog for a non-destructive case.
   -------------------------------------------------------------------------- */
.lab-confirm {
  width: min(28rem, calc(100vw - var(--space-8)));
  padding: 0;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-4);
}
/* Strong enough that the page behind stops competing for attention. */
.lab-confirm::backdrop {
  background: rgba(3, 3, 5, 0.62);
  backdrop-filter: blur(3px);
}
.lab-confirm-form { padding: var(--space-5); }
.lab-confirm-title {
  margin: 0;
  border: 0;
  padding: 0;
  color: var(--ink);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
  text-transform: none;
}
.lab-confirm-body {
  margin: var(--space-2) 0 0;
  color: var(--ink-soft);
  font-size: var(--text-md);
  line-height: 1.55;
}
.lab-confirm-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-5);
}
@media (max-width: 560px) {
  /* Reversed, so the destructive option is not the one under the thumb. */
  .lab-confirm-actions { flex-direction: column-reverse; }
  .lab-confirm-actions button { width: 100%; }
}

/* --- Empty state -----------------------------------------------------------
   Skynet had two well-designed empty states and ~15 bare one-line hints
   doing the same job; HotelLab styled one it never used.
   -------------------------------------------------------------------------- */
.empty-state {
  display: grid;
  justify-items: center;
  gap: var(--space-2);
  padding: var(--space-8) var(--space-4);
  text-align: center;
  color: var(--ink-soft);
}
.empty-state-icon { color: var(--ink-faint); }
.empty-state-title { color: var(--ink); font-size: var(--text-lg); font-weight: var(--weight-semibold); }
.empty-state-body { max-width: 32rem; margin: 0; font-size: var(--text-md); line-height: 1.55; }
.empty-state-action { margin-top: var(--space-2); }
.empty-state--inline { padding: var(--space-5) var(--space-4); }

/* --- Loading ---------------------------------------------------------------
   Skynet hand-duplicated the same block in four templates and a fifth time
   as a JS template string.
   -------------------------------------------------------------------------- */
.loading-block {
  display: grid;
  justify-items: center;
  gap: var(--space-3);
  padding: var(--space-10) var(--space-4);
  color: var(--ink-soft);
  font-size: var(--text-md);
}
.spinner {
  width: 1.5rem;
  height: 1.5rem;
  border: 2px solid var(--hairline-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spinner-spin 700ms linear infinite;
}
@keyframes spinner-spin { to { transform: rotate(360deg); } }

/* Skeleton rows, for when the shape of the content is known in advance. */
.skeleton {
  border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--surface-hover) 50%, var(--surface-2) 75%);
  background-size: 200% 100%;
  animation: skeleton-sweep 1.2s ease-in-out infinite;
}
.skeleton--row { height: 1.15rem; margin-bottom: var(--space-2); }
.skeleton--row:nth-child(even) { width: 82%; }
@keyframes skeleton-sweep {
  from { background-position: 200% 0; }
  to { background-position: -200% 0; }
}

/* --- Busy submit -----------------------------------------------------------
   The slowest, most expensive actions in both apps had no submit feedback and
   no double-submit guard. components.js adds .is-busy on submit.
   -------------------------------------------------------------------------- */
.is-busy { position: relative; cursor: progress; }
.is-busy > .spinner {
  width: 0.85rem;
  height: 0.85rem;
  border-width: 2px;
  display: inline-block;
  margin-right: var(--space-2);
  vertical-align: -0.1em;
}

@media (prefers-reduced-motion: reduce) {
  .toast,
  .toast.is-leaving { animation: none; }
  .spinner,
  .skeleton { animation: none; }
  /* Without the sweep a skeleton still needs to read as a placeholder. */
  .skeleton { background: var(--surface-2); }
}
