/* =============================================================================
   PrivacyWall Design System — Search module
   The results page, its verticals, and the modules that appear inside it.
   Also carries rating / price / merchant, which the travel and shopping
   surfaces reuse.
   ============================================================================= */

/* -----------------------------------------------------------------------------
   HEADER
   ----------------------------------------------------------------------------- */
/* The whole chrome — bar, verticals, filters — is ONE block, 90 + 46 + 60.
   The verticals belong to the header and not to the body: they change what the
   page IS, and everything below them is the answer to whichever one is lit. A
   row of tabs sitting in the results column reads as a filter over the results,
   which is the row underneath it. */
/* The CHROME sticks, not the bar inside it. Sticking the bar alone left the
   verticals to scroll away from the field they belong under — and a row of tabs
   that disappears while the search box stays is two controls that were one. */
.pw-serp-chrome {
  position: sticky; top: 0;
  z-index: var(--pw-z-header);
  background: var(--pw-bg-canvas);
  /* A grid, so the two lower rows can be animated away. `1fr` in an
     auto-height grid resolves to the row's own content, and `0fr` collapses it
     — which is the one way to transition a row from its natural height to
     nothing without measuring that height in JavaScript and pinning it. */
  display: grid;
  grid-template-rows: auto 1fr 1fr;
  transition: grid-template-rows var(--pw-duration-base) var(--pw-ease-out);
}
/* Scrolled past the top: keep the field and give the page back the rest. The
   verticals and the filters are how you got here; once you are reading the
   results, the only one of the three still worth its height is the one you
   would use to leave. */
.pw-serp-chrome.is-condensed { grid-template-rows: auto 0fr 0fr; }
/* `min-width: 0` as well as `min-height`. These are grid items, and a grid item
   defaults to `min-width: auto` — so the filter row refused to shrink below the
   full width of a bar that is meant to SCROLL, and pushed the whole page wider
   instead of letting its own scrollport do the work. */
.pw-serp-nav,
.pw-serp-tools { overflow: hidden; min-height: 0; min-width: 0; }
.pw-serp-chrome.is-condensed .pw-serp-nav,
.pw-serp-chrome.is-condensed .pw-serp-tools { padding-block: 0; }
.pw-serp-chrome.is-condensed .pw-serp-nav { border-bottom-color: transparent; }
@media (prefers-reduced-motion: reduce) { .pw-serp-chrome { transition: none; } }
.pw-serp-header {
  display: flex; align-items: center;
  /* A real gap, so that when the field runs out of room to shrink into it stops
     against a space rather than against the settings button. The auto margin on
     the actions only separates them while there is surplus; the moment the
     field starts shrinking there is none, and without this they touched. */
  gap: var(--pw-space-8);
  height: var(--pw-serp-header-compact);
  padding-inline: var(--pw-serp-logo-inset) var(--pw-space-12);
  background: var(--pw-bg-canvas);
  min-width: 0;
  /* The bar's own height is a state, so it is animated like the rows below it —
     and on the same clock, so the chrome closes as one movement rather than as
     a collapse followed by a shrink. */
  transition: height var(--pw-duration-base) var(--pw-ease-out);
}
/* The rule goes under the VERTICALS, not under the bar. The field and the tabs
   are one control — the tabs say which kind of thing the field just searched —
   and a line between them cuts the sentence in half. Below the tabs it does the
   job it is there for: closing the chrome off from the page. */
.pw-serp-header--scrolled + .pw-serp-nav,
.pw-serp-chrome:has(.pw-serp-header--scrolled) .pw-serp-nav {
  border-bottom: 1px solid var(--pw-border-subtle);
}
/* Standalone — a header specimen with no verticals under it still needs closing. */
.pw-serp-header--scrolled:not(:has(+ .pw-serp-nav)) { border-bottom: 1px solid var(--pw-border-subtle); }
/* The logo is at 112 and the field at 184: the gap between them is the
   difference, stated once here rather than as a magic number on the field. */
.pw-serp-header__mark {
  flex: 0 0 auto;
  /* The gap above is part of this distance now, so it comes out of the margin.
     The field still starts at 184; it is just reached in two pieces. */
  margin-inline-end: calc(
    var(--pw-serp-field-inset) - var(--pw-serp-logo-inset) - var(--pw-serp-logo-size)
    - var(--pw-space-8)
  );
}
/* 40, measured. The brand's default mark is 38 — a size chosen for the mark,
   not for this header — and the two pixels are the difference between the
   field landing on 184 and landing on 186. */
.pw-serp-header__mark .pw-logomark { height: var(--pw-serp-logo-size); }
/* The header carries BOTH marks and the layout picks one — the component
   decides, not each specimen. On the desktop row the shield alone is right: it
   sits in a 112px lane beside a 700px field, and a wordmark there is a second
   line of text competing with the query. On the phone row the mark is centred
   with nothing beside it, and a lone shield in the middle of a header reads as
   a button; the wordmark is what says whose search this is.
   Hidden with `display`, so the one that is not showing is out of the
   accessibility tree too — the link's own `aria-label` carries the name. */
.pw-serp-header__mark .pw-logo { display: none; }
.pw-serp-header--stacked .pw-logomark { display: none; }
.pw-serp-header--stacked .pw-logo { display: block; height: 24px; }
@media (max-width: 768px) {
  .pw-serp-header__mark .pw-logomark { display: none; }
  .pw-serp-header__mark .pw-logo { display: block; height: 24px; }
}

/* The verticals row and the filter row. Both start at the same inset, which is
   NOT the results column's — production sets the nav at 200 and the results at
   212, and the twelve pixels between them are real. Named separately so that
   deciding to align them is a one-line change rather than an archaeology
   project. */
/* The rhythm is the one `.pw-verticals__rule` already defines: 10 above the
   rule, 10 below. Reaching for that value rather than inventing a second one is
   the point — the rule component and the SERP chrome are drawing the same
   separator, and two spacings for one relationship is how a system stops being
   one. Height comes from the padding rather than a fixed row, so the gap under
   the tabs is real space instead of leftover centring. */
.pw-serp-nav {
  display: flex; align-items: center;
  padding-block: var(--pw-serp-row-gap);
  padding-inline-start: var(--pw-serp-nav-inset);
  transition:
    padding-block var(--pw-duration-base) var(--pw-ease-out),
    border-color var(--pw-duration-base) var(--pw-ease-out);
}
/* 670, measured. The row is NOT full width: Tools sits at the end of a fixed
   track, not at the end of the window, or it drifts further from the verticals
   it belongs with on every wider screen. */
.pw-serp-nav > .pw-verticals { width: var(--pw-serp-tabs-width); }
.pw-serp-tools {
  display: flex; align-items: center;
  padding-block: var(--pw-serp-row-gap);
  padding-inline: var(--pw-serp-nav-inset);
  transition: padding-block var(--pw-duration-base) var(--pw-ease-out);
}
/* Tools off: the row gives up its padding as well as its contents, so the page
   closes the gap instead of leaving a band of nothing where the filters were.
   Keyed on the trigger rather than on the panel's `hidden`, because `hidden`
   only lands when the collapse has finished — driven from that, the bar would
   shrink first and the row would shrink after, one movement read as two. The
   trigger flips at the click, so both animate together. */
.pw-serp-chrome:has(.pw-verticals__end [aria-expanded="false"]) .pw-serp-tools {
  padding-block: 0;
}
@media (prefers-reduced-motion: reduce) { .pw-serp-tools { transition: none; } }
@media (max-width: 1439.98px) {
  .pw-serp-header { padding-inline: var(--pw-space-8); }
  .pw-serp-header__mark { margin-inline-end: var(--pw-space-8); }
  .pw-serp-nav { padding-inline-start: var(--pw-space-8); }
  .pw-serp-tools { padding-inline: var(--pw-space-8); }
}
/* The extension prompt goes at the TABLET breakpoint, not the phone one. It
   invites you to install a desktop browser extension, and a tablet cannot take
   that offer any more than a phone can — it is not a smaller version of itself,
   it is an offer that does not apply. It was also the widest thing in the row,
   so it was the first to crowd the field. The settings control stays: that one
   still works here. */
@media (max-width: 1024px) {
  .pw-serp-header__end .pw-btn--secondary { display: none; }
}
/* The results-page search field — a deliberate exception to the plain field.
 *
 * It rests *filled* rather than white, and lightens when you focus it. Same
 * idea as the verticals below it: a resting fill turns the header into a
 * ground, and touching the control lifts it out. A white pill on a white
 * header has only its 1px border to exist by, which is exactly the field you
 * fail to notice is there.
 *
 * This applies only in the header. `pw-search` on its own stays plain — a
 * filled field inside a form would read as disabled. */
/* The magnifier is a BUTTON on the trailing edge. It is never a decoration on
   the leading edge — not on the results header, not on the home page, not in
   the maps launcher. One glyph, one meaning: press it to search.
   A leading magnifier says the same word twice and takes the width from the
   query, which is the thing the person actually needs to read.
 *
 * Two exceptions, both deliberate:
 *   - the maps pin (`.pw-mark-pin`), and only on the PHONE launcher, where it
 *     identifies the app on a screen with no room for a wordmark. On desktop
 *     the maps field leads with nothing, like every other field;
 *   - a filter field inside a menu or popover, which has no submit button to
 *     carry the affordance and is not a search in the first place.
 * Enforced in CSS as well as markup, because a leading icon is the shape every
 * field on the web has and it will get pasted back in. */
.pw-search > .pw-search__icon:not(.pw-mark-pin) { display: none; }
:is(.pw-menu, .pw-select__menu, .pw-filter-menu, .pw-settings-menu, .pw-suggest)
  .pw-search > .pw-search__icon { display: inline-block; }

.pw-serp-header__search {
  /* 52 and 700 are MEASURED off the live header, not reasoned to: the field is
     exactly as wide as the results column beneath it, which is the one
     alignment on this page doing real work.
     But 700 is a CAP, not a width. A fixed 700 is right on a desktop header and
     wrong everywhere else — inside a phone frame it simply overflows, and a
     viewport media query cannot save it, because a simulated phone on a wide
     docs page is a narrow BOX, not a narrow viewport. Sized from its container
     instead, the same field is 700 in the header and 347 in a 375 frame with no
     second rule to keep in step. */
  /* Grow into the free space, cap at 700, shrink when there is not room.
     `width: 100%` was wrong here in a way that only shows on a narrow header: a
     flex item's percentage width resolves against the CONTAINER, not against
     what is left of it, so the field claimed the full header and pushed the
     actions off the end. */
  flex: 1 1 auto;
  /* The base `.pw-search` is `width: 100%`, and `flex-basis: auto` reads that
     width — so the field kept claiming the whole header however it was allowed
     to shrink. `auto` hands the sizing back to flex. */
  width: auto;
  max-width: var(--pw-serp-field-width);
  min-width: 0;
  --pw-search-height: var(--pw-serp-field-height);
  height: var(--pw-serp-field-height);
  min-height: var(--pw-serp-field-height);
  padding-inline: var(--pw-space-8) var(--pw-space-6);
  border-color: var(--pw-serp-search-border);
  background: var(--pw-serp-search-bg);
  box-shadow: var(--pw-shadow-xs);
}
/* On a white field the depth has to come from the shadow — the inset sheen
   that sold the "pressed into the header" look only reads on a tinted fill. */
.pw-serp-header__search:hover {
  background: var(--pw-serp-search-bg-hover);
  box-shadow: var(--pw-shadow-md);
}
.pw-serp-header__search:focus-within {
  background: var(--pw-serp-search-bg-focus);
  border-color: var(--pw-serp-search-border);
  box-shadow: 0 0 0 2px var(--pw-serp-search-ring), var(--pw-shadow-lg);
}
/* The query is the loudest thing in the header, so it carries weight. */
.pw-serp-header__search .pw-search__input {
  font-size: var(--pw-text-md-size);
  font-weight: var(--pw-font-weight-semibold);
}
/* The header's divider is heavier than the base one because it separates two
   controls inside a field that is itself the loudest thing in the header.
   The extra air around it is set once, on the field, as the symmetric amount
   the base component adds to its own gap — not as a left/right pair here. */
.pw-serp-header__search { --pw-search-divider-gap: var(--pw-space-2); }
.pw-serp-header__search .pw-search__divider {
  width: 2px;
  height: 28px;
  background: var(--pw-border-default);
  border-radius: var(--pw-radius-pill);
}
/* Clear and its divider only exist when there is something to clear. The base
   component now fades them in and out and keeps their seat, so this only has
   to cover a clear button that is not marked `.pw-search__clear`. */
.pw-serp-header__search:not(.has-value) :is([aria-label="Clear"], [aria-label^="Clear "]) {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition:
    opacity var(--pw-duration-base) var(--pw-ease-out),
    visibility 0s linear var(--pw-duration-base);
}
@media (max-width: 768px) {
  .pw-serp-header__search { height: 44px; min-height: 44px; padding-inline: var(--pw-space-6) var(--pw-space-4); }
}
/* Pushed to the far edge, which is where production puts them. The auto margin
   is safe here because the field is SIZED rather than grown: an auto margin
   only ever distributes POSITIVE free space, so on a wide header it takes the
   surplus and pins the actions right, and on a narrow one there is no surplus
   to take and `flex-shrink` on the field does the work instead. Growing the
   field was the wrong half of the trade — it put the actions immediately after
   a 700px field and left the rest of the header empty behind them. */
.pw-serp-header__end {
  display: flex; align-items: center; gap: var(--pw-space-4);
  flex: none;
  margin-inline-start: auto;
}

/* The phone arrangement, in the component rather than hand-built per specimen.
   Two rows: settings and the mark on the first, the field across the second.
   The order is a fact about the layout, not about the markup — the mark still
   comes first in the DOM, because it is still the first thing a screen reader
   should meet, and only the grid moves it.
   Settings leads because it is the one control left on that row, and the mark
   centres because a wordmark pinned to a corner on a 375px screen reads as a
   button rather than as the product's name.

   `--stacked` exists because a media query cannot see a phone FRAME on a wide
   docs page, and a container query is not safe here: `container-type` would
   make the sticky chrome a containing block for the fixed-positioned menus its
   own filter row opens. So a specimen that is drawing a phone says so. */
.pw-serp-header--stacked {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  grid-template-areas:
    "end  mark ."
    "field field field";
  align-items: center;
  height: auto;
  gap: var(--pw-space-6);
  /* The desktop inset goes with the desktop row. 112px of leading padding on a
     375px screen leaves 182 for a field that is meant to be the width of the
     phone, which is what it was doing.
     The phone gutter is its own token, and every row on the page reads it — the
     header, the verticals, the filters and the body — so the edge of a phone
     screen is one decision rather than four rules that agree by luck. */
  padding: var(--pw-space-6) var(--pw-serp-gutter-phone);
}
.pw-serp-header--stacked > .pw-serp-header__end  { grid-area: end; justify-self: start; margin-inline-start: 0; }
.pw-serp-header--stacked > .pw-serp-header__mark { grid-area: mark; justify-self: center; margin-inline-end: 0; }
.pw-serp-header--stacked > .pw-serp-header__search { grid-area: field; }
@media (max-width: 768px) {
  .pw-serp-nav   { padding-inline-start: var(--pw-serp-gutter-phone); }
  .pw-serp-tools { padding-inline: var(--pw-serp-gutter-phone); }
  .pw-serp-header {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    grid-template-areas:
      "end  mark ."
      "field field field";
    align-items: center;
    height: auto;
    gap: var(--pw-space-6);
    padding: var(--pw-space-6) var(--pw-serp-gutter-phone);
  }
  .pw-serp-header__end  { grid-area: end; justify-self: start; margin-inline-start: 0; }
  .pw-serp-header__mark { grid-area: mark; justify-self: center; margin-inline-end: 0; }
  .pw-serp-header__search { grid-area: field; }
}

/* The results column sits in a fixed gutter, not centred — matching production. */
/* Both sides. The body only had a left gutter, so the hero and the grid ran to
   whatever the container's right edge happened to be — 100px of accidental air
   on a wide screen and none at all on a narrow one. Production measures 80 on
   the right, and a gutter that only exists on one side is not a gutter, it is a
   left margin that has been mistaken for one. */
.pw-serp-body { padding-inline: var(--pw-serp-gutter) var(--pw-serp-gutter-end); }
.pw-serp-column { max-width: var(--pw-serp-content-width); }
@media (max-width: 1439.98px) { .pw-serp-body { padding-inline: var(--pw-space-8) var(--pw-space-12); } }
@media (max-width: 768px)     { .pw-serp-body { padding-inline: var(--pw-serp-gutter-phone); } }

/* Results and the knowledge rail, as one grid rather than two floats. The
   results track is FIXED and the rail takes what is left: the reading measure
   of a results column is the thing that must not move, and a page that shares
   the surplus between them changes the line length of forty results to give a
   fact sheet a wider photograph.
   The rail is capped at its own width, so on a very wide screen the surplus
   ends up as margin instead of stretching a panel that has nothing more to
   say. */
.pw-serp-grid {
  display: grid;
  /* `minmax(0, …)`, not a bare 700px. A fixed track cannot shrink, so below
     roughly 730px the column simply pushed the page sideways — the reading
     measure is the thing that must not move, but "must not move" has to stop
     being true before it starts breaking the page. */
  grid-template-columns: minmax(0, var(--pw-serp-content-width)) minmax(0, 1fr);
  gap: var(--pw-serp-column-gap);
  align-items: start;
}
/* The page is as wide as its two columns and no wider. Surplus on a large
   screen becomes margin rather than stretching a rail that has nothing more to
   say — and it gives the hero above a width to match, which is the whole
   reason it is stated here rather than left to `1fr`. */
.pw-serp-grid,
.pw-serp-body > .pw-entity {
  max-width: calc(
    var(--pw-serp-content-width) + var(--pw-serp-column-gap) + var(--pw-serp-rail-width)
  );
}
/* The rail is separated from the results by a hairline on its leading edge, not
   by the gap alone — measured on the live page. Twenty pixels of white between
   two columns of text is ambiguous about which column a stray line belongs to;
   a rule is not. */
.pw-serp-grid > .pw-serp-rail {
  width: 100%;
  max-width: var(--pw-serp-rail-width);
  /* Two separate distances, not one: the gap puts the RULE away from the
     results, and the padding puts the rail's text away from the rule. Written
     as a single gap the hairline would sit hard against the panel it is meant
     to be separating from. */
  padding-inline-start: var(--pw-serp-rail-pad);
  border-inline-start: 1px solid var(--pw-border-subtle);
}
@media (max-width: 1439.98px) {
  .pw-serp-grid > .pw-serp-rail { padding-inline-start: 0; border-inline-start: 0; }
}
/* The hero spans BOTH columns. It is the answer to the query, and the split
   below it is a split between two kinds of follow-up — squeezed into the
   results column it would be a third of the width the collage needs, and the
   fact cards would be 160px wide. */
.pw-serp-body > .pw-entity { margin-block-end: var(--pw-space-10); }
/* Below the point where both fit, the rail goes UNDER the results rather than
   beside them at half width. A knowledge panel squeezed into 240px is a column
   of two-word lines; the same panel full width under the first few results is
   still the same panel. */
/* Stack when the two columns plus the gutter and the gap no longer fit. The
   number follows from the parts — 212 + 700 + 60 + 420 — so widening the gap
   moves the breakpoint with it instead of leaving a band where the rail is
   quietly crushed to 220px. */
@media (max-width: 1439.98px) {
  .pw-serp-grid { grid-template-columns: minmax(0, 1fr); }
  .pw-serp-grid > .pw-serp-rail { max-width: var(--pw-serp-content-width); }
}

/* -----------------------------------------------------------------------------
   RELATED SEARCHES
   The end of the column: six queries the page could have been. Two columns, not
   a wrapped rail of chips — these are full phrases, and a wrap puts three on one
   line and one on the next, which reads as a ragged afterthought rather than a
   set of six equal offers.
   ----------------------------------------------------------------------------- */
