/* ===========================================================================
   Surfaces — sheets, menus, toasts, skeletons, empty states.

   Everything that arrives on top of the page. All of it is built on platform
   primitives, and that is a correctness decision rather than a preference:
   <dialog> and the Popover API bring focus trapping, focus restoration, Escape
   to dismiss, light dismiss on outside click, and top-layer stacking that
   ignores z-index and overflow. Every one of those is a thing hand-rolled
   overlays get wrong, and getting them wrong is an accessibility defect rather
   than a rough edge.

   Reference: docs/design/03-ui-patterns.md
              docs/rules/11-accessibility.md
   =========================================================================== */

@layer components {

  /* -------------------------------------------------------------------------
     Sheet.

     A bottom sheet on a phone and a centred dialog on a desktop, which is the
     same element and the same markup: the difference is where it is anchored and
     which direction it enters from, and both are presentation.

     Built on popover rather than on <dialog>.showModal(), which is the same
     choice docs/design/03-ui-patterns.md already made for the Credential Sheet
     and for the same reason: light dismiss, Escape and focus handling come from
     the platform, which does them correctly, so the component ships no
     JavaScript at all. A dialog would need a script to open it, another to close
     it on an outside click, and a third to remember where focus came from.

     The cost is that the page behind stays reachable. That is acceptable for
     what these hold - filters, a credential, a confirmation - and it is not
     acceptable for anything that must be answered before continuing. If such a
     sheet ever exists it gets <dialog> and its own script, deliberately.
     ------------------------------------------------------------------------- */
  /* Phone first: full width, pinned to the bottom, rounded only on the leading
     edge because the trailing edge is off-screen. */
  .bh-sheet {
    position: fixed;
    z-index: var(--z-sheet);
    inset: auto 0 0 0;
    inline-size: 100%;
    max-inline-size: 100%;
    max-block-size: 88dvh;
    padding: 0;
    border: 0;
    border-start-start-radius: var(--r-xl);
    border-start-end-radius: var(--r-xl);
    background: var(--bh-vapor);
    color: var(--bh-chrome);
    box-shadow: var(--elev-3);
    overflow: hidden auto;
    /* overscroll containment, so dragging past the end of a long sheet does not
       start scrolling the page underneath it. */
    overscroll-behavior: contain;
  }

  .bh-sheet__body { padding: var(--sp-5); }

  /* The grab handle. Purely a signifier: the sheet is dismissed with Escape, the
     close button or a click outside, and none of those need this. It is here
     because it is what tells a thumb that the thing came from the bottom. */
  .bh-sheet__grip {
    inline-size: var(--sp-7);
    block-size: var(--sp-1);
    margin: var(--sp-3) auto 0;
    border-radius: var(--r-full);
    background: var(--bh-titanium-dim);
  }

  .bh-sheet__head {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    padding: var(--sp-4) var(--sp-5);
    border-block-end: 1px solid var(--bh-hairline);
  }

  .bh-sheet__title {
    margin: 0;
    font-size: var(--fs-h3);
  }

  .bh-sheet__head .bh-icon-btn { margin-inline-start: auto; }

  /* The scrim. Warm ink rather than black, because pure black over a skin-toned
     page reads as a hole punched in paper. */
  .bh-sheet::backdrop {
    background: var(--bh-scrim);
    -webkit-backdrop-filter: blur(var(--blur-vapor));
    backdrop-filter: blur(var(--blur-vapor));
  }

  /* Entry, and it is @starting-style rather than a class toggled by script,
     which means the animation is declarative and there is no JavaScript timing
     to get out of sync with. */
  @starting-style {
    .bh-sheet:popover-open {
      translate: 0 100%;
      opacity: 0;
    }
  }

  .bh-sheet:popover-open {
    translate: 0 0;
    opacity: 1;
    transition:
      translate var(--dur-base) var(--ease-enter),
      opacity var(--dur-base) var(--ease-enter),
      overlay var(--dur-base) allow-discrete,
      display var(--dur-base) allow-discrete;
  }

  .bh-sheet::backdrop {
    opacity: 1;
    transition:
      opacity var(--dur-base) var(--ease-enter),
      overlay var(--dur-base) allow-discrete,
      display var(--dur-base) allow-discrete;
  }

  /* Desktop: a centred card that grows from the middle instead of sliding up,
     because on a pointer device there is no bottom edge to come from. */
  @media (min-width: 48rem) {
    .bh-sheet {
      inset: 50% auto auto 50%;
      translate: -50% -50%;
      inline-size: min(34rem, calc(100vw - var(--sp-8)));
      border-radius: var(--r-xl);
    }

    .bh-sheet__grip { display: none; }

    @starting-style {
      .bh-sheet:popover-open {
        translate: -50% -46%;
        scale: 0.96;
        opacity: 0;
      }
    }

    .bh-sheet:popover-open {
      translate: -50% -50%;
      scale: 1;
      transition:
        translate var(--dur-base) var(--ease-enter),
        scale var(--dur-base) var(--ease-spring),
        opacity var(--dur-base) var(--ease-enter),
        overlay var(--dur-base) allow-discrete,
        display var(--dur-base) allow-discrete;
    }
  }

  @supports (corner-shape: squircle) {
    .bh-sheet { corner-shape: squircle; }
  }

  /* No Popover API. The attribute is ignored rather than honoured, so the element
     is simply visible - and a visible position: fixed sheet is a panel welded over
     the bottom of the page that nothing can dismiss, because the button that would
     dismiss it is also inert.
     Falling back to page content is the right answer and not a compromise: the
     sheets in this product hold a credential, a Merkle path, a set of filters.
     Every one of them reads correctly as part of the page, which is exactly why
     .bh-sheet--inline exists at all. */
  @supports not selector(:popover-open) {
    .bh-sheet[popover] {
      position: static;
      inset: auto;
      inline-size: 100%;
      max-inline-size: 100%;
      max-block-size: none;
      translate: 0 0;
      scale: 1;
      border: 1px solid var(--bh-hairline);
      border-radius: var(--r-lg);
      background: var(--bh-surface-1);
      box-shadow: none;
    }

    .bh-sheet[popover] .bh-sheet__grip { display: none; }
  }

  /* -------------------------------------------------------------------------
     Inline sheet.

     The same content, rendered as part of the page instead of on the top layer.
     The verify portal uses this: there the credential is the point of the page,
     so hiding it behind a trigger would be absurd.

     Everything the popover version needs has to be undone here, and that is why
     it is a long rule rather than a short one. An inline sheet that keeps
     position: fixed lands on top of the article it is supposed to be part of,
     which is precisely the bug that shipped the first time this variant existed.
     ------------------------------------------------------------------------- */
  .bh-sheet--inline {
    position: static;
    inset: auto;
    inline-size: 100%;
    max-inline-size: 100%;
    max-block-size: none;
    padding: var(--sp-5);
    border: 1px solid var(--bh-hairline);
    border-radius: var(--r-lg);
    background: var(--bh-surface-1);
    box-shadow: var(--elev-1);
    overflow: visible;
    opacity: 1;
    translate: 0 0;
    scale: 1;
    /* It was never hidden, so there is no entry to animate and nothing for a
       transition to interpolate from. */
    transition: none;
  }

  @media (min-width: 48rem) {
    .bh-sheet--inline {
      position: static;
      inset: auto;
      translate: 0 0;
      inline-size: 100%;
      border-radius: var(--r-lg);
    }
  }

  .bh-sheet--inline .bh-sheet__head {
    padding: 0 0 var(--sp-4);
    margin-block-end: var(--sp-4);
  }

  .bh-sheet--inline .bh-sheet__body { padding: 0; }

  /* -------------------------------------------------------------------------
     Menu.

     Popover API plus anchor positioning. No JavaScript at all: the button
     carries popovertarget, the platform handles toggling, Escape, light dismiss
     and focus. The language switcher and the account menu are both this.

     position-area with a position-try fallback means a menu near the end of the
     inline axis flips instead of hanging off the viewport, which is the bug every
     hand-positioned dropdown ships with at least once.
     ------------------------------------------------------------------------- */
  .bh-menu {
    position: fixed;
    z-index: var(--z-deck);
    inline-size: max-content;
    min-inline-size: var(--sp-10);
    max-inline-size: min(20rem, calc(100vw - var(--sp-6)));
    margin: 0;
    padding: var(--sp-2);
    border: 1px solid var(--bh-hairline);
    border-radius: var(--r-md);
    background: var(--bh-vapor);
    box-shadow: var(--elev-2);
  }

  @supports (position-area: block-end span-inline-start) {
    .bh-menu {
      position-area: block-end span-inline-start;
      position-try-fallbacks: block-start span-inline-start, block-end span-inline-end;
      margin-block: var(--sp-2);
    }
  }

  /* The trigger, while its menu is open.

     popovertarget does not flip aria-expanded on the invoker - the spec gives the
     invoker an implicit expanded state for assistive tech, but the attribute in
     the DOM never changes, so a [aria-expanded="true"] rule would never match and
     the button would look idle with its menu on screen.

     :has() on the adjacent sibling reads the real state instead. It is why every
     menu in the product is rendered immediately after its button. */
  .bh-icon-btn:has(+ .bh-menu:popover-open),
  .bh-btn:has(+ .bh-menu:popover-open) {
    background: var(--bh-surface-3);
    color: var(--bh-chrome);
  }

  .bh-menu__item {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    min-block-size: var(--tap-min);
    padding-inline: var(--sp-3);
    border-radius: var(--r-sm);
    color: var(--bh-chrome);
    font-size: var(--fs-body-s);
    font-weight: 500;
    text-decoration: none;
    transition: background var(--dur-instant) var(--ease-human);
  }

  .bh-menu__item:hover { background: var(--bh-surface-2); color: var(--bh-chrome); }

  .bh-menu__item[aria-current="true"] { font-weight: 700; }

  /* The check that marks the current choice. Present as an element rather than
     implied by weight, so the state is not carried by typography alone. */
  .bh-menu__item[aria-current="true"] .bh-icon { color: var(--bh-ember); }

  .bh-menu__sep {
    block-size: 1px;
    margin-block: var(--sp-2);
    border: 0;
    background: var(--bh-hairline);
  }

  @starting-style {
    .bh-menu:popover-open {
      opacity: 0;
      translate: 0 calc(var(--sp-1) * -1);
      scale: 0.98;
    }
  }

  .bh-menu:popover-open {
    opacity: 1;
    translate: 0 0;
    scale: 1;
    transition:
      opacity var(--dur-fast) var(--ease-enter),
      translate var(--dur-fast) var(--ease-enter),
      scale var(--dur-fast) var(--ease-spring),
      overlay var(--dur-fast) allow-discrete,
      display var(--dur-fast) allow-discrete;
  }

  @supports (corner-shape: squircle) {
    .bh-menu { corner-shape: squircle; }
  }

  /* -------------------------------------------------------------------------
     Toast.

     A live region that already exists in the DOM, so announcing a message means
     putting text into it rather than inserting a container. A live region
     inserted at the same moment as its content is frequently not announced at
     all, which is the single most common way toasts fail for screen readers.

     role="status" and aria-live="polite" belong in the markup, not here; this
     file only places and paints it.
     ------------------------------------------------------------------------- */
  .bh-toast-region {
    position: fixed;
    z-index: var(--z-toast);
    inset-block-end: calc(var(--sp-5) + env(safe-area-inset-bottom));
    inset-inline: var(--sp-4);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-2);
    /* The region spans the viewport so its children can centre, but it must not
       intercept clicks meant for the page underneath. Individual toasts opt
       back in. */
    pointer-events: none;
  }

  .bh-toast {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    /* A toast is a <p>, so it inherits the paragraph bottom margin from base.css
       and would push the stack apart by a full block step. */
    margin: 0;
    max-inline-size: min(28rem, 100%);
    min-block-size: var(--tap-min);
    padding-inline: var(--sp-4);
    padding-block: var(--sp-2);
    border: 1px solid var(--bh-hairline);
    border-radius: var(--r-full);
    background: var(--bh-vapor);
    color: var(--bh-chrome);
    font-size: var(--fs-body-s);
    font-weight: 500;
    box-shadow: var(--elev-2);
    pointer-events: auto;
  }

  .bh-toast--ok .bh-icon { color: var(--bh-aurora); }
  .bh-toast--bad .bh-icon { color: var(--bh-crimson); }

  @starting-style {
    .bh-toast { opacity: 0; translate: 0 var(--sp-4); }
  }

  .bh-toast {
    opacity: 1;
    translate: 0 0;
    transition:
      opacity var(--dur-base) var(--ease-enter),
      translate var(--dur-base) var(--ease-spring);
  }

  /* Tab bar and toasts share the bottom edge, so on a tabbed shell the toast
     stack is lifted clear of it rather than landing on top of navigation. */
  .bh-shell--tabbed .bh-toast-region {
    inset-block-end: calc(var(--sp-9) + env(safe-area-inset-bottom));
  }

  @media (min-width: 48rem) {
    .bh-shell--tabbed .bh-toast-region {
      inset-block-end: calc(var(--sp-5) + env(safe-area-inset-bottom));
    }
  }

  /* -------------------------------------------------------------------------
     Skeleton.

     Shown while a server partial is in flight. Deliberately a shimmer of the
     surface ramp and not a spinner: a skeleton says "this shape is arriving",
     which is information, where a spinner only says "wait".

     Reduced motion is handled by the global duration reset in tokens.css, which
     collapses the animation rather than leaving a pulsing block on screen for
     someone who asked for stillness.
     ------------------------------------------------------------------------- */
  .bh-skeleton {
    border-radius: var(--r-sm);
    background: linear-gradient(
      90deg,
      var(--bh-surface-1) 0%,
      var(--bh-surface-2) 50%,
      var(--bh-surface-1) 100%);
    background-size: 200% 100%;
    animation: bh-shimmer var(--dur-ceremony) var(--ease-human) infinite;
  }

  @keyframes bh-shimmer {
    to { background-position: -200% 0; }
  }

  .bh-skeleton__line {
    block-size: var(--sp-3);
    margin-block-end: var(--sp-2);
  }

  .bh-skeleton__line--short { inline-size: 40%; }
  .bh-skeleton__line--medium { inline-size: 70%; }

  /* -------------------------------------------------------------------------
     Empty state.

     Replaces the bare sentence that every surface grew its own version of
     (.lf-empty, .ad-empty, .one-gap, .home-gap). Three parts, and the third is
     the one that was always missing: a title that says what is absent, a line
     that says why, and an action that gets the reader out of the dead end.

     An empty state with no way forward is the Invalid magic-link page, which is
     exactly the screen this component exists to stop happening again.
     ------------------------------------------------------------------------- */
  .bh-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-3);
    max-inline-size: 34rem;
    margin-inline: auto;
    padding-block: var(--sp-8);
    text-align: center;
  }

  /* -------------------------------------------------------------------------
     The figure.

     Line art rather than an enlarged interface icon, and the weight is the
     reason it is a separate component. A 24-grid icon at 1.5 is a confident
     mark; the same proportion blown up to 90px is a heavy cartoon. Here the
     stroke stays at 1.5 over a 120x90 viewBox, so the drawing reads as a
     hairline sketch at the size the page needs.

     Titanium-dim, so the figure never competes with the sentence beside it. An
     empty state is a place where a reader needs to be told something, and the
     drawing's job is to set the tone, not to be read.
     ------------------------------------------------------------------------- */
  .bh-figure {
    display: block;
    inline-size: auto;
    block-size: var(--bh-figure-size, 5.625rem);
    color: var(--bh-titanium-dim);
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
  }

  /* Inside an empty state the figure is set on the surface's own rhythm and
     given a little air, because it is the first thing in the block and sitting
     it directly on the heading makes the heading look like a caption. */
  .bh-empty__figure {
    margin-block-end: var(--sp-1);
  }

  /* A dense surface - the console's queue, a table cell, a card - takes the
     small rendition and gives back the vertical room. The figures survive the
     reduction: nothing in them is finer than the gap between two ledger ticks. */
  .bh-empty--tight {
    padding-block: var(--sp-5);
  }

  .bh-empty--tight .bh-empty__figure {
    --bh-figure-size: 3.5rem;
  }

  .bh-empty__title {
    margin: 0;
    font-size: var(--fs-h3);
  }

  .bh-empty__body {
    margin: 0;
    color: var(--bh-titanium);
    text-wrap: pretty;
  }

  .bh-empty__actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--sp-3);
    margin-block-start: var(--sp-2);
  }
}
