/* ============================================================================
 * searchbar — a detached mark and a card you type in, with a panel of results
 * ============================================================================
 *
 * "Or just tell me what you are looking for." It goes wherever a host wants a
 * search: `topbar` is the shelf it ships beside, but it asks for nothing from
 * that shelf beyond two numbers it can default.
 *
 * ---- Using it --------------------------------------------------------------
 *
 *   <link rel="stylesheet" href="/shared/components/searchbar/searchbar.css">
 *   <!--# include virtual="/shared/components/searchbar/searchbar.html" -->
 *
 * The include emits the control and its own script; the script makes the scrim.
 *
 * ---- What a host can say ---------------------------------------------------
 *
 *   --searchbar-h      how tall the control is. Default 36px.
 *   --searchbar-drop   where the results panel starts, measured from the top of
 *                      the control. Default: just under it.
 *
 * A host with chrome of its own sets --searchbar-drop so the panel clears that
 * chrome rather than opening across it — see topbar.css, which hands down its
 * own height. Both are defaulted, so a bare host needs to say nothing.
 *
 * ---- Layer -----------------------------------------------------------------
 *
 * `component`, as every shared component is. The order is declared once in
 * brand.css (brand, site, component, page), so this outranks a site's own
 * layout and is outranked by the page hosting it, wherever the <link> sits.
 * ========================================================================== */
@layer component {

/* ── The search control ───────────────────────────────────────────────────────
 * A detached mark and a card you type in: the anatomy of a .docs-list row,
 * because that pair already reads as Drift — a square that says what the thing
 * is, and a bordered card that holds the content.
 *
 * The mark is INK-filled where a list row's is white. A list row's square labels
 * a fact and must stay quiet next to seven others; this one is a control's
 * business end, and ink-on-white is how this brand marks the active object — the
 * column's ground, the list title's bar, the primary button.
 *
 * Nothing MOVES on focus. A host tunes the control's height to line it up with
 * whatever sits beside it, and a press animation would spend that alignment on
 * a flourish. What changes is the LAYER: both halves take the results panel's
 * own 4px 4px ink shadow, so the control and the panel dropping out of it read
 * as one floating object instead of a field and an unrelated menu. Shadows cost
 * no layout, so the alignment holds exactly. */
.searchbar {
  /* THE TWO NUMBERS A HOST MAY SET, defaulted so it need not. On the control
     itself rather than :root, so two searchbars on one page can differ. */
  --searchbar-h: 36px;
  --searchbar-drop: calc(100% + 8px);
  position: relative;
  display: flex;
  /* Both halves state --searchbar-h, which IS this box's content height, so
     centring and stretching land in the same place. Centring is the safer of
     the two: it degrades to a centred control rather than a distorted one if
     the host is ever given a height the token does not describe. */
  align-items: center;
  /* The same gap a .docs-list row puts between its square and its card. */
  gap: 8px;
  /* Takes the room it needs up to a cap: a search field as wide as a 1600px
     monitor reads as a text editor. min-width:0 because a flex item's default
     `min-width: auto` refuses to shrink below its content. */
  flex: 1;
  min-width: 0;
  max-width: 520px;
  /* Above the bar's own dimmer. Stated unconditionally: when nothing is dimming
     there is no other positioned child to sit above, so it costs nothing, and a
     z-index that appears only under a state class is one someone has to reason
     about. */
  z-index: 2;
}

/* The mark, which is also the field's <label>: clicking the square focuses the
   input, so it is an affordance rather than an ornament. cursor: text says so
   before the click. */
.searchbar-mark {
  flex: none;
  /* ZERO, against brand.css's `label { margin-bottom: 4px }`. That margin is the
     gap under a label STACKED above its input, which is what a label is
     everywhere else in this brand. This one sits beside the field instead, so
     the margin is a caption's spacing on something that is not a caption — and
     as a flex item it lands in the layout: 4px shorter under `stretch`, 2px
     above centre under `center`. */
  margin: 0;
  /* Stated on BOTH axes from the bar's own content height, so the square is a
     square and is exactly as tall as the field beside it. Deriving the width
     from the height with `aspect-ratio` instead asks the browser to transfer one
     axis to the other across two levels of flex stretch, and where that transfer
     does not happen the box collapses to the width of its glyph. */
  width: var(--searchbar-h);
  height: var(--searchbar-h);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--ink);
  border: 2px solid var(--ink);
  color: var(--panel);
  cursor: text;
}
.searchbar-mark svg {
  width: 16px;
  height: 16px;
  fill: currentColor;
}

/* The card you type in. It is the positioning ground for the shortcut hint: an
   <input> takes no children, so the only way a glyph sits inside the box is to
   lay it over one. */
