/* WeatherGPT client — the sounding.
 *
 * The page is read as an atmospheric sounding: one continuous vertical column
 * of air, cool at the top and warm at the surface, ruled by a hairline skew-T
 * graticule. The conversation ascends through it. One signal colour — the
 * parcel blue — carries action and state and nothing else.
 */

/* ------------------------------------------------------------------ tokens */

:root {
  color-scheme: light;

  /* Ground. These three stops are swapped per sky state further down. */
  --air-top: #e7eef7;
  --air-mid: #f5f8fb;
  --air-surface: #fdfbf7;
  --graticule: rgba(23, 58, 99, 0.07);
  --graticule-header: rgba(23, 58, 99, 0.13);

  --surface: #ffffff;
  --surface-sunk: #f1f4f8;
  --surface-quiet: #f7f9fc;

  --ink: #0e1720;
  --ink-2: #45545f;
  --ink-3: #5c6b78;

  --line: rgba(14, 23, 32, 0.1);
  --line-strong: rgba(14, 23, 32, 0.18);

  /* The parcel path: primary action, active state, current selection. */
  --parcel: #1d63e6;
  --parcel-hover: #164fbd;
  --parcel-active: #123f97;
  --parcel-soft: #e8f0fe;
  --parcel-soft-line: #c3d8fb;

  --stop: #c7301f;
  --stop-hover: #a82717;
  --stop-soft: #fdecea;

  --warn: #8a5a00;
  --warn-soft: #fdf4e3;
  --ok: #17694a;

  --radius-sm: 8px;
  --radius: 12px;
  --radius-lg: 16px;
  --radius-pill: 999px;

  /* Elevation is declared once per element: a hairline or a shadow, never a
     heavy border under a wide shadow. */
  --shadow-raise: 0 1px 2px rgba(14, 23, 32, 0.05), 0 6px 20px -8px rgba(14, 23, 32, 0.16);
  --shadow-float: 0 2px 4px rgba(14, 23, 32, 0.06), 0 18px 44px -16px rgba(14, 23, 32, 0.26);

  /* Curves. The browser keywords are too weak to read as intent at these
     durations, so each curve here is authored and each has one job. */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);      /* entering, exiting */
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);  /* moving across the screen */
  --ease: cubic-bezier(0.32, 0.72, 0, 1);          /* surfaces that slide */

  --dur-0: 120ms;   /* press and hover: felt, not watched */
  --dur-1: 160ms;
  --dur-2: 220ms;
  --dur-3: 300ms;
  /* The air runs on its own clock. Weather does not change in a fifth of a
     second, and the ground layer should never feel like a UI transition. */
  --dur-sky: 900ms;

  --font: system-ui, -apple-system, "Segoe UI Variable Text", "Segoe UI", Roboto,
    "Helvetica Neue", Arial, "Noto Sans Devanagari", sans-serif;

  --column: 960px;
  --shell: var(--column);
}

/* Reduced motion is fewer and gentler, not none: a state change still fades so
   it stays legible, but nothing travels, drifts or loops. */
@media (prefers-reduced-motion: reduce) {
  :root { --dur-0: 1ms; --dur-1: 100ms; --dur-2: 120ms; --dur-3: 140ms; --dur-sky: 220ms; }
}

/* Sky states. Only the ground and the graticule move; ink never does. */
:root[data-sky="clear"]  { --air-top: #d9e8fa; --air-mid: #f2f7fd; --air-surface: #fefcf7; --graticule: rgba(23, 58, 99, 0.06); }
:root[data-sky="clouds"] { --air-top: #e4e9ee; --air-mid: #f4f6f9; --air-surface: #fbfbfa; --graticule: rgba(23, 58, 99, 0.075); }
:root[data-sky="rain"]   { --air-top: #dde5ec; --air-mid: #eff3f7; --air-surface: #f8fafb; --graticule: rgba(23, 58, 99, 0.075); }
:root[data-sky="storm"]  { --air-top: #d5dbe3; --air-mid: #e9edf2; --air-surface: #f6f7f9; --graticule: rgba(23, 58, 99, 0.095); }
:root[data-sky="snow"]   { --air-top: #e6ecf2; --air-mid: #f6f8fb; --air-surface: #fdfdfd; --graticule: rgba(23, 58, 99, 0.05); }
:root[data-sky="fog"]    { --air-top: #e6e8ea; --air-mid: #f2f3f4; --air-surface: #fafaf9; --graticule: rgba(23, 58, 99, 0.04); }
:root[data-sky="night"]  { --air-top: #dce2ec; --air-mid: #eef1f6; --air-surface: #f9f9fb; --graticule: rgba(23, 58, 99, 0.085); }

/* ------------------------------------------------------------------- reset */

*, *::before, *::after { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink);
  background: var(--air-surface);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow: hidden;
}

h1, h2, h3, p, ol, ul, figure { margin: 0; }
ol, ul { padding: 0; list-style: none; }
button, input, textarea, select { font: inherit; color: inherit; }

/* Browser surfaces belong to the design, not to the browser. */
::selection { background: #cfe0fb; color: var(--ink); }

:where(a, button, input, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--parcel);
  outline-offset: 2px;
}

.scroller { scrollbar-width: thin; scrollbar-color: var(--line-strong) transparent; }
.scroller::-webkit-scrollbar { width: 10px; height: 10px; }
.scroller::-webkit-scrollbar-track { background: transparent; }
.scroller::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border: 3px solid transparent;
  border-radius: var(--radius-pill);
  background-clip: content-box;
}
.scroller::-webkit-scrollbar-thumb:hover {
  background: rgba(14, 23, 32, 0.32);
  background-clip: content-box;
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ------------------------------------------------------------ the sounding */

.sky {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  background: linear-gradient(180deg, var(--air-top) 0%, var(--air-mid) 46%, var(--air-surface) 100%);
  /* A sky does not cut between states; it turns over. */
  transition: background var(--dur-sky) var(--ease-in-out);
}

/* The skew-T graticule: isobars level, dry adiabats skewed. It is a measuring
   grid drawn as one, and held under 6% so it never competes with the column.
   It is the one layer that never moves — a grid that drifts is not a grid. */
.sky::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    repeating-linear-gradient(0deg, var(--graticule) 0 1px, transparent 1px 68px),
    repeating-linear-gradient(45deg, var(--graticule) 0 1px, transparent 1px 96px);
  -webkit-mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, 0.35) 42%, transparent 78%);
  mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, 0.35) 42%, transparent 78%);
  transition: background-image var(--dur-sky) var(--ease-in-out);
}

/* The light in the column. One soft body of luminance over the upper air,
   moving on a scale a person registers only by looking away and back. It is
   white and parcel blue at trace strength — the palette, nothing new. */
.sky__glow {
  position: absolute;
  inset: -28% -18% auto -18%;
  height: 92%;
  background:
    radial-gradient(46% 58% at 68% 16%, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0) 68%),
    radial-gradient(40% 48% at 20% 4%, rgba(29, 99, 230, 0.07), rgba(29, 99, 230, 0) 72%);
  will-change: transform;
  animation: breathe 54s var(--ease-in-out) infinite alternate;
  transition: opacity var(--dur-sky) var(--ease-in-out);
}
:root[data-sky="storm"] .sky__glow { opacity: 0.34; }
:root[data-sky="fog"]   .sky__glow { opacity: 0.45; }
:root[data-sky="night"] .sky__glow { opacity: 0.55; }

