/* Copyright (c) 2026 Goldeneye Technologies LLC. All rights reserved. */
/**************************************************************************/
/********       Content classes: Info Panel     ***************************/
/**************************************************************************/
/* Scoped float rule: preserves the inline percentage-width grid inside
   the info-panel cards (symbol_info.html / symloc_content.html) without
   restructuring their HTML. Replaces the project-wide `div { float: left }`
   rule that was retired with the visual-modernization chunk; Q4 keeps
   card HTML out of scope. */
#infoPanel div {float: left;}
/* ...which makes each symbol CARD (`#SYMBOL`, a bare div appended to
   `#info_content` by symbol_card.js) a float too — and `.info-content` is a
   plain block, not a flex container, so nothing makes that rule inert there.
   A float with `width: auto` is SHRINK-TO-FIT, so the card is only as wide as
   its widest visible row.

   Expanded that is invisible: the company name + the 7-control action row are
   wider than the panel, so the float clamps to the full column. COLLAPSED it is
   not — `info_collapsed_layout` hides every row except the ticker and the
   price/delta pair, so the card shrink-wraps to ~100px inside a ~187px rail.
   The cards then sit wherever the float algorithm puts them (left of their line,
   or beside the previous card when two happen to fit), which is why the ticker
   and price read as centred on some cards and right-shifted on others — the
   placement is CONTENT-dependent. The two `justify-content: center` rules in the
   collapse block were centring correctly all along; they were centring inside a
   shrink-wrapped card, not inside the rail.

   Pin the wrapper to the full column width so there is only ever one card per
   line and `justify-content: center` centres in the rail. `float: left` is left
   in place deliberately: at 100% width a float fills its line and the next one
   wraps below it, so the stacking, the scroll height of `.info-content` and the
   sticky `.collapsible` headers all behave exactly as before — this changes the
   WIDTH only. <JAS 2026-08-21>

   NOTE: this is not a P20/P16 regression. It has been latent since the collapse
   rail shipped; nothing in those prompts touched it. <JAS 2026-08-21> */
#info_content > div {
    width: 100%;
    box-sizing: border-box;
}

.info {
    height: 100%;
    width: 25%;
    float: left;
    /* Flex column: .info-header is an intrinsic-height row and .info-content
       takes the slack (flex: 1) so the card list scrolls in the remaining
       space. (Originally introduced to pin a bottom ad slot; the ad inventory
       was removed on 2026-07-25 — S6 — but the flex column is still the right
       shape for the header + scrolling list.) The panel-wide
       `#infoPanel div { float: left }` rule is inert on these direct children
       because flex items ignore float. <JAS 2026-05-26, revised 2026-07-25> */
    display: flex;
    flex-direction: column;
    background-color: var(--color-bg-elevated);
    border-left: 1px solid var(--color-border);
    /* border-left adds 1px to width under default content-box, pushing
       width+map past 100% and floating .info below .map. <JAS 2026-05-22> */
    box-sizing: border-box;
    overflow: hidden;
    transition: width var(--transition-base);
}

/* Collapsed state — `#content_main.info_collapsed_layout` is toggled by
   `info_panel/toggle.js` when `#info_button` is clicked. Simple
   click-to-toggle: the panel shrinks in place (still floated next to
   .map) and `.map` grows to 90%. No overlay, no hover-expand.

   Wrapped in `@media (min-width: 769px)` so it only fires on desktop: on
   mobile the panel is a sheet (the mobile @media block
   below) and the hamburger that toggles this class is CSS-hidden, so the
   `!important` collapse widths must never win against the sheet snap
   classes. <JAS 2026-06-22> */
@media (min-width: 769px) {
#content_main.info_collapsed_layout .info {
    width: 10% !important;
}
/* Hide the header's right cluster (sort + 5 action buttons) so the
   hamburger toggle can fill the strip. */
#content_main.info_collapsed_layout .info-header-right {
    display: none;
}
#content_main.info_collapsed_layout .info-header-left {
    width: 100%;
}
/* Shared delayed-quote disclosure typography (info panel + chart header, both
   map-page surfaces so this rule reaches them). Decoration is per-surface: the
   panel line adds a border/background below; the chart span stays inline.
   <JAS 2026-07-01> */
.quote-delay-note {
    font-size: var(--font-size-xs);
    line-height: var(--line-height-snug, 1.3);
    color: var(--color-text-muted);
}
/* Panel disclosure line under the header. Intrinsic-height row; the flex
   `.info-content` (flex: 1) absorbs the slack below it. `[hidden]` (set by
   info_panel/update.js when no delay is in effect) keeps it out of flow. */
#quote_delay_note {
    margin: 0;
    padding: var(--space-1) var(--space-3);
    background-color: var(--color-bg-elevated);
    border-bottom: 1px solid var(--color-border);
}
/* Collapsed rail is too narrow for the disclosure text; the note lives on
   methodology + the expanded panel. */
#content_main.info_collapsed_layout .quote-delay-note {
    display: none;
}
/* Inside each symbol card: keep SYMBOL_symbol, SYMBOL_price0,
   SYMBOL_delta0 visible; hide the rest (actions, company, status,
   reference row, and the percent change). `.content` (symloc list) is
   force-collapsed via !important to beat the inline `style.maxHeight`
   written by `collapsible_info` — the user's stored expand/collapse
   choice survives on the inline style and applies again when the panel
   is reopened. */