.pw-related {
  padding-block: var(--pw-space-10) var(--pw-space-16);
  border-top: 1px solid var(--pw-border-subtle);
}
.pw-related__title {
  margin: 0 0 var(--pw-space-8);
  font-size: var(--pw-text-lg-size); line-height: var(--pw-text-lg-line);
  font-weight: var(--pw-font-weight-medium);
  color: var(--pw-fg-primary);
}
.pw-related__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--pw-space-6) var(--pw-space-12);
}
/* A full-width pill rather than a chip that shrinks to its text: six queries of
   different lengths in two columns look like a broken grid unless they share an
   edge. The magnifier says these are searches and not links to pages. */
.pw-related__item {
  display: flex; align-items: center; gap: var(--pw-space-5);
  min-height: 43px;
  padding: var(--pw-space-4) var(--pw-space-8);
  border: 1px solid var(--pw-border-default);
  border-radius: var(--pw-radius-pill);
  background: var(--pw-bg-surface);
  color: var(--pw-fg-primary);
  font-size: var(--pw-text-md-size); line-height: var(--pw-text-md-line);
  text-align: start;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--pw-duration-fast) var(--pw-ease-out),
              border-color var(--pw-duration-fast) var(--pw-ease-out);
}
.pw-related__item:hover { background: var(--pw-bg-hover); border-color: var(--pw-border-strong); }
.pw-related__item .pw-icon { color: var(--pw-fg-tertiary); flex: 0 0 auto; }
@media (max-width: 768px) { .pw-related__grid { grid-template-columns: minmax(0, 1fr); } }

/* -----------------------------------------------------------------------------
   ENTITY HERO
   When the query IS a thing — a city, a country, a person — the answer is not a
   list of pages about it. The hero puts what the thing looks like, where it is
   and the two or three facts people came for above the results, and leaves the
   knowledge panel to hold the reading: the description, the fact sheet, the
   related entities.

   It is a strip, not a card: no border, no fill, a rule underneath. A bordered
   box at the top of a results page reads as an ad, and this is the opposite of
   one — it is the page answering directly.

   Structure
     .pw-entity > .pw-entity__head  ( __title / __meta: __subtitle + the ⋮ )
                > .pw-entity__grid  ( .pw-collage | .pw-entity__map | .pw-entity__aside )
   ----------------------------------------------------------------------------- */
.pw-entity {
  /* The strip's height, and therefore the floor the aside has to fit two cards
     into. It went up with the forecast: a week stacks its readings, which makes
     the weather card ~164 tall, and 164 + 16 + a second card of 136 is 316. The
     aside is clipped rather than scrolled — a card that does not fit is cut off
     and says nothing about it — so this number has to lead the content, not
     follow it. Raise it when a card grows; do not let a card outgrow it. */
  --pw-entity-height: 324px;
  padding-block-end: var(--pw-space-10);
  border-block-end: 1px solid var(--pw-border-subtle);
}
/* The name of the thing, then what kind of thing it is. Without the name the
   strip is a set of pictures with no subject: the reader has to infer from the
   photographs what the page decided the query meant. Naming it also lets the
   subtitle stay short — "Capital of France" is a description, not a label.

   It is one step above the knowledge panel's title (24 against 20), because
   this is the page's answer and that is the reading beside it. */
.pw-entity__head {
  display: flex; flex-direction: column; align-items: flex-start;
  /* 2px, the same step every title/subtitle pair in the system uses. A name and
     its description are one block; 4px starts to read as two. */
  gap: var(--pw-space-1);
  margin-block-end: var(--pw-space-6);
}
.pw-entity__title {
  margin: 0;
  font-size: var(--pw-display-xs-size); line-height: var(--pw-display-xs-line);
  font-weight: var(--pw-font-weight-semibold);
  letter-spacing: var(--pw-tracking-tight);
  color: var(--pw-fg-primary);
}
.pw-entity__meta { display: flex; align-items: center; gap: var(--pw-space-3); }
.pw-entity__subtitle { font-size: var(--pw-text-sm-size); color: var(--pw-fg-secondary); }
/* Media, place, then the facts. The proportion is deliberate and not a guess:
   the collage is the only part that answers "what does it look like", which is
   what most of these queries are actually asking. */
.pw-entity__grid {
  display: grid;
  grid-template-columns: minmax(0, 2.1fr) minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--pw-space-8);
  height: var(--pw-entity-height);
  /* The track sizes are what animate, not a display swap. Opening the map is a
     change of proportion — one column taking the row from the other two — and
     proportion is a thing you can watch happen. Cutting to it leaves the reader
     to work out that the big map is the small one they just pressed. */
  transition:
    grid-template-columns var(--pw-duration-slow) var(--pw-ease-out),
    column-gap var(--pw-duration-slow) var(--pw-ease-out),
    height var(--pw-duration-slow) var(--pw-ease-out);
}
.pw-entity__map {
  position: relative;
  border-radius: var(--pw-radius-lg);
  overflow: hidden;
}
.pw-entity__aside { display: flex; flex-direction: column; gap: var(--pw-space-8); min-height: 0; }
/* `flex: 1` sets a ZERO basis, which throws away what each card actually holds
   and makes them equal — so a card carrying one number gets exactly as much
   height as a three-day forecast and spends the difference on a hole between
   its title and its answer. An `auto` basis keeps the natural heights and
   shares only the SURPLUS, so a card is as tall as what is in it plus its share
   of the room left over. Both edges of the column still meet the strip. */
.pw-entity__aside > * { flex: 1 1 auto; }

/* -- When the subject has less to show ---------------------------------------
   Two cases the strip has to survive, and neither is rare: a subject with no
   photographs, and a subject that is not anywhere.

   Both are answered by re-proportioning. Not by a placeholder — a grey box
   where a photograph should be is a claim that there IS a photograph and it
   failed to load — and not by an empty state, because an empty state inside
   the hero is the page reporting on itself in the space where it was meant to
   answer. What is left simply takes the room.
   ---------------------------------------------------------------------------- */
/* No pictures: the map leads, at the proportion the collage had. */
.pw-entity--no-media .pw-entity__grid {
  grid-template-columns: minmax(0, 2.1fr) minmax(0, 1fr);
}
/* Nothing to show and nowhere to show: what is left is facts, and facts do not
   need 308px of height. They spread along the row rather than stacking in a
   column a third of the width — a date and a duration read as a line, not as a
   tower. */
.pw-entity--facts-only .pw-entity__grid {
  grid-template-columns: minmax(0, 1fr);
  height: auto;
}
.pw-entity--facts-only .pw-entity__aside { flex-direction: row; }

/* The map, opened. It takes the whole strip rather than growing in place: a map
   is read by panning around it, and a 260px column is a picture of a map rather
   than a map. Everything else in the strip stands down while it is open — they
   are still one answer, and two of them cannot be the main one at once. */
/* The other two columns go to zero WIDTH rather than to `display: none`. A
   display swap cannot be animated, and it also drops them out of the grid mid
   transition, so the map would jump the width of two gaps on the last frame. At
   0fr they are still there, still in order, and simply have no room. */
.pw-entity.is-map-open .pw-entity__grid {
  grid-template-columns: minmax(0, 0fr) minmax(0, 1fr) minmax(0, 0fr);
  column-gap: 0;
  height: 700px;
}
/* Written AFTER the rule it overrides, not before: same specificity, so source
   order settles it. Without this the three-column template lands on a
   two-column grid and collapses the map instead of the cards — the one thing
   the map button must never do. */
.pw-entity--no-media.is-map-open .pw-entity__grid {
  grid-template-columns: minmax(0, 1fr) minmax(0, 0fr);
}
.pw-collage,
.pw-entity__aside {
  overflow: hidden;
  transition: opacity var(--pw-duration-base) var(--pw-ease-out);
}
.pw-entity.is-map-open .pw-collage,
.pw-entity.is-map-open .pw-entity__aside {
  opacity: 0;
  pointer-events: none;
}
/* The controls that only exist on the open map fade in behind the movement, so
   they arrive once there is somewhere for them to be. The DELAY is the part that
   makes that sentence true: without it they started on the same frame as the
   grid and finished at 180ms while the map was still growing until 240ms, which
   is in front of the movement rather than behind it. */
.pw-entity__map-actions,
.pw-entity__map-zoom {
  animation: pw-more-in var(--pw-duration-base) var(--pw-ease-out) var(--pw-duration-fast) both;
}
@media (prefers-reduced-motion: reduce) {
  .pw-entity__grid,
  .pw-collage,
  .pw-entity__aside { transition: none; }
  .pw-entity__map-actions,
  .pw-entity__map-zoom { animation: none; }
}
.pw-entity__map-actions {
  position: absolute; inset-inline-start: var(--pw-space-8); inset-block-end: var(--pw-space-8);
}
.pw-entity__map-zoom {
  position: absolute; inset-inline-end: var(--pw-space-8); inset-block-end: var(--pw-space-8);
}
.pw-entity__map-toggle {
  position: absolute; inset-inline-end: var(--pw-space-6); inset-block-start: var(--pw-space-6);
}
@media (max-width: 1199.98px) {
  .pw-entity__grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); height: auto; }
  /* The modifiers above are one class deeper, so they win on specificity
     wherever they are written. Restated here rather than left to out-rank the
     breakpoint that is meant to override them. */
  .pw-entity--no-media .pw-entity__grid { grid-template-columns: minmax(0, 1fr); }
  .pw-entity--facts-only .pw-entity__grid { grid-template-columns: minmax(0, 1fr); }
  .pw-collage { height: var(--pw-entity-height); }
  .pw-entity__map { height: var(--pw-entity-height); }
  .pw-entity__aside { grid-column: span 2; flex-direction: row; }
}
@media (max-width: 768px) {
  .pw-entity__grid { grid-template-columns: minmax(0, 1fr); }
  .pw-entity__aside { grid-column: auto; flex-direction: column; }
  .pw-entity--facts-only .pw-entity__aside { flex-direction: column; }
  /* One step down on a phone: 24px over a 342px measure is a heading that takes
     two lines for a name of any length. */
  .pw-entity__title {
    font-size: var(--pw-text-xl-size); line-height: var(--pw-text-xl-line);
  }
}

/* -- Collage ----------------------------------------------------------------
   One big image and three smaller ones, in a fixed arrangement rather than a
   uniform grid. A row of equal thumbnails says "here are four pictures"; a
   dominant image with three supporting it says "here is what this place looks
   like", which is the claim being made.
   ---------------------------------------------------------------------------- */
.pw-collage {
  position: relative;
  display: grid;
  grid-template-columns: 1.6fr 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: var(--pw-space-2);
  border-radius: var(--pw-radius-lg);
  overflow: hidden;
  /* Fills whatever it is given rather than sizing to its contents: every item
     in it is a background, so left to itself the whole thing measures zero. */
  height: 100%;
  min-height: 0;
}
.pw-collage__item {
  position: relative;
  display: block;
  padding: 0; border: 0;
  background: var(--pw-bg-muted);
  overflow: hidden;
  cursor: pointer;
}
.pw-collage__item > img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The lead image spans both rows; the second spans the top of the remaining two
   columns. Written as spans rather than named areas so the same rule survives a
   collage with fewer images — what is left simply fills the grid. */
.pw-collage__item:first-child { grid-row: span 2; }
.pw-collage__item:nth-child(2) { grid-column: span 2; }
.pw-collage__item::after {
  content: ""; position: absolute; inset: 0;
  background: var(--pw-ink-hover);
  opacity: 0;
  transition: opacity var(--pw-duration-fast) var(--pw-ease-out);
}
.pw-collage__item:hover::after { opacity: 1; }
.pw-collage__more {
  position: absolute;
  inset-inline-end: var(--pw-space-6); inset-block-end: var(--pw-space-6);
}

/* -- Collage structures -----------------------------------------------------
   How many pictures there are is not a styling choice, it is a fact about the
   subject: a city has forty, a footballer has three, a fixture has one. Each
   arrangement is the honest one for that count — padding a set of two out to
   four slots means repeating an image, which tells the reader something untrue
   about how much there is to see.
   ---------------------------------------------------------------------------- */
/* Two, equal. Not one big and one small: with only two, making one dominant
   claims a lead image the set does not have. */
.pw-collage--pair { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr; }
.pw-collage--pair .pw-collage__item:first-child { grid-row: auto; }
.pw-collage--pair .pw-collage__item:nth-child(2) { grid-column: auto; }

/* A person. The lead is a portrait, so it takes a narrow column at full height
   and the supporting shots stack beside it — the reverse of the city's
   proportions, because a face cropped to a landscape box is a face with the top
   of its head missing. */
.pw-collage--portrait { grid-template-columns: 0.72fr 1fr; grid-template-rows: 1fr 1fr; }
.pw-collage--portrait .pw-collage__item:first-child { grid-row: span 2; }
.pw-collage--portrait .pw-collage__item:nth-child(2) { grid-column: auto; }

/* One. A fixture, a product, a building — things with a single canonical
   picture. The grid collapses rather than the item stretching, so the radius
   and the overlay button stay where they are. */
.pw-collage--single { grid-template-columns: 1fr; grid-template-rows: 1fr; }
.pw-collage--single .pw-collage__item:first-child { grid-row: auto; }
.pw-collage--single .pw-collage__item:nth-child(2) { grid-column: auto; }

/* -- Video in the map's slot ------------------------------------------------
   For a subject whose "where" is not a place — a person, a band, a match — the
   second column carries the thing people actually go looking for instead. It
   keeps the map's shape exactly: same slot, same radius, same size, so the hero
   does not re-flow between kinds of subject.
   ---------------------------------------------------------------------------- */
.pw-entity__video {
  position: relative;
  border-radius: var(--pw-radius-lg);
  overflow: hidden;
  background: var(--pw-bg-muted);
  border: 0; padding: 0;
  cursor: pointer;
}
.pw-entity__video > img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pw-entity__video::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0) 55%);
}
.pw-entity__video-play {
  position: absolute; inset-block-start: 50%; inset-inline-start: 50%;
  translate: -50% -50%;
  z-index: 1;
}
.pw-entity__video-title {
  position: absolute; z-index: 1;
  inset-inline: var(--pw-space-6); inset-block-end: var(--pw-space-6);
  color: var(--pw-color-white);
  font-size: var(--pw-text-sm-size); line-height: var(--pw-text-sm-line);
  font-weight: var(--pw-font-weight-semibold);
  text-align: start;
}

/* -- Score ------------------------------------------------------------------
   A fixture's card. The two sides read as a pair rather than a sentence, so
   they are a grid with the score between them and neither one leading.
   ---------------------------------------------------------------------------- */
.pw-factcard__score {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--pw-space-4);
}
.pw-factcard__team { display: flex; flex-direction: column; align-items: center; gap: var(--pw-space-3); }
.pw-factcard__team-name { font-size: var(--pw-text-xs-size); color: var(--pw-fg-secondary); text-align: center; }
.pw-factcard__team-crest {
  display: grid; place-items: center;
  width: 28px; height: 28px;
  border-radius: var(--pw-radius-circle);
  background: var(--pw-mark-bg, var(--pw-bg-muted));
  color: var(--pw-mark-fg, var(--pw-fg-secondary));
  font-size: var(--pw-text-xs-size);
  font-weight: var(--pw-font-weight-bold);
}
.pw-factcard__score-line {
  font-size: var(--pw-text-xl-size); line-height: var(--pw-text-xl-line);
  font-weight: var(--pw-font-weight-semibold);
  color: var(--pw-fg-primary);
  font-variant-numeric: tabular-nums;
}
.pw-factcard__state { font-size: var(--pw-text-xs-size); color: var(--pw-fg-tertiary); text-align: center; }

/* A short list inside a card — the companies a founder runs, the offices a
   politician has held. Lines rather than chips: these are read down, and each
   one is a full phrase rather than a token. */
.pw-factcard__lines {
  display: flex; flex-direction: column; gap: var(--pw-space-2);
  font-size: var(--pw-text-sm-size); line-height: var(--pw-text-sm-line);
  color: var(--pw-fg-secondary);
}
.pw-factcard__lines > strong { color: var(--pw-fg-primary); font-weight: var(--pw-font-weight-medium); }
/* A number that has moved. The arrow carries the direction as well as the
   colour, because the two states must not differ by hue alone. */
.pw-factcard__delta {
  display: inline-flex; align-items: center; gap: var(--pw-space-2);
  font-size: var(--pw-text-sm-size);
  font-weight: var(--pw-font-weight-medium);
}
.pw-factcard__delta--up   { color: var(--pw-fg-success); }
.pw-factcard__delta--down { color: var(--pw-fg-danger); }

/* -- Fact card --------------------------------------------------------------
   Weather, and how to get there. Quiet fills rather than bordered cards: they
   sit inside a strip that already has an edge, and a border inside a border is
   two frames around one thing.
   ---------------------------------------------------------------------------- */
/* The title is at the top and the answer is at the BOTTOM, with the space
   between them rather than under them. Two cards side by side then agree on
   both edges — the labels line up and so do the numbers — where a top-packed
   column leaves the numbers at whatever height their own titles ended. */
.pw-factcard {
  display: flex; flex-direction: column; justify-content: space-between;
  gap: var(--pw-space-6);
  padding: var(--pw-space-8);
  border: 0;
  border-radius: var(--pw-radius-xl);
  background: var(--pw-bg-subtle);
  text-align: start;
}
.pw-factcard__head { display: flex; align-items: center; gap: var(--pw-space-4); }
.pw-factcard__title {
  font-size: var(--pw-text-sm-size);
  font-weight: var(--pw-font-weight-semibold);
  color: var(--pw-fg-primary);
}
/* A card that goes somewhere. The chevron is the whole affordance — a fill or a
   border would make it compete with the results underneath, which it must not:
   it is an offer, not the answer. */
.pw-factcard--link { cursor: pointer; transition: background-color var(--pw-duration-fast) var(--pw-ease-out); }
.pw-factcard--link:hover { background: var(--pw-bg-active); }
.pw-factcard__caret { margin-inline-start: auto; color: var(--pw-fg-quaternary); }
/* Value and meta are one block so `space-between` keeps them together at the
   foot of the card rather than pushing the caption away from the number it
   captions. */
.pw-factcard__foot { display: flex; flex-direction: column; gap: var(--pw-space-2); }
.pw-factcard__value {
  display: flex; align-items: center; gap: var(--pw-space-4);
  font-size: var(--pw-text-lg-size); line-height: var(--pw-text-lg-line);
  font-weight: var(--pw-font-weight-semibold);
  color: var(--pw-fg-primary);
}
.pw-factcard__value .pw-icon { color: var(--pw-fg-tertiary); }
.pw-factcard__meta { font-size: var(--pw-text-xs-size); color: var(--pw-fg-tertiary); }
/* It used to be a `.pw-link` reading "PrivacyWall Weather", which is a claim
   rather than a credit: the data is Apple's, and the widget three components
   away has been saying so correctly the whole time. So the card says it the same
   way — WeatherKit's terms are that the trademark is shown and links to the list
   of the other sources the data came from, and a blue link naming us instead
   satisfies neither the terms nor the reader.

   Quiet, because an attribution is not an action. `--pw-fg-tertiary` is the
   colour the widget's foot already uses for the same sentence. */
.pw-factcard__source { font-size: var(--pw-text-xs-size); color: var(--pw-fg-tertiary); }

/* A week, shown as much of it as the card can hold. The number of days is not
   fixed by this component: it takes what it is given, changes shape past three
   (see below), and drops the far end when the card is too narrow to run them
   all. The days that go are the last ones, which are also the least certain.
   Spread rather than spaced: `space-between` gives the gap back as the card
   narrows, so the days shrink with it instead of pushing past the edge. */
