/* ============================================================
   Handshake Design System — shared parts

   Alert, dialog, empty state and toast. These live apart from
   hs-components.css for one reason: that file restyles class names v1 already
   uses (.hs-btn, .hs-card, .hs-field, .hs-table …), so it cannot be loaded on
   a bridged page without repainting the product. These four collide with
   nothing, use only semantic tokens, and are what hs-ui.js composes — so both
   the design-system pages and the live app can load exactly this file and
   share one definition instead of two that drift.

   Load after hs-tokens.css. Pairs with css/hs-ui.js.
   ============================================================ */

/* ============================================================
   Alert / banner
   ============================================================ */
.hs-alert {
  display: flex;
  align-items: flex-start;
  gap: var(--hs-space-3);
  padding: var(--hs-space-4);
  border: var(--hs-size-hairline) solid var(--hs-border-subtle);
  border-radius: var(--hs-radius-md);
  background: var(--hs-bg-sunken);
  font-size: var(--hs-text-sm);
  line-height: var(--hs-leading-normal);
}

.hs-alert__icon {
  flex: none;
  margin-block-start: 1px;
}

.hs-alert__title {
  font-family: var(--hs-font-ui);
  font-weight: var(--hs-weight-semibold);
}

.hs-alert--success {
  background: var(--hs-success-bg);
  border-color: var(--hs-success-border);
  color: var(--hs-success-fg);
}

.hs-alert--warning {
  background: var(--hs-warning-bg);
  border-color: var(--hs-warning-border);
  color: var(--hs-warning-fg);
}

.hs-alert--danger {
  background: var(--hs-danger-bg);
  border-color: var(--hs-danger-border);
  color: var(--hs-danger-fg);
}

.hs-alert--info {
  background: var(--hs-info-bg);
  border-color: var(--hs-info-border);
  color: var(--hs-info-fg);
}

/* ============================================================
   Modal / dialog
   ============================================================ */
.hs-dialog {
  width: min(560px, calc(100vw - var(--hs-space-8)));
  max-height: calc(100vh - var(--hs-space-16));
  padding: 0;
  border: var(--hs-size-hairline) solid var(--hs-border-subtle);
  border-radius: var(--hs-radius-xl);
  /* Control weight, not card weight. On a bridged page --hs-bg-raised is
     remapped to the 0.72 card glass, and a modal that translucent sits muddy
     over its own scrim. A dialog is the most foreground thing on screen. */
  background: var(--hs-glass-control);
  backdrop-filter: var(--hs-glass-blur);
  -webkit-backdrop-filter: var(--hs-glass-blur);
  color: var(--hs-fg-primary);
  box-shadow: var(--hs-elevation-modal);
  overflow: hidden;

  /* The dialog grows out of the control that opened it. hs-ui.js measures the
     trigger and writes --hs-dialog-origin; without it the origin is the centre,
     which is the right answer when there is no trigger to point at.

     Enter and exit are the same transition read in two directions, so a dialog
     always returns the way it came. That is why this is @starting-style and
     allow-discrete rather than a pair of keyframes: one declaration, no way for
     the two directions to drift apart, and no JS holding the element alive
     while an exit animation finishes.

     opacity is on --hs-duration-fade and the scale is not. Under reduced motion
     the growth collapses to nothing and the cross-fade remains. */
  transform-origin: var(--hs-dialog-origin, 50% 50%);
  opacity: 0;
  scale: 0.96;
  transition:
    opacity var(--hs-duration-fade) var(--hs-ease-out),
    scale var(--hs-duration-base) var(--hs-ease-spring),
    display var(--hs-duration-base) allow-discrete,
    overlay var(--hs-duration-base) allow-discrete;
}

.hs-dialog[open] {
  opacity: 1;
  scale: 1;
}

@starting-style {
  .hs-dialog[open] {
    opacity: 0;
    scale: 0.96;
  }
}

.hs-dialog::backdrop {
  background: var(--hs-bg-scrim);
  backdrop-filter: var(--hs-glass-blur-scrim);
  opacity: 0;
  transition:
    opacity var(--hs-duration-fade) var(--hs-ease-out),
    display var(--hs-duration-fade) allow-discrete,
    overlay var(--hs-duration-fade) allow-discrete;
}

.hs-dialog[open]::backdrop {
  opacity: 1;
}

@starting-style {
  .hs-dialog[open]::backdrop {
    opacity: 0;
  }
}