.searchbar-field {
  position: relative;
  flex: 1;
  min-width: 0;
  /* The same token as the mark, so the two cannot disagree — and it gives the
     input below a definite parent height to take its 100% from. Left to stretch,
     that percentage resolves against an auto height and the input falls back to
     the height of one line of text. */
  height: var(--searchbar-h);
}

/* BRAND.html → Forms: "Inputs are cards you can type in: white fill, ink border,
   zero radius." Weight 300 is stated because browsers default form controls to
   400, and 400 is a weight this brand does not have. */
.searchbar input {
  display: block;
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  font-family: inherit;
  font-size: 14px;
  font-weight: 300;
  line-height: 1.5;
  /* Left is the card padding a .docs-list row uses; right clears the hint —
     a 20px cap sitting 10px in, plus room to breathe. */
  padding: 0 40px 0 14px;
  color: var(--ink);
  background: var(--panel);
  border: 2px solid var(--ink);
  /* Safari paints type="search" as a rounded capsule with its own inner
     spacing — the one shape this brand does not have. */
  -webkit-appearance: none;
  appearance: none;
  border-radius: 0;
}
.searchbar input::-webkit-search-decoration,
.searchbar input::-webkit-search-cancel-button { -webkit-appearance: none; }
.searchbar input::placeholder { color: var(--muted); opacity: 1; }
/* The brand's purple ring, inset so it cannot shift the bar's contents. Stated
   on :focus rather than :focus-visible — a search box is the one control where a
   mouse click and a keyboard tab mean the same thing. */
.searchbar input:focus {
  outline: 2px solid var(--brand-2);
  outline-offset: -2px;
}

/* Onto the panel's layer. :focus-within rather than :focus, because the control
   is two elements and only one of them can hold focus — the pair has to lift
   together or it stops being one object. */
.searchbar:focus-within .searchbar-mark,
.searchbar:focus-within .searchbar-field {
  box-shadow: 4px 4px 0 var(--ink);
}
/* The mark answers the field's ring in the same colour. Purple is this brand's
   focus state, not a second accent. */
.searchbar:focus-within .searchbar-mark {
  background: var(--brand-2);
  border-color: var(--brand-2);
}

/* The keyboard hint. A label on the field, not a button — pointer-events off, so
   a click over it still lands in the input underneath. It goes on focus, because
   by then it has done its job. */
.searchbar-key {
  position: absolute;
  top: 50%;
  right: 10px;
  transform: translateY(-50%);
  pointer-events: none;
  font-family: inherit;
  /* ONE glyph, so the cap is sized rather than left to hug it: "/" is a thin
     character and a shrink-to-fit box around it reads as a sliver rather than as
     a key. Square-ish and centred, with letter-spacing cleared — tracking puts
     its space AFTER the last glyph, which pushes a single centred character
     visibly off to the left. */
  min-width: 20px;
  text-align: center;
  font-size: 12px;
  line-height: 16px;
  font-weight: 600;
  letter-spacing: normal;
  color: var(--muted);
  background: var(--bg);
  border: 2px solid var(--ink-subtle);
  padding: 0 4px;
}
.searchbar input:focus ~ .searchbar-key { display: none; }

/* THE FLOATING PANEL (BRAND.html → "the floating panel"): --panel ground, 2px
 * ink border, a STATIC 4px 4px diagonal ink shadow. The one place a horizontal
 * shadow offset is correct, and never animated.
 *
 * Absolute against .searchbar, so it hangs off the field and needs no script
 * to place it. Nothing clips it: the bar sets no overflow, and a fixed ancestor
 * does not clip its descendants.
 *
 * WHERE IT STARTS IS THE HOST'S TO SAY. `top: 100%` — the default — is the
 * control's own bottom edge, which is right when the control is the whole of
 * the chrome. Inside a bar it is not: the control sits inset from the bar's top,
 * so its bottom edge is well INSIDE the bar and the panel would open across the
 * bar's underline instead of below it. A host with chrome of its own therefore
 * hands down --searchbar-drop; topbar.css derives it from the bar's height, so
 * the panel follows the bar whatever height it is tuned to. */
.searchbar-results {
  display: none;
  position: absolute;
  top: var(--searchbar-drop);
  left: 0;
  z-index: 1002;
  width: 100%;
  min-width: min(460px, 76vw);
  max-height: min(66vh, 480px);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--panel);
  border: 2px solid var(--ink);
  box-shadow: 4px 4px 0 var(--ink);
}
.searchbar-results.open { display: block; }