.pw-forecast { display: flex; justify-content: space-between; gap: var(--pw-space-5); min-width: 0; }
.pw-forecast__day { display: flex; flex-direction: column; gap: var(--pw-space-3); min-width: 0; }
.pw-forecast__label { font-size: var(--pw-text-xs-size); color: var(--pw-fg-tertiary); }
.pw-forecast__reading { display: flex; align-items: center; gap: var(--pw-space-3); }
/* Past three days it is a different shape, not the same one repeated: at seven,
   an icon beside the number leaves no room for the number. The reading stacks,
   the temperature steps down, and the days centre on their own column.

   Keyed on the fourth day rather than on a modifier, because how many days
   there are is a fact about the data — an author who adds a day should not also
   have to remember to change a class, and the shape that fits four does not
   depend on anyone noticing. */
.pw-forecast:has(> :nth-child(4)) {
  gap: var(--pw-space-3);
}
.pw-forecast:has(> :nth-child(4)) .pw-forecast__day {
  flex: 1 1 0; align-items: center; text-align: center;
  gap: var(--pw-space-2);
}
.pw-forecast:has(> :nth-child(4)) .pw-forecast__reading {
  flex-direction: column; gap: var(--pw-space-2);
}
.pw-forecast:has(> :nth-child(4)) .pw-forecast__temp {
  font-size: var(--pw-text-md-size); line-height: var(--pw-text-md-line);
}
/* The temperature is the answer; the day above it and the attribution below it
   are both smaller than it for the same reason. At the label's size the card
   was three equal rows and nothing in it was the point. */
.pw-forecast__temp {
  font-size: var(--pw-text-lg-size); line-height: var(--pw-text-lg-line);
  font-weight: var(--pw-font-weight-semibold);
  color: var(--pw-fg-primary);
}
/* Attribution belongs at the foot of the card, under the thing it is
   attributing — above the forecast it reads as a heading for it. */
.pw-factcard__source { margin-block-start: auto; }
/* The card is its own container as well. Two of these sit side by side in the
   hero's aside and again in the docs grid, so "narrow" here is a fact about the
   card, not about the page. At the bottom of the range the temperature gives up
   its step: three readings at 18px do not fit 170px, and a reading that spills
   past the card is worse than a smaller one. */
.pw-factcard { container-type: inline-size; container-name: pw-factcard; }
@container pw-factcard (max-width: 220px) {
  .pw-forecast { gap: var(--pw-space-3); }
  .pw-forecast__temp { font-size: var(--pw-text-md-size); line-height: var(--pw-text-md-line); }
  .pw-forecast__reading { gap: 2px; }
}
/* A week does not fit every card, so the card decides how much of the week it
   shows — the far end of a forecast is the part with the least in it, and the
   days that go are always the last ones. Seven readings need ~275px of run;
   below that it is five, and below that the original three. */
@container pw-factcard (max-width: 300px) {
  .pw-forecast__day:nth-child(n+6) { display: none; }
}
@container pw-factcard (max-width: 220px) {
  .pw-forecast__day:nth-child(n+4) { display: none; }
}
.pw-forecast__icon--sun   { color: var(--pw-color-warning-500); }
.pw-forecast__icon--cloud { color: var(--pw-fg-quaternary); }
.pw-forecast__icon--rain  { color: var(--pw-color-brand-500); }

/* -- Gallery ----------------------------------------------------------------
   Every image, in a lightbox. Columns rather than a grid: photographs are not
   all the same shape, and a uniform grid either crops them all or leaves ragged
   gaps. Masonry keeps each one whole.
   ---------------------------------------------------------------------------- */
/* The modal body carries inline padding and no block padding, which is right
   for a body that starts with a header component and wrong for one that IS
   the whole panel: the title sat on the panel's top edge, tucked under a
   20px corner. The grid carries the matching bottom inset itself, because it
   is the scroll container and the padding has to be inside the scroll. */
/* THE SCROLLBAR SITS AT THE PANEL'S EDGE, NOT INSIDE ITS PADDING.

   `.pw-modal__body` pads 20px each side, and the grid inside it was the thing
   that scrolled — so the bar was drawn at the GRID's right edge, 20px in from
   the panel, hard against the last column of photographs with an empty 20px
   strip to the right of it. A bar floating in the gutter reads as something laid
   over the content rather than something beside it.

   So the body gives up its inline padding and each of its children takes that
   padding for itself. The scroll container's box now reaches the panel's edge,
   the bar is drawn inside that edge, and the padding — which is inside the
   scrollbar, not outside it — holds the content off. Same inset as before, bar
   in the right place. See DESIGN.md. */
.pw-gallery { display: flex; flex-direction: column; min-height: 0; padding-block-start: var(--pw-space-10); padding-inline: 0; }
.pw-gallery__head { display: flex; align-items: center; gap: var(--pw-space-6); margin-block-end: var(--pw-space-8); padding-inline: var(--pw-space-10); }
.pw-gallery__title {
  font-size: var(--pw-text-md-size);
  color: var(--pw-fg-primary);
}
.pw-gallery__close { margin-inline-start: auto; }
.pw-gallery__filters { display: flex; gap: var(--pw-space-5); margin-block-end: var(--pw-space-8); padding-inline: var(--pw-space-10); }
/* The filter chips name a KIND of thing — all media, or only videos — so they
   carry an icon, not a photograph. A thumbnail here was a picture of one of the
   items being filtered, which says "this one" when the chip means "all of
   these"; and with a different photo in each chip the row read as a gallery of
   its own competing with the grid below it.
   The medallion is the mark the rest of the system already uses for a category,
   so a chip in this row and a chip anywhere else are the same object. */
/* Height only. The medallion already carries its own -4px lead-in, so the
   chip's normal inline padding puts it 8px from the edge — the same inset it
   has on every other chip in the system. The old thumbnail cancelled that
   padding outright and sat hard against the border, which is what made this
   row look wrong next to every other chip row. */
.pw-gallery__filters .pw-chip { height: 36px; }
/* The scroller and the masonry are two elements, and they have to be. A
   multi-column box with a DEFINITE height does not grow downwards when it runs
   out of room — it fragments into further columns to the RIGHT. So the moment
   the dialog got a fixed height this grid started scrolling sideways. The
   scroller keeps the definite height; the masonry inside it is auto-height and
   flows down, which is the only direction a wall of photographs should go. */
.pw-gallery__grid {
  overflow-y: auto;
  overflow-x: hidden;
  min-height: 0;
  /* The same inset the head has. A scroll container keeps its end padding, so
     this costs nothing while the set is long — content still runs under the
     edge mid-scroll, which is the affordance — and it is the whole difference
     when the set is short, where there is nothing to scroll and the last tile
     was simply jammed into the panel's bottom corner. */
  /* The full 20px, matching the top, and NOT minus the tile's trailing margin:
     the tallest column drops that margin (it does not add to the column's
     height), so subtracting it left the one column that actually reads as the
     bottom edge with 10px while the short ones kept 30. */
  padding-block-end: var(--pw-space-10);
  padding-inline: var(--pw-space-10);
}
/* MASONRY THAT READS ACROSS.

   It used to be `columns: 4`, and a multi-column box flows DOWN one column and
   then down the next. That is the whole trick of multicol and it is the wrong
   trick here: a set of twelve photographs followed by ten videos became four
   vertical runs, so the photographs filled the left two columns and the videos
   the right two. Nothing was out of order — the order simply ran the wrong way
   for an eye that reads across, and no amount of CSS changes which way a column
   flows.

   So the columns are real elements and the items are dealt into them one at a
   time, each going to whichever column is shortest so far — see the gallery
   controller. Dealing in document order is what makes the set read as ONE
   sequence: item 1 to the first column, item 2 to the second, and by the time
   the videos start the photographs have filled the width above them. No heading
   between the two kinds, because there is no longer a seam to explain.

   CSS owns HOW MANY columns and JavaScript owns WHICH column each item lands
   in. That split matters: the count is a question about the width of the screen,
   which the stylesheet already answers everywhere else, and a media query in a
   controller is a second place for the breakpoints to disagree from. */
.pw-gallery__masonry {
  --pw-masonry-cols: 4;
  display: flex;
  align-items: flex-start;
  gap: var(--pw-space-5);
}
.pw-masonry__col {
  flex: 1 1 0;
  min-width: 0;
  display: flex; flex-direction: column;
  gap: var(--pw-space-5);
}
/* Videos are all one shape — 16:9 with two lines under them — so the ragged
   edge masonry exists to solve does not exist here, and four columns of
   identical cards read as a wall that failed to line up. Three across. */
.pw-gallery__masonry.is-videos { --pw-masonry-cols: 3; gap: var(--pw-space-8); }
.pw-gallery__masonry.is-videos .pw-masonry__col { gap: var(--pw-space-10); }
@media (max-width: 900px) {
  .pw-gallery__masonry, .pw-gallery__masonry.is-videos { --pw-masonry-cols: 2; }
}
@media (max-width: 560px) {
  .pw-gallery__masonry, .pw-gallery__masonry.is-videos { --pw-masonry-cols: 1; }
}
/* Centred under the set, not aligned to a column: it belongs to all of them.

   `flex` and not `block`, which is the whole of it. `.pw-btn` is an inline-flex
   box that centres its own label with `align-items` and `justify-content` — and
   both of those are INERT on a block box. Setting `display: block` here to get
   the auto margins therefore did not move the pill's label, it stopped the pill
   centring it at all: 14px of text sitting at the top of a 32px box with 18px of
   nothing under it. `flex` keeps the centring and is block-level, so the auto
   margins still work, and `max-content` shrink-wraps it — which a <button> does
   under `block` and an <a> does not. */
.pw-gallery__more {
  display: flex;
  width: max-content;
  max-width: 100%;
  margin: var(--pw-space-8) auto 0;
}
.pw-gallery__item {
  display: block;
  width: 100%;
  /* The column's `gap` owns the space between tiles now. A margin as well would
     be the same distance counted twice. */
  margin-block-end: 0;
  padding: 0; border: 0;
  border-radius: var(--pw-radius-md);
  background: var(--pw-bg-muted);
  overflow: hidden;
  break-inside: avoid;
  cursor: pointer;
}
.pw-gallery__item > img { width: 100%; display: block; }
/* A video is not a photograph with a triangle on it. It is a document: it has a
   title, a channel, a running time, and a 16:9 frame — YouTube has no portrait
   thumbnails, so a tall tile reads as a still no matter what badge is on it.
   The tile keeps the grid's masonry seat and borrows the video card's own title
   and channel parts, so a video in the gallery and a video in the results
   module are recognisably the same object.

   AFTER the base rule, not before: both are one class deep, so `display` here
   is decided by source order and a modifier written first would silently lose. */
.pw-gallery__item--video {
  display: flex; flex-direction: column; gap: var(--pw-space-4);
  background: none; overflow: visible;
  text-align: start;
}
.pw-gallery__thumb {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--pw-radius-md);
  background: var(--pw-bg-muted);
  overflow: hidden;
}
.pw-gallery__thumb img { width: 100%; height: 100%; object-fit: cover; }
/* Two lines' worth of room whether or not the title needs both, so a row of
   video tiles keeps its channel lines on one level. Reserved, not clamped —
   `pw-clamp-2` already caps the top end. */
.pw-gallery__item--video .pw-video-card__title {
  font-size: var(--pw-text-sm-size); line-height: var(--pw-text-sm-line);
  min-height: calc(2 * var(--pw-text-sm-line));
}
.pw-gallery__item--video .pw-video-card__channel { font-size: var(--pw-text-xs-size); }
/* Filtering is one movement, not twelve. The set crosses — out, swap, in —
   because masonry reflows the moment anything is hidden, and tiles animating
   individually while the columns re-pack underneath them reads as a glitch
   rather than as a filter. */
.pw-gallery__grid {
  transition:
    opacity var(--pw-duration-fast) var(--pw-ease-out),
    transform var(--pw-duration-fast) var(--pw-ease-out);
}
/* The outgoing set settles a few pixels as it goes and the incoming one rises
   back — enough for the eye to read a replacement rather than a repaint, and
   small enough not to become an animation anybody has to sit through. */
.pw-gallery__grid.is-swapping { opacity: 0; transform: translateY(6px); }
@media (prefers-reduced-motion: reduce) { .pw-gallery__grid { transition: none; } }
@media (max-width: 900px) { .pw-gallery__masonry { columns: 2; } }

/* -- Gallery: one photo -----------------------------------------------------
   The step after the grid. It covers the grid rather than replacing it, so
   nothing reflows on the way in and there is a picture to fly to: the thumbnail
   scales up into the stage, which is the whole reason this reads as *opening a
   photo* rather than as a second screen arriving. Because it covers the head as
   well, it carries its own bar — back, the count, and the same close.

   Hidden by visibility rather than `display`, so it is out of the tab order
   while it is out of sight and can still be faded. */
/* The panel takes the height of what is in it, and TRANSITIONS between those
   heights. A dialog that snaps to a new size on a filter press moves the chip
   you just pressed and swallows the swap underneath; a dialog frozen at one
   height leaves a hand's width of nothing under two rows of videos. Animating
   the change is the answer to both — the box is honest about what it holds, and
   it never jumps to get there. The controller sets the two heights; this rule
   is what makes the distance between them take time.

   `:has(> .pw-gallery)` and not a modifier class: it is a property of what the
   panel is holding, so a new gallery cannot forget to ask for it. */
.pw-modal__panel:has(> .pw-gallery) {
  /* A FIXED 80% OF THE SCREEN, not a height the contents decide.

     It used to size to its set, and a set is any size: twenty-two photographs
     put it at its 100vh cap and four left it a third of the screen, so the same
     dialog was a different shape depending on which chip was pressed — and the
     chips moved under the pointer as it changed. A constant height is a constant
     target, and the grid inside it scrolls, which is what a grid is for. */
  height: 80vh;
  transition:
    opacity var(--pw-duration-base) var(--pw-ease-out),
    transform var(--pw-duration-base) var(--pw-ease-out),
    height var(--pw-duration-base) var(--pw-ease-out);
}
@media (prefers-reduced-motion: reduce) { .pw-modal__panel:has(> .pw-gallery) { transition: none; } }

/* `inherit`, not the panel's token spelled out again. The gallery IS the modal
   body, so it takes the panel's corner and the viewer takes the gallery's — a
   full-cover overlay that squared them off was painting over the one thing the
   panel's shape is made of. */
.pw-gallery { position: relative; border-radius: inherit; }
/* A LIGHTBOX, not a panel inside the dialog.

   It used to be `position: absolute; inset: 0` over the gallery's own panel,
   and the arithmetic of that was the problem. The panel is as tall as the GRID
   — twenty-two photographs, so it sits at its 100vh cap — while its width is the
   dialog's 720px, of which the stage gets 568 after the padding and the two nav
   columns. So a 900×598 photograph was drawn at 568×377: SMALLER than the file,
   inside 449px of white. The box was the wrong size in both directions at once,
   and no amount of centring inside it was going to fix that.

   Fixed to the viewport instead. The photograph is bounded by the screen, which
   is the only bound a full view should have, and this one lands at its own
   900×598 — 2.4× the area, and still not upscaled, which the frame rule below
   is careful about.

   The background is a scrim and not a surface, so opening a photograph reads as
   going deeper into the same stack rather than as a second window arriving —
   and a heavier one than the dialog's, because this is the surface whose whole
   job is that nothing behind it competes. It is a
   child of `.pw-modal`, which is above the scrim, so it needs no z-index of its
   own — and because nothing between here and the root sets a transform, filter
   or containment, `fixed` really is the viewport.

   The dialog behind does not move, which is what keeps the flip honest: the
   thumbnail it flies from is still where it was when it was clicked, and still
   there to fly back to. */
.pw-gallery__viewer {
  position: fixed; inset: 0;
  padding: var(--pw-space-10);
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: auto minmax(0, 1fr);
  align-items: center;
  gap: var(--pw-space-6);
  background: var(--pw-bg-lightbox);
  backdrop-filter: blur(var(--pw-blur-lightbox));
  opacity: 0; visibility: hidden;
  transition:
    opacity var(--pw-duration-base) var(--pw-ease-out),
    visibility var(--pw-duration-base) var(--pw-ease-out);
}
.pw-gallery.is-viewing .pw-gallery__viewer { opacity: 1; visibility: visible; }
.pw-gallery__viewbar {
  grid-column: 1 / -1;
  display: flex; align-items: center; gap: var(--pw-space-6);
}
.pw-gallery__exit { flex: 0 0 auto; }
/* The one action a video viewer owes you. It lives in the GATE now, not in the
   bar -- the bar carried a copy of it beside a copy of the byline, so a blocked
   video showed the same sentence twice and two YouTube buttons a thumb apart.
   The gate is the better of the two places, because it is where the reader is
   being asked to decide.

   It rests VISIBLE, unlike the bare glyphs elsewhere in the viewer, because it
   is the only thing on the screen you might be here to do -- and it is the same
   white material as their hover, one step up, so the viewer reads as one set of
   controls rather than two. */
.pw-gallery__watch,
.pw-gate__watch {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: var(--pw-space-4);
  height: 32px;
  padding-inline: var(--pw-space-6);
  border-radius: var(--pw-radius-pill);
  background: var(--pw-bg-on-overlay);
  color: var(--pw-fg-on-overlay);
  font-size: var(--pw-text-sm-size);
  font-weight: var(--pw-font-weight-medium);
  text-decoration: none;
  transition: background-color var(--pw-duration-fast) var(--pw-ease-out);
}
.pw-gallery__watch:hover,
.pw-gate__watch:hover { background: var(--pw-bg-on-overlay-strong); }
.pw-gallery__watch:focus-visible,
.pw-gate__watch:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--pw-bg-lightbox), 0 0 0 4px var(--pw-fg-on-overlay); }
/* The same 14px the video card's mark already uses, and `width: auto` with it.
   A square box was wrong twice over: the YouTube wordmark is a wide rounded
   rectangle, so 18 square both oversized it against a 14px label AND squashed
   it into a shape it is not. Height is the dimension a mark beside text should
   be set by — it is the one the text also has. */
.pw-gallery__watch-mark,
.pw-gate__watch-mark { height: 14px; width: auto; flex: 0 0 auto; display: block; }
/* The words are the one part of the bar that must not be trimmed: the byline
   beside it gives up characters to the ellipsis, and it can, because it is
   description. This is the action, and half an action is not one. */
.pw-gallery__watch-label,
.pw-gate__watch-label { white-space: nowrap; }
/* The controls on the lightbox do not invert, for the same reason its text does
   not: the scrim beneath them is dark in both themes.

   They were `--float-media` pills, which is right over IMAGERY — a white disc on
   a photograph — and that is what the viewer used to be a panel of. It is a
   scrim now, and in dark mode `--float-media` resolves to a gray-850 disc on a
   ground that composites to rgb(9,9,10): the nav chevrons measured 1.48:1
   against their own background, which is to say they were there and you could
   not see them. Non-text controls need 3:1.

   So they lose the disc rather than gaining a lighter one. A white glyph on the
   scrim is what a photo viewer's controls are everywhere, it reads identically
   in both themes, and it stops a row of white pills competing with the
   photograph they are there to serve. The press is the only time a background
   appears, and it is a wash rather than a shape. */
.pw-gallery__viewer .pw-btn--float-media {
  background: transparent;
  box-shadow: none;
  color: var(--pw-fg-on-overlay);
}
.pw-gallery__viewer .pw-btn--float-media:hover,
.pw-gallery__viewer .pw-btn--float-media:active {
  background: var(--pw-bg-on-overlay);
  color: var(--pw-fg-on-overlay);
}
/* `.pw-btn--float-media` fills its focus gap with the surface colour, because
   over a photograph a blue ring at a 2px offset separates from nothing. Here
   the gap is the scrim, which is dark enough to do that job itself. */