#content_main.info_collapsed_layout .info_actions,
#content_main.info_collapsed_layout .info_card_company,
#content_main.info_collapsed_layout .info_card_status,
#content_main.info_collapsed_layout .info_details_row_extras,
#content_main.info_collapsed_layout [id$="_percent0"] {
    display: none;
}
#content_main.info_collapsed_layout .content {
    max-height: 0 !important;
    /* `visibility: hidden` propagates to descendants and removes them
       from the keyboard tab order — without this, the SYMLOC buttons
       inside each collapsed .content stay focusable but clipped
       invisible, and Tab walks land on phantom focus rings while the
       panel is collapsed. The per-card individual collapse path uses
       the `inert` attribute (set in symbol_card.js) for the same
       effect when one card is collapsed but the panel is open. */
    visibility: hidden;
}
/* Center the remaining ticker + price/delta in the narrow collapsed
   strip. The card body is already a flex column (see `.collapsible`
   below), so hiding the other rows leaves the ticker, then the
   price/delta pair, stacked top-to-bottom; just re-center them. */
#content_main.info_collapsed_layout .collapsible {
    padding: var(--space-2) 0;
}
#content_main.info_collapsed_layout .info_card_header {
    justify-content: center;
}
#content_main.info_collapsed_layout .info_card_quote {
    justify-content: center;
    flex-wrap: wrap;
}
} /* end @media (min-width: 769px) — desktop info-panel collapse */

.info_panel {
  position: relative;
  left: 2.5%;
  width: 2.5%;
  height: 100%;
  z-index: 10;
  overflow: auto;
  background-color: var(--color-bg-elevated);
  transition: 0.5s;
}

.info_panel_open {
    left: 10%;
    width: 25%;
}

/* ----------------------------------------- */
/* Info-panel header (lifted from inline `style="..."` in info_panel.html
   per Q3). Inline percentage widths migrate to these classes. */
/* ----------------------------------------- */
.info-header {
  width: 100%;
  height: 5%;
  background-color: var(--color-bg-elevated);
  border-bottom: 1px solid var(--color-border);
  /* Positioning context for #sort-dropdown: anchoring the popup to the full
     header (panel) width — rather than to the narrow #sort-content it nests in
     — lets it open from the panel's left edge with the whole panel width
     available, so its fixed-pixel (longest-label) box can't overflow the
     panel's right edge into `.info { overflow: hidden }` on narrow windows.
     <JAS 2026-06-12> */
  position: relative;
}
.info-header-left {
  width: 25%;
  height: 100%;
}
.info-header-left #info_button {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  width: 100%;
  /* Button reset (was an <a> before the a11y pass; mirrors the
     .info-header-actions > button reset). */
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font: inherit;
  font-size: 28px;
  color: var(--color-text);
  text-decoration: none;
  transition: background-color var(--transition-base);
}
.info-header-left #info_button:hover {
  background-color: var(--color-bg-hover);
}
.info-header-right {
  width: 75%;
  height: 100%;
}
.info-header-sort {
  /* No `position: relative` here on purpose — #sort-dropdown anchors to the
     wider `.info-header` (see there) so the popup gets the full panel width. */
  height: 100%;
  width: 50%;
}
.info-header-actions {
  height: 100%;
  width: 50%;
  /* Flex so the action buttons split the row evenly regardless of count
     (was fixed width:25% per button, which capped the row at 4). <JAS 2026-05-31> */
  display: flex;
}

#sort-button {
  height: 100%;
  width: 75%;
  font: inherit;
  font-size: var(--font-size-sm);
  color: var(--color-text);
  background-color: var(--color-bg-elevated);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background-color var(--transition-base), border-color var(--transition-base);
}
#sort-button:hover {
  background-color: var(--color-bg-hover);
  border-color: var(--color-accent);
}

/* Sort dropdown (lifted from base.html inline `<style>` per Q6).
   Drops the `aquamarine` placeholder; gains border + shadow + hover. */
#sort-dropdown {
  position: absolute;
  /* Anchored to `.info-header` (panel-width positioning context): open just
     below the header from the panel's left edge. Keeps the box inside the
     panel on narrow windows so its right edge + focus ring aren't clipped by
     `.info { overflow: hidden }`. <JAS 2026-06-12> */
  top: 100%;
  left: 0;
  z-index: 10;
  display: none;
  /* Hug the longest option label (+ row padding) on every panel width. The old
     `min-width: 100%` floored the box to the full sort container, so on wider
     panels the menu was wider than its longest label and the full-row hover +
     focus highlights painted dead space to the right of the text. `max-content`
     sizes the box to its content; full-width rows then fill it exactly, keeping
     full-row clicking with no dead space. <JAS 2026-06-10> */
  width: max-content;
  background-color: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  padding: var(--space-1) 0;
  font-size: var(--font-size-sm);
}
#sort-content.sort_open #sort-dropdown {display: block;}
/* Hover-open is for true-hover (mouse) devices only. On a touch device `:hover`
   STICKS on the tapped element, so this rule would keep the dropdown displayed
   even after the JS toggle removed `.sort_open` — a second tap on the sort
   button couldn't close it (bug observed on phone 2026-06-22). Gating it behind
   `@media (hover: hover)` keeps the mouse hover-open intent on desktop while
   letting the `.sort_open` JS state machine drive open/close on touch.
   <JAS 2026-06-22> */