/* ---- The scrim --------------------------------------------------------------
 * While a search is open the page behind it dims, so the only lit objects are
 * the field and the panel of results hanging off it.
 *
 * INK at 50%, never rgba black. The palette has one dark and a dimmed page
 * should read as the same ink everything else is drawn in. And no blur: a
 * frosted backdrop is the one dimming idiom this brand does not own.
 *
 * The stacking is the whole trick, and it is not obvious.
 *
 * `position: fixed` CREATES A STACKING CONTEXT BY ITSELF, whatever its z-index.
 * So a fixed host always forms one, and nothing inside it can ever be painted
 * past something layered above that context — no z-index on the field can lift
 * it out from under a scrim covering that host. Lowering the host's z-index does
 * not help either: the context is the fixed positioning, not the number.
 *
 * Such a host therefore goes ABOVE the scrim while searching, and dims its own
 * ground from the inside with ::after — a dimmer that lives in the host's own
 * context, where the field can out-rank it. Two dimmers, one appearance: the
 * page darkens under the scrim, the host darkens under its own pseudo-element,
 * and the only thing above either is the search control.
 *
 * THIS IS A RULE ABOUT THE HOST, and it is stated here rather than in topbar.css
 * because it is the searchbar's dimming that needs it — a shelf holding a row of
 * buttons has no scrim to climb over. Any other fixed host that wants the same
 * needs the same two declarations; `:has()` keys them off the control's own
 * state, so nothing has to reach up and set a class on its parent. */
.searchbar-scrim {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: #1b284480;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  /* visibility rides the same duration so the scrim stays hittable until it has
     finished fading, rather than vanishing from under the pointer mid-fade. */
  transition: opacity .12s ease, visibility .12s;
}
.searchbar-scrim.open {
  opacity: 1;
  visibility: visible;
  /* Hitting the scrim closes the search: the document's click handler already
     treats anything outside the field and the panel as a dismissal. */
  pointer-events: auto;
}
/* Above the scrim, with the whole bar, because the bar cannot hand its children
   out of its own stacking context. */
.topbar:has(.searchbar.searching) { z-index: 1001; }
/* The bar's own dimmer, matching the scrim exactly so the two read as one wash
   across the page. Absolute, so it covers the bar's white ground and the triad
   along its bottom edge — everything in the bar except the control. */
.topbar:has(.searchbar.searching)::after {
  content: "";
  position: absolute;
  inset: 0;
  background: #1b284480;
  z-index: 1;
}

/* ---- Inside the results panel ---------------------------------------------- */

/* The group heading — "Pages", then "In the text". Sticky, so the reader can
   still tell which half of the list they are scrolling through. */
.searchbar-result-group {
  position: sticky;
  top: 0;
  background: var(--bg);
  border-bottom: 2px solid var(--ink);
  padding: 5px 10px;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}
.searchbar-results a {
  display: block;
  padding: 8px 10px;
  text-decoration: none;
  color: var(--ink);
  border-bottom: 2px solid var(--bg);
}
.searchbar-results a:last-child { border-bottom: none; }
/* ONE highlight, and exactly one row carries it at all times — the first hit is
   selected the moment results render, and the mouse moves that same cursor
   rather than painting a second highlight of its own (topbar.js). So arrowing
   and pointing can never disagree about what Enter will open.
   Canvas green because this row is the OUTCOME of the search — the same sense
   the palette already gives it for "done". */
.searchbar-results a.cursor { background: var(--brand-3); }
/* Ink, not --muted, for everything inside the selected row. --muted on this
   green measures 2.1:1 and is unreadable; ink measures 5.8:1 and clears AA. The
   label gives up its step down in COLOUR on this one row and keeps its step down
   in size — the green already says which row this is. */
.searchbar-results a.cursor .searchbar-result-section,
.searchbar-results a.cursor .searchbar-result-snippet { color: var(--ink); }
.searchbar-result-section {
  display: block;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}
.searchbar-result-title {
  display: block;
  font-size: 14px;
  font-weight: 600;
  overflow-wrap: break-word;
}
/* The matched line, trimmed to a window around the hit. Two lines maximum: the
   snippet confirms the match, it is not there to be read instead of the page. */
.searchbar-result-snippet {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin-top: 2px;
  font-size: 12px;
  font-weight: 300;
  line-height: 1.45;
  color: var(--muted);
}
/* brand-1 is already the "you are here" accent on a Drift sidebar — the active
   page and the active TOC entry both carry it — so the highlight is the same
   signal rather than a sixth colour. Ink on amber, not white: the badges get
   away with white at weight 600, and this is a 12px snippet at 300. */
.searchbar-results mark {
  background: var(--brand-1);
  color: var(--ink);
  font-weight: 600;
  padding: 0 1px;
}
.searchbar-empty,
.searchbar-more {
  padding: 9px 10px;
  font-size: 12px;
  color: var(--muted);
}
.searchbar-more { border-top: 2px solid var(--bg); }

/* An accessible name for a control whose only visible content is a glyph: the
   search mark. Positioned out of sight rather than hidden, because display:none
   and visibility:hidden would take it from a screen reader too. */
.searchbar .sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

} /* @layer component */
