/* ============================================================================
   FireBrief — the unified signed-in header
   ----------------------------------------------------------------------------
   One bar, shared by the four signed-in surfaces: the article viewer (`/`),
   Search (`/search`), Catch Up (`/catchup`) and Settings (`/settings`). Loaded
   by all of them alongside header.js, which owns the avatar, the menu and the
   scroll state.

   The design rules this file encodes:

   1. SMALL Y-HEIGHT. 56px on desktop, 52px on phones, 44px on short/landscape
      viewports. It is chrome, not a masthead — the big `.ftitle` lockups that
      Search and Catch Up used to open with are gone, and the section name now
      rides the wordmark instead ("FireBrief Search", "FireBrief Catch Up").

   2. TRANSPARENT AT REST. At scroll 0 there is no bar: no fill, no rule, just
      the mark, the wordmark and three quiet controls sitting in the page's top
      margin. The scrim + hairline fade in only once you have scrolled past it.
      On the reading surface this matters most — an opaque bar over an article
      is a permanent horizontal line competing with the first paragraph.

   3. IT GETS OUT OF THE WAY WHILE YOU READ. On the viewer only, scrolling down
      slides the header off; scrolling up brings it back. Search and Catch Up
      are scanning surfaces where persistent chrome is expected, so there it
      simply sticks.

   4. NO FLASH, NO REFLOW. The bar's height and every control's box is fixed in
      CSS, so the server-rendered signed-in shell paints the final layout on
      the first frame. Only the avatar's letter arrives later, into a box that
      was already the right size.

   Tokens come from Catch Up's vocabulary (the richest of the three) with
   `light-dark()` fallbacks, because the viewer's critical CSS defines a
   smaller set and header.css must not depend on load order to look right.
   ============================================================================ */

/* ------------------------------------------------------------------------- *
   THE SHARED MEASURE

   One column width for the whole signed-in app. The four surfaces used to be
   669px, 720px, 740px and 800px, which nobody notices until a single header
   sits on top of all of them -- then the bar changes width as you move between
   pages and the app stops feeling like one thing.

   720px outer, 22px gutters, so text spans 676px. On the reading surface that
   is ~70 characters at the desktop base size, inside the 60-75 range that is
   comfortable for continuous prose; the old 65ch was 669px, so the reader
   gains 7px of measure rather than being stretched to Search's old 800px.

   Every surface pins itself to this, including the header, so the flame sits
   directly above the first character of the page on all four.
 * ------------------------------------------------------------------------- */
