/* ===========================================================================
   Controls — icons, fields, segments, chips, switches, sliders.

   The primitives every form and filter in the product is built from. Before
   this file the apps each grew their own ".one-form__input" and ".lf-form__input"
   with a 1px border and nothing else: no error state, no hint, no disabled, no
   loading. A pattern without an error state is not finished, per
   docs/design/03-ui-patterns.md.

   Reference: docs/design/03-ui-patterns.md
              docs/rules/11-accessibility.md
   =========================================================================== */

@layer components {

  /* -------------------------------------------------------------------------
     Icon.

     Presentation lives here rather than on the sprite's symbols, because host
     CSS cannot select into the shadow tree an external <use> creates while
     inherited SVG properties cross it freely. That is what lets the active tab
     thicken its stroke and an error icon pick up Crimson from its container.
     ------------------------------------------------------------------------- */
  .bh-icon {
    --bh-icon-size: 20px;

    inline-size: var(--bh-icon-size);
    block-size: var(--bh-icon-size);
    flex: none;
    display: inline-block;
    vertical-align: middle;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
  }

  /* One continuous rotation, and the only looping animation in the system. A
     spinner is the one case where a loop is honest: it says "still working",
     and it stops when the work does. */
  .bh-icon--spin { animation: bh-spin var(--dur-ceremony) linear infinite; }

  @keyframes bh-spin {
    to { transform: rotate(1turn); }
  }

  /* -------------------------------------------------------------------------
     Icon button. A square tap target with no label, so it always carries one
     through <bh-icon label="..."> or an aria-label on the button.
     ------------------------------------------------------------------------- */
  .bh-icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: var(--tap-min);
    block-size: var(--tap-min);
    padding: 0;
    border: 1px solid transparent;
    border-radius: var(--r-full);
    background: transparent;
    color: var(--bh-titanium);
    transition:
      background var(--dur-instant) var(--ease-human),
      color var(--dur-instant) var(--ease-human),
      transform var(--dur-instant) var(--ease-spring);
  }

  .bh-icon-btn:hover { background: var(--bh-surface-1); color: var(--bh-chrome); }
  .bh-icon-btn:active { transform: scale(0.92); }

  /* Pressed, for a button that manages aria-expanded itself. A popover invoker
     does not: popovertarget gives the button an implicit expanded state for
     assistive tech but never writes the attribute, so surfaces.css carries a
     second rule keyed on :has(+ .bh-menu:popover-open) for that case. Both land
     on the same two declarations on purpose. */
  .bh-icon-btn[aria-expanded="true"] {
    background: var(--bh-surface-3);
    color: var(--bh-chrome);
  }

  .bh-icon-btn:disabled { opacity: 0.4; cursor: not-allowed; }

  /* For a button that sits inside a dense row of data rather than in a toolbar.
     The visible disc shrinks; the hit area does not. A 44px control repeated down
     fourteen rows of the evidence grid would set the row height and turn a table
     into a list of buttons, and dropping to a 24px target instead would fail
     docs/rules/11-accessibility.md. So the ::after pad keeps --tap-min while the
     paint stays small - which is what the platform's own compact controls do. */
  .bh-icon-btn--tight {
    --bh-icon-size: 16px;

    position: relative;
    inline-size: var(--sp-6);
    block-size: var(--sp-6);
  }

  .bh-icon-btn--tight::after {
    content: "";
    position: absolute;
    inset: calc((var(--tap-min) - var(--sp-6)) / -2);
  }

  @supports (corner-shape: squircle) {
    .bh-icon-btn { corner-shape: squircle; }
  }

  /* -------------------------------------------------------------------------
     Copy button.

     Hidden until bh/copy.js marks it live, which is backwards from the usual
     enhancement direction and is correct for this one control: it does nothing
     except talk to the Clipboard API, so a rendered button that never attached is
     a control that lies. That covers more than scripting being off - the
     Clipboard API does not exist in an insecure context, so the same page over
     plain HTTP would otherwise offer buttons that quietly fail.
     ------------------------------------------------------------------------- */
  [data-bh-copy] { display: none; }

  [data-bh-copy][data-bh-copy-live] { display: inline-flex; }

  /* -------------------------------------------------------------------------
     Field.

     Label, control, hint and error in one block, and the error is native.

     :user-invalid is the whole point of this component. It fires only after the
     reader has actually interacted with the field, which is the difference
     between helpful and hostile: :invalid alone paints every required field red
     the instant the page loads, before anyone has typed anything. Getting that
     right needs no JavaScript at all, which is why this is CSS and not a
     validation module.
     ------------------------------------------------------------------------- */
  .bh-field {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
  }

  .bh-field__label {
    color: var(--bh-titanium);
    font-size: var(--fs-body-s);
    font-weight: 600;
  }

  .bh-field__control {
    /* Both declarations are needed and they fight each other by default. Fields
       must fill their column, and field-sizing: content (inherited from a
       select rule in base.css if it is ever widened) collapses an empty input
       into a sliver, which is exactly what happened to the search box. */
    inline-size: 100%;
    field-sizing: fixed;
    min-block-size: var(--tap-min);
    padding-inline: var(--sp-4);
    padding-block: var(--sp-3);
    border: 1px solid var(--bh-hairline);
    border-radius: var(--r-md);
    background: var(--bh-surface-1);
    color: var(--bh-chrome);
    transition:
      border-color var(--dur-instant) var(--ease-human),
      background var(--dur-instant) var(--ease-human);
  }

  .bh-field__control::placeholder { color: var(--bh-state-dim); }

  .bh-field__control:hover { border-color: var(--bh-titanium-dim); }

  .bh-field__control:focus-visible {
    border-color: var(--bh-ember);
    background: var(--bh-surface-2);
  }

  .bh-field__control:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    background: var(--bh-surface-0);
  }

  textarea.bh-field__control {
    min-block-size: var(--sp-10);
    resize: vertical;
    line-height: var(--lh-ui);
  }

  /* A hint is always present tense and always says what a valid value looks
     like. It is not a place for a warning. */
  .bh-field__hint {
    color: var(--bh-titanium);
    font-size: var(--fs-caption);
  }

  /* The error, which the browser reveals rather than the server. Colour plus an
     icon plus words: never colour alone. */
  .bh-field__error {
    display: none;
    align-items: center;
    gap: var(--sp-2);
    color: var(--bh-crimson);
    font-size: var(--fs-caption);
    font-weight: 500;
  }

  .bh-field:has(.bh-field__control:user-invalid) .bh-field__control {
    border-color: var(--bh-crimson);
  }

  .bh-field:has(.bh-field__control:user-invalid) .bh-field__error {
    display: flex;
  }

  /* A server-side failure has no :user-invalid to hang off, because the value
     round-tripped and the browser considers it fine. data-invalid is how the
     server says otherwise, and it drives the identical presentation so the two
     kinds of error look like one thing to the reader. */
  .bh-field[data-invalid] .bh-field__control { border-color: var(--bh-crimson); }
  .bh-field[data-invalid] .bh-field__error { display: flex; }

  /* Live availability feedback, as used by the handle field on Join. Both states
     pair their colour with an icon in the markup. */
  .bh-field__status {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    font-size: var(--fs-caption);
    font-weight: 500;
  }

  .bh-field__status--ok { color: var(--bh-aurora); }
  .bh-field__status--taken { color: var(--bh-crimson); }
  .bh-field__status--busy { color: var(--bh-titanium); }

  /* The canonical spelling the server settled on, echoed back beside the verdict.
     Data, so the evidence voice: it is how somebody who typed a capital letter
     finds out what name they are actually about to be given. */
  .bh-field__status-handle {
    color: var(--bh-titanium);
    font-family: var(--ff-evidence);
    font-weight: 400;
  }

  /* A counter that only appears when it starts to matter. A character count
     visible from the first keystroke reads as a limit being enforced; one that
     appears near the ceiling reads as help. */
  .bh-field__count {
    align-self: flex-end;
    color: var(--bh-titanium);
    font-family: var(--ff-evidence);
    font-size: var(--fs-micro);
    font-variant-numeric: tabular-nums;
  }

  .bh-field__count[data-near-limit] { color: var(--bh-amber); }
  .bh-field__count[data-over-limit] { color: var(--bh-crimson); }

  /* -------------------------------------------------------------------------
     Segmented control.

     Built on real radio inputs, so it submits without JavaScript, it is a real
     radio group to assistive tech, and arrow-key navigation comes from the
     platform. The radio itself is hidden and its label is the visible segment.

     This replaces the vertical radio list plus Apply button that the Spectrum
     Scrubber used to be. Same semantics, a fraction of the vertical space, and
     it reads as one control rather than four decisions.
     ------------------------------------------------------------------------- */
  .bh-segment {
    display: inline-flex;
    gap: var(--sp-05);
    padding: var(--sp-05);
    border: 1px solid var(--bh-hairline);
    border-radius: var(--r-full);
    background: var(--bh-surface-1);
  }

  .bh-segment--fill { display: flex; }
  .bh-segment--fill .bh-segment__opt { flex: 1; }

  .bh-segment__input {
    /* Not display:none and not visibility:hidden: either one takes the input out
       of the accessibility tree and off the focus order, which would throw away
       the keyboard behaviour this component exists to inherit. */
    position: absolute;
    opacity: 0;
    pointer-events: none;
  }

  .bh-segment__opt {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    min-block-size: var(--tap-min);
    padding-inline: var(--sp-4);
    border-radius: var(--r-full);
    color: var(--bh-titanium);
    font-size: var(--fs-body-s);
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
    transition:
      background var(--dur-instant) var(--ease-human),
      color var(--dur-instant) var(--ease-human),
      transform var(--dur-instant) var(--ease-spring);
  }

  .bh-segment__opt:hover { color: var(--bh-chrome); }
  .bh-segment__opt:active { transform: scale(0.96); }

  .bh-segment__input:checked + .bh-segment__opt {
    background: var(--bh-surface-3);
    color: var(--bh-chrome);
    box-shadow: var(--elev-1);
  }

  /* The focus ring has to land on the label, because the input it belongs to is
     transparent and one pixel wide. Without this the whole control is invisible
     to keyboard users while remaining perfectly operable, which is the worst
     combination. */
  .bh-segment__input:focus-visible + .bh-segment__opt {
    outline: 2px solid var(--bh-ember);
    outline-offset: 2px;
  }

  @supports (corner-shape: squircle) {
    .bh-segment,
    .bh-segment__opt { corner-shape: squircle; }
  }

  /* -------------------------------------------------------------------------
     Spectrum slider — the Scrubber's rail.

     The one place a full-range Human Spectrum gradient is allowed to be a
     background, because it is the control's scale and not decoration. The track
     carries the whole ramp so the reader can see that raising the threshold
     moves along humanity rather than along an abstract number.
     ------------------------------------------------------------------------- */
  .bh-slider {
    inline-size: 100%;
    min-block-size: var(--tap-min);
    background: transparent;
    accent-color: var(--bh-ember);
  }

  .bh-slider::-webkit-slider-runnable-track {
    block-size: var(--sp-2);
    border-radius: var(--r-full);
    background: var(--bh-spectrum-full);
  }

  .bh-slider::-moz-range-track {
    block-size: var(--sp-2);
    border-radius: var(--r-full);
    background: var(--bh-spectrum-full);
  }

  .bh-slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    inline-size: var(--sp-5);
    block-size: var(--sp-5);
    /* Centres a 24px thumb on an 8px track. calc(), not "-var(...)", which is
       not valid CSS and fails silently by dropping the whole declaration. */
    margin-block-start: calc(var(--sp-2) * -1);
    border: 2px solid var(--bh-surface-0);
    border-radius: var(--r-full);
    background: var(--bh-chrome);
    box-shadow: var(--elev-2);
  }

  .bh-slider::-moz-range-thumb {
    inline-size: var(--sp-5);
    block-size: var(--sp-5);
    border: 2px solid var(--bh-surface-0);
    border-radius: var(--r-full);
    background: var(--bh-chrome);
    box-shadow: var(--elev-2);
  }

  /* -------------------------------------------------------------------------
     Chip.

     A filter that is also a link, so filtering survives with JavaScript off and
     every filtered view has a shareable URL. Pressed state is carried by
     aria-pressed on a button or aria-current on a link, never by class alone.
     ------------------------------------------------------------------------- */
  .bh-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    min-block-size: var(--sp-6);
    padding-inline: var(--sp-4);
    padding-block: var(--sp-1);
    border: 1px solid var(--bh-hairline);
    border-radius: var(--r-full);
    background: var(--bh-surface-1);
    color: var(--bh-titanium);
    font-size: var(--fs-caption);
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
    transition:
      background var(--dur-instant) var(--ease-human),
      color var(--dur-instant) var(--ease-human),
      transform var(--dur-instant) var(--ease-spring);
  }

  .bh-chip:hover { color: var(--bh-chrome); background: var(--bh-surface-2); }
  .bh-chip:active { transform: scale(0.95); }

  .bh-chip[aria-pressed="true"],
  .bh-chip[aria-current="true"] {
    border-color: transparent;
    background: var(--bh-chrome);
    color: var(--bh-surface-0);
  }

  /* The Honest Disclosure Chip. Amber, a triangle icon, and words: three
     signals, because docs/design/03-ui-patterns.md is explicit that this must
     never be colour alone. It is deliberately not styled as a penalty - a
     declared disclosure keeps the trust level, and the UI should not shout. */
  .bh-chip--disclosure {
    border-color: var(--bh-amber);
    background: transparent;
    color: var(--bh-amber);
    cursor: default;
  }
  .bh-chip--disclosure:hover {
    background: transparent;
    color: var(--bh-amber);
  }

  .bh-chip__row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
  }

  @supports (corner-shape: squircle) {
    .bh-chip { corner-shape: squircle; }
  }

  /* -------------------------------------------------------------------------
     Switch. A checkbox that looks like a physical toggle, still a checkbox.
     ------------------------------------------------------------------------- */
  .bh-switch {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-3);
    min-block-size: var(--tap-min);
    cursor: pointer;
  }

  .bh-switch__input {
    appearance: none;
    position: relative;
    inline-size: var(--sp-7);
    block-size: var(--sp-5);
    flex: none;
    margin: 0;
    border-radius: var(--r-full);
    background: var(--bh-surface-3);
    transition: background var(--dur-fast) var(--ease-human);
  }

  .bh-switch__input::after {
    content: "";
    position: absolute;
    inset-block-start: var(--sp-05);
    inset-inline-start: var(--sp-05);
    inline-size: var(--sp-4);
    block-size: var(--sp-4);
    border-radius: var(--r-full);
    background: var(--bh-surface-0);
    box-shadow: var(--elev-1);
    transition: translate var(--dur-fast) var(--ease-spring);
  }

  .bh-switch__input:checked { background: var(--bh-ember); }
  .bh-switch__input:checked::after { translate: var(--sp-5) 0; }

  /* -------------------------------------------------------------------------
     Default avatar — initials on a stop of the Human Spectrum.

     Replaces the grey silhouette, and the reason is not decoration: a feed of
     identical grey circles reads as empty, and a feed of spectrum discs reads as
     populated by people. The stop is picked from the account id on the server,
     so it is stable for a given account forever.

     The ramp here is --bh-avatar-*, a five-stop window, not the full seven. The
     shallow end of the spectrum cannot carry a label at 4.5:1; see the note in
     tokens.css.
     ------------------------------------------------------------------------- */
  .bh-avatar {
    --bh-avatar-size: 40px;

    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: var(--bh-avatar-size);
    block-size: var(--bh-avatar-size);
    flex: none;
    border-radius: var(--r-full);
    background: var(--bh-avatar-tone, var(--bh-avatar-3));
    color: var(--bh-on-tone);
    font-size: var(--fs-body-s);
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    user-select: none;
  }

  .bh-avatar--tone-1 { --bh-avatar-tone: var(--bh-avatar-1); }
  .bh-avatar--tone-2 { --bh-avatar-tone: var(--bh-avatar-2); }
  .bh-avatar--tone-3 { --bh-avatar-tone: var(--bh-avatar-3); }
  .bh-avatar--tone-4 { --bh-avatar-tone: var(--bh-avatar-4); }
  .bh-avatar--tone-5 { --bh-avatar-tone: var(--bh-avatar-5); }

  /* -------------------------------------------------------------------------
     The Authorship HUD.

     Here rather than in an app because both editors show it, and a copy per app
     would eventually mean two instruments that report differently about the same
     thing. For this component that is not an inconsistency, it is a second answer
     to "what are you recording about me".

     Hidden until bh/hud.js marks it live. An instrument that renders its readouts
     as zeros because no script attached is not a degraded instrument, it is a false
     one, and this panel's entire purpose is that the measurement is honest.
     ------------------------------------------------------------------------- */
  .bh-hud { display: none; }

  .bh-hud[data-bh-hud-live] {
    display: grid;
    gap: var(--sp-3);
    padding: var(--sp-4);
    border: 1px solid var(--bh-hairline);
    border-radius: var(--r-lg);
    background: var(--bh-surface-1);
  }

  @supports (corner-shape: squircle) {
    .bh-hud { corner-shape: squircle; }
  }

  .bh-hud__head {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
  }

  .bh-hud__title {
    margin: 0;
    font-size: var(--fs-body-s);
    font-weight: 650;
  }

  .bh-hud__head .bh-icon-btn { margin-inline-start: auto; }

  /* Colour, a shape and a word. The dot on its own would be state carried by
     colour, and this is the control that has to be trusted most on the page. */
  .bh-hud__rec {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    margin: 0;
    color: var(--bh-crimson);
    font-size: var(--fs-micro);
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
  }

  .bh-hud__dot {
    inline-size: var(--sp-2);
    block-size: var(--sp-2);
    border-radius: var(--r-full);
    background: currentColor;
    /* The Heartbeat, at the one place in the product where a pulse is literally
       what is being reported. */
    animation: bh-hud-beat var(--dur-ceremony) var(--ease-human) infinite;
  }

  @keyframes bh-hud-beat {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.35; }
  }

  /* Paused is not "recording in another colour": the pulse stops, which is the
     signal that costs nothing to read and cannot be missed. */
  .bh-hud__rec[data-bh-paused] { color: var(--bh-titanium); }
  .bh-hud__rec[data-bh-paused] .bh-hud__dot { animation: none; }

  /* Cadence sparkline. Twelve bars, five seconds each, sixty seconds of window.
     Drawn as bars rather than on a canvas so it inherits the theme, needs no
     second rendering path, and costs nothing when the panel is hidden. */
  .bh-hud__spark {
    display: flex;
    align-items: flex-end;
    gap: var(--sp-05);
    block-size: var(--sp-6);
  }

  .bh-hud__bar {
    flex: 1;
    /* --bh-bar is 0..1 and comes from the script. The fallback is the idle
       baseline, so the trace is a flat line before the first keystroke rather than
       an empty box. */
    block-size: calc(100% * var(--bh-bar, 0.06));
    border-radius: var(--r-sm);
    background: var(--bh-ember);
    transition: block-size var(--dur-fast) var(--ease-human);
  }

  .bh-hud__readout {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
    gap: var(--sp-2) var(--sp-4);
    margin: 0;
  }

  .bh-hud__readout dt {
    color: var(--bh-titanium);
    font-size: var(--fs-micro);
    letter-spacing: 0.03em;
    text-transform: uppercase;
  }

  .bh-hud__readout dd {
    margin: 0;
    color: var(--bh-chrome);
    font-family: var(--ff-evidence);
    font-size: var(--fs-body-s);
    font-variant-numeric: tabular-nums;
  }

  /* An external paste is not an accusation, so it is amber and not crimson, and it
     is the count that changes colour rather than a warning appearing. */
  .bh-hud__readout dd[data-external] { color: var(--bh-amber); }

  .bh-hud__what {
    color: var(--bh-titanium);
    font-size: var(--fs-caption);
  }

  .bh-hud__what summary { cursor: pointer; }
  .bh-hud__what p { margin-block: var(--sp-2) 0; text-wrap: pretty; }
}