@media (hover: hover) {
  #sort-content:hover #sort-dropdown {display: block;}
}
#sort-dropdown > div[role="button"] {
  /* Full-row rows so the whole row is a click target and the highlight fills
     the (tight) menu box. The box itself hugs the longest option via the
     parent's `width: max-content`, so there's no dead space to the right.
     <JAS 2026-06-10> */
  width: 100%;
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
  color: var(--color-text);
  white-space: nowrap;
  transition: background-color var(--transition-base);
}
#sort-dropdown > div[role="button"]:hover {
  background-color: var(--color-bg-hover);
}
/* Info-panel-wide inset focus ring. The `.info` container uses
   `overflow: hidden` (clips outlines that paint past the panel edge),
   and the header / card buttons sit edge-to-edge so the global +2px
   outline-offset overlaps neighboring buttons and effectively hides
   the ring on the hamburger and any button adjacent to another. The
   inset variant paints inside each button's own bounds, so it stays
   visible regardless of clip or neighbor overlap. Also covers the
   sort-dropdown entries (zero-h-padding container — Delta CSS-A). */
#infoPanel :focus-visible {
  outline-offset: -3px;
}

/* Info-panel header icon buttons (the five chart / filter / collapse /
   corners / trash buttons). Cohesive icon-button styling with hover and
   disabled states. The disabled-state semantics come from the
   2026-05-18 lock+visibility chunk (Q10 (b) / Q11 (a)). */
.info-header-actions > button {
  height: 100%;
  flex: 1 1 0;
  min-width: 0;
  background-color: transparent;
  border: 0;
  border-left: 1px solid var(--color-border);
  padding: 4px;
  cursor: pointer;
  color: var(--color-text-muted);
  transition: background-color var(--transition-base), color var(--transition-base);
}
.info-header-actions > button:first-child {border-left: 0;}
.info-header-actions > button:hover:not(:disabled) {
  background-color: var(--color-bg-hover);
  color: var(--color-text);
}
.info-header-actions > button:disabled {
  cursor: not-allowed;
  opacity: 0.4;
}
.info-header-actions > button.active {
  background-color: var(--color-bg-hover);
  color: var(--color-accent);
}

/* Info-panel content scroll region (formerly the inline
   style="width:100%; height:95%; overflow: scroll;" on #info_content). */
.info-content {
  width: 100%;
  /* Flex item: take the vertical slack between the header and the optional
     bottom ad slot. `min-height: 0` lets it shrink below its content height so
     `overflow: auto` actually scrolls inside the flex column (replaces the old
     fixed `height: 95%`). <JAS 2026-05-26> */
  flex: 1 1 auto;
  min-height: 0;
  /* border-box so the `padding-bottom` set dynamically by
     `scroll_to_location` (the scroll-to-top whitespace) is reserved INSIDE
     the flex-sized height — creating real scroll range. Under the default
     content-box, padding-bottom instead grows the element, increasing
     clientHeight in lockstep with scrollHeight, so the scrollable range
     (scrollHeight − clientHeight) never changes and the bottom cards still
     can't reach the top. <JAS 2026-05-26> */
  box-sizing: border-box;
  overflow: auto;
  /* Auto-hiding scrollbar (mirrors the side nav's treatment). Thumb is
     transparent by default and reveals only on hover or while actively
     scrolling (the `is-scrolling` class is toggled by
     nav/toggle.js::init_infopanel_scroll_reveal). Unlike the dark nav, the
     panel is a LIGHT surface, so the thumb is a DARK translucent pill — a light
     thumb would be near-invisible on white. <JAS 2026-06-22> */
  scrollbar-width: thin;                       /* Firefox */
  scrollbar-color: transparent transparent;    /* thumb track — hidden by default */
}
.info-content::-webkit-scrollbar {             /* Chromium / Edge / WebKit */
  width: 8px;
}
.info-content::-webkit-scrollbar-track {
  background: transparent;
}
.info-content::-webkit-scrollbar-thumb {
  background-color: transparent;
  border-radius: var(--radius-lg);
  /* Transparent border + padding-box clip insets the thumb to a thin pill. */
  border: 2px solid transparent;
  background-clip: padding-box;
  transition: background-color var(--transition-base);
}
.info-content:hover::-webkit-scrollbar-thumb,
.info-content.is-scrolling::-webkit-scrollbar-thumb {
  background-color: rgba(60, 60, 60, 0.4);
}
.info-content:hover::-webkit-scrollbar-thumb:hover {
  background-color: rgba(60, 60, 60, 0.6);
}
.info-content:hover,
.info-content.is-scrolling {
  scrollbar-color: rgba(60, 60, 60, 0.4) transparent;  /* Firefox reveal */
}