.pw-gallery__viewer .pw-btn--float-media:focus-visible {
  box-shadow: 0 0 0 2px var(--pw-bg-lightbox);
}
/* Tabular, because it changes on every press and a proportional "1" would make
   the whole line twitch left and right as the number moves through the set. */
.pw-gallery__count {
  margin-inline-start: auto;
  /* Never squeezed and never wrapped. Nothing shares the line with it now, but
     that is a fact about today's markup rather than about the control. */
  flex: 0 0 auto;
  white-space: nowrap;
  font-size: var(--pw-text-sm-size);
  color: var(--pw-fg-on-overlay);
  font-variant-numeric: tabular-nums;
}
.pw-gallery__stage {
  grid-column: 2;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--pw-space-5);
  min-width: 0; min-height: 0; height: 100%;
  margin: 0;
}
/* `contain` in both places. The masonry thumbnail is the whole uncropped photo,
   so thumb and stage share an aspect ratio — which is what lets the transition
   be a straight scale with nothing sliding inside the frame. */
/* Capped on both axes, and no `object-fit`: the box IS the photo, so the corner
   radius hugs the picture instead of rounding a letterbox around it. The cost
   is that a small source is shown small rather than enlarged, which is the
   right trade — a photo blown past its own resolution is a worse full view
   than a slightly smaller sharp one. */
.pw-gallery__stage .pw-gallery__frame {
  flex: 0 1 auto;
  border-radius: var(--pw-radius-md);
  overflow: hidden;
}
/* A video keeps its 16:9 frame here too, cropped the same way the tile crops
   it. Not only because that is the shape a video is — it also makes the tile's
   rectangle and the stage's rectangle the same shape, which is the condition
   the open transition needs: a scale between two different aspect ratios shows
   the picture changing shape on the way up. */
.pw-gallery__stage.is-video .pw-gallery__frame { width: 100%; aspect-ratio: 16 / 9; max-height: 100%; }
/* `> img`, and the descendant version was a real bug rather than tidiness. The
   frame holds the still AND everything that sits on top of it -- the play
   button, and now the gate, which carries a YouTube wordmark of its own. Any
   `img` inside the frame therefore matched the rule that makes the STILL fill
   the frame, so the wordmark was being stretched to 176x32 and cropped to fit a
   16:9 box. The child combinator says the one thing that was always meant: this
   is about the frame's own picture. */
.pw-gallery__stage.is-video .pw-gallery__frame > img { width: 100%; height: 100%; object-fit: cover; }
/* The badge is centred on the PICTURE, so the frame is its host and not the
   figure — the figure includes the caption, and centring on that would put the
   badge below the middle of the photo by half a line. */
.pw-gallery__frame { position: relative; display: block; min-height: 0; min-width: 0; }
.pw-gallery__frame > img { display: block; max-width: 100%; max-height: 100%; }
.pw-gallery__stage .pw-gallery__play {
  width: 64px; height: 64px;
  transition:
    opacity var(--pw-duration-fast) var(--pw-ease-out),
    visibility var(--pw-duration-fast) var(--pw-ease-out),
    background-color var(--pw-duration-fast) var(--pw-ease-out),
    scale var(--pw-duration-fast) var(--pw-ease-out);
}
/* Only a video gets one. A play control over a still promises something the
   press cannot deliver. Faded rather than cut, so paging from a photo to a
   video does not flash a control into existence mid-transition. */
.pw-gallery__stage:not(.is-video) .pw-gallery__play { opacity: 0; visibility: hidden; }
.pw-gallery__stage .pw-gallery__play:hover { background: #fff; scale: 1.06; }
/* -- When the video will not simply play ------------------------------------
   Three things stand between a reader and a third-party video, and two of them
   are somebody else's decision:

     consent    nothing has been asked of the host yet, and asking is the
                tracking. Playing an embedded video means streaming it from
                whoever hosts it, which tells them who watched — so for a
                PRIVACY product that is a choice to put to the reader, not a
                default to take on their behalf.
     embed-off  the owner turned off playback on other sites.
     region     the owner limited which countries can watch it.

   All three are the same shape: the still stays, a scrim goes over it, and the
   reader is told what is true and given whatever path actually exists. The
   still stays on purpose — it is the one thing here that is not blocked, and a
   grey box would be the page reporting on itself in the space where the video
   was meant to be.

   `.pw-empty` does the block, because "there is nothing to show you and here is
   why" is what an empty state IS, and inventing a second component for it would
   be the same object under a different name. */
/* THE GATE IS NOT THE LIGHTBOX'S. It was built there and it is the same object
   anywhere a video is about to be streamed from somebody else's server -- the
   Videos page needs it inline, with no viewer around it. So the rules are
   written once and carry BOTH names: `.pw-gate*` is the object, and the
   `.pw-gallery__*` names beside it are the ones the viewer's markup and
   controller still use. Not two components; one component and a rename nobody
   has done yet, held to a single declaration so the two can never drift. */
.pw-gallery__gate,
.pw-gate {
  position: absolute; inset: 0;
  display: grid;
  align-content: center; justify-items: center;
  gap: var(--pw-space-5);
  padding: var(--pw-space-8);
  background: var(--pw-bg-lightbox);
  backdrop-filter: blur(var(--pw-blur-scrim));
  border-radius: inherit;
}
/* An empty state standing on imagery rather than on paper. Same reasoning as
   the lightbox's controls: the scrim under it is dark in both themes, so
   nothing on it inverts. */
.pw-empty--on-media { color: var(--pw-fg-on-overlay); }
.pw-empty--on-media .pw-empty__art {
  background: var(--pw-bg-on-overlay);
  color: var(--pw-fg-on-overlay);
}
.pw-empty--on-media .pw-empty__text { color: var(--pw-fg-on-overlay); opacity: 0.8; }
/* Wider than a panel's empty state, because the sentence is doing real work:
   it is the only place the reader is told what playing it actually costs. */
.pw-empty--on-media { max-width: 420px; }
/* The tick, and under it where the standing answer lives.

   The sentence is a SIBLING of the <label> rather than inside it, which is not
   a nicety: a label forwards its clicks to its control, so a link inside one
   toggles the checkbox on the way to being followed. Two elements, and the
   checkbox keeps the words that are actually its name.

   What standing on a scrim costs the checkbox is the COMPONENT's business, not
   this file's: it wears `.pw-choice--on-media` and nothing is restated here. */
.pw-gallery__remember,
.pw-gate__remember {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--pw-space-3);
}
.pw-gallery__remember-note,
.pw-gate__remember-note {
  font-size: var(--pw-text-xs-size);
  color: var(--pw-fg-on-overlay);
  opacity: 0.72;
}
/* `.pw-btn--link` paints itself brand blue, which is correct on paper and
   unreadable on a scrim — and the scrim does not invert, so neither can this.
   It takes the sentence's own colour and says it is a control with an
   underline, which is what a link inside a line of prose does anyway. */
.pw-gallery__prefs,
.pw-gate__prefs {
  color: inherit;
  font-weight: inherit;
  font-size: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.pw-gallery__prefs:hover,
.pw-gate__prefs:hover { color: inherit; opacity: 1; }
/* The two paths out. `--here` plays it where you are and `--away` leaves, which
   is why one is a button and the other is a link — the difference is real and
   the markup should say so before the label does. */
.pw-gallery__gate-here,
.pw-gate__here { flex: 0 0 auto; }
.pw-gallery__gate-away,
.pw-gate__away { flex: 0 0 auto; }
/* The gate covers the picture, so the play button under it must not sit on top
   of its own replacement. */
.pw-gallery__frame:has(> .pw-gallery__gate:not([hidden])) .pw-gallery__play {
  opacity: 0; visibility: hidden;
}
.pw-gallery__nav { grid-row: 2; }
.pw-gallery__nav--prev { grid-column: 1; }
.pw-gallery__nav--next { grid-column: 3; }
/* Phone: the photo takes the width it can get, so the chevrons come off the
   sides and sit on the picture instead of beside it. */
@media (max-width: 640px) {
  .pw-gallery__viewer { grid-template-columns: minmax(0, 1fr); }
  /* `grid-row: 2` and not just the column. The chevrons come off the sides here
     and sit ON the picture, which means they are placed into row 2 explicitly —
     and an explicitly placed item takes the cell, so the stage, which was only
     ever auto-placed, was pushed past them into an implicit THIRD row. The
     viewer measured `60px 499px 188px` and the photograph sat in the bottom
     fifth of the screen with half a phone of scrim above it. */
  .pw-gallery__stage { grid-column: 1; grid-row: 2; }
  .pw-gallery__nav { grid-column: 1; grid-row: 2; justify-self: start; }
  .pw-gallery__nav--next { justify-self: end; }
}

/* -----------------------------------------------------------------------------
   KNOWLEDGE PANEL
   The rail beside the results: what the thing IS, before the list of places
   that mention it. It is a column of sections rather than a card stack —
   sections divided by a rule read as one panel about one subject, where cards
   read as several answers of equal weight, which is the opposite of what a
   knowledge panel claims.

   Structure
     .pw-kpanel > .pw-kpanel__head  ( __title + __subtitle )
                > .pw-kpanel__media
                > .pw-kpanel__desc  ( + .pw-link source )
                > .pw-kpanel__section > .pw-kpanel__facts > .pw-kpanel__fact
   ----------------------------------------------------------------------------- */
/* Its own container too, for the same reason: stacked under the results at
   1200px the panel is 700 wide and behaves like the desktop panel; in a phone
   frame it is 300 and must not. Neither of those is a viewport question. */
.pw-kpanel {
  container-type: inline-size;
  container-name: pw-kpanel;
  display: flex; flex-direction: column; gap: var(--pw-space-8);
}
.pw-kpanel__head { display: flex; flex-direction: column; gap: var(--pw-space-1); }
.pw-kpanel__title {
  margin: 0;
  font-size: var(--pw-text-xl-size); line-height: var(--pw-text-xl-line);
  font-weight: var(--pw-font-weight-semibold);
  color: var(--pw-fg-primary);
}
.pw-kpanel__subtitle { font-size: var(--pw-text-sm-size); color: var(--pw-fg-tertiary); }
.pw-kpanel__media {
  width: 100%; aspect-ratio: 16 / 10;
  border-radius: var(--pw-radius-lg);
  object-fit: cover;
  background: var(--pw-bg-muted);
}
.pw-kpanel__desc {
  margin: 0;
  font-size: var(--pw-text-sm-size); line-height: var(--pw-text-md-line);
  color: var(--pw-fg-secondary);
}
.pw-kpanel__section { padding-top: var(--pw-space-8); border-top: 1px solid var(--pw-border-subtle); }
/* The first section opens the panel; a rule above it would close nothing. */
.pw-kpanel__section:first-child { padding-top: 0; border-top: 0; }
.pw-kpanel__section-head { display: flex; align-items: center; gap: var(--pw-space-4); margin-bottom: var(--pw-space-6); }
.pw-kpanel__section-title {
  margin: 0;
  font-size: var(--pw-text-md-size); line-height: var(--pw-text-md-line);
  font-weight: var(--pw-font-weight-semibold);
  color: var(--pw-fg-primary);
}
/* Two per row, and they wrap rather than scroll: a fact sheet is read down the
   page, not swiped through, and a fact that is off the edge of a scroller is a
   fact nobody knows is there. */
.pw-kpanel__facts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--pw-space-5);
}
/* Two per row at a fixed height, so the four read as one block rather than four
   boxes of different sizes. The value sits at the bottom: a column of labels at
   a shared height is scannable, and the answers line up underneath them. */
.pw-kpanel__fact {
  display: flex; flex-direction: column; justify-content: space-between;
  gap: var(--pw-space-4);
  min-height: 100px;
  padding: var(--pw-space-5) var(--pw-space-6);
  border-radius: var(--pw-radius-xl);
  background: var(--pw-bg-subtle);
}
.pw-kpanel__fact-label {
  display: flex; align-items: center; gap: var(--pw-space-4);
  font-size: var(--pw-text-sm-size); line-height: var(--pw-text-sm-line);
  color: var(--pw-fg-secondary);
}
/* The mark says where the fact came from, not what it is about — every card
   carries the same one. Small and square rather than a circle: a circle at this
   size reads as a bullet, and these are not a list. */
.pw-kpanel__fact-mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; flex: 0 0 auto;
  border-radius: var(--pw-radius-xs);
  background: var(--pw-bg-strong);
  color: var(--pw-fg-secondary);
}
.pw-kpanel__fact-mark .pw-icon { width: 10px; height: 10px; }
.pw-kpanel__fact-value {
  font-size: var(--pw-text-md-size); line-height: var(--pw-text-md-line);
  font-weight: var(--pw-font-weight-medium);
  color: var(--pw-fg-primary);
}
/* Some values are a set rather than a sentence — founders, tickers, tags. They
   become chips so the set is countable at a glance instead of a comma-separated
   line the eye has to parse. */
.pw-kpanel__fact-chips { display: flex; flex-wrap: wrap; gap: var(--pw-space-3); }

/* -- Details ----------------------------------------------------------------
   The long tail of the fact sheet: label on the left, value on the right, one
   row per fact. A definition list, because that is what it is.

   The label column is FIXED at 130px, measured. Letting it size to content
   makes every panel a different shape and the values stop lining up down the
   page — which is the only reason a two-column layout beats a stack here.
   ---------------------------------------------------------------------------- */
.pw-kpanel__details {
  display: grid;
  /* `fit-content(45%)`, not a fixed 130px. Fixed, the column is whatever the
     widest label happens to need and every longer one breaks a word at a time —
     "Number of locations" over three lines beside a value that is one. This
     asks for the label column's own width and caps it before it can start
     eating the values: short labels give the space back, long ones take what
     they need, and inside one panel every value still starts on the same line,
     which is the only alignment a two-column list is for. */
  grid-template-columns: fit-content(45%) minmax(0, 1fr);
  gap: var(--pw-space-5) var(--pw-space-6);
  margin: 0;
}
/* And a label is a phrase, not a paragraph: it may wrap between words when it
   genuinely has to, never inside one. */
.pw-kpanel__term { overflow-wrap: normal; hyphens: none; }
/* Two sizes in the whole panel. Labels and the details list are 14: the label in
   secondary, the value in primary. Only a card's value and a section title are 16.
   Labels used to be 16 beside 14 values, and the card label and value shared a
   size, so nothing said which was which. */
.pw-kpanel__term {
  font-size: var(--pw-text-sm-size); line-height: var(--pw-text-sm-line);
  color: var(--pw-fg-secondary);
}
/* One step down from its label, which looks backwards written out and is right
   on the page: the labels are the column you scan and the values are what you
   stop to read. Live sets 13px; the scale's nearest step is 14 with the same
   20px line, and a bespoke size for one component is not a scale. */
.pw-kpanel__def {
  margin: 0;
  display: flex; flex-direction: column; gap: var(--pw-space-2);
  font-size: var(--pw-text-sm-size); line-height: var(--pw-text-sm-line);
  color: var(--pw-fg-primary);
}
/* Values that are a list of one kind of thing stack; values that are one thing
   said in parts stay inline, separated by a dot. */
.pw-kpanel__def--inline { flex-direction: row; flex-wrap: wrap; gap: var(--pw-space-3); }
.pw-kpanel__def--inline > * + *::before {
  content: "·";
  margin-inline-end: var(--pw-space-3);
  color: var(--pw-fg-quaternary);
}
/* A truncated list says how much it is hiding. "+4 more" is a count, not a
   label — it has to be the real number, for the same reason "See N more" does. */
.pw-kpanel__more {
  align-self: flex-start;
  padding: 0; border: 0;
  background: transparent;
  color: var(--pw-fg-link);
  font: inherit;
  cursor: pointer;
}
.pw-kpanel__more:hover { text-decoration: underline; }

/* The whole fact sheet folds away. `More about X` rather than `Show more`:
   what is behind it is a specific thing, and naming it is the difference
   between an invitation and a shrug. */
.pw-kpanel__disclosure {
  display: flex; align-items: center; gap: var(--pw-space-2);
  width: 100%;
  padding: var(--pw-space-4) 0 0;
  border: 0; background: transparent;
  color: var(--pw-fg-secondary);
  font-family: inherit;
  font-size: var(--pw-text-sm-size); line-height: var(--pw-text-sm-line);
  font-weight: var(--pw-font-weight-semibold);
  text-align: start;
  cursor: pointer;
}
.pw-kpanel__disclosure:hover { color: var(--pw-fg-primary); }
.pw-kpanel__disclosure .pw-icon { transition: transform var(--pw-duration-base) var(--pw-ease-out); }
.pw-kpanel__disclosure[aria-expanded="true"] .pw-icon { transform: rotate(180deg); }

/* -- Profiles ---------------------------------------------------------------
   Where else this thing officially is. Pills rather than a list of links: they
   are all the same kind of destination, and a bulleted list of six would read
   as six separate offers.
   The mark is an <img>, never an icon in the sprite — see assets/brand/README.
   ---------------------------------------------------------------------------- */
.pw-profiles { display: flex; flex-wrap: wrap; gap: var(--pw-space-5); }
.pw-profile {
  display: inline-flex; align-items: center; gap: var(--pw-space-4);
  height: 36px;
  padding-inline: var(--pw-space-6);
  border: 1px solid var(--pw-border-default);
  border-radius: var(--pw-radius-pill);
  background: var(--pw-bg-surface);
  color: var(--pw-fg-primary);
  font-size: var(--pw-text-sm-size);
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--pw-duration-fast) var(--pw-ease-out),
              border-color var(--pw-duration-fast) var(--pw-ease-out);
}
.pw-profile:hover { background: var(--pw-bg-hover); border-color: var(--pw-border-strong); }
.pw-profile__mark { width: 16px; height: 16px; flex: 0 0 auto; object-fit: contain; }
/* Modifiers AFTER the base and after `__mark`, or the blanket 16px below wins
   on specificity ties and the compact row quietly keeps the pill's sizes —
   the same ordering trap this file hit with `.pw-search--sm`. */
/* Icon-only, for the hero card where there is room for marks but not for names.
   Round rather than a pill: with the label gone a pill is just a wide circle,
   and five of them in a row read as five buttons that lost their text. */
.pw-profiles--compact { gap: var(--pw-space-4); }
.pw-profile--icon {
  width: 36px; padding-inline: 0;
  justify-content: center;
  border-radius: var(--pw-radius-circle);
}
/* One box for every mark in the row, whichever kind it is. Without `flex: none`
   an `.pw-icon` in a zero-padding flex button is shrinkable and collapses — the
   globe was rendering 10px wide beside 16px brand glyphs, which is what made it
   look like a different size rather than a different shape. */
.pw-profile--icon > * {
  flex: none;
  width: 18px;
  height: 18px;
}
/* A monochrome brand glyph, painted through a mask rather than drawn as an
   <img>. The file is reproduced exactly — the mask uses its shape and nothing
   else — and because the colour comes from `currentColor` the mark follows the
   theme instead of staying black on a dark surface, which is the one thing an
   <img> of a black glyph cannot do. Coloured, multi-gradient marks still go
   through <img>; see assets/brand/README.md. */
.pw-profile__mark--brand {
  background: currentColor;
  -webkit-mask: var(--pw-brand-glyph) center / contain no-repeat;
  mask: var(--pw-brand-glyph) center / contain no-repeat;
}



/* Narrow: the summary card only. Title, what it is, the description, the facts
   people came for — and none of the long tail. On a phone the fact sheet is
   forty rows of two-column data above the results the person actually searched
   for, which turns the panel from an answer into an obstacle. The details are
   not lost; they are what the knowledge page is for. */
/* A viewport query, and deliberately not a container query beside it. I tried
   pairing the two so a phone FRAME on a wide page would collapse as well, but
   the widths give it away: the rail's panel is 383 and the fact-sheet specimen
   is 370, so any container threshold low enough to catch a phone also empties a
   demo that is showing the panel at its real size. Thirteen pixels is not a
   distinction to hang a rule on. The panel is short on a phone; a phone is a
   narrow viewport. */