/* ::backdrop inherits from its originating element in every current engine, so
   the token override in hs-tokens.css does reach here. This restates it anyway:
   ::backdrop is the one selector where a failed inheritance degrades to a
   transparent, invisible scrim rather than to something merely off-palette. */
@media (prefers-reduced-transparency: reduce) {
  .hs-dialog::backdrop {
    backdrop-filter: none;
  }
}

.hs-dialog__header {
  display: flex;
  align-items: center;
  gap: var(--hs-space-3);
  padding: var(--hs-space-5);
  border-bottom: var(--hs-size-hairline) solid var(--hs-border-subtle);
}

.hs-dialog__body {
  padding: var(--hs-space-5);
  overflow-y: auto;
}

.hs-dialog__footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--hs-space-2);
  padding: var(--hs-space-5);
  border-top: var(--hs-size-hairline) solid var(--hs-glass-rim);
  background: var(--hs-glass-sunken);
}

/* ============================================================
   Empty state
   ============================================================ */
.hs-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--hs-space-3);
  padding: var(--hs-space-16) var(--hs-space-6);
  text-align: center;
  color: var(--hs-fg-tertiary);
}

.hs-empty__title {
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-base);
  font-weight: var(--hs-weight-semibold);
  color: var(--hs-fg-primary);
}

.hs-empty__body {
  max-width: 44ch;
  font-size: var(--hs-text-sm);
  text-wrap: pretty;
}

/* ============================================================
   Runtime helper surfaces — see css/hs-ui.js

   Alert, dialog and empty were already specified above but nothing rendered
   them, because the app had no shared render layer. These are the few extra
   parts hs-ui.js composes: alert content, dialog title, empty actions, and
   the undo toast that replaces "are you sure?" for reversible actions.
   ============================================================ */

.hs-alert__content {
  display: flex;
  flex-direction: column;
  gap: var(--hs-space-1);
  min-width: 0;
}

.hs-alert__body,
.hs-alert__title {
  margin: 0;
  text-wrap: pretty;
}

.hs-alert__actions {
  margin: var(--hs-space-2) 0 0;
}

/* Status text carries the tone colour; the action inside must not, or a
   warning-coloured button reads as a second warning. */
.hs-alert .hs-btn {
  color: var(--hs-fg-primary);
}

.hs-dialog__title {
  margin: 0;
  font-family: var(--hs-font-display);
  font-size: var(--hs-text-lg);
  font-weight: var(--hs-weight-semibold);
  letter-spacing: var(--hs-tracking-tight);
}

.hs-dialog__body p {
  margin: 0;
  color: var(--hs-fg-secondary);
  text-wrap: pretty;
}

.hs-dialog__icon {
  display: grid;
  place-items: center;
  flex: none;
  width: var(--hs-size-control-sm);
  height: var(--hs-size-control-sm);
  border-radius: var(--hs-radius-sm);
  background: var(--hs-bg-sunken);
  color: var(--hs-fg-secondary);
}

.hs-dialog__icon--danger {
  background: var(--hs-danger-bg);
  color: var(--hs-danger-fg);
}

.hs-dialog__icon--warning {
  background: var(--hs-warning-bg);
  color: var(--hs-warning-fg);
}

.hs-empty__icon {
  display: grid;
  place-items: center;
  width: var(--hs-space-12);
  height: var(--hs-space-12);
  margin-block-end: var(--hs-space-1);
  border-radius: var(--hs-radius-circle);
  background: var(--hs-glass-recessed);
  color: var(--hs-fg-tertiary);
}

.hs-empty__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--hs-space-2);
  margin-block-start: var(--hs-space-2);
}

/* ---- Undo toast ---------------------------------------------------------
   Reversibility beats confirmation. A modal asks the user to predict whether
   they will regret something; an undo lets them find out. */
.hs-toast-host {
  position: fixed;
  z-index: var(--hs-layer-toast);
  inset-inline: 0;
  bottom: calc(var(--hs-space-6) + var(--hs-dock-h, 0px) + env(safe-area-inset-bottom, 0px));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--hs-space-2);
  pointer-events: none;
}