@keyframes breathe {
  from { transform: translate3d(-2%, -1.5%, 0) scale(1); }
  to   { transform: translate3d(3%, 2%, 0) scale(1.075); }
}

/* Precipitation: hairline sheets, never a particle system. Two of them, at
   different rates, so the fall reads as depth rather than as one moving
   texture — the far sheet on the element, the near sheet on its ::after. */
.sky__veil {
  position: absolute;
  inset: -24% -14%;
  opacity: 0;
  background-image: repeating-linear-gradient(100deg, rgba(29, 99, 230, 0.12) 0 1px, transparent 1px 11px);
  -webkit-mask-image: linear-gradient(180deg, #000 0%, transparent 72%);
  mask-image: linear-gradient(180deg, #000 0%, transparent 72%);
  will-change: transform;
  transition: opacity var(--dur-sky) var(--ease-in-out);
}
.sky__veil::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(97deg, rgba(29, 99, 230, 0.09) 0 1px, transparent 1px 26px);
}

:root[data-sky="rain"]  .sky__veil { opacity: 1; animation: drift 2.4s linear infinite; }
:root[data-sky="rain"]  .sky__veil::after { animation: drift 1.55s linear infinite; }
:root[data-sky="storm"] .sky__veil { opacity: 1; animation: drift 1.5s linear infinite; }
:root[data-sky="storm"] .sky__veil::after { animation: drift 0.95s linear infinite; }

:root[data-sky="snow"] .sky__veil {
  opacity: 0.7;
  background-image: repeating-linear-gradient(115deg, rgba(70, 100, 140, 0.1) 0 2px, transparent 2px 22px);
  animation: drift 9s linear infinite;
}
:root[data-sky="snow"] .sky__veil::after {
  background-image: repeating-linear-gradient(112deg, rgba(70, 100, 140, 0.07) 0 2px, transparent 2px 44px);
  animation: drift 5.4s linear infinite;
}

/* Fog is not a fall. It is a band of white lying across the column, rolling
   the width of a slow breath. */
:root[data-sky="fog"] .sky__veil {
  opacity: 0.9;
  inset: 8% -16% auto -16%;
  height: 58%;
  background-image: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.62) 52%, rgba(255, 255, 255, 0) 100%);
  -webkit-mask-image: none;
  mask-image: none;
  animation: roll 28s var(--ease-in-out) infinite alternate;
}
:root[data-sky="fog"] .sky__veil::after { content: none; }

@keyframes drift {
  from { transform: translate3d(0, -12%, 0); }
  to   { transform: translate3d(-9%, 12%, 0); }
}

@keyframes roll {
  from { transform: translate3d(-7%, -2%, 0); }
  to   { transform: translate3d(7%, 2%, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .sky__veil, .sky__veil::after, .sky__glow { animation: none !important; }
}

/* ----------------------------------------------------------------- the app */

.app {
  position: relative;
  z-index: 1;
  height: 100dvh;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  grid-template-columns: minmax(0, 1fr);
}
.app > * { min-width: 0; }

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: 32px;
}

/* -------------------------------------------------------------- masthead */

.masthead {
  position: relative;
  z-index: 30;
  border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.74), rgba(255, 255, 255, 0.42));
  backdrop-filter: saturate(1.4) blur(14px);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
  transition: background var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out),
    border-color var(--dur-2) var(--ease-out);
}

/* Once the column has moved under it, the masthead thickens and casts — the
   only signal that there is conversation above the fold. */
:root[data-scrolled="true"] .masthead {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.88), rgba(255, 255, 255, 0.64));
  border-bottom-color: var(--line-strong);
  box-shadow: 0 10px 26px -22px rgba(14, 23, 32, 0.9);
}