@media (max-width: 768px) {
  .pw-kpanel__details,
  .pw-kpanel__disclosure,
  .pw-kpanel__section:has(> .pw-kpanel__details),
  .pw-kpanel__section:has(> .pw-profiles) { display: none; }
  /* Two facts, not four: the grid keeps its two columns and drops to one row,
     because a 2x2 of key facts is taller than the description above it. */
  .pw-kpanel__fact:nth-child(n + 3) { display: none; }
}

/* -----------------------------------------------------------------------------
   ABOUT THIS RESULT
   The panel behind a result's ⋮ — who is publishing this, and why it is in
   front of you. It is a floating panel rather than a menu because none of it
   is a list of commands: most of it is prose the reader has to be able to sit
   with, so it does not close when something inside it is clicked, and it takes
   `role="dialog"` and a close button instead of dismissing on choice.

   It restates the result using the RESULT's own classes — same title, same
   snippet — because it is not a summary of the result, it is the result, seen
   from the side. Restyling it here would make the two drift, and the reader
   would have to work out whether they are looking at the same thing.

   Structure
     .pw-about-result > .pw-about-result__head   (favicon + site + close)
                      > .pw-about-result__result (title + snippet + Visit)
                      > .pw-about-result__actions
                      > .pw-about-result__section > ul.pw-about-result__list
                      > .pw-about-result__note
   ----------------------------------------------------------------------------- */
.pw-about-result {
  /* Placed beside the results column, so it opens in the rail's lane and
     at the rail's distance — see `data-pw-anchor="beside"`. */
  --pw-popover-gap: var(--pw-serp-column-gap);
  /* Wide enough for prose to hold a comfortable measure, and never wider than
     the viewport it is anchored inside. */
  width: min(560px, calc(100vw - 2 * var(--pw-space-8)));
  padding: var(--pw-space-10);
  border: 1px solid var(--pw-border-subtle);
  border-radius: var(--pw-radius-2xl);
  background: var(--pw-bg-surface);
  box-shadow: var(--pw-shadow-xl);
}
/* It does not drop from a trigger the way a menu does — it arrives beside the
   column it is describing, so it comes IN from that column: a short slide
   across the gap, not a fall from above. The direction follows the side the
   controller actually put it on (`data-pw-side`), because the panel changes
   sides when the trailing edge runs out, and a drawer that slides the wrong way
   points at nothing.

   Gated on `[data-pw-layer]` for the same reason every other layer is: with no
   controller on the page there is nothing coming to turn the opacity back on. */
.pw-about-result[data-pw-layer] {
  opacity: 0;
  transform: translateX(-8px);
  transition: opacity var(--pw-duration-base) var(--pw-ease-out),
              transform var(--pw-duration-base) var(--pw-ease-out);
}
.pw-about-result[data-pw-layer][data-pw-side="start"] { transform: translateX(8px); }
.pw-about-result[data-pw-layer].is-open { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .pw-about-result[data-pw-layer] { transition: none; transform: none; }
}
.pw-about-result__head {
  display: flex; align-items: center; gap: var(--pw-space-6);
  margin-bottom: var(--pw-space-10);
}
.pw-about-result__site {
  font-size: var(--pw-text-lg-size);
  line-height: var(--pw-text-lg-line);
  font-weight: var(--pw-font-weight-semibold);
  color: var(--pw-fg-primary);
}
.pw-about-result__close { margin-inline-start: auto; }
/* The result, and the one thing to do about it. `Visit` is start-aligned with
   the title rather than centred on the block: the block is two or four lines
   deep depending on the snippet, and a button that floats to the middle of it
   moves every time the text does. */
.pw-about-result__result {
  display: flex; align-items: flex-start; gap: var(--pw-space-8);
}
.pw-about-result__body { flex: 1; min-width: 0; }
.pw-about-result__actions {
  display: flex; flex-wrap: wrap; gap: var(--pw-space-5);
  margin-top: var(--pw-space-8);
}
.pw-about-result__section { margin-top: var(--pw-space-10); padding-top: var(--pw-space-10); border-top: 1px solid var(--pw-border-subtle); }
.pw-about-result__heading {
  margin: 0 0 var(--pw-space-6);
  font-size: var(--pw-text-md-size);
  line-height: var(--pw-text-md-line);
  font-weight: var(--pw-font-weight-semibold);
  color: var(--pw-fg-primary);
}
.pw-about-result__list {
  margin: 0; padding-inline-start: var(--pw-space-10);
  list-style: disc;
  font-size: var(--pw-text-sm-size);
  line-height: var(--pw-text-md-line);
  color: var(--pw-fg-secondary);
}
.pw-about-result__list > li + li { margin-top: var(--pw-space-3); }
/* The reader's own words, quoted back. Underlined the way a defined term is,
   not the way a link is — there is nowhere to go, it is being pointed at. */
.pw-about-result__term {
  text-decoration: underline dotted;
  text-underline-offset: 3px;
  color: var(--pw-fg-primary);
}
.pw-about-result__note {
  margin-top: var(--pw-space-10); padding-top: var(--pw-space-8);
  border-top: 1px solid var(--pw-border-subtle);
  font-size: var(--pw-text-sm-size);
  color: var(--pw-fg-tertiary);
}

/* -----------------------------------------------------------------------------
   WEB RESULT
   ----------------------------------------------------------------------------- */
/* 30px between results, measured — production writes it as `15px 0 30px` on a
   card, which is the same rhythm with the first gap halved because the row
   above it is the filter bar rather than another result. Written as a bottom
   margin so the space belongs to the result that just ended. */
.pw-result,
.pw-video-result { padding-block: var(--pw-space-6) 0; margin-block-end: 30px; }
.pw-result:last-child,
.pw-video-result:last-child { margin-block-end: 0; }
.pw-result__head { display: flex; align-items: center; gap: var(--pw-space-5); margin-bottom: var(--pw-space-5); }
/* The address, and the control that asks about the address. The ⋮ opens "about
   this result", which is a question about the SOURCE — so it sits beside the
   source, not out at the trailing edge with PrivateView. PrivateView is an
   action on the page; these are two different kinds of thing and putting them
   in one cluster made the ⋮ look like a sibling of the wrong control. */
.pw-result__source { display: flex; align-items: center; gap: var(--pw-space-5); min-width: 0; }
.pw-result__source > .pw-btn { flex: none; }
/* A web result identifies its source by ADDRESS, not by name. Production shows
   the URL and nothing else beside the favicon — I read a site name into an empty
   column wrapper once and it was never there. The address is the honest
   identifier anyway: a name is a claim the page makes about itself, and the
   whole point of showing the source above the title is that it cannot be
   argued with. */
/* 12px, measured — a step below the name it sits under. The address is there to
   be recognised, not read. */
.pw-result__url {
  font-size: var(--pw-text-xs-size); line-height: var(--pw-text-xs-line);
  color: var(--pw-fg-tertiary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.pw-result__head-end { display: flex; align-items: center; gap: var(--pw-space-3); margin-inline-start: auto; flex: 0 0 auto; }
.pw-result__title {
  display: block;
  font-size: var(--pw-text-xl-size);
  line-height: var(--pw-text-xl-line);
  font-weight: var(--pw-font-weight-medium);
  color: var(--pw-fg-link);
}
.pw-result__title:visited { color: var(--pw-fg-link-visited); }
/* A video result's title sits beside a thumbnail rather than leading a block of
   prose, so it drops a step. That is a property of the video result, not
   something each instance should be setting on itself — two anchors carrying
   the same inline font-size is a rule that has not been written down yet. */
.pw-video-result .pw-result__title {
  font-size: var(--pw-text-md-size);
  line-height: var(--pw-text-md-line);
}
.pw-result__title:hover { text-decoration: underline; }
.pw-result__snippet {
  margin-top: var(--pw-space-2);
  font-size: var(--pw-text-sm-size);
  line-height: var(--pw-text-md-line);
  color: var(--pw-fg-secondary);
}
/* Matched query terms. Weight only — the page already carries enough blue. */
/* A matched word inside a SNIPPET goes dark: the snippet is grey, so weight
   alone is not enough of a step. Inside a TITLE it keeps the link's own colour
   and takes weight only — a title is already the loudest line on the result,
   and recolouring half of it makes one link look like two. */
.pw-result__snippet b { font-weight: var(--pw-font-weight-bold); color: var(--pw-fg-primary); }
.pw-result__title b { font-weight: var(--pw-font-weight-bold); color: inherit; }
.pw-result__extra { margin-top: var(--pw-space-3); display: flex; flex-wrap: wrap; gap: var(--pw-space-6); font-size: var(--pw-text-sm-size); color: var(--pw-fg-tertiary); }

/* Sitelinks under a result. */
.pw-sitelinks { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--pw-space-4) var(--pw-space-10); margin-top: var(--pw-space-5); }
.pw-sitelink__title { font-size: var(--pw-text-sm-size); color: var(--pw-fg-link); font-weight: var(--pw-font-weight-medium); }
.pw-sitelink__title:hover { text-decoration: underline; }
.pw-sitelink__text { font-size: var(--pw-text-xs-size); color: var(--pw-fg-tertiary); }

/* The PrivateView affordance — the product's signature result action. */
.pw-privateview {
  display: inline-flex; align-items: center; gap: var(--pw-space-3);
  height: 28px; padding-inline: var(--pw-space-5);
  border: 1px solid var(--pw-border-default);
  border-radius: var(--pw-radius-pill);
  background: var(--pw-bg-surface);
  color: var(--pw-fg-secondary);
  font-size: var(--pw-text-xs-size);
  font-weight: var(--pw-font-weight-bold);
  white-space: nowrap;
  cursor: pointer;
}
.pw-privateview:hover { background: var(--pw-bg-hover); color: var(--pw-fg-primary); }

/* -----------------------------------------------------------------------------
   IMAGE & VIDEO VERTICALS
   ----------------------------------------------------------------------------- */
.pw-image-grid { display: grid; gap: var(--pw-space-4); grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }
@media (max-width: 480px) { .pw-image-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.pw-image-cell {
  position: relative;
  border: 0; padding: 0;
  border-radius: var(--pw-radius-lg);
  background: var(--pw-bg-muted);
  overflow: hidden;
  cursor: pointer;
  aspect-ratio: 1;
}
.pw-image-cell img { width: 100%; height: 100%; object-fit: cover; }
.pw-image-cell__meta {
  position: absolute; inset-inline: 0; bottom: 0;
  padding: var(--pw-space-8) var(--pw-space-5) var(--pw-space-4);
  background: linear-gradient(to top, rgba(0,0,0,.62), rgba(0,0,0,0));
  color: var(--pw-color-white);
  font-size: var(--pw-text-xs-size);
  text-align: start;
}
.pw-image-cell__source { opacity: .82; }

/* The card is its own container. A result is laid out by how much room the
   CARD has, not by how wide the window is — the same card sits in a 700px
   column, in a phone frame on a wide docs page, and in a split view, and only
   one of those three is described by a media query. Same reasoning as the map
   canvas, which learned it first.
   `inline-size` and not `size`: this element must keep its intrinsic height,
   and layout containment on both axes would take it. */
.pw-video-result {
  container-type: inline-size;
  container-name: pw-result;
  display: flex; gap: var(--pw-space-8);
  padding-block: var(--pw-space-6);
}
.pw-video-result__thumb {
  position: relative;
  flex: 0 0 auto; width: 168px; aspect-ratio: 16 / 10;
  border-radius: var(--pw-radius-lg);
  background: var(--pw-bg-muted);
  overflow: hidden;
}
.pw-video-result__thumb img { width: 100%; height: 100%; object-fit: cover; }
.pw-video-result__play {
  position: absolute; inset: 0; margin: auto;
  width: 40px; height: 40px;
  display: grid; place-items: center;
  border: 0; border-radius: var(--pw-radius-circle);
  background: rgba(0, 0, 0, .55);
  color: var(--pw-color-white);
  cursor: pointer;
}
.pw-video-result__duration {
  position: absolute; inset-inline-end: 6px; bottom: 6px;
  padding: 1px 5px;
  border-radius: var(--pw-radius-xs);
  background: rgba(0, 0, 0, .75);
  color: var(--pw-color-white);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
}


/* A narrow card gives the thumbnail less, not the same. At 168px on a 300px
   card the picture is more than half the row and the title wraps to four lines
   — the thumbnail is there to identify the video, and identifying it does not
   take half the width. The play badge and the duration come down with it, or a
   40px disc on a 112px thumb covers the frame it is supposed to preview. */
@container pw-result (max-width: 480px) {
  .pw-video-result { gap: var(--pw-space-6); }
  .pw-video-result__thumb { width: 112px; }
  .pw-video-result__play { width: 32px; height: 32px; }
  .pw-video-result__duration { inset-inline-end: 4px; bottom: 4px; }
}.pw-video-result__body { min-width: 0; }

/* -----------------------------------------------------------------------------
   HORIZONTAL MODULE RAIL  —  hotel and product carousels inside results
   ----------------------------------------------------------------------------- */
.pw-rail-module { position: relative; }
.pw-rail-module__head { display: flex; align-items: baseline; gap: var(--pw-space-5); margin-bottom: var(--pw-space-5); }
.pw-rail-module__title { font-size: var(--pw-text-sm-size); color: var(--pw-fg-tertiary); }
.pw-rail-track {
  display: flex; gap: var(--pw-space-6);
  overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: none;
  /* Both sides, not just the bottom. Setting `overflow-x` forces the other
     axis to compute to `auto` as well, so this box clips vertically whether
     it was asked to or not — and with nothing above the items, anything that
     leaves the box is cut: a card lifting on hover, and, more quietly, the
     2px focus ring at 2px offset that every rail item already had. */
  padding-block: var(--pw-space-2);
  scroll-snap-type: x proximity;
  /* The row ends in a fade, not a cut. A card sliced down the middle by the
     container's edge reads as a rendering fault; the same card dissolving reads
     as "there is more of this". The fade only appears on a side that actually
     has more — a row that fits has hard edges, because there is nothing there
     to suggest.
     Same mechanism as the map chips, which is deliberate: two scrolling rows in
     one product should end the same way. */
  --pw-fade-start: transparent 0, #000 var(--pw-fade-lead, 0px);
  --pw-fade-end: #000 calc(100% - var(--pw-fade-trail, 0px)), transparent 100%;
  -webkit-mask-image: linear-gradient(to right, var(--pw-fade-start), var(--pw-fade-end));
  mask-image: linear-gradient(to right, var(--pw-fade-start), var(--pw-fade-end));
}
.pw-rail-track::-webkit-scrollbar { display: none; }
.pw-rail-track.has-lead  { --pw-fade-lead: 40px; }
.pw-rail-track.has-trail { --pw-fade-trail: 40px; }
.pw-rail-track > * { flex: 0 0 auto; scroll-snap-align: start; }
.pw-rail-module__nav {
  position: absolute; top: 38%;
  inset-inline-end: calc(var(--pw-space-5) * -1);
  display: grid; place-items: center;
  width: 32px; height: 32px;
  border: 1px solid var(--pw-border-subtle);
  border-radius: var(--pw-radius-circle);
  background: var(--pw-bg-surface);
  color: var(--pw-fg-secondary);
  box-shadow: var(--pw-shadow-md);
  cursor: pointer;
  transition: background-color var(--pw-duration-fast) var(--pw-ease-out);
}
.pw-rail-module__nav:hover { background: var(--pw-bg-hover); }
/* The back arrow, on the leading edge. Mirrored with logical properties so it
   is the *back* arrow in Arabic too rather than the left one. */
.pw-rail-module__nav--prev {
  inset-inline-end: auto;
  inset-inline-start: calc(var(--pw-space-5) * -1);
}
.pw-rail-module__foot {
  display: flex; align-items: center; justify-content: center;
  margin-top: var(--pw-space-4); padding-top: var(--pw-space-4);
  border-top: 1px solid var(--pw-border-subtle);
}
/* The trailing "see everything" tile. */
.pw-rail-more {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--pw-space-4);
  width: 132px; min-height: 132px;
  padding: var(--pw-space-6);
  border: 0;
  border-radius: var(--pw-radius-lg);
  background: var(--pw-bg-muted);
  color: var(--pw-fg-secondary);
  font-size: var(--pw-text-xs-size);
  font-weight: var(--pw-font-weight-semibold);
  text-align: center;
  cursor: pointer;
}
.pw-rail-more__mark { display: grid; place-items: center; width: 40px; height: 40px; border-radius: var(--pw-radius-circle); background: var(--pw-bg-surface); }

/* -----------------------------------------------------------------------------
   MODULE HEADER  —  a branded block inside the results
   A ringed source medallion, a display-weight title and a "Related to …" line.
   Used by the Videos module and anything else that groups third-party content.
   ----------------------------------------------------------------------------- */
.pw-module-head { display: flex; align-items: center; gap: var(--pw-space-6); margin-bottom: var(--pw-space-8); }
.pw-module-head__mark {
  display: grid; place-items: center;
  width: 44px; height: 44px; flex: 0 0 auto;
  border-radius: var(--pw-radius-circle);
  background: var(--pw-module-mark-bg, var(--pw-bg-muted));
  color: var(--pw-module-mark-fg, var(--pw-fg-secondary));
  /* The soft outer ring picks up the source colour at low alpha. */
  box-shadow: 0 0 0 8px var(--pw-module-mark-ring, transparent);
}
.pw-module-head__title { font-size: var(--pw-text-xl-size); line-height: var(--pw-text-xl-line); font-weight: var(--pw-font-weight-bold); letter-spacing: var(--pw-tracking-snug); }
.pw-module-head__sub { margin-top: 1px; font-size: var(--pw-text-sm-size); color: var(--pw-fg-tertiary); }
/* Source tints. YouTube red is the brand's, not ours — it identifies the source. */
.pw-module-head--youtube { --pw-module-mark-bg: var(--pw-bg-surface); --pw-module-mark-ring: rgba(255, 0, 51, .10); }
/* A medallion holding a real trademark carries no fill of its own: the mark
   already has one, and a red disc behind a red mark is the brand's colour used
   twice, once by its owner and once by us. The ring stays — that is ours. */
.pw-module-head--youtube .pw-module-head__mark { box-shadow: 0 0 0 8px var(--pw-module-mark-ring), inset 0 0 0 1px var(--pw-border-subtle); }

/* -----------------------------------------------------------------------------
   VIDEO CARD  —  the Videos carousel inside the results
   ----------------------------------------------------------------------------- */
.pw-video-card {
  display: flex; flex-direction: column; gap: var(--pw-space-4);
  width: 232px;
  border: 0; padding: 0; background: none;
  text-align: start; cursor: pointer;
}
.pw-video-card__thumb {
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: var(--pw-radius-lg);
  background: var(--pw-bg-muted);
  overflow: hidden;
}
.pw-video-card__thumb img { width: 100%; height: 100%; object-fit: cover; }
/* Shared with the gallery's video tiles. Two hosts, one declaration: a play
   badge drawn twice is a play badge that will be drawn differently the third
   time. */
.pw-video-card__play,
.pw-gallery__play {
  position: absolute; inset: 0; margin: auto;
  display: grid; place-items: center;
  width: 48px; height: 48px;
  /* The badge is a <span> on a tile and a <button> on the stage. Only one of
     those two comes with a border and padding from the user agent, which is
     exactly the kind of difference that shows up as "why is this one ringed" —
     so the rule zeroes them and the host stops mattering. */
  border: 0; padding: 0; cursor: pointer;
  border-radius: var(--pw-radius-circle);
  background: rgba(255, 255, 255, .82);
  color: var(--pw-color-gray-950);
  transition: background-color var(--pw-duration-fast) var(--pw-ease-out), scale var(--pw-duration-fast) var(--pw-ease-out);
}
.pw-video-card:hover .pw-video-card__play,
.pw-gallery__item:hover .pw-gallery__play { background: #fff; scale: 1.06; }
.pw-video-card__duration,
.pw-gallery__duration {
  position: absolute; inset-inline-end: 8px; bottom: 8px;
  padding: 2px 6px;
  border-radius: var(--pw-radius-sm);
  background: rgba(0, 0, 0, .72);
  color: var(--pw-color-white);
  font-size: var(--pw-text-xs-size);
  font-variant-numeric: tabular-nums;
}
.pw-video-card__title {
  font-size: var(--pw-text-md-size);
  line-height: var(--pw-text-md-line);
  font-weight: var(--pw-font-weight-semibold);
  color: var(--pw-fg-link);
}
.pw-video-card:hover .pw-video-card__title { text-decoration: underline; }
.pw-video-card__channel { display: flex; align-items: center; gap: var(--pw-space-3); font-size: var(--pw-text-sm-size); color: var(--pw-fg-secondary); }
/* The source's own mark, not a red rectangle with our play glyph in it. That
   was a trademark redrawn on our icon grid, which is the one thing a brand mark
   may never be — see assets/brand/README.md. */
.pw-video-card__mark { height: 14px; width: auto; flex: 0 0 auto; display: block; }
.pw-video-card__date { font-size: var(--pw-text-sm-size); color: var(--pw-fg-tertiary); }

/* -----------------------------------------------------------------------------
   VIDEO GRID  —  the same card, laid out as a block rather than a rail
   A rail says "there is more sideways, keep going"; a grid says "here is the
   set". Related videos are a set: the reader is choosing one, not browsing, and
   choosing means seeing them at once. Three across and two deep is the most a
   result column holds before the module stops being a module and becomes the
   page — the rest is behind one press.
   ----------------------------------------------------------------------------- */
/* The container is the module, not the grid. A container query matches against
   an ANCESTOR container — an element cannot query its own width — so putting
   `container-type` on the grid itself silently does nothing, which is the worst
   kind of nothing: the rules parse, the breakpoints never fire, and the layout
   looks correct on the one width it was built at. */
.pw-video-module { container-type: inline-size; container-name: pw-videogrid; }
.pw-video-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--pw-space-10) var(--pw-space-8);
}
/* The rail's card is 232px because a rail has no columns to fill. In a grid the
   column IS the width, so the card gives that decision up. */