.hs-toast {
  display: flex;
  align-items: center;
  gap: var(--hs-space-3);
  max-width: min(440px, calc(100vw - var(--hs-space-8)));
  padding: var(--hs-space-3) var(--hs-space-3) var(--hs-space-3) var(--hs-space-5);
  border: var(--hs-size-hairline) solid var(--hs-glass-rim);
  border-radius: var(--hs-radius-pill);
  background: var(--hs-glass-control);
  backdrop-filter: var(--hs-glass-blur);
  -webkit-backdrop-filter: var(--hs-glass-blur);
  box-shadow: var(--hs-elevation-overlay);
  color: var(--hs-fg-primary);
  font-size: var(--hs-text-sm);
  pointer-events: auto;
  animation: hs-toast-in var(--hs-duration-slow) var(--hs-ease-spring) both;
}

.hs-toast__msg {
  min-width: 0;
}

.hs-toast.is-leaving {
  animation: hs-toast-out var(--hs-duration-base) var(--hs-ease-in) both;
}

@keyframes hs-toast-in {
  from { opacity: 0; transform: translateY(10px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}

@keyframes hs-toast-out {
  to { opacity: 0; transform: translateY(6px) scale(0.98); }
}

/* ============================================================
   The layout breakpoint

   Both halves are documented in apps/handshake/public/poc5-pages.css:

     rail  @media (min-width: 900px), (min-width: 744px) and (min-height: 900px)
     dock  @media (max-width: 743px), (max-width: 899px) and (max-height: 899px)

   The height term keeps an iPad in portrait (744-834px wide, tall) on the rail
   while a phone in landscape (844 x 390) stays on the dock. Only the rules that
   SWITCH layout carry the pair — in this file that is the page-head band and the
   conversations page tint. The plain `max-width: 899px` blocks below are content
   refinements for a narrow content pane and are right in either layout.
   ============================================================ */
@media (max-width: 743px), (max-width: 899px) and (max-height: 899px) {
  .hs-toast-host {
    top: calc(
      var(--ws-browser-chrome-top, 0px) +
        var(--ws-mobile-topbar-h, calc(52px + env(safe-area-inset-top, 0px))) +
        8px
    );
    bottom: auto;
    inset-inline: 12px;
    align-items: stretch;
  }

  .hs-toast {
    max-width: none;
    width: 100%;
    border-radius: var(--hs-radius-lg, 12px);
    animation-name: hs-toast-in-top;
  }

  .hs-toast.is-leaving {
    animation-name: hs-toast-out-top;
  }

  @keyframes hs-toast-in-top {
    from { opacity: 0; transform: translateY(-10px) scale(0.98); }
    to   { opacity: 1; transform: none; }
  }

  @keyframes hs-toast-out-top {
    to { opacity: 0; transform: translateY(-6px) scale(0.98); }
  }
}

@media (prefers-reduced-motion: reduce) {
  .hs-toast,
  .hs-toast.is-leaving {
    animation-duration: 1ms;
  }
}

/* ============================================================
   Skeleton + refresh-in-place

   Two primitives, one rule between them:

     Use a SKELETON only when there is nothing to show yet.
     Use REFRESH-IN-PLACE whenever valid content is already on screen.

   Before these existed, every loading state in the product was
   `<p class="sub">Loading…</p>` — one italic line replacing a whole panel or
   table body. That is two layout shifts per load (collapse, then expand), it
   throws away data that was still correct, and it loses scroll position and
   focus on the way back. Sorting a table read as a page reload.

   A skeleton is only worth having if it predicts the real layout. Build it
   from the SAME row markup as the real content, with text swapped for
   `.hs-skel` blocks — then it is correct by construction rather than by a
   guess that drifts the first time a column is added. hs-ui.js exposes
   `HsUI.skeletonRows()` for the table case.
   ============================================================ */

.hs-skel {
  display: block;
  border-radius: var(--hs-radius-sm, 6px);
  /* Sits between the sunken and subtle tokens so it reads as "absent" rather
     than as an empty filled cell. */
  background: linear-gradient(
    90deg,
    var(--hs-bg-sunken) 0%,
    var(--hs-border-subtle) 50%,
    var(--hs-bg-sunken) 100%
  );
  background-size: 200% 100%;
  animation: hs-skel-sweep 1.4s ease-in-out infinite;
  /* Inherit the line box so a skeleton inside a cell reserves the height that
     cell's real text would, rather than collapsing it. */
  height: 1em;
  width: 100%;
}

/* Width variants, so a skeleton row does not read as a suspiciously uniform
   grid. Match these to the column's typical content length. */
.hs-skel--xs { width: 24%; }
.hs-skel--sm { width: 40%; }
.hs-skel--md { width: 65%; }
.hs-skel--lg { width: 85%; }

.hs-skel--circle {
  border-radius: 50%;
  width: var(--hs-size-avatar-sm, 24px);
  height: var(--hs-size-avatar-sm, 24px);
}

@keyframes hs-skel-sweep {
  from { background-position: 200% 0; }
  to { background-position: -200% 0; }
}

/* A table body of skeleton rows must not be announced cell by cell. */
.hs-skel-body {
  /* aria-hidden on the tbody handles screen readers; this is the visual half. */
  pointer-events: none;
  user-select: none;
}

/*
   Refresh-in-place. The container keeps its content and gets a 2px
   indeterminate bar at the top edge. Deliberately NOT an overlay: an overlay
   that blocks interaction during a background refresh is worse than the
   staleness it is hiding.
*/
.hs-refreshing {
  position: relative;
}

.hs-refreshing::after {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--hs-accent-solid) 50%,
    transparent 100%
  );
  background-size: 40% 100%;
  background-repeat: no-repeat;
  animation: hs-refreshing-slide 1.1s ease-in-out infinite;
  pointer-events: none;
  z-index: 2;
}