/* The graticule is legible only here, at the top of the column. */
.masthead::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: repeating-linear-gradient(45deg, var(--graticule-header) 0 1px, transparent 1px 96px);
  -webkit-mask-image: linear-gradient(180deg, #000, transparent);
  mask-image: linear-gradient(180deg, #000, transparent);
}

.masthead__inner {
  position: relative;
  display: flex;
  align-items: center;
  gap: 24px;
  padding-block: 18px;
}

.brand { display: flex; align-items: center; gap: 13px; min-width: 0; }

.brand__mark {
  flex: none;
  width: 38px;
  height: 38px;
  border-radius: 11px;
  display: grid;
  place-items: center;
  color: #fff;
  background: linear-gradient(160deg, #2f7bff, #1145b4 72%);
  box-shadow: 0 1px 2px rgba(11, 45, 110, 0.28), 0 8px 18px -8px rgba(17, 69, 180, 0.7);
}
.brand__mark svg { width: 22px; height: 22px; }

.brand__name {
  font-size: 21px;
  font-weight: 640;
  letter-spacing: -0.021em;
  line-height: 1.15;
}

.brand__tagline {
  font-size: 12.5px;
  color: var(--ink-2);
  letter-spacing: -0.004em;
  max-width: 70ch;
}

.masthead__tools { margin-left: auto; display: flex; align-items: center; gap: 10px; }

/* --------------------------------------------------------------- controls */

.btn {
  --btn-bg: var(--surface);
  --btn-ink: var(--ink);
  --btn-line: var(--line-strong);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 40px;
  padding-inline: 15px;
  border: 1px solid var(--btn-line);
  border-radius: var(--radius-sm);
  background: var(--btn-bg);
  color: var(--btn-ink);
  font-size: 13.5px;
  font-weight: 550;
  letter-spacing: -0.006em;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease),
    color var(--dur-1) var(--ease), transform var(--dur-0) var(--ease-out), box-shadow var(--dur-1) var(--ease);
}
.btn svg { width: 17px; height: 17px; flex: none; }
.btn:hover:not(:disabled) { background: var(--surface-quiet); border-color: rgba(14, 23, 32, 0.28); }
.btn:disabled { opacity: 0.44; cursor: not-allowed; }

.btn--primary {
  --btn-bg: var(--parcel);
  --btn-ink: #fff;
  --btn-line: transparent;
  box-shadow: 0 1px 2px rgba(13, 51, 133, 0.3), 0 8px 18px -10px rgba(29, 99, 230, 0.85);
}
.btn--primary:hover:not(:disabled) { background: var(--parcel-hover); border-color: transparent; }
.btn--primary:active:not(:disabled) { background: var(--parcel-active); }

.btn--danger { --btn-bg: var(--surface); --btn-ink: var(--stop); --btn-line: rgba(199, 48, 31, 0.34); }
.btn--danger:hover:not(:disabled) { background: var(--stop-soft); border-color: rgba(199, 48, 31, 0.5); }

/* Header readouts. They open things; they do not compete with Speak. */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.72);
  font-size: 13.5px;
  font-weight: 520;
  letter-spacing: -0.006em;
  cursor: pointer;
  max-width: 330px;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease),
    box-shadow var(--dur-1) var(--ease), transform var(--dur-0) var(--ease-out);
}
.pill:hover { background: #fff; border-color: rgba(14, 23, 32, 0.3); }
.pill[aria-expanded="true"] { background: #fff; border-color: var(--parcel); box-shadow: 0 0 0 3px var(--parcel-soft); }
.pill svg { width: 16px; height: 16px; flex: none; color: var(--ink-3); }
.pill__text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pill__chevron { width: 14px; height: 14px; transition: transform var(--dur-2) var(--ease); }
.pill[aria-expanded="true"] .pill__chevron { transform: rotate(180deg); }
.pill--place > svg:first-child { color: var(--parcel); }

/* Lift is a hover affordance, so it exists only where a hover is real. Press
   is the reply to a touch and belongs everywhere; it wins by coming last. */
@media (hover: hover) and (pointer: fine) {
  .btn:hover:not(:disabled) { transform: translateY(-1px); }
  .pill:hover { transform: translateY(-1px); }
  .chip:hover:not(:disabled) { transform: translateY(-1px); }
  .candidate:hover { transform: translateY(-1px); }
}
.btn:active:not(:disabled) { transform: scale(0.972); }
.pill:active { transform: scale(0.978); }
.chip:active:not(:disabled) { transform: scale(0.96); }
.icon-btn:active:not(:disabled) { transform: scale(0.92); }

/* ---------------------------------------------------------------- popovers */

.anchor { position: relative; }

.popover {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 60;
  min-width: 252px;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-float);
  /* Anchored to its trigger, so it grows out of the trigger, not out of
     nowhere. Modals would be the exception; there are none here. */
  transform-origin: top right;
  animation: pop var(--dur-2) var(--ease-out) both;
}
.popover[hidden] { display: none; }

/* It leaves the way it came: same origin, same path, shorter — a dismissal
   should never take as long as a decision. */
.popover[data-closing="true"] {
  animation: unpop var(--dur-1) var(--ease-out) both;
  pointer-events: none;
}

@keyframes pop {
  from { opacity: 0; transform: translateY(-6px) scale(0.97); }
  to   { opacity: 1; transform: none; }
}

@keyframes unpop {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(-4px) scale(0.985); }
}

/* A control sitting at the foot of the page has no room below it, so its menu
   opens upward — and grows from the edge it is anchored to, like every other
   popover here. Doubled class so the 760px rules, which re-anchor the header
   popovers to the left, cannot flip this one's origin back to the top. */
.popover.popover--up {
  top: auto;
  bottom: calc(100% + 8px);
  left: 0;
  right: auto;
  transform-origin: bottom left;
  animation-name: pop-up;
}
.popover.popover--up[data-closing="true"] { animation-name: unpop-up; }

@keyframes pop-up {
  from { opacity: 0; transform: translateY(6px) scale(0.97); }
  to   { opacity: 1; transform: none; }
}

@keyframes unpop-up {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(4px) scale(0.985); }
}

/* Six short readings need no more width than the longest of them. */
.popover--rate { min-width: 178px; }

.menu__option {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  text-align: left;
  cursor: pointer;
  font-size: 14px;
}
.menu__option:hover, .menu__option[data-active="true"] { background: var(--surface-quiet); }
.menu__option[aria-selected="true"] { color: var(--parcel-hover); font-weight: 600; }
.menu__check { width: 16px; height: 16px; flex: none; opacity: 0; color: var(--parcel); }
.menu__option[aria-selected="true"] .menu__check { opacity: 1; }
.menu__label { flex: 1; min-width: 0; }
.menu__note { display: block; font-size: 11.5px; font-weight: 450; color: var(--ink-3); letter-spacing: 0; }

.menu__footnote {
  padding: 9px 10px 5px;
  margin-top: 4px;
  border-top: 1px solid var(--line);
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--ink-3);
}

/* ------------------------------------------------------- location picker */

.picker { width: 384px; padding: 12px; }
.picker__title { font-size: 13px; font-weight: 620; letter-spacing: -0.006em; margin-bottom: 9px; }