/* ----------------------------------------- */
/* From https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_collapsible_animate */
/* ----------------------------------------- */
.collapsible {
  /* Content-sized card header: a vertical flex stack (ticker+actions row,
     company, quote, status, reference row) whose height flows with its
     content. Replaces the old fixed `height: 15%`, which combined with the
     inner percentage-height rows to overlap content at narrow widths. As a
     flex container its direct children are flex items, so the panel-wide
     `#infoPanel div { float: left }` rule is inert on them. <JAS 2026-05-26> */
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  background-color: var(--color-bg-elevated);
  color: var(--color-text);
  cursor: pointer;
  height: auto;
  width: 100%;
  padding: var(--space-2) var(--space-3);
  box-sizing: border-box;
  border: none;
  border-bottom: 1px solid var(--color-border);
  text-align: left;
  font-size: var(--font-size-base);
  position: sticky;
  top: 0;
  transition: background-color var(--transition-base);
}
/* Suppress the default outline only for mouse focus; :focus-visible
   keyboard focus paints the canonical outline from global.css. */
.collapsible:focus:not(:focus-visible) {
  outline: none;
}
/* For displaying buttons on hover
.collapsible:hover button {
    display: none;
}
*/
.active, .collapsible:hover {
  background-color: var(--color-bg-hover);
}

.content {
  width: 100%;
  /* padding: 0 18px; */
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.2s ease-out;
  background-color: var(--color-bg);
}
.content > ul {
    list-style: none;
}
il:hover {
    list-style: circle;
}
/* ----------------------------------------- */
/* Symbol card body — flex-stacked rows inside `.collapsible` (the card
   redesign, 2026-05-26). Every row is a direct flex child of `.collapsible`,
   so the panel-wide `#infoPanel div { float: left }` rule does not apply to
   them; `float: none` is added belt-and-suspenders. Sizing is intrinsic (no
   percentage heights) and fonts come from tokens (replacing the old inline
   12/14px) so content can never overlap. */
.info_card_header,
.info_card_company,
.info_card_quote,
.info_card_status,
.info_details_row_extras {
    float: none;
    width: 100%;
    box-sizing: border-box;
}
.info_card_header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
}
.info_symbol {
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
}
.info_actions {
    display: flex;
    gap: 2px;
    margin-left: auto;
}
/* The action row is buttons plus ONE link (`SYMBOL_page`, P16 — the card's
   out-link to /ticker/<SYM>). The link is sized and painted by the same rules so
   the row reads as one set; it needs `display: flex` + `box-sizing` of its own
   because an <a> is inline by default (a percentage-height child SVG needs a
   definite block box) and there is no site-wide border-box reset.
   <JAS 2026-08-19> */
.info_actions > button,
.info_actions > a {
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    background-color: transparent;
    border: 0;
    border-radius: var(--radius-sm);
    color: var(--color-text-muted);
    cursor: pointer;
    padding: 4px;
    transition: background-color var(--transition-base), color var(--transition-base);
}
.info_actions > a {
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: center;
    text-decoration: none;
}
.info_actions > button:hover:not(:disabled),
.info_actions > a:hover {
    background-color: var(--color-bg-hover);
    color: var(--color-text);
}
.info_actions > button:disabled {
    cursor: not-allowed;
    opacity: 0.4;
}
.info_card_company {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}
.info_card_quote {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2);
}
.info_card_quote > [id$="_price0"] {
    font-size: var(--font-size-xl);
    font-weight: var(--font-weight-semibold);
}
.info_card_quote > [id$="_delta0"],
.info_card_quote > [id$="_percent0"] {
    font-size: var(--font-size-sm);
}
.info_card_status {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
}
.info_details_row_extras {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
}
.info_details_row_extras > div {
    float: none;
}

/* ----------------------------------------- */
/* SYMLOC row — one location inside a symbol card's `.content`. `.content`
   is a plain block (not flex), so its `.symloc_row` children inherit the
   panel-wide float; `float: none` keeps them stacked vertically. */
.symloc_row {
    float: none;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    width: 100%;
    padding: var(--space-2) var(--space-3);
    box-sizing: border-box;
    font-size: var(--font-size-sm);
    border-bottom: 1px solid var(--color-border);
}
/* Two-column label/value row so multi-line addresses align: every `<br>`
   continuation line sits in the value column under the first line, not
   under the "Address:" label. <JAS 2026-05-26> */
.symloc_field {
    display: flex;
    gap: var(--space-2);
    color: var(--color-text);
}
.symloc_label {
    flex: 0 0 auto;
    min-width: 4.5em;
    white-space: nowrap;
    color: var(--color-text-muted);
}
.symloc_value {
    flex: 1 1 auto;
    min-width: 0;
}
/* The `.ts-badge*` rules themselves are site-wide (global.css) — the info panel
   and the server-rendered ticker page render the same badge. Only the panel's
   own row wrapper is scoped here. */
.symloc_badge_row {
    align-items: center;
}

.symloc_actions {
    display: flex;
    gap: 2px;
}
.symloc_actions > button {
    flex: 1;
    height: 26px;
    background-color: transparent;
    border: 0;
    border-radius: var(--radius-sm);
    color: var(--color-text-muted);
    cursor: pointer;
    padding: 4px;
    transition: background-color var(--transition-base), color var(--transition-base);
}
.symloc_actions > button:hover:not(:disabled) {
    background-color: var(--color-bg-hover);
    color: var(--color-text);
}
.symloc_actions > button:disabled {
    cursor: not-allowed;
    opacity: 0.4;
}

/* Phase-0 HQ-only: hide the inert duplicate controls (the symbol-level
   "get all" asterisk + the per-SYMLOC action row). `body.hq_only` is set
   by main.js from `config.HQ_ONLY`. Markup + JS wiring stay intact for
   Phase-2 multi-location — flip the flag to re-expose them. <JAS 2026-05-26> */