.pw-video-grid > .pw-video-card { width: auto; }
/* Two lines' worth of room whether or not the title needs both. In a rail there
   is one row and nothing to line up with; in a grid the card below is reading
   its neighbour's channel line across, and a one-line title above a two-line
   one staggers the whole row. Reserved, not clamped — `pw-clamp-2` already caps
   the top end. */
.pw-video-grid .pw-video-card__title { min-height: calc(2 * var(--pw-text-md-line)); }
/* Spans the row: the trigger belongs to the whole set, and a button sitting in
   the first column would read as an item of it. Inside the group rather than
   after it, because the height animation measures the group. */
.pw-video-grid__more { grid-column: 1 / -1; justify-self: center; margin-block-start: var(--pw-space-2); }
/* Container, not viewport: this module sits in a 700px result column on the
   page and in a full-width demo frame in the docs, and only one of those is
   described by a media query. */
@container pw-videogrid (max-width: 620px) {
  .pw-video-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@container pw-videogrid (max-width: 360px) {
  .pw-video-grid { grid-template-columns: minmax(0, 1fr); }
}

/* -----------------------------------------------------------------------------
   VIDEO RESULTS  —  the Videos vertical, which is one set in two shapes

   The module above is six videos inside a page about something else. This is
   the page: nothing but videos, as many as there are, and the reader is
   scanning rather than choosing from a short list. That changes two things.

   THE COLUMNS ARE NOT COUNTED. A module picks three because three is what fits
   beside a result column; a page has no such number -- it has whatever the
   window gives it -- so the track is a minimum and the browser does the
   arithmetic. `auto-fill` also means no breakpoints to keep in step, which is
   the real saving: the module needs two container queries to go 3 -> 2 -> 1 and
   this needs none.

   AND THE SHAPE IS A SETTING. A grid is for recognising, a list is for reading:
   the grid gives every video the same seat and lets a thumbnail do the work,
   the list gives the title the width and turns the page into something you go
   down. Which one is right depends on whether the reader knows what they are
   looking for, and only they know that -- so it is theirs to set, not ours to
   guess. It is the SAME CARD in both, laid out differently; two markups would
   be two things to keep in step and a JavaScript swap between them.
   ----------------------------------------------------------------------------- */
.pw-video-results { display: grid; }
.pw-video-results > .pw-video-card { width: auto; }

.pw-video-results[data-pw-view="grid"] {
  grid-template-columns: repeat(auto-fill, minmax(216px, 1fr));
  gap: var(--pw-space-10) var(--pw-space-8);
}
/* Two lines' worth of room whether or not the title needs both -- the card
   below is reading its neighbour's channel line across, and a one-line title
   above a two-line one staggers the whole row. */
.pw-video-results[data-pw-view="grid"] .pw-video-card__title { min-height: calc(2 * var(--pw-text-md-line)); }

.pw-video-results[data-pw-view="list"] { grid-template-columns: minmax(0, 1fr); gap: var(--pw-space-8); }
/* The card turns sideways WITHOUT changing: the thumb takes the first column
   and spans it, and the three lines it already has stack in the second by
   auto-placement. The thumb's width is a percentage capped at the grid card's
   own 216 -- the same reason the subpage's padding is: this list is a page on a
   laptop and a 320px frame in the docs, and a percentage answers about the
   element while a media query answers about the window. */
.pw-video-results[data-pw-view="list"] .pw-video-card {
  grid-template-columns: min(216px, 38%) minmax(0, 1fr);
  grid-template-rows: auto auto auto auto;
  align-content: start;
  column-gap: var(--pw-space-8);
  row-gap: var(--pw-space-3);
}
.pw-video-results[data-pw-view="list"] .pw-video-card { display: grid; }
/* `align-self: start`, or the thumb STRETCHES. It spans every row, and a grid
   item that spans rows taller than itself is stretched to fill them -- which
   for a box with an `aspect-ratio` means the ratio loses and the still is
   squashed. The description is what makes the text column taller than the
   picture, so this rule and that one arrived together. */
.pw-video-results[data-pw-view="list"] .pw-video-card__thumb { grid-row: 1 / -1; grid-column: 1; align-self: start; }
/* THE DESCRIPTION IS THE LIST'S, and only the list's. A grid is for
   recognising: the thumbnail answers "is this the one" and a paragraph under it
   would be read by nobody and would make twelve cards twelve different heights.
   A list is for reading -- it gave the title the width, and the description is
   what that width is FOR. Two lines, because the third is where a snippet stops
   helping you choose and starts being the thing you were choosing. */
.pw-video-card__desc { display: none; }
.pw-video-results[data-pw-view="list"] .pw-video-card__desc {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  font-size: var(--pw-text-sm-size);
  line-height: var(--pw-text-md-line);
  color: var(--pw-fg-secondary);
}
/* The badge is drawn for a 216px still. On a phone the row's thumb is 38% of a
   narrow column -- 114px -- and a fixed 48px disc on that covers a third of the
   picture it is inviting you to look at. A percentage of the thumb keeps the
   same proportion the grid has, and the cap keeps it from growing past it.
   ONE AXIS, THEN THE RATIO. A percentage width resolves against the thumb's
   width and a percentage height against its HEIGHT, and the thumb is 16:10 --
   so the same 30% written twice is two different numbers, and the disc came out
   48 wide by 40.5 tall. The width is the measurement; the circle is
   `aspect-ratio`. */
.pw-video-results[data-pw-view="list"] .pw-video-card__play {
  width: min(48px, 30%);
  height: auto;
  aspect-ratio: 1;
}
/* This row is NOT `.pw-video-result`. That one is a web result that happens to
   be a video -- a title link, a URL, a snippet, sitting among ten blue links.
   This is the vertical's own tile turned on its side, and it carries what the
   tile carries. Same words, different objects, and merging them would give the
   Videos page a URL line it has no use for. */

/* -----------------------------------------------------------------------------
   VIDEO PREVIEW  —  what a press on a card opens, WITHOUT leaving the page

   A lightbox is right when the picture is the whole point and the page behind
   it is not. A video result is the opposite case: the reader is comparing
   twelve of them, and the question they are answering is "is this the one" --
   which is a question about the set, not about this video. Covering the set to
   answer it is the wrong move. So the panel opens IN the grid, at the end of
   the row the card is in, and everything below shifts down: the reader keeps
   their place, their scroll position and their other eleven candidates.

   It opens with the GATE up, never with a player. Playing a video means
   streaming it from whoever hosts it, which tells them who watched; for a
   privacy product that is a question to put to the reader rather than a default
   to take on their behalf. Nothing is requested from the host until they
   answer it, and the still they were already looking at stays -- it is the one
   thing here that is not blocked.
   ----------------------------------------------------------------------------- */
.pw-video-preview {
  grid-column: 1 / -1;
  position: relative;
  /* Flex-basis, not a breakpoint: the two halves each declare the width they
     need and wrap when the row cannot hold both. The frame's basis is larger,
     so on a wide row it takes the extra space and the facts column stays the
     column it is. */
  display: flex; flex-wrap: wrap; gap: var(--pw-space-8);
  padding: var(--pw-space-8);
  border-radius: var(--pw-radius-xl);
  background: var(--pw-bg-subtle);
  overflow: hidden;   /* the height animation measures this box */
}
.pw-video-preview__frame {
  position: relative;
  /* THE PICTURE IS SIZED FROM THE WINDOW, not from the column. A 16:9 box
     measured off its own width is whatever the grid happens to leave: 315px
     tall on this page, and the same 315 on a laptop with half the screen and on
     a monitor with three times it. What the reader is judging is a picture, and
     how big a picture can be is a question about how much of the screen they
     can see -- so the height is the measurement and the width follows from the
     ratio. Clamped at both ends: never so short it is a thumbnail again, never
     so tall that the next row of results leaves the screen with it. */
  flex: 0 1 auto; min-width: 0;
  height: clamp(200px, 44vh, 440px);
  width: auto;
  /* The facts column is not negotiable -- it is what the preview is FOR after
     the picture -- so the picture takes what is left rather than the reverse. */
  max-width: min(66%, 100%);
  aspect-ratio: 16 / 9;
  border-radius: var(--pw-radius-lg);
  background: var(--pw-color-gray-950);
  overflow: hidden;
}
/* A FLOOR WHILE THE QUESTION IS UP. 44vh on a 560px-tall laptop is 246, and the
   question needs 256 to 270 across the widths this layout covers -- measured,
   not guessed -- so the "remember" line fell off the bottom of the picture. The
   floor is the question's, and it lifts the moment the question is answered:
   after that the picture is only a picture and can be as small as the window
   wants it. */
.pw-video-preview__frame:has(> .pw-gate:not([hidden])) { height: clamp(340px, 44vh, 440px); }
/* And a last resort under that, for a window shorter than anything measured
   here. A question you have to scroll is bad; a question with its answer cut
   off is worse -- and `safe` is what makes the scroll actually reach it: a
   CENTRED block that overflows spills out of both ends at once, and the end it
   spills out of at the top is the one scrolling cannot get back to. Same rule
   as the starters rail, one axis over. */
.pw-video-preview .pw-gate { overflow-y: auto; align-content: safe center; }
.pw-video-preview__frame > img,
.pw-video-preview__still { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.pw-video-preview__meta { flex: 1 1 260px; min-width: 0; display: flex; flex-direction: column; gap: var(--pw-space-5); padding-inline-end: var(--pw-space-10); }
.pw-video-preview__title {
  margin: 0;
  font-size: var(--pw-text-lg-size);
  line-height: var(--pw-text-lg-line);
  font-weight: var(--pw-font-weight-semibold);
  color: var(--pw-fg-primary);
}
/* Facts, not prose: one per line with the same glyph column, because they are
   read by jumping between them rather than along them. */
.pw-video-preview__facts { display: flex; flex-direction: column; gap: var(--pw-space-3); margin: 0; padding: 0; list-style: none; }
.pw-video-preview__fact { display: flex; align-items: center; gap: var(--pw-space-4); font-size: var(--pw-text-sm-size); color: var(--pw-fg-secondary); }
.pw-video-preview__fact > .pw-icon { flex: none; color: var(--pw-fg-quaternary); }
.pw-video-preview__fact-value { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The way out to the host, on paper this time rather than on a scrim -- so it
   is a link in the system's own colours, not the gate's overlay pill. */
.pw-video-preview__source { display: inline-flex; align-items: center; gap: var(--pw-space-4); margin-block-start: auto; font-size: var(--pw-text-sm-size); }
.pw-video-preview__source-mark { height: 14px; width: auto; flex: 0 0 auto; display: block; }
.pw-video-preview__close { position: absolute; inset-block-start: var(--pw-space-5); inset-inline-end: var(--pw-space-5); }

/* ON A PHONE IT IS A SCREEN, not a panel in a row. Inline is right where the
   set is what the reader is working with -- keep your place, keep the other
   eleven candidates. On a 375px screen there are no other candidates in view:
   a 16:9 frame is the width of the phone, the gate has to fit inside it, and
   what is left of the row underneath is nothing worth keeping your place in.
   So it stops pretending to be a panel and becomes the thing it now is.

   A MEDIA QUERY, and this is the case for one. Everywhere else in this system
   the question is "how wide is this box" and a media query answers the wrong
   one -- but "cover the screen" IS a statement about the screen, and the box
   this element is in has stopped being the subject. */
@media (max-width: 640px) {
  .pw-video-preview {
    position: fixed; inset: 0; z-index: var(--pw-z-modal);
    flex-direction: column; flex-wrap: nowrap;
    gap: var(--pw-space-6);
    padding: var(--pw-space-8);
    border-radius: 0;
    background: var(--pw-bg-canvas);
    overflow-y: auto;
  }
  /* The frame gives up its flex basis: on one column a 360px floor is a
     horizontal scrollbar. */
  .pw-video-preview__frame { flex: 0 0 auto; width: 100%; }
  /* THE POSTER GOES PORTRAIT WHILE THE QUESTION IS ON IT. A 16:9 frame at phone
     width is 190px tall and the question needs 320: the art was clipped off the
     top and the "remember" line was outside the frame altogether, because a
     layer laid over a box adds no height to it. Rather than unstack them -- two
     boxes where there was one, and a still that stops being the thing the
     question is about -- the frame simply takes the shape that holds both, and
     the gate's own centring does the rest.
     It goes back to 16:9 the moment the question is answered: `:has` reads the
     gate's `hidden`, so the still gets its real shape back for watching. */
  .pw-video-preview__frame { aspect-ratio: 4 / 5; }
  .pw-video-preview__frame:has(> .pw-gate[hidden]) { aspect-ratio: 16 / 9; }
  .pw-video-preview__meta { flex: 0 0 auto; padding-inline-end: 0; }
  /* The close control is the only way back now, so it takes a surface instead
     of sitting transparent over whatever it lands on. */
  .pw-video-preview__close { position: sticky; inset-block-start: 0; align-self: flex-end; order: -1; }
  .pw-video-preview__source { margin-block-start: var(--pw-space-4); }
}

/* The switch itself sits at the end of the filter row: it is not a filter --
   it changes nothing about WHICH videos are here -- so it is pushed away from
   the ones that are, and keeps the row's own end. */
.pw-video-toolbar { display: flex; align-items: center; gap: var(--pw-space-4); }
.pw-video-toolbar > .pw-filter-bar { flex: 1 1 auto; }
.pw-video-toolbar > .pw-segmented { flex: 0 0 auto; margin-inline-start: auto; }

/* -----------------------------------------------------------------------------
   DIVIDER ACTION  —  "View all videos", a pill centred in a rule
   ----------------------------------------------------------------------------- */
.pw-divider-action { display: flex; align-items: center; gap: var(--pw-space-8); margin-top: var(--pw-space-8); }
.pw-divider-action::before,
.pw-divider-action::after { content: ""; flex: 1; height: 1px; background: var(--pw-border-subtle); }

/* -----------------------------------------------------------------------------
   PRODUCT / PROPERTY TILE  (shared by the search modules and the shopping grid)
   ----------------------------------------------------------------------------- */
.pw-tile { display: flex; flex-direction: column; gap: var(--pw-space-3); width: 132px; border: 0; background: none; padding: 0; text-align: start; cursor: pointer; }
.pw-tile--lg { width: 180px; }
.pw-tile__media {
  aspect-ratio: 1;
  border-radius: var(--pw-radius-lg);
  background: var(--pw-bg-surface);
  border: 1px solid var(--pw-border-subtle);
  overflow: hidden;
}
/* Product photography is `contain` on a light surface — cropping a product is
   worse than letterboxing it. Place photography is `cover`. */
.pw-tile__media img { width: 100%; height: 100%; object-fit: contain; }
.pw-tile--place .pw-tile__media img { object-fit: cover; }
.pw-tile__title { font-size: var(--pw-text-xs-size); font-weight: var(--pw-font-weight-medium); color: var(--pw-fg-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pw-tile__source { font-size: var(--pw-text-xs-size); color: var(--pw-fg-tertiary); }

/* -----------------------------------------------------------------------------
   RATING · PRICE · MERCHANT
   ----------------------------------------------------------------------------- */
.pw-rating { display: inline-flex; align-items: center; gap: var(--pw-space-3); font-size: var(--pw-text-xs-size); color: var(--pw-fg-secondary); font-variant-numeric: tabular-nums; font-weight: var(--pw-font-weight-medium); }
/* Filled, not outlined. A rating is read at a glance and at 12px an outlined
   star is mostly its own stroke — the shape reads as a ring rather than a
   star, and half a row of them is visual noise where the number beside it is
   already the answer. The sprite is stroke-based, so the fill is applied here
   rather than by shipping a second glyph. */
.pw-rating__stars { display: inline-flex; gap: 1px; color: var(--pw-color-warning-500); }
.pw-rating__stars .pw-icon { width: 12px; height: 12px; fill: currentColor; stroke-width: 1; }
/* An unearned star keeps the outline: filled-but-grey reads as a star you have
   rather than one you have not. */
.pw-rating__stars .is-empty { color: var(--pw-border-strong); fill: none; stroke-width: var(--pw-icon-stroke); }
.pw-rating__count { color: var(--pw-fg-tertiary); }

.pw-price { display: flex; align-items: baseline; gap: var(--pw-space-3); flex-wrap: wrap; }
.pw-price__was { font-size: var(--pw-text-xs-size); color: var(--pw-fg-tertiary); text-decoration: line-through; }
.pw-price__now { font-size: var(--pw-text-lg-size); font-weight: var(--pw-font-weight-bold); font-variant-numeric: tabular-nums; }
.pw-price__now--sm { font-size: var(--pw-text-sm-size); font-weight: var(--pw-font-weight-semibold); }
.pw-price__total { font-size: var(--pw-text-xs-size); color: var(--pw-fg-tertiary); }
.pw-price__unit  { font-size: var(--pw-text-xs-size); color: var(--pw-fg-quaternary); }

.pw-merchant { display: inline-flex; align-items: center; gap: var(--pw-space-3); font-size: var(--pw-text-xs-size); color: var(--pw-fg-secondary); min-width: 0; }
.pw-merchant__mark { width: 16px; height: 16px; border-radius: var(--pw-radius-circle); flex: 0 0 auto; object-fit: contain; }
.pw-merchant__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* -----------------------------------------------------------------------------
   LOCAL PACK  —  the map module inside web results
   ----------------------------------------------------------------------------- */
.pw-local-pack { border: 1px solid var(--pw-border-subtle); border-radius: var(--pw-radius-xl); overflow: hidden; background: var(--pw-bg-surface); }
.pw-local-pack__map { position: relative; aspect-ratio: 16 / 7; background: var(--pw-bg-muted); }
.pw-local-pack__expand { position: absolute; inset-inline-end: var(--pw-space-5); bottom: var(--pw-space-5); }
.pw-local-pack__row { display: flex; align-items: flex-start; gap: var(--pw-space-6); padding: var(--pw-space-6) var(--pw-space-8); }
.pw-local-pack__row + .pw-local-pack__row { border-top: 1px solid var(--pw-border-subtle); }
.pw-local-pack__index { font-size: var(--pw-text-sm-size); font-weight: var(--pw-font-weight-bold); }
.pw-local-pack__body { flex: 1; min-width: 0; }
.pw-local-pack__thumb { flex: 0 0 auto; width: 64px; height: 48px; border-radius: var(--pw-radius-md); background: var(--pw-bg-muted); overflow: hidden; }
.pw-local-pack__foot { display: flex; justify-content: center; padding: var(--pw-space-5); border-top: 1px solid var(--pw-border-subtle); }


/* -----------------------------------------------------------------------------
   The search field keeps its height inside a flex column. Without this it gets
   squashed by `align-items: stretch` and the pill collapses.
   ----------------------------------------------------------------------------- */
/* `min-width: 0` because a flex item defaults to `min-width: auto` and refuses
   to shrink below its content — and this field's content includes an <input>,
   which carries an intrinsic default width of its own. In a narrow row the
   field therefore pushed past its container instead of narrowing, which is the
   one thing a search field must always be willing to do. */
.pw-search { flex: 0 0 auto; box-sizing: border-box; min-height: 48px; min-width: 0; }
/* Modifiers AFTER the base. These are all one class deep, so the cascade is
   decided by order alone — and with the blanket rule written last, every
   `--sm` field in the product was quietly 48px tall while its own rule said
   40. It is why the small maps launcher matched the full-size field beside it
   and why a 40px button next to it looked wrong: the button was right. */
.pw-search--sm { height: 40px; min-height: 40px; }
.pw-search--xs { min-height: 32px; }
.pw-search--lg { height: 56px; min-height: 56px; }
/* And the results header's field, for exactly the same reason. Its own block is
   written far above this one, so the blanket `flex: 0 0 auto; width: 100%` was
   winning on order and the field could neither hold its 700 nor shrink below
   its content. Sizing belongs next to the rule it has to beat.
   The 700 is a flex BASIS, not a width, and that distinction is the whole rule.
   A width is honoured in every layout: as a grid item — which is what this same
   field is in the phone header — `width: 700px` makes the track 700 wide and
   the frame overflows, and `max-width: 100%` cannot save it because the track
   it is measuring against is the 700 the width just created. A basis is read
   only by a flex container. So the desktop header gets its 700 and its shrink,
   and everywhere else the field simply fills what it is given. */
.pw-serp-header__search {
  flex: 0 1 var(--pw-serp-field-width);
  height: var(--pw-serp-field-height);
  min-height: var(--pw-serp-field-height);
  /* Restating the base field's two transitions because this replaces them, not
     adds to them. Dropping height in and losing the focus ring's fade would be
     a poor trade. */
  transition:
    border-color var(--pw-duration-fast) var(--pw-ease-out),
    box-shadow var(--pw-duration-base) var(--pw-ease-out),
    height var(--pw-duration-base) var(--pw-ease-out),
    min-height var(--pw-duration-base) var(--pw-ease-out);
}

/* -- Condensed --------------------------------------------------------------
   `.is-condensed` is the live state the scroll controller sets on the chrome;
   `--condensed` is the same thing pinned open for a specimen — a bar with no
   chrome around it to condense. They must not drift, so they are written as one
   selector list rather than as two rules that agree today. (`--scrolled` is a
   different thing and stays one: it draws the rule that closes the chrome off
   from the page, at any height.)

   Only the vertical measure moves. The logo inset, the field's start and the
   700 cap are all the same numbers as at rest — the bar tightens around the
   field, it does not rearrange itself, and nothing under the pointer slides
   sideways while the reader is scrolling.
   ------------------------------------------------------------------------- */
.pw-serp-chrome.is-condensed .pw-serp-header,
.pw-serp-header--condensed {
  height: var(--pw-serp-header-condensed);
}
.pw-serp-chrome.is-condensed .pw-serp-header__search,
.pw-serp-header--condensed .pw-serp-header__search {
  --pw-search-height: var(--pw-serp-field-condensed);
  height: var(--pw-serp-field-condensed);
  min-height: var(--pw-serp-field-condensed);
}
/* A phone header is two rows — the mark line, then the field under it — and a
   fixed 64 cannot hold both: the field falls straight out of the bar. There the
   same idea applies to the shape that is actually there, so it tightens its
   padding and keeps its own height. */
.pw-serp-header--condensed.pw-serp-header--stacked { height: auto; }
@media (max-width: 768px) {
  .pw-serp-chrome.is-condensed .pw-serp-header {
    height: auto;
    padding-block: var(--pw-space-3);
  }
}
@media (prefers-reduced-motion: reduce) {
  .pw-serp-header,
  .pw-serp-header__search { transition: none; }
}

/* ======================================================================== *
 * SUGGESTIONS — the autocomplete list attached to the search field.
 *
 * It is not a floating popover: it shares the field's border, drops the
 * seam between them and rounds only its bottom corners, so field + list
 * read as one capsule. The field owns `role="combobox"`, the list owns
 * `role="listbox"`; movement is virtual (aria-activedescendant), so focus
 * never leaves the input and typing keeps working.
 * ======================================================================== */
/* Every horizontal measurement in the list is inherited from the field, so
   the two can never drift apart: the suggestion's magnifier lands on exactly
   the same pixel column as the field's, and the suggestion text starts where
   the typed text does. Change the field's padding and the list follows. */
.pw-suggest-shell { position: relative; }

/* The defaults live on the SHELL, and the list reads them through inheritance
   with a fallback at each point of use. That distinction is the whole rule.

   They used to be declared on both, which looks equivalent and is not: a
   declaration on the list beats a value inherited from the shell, so every
   shell override reached the field and none of them reached the list. A 40px
   field squared its top corners at 20 while the list closed on 24, and the
   capsule the pair are supposed to read as had two different curves on its two
   ends. Nobody had noticed because the only sizes in use agreed by accident.

   The fallbacks keep a list rendered with no shell at all — a docs example, a
   mobile sheet — from having no geometry. */
.pw-suggest-shell {
  --pw-suggest-pad: var(--pw-space-8);      /* = .pw-search padding-inline */
  --pw-suggest-gap: var(--pw-space-6);      /* = .pw-search gap            */
  --pw-suggest-glyph: var(--pw-icon-md);    /* = the field's leading icon  */
  --pw-suggest-radius: 24px;                /* = half the field's height   */
}
.pw-suggest-shell:has(.pw-search--sm) {
  --pw-suggest-pad: var(--pw-space-6);
  --pw-suggest-gap: var(--pw-space-4);
  --pw-suggest-glyph: var(--pw-icon-sm);
  --pw-suggest-radius: 20px;
}
.pw-suggest-shell:has(.pw-search--lg) { --pw-suggest-radius: 28px; }
/* A route field is 44, so its capsule closes on 22 — the same "half the field's
   height" every other size here follows.

   Its padding, gap and glyph are its own numbers rather than `.pw-search`'s,
   and they were the one size in the system that never said so: the list kept
   the 48px field's 16/12/20 while the field it hangs from is 12/10/16. Four
   pixels of icon and ten of text column, so the magnifier sat outboard of the
   pin above it and every suggestion started to the right of the word being
   typed. The list is supposed to be the field continued downward; there it was
   a second, wider thing wearing the same corner. */
.pw-suggest-shell.pw-route-field {
  --pw-suggest-pad: var(--pw-space-6);      /* = .pw-route-field padding-inline */
  --pw-suggest-gap: var(--pw-space-5);      /* = .pw-route-field gap            */
  --pw-suggest-glyph: var(--pw-icon-sm);    /* = .pw-route-field__pin           */
  --pw-suggest-radius: 22px;
}

/* Open, the field and the list are one capsule, so they must behave like one
   box: a single shadow, and a top radius that is the *same number* as the
   list's bottom radius rather than whatever the pill happens to clamp to at
   this height. Left as a pill, the corner is half the field's height — it
   agrees with the list's 24px only while the field is exactly 48px tall. */
/* The second selector is for a shell that IS the field rather than one that
   contains one. A route field carries its own pill and its own shadow, so with
   the list open it kept a fully rounded bottom edge under a list with square
   top corners — two shapes pretending to be one capsule, and the join reading
   as a mistake because it is. Same four corners either way; only the element
   they are on differs. */
.pw-suggest-shell.is-open > .pw-search,
.pw-suggest-shell.pw-route-field.is-open {
  border-start-start-radius: var(--pw-suggest-radius);
  border-start-end-radius: var(--pw-suggest-radius);
  border-end-start-radius: 0;
  border-end-end-radius: 0;
  border-bottom-color: transparent;
  /* The list carries the shadow for the whole capsule. Two shadows meeting
     at the seam draw a grey line straight across the join. */
  box-shadow: none;
}
.pw-suggest-shell.is-open > .pw-search:focus-within,
.pw-suggest-shell.pw-route-field.is-open:focus-within { box-shadow: none; }

.pw-suggest {
  position: absolute;
  inset-inline: 0;
  top: calc(100% - 1px);
  z-index: var(--pw-z-dropdown);
  box-sizing: border-box;
  max-height: 60vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-block-end: var(--pw-space-8);
  background: var(--pw-bg-surface);
  border: 1px solid var(--pw-border-default);
  border-top: 0;
  /* The field is a pill, so its corner is half its height. The list ends on
     the same curve — a tighter or looser one breaks the single-capsule read. */
  border-end-start-radius: var(--pw-suggest-radius, 24px);
  border-end-end-radius: var(--pw-suggest-radius, 24px);
  box-shadow: var(--pw-shadow-lg);
}
/* ABOVE THE FIELD, and detached from it. A composer sits at the bottom of what
   it belongs to, so its list has nowhere to fall -- and once the list is above,
   the single-capsule read is not available anyway: the field's own top corners
   are rounded and the list is a separate card with a gap under it. So it takes
   all four corners, its own hairline and its own shadow, and the seam goes,
   because there is no join left for a seam to mark. */
.pw-suggest--above {
  top: auto;
  bottom: calc(100% + var(--pw-space-4));
  border: 1px solid var(--pw-border-subtle);
  border-radius: var(--pw-radius-xl);
  padding-block: var(--pw-space-5);
  box-shadow: var(--pw-shadow-lg);
}
.pw-suggest--above .pw-suggest__seam { display: none; }

.pw-suggest[hidden] { display: none; }

/* Hairline between the field and the first suggestion — inset, not full
   bleed, so it does not touch the capsule's rounded edge. */
.pw-suggest__seam {
  height: 1px;
  margin: 0 var(--pw-suggest-pad, var(--pw-space-8)) var(--pw-space-4);   /* 8px */
  background: var(--pw-border-subtle);
}

.pw-suggest__item {
  position: relative;              /* the stretched title link anchors here */
  display: flex;
  align-items: center;
  gap: var(--pw-suggest-gap, var(--pw-space-6));
  padding: var(--pw-space-5) var(--pw-suggest-pad, var(--pw-space-8));
  color: var(--pw-fg-primary);
  font-size: var(--pw-text-md-size);
  line-height: var(--pw-text-md-line);
  font-weight: var(--pw-font-weight-regular);
  text-decoration: none;
  cursor: pointer;
  scroll-margin: var(--pw-space-8);
}
/* The row's glyph is the FIELD's glyph. A suggestion is the thing you are
   typing, offered back — so the magnifier beside it has to be the same size as
   the mark inside the field and start on the same pixel column, or the list
   reads as a separate control that happens to be attached. Sized here rather
   than by the `--md` / `--sm` class in the markup for the same reason the
   padding is: one field size to change, and the list follows on its own. */
.pw-suggest__item > .pw-icon {
  flex: none;
  width: var(--pw-suggest-glyph, var(--pw-icon-md));
  height: var(--pw-suggest-glyph, var(--pw-icon-md));
  color: var(--pw-fg-quaternary);
}

/* Pointer hover and keyboard cursor land on the same visual — one row can
   never be "hovered" and "active" at once, so they cannot disagree. */
.pw-suggest__item:hover,
.pw-suggest__item.is-active { background: var(--pw-bg-hover); }
.pw-suggest__item.is-active { outline: none; }

/* The part the user has already typed. Semibold, not colour — it has to
   survive a monochrome or high-contrast rendering. */
.pw-suggest__item b,
.pw-suggest__match { font-weight: var(--pw-font-weight-semibold); }

.pw-suggest__body { flex: 1 1 auto; min-width: 0; }
.pw-suggest__text { display: block; }
.pw-suggest__sub {
  display: block;
  color: var(--pw-fg-tertiary);
  font-size: var(--pw-text-xs-size);
  line-height: var(--pw-text-xs-line);
}
/* The ✕ on a removable recent. Sits above the stretched title link, and
   pads its hit area out to the row height without growing visually. */
.pw-suggest__remove {
  position: relative;
  z-index: 1;
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: var(--pw-radius-circle);
  background: transparent;
  color: var(--pw-fg-quaternary);
  cursor: pointer;
}
.pw-suggest__remove::after { content: ""; position: absolute; inset: -6px; }
.pw-suggest__remove:hover { background: var(--pw-ink-hover); color: var(--pw-fg-primary); }
.pw-suggest__remove:focus-visible { outline: none; box-shadow: var(--pw-focus-ring); }
.pw-suggest__fill { flex: none; color: var(--pw-fg-quaternary); }
.pw-suggest__item:hover .pw-suggest__fill,
.pw-suggest__item.is-active .pw-suggest__fill { color: var(--pw-fg-brand); }

.pw-suggest__group {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--pw-space-4);                                          /* 8px */
  padding: var(--pw-space-4) var(--pw-suggest-pad, var(--pw-space-8)) var(--pw-space-2); /* 8 · pad · 4 */
  color: var(--pw-fg-tertiary);
  font-size: var(--pw-text-xs-size);
  line-height: var(--pw-text-xs-line);
  font-weight: var(--pw-font-weight-semibold);
}
.pw-suggest__empty {
  padding: var(--pw-space-6) var(--pw-suggest-pad, var(--pw-space-8));   /* 12px */
  color: var(--pw-fg-tertiary);
  font-size: var(--pw-text-sm-size);
  line-height: var(--pw-text-sm-line);
}

/* Full-bleed variant for the mobile search overlay: no capsule, no shadow,
   the list simply is the screen. */
.pw-suggest--sheet {
  position: static;
  max-height: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  background: transparent;
}
.pw-suggest--sheet .pw-suggest__item { font-weight: var(--pw-font-weight-medium); }

/* ======================================================================== *
 * VERTICAL TABS — All / Images / Videos / Maps, plus the Tools toggle.
 *
 * Underline tabs would fight the filter bar directly below, so the SERP
 * marks the current vertical with weight + brand colour instead. The Tools
 * item sits at the far end: it is a toggle (aria-expanded), not a vertical.
 * ======================================================================== */
.pw-verticals {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--pw-space-8);                             /* 16px */
  /* A flex or grid item defaults to `min-width: auto`, which means it refuses
     to shrink below its content. This row is a scroller — it is *meant* to be
     narrower than its content — so without this it forces its parent's column
     as wide as all six chips and drags every sibling out with it. That is the
     whole of the "mobile isn't responsive" bug: the search field above was
     being stretched to the width of the tab row below it. */
  min-width: 0;
  max-width: 100%;
}
.pw-verticals__list {
  display: flex;
  align-items: center;
  gap: var(--pw-space-4);
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.pw-verticals__list::-webkit-scrollbar { display: none; }

/* With the overflow controller running, the row never scrolls: what does not
   fit moves into the More menu instead. Scrolling hides things behind a
   gesture the user has to discover; a menu says out loud that there is more. */
[data-pw-overflow] .pw-verticals__list { overflow: visible; flex-wrap: nowrap; }

/* Tools, pinned at the trailing end. Kept out of the measured row so the
   controller can subtract its width before deciding what fits — it is not a
   vertical and it must never be the thing that overflows. `.pw-tabs__end` is
   the same slot on a tablist row. */
:is(.pw-verticals__end, .pw-tabs__end) {
  display: flex;
  align-items: center;
  gap: var(--pw-space-4);
  flex: none;
  margin-inline-start: var(--pw-space-4);
}

/* The More trigger picks up the current-chip treatment when the vertical you
   are on has been moved inside it — otherwise the row would show no "you are
   here" at all. */
:is(.pw-verticals__more, .pw-tabs__more).is-current {
  background: var(--pw-bg-surface);
  border-color: var(--pw-border-default);
  color: var(--pw-fg-primary);
  box-shadow: var(--pw-shadow-xs);
}
:is(.pw-verticals__more, .pw-tabs__more)[hidden] { display: none; }


/* -- Topic hues ------------------------------------------------------------
 * Each vertical tints on hover only. Resting and current stay neutral, so the
 * row never turns into a paint chart and the current chip is never competing
 * with a hue. The tint is a courtesy — it is never the only thing telling you
 * what a chip is, and the label always names it. */
:is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="images"] {
  --pw-chip-hover-bg: #eaf2ff; --pw-chip-hover-fg: #2f5f92; --pw-chip-hover-border: rgba(47, 128, 237, 0.28);
  --pw-chip-current-bg: #dceafe; --pw-chip-current-fg: #1f4e7a; --pw-chip-current-border: rgba(47, 128, 237, 0.55);
}
:is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="videos"] {
  --pw-chip-hover-bg: #fff0ef; --pw-chip-hover-fg: #80504b; --pw-chip-hover-border: rgba(225, 91, 84, 0.28);
  --pw-chip-current-bg: #ffe2e0; --pw-chip-current-fg: #6f3f3a; --pw-chip-current-border: rgba(225, 91, 84, 0.55);
}
:is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="maps"] {
  --pw-chip-hover-bg: #ecf8ef; --pw-chip-hover-fg: #376944; --pw-chip-hover-border: rgba(52, 168, 83, 0.28);
  --pw-chip-current-bg: #dcf1e1; --pw-chip-current-fg: #2b5836; --pw-chip-current-border: rgba(52, 168, 83, 0.55);
}
:is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="hotels"] {
  --pw-chip-hover-bg: #fff4e6; --pw-chip-hover-fg: #7a5426; --pw-chip-hover-border: rgba(234, 142, 40, 0.30);
  --pw-chip-current-bg: #ffe9cf; --pw-chip-current-fg: #68451c; --pw-chip-current-border: rgba(234, 142, 40, 0.55);
}
:is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="flights"] {
  --pw-chip-hover-bg: #eef1fd; --pw-chip-hover-fg: #3b4a8f; --pw-chip-hover-border: rgba(79, 98, 214, 0.28);
  --pw-chip-current-bg: #dfe4fb; --pw-chip-current-fg: #2f3c78; --pw-chip-current-border: rgba(79, 98, 214, 0.55);
}
:is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="news"] {
  --pw-chip-hover-bg: #f2eefc; --pw-chip-hover-fg: #574089; --pw-chip-hover-border: rgba(124, 88, 214, 0.28);
  --pw-chip-current-bg: #e8e0fa; --pw-chip-current-fg: #473471; --pw-chip-current-border: rgba(124, 88, 214, 0.55);
}
:is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="shopping"] {
  --pw-chip-hover-bg: #e9f6f5; --pw-chip-hover-fg: #1f5f5b; --pw-chip-hover-border: rgba(20, 148, 138, 0.28);
  --pw-chip-current-bg: #d7efed; --pw-chip-current-fg: #164a47; --pw-chip-current-border: rgba(20, 148, 138, 0.55);
}