.picker__field { position: relative; display: flex; align-items: center; }
.picker__field > svg { position: absolute; left: 11px; width: 16px; height: 16px; color: var(--ink-3); pointer-events: none; }
.picker__input {
  width: 100%;
  height: 40px;
  padding: 0 12px 0 34px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-quiet);
  caret-color: var(--parcel);
}
.picker__input:focus { outline: none; border-color: var(--parcel); background: #fff; box-shadow: 0 0 0 3px var(--parcel-soft); }
.picker__input::placeholder { color: var(--ink-3); }

.picker__results { margin-top: 8px; max-height: 300px; overflow-y: auto; }
.picker__hint { padding: 11px 4px 5px; font-size: 12.5px; line-height: 1.5; color: var(--ink-3); }

.result {
  display: flex;
  align-items: baseline;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  text-align: left;
  cursor: pointer;
}
.result:hover, .result[data-active="true"] { background: var(--parcel-soft); }
.result__name { font-size: 14px; font-weight: 560; letter-spacing: -0.006em; }
.result__where { font-size: 12.5px; color: var(--ink-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.result__coords { margin-left: auto; font-size: 11.5px; color: var(--ink-3); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* -------------------------------------------------------------- the thread */

/* A column that fills from the bottom: an opening screen sits in the middle of
   the air, and a short conversation rests just above the composer instead of
   hanging from the masthead. */
.thread-wrap {
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  min-width: 0;
  /* The column dissolves into the masthead rather than being cut by its edge,
     so scrolling reads as air moving past rather than as a clipped list. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 28px);
  mask-image: linear-gradient(180deg, transparent 0, #000 28px);
}
.thread-wrap > * { flex: none; }

.thread {
  max-width: var(--column);
  width: 100%;
  margin-inline: auto;
  margin-top: auto;
  padding: 30px 32px 10px;
  display: flex;
  flex-direction: column;
  gap: 22px;
}

/* A turn rises into the column from its own side, so the left/right language
   of the notches is already stated by the time the text is read. */
.turn { display: flex; }
.turn--user { justify-content: flex-end; animation: rise-user var(--dur-3) var(--ease-out) both; }
.turn--agent { justify-content: flex-start; animation: rise-agent var(--dur-3) var(--ease-out) both; }

@keyframes rise-user {
  from { opacity: 0; transform: translate3d(9px, 10px, 0) scale(0.986); }
  to   { opacity: 1; transform: none; }
}

@keyframes rise-agent {
  from { opacity: 0; transform: translate3d(-9px, 10px, 0) scale(0.986); }
  to   { opacity: 1; transform: none; }
}

/* USER — right, and shaped so the side is legible before the text is read. */
.bubble--user {
  max-width: min(560px, 82%);
  padding: 12px 16px;
  border-radius: var(--radius-lg) var(--radius-lg) 4px var(--radius-lg);
  background: var(--parcel);
  color: #fff;
  font-size: 15px;
  letter-spacing: -0.008em;
  box-shadow: 0 1px 2px rgba(13, 51, 133, 0.24), 0 10px 24px -14px rgba(29, 99, 230, 0.9);
  overflow-wrap: anywhere;
}

/* WEATHERGPT — left. Every answer holds the same measure, so a card does not
   resize between its loading state and its result, and a two-column table of
   readings does not collapse to its minimum track. */
.agent { width: min(704px, 94%); min-width: 0; }

.agent__head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 7px;
  font-size: 11px;
  font-weight: 640;
  letter-spacing: 0.075em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.agent__head::after { content: ""; flex: 1; height: 1px; background: var(--line); }

.agent__mark {
  width: 18px; height: 18px;
  border-radius: 5px;
  display: grid; place-items: center;
  color: #fff;
  background: linear-gradient(160deg, #2f7bff, #1145b4 72%);
}
.agent__mark svg { width: 11px; height: 11px; }

.card {
  border: 1px solid var(--line);
  /* The notch points back at the WeatherGPT label above it. */
  border-radius: 4px var(--radius-lg) var(--radius-lg) var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-raise);
  overflow: hidden;
}

.card__body { padding: 15px 18px; }
.card__body > * + * { margin-top: 12px; }

/* An answer settles in reading order — the sentence, then the reading, then
   what was measured — instead of the whole card arriving at once. */
.card__body > * {
  animation: latch var(--dur-3) var(--ease-out) both;
  animation-delay: 60ms;
}
.card__body > *:nth-child(2) { animation-delay: 105ms; }
.card__body > *:nth-child(3) { animation-delay: 150ms; }
.card__body > *:nth-child(n+4) { animation-delay: 195ms; }

/* A grid and a ruled table do not latch as a block. Their cells and rows do,
   one clock further down, so the container must stay out of the way. */
.card__body > .metrics,
.card__body > .rows { animation: none; }

.lede { font-size: 15px; letter-spacing: -0.008em; color: var(--ink); overflow-wrap: anywhere; }
.prose { white-space: pre-wrap; font-size: 14.5px; color: var(--ink-2); letter-spacing: -0.004em; overflow-wrap: anywhere; }

/* The headline reading. */
.reading { display: flex; align-items: center; gap: 16px; padding-top: 2px; flex-wrap: wrap; }
.reading__icon { width: 34px; height: 34px; color: var(--parcel); flex: none; }
.reading__value {
  font-size: 40px;
  font-weight: 600;
  letter-spacing: -0.032em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.reading__unit { font-size: 20px; font-weight: 520; color: var(--ink-3); letter-spacing: -0.01em; }
.reading__desc { font-size: 14.5px; color: var(--ink-2); letter-spacing: -0.006em; }
.reading__sub { font-size: 12.5px; color: var(--ink-3); font-variant-numeric: tabular-nums; }

/* Measured values, each latching in after the reading settles. */
.metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(114px, 1fr));
  gap: 1px;
  /* The rules are drawn by the cells, not by a coloured backdrop showing
     through the gaps: a row that does not divide evenly would otherwise end in
     a stray grey block. */
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.metric {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 4px;
  padding: 9px 12px 10px;
  background: var(--surface);
  box-shadow: 0 0 0 1px var(--line);
  animation: latch var(--dur-3) var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 45ms + 150ms);
}
.metric__label {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 10.5px;
  font-weight: 620;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.metric__value {
  font-size: 15.5px;
  font-weight: 560;
  letter-spacing: -0.012em;
  font-variant-numeric: tabular-nums;
}
.metric__unit { font-size: 12px; font-weight: 500; color: var(--ink-3); margin-left: 2px; }

@keyframes latch {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}

/* The reduced-motion stand-in for every entrance in the file: the change is
   still announced, nothing crosses the screen to announce it. */
@keyframes fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Forecast rows: the sounding's own ruled table. */
.rows { border-top: 1px solid var(--line); }

/* Rows land down the table on the same clock as the metric cells, so the
   forecast reads as a column filling rather than a block appearing. */
.rows .row {
  animation: latch var(--dur-3) var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 34ms + 150ms);
}

.row {
  display: grid;
  grid-template-columns: 112px 20px minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  padding: 9px 2px;
  border-bottom: 1px solid var(--line);
  font-size: 13.5px;
}
.row:last-of-type { border-bottom: 0; }
.row__when { font-weight: 560; font-variant-numeric: tabular-nums; letter-spacing: -0.008em; }
.row__icon { width: 18px; height: 18px; color: var(--ink-3); }
.row__what { color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Figures sit in fixed tracks so min, max and rain read down the table as
   columns rather than drifting with the width of the row above. */
.row__figures { display: flex; font-variant-numeric: tabular-nums; white-space: nowrap; }
.row__fig { min-width: 80px; text-align: right; color: var(--ink-2); }
.row__fig b { font-weight: 600; color: var(--ink); }
.row__fig--wet { color: var(--parcel); }

.more { padding-top: 9px; font-size: 12px; color: var(--ink-3); }

/* Provenance lives inside the answer, on a hairline, and names only what the
   backend returned for this turn. */
.source {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  padding: 9px 18px 11px;
  border-top: 1px solid var(--line);
  background: var(--surface-quiet);
  font-size: 12px;
  color: var(--ink-2);
}
.source__label { font-weight: 620; color: var(--ink-3); letter-spacing: 0.05em; text-transform: uppercase; font-size: 10.5px; }
.source__name { font-weight: 580; color: var(--ink); }
.source__meta { color: var(--ink-3); font-variant-numeric: tabular-nums; }
.source__dot { width: 3px; height: 3px; border-radius: 50%; background: var(--line-strong); }
.source__tag {
  padding: 1px 7px;
  border: 1px solid var(--parcel-soft-line);
  border-radius: var(--radius-pill);
  background: var(--parcel-soft);
  color: #14458f;
  font-size: 10.5px;
  font-weight: 600;
}

/* ---------------------------------------------------------------- alerts

   The watch panel. Same popover, same pills, same rule weights as everything
   else in the header: a person should recognise it as the same product, not
   arrive at a settings screen. Severity is the only place colour is spent,
   because severity is the only thing here worth interrupting someone for. */

.btn--sm { height: 32px; padding-inline: 12px; font-size: 12.5px; }
.btn--ghost { --btn-bg: transparent; --btn-line: transparent; --btn-ink: var(--ink-2); }
.btn--ghost:hover:not(:disabled) { background: var(--surface-quiet); border-color: var(--line); }

.pill__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding-inline: 5px;
  border-radius: 9px;
  background: var(--stop);
  color: #fff;
  font-size: 11px;
  font-weight: 680;
  font-variant-numeric: tabular-nums;
}
.pill__count[hidden] { display: none; }

.watch { width: min(400px, calc(100vw - 32px)); padding: 0; }
.watch__head { padding: 14px 16px 10px; border-bottom: 1px solid var(--line); }
.watch__title { font-size: 13.5px; font-weight: 640; letter-spacing: -0.01em; }
.watch__sub { margin-top: 3px; font-size: 12px; line-height: 1.5; color: var(--ink-2); }

.watch__add { padding: 12px 12px 10px; border-bottom: 1px solid var(--line); }

/* The step that stops a wrong town being watched for a week. */
.watch__confirm {
  margin-top: 9px;
  padding: 11px 12px;
  border: 1px solid var(--parcel-soft-line);
  border-radius: var(--radius-sm);
  background: var(--parcel-soft);
}
.watch__confirm[hidden] { display: none; }
.watch__confirmLabel { font-size: 11.5px; font-weight: 600; color: #14458f; letter-spacing: 0.02em; text-transform: uppercase; }
.watch__confirmPlace { margin-top: 4px; font-size: 13.5px; font-weight: 600; color: var(--ink); }
.watch__confirmActions { display: flex; gap: 8px; margin-top: 10px; }

.watch__list { max-height: 340px; overflow-y: auto; padding: 6px; }
.watch__empty { padding: 18px 12px 20px; text-align: center; font-size: 12.5px; line-height: 1.55; color: var(--ink-2); }

.watched {
  padding: 11px 10px 12px;
  border-radius: var(--radius-sm);
  transition: background var(--dur-1) var(--ease);
}
.watched + .watched { border-top: 1px solid var(--line); border-radius: 0; }
.watched__top { display: flex; align-items: flex-start; gap: 8px; }
.watched__place { flex: 1; min-width: 0; font-size: 13.5px; font-weight: 600; letter-spacing: -0.008em; }
.watched__where { margin-top: 2px; font-size: 11.5px; color: var(--ink-2); }

.watched__actions { display: flex; gap: 2px; flex: none; }
.watched__action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: 7px;
  background: none;
  color: var(--ink-3);
  cursor: pointer;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
.watched__action:hover { background: var(--surface-quiet); color: var(--ink); }
.watched__action svg { width: 15px; height: 15px; }
.watched__action--off:hover { color: var(--stop); background: var(--stop-soft); }

/* "Monitoring: active" vs "paused" vs "not checked yet" — three different
   facts, and the third must never be shown as an all-clear. */
.watched__state {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 7px;
  font-size: 11.5px;
  color: var(--ink-2);
}
.watched__dot { width: 6px; height: 6px; border-radius: 50%; background: var(--ok); flex: none; }
.watched__dot--idle { background: var(--ink-3); }
.watched__dot--waiting { background: var(--warn); }

.watched__alerts { margin-top: 9px; display: flex; flex-direction: column; gap: 7px; }

.hazard {
  padding: 9px 11px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-quiet);
  border-left-width: 3px;
  border-left-color: var(--sev, var(--ink-3));
}
.hazard__top { display: flex; align-items: baseline; gap: 8px; }
.hazard__title { flex: 1; min-width: 0; font-size: 12.8px; font-weight: 620; }
.hazard__sev {
  flex: none;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--sev, var(--ink-3));
  color: #fff;
  font-size: 10px;
  font-weight: 680;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.hazard__body { margin-top: 5px; font-size: 12px; line-height: 1.5; color: var(--ink-2); }
.hazard__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin-top: 7px;
  font-size: 11px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.hazard__meta b { font-weight: 600; color: var(--ink-2); }

.hazard--info    { --sev: #4b6b8a; }
.hazard--watch   { --sev: #b97f13; }
.hazard--warning { --sev: #c2610f; }
.hazard--severe  { --sev: #c7301f; }
.hazard--extreme { --sev: #8c1d10; }

.watch__clear {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-top: 8px;
  font-size: 12px;
  color: var(--ink-2);
}
.watch__clear svg { width: 14px; height: 14px; color: var(--ok); flex: none; }

@media (max-width: 640px) {
  .watch { width: min(360px, calc(100vw - 24px)); }
  .watch__list { max-height: 46vh; }
}

/* The one-line reading of the data above it. Tinted with the signal colour
   because it is the sentence a person came for; kept to one line because a
   verdict that needs a paragraph is not a verdict. */
.verdict {
  display: flex;
  gap: 11px;
  align-items: flex-start;
  padding: 11px 18px 13px;
  border-top: 1px solid var(--line);
  background: linear-gradient(180deg, var(--parcel-soft), rgba(232, 240, 254, 0.45));
}
.verdict__icon { font-size: 17px; line-height: 1.35; flex: none; }
.verdict__text { font-size: 14px; line-height: 1.5; color: #12356f; letter-spacing: -0.006em; }
.verdict__label { font-weight: 640; color: #14458f; }
.verdict__caveat {
  margin-top: 5px;
  font-size: 12.5px;
  line-height: 1.5;
  color: #2f4b7a;
}

/* The device's own position, offered inside the picker rather than asked for
   on load: a browser prompt nobody invited is a prompt people say no to. */
.picker__here {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  margin-top: 8px;
  padding: 9px 10px;
  border: 0;
  border-top: 1px solid var(--line);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  background: none;
  font-size: 13px;
  color: var(--parcel);
  text-align: left;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease);
}
.picker__here:hover { background: var(--parcel-soft); }
.picker__here svg { width: 15px; height: 15px; flex: none; }

.note {
  display: flex;
  gap: 9px;
  padding: 10px 18px 12px;
  border-top: 1px solid var(--line);
  background: var(--warn-soft);
  font-size: 12.5px;
  line-height: 1.5;
  color: #6d4700;
}
.note svg { width: 15px; height: 15px; flex: none; margin-top: 2px; }

/* Ambiguity, answered in the column instead of in a modal. */
.disambig { padding: 12px 18px 14px; border-top: 1px solid var(--line); background: var(--surface-quiet); }
.disambig__title { font-size: 12.5px; font-weight: 600; color: var(--ink-2); margin-bottom: 9px; }
.disambig__list { display: flex; flex-wrap: wrap; gap: 8px; }

.candidate {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: var(--surface);
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease),
    color var(--dur-1) var(--ease), transform var(--dur-0) var(--ease-out);
}
.candidate svg { width: 14px; height: 14px; flex: none; color: var(--ink-3); }
.candidate:hover { background: var(--parcel-soft); border-color: var(--parcel-soft-line); }
.candidate[aria-current="true"] { border-color: var(--parcel); color: var(--parcel-active); background: var(--parcel-soft); font-weight: 560; }
.candidate[aria-current="true"] svg { color: var(--parcel); }

/* Errors name the problem and the recovery, never the exception. */
.card--error { border-color: rgba(199, 48, 31, 0.3); }
.card--error .card__body { display: flex; gap: 11px; align-items: flex-start; }
.error__icon { width: 18px; height: 18px; color: var(--stop); flex: none; margin-top: 1px; }
.error__text { font-size: 14.5px; letter-spacing: -0.006em; }
.error__hint { margin-top: 4px; font-size: 13px; color: var(--ink-2); }
.error__actions { margin-top: 11px; display: flex; gap: 8px; }

/* Working state: nothing is drawn as if a reading had arrived. */
.working { display: flex; align-items: center; gap: 11px; font-size: 14px; color: var(--ink-2); }
.working__bars { display: flex; align-items: flex-end; gap: 3px; height: 15px; }
.working__bars i { width: 3px; border-radius: 2px; background: var(--parcel); animation: sound 1s var(--ease) infinite; }
.working__bars i:nth-child(1) { height: 7px;  animation-delay: 0ms; }
.working__bars i:nth-child(2) { height: 14px; animation-delay: 120ms; }
.working__bars i:nth-child(3) { height: 10px; animation-delay: 240ms; }
.working__bars i:nth-child(4) { height: 15px; animation-delay: 360ms; }

@keyframes sound {
  0%, 100% { transform: scaleY(0.42); opacity: 0.55; }
  50%      { transform: scaleY(1);    opacity: 1; }
}

.skeleton { display: grid; gap: 8px; }
.skeleton i {
  height: 11px;
  border-radius: 4px;
  background: linear-gradient(90deg, var(--surface-sunk), #e6ebf2, var(--surface-sunk));
  background-size: 220% 100%;
  animation: sweep 1.4s linear infinite;
}
.skeleton i:nth-child(2) { width: 78%; }
.skeleton i:nth-child(3) { width: 54%; }
@keyframes sweep { from { background-position: 120% 0; } to { background-position: -120% 0; } }

@media (prefers-reduced-motion: reduce) {
  .working__bars i, .skeleton i { animation: none; }
  /* The staggered latches keep their fade and lose their travel. */
  .metric, .rows .row, .card__body > *,
  .turn--user, .turn--agent { animation-name: fade; }
  .metric { opacity: 1; }
}

/* Opening state: teaches the column instead of apologising for being empty. */
/* The empty column introduces itself line by line, once per session. This is
   the only place in the file with a delight budget. */
.opening { width: 100%; max-width: var(--column); margin: auto; padding: 46px 32px 12px; }
.opening > *, .opening__fact {
  animation: latch var(--dur-3) var(--ease-out) both;
}
.opening__title  { animation-delay: 40ms; }
.opening__body   { animation-delay: 120ms; }
.opening__facts  { animation: none; }
.opening__fact:nth-child(1) { animation-delay: 200ms; }
.opening__fact:nth-child(2) { animation-delay: 250ms; }
.opening__fact:nth-child(3) { animation-delay: 300ms; }
.opening__title {
  font-size: 30px;
  font-weight: 620;
  letter-spacing: -0.028em;
  line-height: 1.16;
  text-wrap: balance;
  max-width: 26ch;
}
.opening__body { margin-top: 12px; font-size: 14.5px; color: var(--ink-2); max-width: 62ch; letter-spacing: -0.004em; }
.opening__facts { margin-top: 24px; display: flex; flex-wrap: wrap; gap: 10px 26px; }
.opening__fact { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--ink-2); }
.opening__fact svg { width: 15px; height: 15px; flex: none; color: var(--parcel); }

/* -------------------------------------------------------------- composer */

.dock {
  position: relative;
  z-index: 20;
  border-top: 1px solid var(--line);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.9));
  backdrop-filter: saturate(1.3) blur(14px);
  -webkit-backdrop-filter: saturate(1.3) blur(14px);
}

.dock__inner { max-width: var(--column); margin-inline: auto; padding: 14px 32px 16px; }

.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 11px; }

.chip {
  padding: 6px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.7);
  font-size: 12.5px;
  color: var(--ink-2);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease),
    color var(--dur-1) var(--ease), transform var(--dur-0) var(--ease-out);
}
.chip:hover:not(:disabled) { background: #fff; border-color: var(--parcel); color: var(--parcel); }
.chip:disabled { opacity: 0.45; cursor: not-allowed; }

.composer {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  padding: 7px 7px 7px 16px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-raise);
  transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-2) var(--ease-out),
    transform var(--dur-2) var(--ease-out);
}
/* Focus lifts the composer off the dock by a hair — the whole surface answers,
   not just its border. */
.composer:focus-within {
  border-color: var(--parcel);
  box-shadow: 0 0 0 3px var(--parcel-soft), var(--shadow-float);
  transform: translateY(-1px);
}

.composer__input {
  flex: 1;
  min-width: 0;
  min-height: 28px;
  max-height: 132px;
  padding: 9px 0;
  border: 0;
  background: none;
  resize: none;
  font-size: 15px;
  line-height: 1.5;
  letter-spacing: -0.008em;
  caret-color: var(--parcel);
  overflow-y: auto;
}
.composer__input:focus { outline: none; }
.composer__input::placeholder { color: var(--ink-3); }
.composer__input:disabled { background: none; color: var(--ink-3); }

.icon-btn {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  flex: none;
  border: 1px solid transparent;
  border-radius: 10px;
  background: none;
  color: var(--ink-2);
  cursor: pointer;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease),
    transform var(--dur-0) var(--ease-out);
}
.icon-btn svg { width: 19px; height: 19px; }
.icon-btn:hover:not(:disabled) { background: var(--surface-sunk); color: var(--ink); }
.icon-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.icon-btn--send { background: var(--parcel); color: #fff; }
.icon-btn--send:hover:not(:disabled) { background: var(--parcel-hover); color: #fff; }
.icon-btn--send:disabled { background: var(--surface-sunk); color: var(--ink-3); opacity: 1; }
.icon-btn[data-listening="true"] { background: var(--stop-soft); color: var(--stop); }

.voicebar { display: flex; align-items: center; gap: 10px; margin-top: 11px; flex-wrap: wrap; }
.voicebar__group { display: flex; align-items: center; gap: 8px; }
.voicebar .btn { height: 38px; min-width: 106px; }

/* The speaking speed sits with the controls it governs, at their height. It is
   a readout that opens something — a pill — not a fourth action competing with
   Speak. */
.voicebar .pill { height: 38px; flex: 0 0 auto; max-width: none; }
.pill--rate { padding-inline: 10px; gap: 6px; }
.pill--rate .pill__text { font-variant-numeric: tabular-nums; min-width: 34px; }
.pill--rate > svg:first-child { color: var(--ink-3); }
.menu__option--rate .menu__label { font-variant-numeric: tabular-nums; }

#speak[data-listening="true"] {
  background: var(--stop);
  box-shadow: 0 1px 2px rgba(120, 24, 12, 0.3), 0 8px 18px -10px rgba(199, 48, 31, 0.8);
}
#speak[data-listening="true"]:hover { background: var(--stop-hover); }

.pulse { position: relative; }
.pulse::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: inherit;
  border: 2px solid var(--stop);
  opacity: 0;
  animation: pulse 1.6s var(--ease) infinite;
}
@keyframes pulse {
  0%   { opacity: 0.55; transform: scale(1); }
  70%  { opacity: 0;    transform: scale(1.09); }
  100% { opacity: 0;    transform: scale(1.09); }
}
@media (prefers-reduced-motion: reduce) {
  .pulse::after { animation: none; opacity: 0.55; }
  /* Hover lift and press scale are position changes; the colour change stays. */
  .btn, .pill, .chip, .candidate, .icon-btn, .composer:focus-within { transform: none !important; }
}

.status {
  flex: 1;
  min-width: 190px;
  min-height: 20px;
  font-size: 12.5px;
  color: var(--ink-2);
  letter-spacing: -0.004em;
}
.status[data-tone="error"] { color: var(--stop); }
.status[data-tone="warn"] { color: var(--warn); }
.status[data-tone="ok"] { color: var(--ok); }

/* -------------------------------------------------------------- ignition */

/* The one time the instrument announces itself: the wordmark stands in the
   middle of the column, a hairline is ruled under it, and then the card
   ascends as the application rises into the space it leaves.
 *
 * The timeline is the stylesheet's, not the script's. This plays at the exact
 * moment the client is busiest booting, and a CSS animation runs off the main
 * thread while a script-driven one drops frames — and it clears itself even if
 * app.js never runs, so a broken script can never leave the page behind a
 * curtain. The script adds one thing CSS cannot: handing the mark to its seat
 * in the masthead by measurement rather than by guess.
 *
 * Timings are shared with IGNITION in app.js. Move one, move both. */
.ignition {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  padding: 24px;
  /* It never takes a click; the skip listener is on the document. */
  pointer-events: none;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.97));
  backdrop-filter: saturate(1.1) blur(2px);
  -webkit-backdrop-filter: saturate(1.1) blur(2px);
  animation: ignition-clear 320ms var(--ease-out) 1000ms both;
}