@keyframes hs-refreshing-slide {
  from { background-position: -40% 0; }
  to { background-position: 140% 0; }
}

/*
   Reduced motion: keep both states legible, drop the movement. A skeleton with
   no animation still communicates "not here yet" through its fill; a refresh
   bar still communicates "working" by being present.
*/
@media (prefers-reduced-motion: reduce) {
  .hs-skel {
    animation: none;
    background: var(--hs-bg-sunken);
  }
  .hs-refreshing::after {
    animation: none;
    background: var(--hs-accent-solid);
    background-size: 100% 100%;
    opacity: 0.55;
  }
}

/* ============================================================
   Table overflow

   A wide table on a narrow screen must scroll inside its own box, never widen
   the page. The admin users/tasks/feedback tables already did this correctly;
   this is that recipe, named once, so the other ~20 tables can adopt it
   without each re-deriving it.

   `table-layout: fixed` is deliberately NOT set here. It is the other half of
   column stability, but applying it globally changes the rendered width of
   every existing table, so it belongs with a per-table width pass — see the
   note in docs/css-load-order.md.
   ============================================================ */
.hs-table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  max-width: 100%;
}

/* ============================================================
   Anchor morph — a menu grows out of its trigger
   ============================================================ */
.hs-morphing {
  will-change: transform, opacity;
  overflow: hidden;
}

.hs-chipmenu {
  position: relative;
  display: inline-flex;
  min-width: 0;
}

.hs-chipmenu__native {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.hs-chipmenu__btn {
  display: inline-flex;
  align-items: center;
  gap: var(--hs-space-2);
  max-width: 100%;
  margin: 0;
  cursor: pointer;
}

.hs-chipmenu__btn-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hs-chipmenu__chev {
  flex: none;
  width: 10px;
  height: 10px;
}

.hs-chipmenu__pop {
  position: absolute;
  top: calc(100% + 6px);
  inset-inline-start: 0;
  z-index: var(--hs-layer-overlay, 40);
  min-width: 100%;
  width: max-content;
  max-width: min(22rem, calc(100vw - 24px));
  max-height: min(320px, 50vh);
  overflow: auto;
  padding: var(--hs-space-1);
  border: var(--hs-size-hairline) solid var(--hs-border-default);
  border-radius: var(--hs-radius-lg);
  background: var(--hs-neutral-0);
  box-shadow: var(--hs-elevation-popover);
}

.hs-chipmenu__pop[hidden] {
  display: none;
}

.hs-chipmenu__opt {
  display: block;
  width: 100%;
  margin: 0;
  padding: var(--hs-space-2) var(--hs-space-3);
  border: 0;
  border-radius: var(--hs-radius-sm);
  background: transparent;
  color: var(--hs-fg-primary);
  font: inherit;
  font-size: var(--hs-text-sm);
  text-align: start;
  cursor: pointer;
}

.hs-chipmenu__opt:hover,
.hs-chipmenu__opt:focus-visible {
  background: var(--hs-bg-sunken);
}

.hs-chipmenu__opt[aria-selected="true"] {
  background: var(--hs-accent-muted);
  color: var(--hs-accent-ink);
}