body.hq_only .phase2_only {
    display: none;
}

/* Atlas-only launch (batch-8 A2): hide the per-card price rows (quote /
   status / reference-price-and-open) and the chart button. `body.atlas_only`
   is set by main.js from the FEATURE_PRICE_LAYER window global, exactly as
   `body.hq_only` is set from config.HQ_ONLY above. Hiding the whole ROW is
   what leaves no blank line, no stray "n/a" and no vertical gap; the markup,
   the ids and the uiState wiring stay intact, so FEATURE_PRICE_LAYER=1
   restores the card unchanged. <JAS 2026-09-05> */
body.atlas_only .price_only {
    display: none;
}

/* ...and its exact mirror (batch-8 A7): a row that exists ONLY in atlas mode.
   The sector line is the card's copy of the map's encoding — it belongs on the
   card that has no price rows, and adding it to the price-layer card would be a
   layout change to the desktop 6ba8be2 card for no reason. Written as
   `:not(.atlas_only)` on the body rather than as a second body class, so there
   is still exactly ONE flag driving both directions. <JAS 2026-09-05> */
body:not(.atlas_only) .atlas_only {
    display: none;
}
/* A symbol EDGAR gives no SIC gets no text here, and an empty block generates
   no line box, so the row costs nothing without a content-dependent CSS rule
   (or a JS class) that could desync from what is actually in the element. */
.info_card_sector {
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
}

/* ----------------------------------------- */
/* Bottom-sheet drag handle (#info_sheet_handle, first child of #infoPanel).
   The element ships in the DOM at every viewport; this base rule hides it,
   and the mobile @media block below reveals it on mobile, where
   the info panel becomes a draggable bottom sheet. <JAS 2026-06-22> */
/* ----------------------------------------- */
.info-sheet-handle {
    display: none;
    width: 100%;
    height: 32px;
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--color-border);
    cursor: grab;
    padding: 0;
    position: relative;
    /* Let pointer events drive the drag rather than the browser's native
       touch scroll/zoom gestures. */
    touch-action: none;
    color: var(--color-text-muted);
}
.info-sheet-handle:active {
    cursor: grabbing;
}
.info-sheet-handle-grip {
    display: block;
    width: 40px;
    height: 4px;
    margin: 0 auto;
    background: var(--color-border-strong);
    border-radius: var(--radius-sm);
}
.info-sheet-handle-count {
    display: none;
    position: absolute;
    right: var(--space-3);
    top: 50%;
    transform: translateY(-50%);
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
}

/* ----------------------------------------- */
/* Mobile viewport: the info panel becomes a bottom sheet over the
   full-viewport map. "Mobile" is ≤ 768 CSS px wide OR a landscape phone
   (P21 — see the LANDSCAPE PHONE block at the foot of this file, which
   ALSO matches there and re-anchors the sheet to the right edge). Three snap classes drive its height;
   info_panel/bottom_sheet.js mutates them. The existing card markup
   (.collapsible flex columns, 2026-05-26 redesign) is left untouched —
   this block only re-homes the panel container + header chrome.
   <JAS 2026-06-22> */