.ignition__stack { display: grid; justify-items: center; gap: 20px; }

.ignition__mark {
  width: 64px;
  height: 64px;
  border-radius: 19px;
  display: grid;
  place-items: center;
  color: #fff;
  background: linear-gradient(160deg, #2f7bff, #1145b4 72%);
  box-shadow: 0 2px 6px rgba(11, 45, 110, 0.3), 0 18px 40px -14px rgba(17, 69, 180, 0.8);
  /* Two animations: the arrival, then a fast exit at the handoff, so the mark
     is never on screen twice while the masthead's copy flies into place. */
  animation:
    ignition-mark 620ms var(--ease-out) both,
    ignition-mark-out 150ms linear 1000ms both;
}
.ignition__mark svg { width: 38px; height: 38px; }

/* Set as letters so the title assembles rather than appears. Tracking is
   trailing on the last letter, so the word is nudged back onto centre. */
.ignition__word {
  display: flex;
  margin-right: -0.16em;
  font-size: 46px;
  font-weight: 640;
  line-height: 1;
  letter-spacing: 0.16em;
  color: var(--ink);
}
.ignition__word span {
  animation: ignition-letter 520ms var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 34ms + 130ms);
}

/* The hairline the column is measured against, drawn under the title. */
.ignition__rule {
  width: 232px;
  height: 1px;
  background: var(--line-strong);
  animation: ignition-rule 620ms var(--ease-out) 300ms both;
}