:root {
  /* 780px outer, 24px gutters -> a 732px text column.
     
     Measured on the running app rather than guessed: the viewer's body text was
     16px in a 676px column, which is 85 characters per line. Every current
     source puts the comfortable range at 50-75 (Baymard, UXPin), with USWDS
     treating 90 as the outer limit of "readable" and 66 as the target for long
     text. So the column was not too narrow for the body -- the body type was
     too small for the column, and the page read as a thin ribbon of small text
     stranded in white space on a large display.
     
     Widening alone would have made that worse (752px at 16px is 94 CPL), so the
     desktop type scale went up with it. At 20px, 732px lands at ~73 CPL. */
  --app-measure: 780px;
  --app-gutter: 24px;
  /* The bar's height lives at :root, not on .fbh, because things OUTSIDE the
     header need it -- the loading overlay starts directly beneath the bar, and
     a hard-coded 56px there would misalign by 4px on phones and 12px on short
     viewports. */
  --app-header-h: 56px;

  /* ONE page background for the whole product.
     
     There were five: #ffffff/#0d0d0d on the marketing page, white/#121212 on
     the viewer and Search, #fbfbfa/#0b0b0c on Catch Up and Settings. Moving
     between them was a visible step, worst in dark mode where three different
     near-blacks were in play.
     
     Light is the warm off-white the card surfaces already used: their cards are
     pure white, so a pure white page would leave them border-only. Dark is
     #0d0d0d rather than #121212 because the cards are #161616 -- only four
     levels of separation at #121212, which is not enough to read as a card.
     
     landing.html cannot see this token (it loads none of the app's CSS), so it
     repeats the same two literals. tests/backgrounds.test.js fails if the two
     ever disagree. */
  --app-bg: light-dark(#fbfbfa, #0d0d0d);
}

@media (max-width: 620px) {
  :root { --app-gutter: 16px; --app-header-h: 52px; }
}

@media (max-height: 560px) {
  :root { --app-header-h: 44px; }
}

.fbh {
  /* ---- geometry ---- */
  --fbh-h: var(--app-header-h, 56px);
  --fbh-gutter: var(--app-gutter, 22px);
  --fbh-maxw: var(--app-measure, 720px);
  --fbh-tap: 36px;
  --fbh-avatar: 30px;

  /* ---- colour ---- */
  --fbh-fg: var(--color-text, light-dark(#0a0a0a, #f2f2f2));
  --fbh-dim: var(--color-text-secondary, light-dark(#5f6368, #9aa0a6));
  /* Icons are NOT --fbh-dim. At #5f6368 two hairline glyphs sat next to a
     filled avatar disc and the bar read light, light, VERY HEAVY -- the single
     biggest reason the right-hand cluster looked unbalanced. Still a step short
     of --fbh-fg, so the current-page state has somewhere to go. */
  --fbh-icon: light-dark(#3a3d41, #b9bdc3);
  --fbh-brand: var(--color-brand, #ff6b35);
  /* FireBrief blue, written out rather than taken from --color-primary. The
     viewer re-themes --color-primary from each article's favicon, so binding
     the header to it would repaint the Pro suffix and the focus ring a
     different colour on every story. */
  --fbh-accent: light-dark(#007bff, #66b0ff);
  --fbh-hairline: light-dark(rgba(0, 0, 0, 0.075), rgba(255, 255, 255, 0.085));
  --fbh-hover: light-dark(rgba(0, 0, 0, 0.055), rgba(255, 255, 255, 0.08));
  --fbh-press: light-dark(rgba(0, 0, 0, 0.095), rgba(255, 255, 255, 0.13));
  /* Per-page: the translucent fill has to be the page's own background, or the
     blur reads as a grey card floating on a tinted page. */
  /* The page's own colour at ~74%, so the blurred bar reads as the page going
     translucent rather than as a grey card floating on it. One value now,
     because there is one background. */
  --fbh-scrim: light-dark(rgba(251, 251, 250, 0.74), rgba(13, 13, 13, 0.72));
  --fbh-menu-bg: var(--color-card, light-dark(#ffffff, #1b1b1e));
  --fbh-menu-shadow: light-dark(0 14px 40px rgba(0, 0, 0, 0.16), 0 14px 40px rgba(0, 0, 0, 0.66));

  position: sticky;
  top: 0;
  z-index: 150;          /* above the viewer's nav arrows (100), below its expanded action bar (200) */
  height: var(--fbh-h);
  flex: none;
  /* The viewer's global `* { transition: color 600ms }` would make every hover
     in here feel underwater. Everything in the header states its own timing. */
  transition: transform 240ms cubic-bezier(0.32, 0.72, 0, 1);
}

/* The scrim lives on a pseudo-element so it can cross-fade. Toggling
   `backdrop-filter` itself flickers; animating the opacity of the layer that
   carries it does not, and at opacity 0 Chrome composites no blur at all. */
.fbh::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--fbh-scrim);
  -webkit-backdrop-filter: blur(18px) saturate(180%);
  backdrop-filter: blur(18px) saturate(180%);
  opacity: 0;
  pointer-events: none;
  transition: opacity 200ms ease;
}

.fbh.is-stuck::before { opacity: 1; }

/* The hairline is always there, at rest as well as scrolled.
   It used to ride on the scrim, so at scroll 0 there was no line at all and the
   bar simply floated in the page with nothing separating it from the content.
   Worst on the reading surface: the wordmark sat directly above a headline with
   no boundary, which read as a rendering fault rather than as restraint.

   Very faint on purpose -- 7.5% black. Enough to say where the chrome ends,
   not enough to become a horizontal rule competing with the first line of the
   article. Separate from the scrim so the two never double into a 2px edge. */
.fbh::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--fbh-hairline);
  pointer-events: none;
}

/* Viewer only: slide away on scroll-down, return on scroll-up. */
.fbh.is-tucked { transform: translateY(calc(-1 * var(--fbh-h) - 2px)); }

.fbh__inner {
  position: relative;
  height: 100%;
  max-width: var(--fbh-maxw);
  margin: 0 auto;
  padding: 0 var(--fbh-gutter);
  display: flex;
  align-items: center;
  gap: 12px;
}

/* ---------------------------------------------------------------- wordmark */

/* The lockup is the only thing in the bar with any weight to it, so it has to
   carry itself. Three things were making it read as an afterthought:

   · The wordmark was gradient-clipped from near-black to #4a4a4a, so the back
     half of "FireBrief" literally faded out. On a 56px bar next to a 28px
     article title, a name that dissolves reads as small and unfinished.
   · "Pro" was a blue word in the same size and weight as the wordmark, which
     made it look like part of the product name rather than a tier.
   · The section name was brand orange competing with the orange flame, two
     saturated marks 8px apart fighting for the same job.

   Now: solid wordmark, section name as a quieter second word, and Pro as an
   actual badge. The flame is the only saturated thing on the left. */

.fbh__brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
  margin-right: auto;
  text-decoration: none;
  border-radius: 10px;
  padding: 4px 6px 4px 0;
  transition: opacity 160ms ease;
}

.fbh__brand:hover { opacity: 0.78; }
.fbh__brand:active { opacity: 0.62; }

/* Sized by HEIGHT, with width following the artwork's 70:90 ratio.
   
   It used to be a square 30x30 box around a square viewBox that the mark only
   filled 70% of horizontally, so the flame drew 21px wide inside it with 4.5px
   of nothing on either side. That left-hand gap pushed the mark inboard of the
   column edge the article text starts on, and inflated the gap to the wordmark
   past the 9px it is actually set to.
   
   30px, not 26: the mark is faceted, so below about 28px the polygons muddle
   into an orange blob and it cannot hold the left end against a 700-weight
   wordmark. The <img> carries width=70 height=90 so the box is reserved at the
   right ratio before the SVG loads -- nothing reflows. */
.fbh__flame {
  height: 30px;
  width: auto;
  flex: none;
  display: block;
}

.fbh__word {
  display: inline-flex;
  align-items: baseline;
  gap: 0.34em;
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: -0.45px;
  line-height: 1;
  white-space: nowrap;
  min-width: 0;
}

/* Solid, not gradient-clipped. This is the single change that stops the
   wordmark looking washed out. */
/* Never shrinks. When the bar ran out of room it used to truncate everything
   equally, so a 320px phone showed "FireBr… Catch …" — the product's own name
   clipped to make space for a label. The section yields first, and disappears
   entirely before this is allowed to lose a character. */
.fbh__name {
  flex: none;
  color: var(--fbh-fg);
}

/* "Search", "Catch Up", "Settings" — the same size so the two words read as one
   phrase, lighter and dimmer so "FireBrief" stays the anchor. Deliberately not
   brand orange any more: that put a second saturated element next to the
   flame. */
.fbh__section {
  flex: 0 1 auto;
  font-weight: 500;
  letter-spacing: -0.3px;
  color: var(--fbh-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* A badge, not a word — and monochrome, which is the whole trick. Compared
   side by side, a tinted-blue chip read as an info notice and a solid blue one
   was the loudest thing in a bar whose premise is staying quiet. Ink against
   the page leaves the flame as the only colour on the left, and reads as
   premium rather than as a label.

   Inverted in dark mode: an ink chip on a dark bar disappears. */
.fbh__pro {
  display: none;
  align-self: center;
  margin-top: -0.06em;
  padding: 3px 5.5px;
  border-radius: 5px;
  font-size: 0.605rem;
  font-weight: 700;
  letter-spacing: 0.55px;
  line-height: 1;
  text-transform: uppercase;
  color: light-dark(#ffffff, #101012);
  background: light-dark(#131315, #ededf0);
}

body.is-pro .fbh__pro { display: inline-block; }

/* ------------------------------------------------------------------ actions */

.fbh__actions {
  display: flex;
  align-items: center;
  gap: 2px;
  flex: none;
}

.fbh__btn {
  width: var(--fbh-tap);
  height: var(--fbh-tap);
  display: grid;
  place-items: center;
  border: none;
  padding: 0;
  background: transparent;
  color: var(--fbh-icon);
  border-radius: 10px;
  cursor: pointer;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: background-color 140ms ease, color 140ms ease;
}

.fbh__btn svg {
  width: 21px;
  height: 21px;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@media (hover: hover) and (pointer: fine) {
  .fbh__btn:hover {
    background: var(--fbh-hover);
    color: var(--fbh-fg);
  }
}

.fbh__btn:active { background: var(--fbh-press); }

/* The surface you are already on. Colour only, no chip: the wordmark's section
   suffix already says where you are, and a filled pill under a duplicate signal
   read as a stuck button — on a phone it was the heaviest thing in the bar. */
.fbh__btn[aria-current='page'] { color: var(--fbh-fg); }

/* The avatar is a different kind of control from the two nav icons -- it opens
   a menu about you, not a page -- so it gets space rather than a divider. There
   used to be a 1px rule here doing that job; a hairline in a 56px transparent
   bar is a piece of furniture standing in for 6px of air. */
.fbh__avatar { margin-left: 8px; }


/* ------------------------------------------------------------------- avatar */

.fbh__avatar {
  position: relative;
  width: var(--fbh-avatar);
  height: var(--fbh-avatar);
  flex: none;
  padding: 0;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  overflow: hidden;
  display: grid;
  place-items: center;
  font-family: inherit;
  font-size: 0.775rem;
  font-weight: 600;
  letter-spacing: 0.1px;
  line-height: 1;
  color: #fff;
  /* Neutral until header.js resolves the user, so the box never resizes and
     an unresolved avatar reads as a placeholder rather than a broken control. */
  background: var(--fbh-hover);
  box-shadow: 0 0 0 1px var(--fbh-hairline);
  -webkit-tap-highlight-color: transparent;
  transition: box-shadow 140ms ease, transform 140ms ease;
}

/* Ink, matching the Pro chip, rather than a saturated blue gradient.
   Two reasons, in order of importance:

   1. The letter is a FALLBACK for a photo, so it should carry a photo's visual
      weight. Rendered side by side, a light grey or outlined disc made the bar
      rebalance depending on whether you happened to have a picture; ink sits at
      the same weight as one, so the composition holds either way.
   2. Saturated blue made a placeholder initial the loudest element in the bar
      -- heavier than the wordmark, styled like a primary action, in the far
      corner. It was the main thing making the header look unresolved. */
/* Signed out: a labelled button, not an empty disc. Same height as the avatar
   so the bar does not jump when auth resolves; width grows for the word. */
.fbh__avatar.is-signin {
  width: auto;
  border-radius: 999px;
  padding: 0 12px;
  background: light-dark(#131315, #ededf0);
  color: light-dark(#ffffff, #101012);
  box-shadow: none;
  overflow: visible;
}

.fbh__avatar.is-signin .fbh__avatar__initial {
  position: static;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.1px;
  white-space: nowrap;
  color: inherit;
}

.fbh__avatar.is-resolved {
  background: light-dark(#131315, #ededf0);
  color: light-dark(#ffffff, #101012);
  box-shadow: none;
}

/* The photo overlays the letter rather than replacing it, so a slow or failed
   image never leaves an empty disc — and there is no reflow when it arrives. */
.fbh__avatar__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0;
  transition: opacity 200ms ease;
}

.fbh__avatar.has-photo .fbh__avatar__img { opacity: 1; }

.fbh__avatar__initial { opacity: 0; transition: opacity 180ms ease; }
.fbh__avatar.is-resolved .fbh__avatar__initial { opacity: 1; }
/* The label is the whole control when signed out, so it is never hidden by the
   fade that exists to stop an initial flashing before auth resolves. */
.fbh__avatar.is-signin .fbh__avatar__initial { opacity: 1; }

/* With a real photo the blue gradient would show as a rim around a non-square
   crop, and the tinted ring is redundant against actual image content. */
.fbh__avatar.has-photo {
  background: var(--fbh-hover);
  box-shadow: 0 0 0 1px light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.14));
}

@media (hover: hover) and (pointer: fine) {
  .fbh__avatar:hover { box-shadow: 0 0 0 2px color-mix(in srgb, var(--fbh-accent) 45%, transparent); }
}

.fbh__avatar:active { transform: scale(0.94); }
.fbh__avatar[aria-expanded='true'] { box-shadow: 0 0 0 2px var(--fbh-accent); }

.fbh__btn:focus-visible,
.fbh__brand:focus-visible,
.fbh__avatar:focus-visible {
  outline: 2px solid var(--fbh-accent);
  outline-offset: 2px;
}

.fbh__item:focus-visible {
  outline: none;
  background: var(--fbh-hover);
  box-shadow: inset 0 0 0 1.5px var(--fbh-accent);
}

/* --------------------------------------------------------------------- menu */

.fbh__menu {
  position: absolute;
  top: calc(100% - 6px);
  right: var(--fbh-gutter);
  min-width: 232px;
  max-width: calc(100vw - 24px);
  padding: 6px;
  background: var(--fbh-menu-bg);
  border: 1px solid var(--fbh-hairline);
  border-radius: 14px;
  box-shadow: var(--fbh-menu-shadow);
  opacity: 0;
  transform: translateY(-6px) scale(0.98);
  transform-origin: top right;
  pointer-events: none;
  transition: opacity 150ms ease, transform 150ms cubic-bezier(0.32, 0.72, 0, 1);
}

.fbh__menu.is-open {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.fbh__who {
  padding: 9px 10px 10px;
  min-width: 0;
}

.fbh__who__name {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--fbh-fg);
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fbh__who__mail {
  margin-top: 2px;
  font-size: 0.78rem;
  color: var(--fbh-dim);
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fbh__menu hr {
  height: 1px;
  margin: 4px 0;
  border: none;
  background: var(--fbh-hairline);
}

.fbh__item {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  border: none;
  background: transparent;
  border-radius: 9px;
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--fbh-fg);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 120ms ease;
}

.fbh__item svg {
  width: 17px;
  height: 17px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  color: var(--fbh-dim);
}

@media (hover: hover) and (pointer: fine) {
  .fbh__item:hover { background: var(--fbh-hover); }
}

.fbh__item:active { background: var(--fbh-press); }
.fbh__item[disabled] { opacity: 0.55; cursor: default; }

/* ============================ per-surface tuning ========================== */

/* Per-surface scrims are gone with the per-surface backgrounds -- see --app-bg.
   All four pages are the same colour, so one scrim is correct everywhere. */

/* ============================== responsive =============================== */

/* Gutters are NOT set here — each surface pins its own below, to stay locked to
   that page's content column. */
@media (max-width: 620px) {
  .fbh {
    --fbh-tap: 34px;
  }
  .fbh__word { font-size: 1rem; letter-spacing: -0.38px; }
  /* Keeps the desktop mark-to-wordmark ratio (~1.63x the type size). At 22px
     the flame was proportionally smaller on a phone than on a desktop, which
     is backwards -- the small screen is where the mark does the most work. */
  .fbh__flame { height: 26px; }
}

@media (max-width: 380px) {
  .fbh__word { font-size: 0.98rem; }
  .fbh__actions { gap: 0; }
  /* Below this the section name cannot fit beside the wordmark without one of
     them clipping. The page itself, and the lit nav icon, both already say
     where you are. */
  .fbh__section { display: none; }
  .fbh__avatar { margin-left: 4px; }
}

/* Short viewports — landscape phones, split-screen, a half-height window. The
   bar cannot take 15% of the screen just to say the product's name. */
@media (max-height: 560px) {
  .fbh {
    --fbh-tap: 32px;
    --fbh-avatar: 27px;
  }
  .fbh__word { font-size: 0.95rem; }
  .fbh__flame { height: 24px; }
  .fbh__btn svg { width: 19px; height: 19px; }
  .fbh__avatar { font-size: 0.72rem; }
}

/* ========================== column alignment ============================= */

/* Nothing to do per surface any more: every page and the header all read
   --app-measure / --app-gutter above, so the bar is the top row of the same
   column everywhere. This block used to carry four separate measurements and a
   comment explaining why the viewer's differed. */

@media (prefers-reduced-motion: reduce) {
  .fbh,
  .fbh::before,
  .fbh__menu,
  .fbh__avatar__initial { transition: none; }
}

/* ===================== host-page layout compensation ====================== */

/* The header now supplies the top margin these pages used to draw themselves,
   so their own leading whitespace comes down to match. Loaded after each
   page's own styles, so plain selectors win on order.
   `.content`'s shorthand also carries the bottom padding that clears the
   floating action bar — restated here rather than overridden piecemeal. */
body.fbh-host .content {
  padding-top: 10px;
  padding-bottom: calc(90px + var(--space-lg, 1.5rem) + 50px);
}

@media (hover: hover) and (pointer: fine) {
  body.fbh-host .content {
    padding-top: 16px;
    padding-bottom: calc(90px + var(--space-xl, 2rem) + 50px);
  }
}

/* The viewer's article column is not the header's column, so nudge the article
   back to true centre now that the old fixed left-gutter logo is gone. */
@media (hover: hover) and (pointer: fine) {
  body.fbh-host .article { padding-left: 0; }
}