/* ----------------------------------------- */
@media (max-width: 768px),
       (orientation: landscape) and (max-height: 500px) and (pointer: coarse) {
    .info {
        position: fixed;
        /* Anchor via `top` + `vh`/`vw` instead of `bottom: 0` + `width: 100%`.
           Chrome DevTools mobile emulation pins position:fixed to the visual
           viewport (the whole DevTools rendering area) instead of the layout
           viewport, so `bottom: 0` + `width: 100%` resolves against the
           DevTools area dimensions and the panel ends up below the visible
           document. `vh` / `vw` are computed from the layout viewport in both
           DevTools and on real devices, so this survives both contexts
           identically. Delta FIXED-A. <JAS 2026-06-22> */
        top: 60vh;
        top: 60dvh;   /* dvh: visible (dynamic) viewport — see the snap note below */
        left: 0;
        width: 100vw;
        float: none;
        border-left: 0;
        border-top: 1px solid var(--color-border);
        box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.12);
        z-index: 1200;
        /* Default to 'mid' until AppController.restoreUserState applies the
           persisted/default snap class. */
        height: 40vh;
        height: 40dvh;
        transition: height var(--transition-base), top var(--transition-base);
        display: flex;
        flex-direction: column;
    }
    /* Each snap class sets BOTH top and height so the panel grows upward from
       the layout-viewport bottom rather than down off the visual-viewport
       bottom (Delta FIXED-A).

       `--map-footer-h` (measured by bottom_sheet.js, default 0) lifts the whole
       sheet up by the slim map-page footer bar's height so the handle/cards sit
       ABOVE the footer instead of behind it (round-3 follow-up). It only shifts
       `top` (the bottom edge anchors at `100dvh - footer`); heights are
       unchanged, so the previously-footer-covered content is now fully visible —
       at mid that means a full symbol card clears the footer. The JS drag math
       anchors `top` to the same `layoutH - footer`. <JAS 2026-06-22> */
    .info_sheet_peek { top: calc(100vh - var(--map-footer-h, 0px) - 56px) !important; height: 56px !important; }
    .info_sheet_mid  { top: calc(60vh - var(--map-footer-h, 0px)) !important;         height: 40vh !important; }
    /* FULL keeps the original 10vh top so the handle stays clear of the top
       search bar (top:8px + 36px); instead trim the HEIGHT by the footer so the
       bottom edge still anchors above it. Lifting full's `top` like peek/mid did
       (round-3 follow-up) pushed the handle up behind the search bar. <JAS 2026-06-22> */
    .info_sheet_full { top: 10vh !important;                                          height: calc(90vh - var(--map-footer-h, 0px)) !important; }
    /* dvh override: on iOS Safari `100vh` includes the area BEHIND the bottom
       toolbar, so the vh-based peek strip renders partly off-screen (hidden
       behind Safari's chrome, overlapping the legend). `dvh` tracks the visible
       (dynamic) viewport and matches the JS drag math (documentElement.
       clientHeight). The vh lines above stay as a fallback for engines without
       dvh support. <JAS 2026-06-22> */
    .info_sheet_peek { top: calc(100dvh - var(--map-footer-h, 0px) - 56px) !important; }
    .info_sheet_mid  { top: calc(60dvh - var(--map-footer-h, 0px)) !important;         height: 40dvh !important; }
    .info_sheet_full { top: 10dvh !important;                                           height: calc(90dvh - var(--map-footer-h, 0px)) !important; }

    /* Reveal the drag handle; hide the desktop hamburger toggle (the sheet's
       snap classes replace the collapse interaction on mobile). `flex: 0 0 32px`
       pins the handle so it can't be squeezed by a tall sibling — without it the
       handle has the flex default (shrink: 1) and collapses to its ~5px grip when
       the panel runs short (the bug observed 2026-06-22). The peek rule below
       (specificity 0,2,0) still grows it to fill the 56px strip. */
    .info-sheet-handle { display: block; flex: 0 0 32px; }
    .info_sheet_peek .info-sheet-handle-count { display: block; }
    .info-header-left #info_button { display: none; }

    /* Fixed-height header strip inside the flex sheet. The desktop rule sizes
       the header to 5% of the panel, which collapses to a few px at the 40vh /
       56px sheet heights — pin it to a comfortable touch height instead. The
       sort dropdown + 5 action buttons stay reachable.

       `height` MUST be a definite 48px (not `auto`): the header buttons' SVGs
       are `<svg style="height:100%">`, and percentage heights only resolve
       against a definite parent. With `height: auto` the chain breaks, the SVGs
       fall back to their large intrinsic size, and the header's default
       `min-height: auto` grows it to fit them — covering the cards (Delta
       SVG-A; bug observed 2026-06-22). The flex-basis alone is not treated as
       definite for the children's % heights, so set both. <JAS 2026-06-22> */
    .info-header {
        flex: 0 0 48px;
        height: 48px;
    }
    /* Hamburger is hidden, so give its column's width to the sort + actions. */
    .info-header-left { width: 0; }
    .info-header-right { width: 100%; }

    /* Sort dropdown inside the short bottom sheet. The dropdown is
       absolutely positioned below the 48px header, and the panel's
       `overflow: hidden` (sheet containment) clips anything that extends past
       the short panel — so at the mid snap only the first couple options
       showed (bug observed on phone 2026-06-22). Cap the dropdown to the room
       below the header within the current snap and let it scroll internally so
       every option is reachable. Snap-aware because the panel height differs by
       snap; peek hides the header (sort unreachable there). vh fallback then
       dvh, mirroring the snap classes. <JAS 2026-06-22> */
    .info_sheet_mid #sort-dropdown,
    .info_sheet_full #sort-dropdown {
        overflow-y: auto;
        /* Pin overflow-x explicitly: a single `overflow-y: auto` leaves
           overflow-x at its `visible` default, which the spec then forces to
           compute to `auto` — adding an unwanted horizontal scrollbar. Setting
           it to `hidden` keeps scrolling vertical-only. <JAS 2026-06-22> */
        overflow-x: hidden;
        -webkit-overflow-scrolling: touch;
    }
    .info_sheet_mid #sort-dropdown {
        max-height: calc(40vh - 56px);
        max-height: calc(40dvh - 56px);
    }
    .info_sheet_full #sort-dropdown {
        max-height: calc(90vh - 56px);
        max-height: calc(90dvh - 56px);
    }

    /* Content area fills the remaining sheet height below the handle + header
       (mirrors the desktop flex-item behaviour; reaffirmed for clarity). */
    .info-content {
        flex: 1 1 auto;
        height: auto;
        min-height: 0;
    }

    /* Peek state: collapse to just the handle strip — hide the header and the
       card list, and let the handle fill the 56px panel so the grip + card
       count read as a tappable bar. */
    .info_sheet_peek .info-header,
    .info_sheet_peek .info-content,
    .info_sheet_peek #quote_delay_note {
        display: none;
    }
    .info_sheet_peek .info-sheet-handle {
        flex: 1 1 auto;
        height: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
    }

    /* While a drag is in progress (`info_sheet_dragging` added by
       bottom_sheet.js on pointerdown, removed on pointerup/cancel) force the
       full-sheet layout regardless of which snap class is still applied. The
       active snap class is intentionally left on during the drag to avoid a
       flash (see bottom_sheet.js _on_pointerdown), but when that class is
       `info_sheet_peek` its rules hide the header/cards and stretch the handle
       to fill the panel — so dragging UP out of peek would balloon the handle
       to the drag height instead of revealing the cards. These three-class
       selectors (specificity 0,3,0) outrank the peek rules (0,2,0). The cards
       reveal as the panel grows; on release set_snap() removes this class and
       the resolved snap class takes over. <JAS 2026-06-22> */
    .info.info_sheet_dragging .info-header { display: block; }
    .info.info_sheet_dragging .info-content { display: block; }
    /* Reveal the delay note while dragging out of peek — but only when it has
       content (`:not([hidden])`), so a no-delay session doesn't flash an empty
       line. */
    .info.info_sheet_dragging #quote_delay_note:not([hidden]) { display: block; }
    .info.info_sheet_dragging .info-sheet-handle {
        flex: 0 0 32px;
        height: 32px;
    }

    /* Touch targets (WCAG 2.5.5; Lane E / Prompt 4). The per-card meta-row
       action buttons are 26x26 on desktop (hover-driven); grow them to 44x44
       for touch. `.info_card_header` is `flex-wrap: wrap`, so on a narrow phone
       the action cluster wraps to its own line under the ticker rather than
       overflowing — no card markup change. The header action buttons are
       already 48px tall (the .info-header rule above), and the hamburger /
       more-tools / data-table buttons meet 44px via their own mobile rules.
       <JAS 2026-06-22> */
    .info_actions > button,
    .info_actions > a {
        width: 44px;
        height: 44px;
        padding: 8px;
    }
    /* Keep the ticker and the action cluster on ONE line (2026-08-21). P16's
       `SYMBOL_page` link made seven 44px controls (SYMBOL_get is .phase2_only,
       hidden in Phase 0): 7*44 + 6*2 gap + the 8px header gap + ~44px of ticker
       = ~372px against ~366px of inner width on a 390pt iPhone (100vw - the
       24px `.collapsible` gutter), so `.info_card_header`'s flex-wrap dropped
       the whole cluster onto a second line — ~50px of vertical space per card,
       in a bottom sheet where that is the scarce axis.

       Two additive trims, and NOT a smaller touch target (44x44 is the WCAG
       2.5.5 floor this whole block exists to hold):
       (a) drop the per-card caret. Tapping the card header already collapses it
           — `#SYMBOL_button` carries `role="button"` and
           header.js::init_info_content_delegation routes suffix 'button' to the
           same `collapsible_info`, so on touch this button is a duplicate of the
           surface behind it. It is HIDDEN, not removed: symbol_card.js
           ::_update_caret still stamps its glyph + aria-expanded, and it also
           mirrors aria-expanded onto `#SYMBOL_button` so the state stays in the
           a11y tree with the caret hidden. Desktop keeps the caret (hover-driven
           26px controls, no width pressure).
       (b) gap 2px -> 0. Each control already carries 8px of internal padding, so
           the glyphs stay visually separated; this is the cushion that keeps a
           5-character ticker on one line on a 360px Android.
       Six controls: 6*44 + 8 + ~53 = ~325px, inside the ~336px a 360px device
       gives. <JAS 2026-08-21> */
    .info_actions {
        gap: 0;
    }
    .info_actions > [id$="_collapse"] {
        display: none;
    }
}