/* Dark: the tints invert — a dark ground with the light end of the same hue,
   so the chip reads as the same colour without glowing off the page. */
html.dark :is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="images"], [data-pw-theme="dark"] :is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="images"] {
  --pw-chip-hover-bg: #1b2733; --pw-chip-hover-fg: #a9cdee; --pw-chip-hover-border: rgba(47, 128, 237, 0.42);
  --pw-chip-current-bg: #22344a; --pw-chip-current-fg: #c3ddf6; --pw-chip-current-border: rgba(47, 128, 237, 0.70);
}
html.dark :is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="videos"], [data-pw-theme="dark"] :is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="videos"] {
  --pw-chip-hover-bg: #2f2020; --pw-chip-hover-fg: #eeb0ab; --pw-chip-hover-border: rgba(225, 91, 84, 0.42);
  --pw-chip-current-bg: #412a29; --pw-chip-current-fg: #f5c7c3; --pw-chip-current-border: rgba(225, 91, 84, 0.70);
}
html.dark :is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="maps"], [data-pw-theme="dark"] :is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="maps"] {
  --pw-chip-hover-bg: #1c2b20; --pw-chip-hover-fg: #a6d6b3; --pw-chip-hover-border: rgba(52, 168, 83, 0.42);
  --pw-chip-current-bg: #25392b; --pw-chip-current-fg: #c0e5ca; --pw-chip-current-border: rgba(52, 168, 83, 0.70);
}
html.dark :is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="hotels"], [data-pw-theme="dark"] :is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="hotels"] {
  --pw-chip-hover-bg: #2f2519; --pw-chip-hover-fg: #e5c49b; --pw-chip-hover-border: rgba(234, 142, 40, 0.42);
  --pw-chip-current-bg: #3f3221; --pw-chip-current-fg: #f0d7b6; --pw-chip-current-border: rgba(234, 142, 40, 0.70);
}
html.dark :is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="flights"], [data-pw-theme="dark"] :is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="flights"] {
  --pw-chip-hover-bg: #1e2338; --pw-chip-hover-fg: #b2bdf0; --pw-chip-hover-border: rgba(79, 98, 214, 0.42);
  --pw-chip-current-bg: #282f4b; --pw-chip-current-fg: #c9d1f5; --pw-chip-current-border: rgba(79, 98, 214, 0.70);
}
html.dark :is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="news"], [data-pw-theme="dark"] :is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="news"] {
  --pw-chip-hover-bg: #26203a; --pw-chip-hover-fg: #cabbe8; --pw-chip-hover-border: rgba(124, 88, 214, 0.42);
  --pw-chip-current-bg: #332a4f; --pw-chip-current-fg: #dbcff0; --pw-chip-current-border: rgba(124, 88, 214, 0.70);
}
html.dark :is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="shopping"], [data-pw-theme="dark"] :is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="shopping"] {
  --pw-chip-hover-bg: #1a2b29; --pw-chip-hover-fg: #a2d6d0; --pw-chip-hover-border: rgba(20, 148, 138, 0.42);
  --pw-chip-current-bg: #233a37; --pw-chip-current-fg: #bde4df; --pw-chip-current-border: rgba(20, 148, 138, 0.70);
}