@keyframes ignition-mark {
  from { opacity: 0; transform: translateY(10px) scale(0.86); }
  to   { opacity: 1; transform: none; }
}

@keyframes ignition-mark-out {
  to { opacity: 0; }
}

@keyframes ignition-letter {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

@keyframes ignition-rule {
  from { opacity: 0; transform: scaleX(0); }
  to   { opacity: 1; transform: scaleX(1); }
}

@keyframes ignition-clear {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(-26px) scale(1.03); }
}

/* The application rises into the column as the card leaves it — the two are
   one movement, which is why they overlap rather than queue. */
.app { animation: ignition-reveal 520ms var(--ease-out) 960ms both; }

@keyframes ignition-reveal {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

/* Any deliberate input ends it. An introduction that cannot be interrupted is
   an obstacle. */
:root[data-ignited="true"] .ignition { animation: fade 140ms linear reverse both; }
:root[data-ignited="true"] .app { animation: none; }

@media (max-width: 760px) {
  .ignition__mark { width: 54px; height: 54px; border-radius: 16px; }
  .ignition__mark svg { width: 32px; height: 32px; }
  .ignition__word { font-size: 30px; letter-spacing: 0.13em; margin-right: -0.13em; }
  .ignition__rule { width: 168px; }
}

@media (max-width: 430px) {
  .ignition__word { font-size: 23px; letter-spacing: 0.1em; margin-right: -0.1em; }
  .ignition__rule { width: 132px; }
}

/* Reduced motion keeps the announcement and drops the choreography: the card
   is shown and crossfaded out, and nothing travels. */
@media (prefers-reduced-motion: reduce) {
  .ignition { animation: fade 220ms linear 420ms reverse both; }
  .ignition__mark, .ignition__word span, .ignition__rule {
    animation: fade 160ms linear both;
  }
  .app { animation: fade 240ms linear 420ms both; }
}

/* ------------------------------------------------------------ responsive */

@media (max-width: 1120px) {
  .shell, .thread, .dock__inner, .opening { padding-inline: 24px; }
}

@media (max-width: 900px) {
  .brand__tagline { display: none; }
  .masthead__inner { padding-block: 14px; }
}

@media (max-width: 760px) {
  .shell, .thread, .dock__inner, .opening { padding-inline: 16px; }

  .masthead__inner { flex-wrap: wrap; gap: 12px; padding-block: 12px; }
  .brand__mark { width: 34px; height: 34px; border-radius: 10px; }
  .brand__name { font-size: 19px; }
  .masthead__tools { width: 100%; margin-left: 0; gap: 8px; }
  /* The anchors are the flex children, so they are what has to be allowed to
     shrink; sizing the pills inside them does nothing on its own. */
  .masthead__tools .anchor { flex: 1 1 auto; min-width: 0; }
  .pill { flex: 1 1 auto; max-width: none; min-width: 0; height: 44px; }
  .pill--lang { flex: 0 0 auto; }
  .masthead__tools .anchor:has(.pill--lang),
  .masthead__tools .anchor:has(.pill--alerts) { flex: 0 0 auto; }

  /* Three labelled pills do not fit a phone. Alerts keeps its icon and its
     count — the two things worth seeing at a glance — and its name lives in
     aria-label rather than disappearing with the text. */
  .pill--alerts { padding-inline: 12px; gap: 6px; }
  .pill--alerts .pill__text,
  .pill--alerts .pill__chevron { display: none; }

  .popover { right: auto; left: 0; transform-origin: top left; }
  .picker { width: min(calc(100vw - 32px), 384px); }
  /* The alerts anchor is the last item in the row, so its panel hangs from the
     right edge. Left-aligning it here — as every other popover is — would push
     it off the side of a phone. */
  .watch {
    right: 0;
    left: auto;
    transform-origin: top right;
    width: min(calc(100vw - 32px), 380px);
  }

  .thread { padding-top: 22px; gap: 18px; }
  .opening { padding-top: 30px; }
  .opening__title { font-size: 25px; }
  .bubble--user { max-width: 88%; }
  .agent { max-width: 100%; }

  .reading__value { font-size: 34px; }
  .metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .row { grid-template-columns: 104px 18px minmax(0, 1fr); gap: 8px; row-gap: 2px; font-size: 13px; }
  .row__figures { grid-column: 1 / -1; padding-left: 104px; }
  .row__fig { min-width: 72px; }

  .chips {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    margin-inline: -16px;
    padding-inline: 16px;
  }
  .chips::-webkit-scrollbar { display: none; }
  .chip { flex: none; padding-block: 8px; }

  .voicebar__group { flex: 1; }
  .voicebar .btn { flex: 1; min-width: 0; height: 44px; padding-inline: 10px; }
  .status { order: 3; width: 100%; flex: none; }
  .icon-btn { width: 42px; height: 42px; }
}

@media (max-width: 430px) {
  .voicebar { gap: 8px; }
  .voicebar__group { gap: 6px; }
  .voicebar .btn { min-width: 0; padding-inline: 8px; gap: 6px; font-size: 13px; }
  .voicebar .btn svg { width: 16px; height: 16px; }
  .reading__value { font-size: 31px; }
  .metric__value { font-size: 14.5px; }
  .verdict { padding-inline: 14px; }
  .composer { padding-left: 13px; }
}

/* Below this the three labels genuinely cannot sit side by side; the buttons
   keep their accessible names. */
@media (max-width: 340px) {
  .voicebar .btn .btn__label { display: none; }
  .voicebar .btn { min-width: 44px; }
}

/* Short viewports keep the column, not the furniture. */
@media (max-height: 700px) {
  .opening { padding-top: 26px; }
  .opening__facts { display: none; }
}