/* ----------------------------------------- */
/* LANDSCAPE PHONE: the info panel is a RIGHT-side sheet, not a bottom sheet
   (P21, 2026-08-21). This block runs AFTER the mobile block above and, on a
   landscape phone, BOTH match - so these rules are the overrides that re-anchor
   what the mobile block just set up. Order is load-bearing: do not move this
   block above it, and do not raise its specificity to compensate.

   WHY A NEW MEDIA QUERY AT ALL. Everything else in this codebase keys off the
   single 768px boundary (tokens.css). But 768px is a WIDTH, and a phone in
   landscape is WIDE - 844x390 on an iPhone 14, 852x393 on a 15 Pro, 956x440 on
   a 16 Pro Max - so before P21 a rotated iPhone matched NO mobile rule and
   rendered the DESKTOP layout: the 75%/25% float split, the hover-expand
   sidenav rail with no hamburger, 26px touch targets, and no sheet at all. (An
   iPhone SE, 667x375 rotated, stayed under 768 and kept the mobile layout,
   which is why the symptom looked device-dependent.) So P21 widened every
   mobile block to a two-branch query, and this block is that second branch on
   its own.

   WHY ORIENTATION/HEIGHT AND NOT WIDTH. The scarce axis in landscape is the
   HEIGHT - that is the entire premise. `max-height: 500px` tests it directly,
   and separates every iPhone in landscape (375-440px tall) from every iPad in
   landscape (744px+) with room on both sides, where the 900px width cap the P21
   prompt sketched already misses the 956px-wide iPhone 16 Pro Max.
   `pointer: coarse` is what keeps DESKTOP UNTOUCHED: without it a mouse user
   who resized their window to 900x450 would get the hamburger and the sheet.
   modules/viewport.js::LANDSCAPE_PHONE_MQ is the JS mirror of this condition.

   THE IDEA IN ONE LINE: the bottom sheet spends the scarce axis - even the 56px
   peek strip is ~14% of a 390px-tall viewport - so anchor it to the abundant
   one instead, where the same 56px costs ~7% of an 844px width and the map
   keeps its FULL height.

   NOT A SECOND STATE MACHINE. The same three snap classes are reused and simply
   reinterpreted as widths, so info_panel/bottom_sheet.js still has one mutation
   site (`set_snap`), one persisted value (`uiState.bottomSheetSnap`, shared
   across orientations on purpose) and one `aria-expanded` writer. Only the drag
   axis branches there. <JAS 2026-08-21> */