/* "All" is deliberately hueless — it is not a topic, it is the absence of one.
   Leaving its current-* properties unset makes it fall through to the generic
   white lifted chip, which is the point: the neutral one looks neutral. */
:is(.pw-vertical, .pw-tabs--chip .pw-tab)[data-pw-topic="all"] {
  --pw-chip-current-bg: var(--pw-bg-surface);
  --pw-chip-current-fg: var(--pw-fg-primary);
  --pw-chip-current-border: var(--pw-border-default);
}

/* Bare-text row, for surfaces that already have a filled control nearby and
   cannot afford a second track — the maps panel header, mostly. */
:is(.pw-verticals--plain, .pw-tabs--chip.pw-tabs--plain) :is(.pw-vertical, .pw-tab) { background: transparent; border-color: transparent; box-shadow: none; }
:is(.pw-verticals--plain, .pw-tabs--chip.pw-tabs--plain) :is(.pw-vertical, .pw-tab):hover { background: var(--pw-bg-hover); border-color: transparent; }
:is(.pw-verticals--plain, .pw-tabs--chip.pw-tabs--plain) :is(.pw-vertical, .pw-tab)[aria-current="page"],
:is(.pw-verticals--plain, .pw-tabs--chip.pw-tabs--plain) :is(.pw-vertical, .pw-tab).is-current { color: var(--pw-fg-brand); background: transparent; border-color: transparent; box-shadow: none; }
:is(.pw-verticals--plain, .pw-tabs--chip.pw-tabs--plain) :is(.pw-vertical, .pw-tab)[aria-current="page"] .pw-icon,
:is(.pw-verticals--plain, .pw-tabs--chip.pw-tabs--plain) :is(.pw-vertical, .pw-tab).is-current .pw-icon { color: var(--pw-fg-brand); }

.pw-verticals__rule {
  align-self: stretch;
  width: 100%;
  height: 1px;
  /* The rule owns the spacing on both of its sides, so the three rows keep
     their rhythm whatever container they are dropped into. Tighter above
     than below: the rule belongs to the verticals it closes, and the filter
     bar needs a little air to read as the start of something new. */
  /* Equal on both sides. The rule sits between two peers — it does not belong
     to the row above any more than the one below — and an uneven gap reads as
     the separator having drifted rather than as a deliberate grouping. */
  margin-block: var(--pw-space-5);                /* 10 above · 10 below */
  border: 0;
  background: var(--pw-border-subtle);
}

/* ======================================================================== *
 * FILTER BAR — the row of value chips under the verticals.
 *
 * A filter chip is a *menu* trigger, not a select: choosing an option
 * re-runs the search. Options are `menuitemradio` so a screen reader
 * announces the current choice without the chip having to repeat it.
 * The chevron is pinned to the trailing edge (house rule).
 * ======================================================================== */
/* Scrolls, never wraps.
 *
 * A wrapping filter bar changes the page height every time a filter is added
 * or cleared, and the results below jump out from under whoever is reading
 * them. One row that scrolls keeps the page still.
 *
 * The scrollbar is hidden and the half-cut chip at the trailing edge is the
 * affordance. That is a weak signal on a desktop with no touch, so the row
 * stays keyboard-reachable: the chips are still tab stops, and focusing one
 * scrolls it into view. Do not add a gradient fade over the edge — it would
 * dim a real, clickable chip to hint at chips beyond it. */
.pw-filter-bar {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: var(--pw-space-4);                        /* 8px */
  min-width: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  /* Room for the chips' extended hit areas, which would otherwise be clipped
     against the top and bottom of the scroll box. */
  padding-block: var(--pw-space-3);
  margin-block: calc(var(--pw-space-3) * -1);
}
.pw-filter-bar::-webkit-scrollbar { display: none; }
.pw-filter-bar > * { flex: none; }
.pw-filter-bar[hidden] { display: none; }

.pw-filter-root { position: relative; }

.pw-filter {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--pw-space-3);                        /* 6px */
  max-width: 100%;
  /* Compact by default: on the results page the filter bar sits under the
     verticals and above the results, and it is the least important of the
     three. Chunky chips there read as the main event.
     Height is pinned, not left to fall out of the line-height — otherwise a
     longer label or a different font step silently changes the bar's height
     and the chips stop lining up with what sits beside them. */
  height: var(--pw-filter-height, 28px);
  padding-inline: var(--pw-space-5);             /* 10px */
  border: 1px solid var(--pw-border-default);
  border-radius: var(--pw-radius-pill);
  background: var(--pw-bg-surface);
  box-shadow: var(--pw-shadow-xs);
  color: var(--pw-fg-primary);
  font: inherit;
  font-size: var(--pw-text-xs-size);
  line-height: var(--pw-text-xs-line);
  font-weight: var(--pw-font-weight-medium);
  white-space: nowrap;
  cursor: pointer;
  transition: border-color var(--pw-duration-fast) var(--pw-ease-out),
              background var(--pw-duration-fast) var(--pw-ease-out);
}
/* 28px is below every touch-target guideline, so the hit area is padded out
   past the visual to 40px. The chip looks compact and still catches a thumb.
   The horizontal padding is half the bar's gap and no more — any further and
   neighbouring chips' hit areas overlap, and a click in the gap between two
   chips lands on whichever happens to paint last. */
.pw-filter::after { content: ""; position: absolute; inset: -6px -4px; }
.pw-filter:hover { background: var(--pw-bg-hover); border-color: var(--pw-border-strong); }
.pw-filter:focus-visible { outline: none; box-shadow: var(--pw-focus-ring); }

/* Compact chip: the mobile filter rail, a filter bar inside a panel, or any
   row where the chips are secondary to what they sit above. Same shape and
   the same chevron — only the padding and the type step down. */
/* Kept as a no-op so existing markup does not break: the default *is* small. */
.pw-filter--sm { --pw-filter-height: 28px; }

/* The size the chip used to be. For a filter bar that is the primary control
   on its screen — travel and shopping, where filtering is the whole task. */
.pw-filter--md {
  --pw-filter-height: 40px;
  gap: var(--pw-space-4);                        /* 8px */
  padding-inline: var(--pw-space-7);             /* 14px */
}
.pw-filter--md .pw-filter__caret { width: 16px; height: 16px; }

/* Roomier chip for a standalone filter row with nothing competing with it. */
.pw-filter--lg {
  --pw-filter-height: 44px;
  gap: var(--pw-space-4);
  padding-inline: var(--pw-space-8);             /* 16px */
  font-size: var(--pw-text-sm-size);
  line-height: var(--pw-text-sm-line);
}
.pw-filter--lg .pw-filter__caret { width: 16px; height: 16px; }

/* The bar can size every chip in it at once, so a caller never has to
   remember to put the modifier on each one. */
/* Size the bar, never each chip — a bar with mixed chip sizes implies a
   hierarchy between the filters that does not exist. */
.pw-filter-bar--md .pw-filter {
  --pw-filter-height: 40px;
  gap: var(--pw-space-4);
  padding-inline: var(--pw-space-7);
}
.pw-filter-bar--md .pw-filter__caret { width: 16px; height: 16px; }
/* The bar is a row of filters and there are always more of them than a phone is
   wide, so it scrolls along its own axis rather than making the page do it —
   the same arrangement `.pw-verticals__list` and the tool rail already use.
   `min-width: 0` is the half that is easy to forget: without it a flex item
   refuses to shrink under its content and forces every ancestor out with it. */
.pw-filter-bar {
  min-width: 0;
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.pw-filter-bar::-webkit-scrollbar { display: none; }
.pw-filter-bar--md { gap: var(--pw-space-4); }
.pw-filter-bar--sm { gap: var(--pw-space-4); }   /* the default; kept for symmetry */

.pw-filter__value { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* Chevron: always the trailing edge, always the last child. */
.pw-filter__caret {
  flex: none;
  width: 14px;
  height: 14px;
  margin-inline-start: auto;
  color: var(--pw-fg-quaternary);
  transition: transform var(--pw-duration-fast) var(--pw-ease-out);
}
.pw-filter[aria-expanded="true"] {
  border-color: var(--pw-color-brand-500);
  background: var(--pw-bg-surface);
}
.pw-filter[aria-expanded="true"] .pw-filter__caret { transform: rotate(180deg); }

/* A chip holding a non-default value carries weight, so a glance at the bar
   tells you which filters are actually narrowing the results. */
.pw-filter.is-set { border-color: var(--pw-border-strong); }
.pw-filter.is-set .pw-filter__value {
  font-weight: var(--pw-font-weight-bold);
  color: var(--pw-fg-primary);
}
.pw-filter[aria-disabled="true"],
.pw-filter:disabled {
  background: var(--pw-bg-disabled);
  border-color: var(--pw-border-subtle);
  color: var(--pw-fg-disabled);
  box-shadow: none;
  cursor: not-allowed;
}
.pw-filter[aria-disabled="true"] .pw-filter__caret,
.pw-filter:disabled .pw-filter__caret { color: var(--pw-fg-disabled); }

/* A chip carrying its own dismiss — used when the value came from the
   query itself (a site: operator, a locked locale). */
/* Sized to be hittable, not decorative: 24px visual with the hit area padded
   out past it. An 18px target is below every touch guideline there is. */
.pw-filter__clear {
  position: relative;
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 24px;
  height: 24px;
  margin-inline-end: calc(var(--pw-space-3) * -1);
  border: 0;
  border-radius: var(--pw-radius-circle);
  background: transparent;
  color: var(--pw-fg-tertiary);
  cursor: pointer;
}
.pw-filter__clear::after { content: ""; position: absolute; inset: -8px; }
.pw-filter__clear:hover { background: var(--pw-ink-hover); color: var(--pw-fg-primary); }
.pw-filter__clear:focus-visible { outline: none; box-shadow: var(--pw-focus-ring); }

.pw-filter-menu {
  position: absolute;
  top: calc(100% + var(--pw-space-6));
  inset-inline-start: 0;
  z-index: var(--pw-z-dropdown);
  /* Wide enough that no option has to wrap. A wrapped option reads as two
     options, and the tick then sits beside the wrong line. */
  min-width: max(100%, 180px);
  max-height: 320px;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--pw-space-2);                         /* 4px */
  background: var(--pw-bg-surface);
  border: 1px solid var(--pw-border-subtle);
  border-radius: var(--pw-radius-xl);
  box-shadow: var(--pw-shadow-lg);
}
.pw-filter-menu[hidden] { display: none; }
.pw-filter-menu--end { inset-inline-start: auto; inset-inline-end: 0; }
.pw-filter-menu--wide { min-width: 240px; }

.pw-filter-menu__item {
  display: flex;
  align-items: center;
  gap: var(--pw-space-4);                        /* 8px */
  width: 100%;
  padding: var(--pw-space-6) var(--pw-space-5);  /* 12 · 10 */
  border: 0;
  border-radius: var(--pw-radius-sm);
  background: transparent;
  color: var(--pw-fg-secondary);
  font: inherit;
  font-size: var(--pw-text-sm-size);
  line-height: var(--pw-text-sm-line);
  font-weight: var(--pw-font-weight-medium);
  text-align: start;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}
.pw-filter-menu__item:hover { background: var(--pw-bg-hover); color: var(--pw-fg-primary); }
.pw-filter-menu__item:focus-visible {
  outline: none;
  background: var(--pw-bg-hover);
  box-shadow: inset 0 0 0 2px var(--pw-focus-ring-color);
}
.pw-filter-menu__item[aria-checked="true"] {
  color: var(--pw-fg-primary);
  font-weight: var(--pw-font-weight-bold);
}
/* The tick is the checked marker; weight is the redundant second cue. */
.pw-filter-menu__text { min-width: 0; }
.pw-filter-menu__empty {
  padding: var(--pw-space-6) var(--pw-space-10);
  color: var(--pw-fg-tertiary);
  font-size: var(--pw-text-sm-size);
  line-height: var(--pw-text-sm-line);
}
.pw-filter-menu__tick { flex: none; margin-inline-start: auto; color: var(--pw-fg-brand); }
.pw-filter-menu__item[aria-checked="false"] .pw-filter-menu__tick { visibility: hidden; }
.pw-filter-menu__flag {
  flex: none;
  width: 20px;
  height: 20px;
  border-radius: var(--pw-radius-circle);
}
.pw-filter-menu__search {
  position: sticky;
  top: calc(var(--pw-space-2) * -1);
  z-index: 1;
  padding: var(--pw-space-4) var(--pw-space-4) var(--pw-space-3);   /* 8 · 8 · 6 */
  margin: calc(var(--pw-space-2) * -1) calc(var(--pw-space-2) * -1) var(--pw-space-2);
  background: var(--pw-bg-surface);
  border-bottom: 1px solid var(--pw-border-subtle);
  border-start-start-radius: var(--pw-radius-xl);
  border-start-end-radius: var(--pw-radius-xl);
}

/* "Clear" is a link, not a chip — it resets rather than narrows, and must
   not look like one more thing you can set. */
.pw-filter-clear {
  display: inline-flex;
  align-items: center;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--pw-fg-secondary);
  font: inherit;
  font-size: var(--pw-text-xs-size);
  line-height: var(--pw-text-xs-line);
  font-weight: var(--pw-font-weight-semibold);
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
}
.pw-filter-clear:hover { text-decoration: underline; text-underline-offset: 2px; }
.pw-filter-clear:focus-visible { outline: none; box-shadow: var(--pw-focus-ring); border-radius: var(--pw-radius-xs); }
/* The display above would otherwise beat [hidden]'s display:none. */
.pw-filter-clear[hidden] { display: none; }
/* Gated on the attribute the controller stamps, for the same reason the layers
   are: with no JS on the page there is nothing coming to turn the opacity back
   on, and a Clear that never appears is worse than one that always does. It
   arrives from the chips it undoes, not from nowhere. */
.pw-filter-clear[data-pw-clear] {
  opacity: 0;
  transform: translateX(-4px);
  transition: opacity var(--pw-duration-fast) var(--pw-ease-out),
              transform var(--pw-duration-fast) var(--pw-ease-out);
}
.pw-filter-clear[data-pw-clear].is-shown {
  opacity: 1;
  transform: none;
  transition-duration: var(--pw-duration-base);
}
@media (prefers-reduced-motion: reduce) {
  .pw-filter-clear[data-pw-clear] { transition: none; transform: none; }
}


/* Re-asserted after the size rules above: a field inside any layer is 32px,
   whatever the caller asked for. Stated once in pw-components.css and once
   here, because this file redefines the search sizes and would otherwise win
   by source order. */
.pw-filter-menu .pw-search,
.pw-select__menu .pw-search,
.pw-menu .pw-search,
.pw-settings-menu .pw-search { height: 32px; min-height: 32px; }


/* =============================================================================
   RESULT SUMMARY
   What one result says, in a panel beside the results. It is opened from the
   result's own menu, so it is about ONE page and says which; the Glance is the
   answer to the whole search and is the only card that wears the AI gradient.
   This one is a plain surface with the AI medallion and nothing else -- an AI
   colour on every card that touches a model would stop meaning anything.

     .pw-summary
       .pw-summary__head      medallion, title, a Beta tag
       .pw-summary__body      the summary, with .pw-cite marks
       .pw-summary__more      "Learn more": the sources it was written from
       .pw-summary__foot      Report
   ============================================================================= */
.pw-summary {
  display: flex; flex-direction: column; gap: var(--pw-space-8);
  padding: var(--pw-space-10);
  border: 1px solid var(--pw-border-subtle);
  border-radius: var(--pw-radius-xl);
  background: var(--pw-bg-surface);
}
.pw-summary__head { display: flex; align-items: center; gap: var(--pw-space-4); }
.pw-summary__mark {
  display: grid; place-items: center; width: 28px; height: 28px;
  border-radius: var(--pw-radius-md);
  background: var(--pw-tint-ai-bg); color: var(--pw-tint-ai-fg);
}
.pw-summary__title { margin: 0; font-size: var(--pw-text-md-size); line-height: var(--pw-text-md-line); font-weight: var(--pw-font-weight-bold); color: var(--pw-fg-primary); }
.pw-summary__body { margin: 0; font-size: var(--pw-text-sm-size); line-height: var(--pw-text-sm-line); font-weight: var(--pw-font-weight-regular); color: var(--pw-fg-primary); }
.pw-summary__more { display: flex; flex-direction: column; gap: var(--pw-space-4); padding-top: var(--pw-space-8); border-top: 1px solid var(--pw-border-subtle); }
.pw-summary__more .pw-source { width: auto; }
.pw-summary__heading { margin: 0; font-size: var(--pw-text-sm-size); line-height: var(--pw-text-sm-line); font-weight: var(--pw-font-weight-semibold); color: var(--pw-fg-secondary); }
.pw-summary__foot { display: flex; justify-content: flex-end; }