/* ----------------------------------------- */
@media (orientation: landscape) and (max-height: 500px) and (pointer: coarse) {
    .info {
        /* Re-anchor: full height at the RIGHT edge. `left: auto` + `right: 0`
           undoes the mobile block's `left: 0`, and right-anchoring is what lets
           the JS drag write a bare `width` and have the panel grow leftward on
           its own - no second coordinate to keep in sync. */
        top: 0;
        left: auto;
        right: 0;
        width: 40vw;
        height: 100vh;
        height: 100dvh;
        /* There is no site-wide border-box reset, so make this panel border-box
           explicitly: the handle is inset via `padding-left` below and must eat
           INTO the declared width rather than add to it, or every snap renders
           32px wider than both the CSS and the JS think it is. */
        box-sizing: border-box;
        padding-left: 32px;
        /* Chrome moves with the anchor: the shadow falls leftward onto the map
           and the hairline sits on the left edge. */
        border-top: 0;
        border-left: 1px solid var(--color-border);
        box-shadow: -4px 0 12px rgba(0, 0, 0, 0.12);
        transition: width var(--transition-base);
    }

    /* Snap classes reinterpreted as WIDTHS. `!important` at the same
       specificity as the mobile block's height rules, and later in the file, so
       these win. Each must also neutralise the height rule it overrides - hence
       the explicit `top` / `height` on all three.

       `full` is 70vw, not the portrait 90vw: at 90vw an 844px-wide phone is
       left with 84px of map, which defeats the point of the whole exercise.
       These three widths are mirrored in bottom_sheet.js::_snap_sizes() -
       change them together. */
    .info_sheet_peek { top: 0 !important; height: 100vh !important; width: 56px !important; }
    .info_sheet_mid  { top: 0 !important; height: 100vh !important; width: 40vw !important; }
    .info_sheet_full { top: 0 !important; height: 100vh !important; width: 70vw !important; }
    /* dvh twin, same reason as the portrait block: on iOS Safari `100vh`
       includes the area behind the browser chrome. */
    .info_sheet_peek { height: 100dvh !important; }
    .info_sheet_mid  { height: 100dvh !important; }
    .info_sheet_full { height: 100dvh !important; }

    /* The handle moves from the panel's top edge to its LEFT edge - the edge
       facing the map, so the drag direction matches the growth direction.

       It is taken OUT of the flex column and pinned absolutely (the panel is
       `position: fixed`, so it is the containing block) rather than made a flex
       row: the header and the card list are siblings that must still stack
       vertically, and turning `.info` into a row would need a wrapper element -
       i.e. a markup change, which the mobile lane's additive-only rule forbids.
       The panel's `padding-left` above reserves the 32px it occupies. */
    .info-sheet-handle {
        position: absolute;
        left: 0;
        top: 0;
        width: 32px;
        height: 100%;
        /* The mobile block pins `flex: 0 0 32px`; out of flow that is inert,
           but reset it so the value can never be read as a width. */
        flex: none;
        border-bottom: 0;
        border-right: 1px solid var(--color-border);
        display: flex;
        align-items: center;
        justify-content: center;
    }
    /* Grip rotates with the handle: a vertical pill, centred by the flexbox
       above rather than by its horizontal `margin: 0 auto`. */
    .info-sheet-handle-grip {
        width: 4px;
        height: 40px;
        margin: 0;
    }
    /* Open-card count moves from the strip's right end to its bottom, which is
       the far end of a vertical strip. */
    .info-sheet-handle-count {
        right: auto;
        top: auto;
        left: 50%;
        bottom: var(--space-3);
        transform: translateX(-50%);
    }

    /* Peek: the whole 56px strip IS the handle, so drop the reserved padding
       and let it span the panel. Mirrors the portrait peek rule, which grows
       the handle to fill the 56px band. */
    .info_sheet_peek {
        padding-left: 0 !important;
    }
    .info_sheet_peek .info-sheet-handle {
        width: 100%;
    }
    /* The portrait peek rule stretches the handle with `flex: 1 1 auto;
       height: 100%`, which is meaningless out of flow - restate the intent for
       the drag state, which reveals the header + cards. */
    .info.info_sheet_dragging .info-sheet-handle {
        flex: none;
        height: 100%;
        width: 32px;
    }
    /* Mid-drag the panel is temporarily between snaps, so the padding must be
       reserved again even while the peek class is still applied (bottom_sheet.js
       leaves it on during the gesture to avoid a flash - see _on_pointerdown).
       Specificity 0,2,0 beats the 0,1,0 peek rule above. */
    .info.info_sheet_dragging {
        padding-left: 32px !important;
    }

    /* The sort dropdown's max-height is snap-derived in portrait (the sheet is
       short there). Here the sheet is always full-height, so the only limit is
       the viewport less the 48px header and the handle band. */
    .info_sheet_mid #sort-dropdown,
    .info_sheet_full #sort-dropdown {
        max-height: calc(100vh - 56px);
        max-height: calc(100dvh - 56px);
    }
}
